update
This commit is contained in:
@@ -10,19 +10,21 @@
|
|||||||
:placeholder="$t('commandHistory.searchPlaceholder', '搜索历史记录...')"
|
:placeholder="$t('commandHistory.searchPlaceholder', '搜索历史记录...')"
|
||||||
:value="searchTerm"
|
:value="searchTerm"
|
||||||
@input="updateSearchTerm($event)"
|
@input="updateSearchTerm($event)"
|
||||||
|
@keydown="handleKeydown"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
/>
|
/>
|
||||||
<button @click="confirmClearAll" class="clear-button" :title="$t('commandHistory.clear', '清空')">
|
<button @click="confirmClearAll" class="clear-button" :title="$t('commandHistory.clear', '清空')">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="filteredHistory.length > 0" class="history-list">
|
<ul ref="historyListRef" v-if="filteredHistory.length > 0" class="history-list">
|
||||||
<li
|
<li
|
||||||
v-for="entry in filteredHistory"
|
v-for="(entry, index) in filteredHistory"
|
||||||
:key="entry.id"
|
:key="entry.id"
|
||||||
class="history-item"
|
class="history-item"
|
||||||
@mouseover="hoveredItemId = entry.id"
|
:class="{ selected: index === selectedIndex }"
|
||||||
@mouseleave="hoveredItemId = null"
|
@mouseover="hoveredItemId = entry.id; selectedIndex = index"
|
||||||
|
@mouseleave="hoveredItemId = null; selectedIndex = -1"
|
||||||
@click="executeCommand(entry.command)"
|
@click="executeCommand(entry.command)"
|
||||||
>
|
>
|
||||||
<span class="command-text">{{ entry.command }}</span>
|
<span class="command-text">{{ entry.command }}</span>
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
||||||
import { useCommandHistoryStore, CommandHistoryEntryFE } from '../stores/commandHistory.store';
|
import { useCommandHistoryStore, CommandHistoryEntryFE } from '../stores/commandHistory.store';
|
||||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -57,6 +59,8 @@ const commandHistoryStore = useCommandHistoryStore();
|
|||||||
const uiNotificationsStore = useUiNotificationsStore();
|
const uiNotificationsStore = useUiNotificationsStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const hoveredItemId = ref<number | null>(null);
|
const hoveredItemId = ref<number | null>(null);
|
||||||
|
const selectedIndex = ref<number>(-1); // -1 表示没有选中
|
||||||
|
const historyListRef = ref<HTMLUListElement | null>(null); // Ref for the history list UL
|
||||||
|
|
||||||
// --- 从 Store 获取状态和 Getter ---
|
// --- 从 Store 获取状态和 Getter ---
|
||||||
const searchTerm = computed(() => commandHistoryStore.searchTerm);
|
const searchTerm = computed(() => commandHistoryStore.searchTerm);
|
||||||
@@ -84,8 +88,57 @@ onMounted(() => {
|
|||||||
const updateSearchTerm = (event: Event) => {
|
const updateSearchTerm = (event: Event) => {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
commandHistoryStore.setSearchTerm(target.value);
|
commandHistoryStore.setSearchTerm(target.value);
|
||||||
|
selectedIndex.value = -1; // Reset selection when search term changes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 滚动到选中的项目
|
||||||
|
const scrollToSelected = async () => {
|
||||||
|
await nextTick(); // 等待 DOM 更新
|
||||||
|
if (selectedIndex.value < 0 || !historyListRef.value) return;
|
||||||
|
|
||||||
|
const listElement = historyListRef.value;
|
||||||
|
const selectedItem = listElement.children[selectedIndex.value] as HTMLLIElement;
|
||||||
|
|
||||||
|
if (selectedItem) {
|
||||||
|
const listRect = listElement.getBoundingClientRect();
|
||||||
|
const itemRect = selectedItem.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (itemRect.top < listRect.top) {
|
||||||
|
// Item is above the visible area
|
||||||
|
listElement.scrollTop -= listRect.top - itemRect.top;
|
||||||
|
} else if (itemRect.bottom > listRect.bottom) {
|
||||||
|
// Item is below the visible area
|
||||||
|
listElement.scrollTop += itemRect.bottom - listRect.bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
const history = filteredHistory.value;
|
||||||
|
if (!history.length) return;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
selectedIndex.value = (selectedIndex.value + 1) % history.length;
|
||||||
|
scrollToSelected();
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
|
selectedIndex.value = (selectedIndex.value - 1 + history.length) % history.length;
|
||||||
|
scrollToSelected();
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedIndex.value >= 0 && selectedIndex.value < history.length) {
|
||||||
|
executeCommand(history[selectedIndex.value].command);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 确认清空所有历史记录
|
// 确认清空所有历史记录
|
||||||
const confirmClearAll = () => {
|
const confirmClearAll = () => {
|
||||||
if (window.confirm(t('commandHistory.confirmClear', '确定要清空所有历史记录吗?'))) {
|
if (window.confirm(t('commandHistory.confirmClear', '确定要清空所有历史记录吗?'))) {
|
||||||
@@ -112,6 +165,8 @@ const deleteSingleCommand = (id: number) => {
|
|||||||
// 新增:执行命令 (发出事件)
|
// 新增:执行命令 (发出事件)
|
||||||
const executeCommand = (command: string) => {
|
const executeCommand = (command: string) => {
|
||||||
emit('execute-command', command);
|
emit('execute-command', command);
|
||||||
|
// Optionally reset selection after execution
|
||||||
|
// selectedIndex.value = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -212,6 +267,16 @@ const executeCommand = (command: string) => {
|
|||||||
.history-item:hover {
|
.history-item:hover {
|
||||||
background-color: var(--header-bg-color); /* Use header background for hover */
|
background-color: var(--header-bg-color); /* Use header background for hover */
|
||||||
}
|
}
|
||||||
|
/* Style for the keyboard-selected item */
|
||||||
|
.history-item.selected {
|
||||||
|
background-color: var(--button-bg-color, #007bff); /* Use button background or fallback */
|
||||||
|
color: var(--button-text-color, white); /* Use button text color or fallback */
|
||||||
|
}
|
||||||
|
.history-item.selected .command-text,
|
||||||
|
.history-item.selected .action-button {
|
||||||
|
color: var(--button-text-color, white); /* Ensure text inside selected item is readable */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.command-text {
|
.command-text {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
:placeholder="$t('quickCommands.searchPlaceholder', '搜索名称或指令...')"
|
:placeholder="$t('quickCommands.searchPlaceholder', '搜索名称或指令...')"
|
||||||
:value="searchTerm"
|
:value="searchTerm"
|
||||||
@input="updateSearchTerm($event)"
|
@input="updateSearchTerm($event)"
|
||||||
|
@keydown="handleKeydown"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
/>
|
/>
|
||||||
<button @click="toggleSortBy" class="sort-toggle-button" :title="sortButtonTitle">
|
<button @click="toggleSortBy" class="sort-toggle-button" :title="sortButtonTitle">
|
||||||
@@ -20,11 +21,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul v-if="filteredAndSortedCommands.length > 0" class="commands-list">
|
<ul v-if="filteredAndSortedCommands.length > 0" class="commands-list">
|
||||||
<li
|
<li
|
||||||
v-for="cmd in filteredAndSortedCommands"
|
v-for="(cmd, index) in filteredAndSortedCommands"
|
||||||
:key="cmd.id"
|
:key="cmd.id"
|
||||||
class="command-item"
|
class="command-item"
|
||||||
@mouseover="hoveredItemId = cmd.id"
|
:class="{ selected: index === selectedIndex }"
|
||||||
@mouseleave="hoveredItemId = null"
|
@mouseover="hoveredItemId = cmd.id; selectedIndex = index"
|
||||||
|
@mouseleave="hoveredItemId = null; selectedIndex = -1"
|
||||||
@click="executeCommand(cmd)"
|
@click="executeCommand(cmd)"
|
||||||
>
|
>
|
||||||
<div class="command-info">
|
<div class="command-info">
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
||||||
import { useQuickCommandsStore, type QuickCommandFE, type QuickCommandSortByType } from '../stores/quickCommands.store';
|
import { useQuickCommandsStore, type QuickCommandFE, type QuickCommandSortByType } from '../stores/quickCommands.store';
|
||||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -73,6 +75,8 @@ const { t } = useI18n();
|
|||||||
const hoveredItemId = ref<number | null>(null);
|
const hoveredItemId = ref<number | null>(null);
|
||||||
const isFormVisible = ref(false);
|
const isFormVisible = ref(false);
|
||||||
const commandToEdit = ref<QuickCommandFE | null>(null);
|
const commandToEdit = ref<QuickCommandFE | null>(null);
|
||||||
|
const selectedIndex = ref<number>(-1); // -1 表示没有选中
|
||||||
|
const commandListRef = ref<HTMLUListElement | null>(null); // Ref for the command list UL
|
||||||
|
|
||||||
// --- 从 Store 获取状态和 Getter ---
|
// --- 从 Store 获取状态和 Getter ---
|
||||||
const searchTerm = computed(() => quickCommandsStore.searchTerm);
|
const searchTerm = computed(() => quickCommandsStore.searchTerm);
|
||||||
@@ -95,8 +99,60 @@ onMounted(() => {
|
|||||||
const updateSearchTerm = (event: Event) => {
|
const updateSearchTerm = (event: Event) => {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
quickCommandsStore.setSearchTerm(target.value);
|
quickCommandsStore.setSearchTerm(target.value);
|
||||||
|
selectedIndex.value = -1; // Reset selection when search term changes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 滚动到选中的项目
|
||||||
|
const scrollToSelected = async () => {
|
||||||
|
await nextTick(); // 等待 DOM 更新
|
||||||
|
if (selectedIndex.value < 0 || !commandListRef.value) return;
|
||||||
|
|
||||||
|
const listElement = commandListRef.value;
|
||||||
|
const selectedItem = listElement.children[selectedIndex.value] as HTMLLIElement;
|
||||||
|
|
||||||
|
if (selectedItem) {
|
||||||
|
const listRect = listElement.getBoundingClientRect();
|
||||||
|
const itemRect = selectedItem.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (itemRect.top < listRect.top) {
|
||||||
|
// Item is above the visible area
|
||||||
|
listElement.scrollTop -= listRect.top - itemRect.top;
|
||||||
|
} else if (itemRect.bottom > listRect.bottom) {
|
||||||
|
// Item is below the visible area
|
||||||
|
listElement.scrollTop += itemRect.bottom - listRect.bottom;
|
||||||
|
}
|
||||||
|
// For smooth scrolling (optional):
|
||||||
|
// selectedItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
const commands = filteredAndSortedCommands.value;
|
||||||
|
if (!commands.length) return;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
selectedIndex.value = (selectedIndex.value + 1) % commands.length;
|
||||||
|
scrollToSelected();
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
|
selectedIndex.value = (selectedIndex.value - 1 + commands.length) % commands.length;
|
||||||
|
scrollToSelected();
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedIndex.value >= 0 && selectedIndex.value < commands.length) {
|
||||||
|
executeCommand(commands[selectedIndex.value]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 切换排序方式
|
// 切换排序方式
|
||||||
const toggleSortBy = () => {
|
const toggleSortBy = () => {
|
||||||
const newSortBy = sortBy.value === 'name' ? 'usage_count' : 'name';
|
const newSortBy = sortBy.value === 'name' ? 'usage_count' : 'name';
|
||||||
@@ -143,6 +199,8 @@ const executeCommand = (command: QuickCommandFE) => {
|
|||||||
quickCommandsStore.incrementUsage(command.id);
|
quickCommandsStore.incrementUsage(command.id);
|
||||||
// 2. 发出执行事件给父组件
|
// 2. 发出执行事件给父组件
|
||||||
emit('execute-command', command.command);
|
emit('execute-command', command.command);
|
||||||
|
// Optionally reset selection after execution
|
||||||
|
// selectedIndex.value = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -254,6 +312,8 @@ const executeCommand = (command: QuickCommandFE) => {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
/* Ensure the list itself can scroll if needed, although container handles it */
|
||||||
|
/* overflow-y: auto; */ /* Let container handle scroll */
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-item {
|
.command-item {
|
||||||
@@ -273,6 +333,18 @@ const executeCommand = (command: QuickCommandFE) => {
|
|||||||
.command-item:hover {
|
.command-item:hover {
|
||||||
background-color: var(--header-bg-color); /* Use header background for hover */
|
background-color: var(--header-bg-color); /* Use header background for hover */
|
||||||
}
|
}
|
||||||
|
/* Style for the keyboard-selected item */
|
||||||
|
.command-item.selected {
|
||||||
|
background-color: var(--button-bg-color, #007bff); /* Use button background or fallback */
|
||||||
|
color: var(--button-text-color, white); /* Use button text color or fallback */
|
||||||
|
}
|
||||||
|
.command-item.selected .command-name,
|
||||||
|
.command-item.selected .command-text,
|
||||||
|
.command-item.selected .usage-count,
|
||||||
|
.command-item.selected .action-button {
|
||||||
|
color: var(--button-text-color, white); /* Ensure text inside selected item is readable */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.command-info {
|
.command-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user