remove URL tool type and simplify tool management
This commit is contained in:
parent
809aebee55
commit
58d44c5291
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
import { t } from '../i18n'
|
||||
import { registerToolRoute, unregisterToolRoute, registerAllToolRoutes } from '../utils/dynamicRoutes'
|
||||
import { registerToolRoute, unregisterToolRoute, registerAllToolRoutes, ensureToolRoutes } from '../utils/dynamicRoutes'
|
||||
|
||||
export interface Tool {
|
||||
id: string
|
||||
@ -130,15 +130,18 @@ export const useToolsStore = defineStore('tools', () => {
|
||||
t => !hiddenDefaultToolIds.value.includes(t.id)
|
||||
).length
|
||||
|
||||
tool.order = defaultToolsCount + userTools.value.length + 1
|
||||
tool.isDefault = false
|
||||
userTools.value.push(tool)
|
||||
// 确保工具具有 routeName 和 routePath(如果缺失则自动生成)
|
||||
// 这样保存到 localStorage 的数据也会包含这些字段
|
||||
const toolWithRoutes = ensureToolRoutes({ ...tool })
|
||||
|
||||
toolWithRoutes.order = defaultToolsCount + userTools.value.length + 1
|
||||
toolWithRoutes.isDefault = false
|
||||
userTools.value.push(toolWithRoutes)
|
||||
saveUserTools(userTools.value)
|
||||
|
||||
// 如果是路由类型工具,注册路由(如果提供了 router)
|
||||
// routeName 会在 registerToolRoute 中自动生成
|
||||
if (tool.type === 'route' && router) {
|
||||
registerToolRoute(router, tool, componentPath)
|
||||
// 注册路由(如果提供了 router)
|
||||
if (router) {
|
||||
registerToolRoute(router, toolWithRoutes, componentPath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,8 +168,8 @@ export const useToolsStore = defineStore('tools', () => {
|
||||
})
|
||||
saveUserTools(userTools.value)
|
||||
|
||||
// 如果是路由类型工具,移除路由(如果提供了 router)
|
||||
if (tool?.type === 'route' && tool.routeName && router) {
|
||||
// 移除路由(如果提供了 router)
|
||||
if (tool && tool.routeName && router) {
|
||||
unregisterToolRoute(router, tool.routeName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,21 +30,23 @@ function generateRoutePath(toolName: string): string {
|
||||
|
||||
/**
|
||||
* 确保工具具有 routeName 和 routePath(如果缺失则自动生成)
|
||||
* 优先使用 tool.marketplaceId(即 tool_name),如果没有则使用 tool.name(工具显示名称)
|
||||
* 这样从市场安装的工具会使用 tool_name,手动添加的工具会使用 name
|
||||
*/
|
||||
function ensureToolRoutes(tool: Tool): Tool {
|
||||
if (tool.type === 'route') {
|
||||
// 如果没有 routeName,基于 tool.id 或 tool.name 生成
|
||||
if (!tool.routeName) {
|
||||
const baseName = tool.id || tool.name
|
||||
tool.routeName = generateRouteName(baseName)
|
||||
}
|
||||
|
||||
// 如果没有 routePath,基于 tool.id 或 tool.name 生成
|
||||
if (!tool.routePath) {
|
||||
const baseName = tool.id || tool.name
|
||||
tool.routePath = generateRoutePath(baseName)
|
||||
}
|
||||
export function ensureToolRoutes(tool: Tool): Tool {
|
||||
// 确定基础名称:优先使用 marketplaceId(即 tool_name,稳定标识符),如果没有则使用 name(工具显示名称),最后回退到 id
|
||||
const baseName = tool.marketplaceId || tool.name || tool.id
|
||||
|
||||
// 如果没有 routeName,基于基础名称生成
|
||||
if (!tool.routeName) {
|
||||
tool.routeName = generateRouteName(baseName)
|
||||
}
|
||||
|
||||
// 如果没有 routePath,基于基础名称生成
|
||||
if (!tool.routePath) {
|
||||
tool.routePath = generateRoutePath(baseName)
|
||||
}
|
||||
|
||||
return tool
|
||||
}
|
||||
|
||||
@ -62,7 +64,8 @@ export function registerToolRoute(
|
||||
// 确保工具具有 routeName 和 routePath
|
||||
const toolWithRoutes = ensureToolRoutes({ ...tool })
|
||||
|
||||
if (toolWithRoutes.type !== 'route' || !toolWithRoutes.routeName) {
|
||||
// 如果没有 routeName,无法注册路由
|
||||
if (!toolWithRoutes.routeName) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -135,8 +138,8 @@ export function unregisterToolRoute(router: Router, routeName: string): boolean
|
||||
* @param tools 工具列表
|
||||
*/
|
||||
export function registerAllToolRoutes(router: Router, tools: Tool[]): void {
|
||||
// 过滤出路由类型的工具(routeName 会在 registerToolRoute 中自动生成)
|
||||
const routeTools = tools.filter(t => t.type === 'route' && !t.isDefault)
|
||||
// 过滤出用户工具(routeName 会在 registerToolRoute 中自动生成)
|
||||
const routeTools = tools.filter(t => !t.isDefault)
|
||||
|
||||
routeTools.forEach(tool => {
|
||||
registerToolRoute(router, tool)
|
||||
|
||||
@ -106,7 +106,7 @@
|
||||
|
||||
<!-- 添加/编辑工具对话框 -->
|
||||
<n-modal v-model:show="showToolModal" preset="dialog" :title="editingTool ? t('Edit Tool') : t('Add Tool')">
|
||||
<n-form :model="toolForm" :rules="dynamicFormRules" label-width="100" ref="toolFormRef">
|
||||
<n-form :model="toolForm" :rules="toolFormRules" label-width="100" ref="toolFormRef">
|
||||
<n-form-item :label="t('Tool Name')" path="name">
|
||||
<n-input v-model:value="toolForm.name" :placeholder="t('Enter tool name')" />
|
||||
</n-form-item>
|
||||
@ -127,16 +127,8 @@
|
||||
<n-form-item :label="t('Color')" path="color">
|
||||
<n-color-picker v-model:value="toolForm.color" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Tool Type')" path="type">
|
||||
<n-select
|
||||
v-model:value="toolForm.type"
|
||||
:options="toolTypeOptions"
|
||||
:placeholder="t('Select tool type')"
|
||||
@update:value="onToolTypeChange"
|
||||
/>
|
||||
</n-form-item>
|
||||
<!-- 内部路由名 -->
|
||||
<n-form-item v-if="toolForm.type === 'route'" :label="t('Route Name')" path="routeName">
|
||||
<!-- 路由名 -->
|
||||
<n-form-item :label="t('Route Name')" path="routeName">
|
||||
<n-auto-complete
|
||||
v-model:value="toolForm.routeName"
|
||||
:options="routeNameOptions"
|
||||
@ -150,19 +142,6 @@
|
||||
</n-text>
|
||||
</template>
|
||||
</n-form-item>
|
||||
<!-- URL路径 -->
|
||||
<n-form-item v-if="toolForm.type === 'url'" :label="t('URL Path')" path="url">
|
||||
<n-input
|
||||
v-model:value="toolForm.url"
|
||||
:placeholder="t('Enter URL path')"
|
||||
/>
|
||||
<template #feedback>
|
||||
<n-text depth="3" style="font-size: 12px;">
|
||||
{{ t('Internal path: /example') }} <br>
|
||||
{{ t('External link: starts with http:// or https://') }}
|
||||
</n-text>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
@ -177,11 +156,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NModal, NForm, NFormItem, NInput, NSelect, NAutoComplete, NColorPicker, NButton, NSpace, NText, NDropdown, useDialog, useMessage, type FormInst, type FormRules, type DropdownOption } from 'naive-ui'
|
||||
import { NModal, NForm, NFormItem, NInput, NAutoComplete, NColorPicker, NButton, NSpace, NText, NDropdown, useDialog, useMessage, type FormInst, type FormRules, type DropdownOption } from 'naive-ui'
|
||||
import { t } from '@/shared/i18n'
|
||||
import DynamicIcon from '@/core/components/DynamicIcon.vue'
|
||||
import IconPicker from '@/core/components/IconPicker.vue'
|
||||
import { useToolsStore, type Tool } from '@/shared/stores/tools'
|
||||
import { ensureToolRoutes } from '@/shared/utils/dynamicRoutes'
|
||||
|
||||
// 生成 UUID(兼容所有环境)
|
||||
function generateUUID(): string {
|
||||
@ -218,52 +198,20 @@ const toolForm = ref<Partial<Tool>>({
|
||||
category: '',
|
||||
icon: 'tool',
|
||||
color: '#1fc76f',
|
||||
type: 'route',
|
||||
routeName: '',
|
||||
url: ''
|
||||
routeName: ''
|
||||
})
|
||||
|
||||
const toolFormRules: FormRules = {
|
||||
name: [{ required: true, message: t('Please enter tool name') }],
|
||||
type: [{ required: true, message: t('Please select tool type') }],
|
||||
routeName: [
|
||||
{
|
||||
required: true,
|
||||
message: t('Please enter route name'),
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
],
|
||||
url: [
|
||||
{
|
||||
required: true,
|
||||
message: t('Please enter URL'),
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 动态表单验证规则:根据工具类型动态显示/隐藏字段的验证
|
||||
const dynamicFormRules = computed(() => {
|
||||
const rules: FormRules = {
|
||||
name: toolFormRules.name,
|
||||
type: toolFormRules.type
|
||||
}
|
||||
|
||||
// 根据类型添加对应的验证规则
|
||||
if (toolForm.value.type === 'route') {
|
||||
rules.routeName = toolFormRules.routeName
|
||||
} else if (toolForm.value.type === 'url') {
|
||||
rules.url = toolFormRules.url
|
||||
}
|
||||
|
||||
return rules
|
||||
})
|
||||
|
||||
const toolTypeOptions = [
|
||||
{ label: t('Route'), value: 'route' },
|
||||
{ label: t('URL'), value: 'url' }
|
||||
]
|
||||
|
||||
// 获取所有可用的路由名(从路由配置中提取)
|
||||
const routeNameOptions = computed(() => {
|
||||
const routes = router.getRoutes()
|
||||
@ -276,12 +224,6 @@ const routeNameOptions = computed(() => {
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
})
|
||||
|
||||
// 工具类型变化处理
|
||||
function onToolTypeChange() {
|
||||
toolForm.value.routeName = ''
|
||||
toolForm.value.url = ''
|
||||
}
|
||||
|
||||
// 使用 store 中的工具列表
|
||||
const displayTools = computed(() => toolsStore.allTools)
|
||||
|
||||
@ -371,9 +313,7 @@ function handleAddTool() {
|
||||
category: '',
|
||||
icon: 'tool',
|
||||
color: '#1fc76f',
|
||||
type: 'route',
|
||||
routeName: '',
|
||||
url: ''
|
||||
routeName: ''
|
||||
}
|
||||
showToolModal.value = true
|
||||
}
|
||||
@ -391,22 +331,12 @@ function handleSaveTool() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!toolForm.value.type) {
|
||||
message.error(t('Please select tool type'))
|
||||
return
|
||||
}
|
||||
|
||||
if (toolForm.value.type === 'route' && (!toolForm.value.routeName || !toolForm.value.routeName.trim())) {
|
||||
if (!toolForm.value.routeName || !toolForm.value.routeName.trim()) {
|
||||
message.error(t('Please enter route name'))
|
||||
return
|
||||
}
|
||||
|
||||
if (toolForm.value.type === 'url' && (!toolForm.value.url || !toolForm.value.url.trim())) {
|
||||
message.error(t('Please enter URL'))
|
||||
return
|
||||
}
|
||||
|
||||
// 表单验证(使用动态规则)
|
||||
// 表单验证
|
||||
toolFormRef.value?.validate((errors) => {
|
||||
if (errors) {
|
||||
// 验证失败,错误信息已由表单组件显示
|
||||
@ -422,9 +352,8 @@ function handleSaveTool() {
|
||||
|
||||
const updates: Partial<Tool> = {
|
||||
...toolForm.value,
|
||||
// 清理不需要的字段
|
||||
routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined,
|
||||
url: toolForm.value.type === 'url' ? toolForm.value.url : undefined
|
||||
type: 'route', // 默认类型为 route
|
||||
routeName: toolForm.value.routeName
|
||||
}
|
||||
|
||||
toolsStore.updateUserTool(editingTool.value.id, updates)
|
||||
@ -438,9 +367,8 @@ function handleSaveTool() {
|
||||
category: toolForm.value.category,
|
||||
icon: toolForm.value.icon || 'tool',
|
||||
color: toolForm.value.color || '#1fc76f',
|
||||
type: toolForm.value.type || 'route',
|
||||
routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined,
|
||||
url: toolForm.value.type === 'url' ? toolForm.value.url : undefined,
|
||||
type: 'route', // 默认类型为 route
|
||||
routeName: toolForm.value.routeName,
|
||||
isDefault: false
|
||||
}
|
||||
|
||||
@ -494,28 +422,14 @@ function handleOpenMarketplace() {
|
||||
}
|
||||
|
||||
function handleOpenTool(tool: Tool) {
|
||||
if (tool.type === 'route' && tool.routeName) {
|
||||
// 使用路由名导航(最佳实践)
|
||||
// 确保工具具有 routeName(如果缺失则自动生成)
|
||||
const toolWithRoutes = ensureToolRoutes({ ...tool })
|
||||
if (toolWithRoutes.routeName) {
|
||||
// 使用路由名导航
|
||||
try {
|
||||
router.push({ name: tool.routeName })
|
||||
router.push({ name: toolWithRoutes.routeName })
|
||||
} catch (error) {
|
||||
message.error(t('Route not found: ') + tool.routeName)
|
||||
}
|
||||
} else if (tool.type === 'url' && tool.url) {
|
||||
// URL导航
|
||||
if (tool.url.startsWith('http://') || tool.url.startsWith('https://')) {
|
||||
// 外部链接,在新窗口打开
|
||||
window.open(tool.url, '_blank')
|
||||
} else {
|
||||
// 内部路径,使用router跳转
|
||||
router.push(tool.url)
|
||||
}
|
||||
} else if (tool.url) {
|
||||
// 兼容旧数据:只有url字段
|
||||
if (tool.url.startsWith('http://') || tool.url.startsWith('https://')) {
|
||||
window.open(tool.url, '_blank')
|
||||
} else {
|
||||
router.push(tool.url)
|
||||
message.error(t('Route not found: ') + toolWithRoutes.routeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,15 +218,15 @@ async function installTool() {
|
||||
category: tool.value.category,
|
||||
icon: tool.value.icon,
|
||||
color: tool.value.color || '#e5e7eb',
|
||||
type: tool.value.type || 'route',
|
||||
routeName: tool.value.route_name,
|
||||
url: tool.value.url,
|
||||
type: 'route', // 默认类型为 route
|
||||
routeName: tool.value.route_name, // 可能不存在,会在 addUserTool 中自动生成
|
||||
isDefault: false,
|
||||
fromMarketplace: true,
|
||||
marketplaceId: tool.value.name
|
||||
}
|
||||
|
||||
toolsStore.addUserTool(toolData)
|
||||
// 传递 router 以便立即注册路由
|
||||
toolsStore.addUserTool(toolData, router)
|
||||
message.success(t('Tool installed successfully'))
|
||||
|
||||
// 刷新已安装工具列表
|
||||
|
||||
@ -234,15 +234,15 @@ async function installTool(tool: any) {
|
||||
category: tool.category,
|
||||
icon: tool.icon,
|
||||
color: tool.color || '#e5e7eb',
|
||||
type: tool.type || 'route',
|
||||
routeName: tool.route_name,
|
||||
url: tool.url,
|
||||
type: 'route', // 默认类型为 route
|
||||
routeName: tool.route_name, // 可能不存在,会在 addUserTool 中自动生成
|
||||
isDefault: false,
|
||||
fromMarketplace: true,
|
||||
marketplaceId: tool.name
|
||||
}
|
||||
|
||||
toolsStore.addUserTool(toolData)
|
||||
// 传递 router 以便立即注册路由
|
||||
toolsStore.addUserTool(toolData, router)
|
||||
message.success(t('Tool installed successfully'))
|
||||
|
||||
// 刷新已安装工具列表
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user