完善购买和套餐功能

This commit is contained in:
2026-09-16 17:26:02 +08:00
parent 898dedd75a
commit b48688cbab
19 changed files with 554 additions and 58 deletions
+3
View File
@@ -18,6 +18,7 @@ declare module 'vue' {
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']
@@ -28,6 +29,7 @@ declare module 'vue' {
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']
@@ -52,6 +54,7 @@ declare module 'vue' {
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']
+16
View File
@@ -231,6 +231,22 @@ export async function loadAndAddDynamicRoutes() {
return routesLoadingPromise;
}
/**
* 重新拉取菜单并重建动态路由。
* 动态路由在构建时会把「未开通模块」的菜单注册为锁定占位页(views/locked),
* 仅调用 menuStore.refreshMenus() 只更新菜单数据、不会重建路由,
* 因此支付开通/套餐变更后必须调用本方法,才能让占位页切换为真实页面。
*/
export async function reloadMenusAndDynamicRoutes() {
const { useMenuStore } = await import("@/stores/menu");
const menuStore = useMenuStore();
// 强制从接口拉取最新菜单(含 locked 标记),写入 store 与缓存
await menuStore.refreshMenus();
// 重置加载标志后重建路由表
resetDynamicRoutes();
await loadAndAddDynamicRoutes();
}
// 核心修改:移除扁平化,直接使用嵌套菜单生成路由
function addDynamicRoutes(menus) {
if (!menus?.length) {
+9 -3
View File
@@ -232,6 +232,7 @@ import {
import { getTenantList } from "@/api/modules";
import { getProductOrderStatus } from "@/api/product";
import { getCurrentUserProfile, logout } from "@/api/login";
import { reloadMenusAndDynamicRoutes } from "@/router";
import { useAuthStore } from "@/stores/auth";
import { useMenuStore } from "@/stores/menu";
@@ -372,7 +373,8 @@ function handleBuyProduct(module: ModuleItem) {
async function handleRefreshMenus() {
refreshLoading.value = true;
try {
await menuStore.refreshMenus();
// 仅刷新菜单数据不会替换动态路由里的锁定占位页,需一并重建路由
await Promise.all([reloadMenusAndDynamicRoutes(), loadModules()]);
ElMessage.success("菜单已刷新");
} catch {
ElMessage.error("刷新菜单失败");
@@ -462,8 +464,12 @@ async function syncReturnedPayment() {
return;
}
if (status === "activated") {
// 订单已开通:重新拉取模块与菜单,解锁「请购买后使用」的卡片
await Promise.all([loadModules(), menuStore.refreshMenus()]);
// 订单已开通:重建动态路由 + 刷新模块,解锁「功能未开通」菜单占位页与「请购买后使用」卡片
try {
await Promise.all([loadModules(), reloadMenusAndDynamicRoutes()]);
} catch (e) {
console.warn("支付成功后刷新菜单失败:", e);
}
ElMessage.success("支付成功,功能已开通");
return;
}
+9 -1
View File
@@ -79,10 +79,11 @@
type="primary"
size="large"
:loading="buyingIds[pkg.id]"
:disabled="isPurchasedOnce(pkg)"
@click="handleBuy(pkg)"
class="buy-btn"
>
立即购买
{{ isPurchasedOnce(pkg) ? "已购买(限购一次)" : "立即购买" }}
</el-button>
</div>
</el-card>
@@ -141,6 +142,7 @@ import { ElMessage } from "element-plus";
import { ArrowLeft, OfficeBuilding, Box, User, InfoFilled } from "@element-plus/icons-vue";
import QRCode from "qrcode";
import { useAuthStore } from "@/stores/auth";
import { reloadMenusAndDynamicRoutes } from "@/router";
import {
getPackageList,
createPackageOrder,
@@ -223,6 +225,10 @@ const renderQR = async () => {
}
};
/** 仅可购买一次的套餐(体验套餐)在已购买过之后禁用购买按钮 */
const isPurchasedOnce = (pkg: any) =>
Number(pkg?.allow_repurchase) === 0 && pkg?.purchased === true;
const handleBuy = async (pkg: any) => {
buyingIds.value[pkg.id] = true;
try {
@@ -295,6 +301,8 @@ const checkOrder = async (orderNo: string, silent = false) => {
ElMessage.success("购买成功,套餐已开通");
payVisible.value = false;
loadMyOrders();
// 重建动态路由,把新开通模块的「功能未开通」菜单占位页切换为真实页面
reloadMenusAndDynamicRoutes().catch(() => {});
return;
}
if (status === "paid") {
+3
View File
@@ -145,6 +145,7 @@ import {
getPaymentChannels,
} from "@/api/product";
import { useAuthStore } from "@/stores/auth";
import { reloadMenusAndDynamicRoutes } from "@/router";
interface FeatureNode {
key: number;
@@ -311,6 +312,8 @@ const checkOrder = async (silent = false) => {
ElMessage.success("购买成功,功能已开通");
payVisible.value = false;
loadMyOrders();
// 重建动态路由,把刚开通模块的「功能未开通」菜单占位页切换为真实页面
reloadMenusAndDynamicRoutes().catch(() => {});
return;
}
if (status === "paid") {