11<script lang="ts" setup>
2- import { useAppControl } from " @/composables/use-app-control" ;
3- import type { ApplicationSearchResult , ReleaseDetail } from " @/types" ;
2+ import { useAppCompare } from " @/composables/use-app-compare" ;
3+ import { useAppDownload } from " @/composables/use-app-download" ;
4+ import { useHaloVersion } from " @/composables/use-halo-version" ;
5+ import type { ApplicationReleaseAsset , ApplicationSearchResult , ReleaseDetail } from " @/types" ;
46import { relativeTimeTo } from " @/utils/date" ;
57import prettyBytes from " pretty-bytes" ;
6- import { toRefs } from " vue" ;
8+ import { computed , toRefs } from " vue" ;
79import TablerCloudDownload from " ~icons/tabler/cloud-download" ;
10+ import semver from " semver" ;
11+ import { useMutation } from " @tanstack/vue-query" ;
12+ import { apiClient } from " @/utils/api-client" ;
13+ import { Dialog , Toast } from " @halo-dev/components" ;
14+ import type { PluginInstallationErrorResponse , ThemeInstallationErrorResponse } from " @/types/core" ;
15+ import type { AxiosError } from " axios" ;
816
917const props = withDefaults (
1018 defineProps <{
@@ -18,7 +26,145 @@ const props = withDefaults(
1826
1927const { app } = toRefs (props );
2028
21- const { handleInstall, installing } = useAppControl (app );
29+ const { haloVersion } = useHaloVersion ();
30+ const {
31+ checkPluginUpgradeStatus,
32+ checkThemeUpgradeStatus,
33+ handleBindingPluginAppId,
34+ handleBindingThemeAppId,
35+ handleClearQueryCache,
36+ handleForceUpgradePlugin,
37+ handleForceUpgradeTheme,
38+ } = useAppDownload (app );
39+
40+ const { matchedPlugin, matchedTheme, appType, hasInstalled : appHasInstalled } = useAppCompare (app );
41+
42+ const hasInstalled = computed (() => {
43+ if (appType .value === " PLUGIN" ) {
44+ return matchedPlugin .value ?.spec .version === props .release .release .spec .version ;
45+ }
46+ if (appType .value === " THEME" ) {
47+ return matchedTheme .value ?.spec .version === props .release .release .spec .version ;
48+ }
49+ return false ;
50+ });
51+
52+ const isSatisfies = computed (() => {
53+ const { requires } = props .release .release .spec ;
54+ if (! haloVersion .value || ! requires ) return false ;
55+ return semver .satisfies (haloVersion .value , requires , { includePrerelease: true });
56+ });
57+
58+ function getDownloadUrl(asset : ApplicationReleaseAsset ) {
59+ return ` ${import .meta .env .VITE_APP_STORE_BACKEND }/store/apps/${
60+ app .value ?.application .metadata .name
61+ }/releases/download/${props .release .release .metadata .name }/assets/${asset .metadata .name } ` ;
62+ }
63+
64+ const { isLoading : installing, mutate : handleInstall } = useMutation ({
65+ mutationKey: [" install-app-from-release" ],
66+ mutationFn : async ({ asset }: { asset: ApplicationReleaseAsset }) => {
67+ const { version : releaseVersion } = props .release .release .spec ;
68+ const { version : currentVersion } = matchedPlugin .value ?.spec || matchedTheme .value ?.spec || {};
69+
70+ const downloadUrl = getDownloadUrl (asset );
71+
72+ if (appType .value === " PLUGIN" ) {
73+ if (appHasInstalled .value ) {
74+ if (semver .gt (releaseVersion || " *" , currentVersion || " *" )) {
75+ await handleForceUpgradePlugin (
76+ matchedPlugin .value ?.metadata .name as string ,
77+ downloadUrl ,
78+ props .release .release .spec .version
79+ );
80+ } else {
81+ Dialog .warning ({
82+ title: " 当前已安装较新的版本" ,
83+ description: " 确定要安装一个旧版本吗?" ,
84+ async onConfirm() {
85+ await handleForceUpgradePlugin (
86+ matchedPlugin .value ?.metadata .name as string ,
87+ downloadUrl ,
88+ props .release .release .spec .version
89+ );
90+ },
91+ });
92+ }
93+ } else {
94+ const { data : plugin } = await apiClient .plugin .installPluginFromUri ({
95+ installFromUriRequest: { uri: downloadUrl },
96+ });
97+ if (await checkPluginUpgradeStatus (plugin , props .release .release .spec .version )) {
98+ await handleBindingPluginAppId ({ plugin: plugin });
99+ Toast .success (" 安装成功" );
100+ handleClearQueryCache ();
101+ }
102+ }
103+ return ;
104+ }
105+
106+ if (appType .value === " THEME" ) {
107+ if (appHasInstalled .value ) {
108+ if (semver .gt (releaseVersion || " *" , currentVersion || " *" )) {
109+ await handleForceUpgradeTheme (
110+ matchedTheme .value ?.metadata .name as string ,
111+ downloadUrl ,
112+ props .release .release .spec .version
113+ );
114+ } else {
115+ Dialog .warning ({
116+ title: " 当前已安装较新的版本" ,
117+ description: " 确定要安装一个旧版本吗?" ,
118+ async onConfirm() {
119+ await handleForceUpgradeTheme (
120+ matchedTheme .value ?.metadata .name as string ,
121+ downloadUrl ,
122+ props .release .release .spec .version
123+ );
124+ },
125+ });
126+ }
127+ } else {
128+ const { data : theme } = await apiClient .theme .installThemeFromUri ({
129+ installFromUriRequest: { uri: downloadUrl },
130+ });
131+ if (await checkThemeUpgradeStatus (theme , props .release .release .spec .version )) {
132+ await handleBindingThemeAppId ({ theme });
133+ Toast .success (" 安装成功" );
134+ handleClearQueryCache ();
135+ }
136+ }
137+ }
138+ },
139+ onError(error : AxiosError <PluginInstallationErrorResponse | ThemeInstallationErrorResponse >, variables ) {
140+ Dialog .warning ({
141+ title: ` 当前${appType .value === " PLUGIN" ? " 插件" : " 主题" }已经安装,是否重新安装? ` ,
142+ description:
143+ " 请确认当前安装的应用是否和已存在的应用一致,重新安装之后会记录应用的安装来源,后续可以通过应用市场进行升级。" ,
144+ onConfirm : async () => {
145+ if (! error .response ?.data ) {
146+ return ;
147+ }
148+
149+ const downloadUrl = getDownloadUrl (variables .asset );
150+
151+ if (" pluginName" in error .response .data ) {
152+ await handleForceUpgradePlugin (
153+ error .response .data .pluginName ,
154+ downloadUrl ,
155+ props .release .release .spec .version
156+ );
157+ return ;
158+ }
159+
160+ if (" themeName" in error .response .data ) {
161+ await handleForceUpgradeTheme (error .response .data .themeName , downloadUrl , props .release .release .spec .version );
162+ return ;
163+ }
164+ },
165+ });
166+ },
167+ });
22168 </script >
23169
24170<template >
@@ -76,10 +222,13 @@ const { handleInstall, installing } = useAppControl(app);
76222 </span >
77223 </div >
78224 <div >
225+ <span v-if =" hasInstalled" class =" as-text-sm as-text-gray-600" > 已安装 </span >
226+ <span v-else-if =" !isSatisfies" class =" as-text-sm as-text-gray-600" > 不兼容 </span >
79227 <span
228+ v-else
80229 class =" as-text-sm as-text-blue-600 hover:as-text-blue-500"
81230 :class =" { 'as-pointer-events-none': installing }"
82- @click =" handleInstall()"
231+ @click =" handleInstall({ asset } )"
83232 >
84233 {{ installing ? "安装中" : "安装" }}
85234 </span >
0 commit comments