mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 05:10:57 +00:00
fix: 优化快速开始/便捷导航
This commit is contained in:
Vendored
+2
@@ -325,6 +325,7 @@ declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
|
||||
readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
|
||||
readonly ElNotification: UnwrapRef<typeof import('element-plus/es')['ElNotification']>
|
||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||
@@ -586,6 +587,7 @@ declare module 'vue' {
|
||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
|
||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||
|
||||
@@ -5,7 +5,6 @@ export interface QuickLink {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
href: string;
|
||||
action: 'navigate' | 'external';
|
||||
id?: string;
|
||||
@@ -35,7 +34,6 @@ class QuickStartManager {
|
||||
title: "用户管理",
|
||||
description: "管理系统用户信息",
|
||||
icon: "User",
|
||||
color: "#409EFF",
|
||||
href: "/system/user",
|
||||
action: "navigate"
|
||||
},
|
||||
@@ -44,7 +42,6 @@ class QuickStartManager {
|
||||
title: "系统监控",
|
||||
description: "监控系统状态",
|
||||
icon: "Monitor",
|
||||
color: "#909399",
|
||||
href: "/monitor",
|
||||
action: "navigate"
|
||||
},
|
||||
@@ -53,7 +50,6 @@ class QuickStartManager {
|
||||
title: "百度搜索",
|
||||
description: "访问百度搜索引擎",
|
||||
icon: "Search",
|
||||
color: "#3385FF",
|
||||
href: "https://www.baidu.com",
|
||||
action: "external"
|
||||
},
|
||||
@@ -62,7 +58,6 @@ class QuickStartManager {
|
||||
title: "GitHub",
|
||||
description: "访问代码托管平台",
|
||||
icon: "Monitor",
|
||||
color: "#24292e",
|
||||
href: "https://github.com",
|
||||
action: "external"
|
||||
}
|
||||
@@ -177,7 +172,6 @@ class QuickStartManager {
|
||||
title: customTitle || route.meta?.title || route.name || '未命名页面',
|
||||
description: customDescription || `快速访问 ${route.meta?.title || route.name || '页面'}`,
|
||||
icon: routeIcon,
|
||||
color: routeColor,
|
||||
href: route.fullPath || route.path,
|
||||
action: 'navigate',
|
||||
id: `route-${route.path.replace(/\//g, '-')}-${Date.now()}`
|
||||
|
||||
@@ -1,406 +0,0 @@
|
||||
# Dashboard 仪表板模块
|
||||
|
||||
## 概述
|
||||
|
||||
Dashboard 仪表板模块是系统的核心展示区域,提供了数据概览、快速导航、工作台等功能。包含工作台页面、数据分析页面以及可复用的组件库。
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
dashboard/
|
||||
├── workplace.vue # 工作台主页面
|
||||
├── analysis.vue # 数据分析页面
|
||||
├── components/ # 可复用组件
|
||||
│ ├── QuickStart.vue # 快速开始组件
|
||||
│ ├── AddQuickLinkDialog.vue # 添加快速链接对话框
|
||||
│ └── index.ts # 组件导出文件
|
||||
└── README.md # 模块说明文档
|
||||
```
|
||||
|
||||
## 页面功能
|
||||
|
||||
### 1. 工作台页面 (workplace.vue)
|
||||
|
||||
**核心功能:**
|
||||
- ✅ 用户欢迎信息展示
|
||||
- ✅ 进行中的项目列表
|
||||
- ✅ 动态时间线
|
||||
- ✅ 快速开始/便捷导航
|
||||
- ✅ 数据指数图表
|
||||
- ✅ 团队成员展示
|
||||
|
||||
**布局特点:**
|
||||
- 响应式栅格布局
|
||||
- 左右分栏设计
|
||||
- 卡片式组件展示
|
||||
- 移动端适配
|
||||
|
||||
### 2. 数据分析页面 (analysis.vue)
|
||||
|
||||
**主要功能:**
|
||||
- ✅ 数据统计图表
|
||||
- ✅ 趋势分析展示
|
||||
- ✅ 实时数据监控
|
||||
- ✅ 可视化数据报表
|
||||
|
||||
## 组件功能
|
||||
|
||||
### 1. 快速开始组件 (QuickStart.vue)
|
||||
|
||||
#### 核心特性
|
||||
- ✅ 快速链接管理和展示
|
||||
- ✅ 支持内部路由跳转和外部链接访问
|
||||
- ✅ 固定高度容器,内容可滚动
|
||||
- ✅ 响应式设计,适配不同屏幕尺寸
|
||||
|
||||
#### 标签栏右键收藏
|
||||
- ✅ 在任意标签页右击可收藏到快速开始
|
||||
- ✅ 支持切换收藏状态(收藏/取消收藏)
|
||||
- ✅ 自动获取路由配置的图标
|
||||
- ✅ 智能图标匹配和颜色配置
|
||||
- ✅ 实时状态显示和图标切换
|
||||
|
||||
#### 手动添加链接
|
||||
- ✅ 通过对话框手动添加自定义链接
|
||||
- ✅ 支持选择图标和自定义颜色
|
||||
- ✅ 表单验证确保数据正确性
|
||||
- ✅ 支持内部链接和外部链接
|
||||
|
||||
#### 右键管理链接
|
||||
- ✅ 在快速链接上右击可编辑或删除
|
||||
- ✅ 编辑功能自动预填充现有数据
|
||||
- ✅ 删除功能带有确认对话框
|
||||
- ✅ 防止误操作的安全提示
|
||||
|
||||
#### 数据持久化
|
||||
- ✅ 使用 localStorage 保存用户配置
|
||||
- ✅ 页面刷新后数据不丢失
|
||||
- ✅ 支持导入/导出配置(可扩展)
|
||||
|
||||
### 2. 添加快速链接对话框 (AddQuickLinkDialog.vue)
|
||||
|
||||
#### 功能特性
|
||||
- ✅ 支持新增和编辑两种模式
|
||||
- ✅ 完整的表单验证
|
||||
- ✅ 图标选择器(22个常用图标)
|
||||
- ✅ 颜色自定义选择
|
||||
- ✅ 链接类型区分(内部/外部)
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 访问 Dashboard
|
||||
|
||||
#### 工作台页面
|
||||
- **路径**:`/dashboard/workplace`
|
||||
- **功能**:个人工作台,包含项目概览、动态信息、快速导航等
|
||||
- **适用场景**:日常工作的起始页面
|
||||
|
||||
#### 数据分析页面
|
||||
- **路径**:`/dashboard/analysis`
|
||||
- **功能**:数据统计和分析展示
|
||||
- **适用场景**:查看系统数据和业务指标
|
||||
|
||||
### 快速开始组件使用
|
||||
|
||||
#### 从标签栏收藏页面
|
||||
|
||||
**收藏页面:**
|
||||
1. 在任意页面的标签上右击
|
||||
2. 选择"收藏到快速开始"
|
||||
3. 系统会自动获取路由图标和颜色
|
||||
4. 前往工作台查看新添加的链接
|
||||
|
||||
**取消收藏:**
|
||||
1. 在已收藏页面的标签上右击
|
||||
2. 选择"取消收藏"(图标会显示为实心星星)
|
||||
3. 链接立即从快速开始列表中移除
|
||||
|
||||
**自动图标获取:**
|
||||
- **优先级1**:使用路由 `meta.icon` 配置
|
||||
- **优先级2**:根据路径智能匹配预设图标
|
||||
- **优先级3**:使用默认链接图标
|
||||
|
||||
#### 手动添加链接
|
||||
|
||||
1. 在快速开始组件中点击"添加"按钮
|
||||
2. 填写链接信息:
|
||||
- **标题**:链接显示名称
|
||||
- **描述**:链接说明文字
|
||||
- **链接地址**:内部路径或外部URL
|
||||
- **链接类型**:内部链接或外部链接
|
||||
- **图标**:从预设图标中选择
|
||||
- **颜色**:自定义图标颜色
|
||||
3. 点击确定保存
|
||||
|
||||
#### 管理快速链接
|
||||
|
||||
**编辑链接:**
|
||||
1. 在快速开始组件中右击任意链接
|
||||
2. 选择"编辑链接"
|
||||
3. 对话框会自动填充现有的链接信息
|
||||
4. 修改需要更改的字段
|
||||
5. 点击"确定"保存修改
|
||||
|
||||
**删除链接:**
|
||||
1. 在快速开始组件中右击任意链接
|
||||
2. 选择"删除链接"
|
||||
3. 在确认对话框中点击"确定"
|
||||
|
||||
#### 使用快速链接
|
||||
|
||||
- **内部链接**:点击后在当前窗口跳转
|
||||
- **外部链接**:点击后在新标签页打开,带有"外链"标识
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
|
||||
```text
|
||||
dashboard/
|
||||
├── workplace.vue # 工作台主页面
|
||||
├── analysis.vue # 数据分析页面
|
||||
├── components/ # 组件库
|
||||
│ ├── QuickStart.vue # 快速开始组件
|
||||
│ ├── AddQuickLinkDialog.vue # 添加链接对话框
|
||||
│ └── index.ts # 组件导出
|
||||
└── README.md # 模块文档
|
||||
```
|
||||
|
||||
### 核心技术栈
|
||||
|
||||
- **Vue 3**: Composition API + `<script setup>`
|
||||
- **TypeScript**: 完整的类型定义
|
||||
- **Element Plus**: UI 组件库
|
||||
- **ECharts**: 数据可视化图表
|
||||
- **Pinia**: 状态管理(如需要)
|
||||
- **Vue Router**: 路由管理
|
||||
|
||||
### 数据管理
|
||||
|
||||
#### 快速开始数据管理器
|
||||
|
||||
```typescript
|
||||
// utils/quickStartManager.ts
|
||||
import { quickStartManager } from '@/utils/quickStartManager';
|
||||
|
||||
// 获取所有链接
|
||||
const links = quickStartManager.getQuickLinks();
|
||||
|
||||
// 添加链接
|
||||
quickStartManager.addQuickLink(newLink);
|
||||
|
||||
// 检查链接是否存在
|
||||
const exists = quickStartManager.isLinkExists(href);
|
||||
|
||||
// 监听数据变化
|
||||
quickStartManager.addListener(callback);
|
||||
```
|
||||
|
||||
#### 本地存储
|
||||
|
||||
- **存储方式**: localStorage
|
||||
- **数据格式**: JSON
|
||||
- **存储键**: `quick-start-links`
|
||||
- **容错处理**: 存储失败时的降级方案
|
||||
|
||||
### 组件设计
|
||||
|
||||
#### 1. 工作台页面 (workplace.vue)
|
||||
|
||||
**技术特点:**
|
||||
- 响应式栅格布局 (`ElRow` + `ElCol`)
|
||||
- 组件化设计,功能模块独立
|
||||
- 数据驱动的动态渲染
|
||||
- 移动端适配
|
||||
|
||||
**主要组件:**
|
||||
```vue
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 用户信息卡片 -->
|
||||
<ElCard>用户欢迎信息</ElCard>
|
||||
|
||||
<ElRow :gutter="16">
|
||||
<!-- 左侧内容区 -->
|
||||
<ElCol :xl="16">
|
||||
<ProjectList /> <!-- 项目列表 -->
|
||||
<ActivityTimeline /> <!-- 动态时间线 -->
|
||||
</ElCol>
|
||||
|
||||
<!-- 右侧侧边栏 -->
|
||||
<ElCol :xl="8">
|
||||
<QuickStart /> <!-- 快速开始 -->
|
||||
<DataChart /> <!-- 数据图表 -->
|
||||
<TeamMembers /> <!-- 团队成员 -->
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 2. 快速开始组件 (QuickStart.vue)
|
||||
|
||||
**核心特性:**
|
||||
- 虚拟滚动优化(大量链接时)
|
||||
- 右键菜单交互
|
||||
- 拖拽排序(可扩展)
|
||||
- 实时数据同步
|
||||
|
||||
**组件架构:**
|
||||
```typescript
|
||||
// 数据响应式
|
||||
const quickLinks = ref<QuickLink[]>([]);
|
||||
const selectedLink = ref<QuickLink | null>(null);
|
||||
|
||||
// 事件处理
|
||||
const handleQuickLinkClick = (item: QuickLink) => { /* 跳转逻辑 */ };
|
||||
const handleEditLink = (item: QuickLink) => { /* 编辑逻辑 */ };
|
||||
const handleDeleteLink = (item: QuickLink) => { /* 删除逻辑 */ };
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => quickStartManager.addListener(updateQuickLinks));
|
||||
onUnmounted(() => quickStartManager.removeListener(updateQuickLinks));
|
||||
```
|
||||
|
||||
### 图标系统
|
||||
|
||||
#### 支持的图标类型
|
||||
|
||||
**Element Plus 图标:**
|
||||
- User, Setting, Document, DataAnalysis
|
||||
- Monitor, Tools, Bell, Message, Search
|
||||
- House, Files, Calendar, ChatDotRound
|
||||
- Connection, DataBoard, Histogram, PieChart
|
||||
- TrendCharts, Operation, Service, Guide, Link
|
||||
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 组件优化
|
||||
|
||||
- **虚拟滚动**: 大量快速链接时使用虚拟滚动
|
||||
- **懒加载**: 图表组件按需加载
|
||||
- **缓存策略**: localStorage 本地缓存
|
||||
- **数据同步**: 事件监听机制
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 已规划的功能
|
||||
|
||||
1. **权限控制**: 根据用户角色显示不同的快速链接
|
||||
2. **分组管理**: 将快速链接按类别分组显示
|
||||
3. **拖拽排序**: 支持用户自定义链接顺序
|
||||
4. **导入导出**: 支持配置的导入和导出
|
||||
5. **使用统计**: 记录链接点击次数和使用频率
|
||||
6. **云端同步**: 将配置同步到云端账户
|
||||
7. **主题定制**: 支持自定义主题和样式
|
||||
8. **快捷键**: 支持键盘快捷键操作
|
||||
|
||||
### 自定义配置
|
||||
|
||||
#### 修改默认链接
|
||||
|
||||
```typescript
|
||||
// utils/quickStartManager.ts
|
||||
private getDefaultLinks(): QuickLink[] {
|
||||
return [
|
||||
{
|
||||
id: 'custom-link',
|
||||
title: "自定义链接",
|
||||
description: "这是一个自定义的快速链接",
|
||||
icon: "Star",
|
||||
color: "#409EFF",
|
||||
href: "/custom-page",
|
||||
action: "navigate"
|
||||
}
|
||||
// 更多自定义链接...
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
#### 扩展图标映射
|
||||
|
||||
```typescript
|
||||
// utils/quickStartManager.ts
|
||||
const iconMap = {
|
||||
'/custom-module': { icon: 'CustomIcon', color: '#FF6B6B' },
|
||||
// 添加更多路径映射...
|
||||
};
|
||||
```
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新页面
|
||||
|
||||
1. **创建页面组件**: `dashboard/new-page.vue`
|
||||
2. **配置路由**: 在 `router/index.ts` 中添加路由配置
|
||||
3. **添加图标映射**: 在 `quickStartManager.ts` 中添加路径映射
|
||||
|
||||
### 创建新组件
|
||||
|
||||
1. **创建组件文件**: `dashboard/components/NewComponent.vue`
|
||||
2. **导出组件**: 在 `components/index.ts` 中导出
|
||||
3. **在页面中使用**: 导入并使用组件
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 性能注意事项
|
||||
|
||||
- **存储限制**: localStorage 有大小限制(通常5-10MB)
|
||||
- **内存管理**: 及时清理事件监听器,避免内存泄漏
|
||||
- **渲染优化**: 大量数据时考虑虚拟滚动或分页
|
||||
|
||||
### 兼容性注意事项
|
||||
|
||||
- **浏览器支持**: 确保目标浏览器支持使用的 API
|
||||
- **图标依赖**: 确保使用的图标已正确导入
|
||||
- **路由匹配**: 内部链接需要确保对应路由存在
|
||||
|
||||
### 安全注意事项
|
||||
|
||||
- **外部链接**: 使用 `noopener,noreferrer` 参数
|
||||
- **XSS 防护**: 对用户输入进行适当的转义
|
||||
- **权限控制**: 敏感功能需要权限验证
|
||||
|
||||
## 测试
|
||||
|
||||
### 功能测试
|
||||
|
||||
访问 `/demo/quick-start-test` 进行完整的功能测试:
|
||||
|
||||
1. **收藏功能测试**
|
||||
2. **编辑功能测试**
|
||||
3. **删除功能测试**
|
||||
4. **图标获取测试**
|
||||
5. **数据持久化测试**
|
||||
|
||||
### 单元测试
|
||||
|
||||
```typescript
|
||||
// tests/dashboard/QuickStart.spec.ts
|
||||
import { mount } from '@vue/test-utils';
|
||||
import QuickStart from '@/views/dashboard/components/QuickStart.vue';
|
||||
|
||||
describe('QuickStart Component', () => {
|
||||
it('should render quick links', () => {
|
||||
const wrapper = mount(QuickStart);
|
||||
expect(wrapper.find('.quick-links-container').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (最新)
|
||||
- ✅ 新增编辑链接预填充功能
|
||||
- ✅ 优化图标获取逻辑
|
||||
- ✅ 支持切换收藏状态
|
||||
- ✅ 完善错误处理和用户提示
|
||||
- ✅ 新增右键删除链接功能
|
||||
- ✅ 添加确认对话框
|
||||
- ✅ 实现标签栏右键收藏功能
|
||||
- ✅ 自动获取路由图标
|
||||
- ✅ 数据持久化存储
|
||||
- ✅ 基础快速开始组件
|
||||
- ✅ 手动添加链接功能
|
||||
- ✅ 内部和外部链接支持
|
||||
@@ -45,48 +45,13 @@
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="图标" prop="icon">
|
||||
<div class="icon-selector">
|
||||
<div class="selected-icon">
|
||||
<el-icon :size="24" :color="form.color">
|
||||
<component :is="form.icon" />
|
||||
</el-icon>
|
||||
<span class="ml-2">{{ getIconName(form.icon) }}</span>
|
||||
</div>
|
||||
<ElButton size="small" @click="showIconPicker = true">选择图标</ElButton>
|
||||
</div>
|
||||
<IconSelect v-model="form.icon" width="400px" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="颜色" prop="color">
|
||||
<ElColorPicker v-model="form.color" />
|
||||
</ElFormItem>
|
||||
|
||||
</ElForm>
|
||||
|
||||
<!-- 图标选择器 -->
|
||||
<ElDialog
|
||||
v-model="showIconPicker"
|
||||
title="选择图标"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="icon-grid">
|
||||
<div
|
||||
v-for="icon in availableIcons"
|
||||
:key="icon.name"
|
||||
class="icon-item"
|
||||
:class="{ active: form.icon === icon.name }"
|
||||
@click="selectIcon(icon.name)"
|
||||
>
|
||||
<el-icon :size="24">
|
||||
<component :is="icon.name" />
|
||||
</el-icon>
|
||||
<span class="icon-name">{{ icon.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<ElButton @click="showIconPicker = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="showIconPicker = false">确定</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -98,31 +63,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
User,
|
||||
Setting,
|
||||
Document,
|
||||
DataAnalysis,
|
||||
Monitor,
|
||||
Tools,
|
||||
Bell,
|
||||
Message,
|
||||
Search,
|
||||
House,
|
||||
Files,
|
||||
Calendar,
|
||||
ChatDotRound,
|
||||
Connection,
|
||||
DataBoard,
|
||||
Histogram,
|
||||
PieChart,
|
||||
TrendCharts,
|
||||
Operation,
|
||||
Service,
|
||||
Guide,
|
||||
Link
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
||||
import IconSelect from '@/components/IconSelect/index.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
@@ -138,7 +80,6 @@ interface QuickLink {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
href: string;
|
||||
action: 'navigate' | 'external';
|
||||
}
|
||||
@@ -147,7 +88,6 @@ const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const showIconPicker = ref(false);
|
||||
|
||||
// 对话框显示状态
|
||||
const dialogVisible = computed({
|
||||
@@ -162,37 +102,12 @@ const isEditMode = computed(() => !!props.editData);
|
||||
const form = reactive<QuickLink>({
|
||||
title: '',
|
||||
description: '',
|
||||
icon: 'User',
|
||||
color: '#409EFF',
|
||||
icon: 'user',
|
||||
href: '',
|
||||
action: 'navigate'
|
||||
});
|
||||
|
||||
// 可用图标列表
|
||||
const availableIcons = [
|
||||
{ name: 'User', label: '用户' },
|
||||
{ name: 'Setting', label: '设置' },
|
||||
{ name: 'Document', label: '文档' },
|
||||
{ name: 'DataAnalysis', label: '分析' },
|
||||
{ name: 'Monitor', label: '监控' },
|
||||
{ name: 'Tools', label: '工具' },
|
||||
{ name: 'Bell', label: '通知' },
|
||||
{ name: 'Message', label: '消息' },
|
||||
{ name: 'Search', label: '搜索' },
|
||||
{ name: 'House', label: '首页' },
|
||||
{ name: 'Files', label: '文件' },
|
||||
{ name: 'Calendar', label: '日历' },
|
||||
{ name: 'ChatDotRound', label: '聊天' },
|
||||
{ name: 'Connection', label: '连接' },
|
||||
{ name: 'DataBoard', label: '数据' },
|
||||
{ name: 'Histogram', label: '柱状图' },
|
||||
{ name: 'PieChart', label: '饼图' },
|
||||
{ name: 'TrendCharts', label: '趋势图' },
|
||||
{ name: 'Operation', label: '操作' },
|
||||
{ name: 'Service', label: '服务' },
|
||||
{ name: 'Guide', label: '指南' },
|
||||
{ name: 'Link', label: '链接' }
|
||||
];
|
||||
|
||||
|
||||
// 表单验证规则
|
||||
const rules: FormRules = {
|
||||
@@ -233,24 +148,14 @@ const rules: FormRules = {
|
||||
]
|
||||
};
|
||||
|
||||
// 获取图标名称
|
||||
const getIconName = (iconName: string) => {
|
||||
const icon = availableIcons.find(item => item.name === iconName);
|
||||
return icon ? icon.label : iconName;
|
||||
};
|
||||
|
||||
// 选择图标
|
||||
const selectIcon = (iconName: string) => {
|
||||
form.icon = iconName;
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
title: '',
|
||||
description: '',
|
||||
icon: 'User',
|
||||
color: '#409EFF',
|
||||
icon: 'user',
|
||||
href: '',
|
||||
action: 'navigate'
|
||||
});
|
||||
@@ -286,7 +191,6 @@ watch(() => props.visible, (visible) => {
|
||||
title: props.editData.title,
|
||||
description: props.editData.description,
|
||||
icon: props.editData.icon,
|
||||
color: props.editData.color,
|
||||
href: props.editData.href,
|
||||
action: props.editData.action
|
||||
});
|
||||
@@ -307,7 +211,6 @@ watch(() => props.editData, (editData) => {
|
||||
title: editData.title,
|
||||
description: editData.description,
|
||||
icon: editData.icon,
|
||||
color: editData.color,
|
||||
href: editData.href,
|
||||
action: editData.action
|
||||
});
|
||||
@@ -325,54 +228,5 @@ watch(() => form.action, (newAction) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.selected-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 12px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.icon-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #409EFF;
|
||||
background-color: #f0f9ff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #409EFF;
|
||||
background-color: #e1f3ff;
|
||||
}
|
||||
|
||||
.icon-name {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<ElCard class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="font-bold text-16px">快速开始 / 便捷导航</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-tooltip content="快速访问常用功能,支持内部路由跳转和外部链接打开。可以自定义添加、编辑和删除快捷方式。" placement="top">
|
||||
<el-icon class="cursor-help" size="16">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<span class="font-bold text-16px">快速开始 / 便捷导航</span>
|
||||
</div>
|
||||
<ElButton size="small" type="primary" plain @click="handleAddQuickLink">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
@@ -12,7 +19,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="quick-links-container">
|
||||
<div class="quick-links-scroll">
|
||||
<div class="quick-links-grid">
|
||||
<div
|
||||
v-for="(item, index) in quickLinks"
|
||||
:key="index"
|
||||
@@ -27,7 +34,7 @@
|
||||
>
|
||||
<div class="link-content-wrapper">
|
||||
<div class="link-icon">
|
||||
<el-icon :size="20" :color="item.color">
|
||||
<el-icon :size="24">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
</div>
|
||||
@@ -36,12 +43,6 @@
|
||||
{{ item.title }}
|
||||
<span v-if="item.action === 'external'" class="external-link-badge">外链</span>
|
||||
</div>
|
||||
<div class="link-description">{{ item.description }}</div>
|
||||
</div>
|
||||
<div class="link-arrow">
|
||||
<el-icon size="14" color="#999">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +85,8 @@ import {
|
||||
Plus,
|
||||
ArrowRight,
|
||||
Edit,
|
||||
Delete
|
||||
Delete,
|
||||
QuestionFilled
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -195,55 +197,39 @@ onUnmounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
// 快速链接容器样式
|
||||
.quick-links-container {
|
||||
height: 280px; // 固定容器高度
|
||||
overflow: hidden;
|
||||
padding: 16px 0;
|
||||
|
||||
.quick-links-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
padding-right: 4px; // 为滚动条留出空间
|
||||
|
||||
// 自定义滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
.quick-links-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.quick-link-item {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
transition: all 0.3s ease;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
max-width: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
border-color: #e4e7ed;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.link-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
@@ -253,57 +239,40 @@ onUnmounted(() => {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background-color: #f0f2f5;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.link-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.link-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
text-align: center;
|
||||
|
||||
.external-link-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
background-color: #409EFF;
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.link-description {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.link-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.link-icon {
|
||||
background-color: #e8f4ff;
|
||||
}
|
||||
|
||||
.link-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<ElCard>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="font-bold text-18px">快速开始收藏功能测试</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="content">
|
||||
<ElAlert
|
||||
title="功能说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<p>在任意标签页上右击,选择"收藏到快速开始"即可将当前页面添加到快速开始列表中。</p>
|
||||
<p>已收藏的页面会显示"已收藏"状态,无法重复收藏。</p>
|
||||
</ElAlert>
|
||||
|
||||
<div class="mt-4">
|
||||
<h3>测试步骤:</h3>
|
||||
<ol class="test-steps">
|
||||
<li><strong>收藏功能:</strong>在当前页面的标签上右击,选择"收藏到快速开始"</li>
|
||||
<li><strong>取消收藏:</strong>在已收藏页面的标签上右击,选择"取消收藏"</li>
|
||||
<li><strong>自动图标:</strong>收藏时会自动获取路由配置的图标</li>
|
||||
<li><strong>查看收藏:</strong>前往工作台页面查看快速开始组件</li>
|
||||
<li><strong>编辑链接:</strong>在快速开始组件中右击任意链接,选择"编辑链接"</li>
|
||||
<li><strong>删除链接:</strong>在快速开始组件中右击任意链接,选择"删除链接"</li>
|
||||
<li><strong>验证跳转:</strong>点击收藏的链接验证跳转功能</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h3>当前页面信息:</h3>
|
||||
<ElDescriptions :column="1" border>
|
||||
<ElDescriptionsItem label="页面标题">
|
||||
{{ $route.meta?.title || '快速开始测试页面' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="路由路径">
|
||||
{{ $route.path }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="完整路径">
|
||||
{{ $route.fullPath }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="路由图标">
|
||||
<div class="flex items-center">
|
||||
<el-icon v-if="$route.meta?.icon" :size="20" class="mr-2">
|
||||
<component :is="$route.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ $route.meta?.icon || '无图标配置' }}</span>
|
||||
</div>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="收藏状态">
|
||||
<ElTag :type="isBookmarked ? 'success' : 'info'">
|
||||
{{ isBookmarked ? '已收藏' : '未收藏' }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="testBookmark"
|
||||
:disabled="isBookmarked"
|
||||
>
|
||||
{{ isBookmarked ? '已收藏' : '手动收藏此页面' }}
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="success"
|
||||
@click="goToWorkplace"
|
||||
class="ml-2"
|
||||
>
|
||||
前往工作台查看
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="danger"
|
||||
@click="removeBookmark"
|
||||
:disabled="!isBookmarked"
|
||||
class="ml-2"
|
||||
>
|
||||
删除收藏
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="info"
|
||||
@click="previewQuickLink"
|
||||
class="ml-2"
|
||||
>
|
||||
预览收藏信息
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="warning"
|
||||
@click="testEditFunction"
|
||||
class="ml-2"
|
||||
>
|
||||
测试编辑功能
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h3>当前快速开始列表:</h3>
|
||||
<div class="quick-links-preview">
|
||||
<div
|
||||
v-for="(link, index) in quickLinks"
|
||||
:key="index"
|
||||
class="link-item"
|
||||
>
|
||||
<el-icon :color="link.color" class="mr-2">
|
||||
<component :is="link.icon" />
|
||||
</el-icon>
|
||||
<span class="link-title">{{ link.title }}</span>
|
||||
<ElTag size="small" class="ml-2">{{ link.action }}</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { quickStartManager, type QuickLink } from '@/utils/quickStartManager';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
defineOptions({
|
||||
name: "QuickStartTest",
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 快速链接列表
|
||||
const quickLinks = ref<QuickLink[]>(quickStartManager.getQuickLinks());
|
||||
|
||||
// 检查当前页面是否已收藏
|
||||
const isBookmarked = computed(() => {
|
||||
return quickStartManager.isLinkExists(route.fullPath);
|
||||
});
|
||||
|
||||
// 手动收藏当前页面
|
||||
const testBookmark = () => {
|
||||
const quickLink = quickStartManager.createQuickLinkFromRoute(
|
||||
route,
|
||||
'快速开始测试页面',
|
||||
'用于测试快速开始收藏功能的演示页面'
|
||||
);
|
||||
quickStartManager.addQuickLink(quickLink);
|
||||
|
||||
// 更新快速链接列表
|
||||
quickLinks.value = quickStartManager.getQuickLinks();
|
||||
};
|
||||
|
||||
// 删除收藏
|
||||
const removeBookmark = () => {
|
||||
const links = quickStartManager.getQuickLinks();
|
||||
const targetLink = links.find(link => link.href === route.fullPath);
|
||||
|
||||
if (targetLink?.id) {
|
||||
quickStartManager.removeQuickLink(targetLink.id);
|
||||
// 更新快速链接列表
|
||||
quickLinks.value = quickStartManager.getQuickLinks();
|
||||
}
|
||||
};
|
||||
|
||||
// 预览收藏信息
|
||||
const previewQuickLink = () => {
|
||||
const quickLink = quickStartManager.createQuickLinkFromRoute(
|
||||
route,
|
||||
'快速开始测试页面',
|
||||
'用于测试快速开始收藏功能的演示页面'
|
||||
);
|
||||
|
||||
ElMessageBox.alert(
|
||||
`<div style="text-align: left;">
|
||||
<p><strong>标题:</strong>${quickLink.title}</p>
|
||||
<p><strong>描述:</strong>${quickLink.description}</p>
|
||||
<p><strong>图标:</strong>${quickLink.icon}</p>
|
||||
<p><strong>颜色:</strong><span style="color: ${quickLink.color};">${quickLink.color}</span></p>
|
||||
<p><strong>链接:</strong>${quickLink.href}</p>
|
||||
<p><strong>类型:</strong>${quickLink.action}</p>
|
||||
</div>`,
|
||||
'收藏信息预览',
|
||||
{
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '确定'
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 测试编辑功能
|
||||
const testEditFunction = () => {
|
||||
const links = quickStartManager.getQuickLinks();
|
||||
if (links.length === 0) {
|
||||
ElMessage.warning('没有可编辑的快速链接,请先添加一些链接');
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.alert(
|
||||
`<div style="text-align: left;">
|
||||
<p><strong>测试说明:</strong></p>
|
||||
<ol>
|
||||
<li>前往工作台页面</li>
|
||||
<li>在快速开始组件中右击任意链接</li>
|
||||
<li>选择"编辑链接"</li>
|
||||
<li>验证表单是否预填充了现有数据</li>
|
||||
<li>修改任意字段后保存</li>
|
||||
<li>确认修改是否生效</li>
|
||||
</ol>
|
||||
<p><strong>当前可编辑的链接:</strong></p>
|
||||
<ul>
|
||||
${links.slice(0, 3).map(link => `<li>${link.title} (${link.href})</li>`).join('')}
|
||||
${links.length > 3 ? `<li>... 还有 ${links.length - 3} 个链接</li>` : ''}
|
||||
</ul>
|
||||
</div>`,
|
||||
'编辑功能测试指南',
|
||||
{
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '前往工作台测试',
|
||||
cancelButtonText: '取消',
|
||||
showCancelButton: true
|
||||
}
|
||||
).then(() => {
|
||||
goToWorkplace();
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
};
|
||||
|
||||
// 前往工作台
|
||||
const goToWorkplace = () => {
|
||||
router.push('/dashboard/workplace');
|
||||
};
|
||||
|
||||
// 监听快速链接变化
|
||||
const updateQuickLinks = (links: QuickLink[]) => {
|
||||
quickLinks.value = links;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
quickStartManager.addListener(updateQuickLinks);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
quickStartManager.removeListener(updateQuickLinks);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.test-steps {
|
||||
padding-left: 20px;
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-links-preview {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
|
||||
.link-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user