From 747c9491c4ff1e776128991576c7d87eca443679 Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:20:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/index.ts | 2 + packages/backend/src/migrations.ts | 20 ++ .../quick-commands.controller.ts | 130 ++++++++ .../quick-commands/quick-commands.routes.ts | 17 + .../repositories/quick-commands.repository.ts | 133 ++++++++ .../src/services/quick-commands.service.ts | 74 +++++ .../components/AddEditQuickCommandForm.vue | 212 ++++++++++++ .../frontend/src/components/FileManager.vue | 2 +- .../src/components/TerminalTabBar.vue | 3 +- packages/frontend/src/locales/zh.json | 21 ++ packages/frontend/src/stores/layout.store.ts | 7 +- .../src/stores/quickCommands.store.ts | 169 ++++++++++ .../frontend/src/views/QuickCommandsView.vue | 313 ++++++++++++++++++ packages/frontend/src/views/WorkspaceView.vue | 22 +- 14 files changed, 1114 insertions(+), 11 deletions(-) create mode 100644 packages/backend/src/quick-commands/quick-commands.controller.ts create mode 100644 packages/backend/src/quick-commands/quick-commands.routes.ts create mode 100644 packages/backend/src/repositories/quick-commands.repository.ts create mode 100644 packages/backend/src/services/quick-commands.service.ts create mode 100644 packages/frontend/src/components/AddEditQuickCommandForm.vue create mode 100644 packages/frontend/src/stores/quickCommands.store.ts create mode 100644 packages/frontend/src/views/QuickCommandsView.vue diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 6ce6498..7f58efa 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -17,6 +17,7 @@ import settingsRoutes from './settings/settings.routes'; // 导入设置路由 import notificationRoutes from './notifications/notification.routes'; // 导入通知路由 import auditRoutes from './audit/audit.routes'; // 导入审计路由 import commandHistoryRoutes from './command-history/command-history.routes'; // 导入命令历史记录路由 +import quickCommandsRoutes from './quick-commands/quick-commands.routes'; // 导入快捷指令路由 import { initializeWebSocket } from './websocket'; import { ipWhitelistMiddleware } from './auth/ipWhitelist.middleware'; // 导入 IP 白名单中间件 @@ -104,6 +105,7 @@ app.use('/api/v1/settings', settingsRoutes); // 挂载设置相关的路由 app.use('/api/v1/notifications', notificationRoutes); // 挂载通知相关的路由 app.use('/api/v1/audit-logs', auditRoutes); // 挂载审计日志相关的路由 app.use('/api/v1/command-history', commandHistoryRoutes); // 挂载命令历史记录相关的路由 +app.use('/api/v1/quick-commands', quickCommandsRoutes); // 挂载快捷指令相关的路由 // 状态检查接口 app.get('/api/v1/status', (req: Request, res: Response) => { diff --git a/packages/backend/src/migrations.ts b/packages/backend/src/migrations.ts index b7d0e7d..2f75056 100644 --- a/packages/backend/src/migrations.ts +++ b/packages/backend/src/migrations.ts @@ -139,6 +139,17 @@ CREATE TABLE IF NOT EXISTS command_history ( timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); `; + +const createQuickCommandsTableSQL = ` +CREATE TABLE IF NOT EXISTS quick_commands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NULL, -- 名称可选 + command TEXT NOT NULL, -- 指令必选 + usage_count INTEGER NOT NULL DEFAULT 0, -- 使用频率 + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) +); +`; // --- 结束新增表结构定义 --- @@ -271,6 +282,15 @@ export const runMigrations = async (db: Database): Promise => { }); }); + // 创建 quick_commands 表 + await new Promise((resolve, reject) => { + db.run(createQuickCommandsTableSQL, (err: Error | null) => { + if (err) return reject(new Error(`创建 quick_commands 表时出错: ${err.message}`)); + console.log('Quick_Commands 表已检查/创建。'); + resolve(); + }); + }); + // --- 结束新增表创建逻辑 --- diff --git a/packages/backend/src/quick-commands/quick-commands.controller.ts b/packages/backend/src/quick-commands/quick-commands.controller.ts new file mode 100644 index 0000000..ae734d5 --- /dev/null +++ b/packages/backend/src/quick-commands/quick-commands.controller.ts @@ -0,0 +1,130 @@ +import { Request, Response } from 'express'; +import * as QuickCommandsService from '../services/quick-commands.service'; +import { QuickCommandSortBy } from '../services/quick-commands.service'; + +/** + * 处理添加新快捷指令的请求 + */ +export const addQuickCommand = async (req: Request, res: Response): Promise => { + const { name, command } = req.body; + + if (!command || typeof command !== 'string' || command.trim().length === 0) { + res.status(400).json({ message: '指令内容不能为空' }); + return; + } + // 名称可以是 null 或 string + if (name !== null && typeof name !== 'string') { + res.status(400).json({ message: '名称必须是字符串或 null' }); + return; + } + + try { + const newId = await QuickCommandsService.addQuickCommand(name, command); + res.status(201).json({ id: newId, message: '快捷指令已添加' }); + } catch (error: any) { + console.error('添加快捷指令控制器出错:', error); + res.status(500).json({ message: error.message || '无法添加快捷指令' }); + } +}; + +/** + * 处理获取所有快捷指令的请求 (支持排序) + */ +export const getAllQuickCommands = async (req: Request, res: Response): Promise => { + const sortBy = req.query.sortBy as QuickCommandSortBy | undefined; + // 验证 sortBy 参数 + const validSortBy: QuickCommandSortBy = (sortBy === 'name' || sortBy === 'usage_count') ? sortBy : 'name'; + + try { + const commands = await QuickCommandsService.getAllQuickCommands(validSortBy); + res.status(200).json(commands); + } catch (error: any) { + console.error('获取快捷指令控制器出错:', error); + res.status(500).json({ message: error.message || '无法获取快捷指令' }); + } +}; + +/** + * 处理更新快捷指令的请求 + */ +export const updateQuickCommand = async (req: Request, res: Response): Promise => { + const id = parseInt(req.params.id, 10); + const { name, command } = req.body; + + if (isNaN(id)) { + res.status(400).json({ message: '无效的 ID' }); + return; + } + if (!command || typeof command !== 'string' || command.trim().length === 0) { + res.status(400).json({ message: '指令内容不能为空' }); + return; + } + if (name !== null && typeof name !== 'string') { + res.status(400).json({ message: '名称必须是字符串或 null' }); + return; + } + + try { + const success = await QuickCommandsService.updateQuickCommand(id, name, command); + if (success) { + res.status(200).json({ message: '快捷指令已更新' }); + } else { + res.status(404).json({ message: '未找到要更新的快捷指令' }); + } + } catch (error: any) { + console.error('更新快捷指令控制器出错:', error); + res.status(500).json({ message: error.message || '无法更新快捷指令' }); + } +}; + +/** + * 处理删除快捷指令的请求 + */ +export const deleteQuickCommand = async (req: Request, res: Response): Promise => { + const id = parseInt(req.params.id, 10); + + if (isNaN(id)) { + res.status(400).json({ message: '无效的 ID' }); + return; + } + + try { + const success = await QuickCommandsService.deleteQuickCommand(id); + if (success) { + res.status(200).json({ message: '快捷指令已删除' }); + } else { + res.status(404).json({ message: '未找到要删除的快捷指令' }); + } + } catch (error: any) { + console.error('删除快捷指令控制器出错:', error); + res.status(500).json({ message: error.message || '无法删除快捷指令' }); + } +}; + +/** + * 处理增加快捷指令使用次数的请求 + */ +export const incrementUsage = async (req: Request, res: Response): Promise => { + const id = parseInt(req.params.id, 10); + + if (isNaN(id)) { + res.status(400).json({ message: '无效的 ID' }); + return; + } + + try { + const success = await QuickCommandsService.incrementUsageCount(id); + if (success) { + res.status(200).json({ message: '使用次数已增加' }); + } else { + // 即使没找到也可能返回成功,避免不必要的错误提示 + console.warn(`尝试增加不存在的快捷指令 (ID: ${id}) 的使用次数`); + res.status(200).json({ message: '使用次数已记录 (或指令不存在)' }); + // 或者严格一点返回 404: + // res.status(404).json({ message: '未找到要增加使用次数的快捷指令' }); + } + } catch (error: any) { + console.error('增加快捷指令使用次数控制器出错:', error); + res.status(500).json({ message: error.message || '无法增加使用次数' }); + } +}; diff --git a/packages/backend/src/quick-commands/quick-commands.routes.ts b/packages/backend/src/quick-commands/quick-commands.routes.ts new file mode 100644 index 0000000..31e03bf --- /dev/null +++ b/packages/backend/src/quick-commands/quick-commands.routes.ts @@ -0,0 +1,17 @@ +import { Router } from 'express'; +import * as QuickCommandsController from './quick-commands.controller'; +import { isAuthenticated } from '../auth/auth.middleware'; // 引入认证中间件 + +const router = Router(); + +// 应用认证中间件到所有快捷指令相关的路由 +router.use(isAuthenticated); + +// 定义路由 +router.post('/', QuickCommandsController.addQuickCommand); // POST /api/v1/quick-commands +router.get('/', QuickCommandsController.getAllQuickCommands); // GET /api/v1/quick-commands?sortBy=name|usage_count +router.put('/:id', QuickCommandsController.updateQuickCommand); // PUT /api/v1/quick-commands/:id +router.delete('/:id', QuickCommandsController.deleteQuickCommand); // DELETE /api/v1/quick-commands/:id +router.post('/:id/increment-usage', QuickCommandsController.incrementUsage); // POST /api/v1/quick-commands/:id/increment-usage + +export default router; diff --git a/packages/backend/src/repositories/quick-commands.repository.ts b/packages/backend/src/repositories/quick-commands.repository.ts new file mode 100644 index 0000000..6db08b2 --- /dev/null +++ b/packages/backend/src/repositories/quick-commands.repository.ts @@ -0,0 +1,133 @@ +import { getDb } from '../database'; + +// 定义快捷指令的接口 +export interface QuickCommand { + id: number; + name: string | null; // 名称可选 + command: string; + usage_count: number; + created_at: number; // Unix 时间戳 (秒) + updated_at: number; // Unix 时间戳 (秒) +} + +/** + * 添加一条新的快捷指令 + * @param name - 指令名称 (可选) + * @param command - 指令内容 + * @returns 返回插入记录的 ID + */ +export const addQuickCommand = (name: string | null, command: string): Promise => { + const db = getDb(); + const sql = `INSERT INTO quick_commands (name, command, created_at, updated_at) VALUES (?, ?, strftime('%s', 'now'), strftime('%s', 'now'))`; + return new Promise((resolve, reject) => { + db.run(sql, [name, command], function (err) { + if (err) { + console.error('添加快捷指令时出错:', err); + return reject(new Error('无法添加快捷指令')); + } + resolve(this.lastID); + }); + }); +}; + +/** + * 更新指定的快捷指令 + * @param id - 要更新的记录 ID + * @param name - 新的指令名称 (可选) + * @param command - 新的指令内容 + * @returns 返回更新的行数 (通常是 1 或 0) + */ +export const updateQuickCommand = (id: number, name: string | null, command: string): Promise => { + const db = getDb(); + const sql = `UPDATE quick_commands SET name = ?, command = ?, updated_at = strftime('%s', 'now') WHERE id = ?`; + return new Promise((resolve, reject) => { + db.run(sql, [name, command, id], function (err) { + if (err) { + console.error('更新快捷指令时出错:', err); + return reject(new Error('无法更新快捷指令')); + } + resolve(this.changes); + }); + }); +}; + +/** + * 根据 ID 删除指定的快捷指令 + * @param id - 要删除的记录 ID + * @returns 返回删除的行数 (通常是 1 或 0) + */ +export const deleteQuickCommand = (id: number): Promise => { + const db = getDb(); + const sql = `DELETE FROM quick_commands WHERE id = ?`; + return new Promise((resolve, reject) => { + db.run(sql, [id], function (err) { + if (err) { + console.error('删除快捷指令时出错:', err); + return reject(new Error('无法删除快捷指令')); + } + resolve(this.changes); + }); + }); +}; + +/** + * 获取所有快捷指令 + * @param sortBy - 排序字段 ('name' 或 'usage_count') + * @returns 返回包含所有快捷指令条目的数组 + */ +export const getAllQuickCommands = (sortBy: 'name' | 'usage_count' = 'name'): Promise => { + const db = getDb(); + let orderByClause = 'ORDER BY name ASC'; // 默认按名称升序 + if (sortBy === 'usage_count') { + orderByClause = 'ORDER BY usage_count DESC, name ASC'; // 按使用频率降序,同频率按名称升序 + } + // SQLite 中 NULLS LAST/FIRST 的支持可能不一致,这里简单处理 NULL 名称排在前面 + const sql = `SELECT id, name, command, usage_count, created_at, updated_at FROM quick_commands ${orderByClause}`; + return new Promise((resolve, reject) => { + db.all(sql, [], (err, rows: QuickCommand[]) => { + if (err) { + console.error('获取快捷指令时出错:', err); + return reject(new Error('无法获取快捷指令')); + } + resolve(rows); + }); + }); +}; + +/** + * 增加指定快捷指令的使用次数 + * @param id - 要增加次数的记录 ID + * @returns 返回更新的行数 (通常是 1 或 0) + */ +export const incrementUsageCount = (id: number): Promise => { + const db = getDb(); + const sql = `UPDATE quick_commands SET usage_count = usage_count + 1, updated_at = strftime('%s', 'now') WHERE id = ?`; + return new Promise((resolve, reject) => { + db.run(sql, [id], function (err) { + if (err) { + console.error('增加快捷指令使用次数时出错:', err); + return reject(new Error('无法增加快捷指令使用次数')); + } + resolve(this.changes); + }); + }); +}; + +/** + * 根据 ID 查找快捷指令 (用于编辑前获取数据) + * @param id - 要查找的记录 ID + * @returns 返回找到的快捷指令条目,如果未找到则返回 undefined + */ +export const findQuickCommandById = (id: number): Promise => { + const db = getDb(); + const sql = `SELECT id, name, command, usage_count, created_at, updated_at FROM quick_commands WHERE id = ?`; + return new Promise((resolve, reject) => { + db.get(sql, [id], (err, row: QuickCommand | undefined) => { + if (err) { + console.error('查找快捷指令时出错:', err); + return reject(new Error('无法查找快捷指令')); + } + resolve(row); + }); + }); +}; diff --git a/packages/backend/src/services/quick-commands.service.ts b/packages/backend/src/services/quick-commands.service.ts new file mode 100644 index 0000000..4c4812c --- /dev/null +++ b/packages/backend/src/services/quick-commands.service.ts @@ -0,0 +1,74 @@ +import * as QuickCommandsRepository from '../repositories/quick-commands.repository'; +import { QuickCommand } from '../repositories/quick-commands.repository'; + +// 定义排序类型 +export type QuickCommandSortBy = 'name' | 'usage_count'; + +/** + * 添加快捷指令 + * @param name - 指令名称 (可选) + * @param command - 指令内容 + * @returns 返回添加记录的 ID + */ +export const addQuickCommand = async (name: string | null, command: string): Promise => { + if (!command || command.trim().length === 0) { + throw new Error('指令内容不能为空'); + } + // 如果 name 是空字符串,则视为 null + const finalName = name && name.trim().length > 0 ? name.trim() : null; + return QuickCommandsRepository.addQuickCommand(finalName, command.trim()); +}; + +/** + * 更新快捷指令 + * @param id - 要更新的记录 ID + * @param name - 新的指令名称 (可选) + * @param command - 新的指令内容 + * @returns 返回是否成功更新 (更新行数 > 0) + */ +export const updateQuickCommand = async (id: number, name: string | null, command: string): Promise => { + if (!command || command.trim().length === 0) { + throw new Error('指令内容不能为空'); + } + const finalName = name && name.trim().length > 0 ? name.trim() : null; + const changes = await QuickCommandsRepository.updateQuickCommand(id, finalName, command.trim()); + return changes > 0; +}; + +/** + * 删除快捷指令 + * @param id - 要删除的记录 ID + * @returns 返回是否成功删除 (删除行数 > 0) + */ +export const deleteQuickCommand = async (id: number): Promise => { + const changes = await QuickCommandsRepository.deleteQuickCommand(id); + return changes > 0; +}; + +/** + * 获取所有快捷指令,并按指定方式排序 + * @param sortBy - 排序字段 ('name' 或 'usage_count') + * @returns 返回排序后的快捷指令数组 + */ +export const getAllQuickCommands = async (sortBy: QuickCommandSortBy = 'name'): Promise => { + return QuickCommandsRepository.getAllQuickCommands(sortBy); +}; + +/** + * 增加快捷指令的使用次数 + * @param id - 记录 ID + * @returns 返回是否成功更新 (更新行数 > 0) + */ +export const incrementUsageCount = async (id: number): Promise => { + const changes = await QuickCommandsRepository.incrementUsageCount(id); + return changes > 0; +}; + +/** + * 根据 ID 获取单个快捷指令 (可能用于编辑) + * @param id - 记录 ID + * @returns 返回找到的快捷指令,或 undefined + */ +export const getQuickCommandById = async (id: number): Promise => { + return QuickCommandsRepository.findQuickCommandById(id); +}; diff --git a/packages/frontend/src/components/AddEditQuickCommandForm.vue b/packages/frontend/src/components/AddEditQuickCommandForm.vue new file mode 100644 index 0000000..827e4f8 --- /dev/null +++ b/packages/frontend/src/components/AddEditQuickCommandForm.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/packages/frontend/src/components/FileManager.vue b/packages/frontend/src/components/FileManager.vue index 0ed6b3f..192a5ce 100644 --- a/packages/frontend/src/components/FileManager.vue +++ b/packages/frontend/src/components/FileManager.vue @@ -529,7 +529,7 @@ onMounted(() => { watchEffect((onCleanup) => { let unregisterSuccess: (() => void) | undefined; let unregisterError: (() => void) | undefined; - let timeoutId: number | undefined; + let timeoutId: NodeJS.Timeout | undefined; // 修正类型为 NodeJS.Timeout const cleanupListeners = () => { unregisterSuccess?.(); diff --git a/packages/frontend/src/components/TerminalTabBar.vue b/packages/frontend/src/components/TerminalTabBar.vue index 05ef530..d90f748 100644 --- a/packages/frontend/src/components/TerminalTabBar.vue +++ b/packages/frontend/src/components/TerminalTabBar.vue @@ -71,7 +71,8 @@ const paneLabels: Record = { fileManager: t('layout.pane.fileManager'), editor: t('layout.pane.editor'), statusMonitor: t('layout.pane.statusMonitor'), - commandHistory: t('layout.pane.commandHistory', '命令历史'), // 添加命令历史标签 + commandHistory: t('layout.pane.commandHistory', '命令历史'), + quickCommands: t('layout.pane.quickCommands', '快捷指令'), // 添加快捷指令标签 }; // 获取所有可控制的面板名称 diff --git a/packages/frontend/src/locales/zh.json b/packages/frontend/src/locales/zh.json index eecf9f6..76edd0a 100644 --- a/packages/frontend/src/locales/zh.json +++ b/packages/frontend/src/locales/zh.json @@ -573,5 +573,26 @@ "confirmClear": "确定要清空所有历史记录吗?", "copied": "已复制到剪贴板", "copyFailed": "复制失败" +}, +"quickCommands": { + "title": "快捷指令", + "searchPlaceholder": "搜索名称或指令...", + "add": "添加", + "sortBy": "排序:", + "sortByName": "名称", + "sortByUsage": "使用频率", + "usageCount": "使用次数", + "empty": "没有快捷指令。点击“添加”按钮创建一个吧!", + "confirmDelete": "确定要删除快捷指令 \"{name}\" 吗?", + "form": { + "titleAdd": "添加快捷指令", + "titleEdit": "编辑快捷指令", + "name": "名称:", + "namePlaceholder": "可选,用于快速识别", + "command": "指令:", + "commandPlaceholder": "例如:ls -alh /home/user", + "errorCommandRequired": "指令内容不能为空", + "add": "添加" + } } } diff --git a/packages/frontend/src/stores/layout.store.ts b/packages/frontend/src/stores/layout.store.ts index 578247c..ee33fd8 100644 --- a/packages/frontend/src/stores/layout.store.ts +++ b/packages/frontend/src/stores/layout.store.ts @@ -1,8 +1,8 @@ import { defineStore } from 'pinia'; import { ref } from 'vue'; -// 定义面板名称的类型,方便管理和引用 (添加 commandHistory) -export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory'; +// 定义面板名称的类型,方便管理和引用 (添加 quickCommands) +export type PaneName = 'connections' | 'terminal' | 'commandBar' | 'fileManager' | 'editor' | 'statusMonitor' | 'commandHistory' | 'quickCommands'; // 定义 Store export const useLayoutStore = defineStore('layout', () => { @@ -14,7 +14,8 @@ export const useLayoutStore = defineStore('layout', () => { fileManager: true, editor: true, statusMonitor: true, - commandHistory: true, // 默认可见 + commandHistory: true, + quickCommands: true, // 默认可见 }); // Action: 切换指定面板的可见性 diff --git a/packages/frontend/src/stores/quickCommands.store.ts b/packages/frontend/src/stores/quickCommands.store.ts new file mode 100644 index 0000000..e180bde --- /dev/null +++ b/packages/frontend/src/stores/quickCommands.store.ts @@ -0,0 +1,169 @@ +import { defineStore } from 'pinia'; +import axios from 'axios'; +import { ref, computed } from 'vue'; +import { useUiNotificationsStore } from './uiNotifications.store'; +import type { QuickCommand } from '../../../backend/src/repositories/quick-commands.repository'; // 复用后端类型定义 + +// 定义前端使用的快捷指令接口 (可以与后端一致) +export type QuickCommandFE = QuickCommand; + +// 定义排序类型 +export type QuickCommandSortByType = 'name' | 'usage_count'; + +export const useQuickCommandsStore = defineStore('quickCommands', () => { + const quickCommandsList = ref([]); + const searchTerm = ref(''); + const sortBy = ref('name'); // 默认按名称排序 + const isLoading = ref(false); + const error = ref(null); + const uiNotificationsStore = useUiNotificationsStore(); + + // --- Getters --- + + // 计算属性:根据搜索词过滤和排序指令 + const filteredAndSortedCommands = computed(() => { + const term = searchTerm.value.toLowerCase().trim(); + let filtered = quickCommandsList.value; + + if (term) { + filtered = filtered.filter(cmd => + (cmd.name && cmd.name.toLowerCase().includes(term)) || + cmd.command.toLowerCase().includes(term) + ); + } + + // Pinia store getter 中直接排序可能不是最佳实践,但这里为了简单起见先这样实现 + // 更好的方式可能是在 fetch 时就按需排序,或者在组件层排序 + // 注意:这里直接修改 ref 数组的顺序,如果需要在多处使用不同排序,需要创建副本 + // return [...filtered].sort((a, b) => { + // if (sortBy.value === 'usage_count') { + // // 按使用次数降序,次数相同按名称升序 + // if (b.usage_count !== a.usage_count) { + // return b.usage_count - a.usage_count; + // } + // } + // // 默认或次数相同时按名称升序 (null 名称排在前面) + // const nameA = a.name ?? ''; + // const nameB = b.name ?? ''; + // return nameA.localeCompare(nameB); + // }); + // **修正:Getter 不应修改原始数组,返回过滤后的即可,排序由 fetch 控制** + return filtered; + }); + + // --- Actions --- + + // 从后端获取快捷指令 (带排序) + const fetchQuickCommands = async () => { + isLoading.value = true; + error.value = null; + try { + const response = await axios.get('/api/v1/quick-commands', { + params: { sortBy: sortBy.value } // 将排序参数传递给后端 + }); + quickCommandsList.value = response.data; + } catch (err: any) { + console.error('获取快捷指令失败:', err); + error.value = err.response?.data?.message || '获取快捷指令时发生错误'; + uiNotificationsStore.showError(error.value ?? '未知错误'); + } finally { + isLoading.value = false; + } + }; + + // 添加快捷指令 + const addQuickCommand = async (name: string | null, command: string): Promise => { + try { + await axios.post('/api/v1/quick-commands', { name, command }); + await fetchQuickCommands(); // 添加成功后刷新列表 + uiNotificationsStore.showSuccess('快捷指令已添加'); + return true; + } catch (err: any) { + console.error('添加快捷指令失败:', err); + const message = err.response?.data?.message || '添加快捷指令时发生错误'; + uiNotificationsStore.showError(message); + return false; + } + }; + + // 更新快捷指令 + const updateQuickCommand = async (id: number, name: string | null, command: string): Promise => { + try { + await axios.put(`/api/v1/quick-commands/${id}`, { name, command }); + await fetchQuickCommands(); // 更新成功后刷新列表 + uiNotificationsStore.showSuccess('快捷指令已更新'); + return true; + } catch (err: any) { + console.error('更新快捷指令失败:', err); + const message = err.response?.data?.message || '更新快捷指令时发生错误'; + uiNotificationsStore.showError(message); + return false; + } + }; + + // 删除快捷指令 + const deleteQuickCommand = async (id: number) => { + try { + await axios.delete(`/api/v1/quick-commands/${id}`); + // 从本地列表中移除,避免重新请求 + const index = quickCommandsList.value.findIndex(cmd => cmd.id === id); + if (index !== -1) { + quickCommandsList.value.splice(index, 1); + } + uiNotificationsStore.showSuccess('快捷指令已删除'); + } catch (err: any) { + console.error('删除快捷指令失败:', err); + const message = err.response?.data?.message || '删除快捷指令时发生错误'; + uiNotificationsStore.showError(message); + } + }; + + // 增加使用次数 (调用 API,然后更新本地数据) + const incrementUsage = async (id: number) => { + try { + await axios.post(`/api/v1/quick-commands/${id}/increment-usage`); + // 更新本地计数,避免重新请求整个列表 + const command = quickCommandsList.value.find(cmd => cmd.id === id); + if (command) { + command.usage_count += 1; + // 如果当前是按使用次数排序,可能需要重新排序或刷新列表 + if (sortBy.value === 'usage_count') { + // 简单起见,重新获取并排序 + await fetchQuickCommands(); + } + } + } catch (err: any) { + console.error('增加使用次数失败:', err); + // 这里可以选择不提示用户错误,因为这是一个后台操作 + } + }; + + // 设置搜索词 + const setSearchTerm = (term: string) => { + searchTerm.value = term; + }; + + // 设置排序方式并重新获取数据 + const setSortBy = async (newSortBy: QuickCommandSortByType) => { + if (sortBy.value !== newSortBy) { + sortBy.value = newSortBy; + await fetchQuickCommands(); // 排序方式改变,重新获取数据 + } + }; + + return { + quickCommandsList, + searchTerm, + sortBy, + isLoading, + error, + filteredAndSortedCommands, // 使用计算属性 + fetchQuickCommands, + addQuickCommand, + updateQuickCommand, + deleteQuickCommand, + incrementUsage, + setSearchTerm, + setSortBy, + }; +}); diff --git a/packages/frontend/src/views/QuickCommandsView.vue b/packages/frontend/src/views/QuickCommandsView.vue new file mode 100644 index 0000000..b0ea52c --- /dev/null +++ b/packages/frontend/src/views/QuickCommandsView.vue @@ -0,0 +1,313 @@ + + + + + diff --git a/packages/frontend/src/views/WorkspaceView.vue b/packages/frontend/src/views/WorkspaceView.vue index af7aa43..ab7e1c3 100644 --- a/packages/frontend/src/views/WorkspaceView.vue +++ b/packages/frontend/src/views/WorkspaceView.vue @@ -11,6 +11,7 @@ import TerminalTabBar from '../components/TerminalTabBar.vue'; import CommandInputBar from '../components/CommandInputBar.vue'; import FileEditorContainer from '../components/FileEditorContainer.vue'; // 导入编辑器容器 import CommandHistoryView from './CommandHistoryView.vue'; // 导入命令历史视图 +import QuickCommandsView from './QuickCommandsView.vue'; // 导入快捷指令视图 import PaneTitleBar from '../components/PaneTitleBar.vue'; // 导入标题栏组件 import { useSessionStore, type SessionTabInfoWithStatus, type SshTerminalInstance } from '../stores/session.store'; // 导入 SshTerminalInstance import { useSettingsStore } from '../stores/settings.store'; // 导入设置 Store @@ -124,10 +125,10 @@ onBeforeUnmount(() => { // 否则,正常发送命令 if (terminalManager && typeof terminalManager.sendData === 'function') { - const commandToSend = command.trim(); // 获取去除首尾空格的命令 + const commandToSend = command.trim(); // 获取去除首尾空格的命令,用于记录历史 console.log(`[WorkspaceView] Sending command to active session ${currentSession.sessionId}: ${commandToSend}`); - // 注意:CommandInputBar 已经添加了 '\n' - terminalManager.sendData(command); // 发送原始命令(包含换行符) + // 发送给终端时,需要添加回车符以模拟执行 + terminalManager.sendData(command + '\r'); // 记录非空命令到历史记录 if (commandToSend.length > 0) { @@ -257,11 +258,16 @@ onBeforeUnmount(() => { - + + + + + + - + @@ -414,7 +420,8 @@ onBeforeUnmount(() => { .file-manager-area-pane, /* 文件管理器区域 Pane */ .file-manager-pane, /* 内部文件管理器 Pane */ .status-monitor-pane, /* 状态监视器样式 */ -.command-history-pane { /* 命令历史窗格样式 */ +.command-history-pane, /* 命令历史窗格样式 */ +.quick-commands-pane { /* 快捷指令窗格样式 */ display: flex; /* 确保 flex 布局 */ flex-direction: column; /* 确保列方向 */ overflow: hidden; /* 默认隐藏溢出 */ @@ -484,6 +491,9 @@ onBeforeUnmount(() => { .command-history-pane { background-color: #f8f9fa; /* 与其他侧边栏一致 */ } +.quick-commands-pane { + background-color: #f8f9fa; /* 与其他侧边栏一致 */ +} .status-monitor-content-wrapper { text-align: center; padding: 1rem;