diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue index f632dfa009..7f4aaa0bc1 100644 --- a/dashboard/src/views/ExtensionPage.vue +++ b/dashboard/src/views/ExtensionPage.vue @@ -103,6 +103,7 @@ const { filteredExtensions, filteredPlugins, filteredMarketPlugins, + getMarketPluginKey, sortedPlugins, RANDOM_PLUGINS_COUNT, randomPlugins, @@ -217,11 +218,18 @@ const selectedMarketPlugin = computed(() => { ? pluginMarketData.value : []; const installedPlugin = selectedInstalledPlugin.value; + // Resolve by the unique market plugin key first; the `name` match is a + // fallback for legacy deep links, since multiple market entries can share + // the same metadata name. + const marketKeyMatch = + market.find((item) => getMarketPluginKey(item) === selectedPluginId.value) || + null; const marketNameMatch = market.find((item) => item.name === selectedPluginId.value) || null; + const marketMatch = marketKeyMatch || marketNameMatch; if (selectedDetailTab.value === "market" || !installedPlugin) { - return marketNameMatch; + return marketMatch; } const repo = normalizeRepoUrl(installedPlugin.repo); diff --git a/dashboard/src/views/extension/MarketPluginsTab.vue b/dashboard/src/views/extension/MarketPluginsTab.vue index fd03b48f59..f93f1a6277 100644 --- a/dashboard/src/views/extension/MarketPluginsTab.vue +++ b/dashboard/src/views/extension/MarketPluginsTab.vue @@ -80,6 +80,7 @@ const { randomPluginNames, marketCategoryFilter, marketCategoryItems, + getMarketPluginKey, normalizeStr, toPinyinText, toInitials, @@ -174,11 +175,14 @@ const marketCategorySelectItems = computed(() => })), ); +// Navigate with the unique market plugin key instead of the metadata name — +// two market entries can share the same `name`. const openMarketPluginDetail = (plugin) => { - if (!plugin?.name) return; + const pluginKey = getMarketPluginKey(plugin); + if (!pluginKey) return; router.push({ name: "ExtensionMarketDetails", - params: { pluginId: plugin.name }, + params: { pluginId: pluginKey }, }); }; @@ -339,7 +343,7 @@ const openMarketPluginDetail = (plugin) => { { { const allPlugins = pluginMarketData.value; if (allPlugins.length === 0) return []; - const pluginsByName = new Map( - allPlugins.map((plugin) => [plugin.name, plugin]), + // Key by the unique market plugin key: metadata `name` is not unique and + // would collapse same-named plugins into one entry. + const pluginsByKey = new Map( + allPlugins.map((plugin) => [getMarketPluginKey(plugin), plugin]), ); const selected = randomPluginNames.value - .map((name) => pluginsByName.get(name)) + .map((key) => pluginsByKey.get(key)) .filter(Boolean); if (selected.length > 0) { @@ -462,7 +464,7 @@ export const useExtensionPage = (initialTab = "installed") => { const shuffled = shufflePlugins(pluginMarketData.value); randomPluginNames.value = shuffled .slice(0, Math.min(RANDOM_PLUGINS_COUNT, shuffled.length)) - .map((plugin) => plugin.name); + .map((plugin) => getMarketPluginKey(plugin)); }; // 分页计算属性 @@ -686,6 +688,17 @@ export const useExtensionPage = (initialTab = "installed") => { return String(plugin?.market_plugin_id || "").trim(); }; + // Unique identity for a market entry. Metadata `name` alone is not unique — + // different authors can publish plugins with the same name — so fall back to + // repo and finally name for legacy registry entries without an explicit id. + const getMarketPluginKey = (plugin) => { + return ( + getMarketPluginId(plugin) || + String(plugin?.repo || "").trim() || + String(plugin?.name || "").trim() + ); + }; + const getMarketInstallSourcePayload = () => { const plugin = selectedMarketInstallPlugin.value; if ( @@ -2574,6 +2587,8 @@ export const useExtensionPage = (initialTab = "installed") => { randomPluginNames, normalizeStr, toPinyinText, + getMarketPluginId, + getMarketPluginKey, toInitials, filteredExtensions, filteredPlugins,