update
This commit is contained in:
@@ -387,7 +387,7 @@ const testButtonText = computed(() => {
|
||||
</form> <!-- End Form -->
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex justify-between items-center pt-5 mt-6 border-t border-border flex-shrink-0">
|
||||
<div class="flex justify-between items-center pt-5 mt-6 flex-shrink-0">
|
||||
<div class="flex flex-col items-start gap-1"> <!-- Test Area -->
|
||||
<div class="flex items-center gap-2"> <!-- Button and Icon -->
|
||||
<button type="button" @click="handleTestConnection" :disabled="isLoading || testStatus === 'testing'"
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 bg-overlay flex justify-center items-center z-[1050]" @click.self="closeForm">
|
||||
<div class="bg-background text-foreground p-6 rounded-lg border border-border shadow-xl w-[90%] max-w-lg"> <!-- Changed bg-dialog to bg-background, text-dialog-text to text-foreground -->
|
||||
<h2 class="m-0 mb-6 text-center text-xl font-medium">{{ isEditing ? t('quickCommands.form.titleEdit', '编辑快捷指令') : t('quickCommands.form.titleAdd', '添加快捷指令') }}</h2>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="mb-4">
|
||||
<label for="qc-name" class="block mb-2 font-bold text-text-secondary text-sm">{{ t('quickCommands.form.name', '名称:') }}</label>
|
||||
<div class="bg-background text-foreground p-6 rounded-xl border border-border/50 shadow-2xl w-[90%] max-w-lg">
|
||||
<h2 class="m-0 mb-6 text-center text-xl font-semibold">{{ isEditing ? t('quickCommands.form.titleEdit', '编辑快捷指令') : t('quickCommands.form.titleAdd', '添加快捷指令') }}</h2>
|
||||
<form @submit.prevent="handleSubmit" class="space-y-5">
|
||||
<div>
|
||||
<label for="qc-name" class="block mb-1.5 text-sm font-medium text-text-secondary">{{ t('quickCommands.form.name', '名称:') }}</label>
|
||||
<input
|
||||
id="qc-name"
|
||||
type="text"
|
||||
v-model="formData.name"
|
||||
:placeholder="t('quickCommands.form.namePlaceholder', '可选,用于快速识别')"
|
||||
class="w-full px-3 py-2 border border-border rounded bg-input text-foreground text-base focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors duration-150"
|
||||
class="w-full px-4 py-2 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="qc-command" class="block mb-2 font-bold text-text-secondary text-sm">{{ t('quickCommands.form.command', '指令:') }} <span class="text-error">*</span></label>
|
||||
<div>
|
||||
<label for="qc-command" class="block mb-1.5 text-sm font-medium text-text-secondary">{{ t('quickCommands.form.command', '指令:') }} <span class="text-error">*</span></label>
|
||||
<textarea
|
||||
id="qc-command"
|
||||
v-model="formData.command"
|
||||
required
|
||||
rows="3"
|
||||
rows="4"
|
||||
:placeholder="t('quickCommands.form.commandPlaceholder', '例如:ls -alh /home/user')"
|
||||
class="w-full px-3 py-2 border border-border rounded bg-input text-foreground text-base resize-vertical min-h-[80px] focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors duration-150"
|
||||
class="w-full px-4 py-2 border border-border/50 rounded-lg bg-input text-foreground text-sm resize-y min-h-[100px] shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition duration-150 ease-in-out"
|
||||
></textarea>
|
||||
<small v-if="commandError" class="text-error text-xs mt-1 block">{{ commandError }}</small>
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 pt-2 border-t border-border">
|
||||
<button type="button" @click="closeForm" class="py-2 px-4 rounded text-sm transition-colors duration-150 bg-button text-button-text hover:bg-button-hover border border-border mr-2">{{ t('common.cancel', '取消') }}</button>
|
||||
<button type="submit" :disabled="isSubmitting || !!commandError" class="py-2 px-4 rounded text-sm transition-colors duration-150 bg-primary text-white hover:bg-primary-dark disabled:bg-gray-400 disabled:opacity-70 disabled:cursor-not-allowed">
|
||||
<div class="flex justify-end mt-8 pt-4 border-t border-border/50">
|
||||
<!-- Secondary/Cancel Button -->
|
||||
<button type="button" @click="closeForm" class="py-2 px-5 rounded-lg text-sm font-medium transition-colors duration-150 bg-background border border-border/50 text-text-secondary hover:bg-border hover:text-foreground mr-3">{{ t('common.cancel', '取消') }}</button>
|
||||
<!-- Primary/Submit Button -->
|
||||
<button type="submit" :disabled="isSubmitting || !!commandError" class="py-2 px-5 rounded-lg text-sm font-semibold transition-colors duration-150 bg-primary text-white border-none shadow-md hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary disabled:bg-gray-400 disabled:opacity-70 disabled:cursor-not-allowed">
|
||||
{{ isSubmitting ? t('common.saving', '保存中...') : (isEditing ? t('common.save', '保存') : t('quickCommands.form.add', '添加')) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -221,26 +221,25 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center px-2.5 py-1.5 bg-background min-h-[30px] gap-1.5">
|
||||
<div class="flex-grow flex items-center bg-transparent relative gap-1.5">
|
||||
<div class="flex items-center px-2 py-1.5 bg-background gap-2"> <!-- Removed border-t and border-border/50 -->
|
||||
<div class="flex-grow flex items-center bg-transparent relative gap-2"> <!-- Adjusted gap -->
|
||||
<!-- Focus Switcher Config Button -->
|
||||
<button
|
||||
@click="focusSwitcherStore.toggleConfigurator(true)"
|
||||
class="flex-shrink-0 flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 hover:bg-black/10 hover:text-foreground"
|
||||
class="flex-shrink-0 flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||
:title="t('commandInputBar.configureFocusSwitch', '配置焦点切换')"
|
||||
>
|
||||
<i class="fas fa-keyboard text-base text-primary transition-colors duration-200"></i>
|
||||
<i class="fas fa-keyboard text-base"></i> <!-- Removed text-primary -->
|
||||
</button>
|
||||
<!-- Command Input -->
|
||||
<input
|
||||
type="text"
|
||||
v-model="commandInput"
|
||||
:placeholder="t('commandInputBar.placeholder')"
|
||||
class="flex-grow px-2.5 py-1.5 border border-border rounded text-sm bg-input text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all duration-300 ease-in-out"
|
||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-300 ease-in-out"
|
||||
:class="{ 'basis-3/4': isSearching, 'basis-full': !isSearching }"
|
||||
ref="commandInputRef"
|
||||
data-focus-id="commandInput"
|
||||
|
||||
@keydown="handleCommandInputKeydown"
|
||||
@blur="handleCommandInputBlur"
|
||||
/>
|
||||
@@ -251,7 +250,7 @@ onBeforeUnmount(() => {
|
||||
type="text"
|
||||
v-model="searchTerm"
|
||||
:placeholder="t('commandInputBar.searchPlaceholder')"
|
||||
class="flex-grow px-2.5 py-1.5 border border-border rounded text-sm bg-input text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all duration-300 ease-in-out basis-1/4 ml-1.5"
|
||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-300 ease-in-out basis-1/4"
|
||||
data-focus-id="terminalSearch"
|
||||
@keydown.enter.prevent="findNext"
|
||||
@keydown.shift.enter.prevent="findPrevious"
|
||||
@@ -261,30 +260,30 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
|
||||
<!-- Search Controls -->
|
||||
<div class="flex items-center gap-1 flex-shrink-0">
|
||||
<div class="flex items-center gap-1 flex-shrink-0"> <!-- Adjusted gap -->
|
||||
<button
|
||||
@click="toggleSearch"
|
||||
class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 hover:bg-black/10 hover:text-foreground"
|
||||
class="flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||
:title="isSearching ? t('commandInputBar.closeSearch') : t('commandInputBar.openSearch')"
|
||||
>
|
||||
<i v-if="!isSearching" class="fas fa-search text-base text-primary transition-colors duration-200"></i>
|
||||
<i v-else class="fas fa-times text-base text-primary transition-colors duration-200"></i>
|
||||
<i v-if="!isSearching" class="fas fa-search text-base"></i>
|
||||
<i v-else class="fas fa-times text-base"></i>
|
||||
</button>
|
||||
|
||||
<template v-if="isSearching">
|
||||
<button
|
||||
@click="findPrevious"
|
||||
class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 hover:bg-black/10 hover:text-foreground"
|
||||
class="flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||
:title="t('commandInputBar.findPrevious')"
|
||||
>
|
||||
<i class="fas fa-arrow-up text-base text-primary transition-colors duration-200"></i>
|
||||
<i class="fas fa-arrow-up text-base"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="findNext"
|
||||
class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 hover:bg-black/10 hover:text-foreground"
|
||||
class="flex items-center justify-center w-8 h-8 border border-border/50 rounded-lg text-text-secondary transition-colors duration-200 hover:bg-border hover:text-foreground"
|
||||
:title="t('commandInputBar.findNext')"
|
||||
>
|
||||
<i class="fas fa-arrow-down text-base text-primary transition-colors duration-200"></i>
|
||||
<i class="fas fa-arrow-down text-base"></i>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -910,7 +910,7 @@ defineExpose({ focusSearchInput, startPathEdit });
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full overflow-hidden bg-background text-foreground text-sm font-sans">
|
||||
<div class="flex items-center justify-between flex-wrap gap-2 p-2 bg-header border-b border-border flex-shrink-0">
|
||||
<div class="flex items-center justify-between flex-wrap gap-2 p-2 bg-header flex-shrink-0">
|
||||
<!-- Path Bar -->
|
||||
<div class="flex items-center bg-background border border-border rounded px-1.5 py-0.5 overflow-hidden min-w-[100px] flex-shrink">
|
||||
<span v-show="!isEditingPath" class="text-text-secondary whitespace-nowrap overflow-x-auto pr-2">
|
||||
@@ -1045,7 +1045,7 @@ defineExpose({ focusSearchInput, startPathEdit });
|
||||
</div>
|
||||
|
||||
<!-- File Table -->
|
||||
<table ref="tableRef" class="w-full border-collapse table-fixed border border-border rounded" @contextmenu.prevent>
|
||||
<table ref="tableRef" class="w-full border-collapse table-fixed border-border rounded" @contextmenu.prevent>
|
||||
<colgroup>
|
||||
<col :style="{ width: `${colWidths.type}px` }">
|
||||
<col :style="{ width: `${colWidths.name}px` }">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- Root element with padding, background, border, and text styles -->
|
||||
<div class="status-monitor p-4 border-l border-border bg-background text-foreground h-full overflow-y-auto text-sm">
|
||||
<div class="status-monitor p-4 bg-background text-foreground h-full overflow-y-auto text-sm">
|
||||
<!-- Title with margin, border, padding, font size, and color -->
|
||||
<h4 class="mt-0 mb-4 border-b border-border pb-2 text-base font-medium">
|
||||
{{ t('statusMonitor.title') }}
|
||||
|
||||
@@ -301,96 +301,109 @@ const scrollToHighlighted = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex flex-col overflow-hidden bg-background text-sm text-foreground">
|
||||
<div v-if="connectionsLoading || tagsLoading" class="p-4 text-center text-text-secondary">
|
||||
{{ t('common.loading') }}
|
||||
<div class="h-full flex flex-col overflow-hidden bg-background text-foreground">
|
||||
<!-- Loading / Error State -->
|
||||
<div v-if="connectionsLoading || tagsLoading" class="flex items-center justify-center h-full text-text-secondary">
|
||||
<i class="fas fa-spinner fa-spin mr-2"></i> {{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="connectionsError || tagsError" class="p-4 text-center text-red-600">
|
||||
{{ connectionsError || tagsError }}
|
||||
<div v-else-if="connectionsError || tagsError" class="flex items-center justify-center h-full text-error px-4 text-center">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i> {{ connectionsError || tagsError }}
|
||||
</div>
|
||||
|
||||
<!-- 搜索和添加栏 -->
|
||||
<div class="flex p-2 border-b border-border bg-header">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchTerm"
|
||||
:placeholder="t('workspaceConnectionList.searchPlaceholder')"
|
||||
ref="searchInputRef"
|
||||
class="flex-grow min-w-0 px-3 py-1.5 border border-border rounded-l-md text-sm outline-none bg-background text-foreground focus:border-primary focus:ring-2 focus:ring-primary focus:ring-opacity-50 transition-colors duration-150"
|
||||
data-focus-id="connectionListSearch"
|
||||
@keydown="handleKeyDown"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
<button
|
||||
class="px-3 py-1.5 border border-border border-l-0 bg-background cursor-pointer rounded-r-md text-text-secondary hover:bg-border hover:text-foreground transition-colors duration-150"
|
||||
@click="handleMenuAction('add')"
|
||||
:title="t('connections.addConnection')"
|
||||
>
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Main Content Area -->
|
||||
<div v-else class="flex flex-col h-full">
|
||||
<!-- Search and Add Bar -->
|
||||
<div class="flex p-2 border-b border-border/50"> <!-- Reduced padding p-3 to p-2 -->
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchTerm"
|
||||
:placeholder="t('workspaceConnectionList.searchPlaceholder')"
|
||||
ref="searchInputRef"
|
||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition duration-150 ease-in-out"
|
||||
data-focus-id="connectionListSearch"
|
||||
@keydown="handleKeyDown"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 w-8 h-8 bg-primary text-white border-none rounded-lg text-sm font-semibold cursor-pointer shadow-md transition-colors duration-200 ease-in-out hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary disabled:bg-gray-400 disabled:cursor-not-allowed disabled:opacity-70 flex-shrink-0 flex items-center justify-center"
|
||||
@click="handleMenuAction('add')"
|
||||
:title="t('connections.addConnection')"
|
||||
>
|
||||
<i class="fas fa-plus text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 连接列表区域 -->
|
||||
<div class="flex-grow overflow-y-auto" ref="listAreaRef">
|
||||
<div v-if="connectionsLoading || tagsLoading" class="p-4 text-center text-text-secondary">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="connectionsError || tagsError" class="p-4 text-center text-red-600">
|
||||
{{ connectionsError || tagsError }}
|
||||
</div>
|
||||
<div v-else-if="filteredAndGroupedConnections.length === 0 && connections.length > 0" class="p-4 text-center text-text-secondary">
|
||||
{{ t('workspaceConnectionList.noResults') }} "{{ searchTerm }}"
|
||||
</div>
|
||||
<div v-else-if="connections.length === 0" class="p-4 text-center text-text-secondary">
|
||||
{{ t('connections.noConnections') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- 循环分组 -->
|
||||
<div v-for="groupData in filteredAndGroupedConnections" :key="groupData.groupName" class="mb-0 last:mb-0">
|
||||
<div class="group px-3 py-2 font-semibold cursor-pointer bg-header border-t border-b border-border flex items-center text-foreground hover:bg-border transition-colors duration-150" @click="toggleGroup(groupData.groupName)">
|
||||
<i :class="['fas', expandedGroups[groupData.groupName] ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 w-4 text-center text-text-secondary group-hover:text-foreground transition-transform duration-200 ease-in-out']"></i>
|
||||
<span>{{ groupData.groupName }}</span>
|
||||
</div>
|
||||
<!-- 连接项列表 -->
|
||||
<ul v-show="expandedGroups[groupData.groupName]" class="list-none p-0 m-0">
|
||||
<li
|
||||
v-for="conn in groupData.connections"
|
||||
:key="conn.id"
|
||||
class="group py-2 pr-4 pl-6 cursor-pointer flex items-center border-b border-border whitespace-nowrap overflow-hidden text-ellipsis text-foreground hover:bg-header/50 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/10 text-primary': conn.id === highlightedConnectionId }"
|
||||
:data-conn-id="conn.id"
|
||||
@click.left="handleConnect(conn.id)"
|
||||
@contextmenu.prevent="showContextMenu($event, conn)"
|
||||
<!-- Connection List Area -->
|
||||
<div class="flex-grow overflow-y-auto p-2" ref="listAreaRef">
|
||||
<!-- No Results / No Connections State -->
|
||||
<div v-if="filteredAndGroupedConnections.length === 0 && connections.length > 0" class="p-6 text-center text-text-secondary">
|
||||
<i class="fas fa-search text-xl mb-2"></i>
|
||||
<p>{{ t('workspaceConnectionList.noResults') }} "{{ searchTerm }}"</p>
|
||||
</div>
|
||||
<div v-else-if="connections.length === 0" class="p-6 text-center text-text-secondary">
|
||||
<i class="fas fa-plug text-xl mb-2"></i>
|
||||
<p>{{ t('connections.noConnections') }}</p>
|
||||
<button
|
||||
class="mt-4 px-4 py-2 bg-primary text-white border-none rounded-lg text-sm font-semibold cursor-pointer shadow-md transition-colors duration-200 ease-in-out hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
|
||||
@click="handleMenuAction('add')"
|
||||
>
|
||||
{{ t('connections.addFirstConnection') }} <!-- Need translation -->
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Groups and Connections -->
|
||||
<div v-else>
|
||||
<div v-for="groupData in filteredAndGroupedConnections" :key="groupData.groupName" class="mb-1 last:mb-0">
|
||||
<!-- Group Header -->
|
||||
<div
|
||||
class="group px-3 py-2 font-semibold cursor-pointer flex items-center text-foreground rounded-md hover:bg-header/80 transition-colors duration-150"
|
||||
@click="toggleGroup(groupData.groupName)"
|
||||
>
|
||||
<i class="fas fa-server mr-2.5 w-4 text-center text-text-secondary group-hover:text-foreground" :class="{ 'text-primary': conn.id === highlightedConnectionId }"></i>
|
||||
<span class="overflow-hidden text-ellipsis whitespace-nowrap flex-grow" :title="conn.name || conn.host">
|
||||
{{ conn.name || conn.host }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<i :class="['fas', expandedGroups[groupData.groupName] ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 w-4 text-center text-text-secondary group-hover:text-foreground transition-transform duration-200 ease-in-out', {'transform rotate-0': !expandedGroups[groupData.groupName]}]"></i>
|
||||
<span class="text-sm">{{ groupData.groupName }}</span>
|
||||
</div>
|
||||
<!-- Connection Items List -->
|
||||
<ul v-show="expandedGroups[groupData.groupName]" class="list-none p-0 m-0 pl-3">
|
||||
<li
|
||||
v-for="conn in groupData.connections"
|
||||
:key="conn.id"
|
||||
class="group my-0.5 py-2 pr-3 pl-4 cursor-pointer flex items-center rounded-md whitespace-nowrap overflow-hidden text-ellipsis text-foreground hover:bg-primary/10 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/20 text-white font-medium': conn.id === highlightedConnectionId }"
|
||||
:data-conn-id="conn.id"
|
||||
@click.left="handleConnect(conn.id)"
|
||||
@contextmenu.prevent="showContextMenu($event, conn)"
|
||||
@mousedown.middle.prevent="handleOpenInNewTab(conn.id)"
|
||||
@auxclick.prevent="handleOpenInNewTab(conn.id)"
|
||||
>
|
||||
<i class="fas fa-server mr-2.5 w-4 text-center text-text-secondary group-hover:text-primary" :class="{ 'text-white': conn.id === highlightedConnectionId }"></i>
|
||||
<span class="overflow-hidden text-ellipsis whitespace-nowrap flex-grow text-sm" :title="conn.name || conn.host">
|
||||
{{ conn.name || conn.host }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<!-- Context Menu -->
|
||||
<div
|
||||
v-if="contextMenuVisible"
|
||||
class="fixed bg-background border border-border shadow-lg rounded-md py-1 z-50 min-w-[160px]"
|
||||
class="fixed bg-background border border-border/50 shadow-xl rounded-lg py-1.5 z-50 min-w-[180px]"
|
||||
:style="{ top: `${contextMenuPosition.y}px`, left: `${contextMenuPosition.x}px` }"
|
||||
@click.stop
|
||||
>
|
||||
<!-- 防止点击菜单内部关闭菜单 -->
|
||||
<ul class="list-none p-0 m-0">
|
||||
<li class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-header text-sm transition-colors duration-150" @click="handleMenuAction('add')">
|
||||
<i class="fas fa-plus mr-3 w-4 text-center text-text-secondary group-hover:text-foreground"></i>
|
||||
<li class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-primary/10 hover:text-primary text-sm transition-colors duration-150 rounded-md mx-1" @click="handleMenuAction('add')">
|
||||
<i class="fas fa-plus mr-3 w-4 text-center text-text-secondary group-hover:text-primary"></i>
|
||||
<span>{{ t('connections.addConnection') }}</span>
|
||||
</li>
|
||||
<li v-if="contextTargetConnection" class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-header text-sm transition-colors duration-150" @click="handleMenuAction('edit')">
|
||||
<i class="fas fa-edit mr-3 w-4 text-center text-text-secondary group-hover:text-foreground"></i>
|
||||
<li v-if="contextTargetConnection" class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-primary/10 hover:text-primary text-sm transition-colors duration-150 rounded-md mx-1" @click="handleMenuAction('edit')">
|
||||
<i class="fas fa-edit mr-3 w-4 text-center text-text-secondary group-hover:text-primary"></i>
|
||||
<span>{{ t('connections.actions.edit') }}</span>
|
||||
</li>
|
||||
<li v-if="contextTargetConnection" class="group px-4 py-1.5 cursor-pointer flex items-center text-red-600 hover:bg-red-500/10 text-sm transition-colors duration-150" @click="handleMenuAction('delete')">
|
||||
<i class="fas fa-trash-alt mr-3 w-4 text-center text-red-500 group-hover:text-red-600"></i>
|
||||
<li v-if="contextTargetConnection" class="group px-4 py-1.5 cursor-pointer flex items-center text-error hover:bg-error/10 text-sm transition-colors duration-150 rounded-md mx-1" @click="handleMenuAction('delete')">
|
||||
<i class="fas fa-trash-alt mr-3 w-4 text-center text-error/80 group-hover:text-error"></i>
|
||||
<span>{{ t('connections.actions.delete') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -107,7 +107,6 @@ button:hover i, button:hover .fas, button:hover .far, button:hover .fab, /* 按
|
||||
.icon-interactive:hover i, .icon-interactive:hover .fas, .icon-interactive:hover .far, .icon-interactive:hover .fab { /* 可交互图标容器 */
|
||||
color: var(--icon-hover-color);
|
||||
}
|
||||
|
||||
/* 全局分割线样式 */
|
||||
hr {
|
||||
border: none;
|
||||
@@ -193,3 +192,19 @@ input:focus, textarea:focus, select:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 3px rgba(var(--input-focus-glow-rgb), 0.2) !important; /* Use new variable, !important might be needed */
|
||||
}
|
||||
|
||||
/* Ensure icons inside primary buttons are white */
|
||||
button.bg-primary i,
|
||||
button.bg-primary .fas,
|
||||
button.bg-primary .far,
|
||||
button.bg-primary .fab {
|
||||
color: white !important; /* Force white color */
|
||||
}
|
||||
|
||||
/* Optional: Keep icon white even on hover for primary buttons */
|
||||
button.bg-primary:hover i,
|
||||
button.bg-primary:hover .fas,
|
||||
button.bg-primary:hover .far,
|
||||
button.bg-primary:hover .fab {
|
||||
color: white !important; /* Keep white on hover */
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div class="flex flex-col h-full overflow-hidden bg-background">
|
||||
<!-- Container for controls and list -->
|
||||
<div class="flex flex-col flex-grow overflow-hidden bg-background">
|
||||
<!-- Controls embedded within the container -->
|
||||
<div class="flex items-stretch p-2 border-b border-border flex-shrink-0 gap-1 bg-header">
|
||||
<!-- Controls Area -->
|
||||
<div class="flex items-center p-2 flex-shrink-0 gap-2 bg-background"> <!-- Reduced padding p-3 to p-2 -->
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="$t('commandHistory.searchPlaceholder', '搜索历史记录...')"
|
||||
@@ -12,41 +12,49 @@
|
||||
@input="updateSearchTerm($event)"
|
||||
@keydown="handleSearchInputKeydown"
|
||||
ref="searchInputRef"
|
||||
class="flex-grow min-w-[8px] px-2 py-1 border border-border rounded-sm bg-background text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition duration-150 ease-in-out"
|
||||
/>
|
||||
<button @click="confirmClearAll" class="w-8 py-1 border border-border rounded-sm text-text-secondary hover:bg-error/10 hover:text-error hover:border-error/50 transition-colors duration-150 flex-shrink-0 flex" :title="$t('commandHistory.clear', '清空')">
|
||||
<i class="fas fa-trash-alt text-sm m-auto"></i>
|
||||
<!-- Clear Button -->
|
||||
<button @click="confirmClearAll" class="w-8 h-8 border border-border/50 rounded-lg text-text-secondary hover:bg-error/10 hover:text-error hover:border-error/50 transition-colors duration-150 flex-shrink-0 flex items-center justify-center" :title="$t('commandHistory.clear', '清空')"> <!-- Use w-8 h-8 -->
|
||||
<i class="fas fa-trash-alt text-base"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- List Area -->
|
||||
<div class="flex-grow overflow-y-auto">
|
||||
<ul ref="historyListRef" v-if="filteredHistory.length > 0" class="list-none p-0 m-0">
|
||||
<div class="flex-grow overflow-y-auto p-2">
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="p-6 text-center text-text-secondary text-sm flex flex-col items-center justify-center h-full">
|
||||
<i class="fas fa-spinner fa-spin text-xl mb-2"></i>
|
||||
<p>{{ $t('commandHistory.loading', '加载中...') }}</p>
|
||||
</div>
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="filteredHistory.length === 0" class="p-6 text-center text-text-secondary text-sm flex flex-col items-center justify-center h-full">
|
||||
<i class="fas fa-history text-xl mb-2"></i>
|
||||
<p>{{ $t('commandHistory.empty', '没有历史记录') }}</p>
|
||||
</div>
|
||||
<!-- History List -->
|
||||
<ul ref="historyListRef" v-else class="list-none p-0 m-0">
|
||||
<li
|
||||
v-for="(entry, index) in filteredHistory"
|
||||
:key="entry.id"
|
||||
class="group flex justify-between items-center px-3 py-2 cursor-pointer border-b border-border last:border-b-0 hover:bg-header/50 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/10 text-primary': index === storeSelectedIndex }"
|
||||
|
||||
|
||||
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/20 text-white font-medium': index === storeSelectedIndex }"
|
||||
@click="executeCommand(entry.command)"
|
||||
>
|
||||
<span class="truncate mr-2 flex-grow font-mono text-sm text-foreground" :class="{'text-primary': index === storeSelectedIndex}">{{ entry.command }}</span>
|
||||
<div class="flex items-center flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-150">
|
||||
<button @click.stop="copyCommand(entry.command)" class="p-1 text-text-secondary hover:text-primary transition-colors duration-150" :class="{'text-primary': index === storeSelectedIndex}" :title="$t('commandHistory.copy', '复制')">
|
||||
<i class="fas fa-copy text-xs"></i>
|
||||
<!-- Command Text -->
|
||||
<span class="truncate mr-2 flex-grow font-mono text-sm" :class="{'text-white': index === storeSelectedIndex, 'text-foreground': index !== storeSelectedIndex}">{{ entry.command }}</span>
|
||||
<!-- Actions (Show on Hover) -->
|
||||
<div class="flex items-center flex-shrink-0 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150">
|
||||
<!-- Copy Button -->
|
||||
<button @click.stop="copyCommand(entry.command)" class="p-1.5 rounded hover:bg-black/10 transition-colors duration-150" :class="{'text-white hover:bg-white/20': index === storeSelectedIndex, 'text-text-secondary hover:text-primary': index !== storeSelectedIndex}" :title="$t('commandHistory.copy', '复制')">
|
||||
<i class="fas fa-copy text-sm"></i>
|
||||
</button>
|
||||
<button @click.stop="deleteSingleCommand(entry.id)" class="ml-1 p-1 text-text-secondary hover:text-error transition-colors duration-150" :class="{'text-primary': index === storeSelectedIndex}" :title="$t('commandHistory.delete', '删除')">
|
||||
<i class="fas fa-times text-xs"></i>
|
||||
<!-- Delete Button -->
|
||||
<button @click.stop="deleteSingleCommand(entry.id)" class="ml-1 p-1.5 rounded hover:bg-black/10 transition-colors duration-150" :class="{'text-white hover:bg-white/20': index === storeSelectedIndex, 'text-text-secondary hover:text-error': index !== storeSelectedIndex}" :title="$t('commandHistory.delete', '删除')">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else-if="isLoading" class="p-6 text-center text-text-secondary text-sm">
|
||||
{{ $t('commandHistory.loading', '加载中...') }}
|
||||
</div>
|
||||
<div v-else class="p-6 text-center text-text-secondary text-sm italic">
|
||||
{{ $t('commandHistory.empty', '没有历史记录') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div class="flex flex-col h-full overflow-hidden bg-background">
|
||||
<!-- Container for controls and list -->
|
||||
<div class="flex flex-col flex-grow overflow-hidden bg-background">
|
||||
<!-- Controls embedded within the container -->
|
||||
<div class="flex items-stretch p-2 border-b border-border flex-shrink-0 gap-1 bg-header">
|
||||
<!-- Controls Area -->
|
||||
<div class="flex items-center p-2 flex-shrink-0 gap-2 bg-background"> <!-- Reduced padding p-3 to p-2 -->
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="$t('quickCommands.searchPlaceholder', '搜索名称或指令...')"
|
||||
@@ -12,48 +12,61 @@
|
||||
@input="updateSearchTerm($event)"
|
||||
@keydown="handleSearchInputKeydown"
|
||||
ref="searchInputRef"
|
||||
class="flex-grow min-w-[8px] px-2 py-1 border border-border rounded-sm bg-background text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
class="flex-grow min-w-0 px-4 py-1.5 border border-border/50 rounded-lg bg-input text-foreground text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition duration-150 ease-in-out"
|
||||
/>
|
||||
<button @click="toggleSortBy" class="w-8 py-1 border border-border rounded-sm text-text-secondary hover:bg-border hover:text-foreground transition-colors duration-150 flex-shrink-0 flex" :title="sortButtonTitle">
|
||||
<i :class="[sortButtonIcon, 'text-sm', 'm-auto']"></i>
|
||||
<!-- Sort Button -->
|
||||
<button @click="toggleSortBy" class="w-8 h-8 border border-border/50 rounded-lg text-text-secondary hover:bg-border hover:text-foreground transition-colors duration-150 flex-shrink-0 flex items-center justify-center" :title="sortButtonTitle"> <!-- Use w-8 h-8 -->
|
||||
<i :class="[sortButtonIcon, 'text-base']"></i>
|
||||
</button>
|
||||
<button @click="openAddForm" class="w-8 py-1 border border-border rounded-sm text-text-secondary hover:bg-border hover:text-foreground transition-colors duration-150 flex-shrink-0 flex" :title="$t('quickCommands.add', '添加快捷指令')">
|
||||
<i class="fas fa-plus text-sm m-auto"></i>
|
||||
<!-- Add Button -->
|
||||
<button @click="openAddForm" class="w-8 h-8 bg-primary text-white border-none rounded-lg text-sm font-semibold cursor-pointer shadow-md transition-colors duration-200 ease-in-out hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary flex-shrink-0 flex items-center justify-center" :title="$t('quickCommands.add', '添加快捷指令')"> <!-- Use w-8 h-8 -->
|
||||
<i class="fas fa-plus text-base"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- List Area -->
|
||||
<div class="flex-grow overflow-y-auto">
|
||||
<ul v-if="filteredAndSortedCommands.length > 0" class="list-none p-0 m-0" ref="commandListRef">
|
||||
<div class="flex-grow overflow-y-auto p-2">
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="p-6 text-center text-text-secondary text-sm flex flex-col items-center justify-center h-full">
|
||||
<i class="fas fa-spinner fa-spin text-xl mb-2"></i>
|
||||
<p>{{ t('common.loading', '加载中...') }}</p>
|
||||
</div>
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="filteredAndSortedCommands.length === 0" class="p-6 text-center text-text-secondary text-sm flex flex-col items-center justify-center h-full">
|
||||
<i class="fas fa-bolt text-xl mb-2"></i>
|
||||
<p class="mb-3">{{ $t('quickCommands.empty', '没有快捷指令。') }}</p>
|
||||
<button @click="openAddForm" class="px-4 py-2 bg-primary text-white border-none rounded-lg text-sm font-semibold cursor-pointer shadow-md transition-colors duration-200 ease-in-out hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
||||
{{ $t('quickCommands.addFirst', '创建第一个快捷指令') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Command List -->
|
||||
<ul v-else class="list-none p-0 m-0" ref="commandListRef">
|
||||
<li
|
||||
v-for="(cmd, index) in filteredAndSortedCommands"
|
||||
:key="cmd.id"
|
||||
class="group flex justify-between items-center px-3 py-2 cursor-pointer border-b border-border last:border-b-0 hover:bg-header/50 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/10 text-primary': index === storeSelectedIndex }"
|
||||
|
||||
|
||||
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
||||
:class="{ 'bg-primary/20 text-white font-medium': index === storeSelectedIndex }"
|
||||
@click="executeCommand(cmd)"
|
||||
>
|
||||
<!-- Command Info -->
|
||||
<div class="flex flex-col overflow-hidden mr-2 flex-grow">
|
||||
<span v-if="cmd.name" class="font-medium text-foreground text-sm truncate mb-0.5">{{ cmd.name }}</span>
|
||||
<span class="text-xs text-text-secondary truncate font-mono" :class="{ 'text-sm text-foreground': !cmd.name }">{{ cmd.command }}</span>
|
||||
<span v-if="cmd.name" class="font-medium text-sm truncate mb-0.5" :class="{'text-white': index === storeSelectedIndex, 'text-foreground': index !== storeSelectedIndex}">{{ cmd.name }}</span>
|
||||
<span class="text-xs truncate font-mono" :class="{ 'text-sm': !cmd.name, 'text-white/80': index === storeSelectedIndex, 'text-text-secondary': index !== storeSelectedIndex }">{{ cmd.command }}</span>
|
||||
</div>
|
||||
<div class="flex items-center flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-150">
|
||||
<span class="text-xs text-text-secondary bg-border px-1.5 py-0.5 rounded mr-1" :class="{'text-primary bg-primary/20': index === storeSelectedIndex}" :title="t('quickCommands.usageCount', '使用次数')">{{ cmd.usage_count }}</span>
|
||||
<button @click.stop="openEditForm(cmd)" class="p-1 text-text-secondary hover:text-primary transition-colors duration-150" :class="{'text-primary': index === storeSelectedIndex}" :title="$t('common.edit', '编辑')">
|
||||
<i class="fas fa-edit text-xs"></i>
|
||||
<!-- Actions (Show on Hover) -->
|
||||
<div class="flex items-center flex-shrink-0 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150">
|
||||
<!-- Usage Count -->
|
||||
<span class="text-xs bg-border px-1.5 py-0.5 rounded mr-2" :class="{'text-white/80 bg-white/20': index === storeSelectedIndex, 'text-text-secondary': index !== storeSelectedIndex}" :title="t('quickCommands.usageCount', '使用次数')">{{ cmd.usage_count }}</span>
|
||||
<!-- Edit Button -->
|
||||
<button @click.stop="openEditForm(cmd)" class="p-1.5 rounded hover:bg-black/10 transition-colors duration-150" :class="{'text-white hover:bg-white/20': index === storeSelectedIndex, 'text-text-secondary hover:text-primary': index !== storeSelectedIndex}" :title="$t('common.edit', '编辑')">
|
||||
<i class="fas fa-edit text-sm"></i>
|
||||
</button>
|
||||
<button @click.stop="confirmDelete(cmd)" class="p-1 text-text-secondary hover:text-error transition-colors duration-150" :class="{'text-primary': index === storeSelectedIndex}" :title="$t('common.delete', '删除')">
|
||||
<i class="fas fa-times text-xs"></i>
|
||||
<!-- Delete Button -->
|
||||
<button @click.stop="confirmDelete(cmd)" class="p-1.5 rounded hover:bg-black/10 transition-colors duration-150" :class="{'text-white hover:bg-white/20': index === storeSelectedIndex, 'text-text-secondary hover:text-error': index !== storeSelectedIndex}" :title="$t('common.delete', '删除')">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else-if="isLoading" class="p-6 text-center text-text-secondary text-sm">
|
||||
{{ t('common.loading', '加载中...') }}
|
||||
</div>
|
||||
<div v-else class="p-6 text-center text-text-secondary text-sm italic">
|
||||
{{ $t('quickCommands.empty', '没有快捷指令。点击“添加”按钮创建一个吧!') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user