diff --git a/frontend/src/layouts/components/TagsView/index.vue b/frontend/src/layouts/components/TagsView/index.vue index 0e2cb041..20c793bc 100644 --- a/frontend/src/layouts/components/TagsView/index.vue +++ b/frontend/src/layouts/components/TagsView/index.vue @@ -10,41 +10,63 @@ - - + + {{ translateRouteTitle(tag.title) }} - {{ translateRouteTitle(tag.title) }} + @@ -143,6 +168,9 @@ import { resolve } from "path-browserify"; import { translateRouteTitle } from "@/utils/i18n"; import { usePermissionStore, useTagsViewStore } from "@/store"; import { VueDraggable } from "vue-draggable-plus"; +import { quickStartManager } from "@/utils/quickStartManager"; +import { Star } from "@element-plus/icons-vue"; +import { ElMessage } from 'element-plus'; const { t } = useI18n(); @@ -420,16 +448,36 @@ const handleAction = (action: string) => { /** * 右键菜单显示状态变化处理函数 */ -const onContextMenuVisibleChange = (visible: boolean) => { +const onContextMenuVisibleChange = (visible: boolean, tag?: TagView) => { if (visible) { - // 总是获取最新的路由对应标签 - selectedTag.value = routePathMap.value.get(route.path) || null; + // 设置当前右键点击的标签 + selectedTag.value = tag || routePathMap.value.get(route.path) || null; } else { // 关闭菜单时清空选择 selectedTag.value = null; } }; +/** + * 添加到快速开始 + */ +const addToQuickStart = (tag: TagView) => { + try { + const quickLink = quickStartManager.createQuickLinkFromRoute(tag); + quickStartManager.addQuickLink(quickLink); + } catch (error) { + console.error('Failed to add to quick start:', error); + ElMessage.error('添加到快速开始失败'); + } +}; + +/** + * 检查快速链接是否已存在 + */ +const isQuickLinkExists = (tag: TagView): boolean => { + return quickStartManager.isLinkExists(tag.fullPath || tag.path); +}; + /** * 向左滚动标签页 */ diff --git a/frontend/src/types/auto-imports.d.ts b/frontend/src/types/auto-imports.d.ts index c53d8cf8..8b71b5d8 100644 --- a/frontend/src/types/auto-imports.d.ts +++ b/frontend/src/types/auto-imports.d.ts @@ -270,6 +270,7 @@ declare global { const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl'] const useTimeout: typeof import('@vueuse/core')['useTimeout'] const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] @@ -585,6 +586,7 @@ declare module 'vue' { readonly useThrottleFn: UnwrapRef readonly useThrottledRefHistory: UnwrapRef readonly useTimeAgo: UnwrapRef + readonly useTimeAgoIntl: UnwrapRef readonly useTimeout: UnwrapRef readonly useTimeoutFn: UnwrapRef readonly useTimeoutPoll: UnwrapRef diff --git a/frontend/src/types/components.d.ts b/frontend/src/types/components.d.ts index f71061b0..c241db18 100644 --- a/frontend/src/types/components.d.ts +++ b/frontend/src/types/components.d.ts @@ -8,6 +8,7 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + AddQuickLinkDialog: typeof import('./../views/dashboard/components/AddQuickLinkDialog.vue')['default'] AppLink: typeof import('./../components/AppLink/index.vue')['default'] AppLogo: typeof import('./../layouts/components/AppLogo/index.vue')['default'] AppMain: typeof import('./../layouts/components/AppMain/index.vue')['default'] @@ -112,6 +113,7 @@ declare module 'vue' { PageSearch: typeof import('./../components/CURD/PageSearch.vue')['default'] Pagination: typeof import('./../components/Pagination/index.vue')['default'] PermissonDrawer: typeof import('./../views/system/role/components/PermissonDrawer.vue')['default'] + QuickStart: typeof import('./../views/dashboard/components/QuickStart.vue')['default'] Register: typeof import('./../views/system/auth/components/Register.vue')['default'] ResetPwd: typeof import('./../views/system/auth/components/ResetPwd.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/frontend/src/utils/quickStartManager.ts b/frontend/src/utils/quickStartManager.ts new file mode 100644 index 00000000..43ab7d94 --- /dev/null +++ b/frontend/src/utils/quickStartManager.ts @@ -0,0 +1,185 @@ +import { ElMessage } from 'element-plus'; + +// 快速链接数据类型 +export interface QuickLink { + title: string; + description: string; + icon: string; + color: string; + href: string; + action: 'navigate' | 'external'; + id?: string; +} + +// 快速开始管理器类 +class QuickStartManager { + private storageKey = 'quick-start-links'; + private listeners: Array<(links: QuickLink[]) => void> = []; + + // 获取所有快速链接 + getQuickLinks(): QuickLink[] { + try { + const stored = localStorage.getItem(this.storageKey); + return stored ? JSON.parse(stored) : this.getDefaultLinks(); + } catch (error) { + console.error('Failed to load quick links:', error); + return this.getDefaultLinks(); + } + } + + // 获取默认链接 + private getDefaultLinks(): QuickLink[] { + return [ + { + id: 'user-management', + title: "用户管理", + description: "管理系统用户信息", + icon: "User", + color: "#409EFF", + href: "/system/user", + action: "navigate" + }, + { + id: 'monitor', + title: "系统监控", + description: "监控系统状态", + icon: "Monitor", + color: "#909399", + href: "/monitor", + action: "navigate" + }, + { + id: 'baidu', + title: "百度搜索", + description: "访问百度搜索引擎", + icon: "Search", + color: "#3385FF", + href: "https://www.baidu.com", + action: "external" + }, + { + id: 'github', + title: "GitHub", + description: "访问代码托管平台", + icon: "Monitor", + color: "#24292e", + href: "https://github.com", + action: "external" + } + ]; + } + + // 保存快速链接 + saveQuickLinks(links: QuickLink[]): void { + try { + localStorage.setItem(this.storageKey, JSON.stringify(links)); + this.notifyListeners(links); + } catch (error) { + console.error('Failed to save quick links:', error); + ElMessage.error('保存快速链接失败'); + } + } + + // 添加快速链接 + addQuickLink(link: QuickLink): void { + const links = this.getQuickLinks(); + + // 生成唯一ID + link.id = link.id || `link-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + // 检查是否已存在相同路径的链接 + const existingIndex = links.findIndex(l => l.href === link.href); + if (existingIndex !== -1) { + // 更新现有链接 + links[existingIndex] = { ...links[existingIndex], ...link }; + ElMessage.success(`已更新快速链接:${link.title}`); + } else { + // 添加新链接 + links.push(link); + ElMessage.success(`已添加快速链接:${link.title}`); + } + + this.saveQuickLinks(links); + } + + // 删除快速链接 + removeQuickLink(id: string): void { + const links = this.getQuickLinks(); + const filteredLinks = links.filter(link => link.id !== id); + + if (filteredLinks.length < links.length) { + this.saveQuickLinks(filteredLinks); + ElMessage.success('已删除快速链接'); + } + } + + // 从路由信息创建快速链接 + createQuickLinkFromRoute(route: any, customTitle?: string, customDescription?: string): QuickLink { + // 根据路径推断图标和颜色 + const iconMap: Record = { + '/dashboard': { icon: 'House', color: '#409EFF' }, + '/system': { icon: 'Setting', color: '#67C23A' }, + '/user': { icon: 'User', color: '#409EFF' }, + '/role': { icon: 'UserFilled', color: '#E6A23C' }, + '/menu': { icon: 'Menu', color: '#F56C6C' }, + '/dept': { icon: 'OfficeBuilding', color: '#909399' }, + '/monitor': { icon: 'Monitor', color: '#606266' }, + '/log': { icon: 'Document', color: '#FF6B6B' }, + '/codegen': { icon: 'Code', color: '#4ECDC4' }, + '/demo': { icon: 'DataAnalysis', color: '#3385FF' }, + }; + + // 查找匹配的图标配置 + let iconConfig = { icon: 'Link', color: '#909399' }; // 默认配置 + for (const [path, config] of Object.entries(iconMap)) { + if (route.path.includes(path)) { + iconConfig = config; + break; + } + } + + return { + title: customTitle || route.meta?.title || route.name || '未命名页面', + description: customDescription || `快速访问 ${route.meta?.title || route.name || '页面'}`, + icon: iconConfig.icon, + color: iconConfig.color, + href: route.fullPath || route.path, + action: 'navigate', + id: `route-${route.path.replace(/\//g, '-')}-${Date.now()}` + }; + } + + // 添加监听器 + addListener(callback: (links: QuickLink[]) => void): void { + this.listeners.push(callback); + } + + // 移除监听器 + removeListener(callback: (links: QuickLink[]) => void): void { + const index = this.listeners.indexOf(callback); + if (index > -1) { + this.listeners.splice(index, 1); + } + } + + // 通知所有监听器 + private notifyListeners(links: QuickLink[]): void { + this.listeners.forEach(callback => { + try { + callback(links); + } catch (error) { + console.error('Error in quick start listener:', error); + } + }); + } + + // 检查链接是否已存在 + isLinkExists(href: string): boolean { + const links = this.getQuickLinks(); + return links.some(link => link.href === href); + } +} + +// 创建全局实例 +export const quickStartManager = new QuickStartManager(); + diff --git a/frontend/src/views/dashboard/components/AddQuickLinkDialog.vue b/frontend/src/views/dashboard/components/AddQuickLinkDialog.vue new file mode 100644 index 00000000..8d6416b5 --- /dev/null +++ b/frontend/src/views/dashboard/components/AddQuickLinkDialog.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/frontend/src/views/dashboard/components/QuickStart.vue b/frontend/src/views/dashboard/components/QuickStart.vue new file mode 100644 index 00000000..bacddb95 --- /dev/null +++ b/frontend/src/views/dashboard/components/QuickStart.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/frontend/src/views/dashboard/components/README.md b/frontend/src/views/dashboard/components/README.md new file mode 100644 index 00000000..9f1c7f0a --- /dev/null +++ b/frontend/src/views/dashboard/components/README.md @@ -0,0 +1,160 @@ +# 快速开始组件功能说明 + +## 概述 + +快速开始组件提供了一个便捷的导航面板,用户可以快速访问常用的页面和外部链接。支持从标签栏右键收藏页面到快速开始列表。 + +## 功能特性 + +### 1. 快速链接管理 +- ✅ 显示预设的常用链接 +- ✅ 支持内部路由跳转和外部链接访问 +- ✅ 固定高度容器,内容可滚动 +- ✅ 响应式设计,适配不同屏幕尺寸 + +### 2. 标签栏右键收藏 +- ✅ 在任意标签页右击可收藏到快速开始 +- ✅ 已收藏的页面显示"已收藏"状态 +- ✅ 自动生成合适的图标和颜色 +- ✅ 防止重复收藏 + +### 3. 手动添加链接 +- ✅ 通过对话框手动添加自定义链接 +- ✅ 支持选择图标和自定义颜色 +- ✅ 表单验证确保数据正确性 +- ✅ 支持内部链接和外部链接 + +### 4. 右键管理链接 +- ✅ 在快速链接上右击可编辑或删除 +- ✅ 编辑功能支持修改所有属性 +- ✅ 删除功能带有确认对话框 +- ✅ 防止误操作的安全提示 + +### 4. 数据持久化 +- ✅ 使用 localStorage 保存用户配置 +- ✅ 页面刷新后数据不丢失 +- ✅ 支持导入/导出配置(可扩展) + +## 使用方法 + +### 从标签栏收藏页面 + +1. 在任意页面的标签上右击 +2. 选择"收藏到快速开始" +3. 系统会自动添加到快速开始列表 +4. 前往工作台查看新添加的链接 + +### 手动添加链接 + +1. 在快速开始组件中点击"添加"按钮 +2. 填写链接信息: + - 标题:链接显示名称 + - 描述:链接说明文字 + - 链接地址:内部路径或外部URL + - 链接类型:内部链接或外部链接 + - 图标:从预设图标中选择 + - 颜色:自定义图标颜色 +3. 点击确定保存 + +### 管理快速链接 + +#### 右键菜单操作 + +1. **编辑链接**: + - 在快速开始组件中右击任意链接 + - 选择"编辑链接" + - 修改链接信息后保存 + +2. **删除链接**: + - 在快速开始组件中右击任意链接 + - 选择"删除链接" + - 确认删除操作 + +### 使用快速链接 + +- **内部链接**:点击后在当前窗口跳转 +- **外部链接**:点击后在新标签页打开,带有"外链"标识 + +## 技术实现 + +### 组件结构 + +``` +dashboard/components/ +├── QuickStart.vue # 主要的快速开始组件 +├── AddQuickLinkDialog.vue # 添加快速链接对话框 +├── index.ts # 组件导出文件 +└── README.md # 功能说明文档 +``` + +### 核心文件 + +- `QuickStart.vue`: 主组件,负责显示快速链接列表 +- `AddQuickLinkDialog.vue`: 添加/编辑对话框 +- `utils/quickStartManager.ts`: 全局数据管理器 +- `layouts/components/TagsView/index.vue`: 标签栏组件(已修改) + +### 数据管理 + +使用 `quickStartManager` 全局管理器: + +```typescript +import { quickStartManager } from '@/utils/quickStartManager'; + +// 获取所有链接 +const links = quickStartManager.getQuickLinks(); + +// 添加链接 +quickStartManager.addQuickLink(newLink); + +// 检查链接是否存在 +const exists = quickStartManager.isLinkExists(href); + +// 监听数据变化 +quickStartManager.addListener(callback); +``` + +### 图标配置 + +支持的图标类型: +- User, Setting, Document, DataAnalysis +- Monitor, Tools, Bell, Message, Search +- House, Files, Calendar, ChatDotRound +- Connection, DataBoard, Histogram, PieChart +- TrendCharts, Operation, Service, Guide, Link + +### 路径映射 + +系统会根据路径自动推断合适的图标: + +```typescript +const iconMap = { + '/dashboard': { icon: 'House', color: '#409EFF' }, + '/system': { icon: 'Setting', color: '#67C23A' }, + '/user': { icon: 'User', color: '#409EFF' }, + // ... 更多映射 +}; +``` + +### 自定义配置 + +可以通过修改 `quickStartManager.ts` 中的默认配置来自定义预设链接: + +```typescript +private getDefaultLinks(): QuickLink[] { + return [ + // 自定义默认链接 + ]; +} +``` + +## 注意事项 + +1. **存储限制**: localStorage 有大小限制,建议控制链接数量 +2. **图标依赖**: 确保使用的图标已在 Element Plus 中导入 +3. **路由匹配**: 内部链接需要确保路由存在 +4. **安全性**: 外部链接会在新窗口打开,已添加安全参数 + +## 测试页面 + +访问 `/demo/quick-start-test` 可以测试收藏功能的完整流程。 diff --git a/frontend/src/views/dashboard/components/index.ts b/frontend/src/views/dashboard/components/index.ts new file mode 100644 index 00000000..f7e9f364 --- /dev/null +++ b/frontend/src/views/dashboard/components/index.ts @@ -0,0 +1,2 @@ +export { default as QuickStart } from './QuickStart.vue'; +export { default as AddQuickLinkDialog } from './AddQuickLinkDialog.vue'; diff --git a/frontend/src/views/dashboard/workplace.vue b/frontend/src/views/dashboard/workplace.vue index 643d227c..d3332fdb 100644 --- a/frontend/src/views/dashboard/workplace.vue +++ b/frontend/src/views/dashboard/workplace.vue @@ -91,21 +91,7 @@ - - -
- - {{ item.title }} - -
-
+ @@ -136,6 +122,7 @@ import { Plus } from "@element-plus/icons-vue"; import { EChartsOption } from 'echarts' import { useUserStore } from "@/store/index"; import { greetings } from '@/utils/common'; +import QuickStart from './components/QuickStart.vue'; const { t } = useI18n(); @@ -360,32 +347,7 @@ const chartOptions = reactive({ }] }); -const links = [ - { - title: "操作一", - href: "", - }, - { - title: "操作二", - href: "", - }, - { - title: "操作三", - href: "", - }, - { - title: "操作四", - href: "", - }, - { - title: "操作五", - href: "", - }, - { - title: "操作六", - href: "", - }, -]; + @@ -413,19 +375,5 @@ const links = [ } } -.linkGroup { - padding: 20px 0 8px 24px; - font-size: 0; - &>a { - display: inline-block; - width: 25%; - margin-bottom: 13px; - font-size: 14px; - - &:hover { - color: #1890ff; - } - } -} diff --git a/frontend/src/views/demo/quick-start-test.vue b/frontend/src/views/demo/quick-start-test.vue new file mode 100644 index 00000000..bae655b6 --- /dev/null +++ b/frontend/src/views/demo/quick-start-test.vue @@ -0,0 +1,200 @@ + + + + +