feat(appSubscription): 实现Dashboard订阅到期显示及扫码支付功能

- dashboard订阅到期时间信息支持三态渲染:激活显示剩余天数和已激活标签、待支付显示金额及去支付按钮、无订阅显示立即订阅按钮
- 通过租户ID查询应用产品,实现按租户过滤的订阅展示逻辑
- 新增appProduct分页接口及类型定义,提供应用列表获取能力
- 实现「立即订阅」流程,支持免费应用直接激活,付费应用生成微信扫码支付二维码及轮询订单状态
- 实现待支付订阅复用支付接口生成支付二维码,避免重复创建订阅
- 小程序端扫码支付流程支持订单号查询及支付确认,后端使用Redis标记支付成功状态
- 修复后端generatePayQrcode接口缺少priceType字段导致的数据库报错,前端兼容现有逻辑无需修改
- 移除dashboard中siteInfo旧的expirationTime字段相关代码,改为读订阅expireTime
- 新增订阅及支付相关样式,优化界面体验
- 支付状态轮询实现超时提示及自动停止机制,提升用户支付体验
- 组件卸载时确保支付轮询停止,避免内存泄漏
- 独立加载订阅信息,不影响其他数据请求,避免页面加载失败影响订阅展示
This commit is contained in:
2026-07-15 14:11:14 +08:00
parent e0d27f67d5
commit 29a18ffbc6
4 changed files with 613 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { AppProduct, AppProductQueryParam } from './model';
/**
* websopy 特殊接口域名
* 后端 Controller: com.gxwebsoft.app.controller.AppProductController
* @RequestMapping("/api/app/product"),后端无 context-path
* 完整 URL = https://websopy-api.websoft.top/api/app/product/xxx
*/
const WEBSOPY_API_BASE = 'https://websopy-api.websoft.top/api';
const BASE = `${WEBSOPY_API_BASE}/app/product`;
/**
* 分页查询应用列表
* GET /app/product/page
* 支持按 tenantId 过滤(管理后台按租户查询应用)
* 注意:分页参数为 current/size非 page/limit
*/
export async function pageProducts(
params: AppProductQueryParam
): Promise<PageResult<AppProduct>> {
const res = await request.get<ApiResult<PageResult<AppProduct>>>(
`${BASE}/page`,
{ params }
);
if (res.data.code === 0) {
return res.data.data || { list: [], count: 0 };
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取应用详情
* GET /app/product/detail/{id}
*/
export async function getProductDetail(id: number): Promise<AppProduct> {
const res = await request.get<ApiResult<AppProduct>>(`${BASE}/detail/${id}`);
if (res.data.code === 0) {
return res.data.data as AppProduct;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,143 @@
/**
* 应用产品实体(对应表 app_product
* 字段对照后端 com.gxwebsoft.app.entity.AppProduct
*/
export interface AppProduct {
// 应用ID主键
productId?: number;
// 应用名称
productName?: string;
// 应用标识(唯一)
productCode?: string;
// 应用密钥
productSecret?: string;
// 应用类型: 10网站 20微信小程序 30抖音小程序 40百度小程序 50支付宝小程序 60Android 70iOS 80macOS 90Windows 100插件
appType?: number;
// 应用类型名称(关联查询)
appTypeName?: string;
// 分类ID
categoryId?: number;
// 行业类型(父级)
industryParent?: string;
// 行业类型(子级)
industryChild?: string;
// 应用Logo
logo?: string;
// 应用图标
icon?: string;
// 二维码
qrcode?: string;
// 应用截图(JSON数组)
screenshots?: string;
// 应用简介
description?: string;
// 详细说明
content?: string;
// 关键词
keywords?: string;
// 域名
domain?: string;
// 域名前缀
prefix?: string;
// 包名/AppID
packageName?: string;
// 后台地址
adminUrl?: string;
// API地址
apiUrl?: string;
// 下载地址
downloadUrl?: string;
// 版本号
version?: string;
// 版本: standard标准版 professional专业版 perpetual永久授权
edition?: string;
// 最低版本要求
minVersion?: string;
// 定价: free免费 one_time一次性 subscription订阅
priceType?: string;
// 价格(元)
price?: number;
// 划线价格
linePrice?: number;
// 续费价格
renewPrice?: number;
// 交付方式: 1源码 2托管 3授权
deliveryMethod?: number;
// 计费方式: 1按年 2按月 3一次性
chargingMethod?: number;
// 订阅周期: month/year
subscriptionPeriod?: string;
// 发布状态: developing pending_review published rejected deprecated
publishStatus?: string;
// 发布时间
publishTime?: string;
// 审核时间
reviewTime?: string;
// 审核人ID
reviewerId?: number;
// 拒绝原因
rejectReason?: string;
// 浏览次数
clicks?: number;
// 安装次数
installs?: number;
// 下载次数
downloads?: number;
// 评分(1-5)
rating?: number;
// 点赞数
likes?: number;
// 开发者
developer?: string;
// 开发者电话
developerPhone?: string;
// 开发者邮箱
developerEmail?: string;
// 是否推荐: 0否 1是
recommend?: number;
// 是否官方: 0否 1是
official?: number;
// 是否上架市场: 0否 1是
market?: number;
// 是否显示首页: 0否 1是
showIndex?: number;
// 是否可搜索: 0否 1是
searchEnabled?: number;
// 模板ID
templateId?: number;
// 租户ID
tenantId?: number;
// 创建时间
createTime?: string;
// 更新时间
updateTime?: string;
}
/**
* 应用产品分页查询参数
* 注意:后端 AppProductController.page 的分页参数为 current/size非 page/limit
*/
export interface AppProductQueryParam {
// 页码
current?: number;
// 每页条数
size?: number;
// 应用名称
productName?: string;
// 应用标识
productCode?: string;
// 应用类型
appType?: number;
// 分类ID
categoryId?: number;
// 发布状态
publishStatus?: string;
// 状态
status?: number;
// 用户ID
userId?: number;
// 租户ID按租户查询应用
tenantId?: number;
// 关键词搜索(同时搜索应用名称和应用标识)
keywords?: string;
}