diff --git a/src/components/cards/PluginCard.vue b/src/components/cards/PluginCard.vue index e47ec64f..04bd23e2 100644 --- a/src/components/cards/PluginCard.vue +++ b/src/components/cards/PluginCard.vue @@ -73,6 +73,18 @@ const imageLoadError = ref(false) // 更新日志弹窗 const releaseDialog = ref(false) +// 插件分身对话框 +const pluginCloneDialog = ref(false) + +// 插件分身表单 +const cloneForm = ref({ + suffix: '', + name: '', + description: '', + version: '', + icon: '' +}) + // 监听动作标识,如为true则打开详情 watch( () => props.action, @@ -241,6 +253,54 @@ function configDone() { emit('save') } +// 显示插件分身对话框 +function showPluginClone() { + cloneForm.value = { + suffix: '', + name: t('plugin.cloneDefaultName', { name: props.plugin?.plugin_name }), + description: t('plugin.cloneDefaultDescription', { description: props.plugin?.plugin_desc }), + version: props.plugin?.plugin_version || '1.0', + icon: props.plugin?.plugin_icon || '' + } + pluginCloneDialog.value = true +} + +// 执行插件分身 +async function executePluginClone() { + if (!cloneForm.value.suffix.trim()) { + $toast.error(t('plugin.suffixRequired')) + return + } + + try { + progressDialog.value = true + progressText.value = t('plugin.cloning', { name: props.plugin?.plugin_name }) + + const result: { [key: string]: any } = await api.post(`plugin/clone/${props.plugin?.id}`, { + suffix: cloneForm.value.suffix.trim(), + name: cloneForm.value.name.trim(), + description: cloneForm.value.description.trim(), + version: cloneForm.value.version.trim(), + icon: cloneForm.value.icon.trim() + }) + + progressDialog.value = false + + if (result.success) { + $toast.success(t('plugin.cloneSuccess', { name: cloneForm.value.name })) + pluginCloneDialog.value = false + // 通知父组件刷新 + emit('remove') + } else { + $toast.error(t('plugin.cloneFailed', { message: result.message })) + } + } catch (error) { + progressDialog.value = false + $toast.error(t('plugin.cloneFailedGeneral')) + console.error(error) + } +} + // 弹出菜单 const dropdownItems = ref([ { @@ -261,6 +321,16 @@ const dropdownItems = ref([ click: showPluginConfig, }, }, + { + title: t('plugin.clone'), + value: 8, + show: true, + props: { + prependIcon: 'mdi-content-copy', + color: 'info', + click: showPluginClone, + }, + }, { title: t('plugin.update'), value: 3, @@ -470,6 +540,136 @@ watch( + + + + + + +
+
🎭 {{ t('plugin.cloneTitle') }}
+
{{ t('plugin.cloneSubtitle', { name: props.plugin?.plugin_name }) }}
+
+
+ + + + + + +
+ {{ t('plugin.cloneFeature') }}:{{ t('plugin.cloneDescription') }} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ t('common.notice') }}:{{ t('plugin.cloneNotice') }} +
+
+
+
+
+
+ + + + + + {{ t('common.cancel') }} + + + + {{ t('plugin.createClone') }} + + +
+
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 10fca431..8e0be8de 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -39,6 +39,7 @@ export default { unsubscribe: 'Unsubscribe', media: 'Media', unknown: 'Unknown', + notice: 'Notice', }, mediaType: { movie: 'Movie', @@ -2013,6 +2014,37 @@ export default { folderName: 'Folder Name', cancel: 'Cancel', create: 'Create', + clone: 'Plugin Clone', + cloneTitle: 'Create Plugin Clone', + cloneSubtitle: 'Create an independent clone instance for {name}', + cloneFeature: 'Plugin Clone Feature', + cloneDescription: 'Create an independent copy of the plugin with separate configuration and data, suitable for multi-account, testing environments, etc.', + suffix: 'Clone Suffix', + suffixPlaceholder: 'e.g.: Test, Backup, Site1', + suffixHint: 'Unique identifier to distinguish clones, only letters and numbers allowed', + suffixRequired: 'Clone suffix cannot be empty', + suffixFormatError: 'Only letters and numbers allowed', + suffixLengthError: 'Length cannot exceed 20 characters', + cloneName: 'Clone Name', + cloneNamePlaceholder: 'e.g.: Auto Backup Test Version', + cloneNameHint: 'Display name for the clone plugin (optional)', + cloneDefaultName: '{name} Clone', + cloneDescriptionLabel: 'Clone Description', + cloneDescriptionPlaceholder: 'Describe the purpose and features of this clone...', + cloneDescriptionHint: 'Detailed description of the clone plugin purpose (optional)', + cloneDefaultDescription: '{description} (Clone Version)', + cloneVersion: 'Version', + cloneVersionPlaceholder: 'e.g.: 1.0, 2.1.0', + cloneVersionHint: 'Custom version number for the clone plugin (optional)', + cloneIcon: 'Icon URL', + cloneIconPlaceholder: 'https://example.com/icon.png', + cloneIconHint: 'Custom icon for the clone plugin (optional)', + cloneNotice: 'Clone plugins are disabled by default after creation and need to be manually configured and enabled. The clone suffix cannot be modified once set.', + createClone: 'Create Clone', + cloning: 'Creating clone for {name}...', + cloneSuccess: 'Plugin clone {name} created successfully!', + cloneFailed: 'Plugin clone creation failed: {message}', + cloneFailedGeneral: 'Plugin clone creation failed', }, profile: { personalInfo: 'Personal Information', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index df5f55eb..e0c43258 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -39,6 +39,7 @@ export default { unsubscribe: '取消订阅', media: '媒体', unknown: '未知', + notice: '注意', }, mediaType: { movie: '电影', @@ -1989,6 +1990,37 @@ export default { folderName: '文件夹名称', cancel: '取消', create: '创建', + clone: '插件分身', + cloneTitle: '创建插件分身', + cloneSubtitle: '为 {name} 创建独立的分身实例', + cloneFeature: '插件分身功能', + cloneDescription: '创建插件的独立副本,拥有独立的配置和数据,适用于多账号、测试环境等场景', + suffix: '分身后缀', + suffixPlaceholder: '例如:Test、Backup、Site1', + suffixHint: '用于区分分身的唯一标识,只能包含英文字母和数字', + suffixRequired: '分身后缀不能为空', + suffixFormatError: '只能包含英文字母和数字', + suffixLengthError: '长度不能超过20个字符', + cloneName: '分身名称', + cloneNamePlaceholder: '例如:自动备份 测试版', + cloneNameHint: '分身插件的显示名称(可选)', + cloneDefaultName: '{name} 分身', + cloneDescriptionLabel: '分身描述', + cloneDescriptionPlaceholder: '描述这个分身的用途和特点...', + cloneDescriptionHint: '详细描述分身插件的用途(可选)', + cloneDefaultDescription: '{description} (分身版本)', + cloneVersion: '版本号', + cloneVersionPlaceholder: '例如:1.0、2.1.0', + cloneVersionHint: '自定义分身插件的版本号(可选)', + cloneIcon: '图标URL', + cloneIconPlaceholder: 'https://example.com/icon.png', + cloneIconHint: '自定义分身插件的图标(可选)', + cloneNotice: '分身插件创建后默认为禁用状态,需要手动配置启用。分身后缀一旦确定无法修改。', + createClone: '创建分身', + cloning: '正在创建 {name} 的分身...', + cloneSuccess: '插件分身 {name} 创建成功!', + cloneFailed: '插件分身创建失败:{message}', + cloneFailedGeneral: '插件分身创建失败', }, profile: { personalInfo: '个人信息', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index e66b58ff..1b3249fb 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -39,6 +39,7 @@ export default { unsubscribe: '取消訂閱', media: '媒體', unknown: '未知', + notice: '注意', }, mediaType: { movie: '電影', @@ -1990,6 +1991,37 @@ export default { folderName: '文件夾名稱', cancel: '取消', create: '創建', + clone: '插件分身', + cloneTitle: '創建插件分身', + cloneSubtitle: '為 {name} 創建獨立的分身實例', + cloneFeature: '插件分身功能', + cloneDescription: '創建插件的獨立副本,擁有獨立的配置和數據,適用於多賬號、測試環境等場景', + suffix: '分身後綴', + suffixPlaceholder: '例如:Test、Backup、Site1', + suffixHint: '用於區分分身的唯一標識,只能包含英文字母和數字', + suffixRequired: '分身後綴不能為空', + suffixFormatError: '只能包含英文字母和數字', + suffixLengthError: '長度不能超過20個字符', + cloneName: '分身名稱', + cloneNamePlaceholder: '例如:自動備份 測試版', + cloneNameHint: '分身插件的顯示名稱(可選)', + cloneDefaultName: '{name} 分身', + cloneDescriptionLabel: '分身描述', + cloneDescriptionPlaceholder: '描述這個分身的用途和特點...', + cloneDescriptionHint: '詳細描述分身插件的用途(可選)', + cloneDefaultDescription: '{description} (分身版本)', + cloneVersion: '版本號', + cloneVersionPlaceholder: '例如:1.0、2.1.0', + cloneVersionHint: '自定義分身插件的版本號(可選)', + cloneIcon: '圖標URL', + cloneIconPlaceholder: 'https://example.com/icon.png', + cloneIconHint: '自定義分身插件的圖標(可選)', + cloneNotice: '分身插件創建後默認為禁用狀態,需要手動配置啟用。分身後綴一旦確定無法修改。', + createClone: '創建分身', + cloning: '正在創建 {name} 的分身...', + cloneSuccess: '插件分身 {name} 創建成功!', + cloneFailed: '插件分身創建失敗:{message}', + cloneFailedGeneral: '插件分身創建失敗', }, profile: { personalInfo: '個人信息',