import { defineAsyncComponent, h } from 'vue'; import { Button, Badge } from 'jingrow-ui'; import { toast } from 'vue-sonner'; import ChangeAppBranchDialog from '../components/marketplace/ChangeAppBranchDialog.vue'; import { confirmDialog, icon, renderDialog } from '../utils/components'; import PlansDialog from '../components/marketplace/PlansDialog.vue'; import CodeReview from '../components/marketplace/CodeReview.vue'; import GenericDialog from '../components/GenericDialog.vue'; import ObjectList from '../components/ObjectList.vue'; import { userCurrency, currency } from '../utils/format'; import { getToastErrorMessage } from '../utils/toast'; import { isMobile } from '../utils/device'; import router from '../router'; export default { pagetype: 'Marketplace App', whitelistedMethods: { removeVersion: 'remove_version', addVersion: 'add_version', siteInstalls: 'site_installs', createApprovalRequest: 'create_approval_request', cancelApprovalRequest: 'cancel_approval_request', updateListing: 'update_listing', markAppReadyForReview: 'mark_app_ready_for_review' }, list: { route: '/apps', title: '应用市场', fields: ['image', 'title', 'status', 'description'], columns: [ { label: '应用', fieldname: 'title', class: 'font-medium', width: 0.3, prefix(row) { return row.image ? h('img', { src: row.image, class: 'w-6 h-6 rounded-sm', alt: row.title }) : h( 'div', { class: 'w-6 h-6 rounded bg-gray-300 text-gray-600 flex items-center justify-center' }, row.title[0].toUpperCase() ); } }, { label: '状态', type: 'Badge', fieldname: 'status', width: 0.3 }, { label: '描述', fieldname: 'description', width: 1.0 } ], primaryAction() { return { label: '新建应用', variant: 'solid', slots: { prefix: icon('plus') }, onClick() { const NewMarketplaceAppDialog = defineAsyncComponent(() => import('../components/marketplace/NewMarketplaceAppDialog.vue') ); renderDialog(h(NewMarketplaceAppDialog)); } }; } }, detail: { titleField: 'name', route: '/apps/:name', statusBadge({ documentResource: app }) { return { label: app.pg.status }; }, breadcrumbs({ items, documentResource: app }) { return [ items[0], { label: app.pg.title, route: `/apps/${app.pg.name}` } ]; }, tabs: [ { label: '分析', icon: icon('bar-chart-2'), route: 'analytics', type: 'Component', component: defineAsyncComponent(() => import('../components/marketplace/MarketplaceAppAnalytics.vue') ), props: app => { return { app: app.pg.name }; } }, { label: '列表', icon: icon('shopping-cart'), route: 'listing', type: 'Component', component: defineAsyncComponent(() => import('../components/MarketplaceAppListing.vue') ), props: app => { return { app: app }; } }, { label: '版本', icon: icon('package'), route: 'versions', type: 'list', list: { pagetype: 'Marketplace App Version', filters: app => { return { parent: app.pg.name, parenttype: 'Marketplace App' }; }, onRowClick: (row, context) => { const { listResource: versions, documentResource: app } = context; showReleases(row, app); }, fields: [ 'source.repository_owner as repository_owner', 'source.repository as repository', 'source.branch as branch' ], columns: [ { label: '版本', fieldname: 'version', width: 0.5 }, { label: '来源', fieldname: 'source', width: 0.5 }, { label: '仓库', width: 0.5, format: (value, row) => { return `${row.repository_owner}/${row.repository}`; } }, { label: '分支', fieldname: 'branch', type: 'Badge', width: 0.5 } ], primaryAction({ listResource: versions, documentResource: app }) { return { label: '新建版本', slots: { prefix: icon('plus') }, onClick() { renderDialog( h( GenericDialog, { options: { title: `为 ${app.pg.title} 添加版本支持`, size: '4xl' } }, { default: () => h(ObjectList, { options: { label: '版本', fieldname: 'version', fieldtype: 'ListSelection', columns: [ { label: '版本', fieldname: 'version' }, { label: '分支', type: 'Select', fieldname: 'branch', format: (value, row) => { row.selectedOption = value[0]; return value.map(v => ({ label: v, value: v, onClick: () => { row.selectedOption = v; } })); } }, { label: '', fieldname: '', align: 'right', type: 'Button', width: '5rem', Button({ row, listResource: versionsOptions }) { return { label: '添加', onClick() { if (app.addVersion.loading) return; toast.promise( app.addVersion.submit({ version: row.version, branch: row.selectedOption }), { loading: '正在添加新版本...', success: () => { versions.reload(); versionsOptions.reload(); return '新版本已添加'; }, error: e => getToastErrorMessage(e) } ); } }; } } ], resource() { return { url: 'jcloud.api.marketplace.options_for_version', params: { name: app.pg.name }, auto: true }; } } }) } ) ); } }; }, rowActions({ row, listResource: versions, documentResource: app }) { return [ { label: '显示发布', slots: { prefix: icon('plus') }, onClick() { showReleases(row, app); } }, { label: '更改分支', onClick() { renderDialog( h(ChangeAppBranchDialog, { app: app.pg.name, source: row.source, version: row.version, activeBranch: row.branch, onBranchChanged() { versions.reload(); } }) ); } }, { label: '移除版本', onClick() { toast.promise( app.removeVersion.submit({ version: row.version }), { loading: '正在移除版本...', success: () => { versions.reload(); return '版本已成功移除'; }, error: e => getToastErrorMessage(e) } ); } } ]; } } }, { label: '定价', icon: icon('dollar-sign'), route: 'pricing', type: 'list', list: { pagetype: 'Marketplace App Plan', filters: app => { return { app: app.pg.name }; }, fields: ['name', 'title', 'price_cny', 'price_usd', 'enabled'], columns: [ { label: '标题', fieldname: 'title' }, { label: '启用', type: 'Badge', fieldname: 'enabled', format: value => { return value == 1 ? '已启用' : '已禁用'; } }, { label: '价格 (CNY)', fieldname: 'price_cny', format: value => { return currency(value, 'CNY'); } }, { label: '价格 (USD)', fieldname: 'price_usd', format: value => { return currency(value, 'USD'); } } ], primaryAction({ listResource: plans, documentResource: app }) { return { label: '新建计划', slots: { prefix: icon('plus') }, onClick() { renderDialog( h(PlansDialog, { app: app.pg.name, onPlanCreated() { plans.reload(); } }) ); } }; }, rowActions({ row, listResource: plans, documentResource: app }) { return [ { label: '编辑', onClick() { renderDialog( h(PlansDialog, { app: app.pg.name, plan: row, onPlanUpdated() { plans.reload(); } }) ); } } ]; } } }, { label: '订阅', icon: icon('users'), route: 'subscription', type: 'list', list: { pagetype: 'Subscription', filters: app => { return { document_type: 'Marketplace App', document_name: app.name }; }, fields: ['site', 'enabled', 'team'], filterControls() { return [ { type: 'select', label: '状态', class: !isMobile() ? 'w-24' : '', fieldname: 'enabled', options: ['', '激活', '禁用'] } ]; }, columns: [ { label: '站点', fieldname: 'site', width: 0.6 }, { label: '状态', type: 'Badge', fieldname: 'enabled', width: 0.3, format: value => { return value ? '激活' : '禁用'; } }, { label: '价格', fieldname: 'price', width: 0.3, format: value => { return userCurrency(value); } }, { label: '已激活天数', fieldname: 'active_for', width: 0.3, format: value => { return value + (value == 1 ? ' 天' : ' 天'); } } ] } } ], actions({ documentResource: app }) { return [ { label: '在市场中查看', slots: { prefix: icon('external-link') }, condition: () => app.pg.status === 'Published', onClick() { window.open( `${window.location.origin}/marketplace/apps/${app.name}`, '_blank' ); } }, { label: '指南', slots: { icon: icon('help-circle') }, condition: () => app.pg.status === 'Draft', onClick() { window.open( 'https://jingrow.com/docs/marketplace/marketplace-guidelines', '_blank' ); } }, { label: '完成列表', variant: 'solid', slots: { prefix: icon('alert-circle') }, condition: () => app.pg.status === 'Draft', onClick() { let AppListingStepsDialog = defineAsyncComponent(() => import('../components/marketplace/AppListingStepsDialog.vue') ); renderDialog( h(AppListingStepsDialog, { app: app.pg.name }) ); } }, { label: '选项', condition: () => app.pg.status === 'Draft', options: [ { label: '删除', icon: icon('trash-2'), condition: () => app.pg.status === 'Draft', onClick() { confirmDialog({ title: `删除应用 ${app.pg.title}`, message: '您确定要删除此应用吗?', onSuccess({ hide }) { toast.promise(app.delete.submit(), { loading: '正在删除应用...', success: () => { hide(); router.push({ name: 'Marketplace App List' }); return '应用删除成功'; }, error: e => getToastErrorMessage(e) }); } }); } } ] } ]; } } }; function showReleases(row, app) { renderDialog( h( GenericDialog, { options: { title: `${app.pg.name} 在 ${row.branch} 分支的发布`, size: '6xl' } }, { default: () => h(ObjectList, { options: { label: '版本', type: 'list', pagetype: 'App Release', filters: { app: app.pg.name, source: row.source }, fields: ['message', 'tag', 'author', 'status'], orderBy: 'creation desc', columns: [ { label: '提交信息', fieldname: 'message', class: 'w-64', width: 0.5 }, { label: '哈希值', fieldname: 'hash', class: 'w-24', type: 'Badge', width: 0.2, format: value => { return value.slice(0, 7); } }, { label: '作者', fieldname: 'author', width: 0.2 }, { label: '状态', fieldname: 'status', type: 'Badge', width: 0.3 }, { label: '代码审查', type: 'Component', width: 0.2, component: ({ row, listResource: releases, app }) => { if ( (row.status === 'Awaiting Approval' || row.status === 'Rejected') && row.screening_status === 'Complete' ) { return h(Button, { label: '代码审查', variant: 'subtle', theme: 'blue', size: 'sm', onClick: () => codeReview(row, app, window.is_system_user) }); } return h(Badge, { label: row.screening_status || '未开始' }); } }, { label: '', fieldname: '', align: 'right', type: 'Button', width: 0.2, Button({ row, listResource: releases }) { let label = ''; let successMessage = ''; let loadingMessage = ''; if (row.status === 'Awaiting Approval') { label = '取消'; successMessage = '发布已取消'; loadingMessage = '正在取消发布...'; } else if (row.status === 'Draft') { label = '提交'; loadingMessage = '正在提交发布以供审批...'; successMessage = '发布已提交以供审批'; } return { label: label, onClick() { toast.promise( row.status === 'Awaiting Approval' ? app.cancelApprovalRequest.submit({ app_release: row.name }) : app.createApprovalRequest.submit({ app_release: row.name }), { loading: loadingMessage, success: () => { releases.reload(); return successMessage; }, error: e => getToastErrorMessage(e) } ); } }; } } ] } }) } ) ); } function codeReview(row, app, isSystemUser) { renderDialog( h(CodeReview, { row: row, app: app, isSystemUser: isSystemUser }) ); }