remove URL tool type and simplify tool management

This commit is contained in:
jingrow 2025-11-21 16:22:00 +08:00
parent 809aebee55
commit 58d44c5291
5 changed files with 59 additions and 139 deletions

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import type { Router } from 'vue-router' import type { Router } from 'vue-router'
import { t } from '../i18n' import { t } from '../i18n'
import { registerToolRoute, unregisterToolRoute, registerAllToolRoutes } from '../utils/dynamicRoutes' import { registerToolRoute, unregisterToolRoute, registerAllToolRoutes, ensureToolRoutes } from '../utils/dynamicRoutes'
export interface Tool { export interface Tool {
id: string id: string
@ -130,15 +130,18 @@ export const useToolsStore = defineStore('tools', () => {
t => !hiddenDefaultToolIds.value.includes(t.id) t => !hiddenDefaultToolIds.value.includes(t.id)
).length ).length
tool.order = defaultToolsCount + userTools.value.length + 1 // 确保工具具有 routeName 和 routePath如果缺失则自动生成
tool.isDefault = false // 这样保存到 localStorage 的数据也会包含这些字段
userTools.value.push(tool) const toolWithRoutes = ensureToolRoutes({ ...tool })
toolWithRoutes.order = defaultToolsCount + userTools.value.length + 1
toolWithRoutes.isDefault = false
userTools.value.push(toolWithRoutes)
saveUserTools(userTools.value) saveUserTools(userTools.value)
// 如果是路由类型工具,注册路由(如果提供了 router // 注册路由(如果提供了 router
// routeName 会在 registerToolRoute 中自动生成 if (router) {
if (tool.type === 'route' && router) { registerToolRoute(router, toolWithRoutes, componentPath)
registerToolRoute(router, tool, componentPath)
} }
} }
@ -165,8 +168,8 @@ export const useToolsStore = defineStore('tools', () => {
}) })
saveUserTools(userTools.value) saveUserTools(userTools.value)
// 如果是路由类型工具,移除路由(如果提供了 router // 移除路由(如果提供了 router
if (tool?.type === 'route' && tool.routeName && router) { if (tool && tool.routeName && router) {
unregisterToolRoute(router, tool.routeName) unregisterToolRoute(router, tool.routeName)
} }
} }

View File

@ -30,21 +30,23 @@ function generateRoutePath(toolName: string): string {
/** /**
* routeName routePath * routeName routePath
* 使 tool.marketplaceId tool_name使 tool.name
* 使 tool_name使 name
*/ */
function ensureToolRoutes(tool: Tool): Tool { export function ensureToolRoutes(tool: Tool): Tool {
if (tool.type === 'route') { // 确定基础名称:优先使用 marketplaceId即 tool_name稳定标识符如果没有则使用 name工具显示名称最后回退到 id
// 如果没有 routeName基于 tool.id 或 tool.name 生成 const baseName = tool.marketplaceId || tool.name || tool.id
if (!tool.routeName) {
const baseName = tool.id || tool.name // 如果没有 routeName基于基础名称生成
tool.routeName = generateRouteName(baseName) if (!tool.routeName) {
} tool.routeName = generateRouteName(baseName)
// 如果没有 routePath基于 tool.id 或 tool.name 生成
if (!tool.routePath) {
const baseName = tool.id || tool.name
tool.routePath = generateRoutePath(baseName)
}
} }
// 如果没有 routePath基于基础名称生成
if (!tool.routePath) {
tool.routePath = generateRoutePath(baseName)
}
return tool return tool
} }
@ -62,7 +64,8 @@ export function registerToolRoute(
// 确保工具具有 routeName 和 routePath // 确保工具具有 routeName 和 routePath
const toolWithRoutes = ensureToolRoutes({ ...tool }) const toolWithRoutes = ensureToolRoutes({ ...tool })
if (toolWithRoutes.type !== 'route' || !toolWithRoutes.routeName) { // 如果没有 routeName无法注册路由
if (!toolWithRoutes.routeName) {
return false return false
} }
@ -135,8 +138,8 @@ export function unregisterToolRoute(router: Router, routeName: string): boolean
* @param tools * @param tools
*/ */
export function registerAllToolRoutes(router: Router, tools: Tool[]): void { export function registerAllToolRoutes(router: Router, tools: Tool[]): void {
// 过滤出路由类型的工具routeName 会在 registerToolRoute 中自动生成) // 过滤出用户工具routeName 会在 registerToolRoute 中自动生成)
const routeTools = tools.filter(t => t.type === 'route' && !t.isDefault) const routeTools = tools.filter(t => !t.isDefault)
routeTools.forEach(tool => { routeTools.forEach(tool => {
registerToolRoute(router, tool) registerToolRoute(router, tool)

View File

@ -106,7 +106,7 @@
<!-- 添加/编辑工具对话框 --> <!-- 添加/编辑工具对话框 -->
<n-modal v-model:show="showToolModal" preset="dialog" :title="editingTool ? t('Edit Tool') : t('Add Tool')"> <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-form-item :label="t('Tool Name')" path="name">
<n-input v-model:value="toolForm.name" :placeholder="t('Enter tool name')" /> <n-input v-model:value="toolForm.name" :placeholder="t('Enter tool name')" />
</n-form-item> </n-form-item>
@ -127,16 +127,8 @@
<n-form-item :label="t('Color')" path="color"> <n-form-item :label="t('Color')" path="color">
<n-color-picker v-model:value="toolForm.color" /> <n-color-picker v-model:value="toolForm.color" />
</n-form-item> </n-form-item>
<n-form-item :label="t('Tool Type')" path="type"> <!-- 路由名 -->
<n-select <n-form-item :label="t('Route Name')" path="routeName">
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-auto-complete <n-auto-complete
v-model:value="toolForm.routeName" v-model:value="toolForm.routeName"
:options="routeNameOptions" :options="routeNameOptions"
@ -150,19 +142,6 @@
</n-text> </n-text>
</template> </template>
</n-form-item> </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> </n-form>
<template #action> <template #action>
<n-space> <n-space>
@ -177,11 +156,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, h } from 'vue' import { ref, computed, onMounted, h } from 'vue'
import { useRouter } from 'vue-router' 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 { t } from '@/shared/i18n'
import DynamicIcon from '@/core/components/DynamicIcon.vue' import DynamicIcon from '@/core/components/DynamicIcon.vue'
import IconPicker from '@/core/components/IconPicker.vue' import IconPicker from '@/core/components/IconPicker.vue'
import { useToolsStore, type Tool } from '@/shared/stores/tools' import { useToolsStore, type Tool } from '@/shared/stores/tools'
import { ensureToolRoutes } from '@/shared/utils/dynamicRoutes'
// UUID // UUID
function generateUUID(): string { function generateUUID(): string {
@ -218,52 +198,20 @@ const toolForm = ref<Partial<Tool>>({
category: '', category: '',
icon: 'tool', icon: 'tool',
color: '#1fc76f', color: '#1fc76f',
type: 'route', routeName: ''
routeName: '',
url: ''
}) })
const toolFormRules: FormRules = { const toolFormRules: FormRules = {
name: [{ required: true, message: t('Please enter tool name') }], name: [{ required: true, message: t('Please enter tool name') }],
type: [{ required: true, message: t('Please select tool type') }],
routeName: [ routeName: [
{ {
required: true, required: true,
message: t('Please enter route name'), message: t('Please enter route name'),
trigger: ['input', 'blur'] 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 routeNameOptions = computed(() => {
const routes = router.getRoutes() const routes = router.getRoutes()
@ -276,12 +224,6 @@ const routeNameOptions = computed(() => {
.sort((a, b) => a.label.localeCompare(b.label)) .sort((a, b) => a.label.localeCompare(b.label))
}) })
//
function onToolTypeChange() {
toolForm.value.routeName = ''
toolForm.value.url = ''
}
// 使 store // 使 store
const displayTools = computed(() => toolsStore.allTools) const displayTools = computed(() => toolsStore.allTools)
@ -371,9 +313,7 @@ function handleAddTool() {
category: '', category: '',
icon: 'tool', icon: 'tool',
color: '#1fc76f', color: '#1fc76f',
type: 'route', routeName: ''
routeName: '',
url: ''
} }
showToolModal.value = true showToolModal.value = true
} }
@ -391,22 +331,12 @@ function handleSaveTool() {
return return
} }
if (!toolForm.value.type) { if (!toolForm.value.routeName || !toolForm.value.routeName.trim()) {
message.error(t('Please select tool type'))
return
}
if (toolForm.value.type === 'route' && (!toolForm.value.routeName || !toolForm.value.routeName.trim())) {
message.error(t('Please enter route name')) message.error(t('Please enter route name'))
return return
} }
if (toolForm.value.type === 'url' && (!toolForm.value.url || !toolForm.value.url.trim())) {
message.error(t('Please enter URL'))
return
}
// 使 //
toolFormRef.value?.validate((errors) => { toolFormRef.value?.validate((errors) => {
if (errors) { if (errors) {
// //
@ -422,9 +352,8 @@ function handleSaveTool() {
const updates: Partial<Tool> = { const updates: Partial<Tool> = {
...toolForm.value, ...toolForm.value,
// type: 'route', // route
routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined, routeName: toolForm.value.routeName
url: toolForm.value.type === 'url' ? toolForm.value.url : undefined
} }
toolsStore.updateUserTool(editingTool.value.id, updates) toolsStore.updateUserTool(editingTool.value.id, updates)
@ -438,9 +367,8 @@ function handleSaveTool() {
category: toolForm.value.category, category: toolForm.value.category,
icon: toolForm.value.icon || 'tool', icon: toolForm.value.icon || 'tool',
color: toolForm.value.color || '#1fc76f', color: toolForm.value.color || '#1fc76f',
type: toolForm.value.type || 'route', type: 'route', // route
routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined, routeName: toolForm.value.routeName,
url: toolForm.value.type === 'url' ? toolForm.value.url : undefined,
isDefault: false isDefault: false
} }
@ -494,28 +422,14 @@ function handleOpenMarketplace() {
} }
function handleOpenTool(tool: Tool) { function handleOpenTool(tool: Tool) {
if (tool.type === 'route' && tool.routeName) { // routeName
// 使 const toolWithRoutes = ensureToolRoutes({ ...tool })
if (toolWithRoutes.routeName) {
// 使
try { try {
router.push({ name: tool.routeName }) router.push({ name: toolWithRoutes.routeName })
} catch (error) { } catch (error) {
message.error(t('Route not found: ') + tool.routeName) message.error(t('Route not found: ') + toolWithRoutes.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)
} }
} }
} }

View File

@ -218,15 +218,15 @@ async function installTool() {
category: tool.value.category, category: tool.value.category,
icon: tool.value.icon, icon: tool.value.icon,
color: tool.value.color || '#e5e7eb', color: tool.value.color || '#e5e7eb',
type: tool.value.type || 'route', type: 'route', // route
routeName: tool.value.route_name, routeName: tool.value.route_name, // addUserTool
url: tool.value.url,
isDefault: false, isDefault: false,
fromMarketplace: true, fromMarketplace: true,
marketplaceId: tool.value.name marketplaceId: tool.value.name
} }
toolsStore.addUserTool(toolData) // router 便
toolsStore.addUserTool(toolData, router)
message.success(t('Tool installed successfully')) message.success(t('Tool installed successfully'))
// //

View File

@ -234,15 +234,15 @@ async function installTool(tool: any) {
category: tool.category, category: tool.category,
icon: tool.icon, icon: tool.icon,
color: tool.color || '#e5e7eb', color: tool.color || '#e5e7eb',
type: tool.type || 'route', type: 'route', // route
routeName: tool.route_name, routeName: tool.route_name, // addUserTool
url: tool.url,
isDefault: false, isDefault: false,
fromMarketplace: true, fromMarketplace: true,
marketplaceId: tool.name marketplaceId: tool.name
} }
toolsStore.addUserTool(toolData) // router 便
toolsStore.addUserTool(toolData, router)
message.success(t('Tool installed successfully')) message.success(t('Tool installed successfully'))
// //