mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 05:10:57 +00:00
feat:完善快速开始功能
This commit is contained in:
@@ -23,13 +23,12 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<!-- 收藏到快速开始 -->
|
||||
<el-dropdown-item
|
||||
@click="addToQuickStart(tag)"
|
||||
:disabled="isQuickLinkExists(tag)">
|
||||
<el-dropdown-item @click="toggleQuickStart(tag)">
|
||||
<el-icon>
|
||||
<Star />
|
||||
<Star v-if="!isQuickLinkExists(tag)" />
|
||||
<StarFilled v-else />
|
||||
</el-icon>
|
||||
{{ isQuickLinkExists(tag) ? '已收藏' : '收藏到快速开始' }}
|
||||
{{ isQuickLinkExists(tag) ? '取消收藏' : '收藏到快速开始' }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item divided @click="refreshSelectedTag(tag)">
|
||||
@@ -169,7 +168,7 @@ 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 { Star, StarFilled } from "@element-plus/icons-vue";
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -459,15 +458,30 @@ const onContextMenuVisibleChange = (visible: boolean, tag?: TagView) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加到快速开始
|
||||
* 切换快速开始收藏状态
|
||||
*/
|
||||
const addToQuickStart = (tag: TagView) => {
|
||||
const toggleQuickStart = (tag: TagView) => {
|
||||
try {
|
||||
const quickLink = quickStartManager.createQuickLinkFromRoute(tag);
|
||||
quickStartManager.addQuickLink(quickLink);
|
||||
const href = tag.fullPath || tag.path;
|
||||
const isExists = quickStartManager.isLinkExists(href);
|
||||
|
||||
if (isExists) {
|
||||
// 取消收藏:找到对应的链接并删除
|
||||
const links = quickStartManager.getQuickLinks();
|
||||
const targetLink = links.find(link => link.href === href);
|
||||
if (targetLink?.id) {
|
||||
quickStartManager.removeQuickLink(targetLink.id);
|
||||
ElMessage.success(`已取消收藏:${tag.title}`);
|
||||
}
|
||||
} else {
|
||||
// 添加收藏
|
||||
const quickLink = quickStartManager.createQuickLinkFromRoute(tag);
|
||||
quickStartManager.addQuickLink(quickLink);
|
||||
ElMessage.success(`已收藏:${tag.title}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add to quick start:', error);
|
||||
ElMessage.error('添加到快速开始失败');
|
||||
console.error('Failed to toggle quick start:', error);
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
@@ -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']>
|
||||
|
||||
@@ -115,34 +115,72 @@ class QuickStartManager {
|
||||
|
||||
// 从路由信息创建快速链接
|
||||
createQuickLinkFromRoute(route: any, customTitle?: string, customDescription?: string): QuickLink {
|
||||
// 根据路径推断图标和颜色
|
||||
const iconMap: Record<string, { icon: string; color: string }> = {
|
||||
'/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 routeIcon = route.meta?.icon;
|
||||
let routeColor = '#409EFF'; // 默认颜色
|
||||
|
||||
// 查找匹配的图标配置
|
||||
let iconConfig = { icon: 'Link', color: '#909399' }; // 默认配置
|
||||
for (const [path, config] of Object.entries(iconMap)) {
|
||||
if (route.path.includes(path)) {
|
||||
iconConfig = config;
|
||||
break;
|
||||
// 如果路由没有配置图标,根据路径推断
|
||||
if (!routeIcon) {
|
||||
const iconMap: Record<string, { icon: string; color: string }> = {
|
||||
'/dashboard/workplace': { icon: 'House', color: '#409EFF' },
|
||||
'/dashboard/analysis': { icon: 'TrendCharts', color: '#67C23A' },
|
||||
'/dashboard': { icon: 'House', color: '#409EFF' },
|
||||
'/system/user': { icon: 'User', color: '#409EFF' },
|
||||
'/system/role': { icon: 'UserFilled', color: '#E6A23C' },
|
||||
'/system/menu': { icon: 'Menu', color: '#F56C6C' },
|
||||
'/system/dept': { icon: 'OfficeBuilding', color: '#909399' },
|
||||
'/system/position': { icon: 'Briefcase', color: '#606266' },
|
||||
'/system/dict': { icon: 'Collection', color: '#FF6B6B' },
|
||||
'/system/config': { icon: 'Setting', color: '#67C23A' },
|
||||
'/system/notice': { icon: 'Bell', color: '#E6A23C' },
|
||||
'/system/log': { icon: 'Document', color: '#FF6B6B' },
|
||||
'/system': { icon: 'Setting', color: '#67C23A' },
|
||||
'/monitor/server': { icon: 'Monitor', color: '#606266' },
|
||||
'/monitor/cache': { icon: 'Coin', color: '#F56C6C' },
|
||||
'/monitor/job': { icon: 'Timer', color: '#909399' },
|
||||
'/monitor/online': { icon: 'Connection', color: '#67C23A' },
|
||||
'/monitor': { icon: 'Monitor', color: '#606266' },
|
||||
'/codegen': { icon: 'Code', color: '#4ECDC4' },
|
||||
'/demo': { icon: 'DataAnalysis', color: '#3385FF' },
|
||||
'/profile': { icon: 'User', color: '#409EFF' },
|
||||
};
|
||||
|
||||
// 查找最匹配的路径配置
|
||||
let bestMatch = { icon: 'Link', color: '#909399' }; // 默认配置
|
||||
let maxMatchLength = 0;
|
||||
|
||||
for (const [path, config] of Object.entries(iconMap)) {
|
||||
if (route.path.startsWith(path) && path.length > maxMatchLength) {
|
||||
bestMatch = config;
|
||||
maxMatchLength = path.length;
|
||||
}
|
||||
}
|
||||
|
||||
routeIcon = bestMatch.icon;
|
||||
routeColor = bestMatch.color;
|
||||
} else {
|
||||
// 如果有路由图标,尝试获取对应的颜色
|
||||
const colorMap: Record<string, string> = {
|
||||
'house': '#409EFF',
|
||||
'user': '#409EFF',
|
||||
'setting': '#67C23A',
|
||||
'monitor': '#606266',
|
||||
'document': '#FF6B6B',
|
||||
'bell': '#E6A23C',
|
||||
'menu': '#F56C6C',
|
||||
'office-building': '#909399',
|
||||
'data-analysis': '#3385FF',
|
||||
'trend-charts': '#67C23A',
|
||||
};
|
||||
|
||||
routeColor = colorMap[routeIcon.toLowerCase()] || '#409EFF';
|
||||
}
|
||||
|
||||
return {
|
||||
title: customTitle || route.meta?.title || route.name || '未命名页面',
|
||||
description: customDescription || `快速访问 ${route.meta?.title || route.name || '页面'}`,
|
||||
icon: iconConfig.icon,
|
||||
color: iconConfig.color,
|
||||
icon: routeIcon,
|
||||
color: routeColor,
|
||||
href: route.fullPath || route.path,
|
||||
action: 'navigate',
|
||||
id: `route-${route.path.replace(/\//g, '-')}-${Date.now()}`
|
||||
|
||||
+21
-7
@@ -14,9 +14,10 @@
|
||||
|
||||
### 2. 标签栏右键收藏
|
||||
- ✅ 在任意标签页右击可收藏到快速开始
|
||||
- ✅ 已收藏的页面显示"已收藏"状态
|
||||
- ✅ 自动生成合适的图标和颜色
|
||||
- ✅ 防止重复收藏
|
||||
- ✅ 支持切换收藏状态(收藏/取消收藏)
|
||||
- ✅ 自动获取路由配置的图标
|
||||
- ✅ 智能图标匹配和颜色配置
|
||||
- ✅ 实时状态显示和图标切换
|
||||
|
||||
### 3. 手动添加链接
|
||||
- ✅ 通过对话框手动添加自定义链接
|
||||
@@ -33,17 +34,28 @@
|
||||
### 4. 数据持久化
|
||||
- ✅ 使用 localStorage 保存用户配置
|
||||
- ✅ 页面刷新后数据不丢失
|
||||
- ✅ 支持导入/导出配置(可扩展)
|
||||
- ✅ 支持导入/导出配置(后续可扩展)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 从标签栏收藏页面
|
||||
|
||||
#### 收藏页面
|
||||
1. 在任意页面的标签上右击
|
||||
2. 选择"收藏到快速开始"
|
||||
3. 系统会自动添加到快速开始列表
|
||||
3. 系统会自动获取路由图标和颜色
|
||||
4. 前往工作台查看新添加的链接
|
||||
|
||||
#### 取消收藏
|
||||
1. 在已收藏页面的标签上右击
|
||||
2. 选择"取消收藏"(图标会显示为实心星星)
|
||||
3. 链接立即从快速开始列表中移除
|
||||
|
||||
#### 自动图标获取
|
||||
- **优先级1**:使用路由 `meta.icon` 配置
|
||||
- **优先级2**:根据路径智能匹配预设图标
|
||||
- **优先级3**:使用默认链接图标
|
||||
|
||||
### 手动添加链接
|
||||
|
||||
1. 在快速开始组件中点击"添加"按钮
|
||||
@@ -63,12 +75,14 @@
|
||||
1. **编辑链接**:
|
||||
- 在快速开始组件中右击任意链接
|
||||
- 选择"编辑链接"
|
||||
- 修改链接信息后保存
|
||||
- 对话框会自动填充现有的链接信息
|
||||
- 修改需要更改的字段
|
||||
- 点击"确定"保存修改
|
||||
|
||||
2. **删除链接**:
|
||||
- 在快速开始组件中右击任意链接
|
||||
- 选择"删除链接"
|
||||
- 确认删除操作
|
||||
- 在确认对话框中点击"确定"
|
||||
|
||||
### 使用快速链接
|
||||
|
||||
@@ -276,9 +276,32 @@ const handleConfirm = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听编辑数据变化,填充表单
|
||||
// 监听对话框显示状态,处理表单数据
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible) {
|
||||
if (props.editData) {
|
||||
// 编辑模式:填充现有数据
|
||||
console.log('编辑模式 - 填充数据:', props.editData);
|
||||
Object.assign(form, {
|
||||
title: props.editData.title,
|
||||
description: props.editData.description,
|
||||
icon: props.editData.icon,
|
||||
color: props.editData.color,
|
||||
href: props.editData.href,
|
||||
action: props.editData.action
|
||||
});
|
||||
console.log('表单数据已填充:', form);
|
||||
} else {
|
||||
// 新增模式:重置表单
|
||||
console.log('新增模式 - 重置表单');
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监听编辑数据变化(当对话框已经打开时)
|
||||
watch(() => props.editData, (editData) => {
|
||||
if (editData && props.visible) {
|
||||
if (props.visible && editData) {
|
||||
// 编辑模式:填充现有数据
|
||||
Object.assign(form, {
|
||||
title: editData.title,
|
||||
@@ -289,15 +312,7 @@ watch(() => props.editData, (editData) => {
|
||||
action: editData.action
|
||||
});
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible && !props.editData) {
|
||||
// 新增模式:重置表单
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
}, { immediate: false });
|
||||
|
||||
// 监听链接类型变化,自动调整链接地址格式
|
||||
watch(() => form.action, (newAction) => {
|
||||
|
||||
@@ -137,7 +137,10 @@ const onContextMenuChange = (visible: boolean, item?: QuickLink) => {
|
||||
|
||||
// 处理编辑链接
|
||||
const handleEditLink = (item: QuickLink) => {
|
||||
selectedLink.value = item;
|
||||
// 深拷贝链接数据,避免直接修改原数据
|
||||
console.log('开始编辑链接:', item);
|
||||
selectedLink.value = { ...item };
|
||||
console.log('设置选中链接:', selectedLink.value);
|
||||
addDialogVisible.value = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<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>
|
||||
@@ -41,6 +43,14 @@
|
||||
<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 ? '已收藏' : '未收藏' }}
|
||||
@@ -74,6 +84,22 @@
|
||||
>
|
||||
删除收藏
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="info"
|
||||
@click="previewQuickLink"
|
||||
class="ml-2"
|
||||
>
|
||||
预览收藏信息
|
||||
</ElButton>
|
||||
|
||||
<ElButton
|
||||
type="warning"
|
||||
@click="testEditFunction"
|
||||
class="ml-2"
|
||||
>
|
||||
测试编辑功能
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
@@ -100,6 +126,7 @@
|
||||
<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",
|
||||
@@ -141,6 +168,70 @@ const removeBookmark = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 预览收藏信息
|
||||
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');
|
||||
|
||||
Reference in New Issue
Block a user