import { defineAsyncComponent, h } from 'vue'; import { toast } from 'vue-sonner'; import { getTeam } from '../../data/team'; import router from '../../router'; import { confirmDialog, icon, renderDialog } from '../../utils/components'; import { planTitle } from '../../utils/format'; import type { ColumnField, DialogConfig, FilterField, Tab, TabList } from './types'; import { getUpsellBanner } from '.'; import { isMobile } from '../../utils/device'; import { getToastErrorMessage } from '../../utils/toast'; export function getAppsTab(forSite: boolean) { return { label: '应用', icon: icon('grid'), route: 'apps', type: 'list', condition: docResource => forSite && docResource.pg?.status !== 'Archived', list: getAppsTabList(forSite) } satisfies Tab as Tab; } function getAppsTabList(forSite: boolean) { const options = forSite ? siteAppListOptions : benchAppListOptions; const list: TabList = { pagetype: '', filters: () => ({}), ...options, columns: getAppsTabColumns(forSite), searchField: !forSite ? 'title' : undefined, filterControls: r => { if (forSite) return []; else return [ { type: 'select', label: '分支', class: !isMobile() ? 'w-24' : '', fieldname: 'branch', options: [ '', ...new Set(r.listResource.data?.map(i => String(i.branch)) || []) ] }, { type: 'select', label: '所有者', class: !isMobile() ? 'w-24' : '', fieldname: 'repository_owner', options: [ '', ...new Set( r.listResource.data?.map( i => String(i.repository_url).split('/').at(-2) || '' ) || [] ) ] } ] satisfies FilterField[]; } }; return list; } function getAppsTabColumns(forSite: boolean) { const appTabColumns: ColumnField[] = [ { label: '应用', fieldname: 'title', width: 1, suffix(row) { if (!row.is_app_patched) { return; } return h( 'div', { title: '应用已打补丁', class: 'rounded-full bg-gray-100 p-1' }, h(icon('hash', 'w-3 h-3')) ); }, format: (value, row) => value || row.app_title }, { label: '计划', width: 0.75, class: 'text-gray-600 text-sm', format(_, row) { const planText = planTitle(row.plan_info); if (planText) return `${planText}/月`; else return '免费'; } }, { label: '版本', fieldname: 'branch', type: 'Badge', width: 1, } ]; // 为站点应用添加操作列,包含卸载按钮 if (forSite) { appTabColumns.push({ label: '操作', width: 0.75, align: 'right', type: 'Button', Button: ({ row, listResource, documentResource }) => { // 如果是 jingrow 应用,不显示卸载按钮 if (row.app === 'jingrow') { return null; } return { label: '卸载', variant: 'ghost', class: 'text-red-600 hover:text-red-700 hover:bg-red-50', slots: { prefix: icon('trash-2') }, onClick: () => { const appName = row.title || row.app_title; const dialogConfig: DialogConfig = { title: `卸载应用`, message: `您确定要从站点 ${documentResource.pg?.name} 卸载应用 ${appName} 吗?
所有与此应用相关的页面类型和模块将被移除。`, onSuccess({ hide }) { // 立即从列表中移除该应用(乐观更新) const currentData = listResource.data || []; const updatedData = currentData.filter(item => item.name !== row.name); listResource.data = updatedData; const promise = documentResource.uninstallApp.submit({ app: row.app }); toast.promise(promise, { loading: '正在安排应用卸载...', success: (jobId: string) => { hide(); return '应用卸载已安排'; }, error: (e: Error) => { // 如果失败,重新加载列表恢复状态 listResource.reload(); return getToastErrorMessage(e); } }); } }; confirmDialog(dialogConfig); } }; } }); return appTabColumns; } return appTabColumns.filter(c => c.label !== '计划'); } const siteAppListOptions: Partial = { pagetype: 'Site App', filters: res => { return { parenttype: 'Site', parent: res.pg?.name }; }, primaryAction({ listResource: apps, documentResource: site }) { return { label: '安装应用', slots: { prefix: icon('plus') }, onClick() { const InstallAppDialog = defineAsyncComponent( () => import('../../components/site/InstallAppDialog.vue') ); renderDialog( h(InstallAppDialog, { site: site.name, onInstalled() { apps.reload(); } }) ); } }; }, rowActions({ row, listResource: apps, documentResource: site }) { let $team = getTeam(); return [ { label: '在 Desk 中查看', condition: () => $team.pg?.is_desk_user, onClick() { window.open(`/app/app-source/${row.name}`, '_blank'); } }, { label: '更改计划', condition: () => row.plan_info && row.plans.length > 1, onClick() { let SiteAppPlanChangeDialog = defineAsyncComponent( () => import('../../components/site/SiteAppPlanSelectDialog.vue') ); renderDialog( h(SiteAppPlanChangeDialog, { app: row, currentPlan: row.plans.find( (plan: Record) => plan.name === row.plan_info.name ), onPlanChanged() { apps.reload(); } }) ); } }, { label: '卸载', condition: () => row.app !== 'jingrow', onClick() { const appName = row.title || row.app_title; const dialogConfig: DialogConfig = { title: `卸载应用`, message: `您确定要从站点 ${site.pg?.name} 卸载应用 ${appName} 吗?
所有与此应用相关的页面类型和模块将被移除。`, onSuccess({ hide }) { toast.promise( site.uninstallApp.submit({ app: row.app }), { loading: '正在安排应用卸载...', success: (jobId: string) => { hide(); apps.reload(); return '应用卸载已安排'; }, error: (e: Error) => { return getToastErrorMessage(e); } } ); } }; confirmDialog(dialogConfig); } } ]; } }; const benchAppListOptions: Partial = { pagetype: 'Bench App', filters: res => { return { parenttype: 'Bench', parent: res.pg?.name }; }, rowActions({ row }) { let $team = getTeam(); return [ { label: '在 Desk 中查看', condition: () => $team.pg?.is_desk_user, onClick() { window.open(`/app/app-release/${row.release}`, '_blank'); } } ]; } };