diff --git a/backend/components.d.ts b/backend/components.d.ts index b2c4634..35fdad2 100644 --- a/backend/components.d.ts +++ b/backend/components.d.ts @@ -14,72 +14,43 @@ declare module 'vue' { CommonAside: typeof import('./src/components/CommonAside.vue')['default'] CommonHeader: typeof import('./src/components/CommonHeader.vue')['default'] ElAlert: typeof import('element-plus/es')['ElAlert'] - ElAside: typeof import('element-plus/es')['ElAside'] - ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] - ElBacktop: typeof import('element-plus/es')['ElBacktop'] - ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] - ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElCard: typeof import('element-plus/es')['ElCard'] - ElCascader: typeof import('element-plus/es')['ElCascader'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] - ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] - ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] ElDrawer: typeof import('element-plus/es')['ElDrawer'] - ElDropdown: typeof import('element-plus/es')['ElDropdown'] - ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] - ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElImage: typeof import('element-plus/es')['ElImage'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] - ElMain: typeof import('element-plus/es')['ElMain'] - ElMenu: typeof import('element-plus/es')['ElMenu'] - ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] - ElOptionGroup: typeof import('element-plus/es')['ElOptionGroup'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElProgress: typeof import('element-plus/es')['ElProgress'] ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] - ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] - ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElStep: typeof import('element-plus/es')['ElStep'] - ElSteps: typeof import('element-plus/es')['ElSteps'] - ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] - ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTree: typeof import('element-plus/es')['ElTree'] - ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] MessageDetailDialog: typeof import('./src/components/MessageDetailDialog.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/backend/src/views/home/index.vue b/backend/src/views/home/index.vue index 210aff8..ba72cb1 100644 --- a/backend/src/views/home/index.vue +++ b/backend/src/views/home/index.vue @@ -230,6 +230,7 @@ import { } from "@element-plus/icons-vue"; import { getTenantList } from "@/api/modules"; +import { getProductOrderStatus } from "@/api/product"; import { getCurrentUserProfile, logout } from "@/api/login"; import { useAuthStore } from "@/stores/auth"; import { useMenuStore } from "@/stores/menu"; @@ -437,9 +438,45 @@ async function loadModules() { } } +// 支付宝同步回跳会把交易参数附在地址查询串中(out_trade_no 为支付单号)。 +// 拿到支付单号后立即清理地址栏(保留 #/home 等路由 hash),避免用户停留在带支付 +// 签名参数的 URL 上;随后在后台重试同步订单(异步通知可能晚于回跳到达), +// 开通完成后自动刷新模块,避免首页继续显示“请购买后使用”。 +async function syncReturnedPayment() { + const payNo = new URLSearchParams(window.location.search).get("out_trade_no")?.trim(); + if (!payNo || !authStore.token) return; + + // 参数已解析:从地址栏移除支付宝交易参数,恢复为干净的 /#/home + window.history.replaceState({}, document.title, window.location.pathname + window.location.hash); + + // 异步通知通常数秒内到达;最多等待约 15 秒 + const MAX_ATTEMPTS = 10; + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + let status = ""; + try { + const res = await getProductOrderStatus(payNo); + if (res?.code !== 200) return; // 非本租户订单,无需继续 + status = res.data?.status || ""; + } catch { + // 回跳地址也可能来自其他业务订单;查询不到时直接忽略 + return; + } + if (status === "activated") { + // 订单已开通:重新拉取模块与菜单,解锁「请购买后使用」的卡片 + await Promise.all([loadModules(), menuStore.refreshMenus()]); + ElMessage.success("支付成功,功能已开通"); + return; + } + if (status === "closed") return; + await new Promise((resolve) => setTimeout(resolve, 1500)); + } +} + // 组件挂载时加载模块列表 onMounted(() => { + // 先按当前状态渲染首页;支付回跳的订单同步在后台完成,成功后自动刷新模块 loadModules(); + syncReturnedPayment(); initTheme(); // 拉取当前登录用户的角色 / 部门 / 职位,用于右上角展示 if (authStore.token) { diff --git a/backend/src/views/package/list.vue b/backend/src/views/package/list.vue index 9306f86..3d5d650 100644 --- a/backend/src/views/package/list.vue +++ b/backend/src/views/package/list.vue @@ -114,14 +114,32 @@ + + + +
+ +

请使用{{ currentChannelName }}扫码支付

+

¥{{ Number(payAmount || 0).toFixed(2) }}

+
+
+

正在跳转{{ currentChannelName }}收银台…

+ 重新打开支付页 +
+

{{ payStatusText }}

+ +