feat(appSubscription): 新增应用订阅模块及商城欢迎横幅Logo支持
- 新增 useAppSubscriptionStore,封装 websopy 特殊接口的应用订阅功能 - 实现订阅列表分页查询、详情获取、支付状态查询、订阅管理等接口调用 - 适配不同支付方式,支持余额支付、微信支付及小程序支付预支付流程 - 在商城 Dashboard 欢迎横幅左侧新增商城 Logo 显示,读取商城设置的 shopLogo 字段 - 使用图像压缩接口处理 Logo 大小和质量,优化加载体验 - 优化 Dashboard 待处理事项展示,恢复退款和优惠券使用项 - 统一订阅接口调用地址为独立域名,复用请求工具共享认证和错误处理
This commit is contained in:
368
src/store/modules/appSubscription.ts
Normal file
368
src/store/modules/appSubscription.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* 应用订阅 store
|
||||
* 数据来源:websopy 特殊接口(db_websopy.app_subscription 表)
|
||||
* 接口域名:https://websopy-api.websoft.top/api
|
||||
* 后端:com.gxwebsoft.app.controller.AppSubscriptionController
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
pageMySubscriptions,
|
||||
getSubscriptionDetail,
|
||||
getSubscriptionDetailByNo,
|
||||
checkSubscriptionStatus,
|
||||
checkPurchased,
|
||||
getBalance,
|
||||
subscribe,
|
||||
generatePayQrcode,
|
||||
paySubscription,
|
||||
mpPrepay,
|
||||
mpConfirm,
|
||||
renewSubscription,
|
||||
cancelSubscription,
|
||||
toggleEnable
|
||||
} from '@/api/app/appSubscription';
|
||||
import type {
|
||||
AppSubscription,
|
||||
AppSubscriptionQueryParam,
|
||||
SubscribeParam,
|
||||
SubscribeResult,
|
||||
GeneratePayQrcodeParam,
|
||||
GeneratePayQrcodeResult,
|
||||
CheckStatusResult,
|
||||
PayResult,
|
||||
WechatNativePayResult,
|
||||
MpPrepayResult
|
||||
} from '@/api/app/appSubscription/model';
|
||||
import type { PageResult } from '@/api';
|
||||
|
||||
export interface AppSubscriptionState {
|
||||
// 我的订阅列表
|
||||
subscriptionList: AppSubscription[];
|
||||
// 总数量
|
||||
total: number;
|
||||
// 当前查看的订阅详情
|
||||
currentSubscription: AppSubscription | null;
|
||||
// 用户余额
|
||||
balance: number;
|
||||
// 加载状态
|
||||
loading: boolean;
|
||||
// 最后更新时间
|
||||
lastUpdateTime: number | null;
|
||||
// 缓存有效期(毫秒)
|
||||
cacheExpiry: number;
|
||||
}
|
||||
|
||||
export const useAppSubscriptionStore = defineStore('appSubscription', {
|
||||
state: (): AppSubscriptionState => ({
|
||||
subscriptionList: [],
|
||||
total: 0,
|
||||
currentSubscription: null,
|
||||
balance: 0,
|
||||
loading: false,
|
||||
lastUpdateTime: null,
|
||||
// 默认缓存5分钟(订阅涉及支付状态,不宜过长)
|
||||
cacheExpiry: 5 * 60 * 1000
|
||||
}),
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* 已激活订阅
|
||||
*/
|
||||
activeSubscriptions: (state): AppSubscription[] => {
|
||||
return state.subscriptionList.filter((s) => s.status === 'active');
|
||||
},
|
||||
|
||||
/**
|
||||
* 待支付订阅
|
||||
*/
|
||||
pendingSubscriptions: (state): AppSubscription[] => {
|
||||
return state.subscriptionList.filter((s) => s.status === 'pending');
|
||||
},
|
||||
|
||||
/**
|
||||
* 已过期订阅
|
||||
*/
|
||||
expiredSubscriptions: (state): AppSubscription[] => {
|
||||
return state.subscriptionList.filter((s) => s.status === 'expired');
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查缓存是否有效
|
||||
*/
|
||||
isCacheValid: (state): boolean => {
|
||||
if (!state.lastUpdateTime) return false;
|
||||
const now = Date.now();
|
||||
return now - state.lastUpdateTime < state.cacheExpiry;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// ============================================================
|
||||
// 查询类
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 获取我的订阅列表(分页,带缓存)
|
||||
* @param params 查询参数(page/limit/status)
|
||||
* @param forceRefresh 是否强制刷新(切换 status 过滤时建议传 true)
|
||||
*/
|
||||
async fetchMySubscriptions(
|
||||
params: AppSubscriptionQueryParam = { page: 1, limit: 10 },
|
||||
forceRefresh = false
|
||||
): Promise<PageResult<AppSubscription>> {
|
||||
// 缓存有效且不强制刷新,直接返回缓存列表
|
||||
if (!forceRefresh && this.isCacheValid && this.subscriptionList.length > 0) {
|
||||
return { list: this.subscriptionList, count: this.total };
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = await pageMySubscriptions(params);
|
||||
this.subscriptionList = data.list || [];
|
||||
this.total = data.count || 0;
|
||||
this.lastUpdateTime = Date.now();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('获取我的订阅列表失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取订阅详情(按ID)
|
||||
*/
|
||||
async fetchDetail(id: number): Promise<AppSubscription> {
|
||||
try {
|
||||
const data = await getSubscriptionDetail(id);
|
||||
this.currentSubscription = data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('获取订阅详情失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据订阅编号查询详情(小程序入口用)
|
||||
*/
|
||||
async fetchDetailByNo(subscriptionNo: string): Promise<AppSubscription> {
|
||||
try {
|
||||
const data = await getSubscriptionDetailByNo(subscriptionNo);
|
||||
this.currentSubscription = data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('根据订阅编号查询详情失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询支付状态(前端轮询用,强制实时请求,不走缓存)
|
||||
*/
|
||||
async checkStatus(subscriptionNo: string): Promise<CheckStatusResult> {
|
||||
return await checkSubscriptionStatus(subscriptionNo);
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查是否已购买某应用(强制实时请求,不走缓存)
|
||||
*/
|
||||
async checkPurchased(productId: number): Promise<boolean> {
|
||||
return await checkPurchased(productId);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前用户余额
|
||||
*/
|
||||
async fetchBalance(): Promise<number> {
|
||||
try {
|
||||
const data = await getBalance();
|
||||
this.balance = data.balance;
|
||||
return data.balance;
|
||||
} catch (error) {
|
||||
console.error('获取用户余额失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// 订阅与支付操作
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 创建订阅
|
||||
* 免费应用直接激活,付费应用创建待支付记录
|
||||
*/
|
||||
async subscribe(data: SubscribeParam): Promise<SubscribeResult> {
|
||||
try {
|
||||
const result = await subscribe(data);
|
||||
// 订阅状态变化,失效缓存
|
||||
this.invalidateCache();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('创建订阅失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成支付小程序码(同时创建订阅记录)
|
||||
*/
|
||||
async generatePayQrcode(
|
||||
data: GeneratePayQrcodeParam
|
||||
): Promise<GeneratePayQrcodeResult> {
|
||||
try {
|
||||
const result = await generatePayQrcode(data);
|
||||
// 已创建订阅记录,失效缓存
|
||||
this.invalidateCache();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('生成支付小程序码失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 发起支付
|
||||
* @param id 订阅ID
|
||||
* @param method 支付方式:balance-余额支付 / wechat-微信支付
|
||||
* @param envVersion 小程序版本(微信支付时使用):develop/trial/release
|
||||
*/
|
||||
async pay(
|
||||
id: number,
|
||||
method: 'balance' | 'wechat' = 'wechat',
|
||||
envVersion?: string
|
||||
): Promise<PayResult | WechatNativePayResult> {
|
||||
try {
|
||||
const result = await paySubscription(id, method, envVersion);
|
||||
// 支付成功后余额/订阅状态变化,失效缓存并刷新余额
|
||||
this.invalidateCache();
|
||||
if (method === 'balance') {
|
||||
const payResult = result as PayResult;
|
||||
if (typeof payResult.balance === 'number') {
|
||||
this.balance = payResult.balance;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('发起支付失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 小程序 JSAPI 预支付下单
|
||||
*/
|
||||
async mpPrepay(id: number, openid: string): Promise<MpPrepayResult> {
|
||||
try {
|
||||
return await mpPrepay(id, { openid });
|
||||
} catch (error) {
|
||||
console.error('小程序预支付失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 小程序支付成功确认
|
||||
*/
|
||||
async mpConfirm(
|
||||
subscriptionNo: string,
|
||||
transactionId?: string
|
||||
): Promise<PayResult> {
|
||||
try {
|
||||
const result = await mpConfirm(subscriptionNo, { transactionId });
|
||||
// 支付确认后订阅状态变化,失效缓存
|
||||
this.invalidateCache();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('小程序支付确认失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// 订阅管理
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 续费
|
||||
* @param id 订阅ID
|
||||
* @param period 周期:month/year
|
||||
*/
|
||||
async renew(id: number, period: 'month' | 'year' = 'month'): Promise<string> {
|
||||
try {
|
||||
const msg = await renewSubscription(id, period);
|
||||
this.invalidateCache();
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('续费失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 退订/取消
|
||||
*/
|
||||
async cancel(id: number): Promise<string> {
|
||||
try {
|
||||
const msg = await cancelSubscription(id);
|
||||
this.invalidateCache();
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('退订失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 启用/禁用
|
||||
*/
|
||||
async toggleEnable(id: number, enabled: boolean): Promise<string> {
|
||||
try {
|
||||
const msg = await toggleEnable(id, enabled);
|
||||
this.invalidateCache();
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('启用/禁用失败:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// 缓存控制
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 失效缓存(仅重置时间标记,保留已有数据,下次拉取时更新)
|
||||
* 用于订阅/支付/管理操作后,确保下次查询获取最新数据
|
||||
*/
|
||||
invalidateCache() {
|
||||
this.lastUpdateTime = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除缓存(清空数据并重置时间标记)
|
||||
*/
|
||||
clearCache() {
|
||||
this.subscriptionList = [];
|
||||
this.total = 0;
|
||||
this.currentSubscription = null;
|
||||
this.lastUpdateTime = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 强制刷新订阅列表
|
||||
*/
|
||||
async refresh() {
|
||||
return await this.fetchMySubscriptions({ page: 1, limit: 10 }, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置缓存有效期
|
||||
*/
|
||||
setCacheExpiry(expiry: number) {
|
||||
this.cacheExpiry = expiry;
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user