diff --git a/src/components/cards/PluginAppCard.vue b/src/components/cards/PluginAppCard.vue index 37789559..38819522 100644 --- a/src/components/cards/PluginAppCard.vue +++ b/src/components/cards/PluginAppCard.vue @@ -118,6 +118,9 @@ const iconPath: Ref = computed(() => { function visitPluginPage() { // 将raw.githubusercontent.com转换为项目地址 let repoUrl = props.plugin?.repo_url + if (props.plugin?.is_local || repoUrl?.startsWith('local://')) { + repoUrl = props.plugin?.author_url + } if (repoUrl) { if (repoUrl.includes('raw.githubusercontent.com')) { if (!repoUrl.endsWith('/')) repoUrl += '/' diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index ca9b67d2..5673ce8b 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -1467,6 +1467,9 @@ export default { logFileFormatHint: 'Set the output format of log files to customize the displayed content of logs', pluginAutoReload: 'Plugin Hot Reload', pluginAutoReloadHint: 'Automatically reload after modifying plugin files, used when developing plugins', + pluginLocalRepoPaths: 'Local Plugin Repository Paths', + pluginLocalRepoPathsHint: + 'Local plugin repository directories. Separate multiple directories with commas. Relative and absolute paths are supported.', encodingDetectionPerformanceMode: 'Encoding Detection Performance Mode', encodingDetectionPerformanceModeHint: 'Prioritize detection efficiency, but may reduce encoding detection accuracy', @@ -2602,6 +2605,7 @@ export default { settings: 'Settings', projectHome: 'Project Home', updateHistory: 'Update History', + local: 'Local', installToLocal: 'Install to Local', totalDownloads: 'Total {count} downloads', viewData: 'View Data', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 7c48c6fd..96fda8fa 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1459,6 +1459,8 @@ export default { logFileFormatHint: '设置日志文件的输出格式,用于自定义日志的显示内容', pluginAutoReload: '插件热加载', pluginAutoReloadHint: '修改插件文件后自动重新加载,开发插件时使用', + pluginLocalRepoPaths: '本地插件仓库路径', + pluginLocalRepoPathsHint: '本地插件仓库目录,多个目录用英文逗号分隔,支持相对路径和绝对路径', encodingDetectionPerformanceMode: '编码探测性能模式', encodingDetectionPerformanceModeHint: '优先提升探测效率,但可能降低编码探测的准确性', transferThreads: '文件整理线程数', @@ -2573,6 +2575,7 @@ export default { settings: '设置', projectHome: '项目主页', updateHistory: '更新说明', + local: '本地', installToLocal: '安装到本地', totalDownloads: '共 {count} 次下载', viewData: '查看数据', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 6c422de9..5c3d2361 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1461,6 +1461,8 @@ export default { logFileFormatHint: '設置日誌文件的輸出格式,用於自定義日誌的顯示內容', pluginAutoReload: '插件熱加載', pluginAutoReloadHint: '修改插件文件後自動重新加載,開發插件時使用', + pluginLocalRepoPaths: '本地插件倉庫路徑', + pluginLocalRepoPathsHint: '本地插件倉庫目錄,多個目錄用英文逗號分隔,支持相對路徑和絕對路徑', encodingDetectionPerformanceMode: '編碼探測性能模式', encodingDetectionPerformanceModeHint: '優先提升探測效率,但可能降低編碼探測的準確性', transferThreads: '文件整理線程數', @@ -2575,6 +2577,7 @@ export default { settings: '設置', projectHome: '項目主頁', updateHistory: '更新說明', + local: '本地', installToLocal: '安裝到本地', totalDownloads: '共 {count} 次下載', viewData: '查看數據', diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 8d05f9eb..cacc72dd 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -34,6 +34,9 @@ const activeTab = ref('installed') // 获取插件标签页 const pluginTabs = computed(() => getPluginTabs(t)) +// 本地插件来源显示名称 +const localRepoLabel = computed(() => t('plugin.local')) + // 使用动态标签页 const { registerHeaderTab } = useDynamicHeaderTab() @@ -610,15 +613,17 @@ async function saveFolderPluginOrder() { // 初始化过滤选项 function initOptions(item: Plugin) { - const optionValue = (options: Array, value: string | undefined) => { - value && !options.includes(value) && options.push(value) + const optionValue = (options: Array, value: string | undefined, preferred = false) => { + if (!value || options.includes(value)) return + if (preferred) options.unshift(value) + else options.push(value) } const optionMutipleValue = (options: Array, value: string | undefined) => { value && value.split(',').forEach(v => !options.includes(v) && options.push(v)) } optionValue(authorFilterOptions.value, item.plugin_author) optionMutipleValue(labelFilterOptions.value, item.plugin_label) - optionValue(repoFilterOptions.value, handleRepoUrl(item.repo_url)) + optionValue(repoFilterOptions.value, handleRepoUrl(item), Boolean(item.is_local || item.repo_url?.startsWith('local://'))) } // 关闭插件市场窗口 @@ -650,7 +655,7 @@ async function installPlugin(item: Plugin) { enabledFilter.value = false installedFilter.value = null // 刷新 - refreshData() + await refreshData() } else { $toast.error(t('plugin.installFailed', { name: item?.plugin_name, message: result.message })) } @@ -750,6 +755,7 @@ async function fetchUninstalledPlugins(force: boolean = false) { // 排除已安装且有更新的,上面的问题在于"本地存在未安装的旧版本插件且云端有更新时"不会在插件市场展示 marketList.value = uninstalledList.value.filter(item => !(item.has_update && item.installed)) // 初始化过滤选项 + repoFilterOptions.value = [] marketList.value.forEach(initOptions) // 设置APP市场加载完成 isAppMarketLoaded.value = true @@ -770,13 +776,14 @@ async function getPluginStatistics() { // 加载所有数据 async function refreshData() { await fetchInstalledPlugins() - fetchUninstalledPlugins() + await fetchUninstalledPlugins() + getPluginStatistics() // 重新加载文件夹配置,确保分身插件能正确显示在文件夹中 await loadPluginFolders() } // 对uninstalledList进行排序到sortedUninstalledList -watch([marketList, filterForm, activeSort], () => { +watch([marketList, filterForm, activeSort, PluginStatistics], () => { // 匹配过滤函数 const match = (filter: Array, value: string | undefined) => filter.length === 0 || (value && filter.includes(value)) @@ -794,7 +801,7 @@ watch([marketList, filterForm, activeSort], () => { filterText(filterForm.name, `${value.plugin_name} ${value.plugin_desc}`) && match(filterForm.author, value.plugin_author) && matchMultiple(filterForm.label, value.plugin_label) && - match(filterForm.repo, handleRepoUrl(value.repo_url)) + match(filterForm.repo, handleRepoUrl(value)) ) { sortedUninstalledList.value.push(value) } @@ -805,7 +812,7 @@ watch([marketList, filterForm, activeSort], () => { if (!isNullOrEmptyObject(PluginStatistics.value)) { if (!activeSort.value || activeSort.value === 'count') { sortedUninstalledList.value = sortedUninstalledList.value.sort((a, b) => { - return PluginStatistics.value[b.id || '0'] - PluginStatistics.value[a.id || '0'] + return (PluginStatistics.value[b.id || '0'] ?? 0) - (PluginStatistics.value[a.id || '0'] ?? 0) }) } else if (activeSort.value) { sortedUninstalledList.value = sortedUninstalledList.value.sort((a: any, b: any) => { @@ -825,9 +832,9 @@ function pluginLabels(label: string | undefined) { } // 新安装了插件 -function pluginInstalled() { +async function pluginInstalled() { pluginDialogClose() - refreshData() + await refreshData() } // 插件市场设置完成 @@ -842,7 +849,7 @@ async function refreshMarket() { isMarketRefreshing.value = true try { await fetchUninstalledPlugins(true) - await getPluginStatistics() + getPluginStatistics() } catch (error) { console.error(error) } finally { @@ -850,9 +857,22 @@ async function refreshMarket() { } } +function parseLocalRepoPath(repoUrl: string | undefined) { + if (!repoUrl?.startsWith('local://')) return '' + + try { + return new URL(repoUrl).searchParams.get('path') || '' + } catch (error) { + return decodeURIComponent(repoUrl.match(/[?&]path=([^&]+)/)?.[1] || '') + } +} + // 处理掉github地址的前缀 -function handleRepoUrl(url: string | undefined) { +function handleRepoUrl(item: Plugin | string | undefined) { + const url = typeof item === 'string' ? item : item?.repo_url if (!url) return '' + if (url.startsWith('local://')) return parseLocalRepoPath(url) || localRepoLabel.value + if (typeof item !== 'string' && item?.is_local) return parseLocalRepoPath(url) || localRepoLabel.value return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '') } @@ -886,7 +906,6 @@ onMounted(async () => { await loadPluginOrderConfig() await loadPluginFolders() // 加载文件夹配置 await refreshData() - getPluginStatistics() if (activeTab.value != 'market' && pluginId.value) { // 找到这个插件 const plugin = dataList.value.find(item => item.id === pluginId.value) diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index 7bc560f7..a240a934 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -84,6 +84,7 @@ const SystemSettings = ref({ LOG_FILE_FORMAT: '【%(levelname)s】%(asctime)s - %(message)s', // 实验室 PLUGIN_AUTO_RELOAD: false, + PLUGIN_LOCAL_REPO_PATHS: '', ENCODING_DETECTION_PERFORMANCE_MODE: true, TRANSFER_THREADS: 1, }, @@ -1458,6 +1459,15 @@ onDeactivated(() => { persistent-hint /> + + +