style(shopGoodsBrowse): 移除商品名称列的居中对齐
- 删除了商品名称列中多余的 align: 'center' 属性 - 保持了 ellipsis 属性以支持文本省略显示 - 优化了表格列的展示样式一致性
This commit is contained in:
@@ -166,3 +166,41 @@
|
||||
- 「系统运行」加 `a-tooltip title="自您首次开通小程序商城起"`(用户要求)。
|
||||
- 数据流复用:`loadSubscriptionInfo()` 原本就调 `getProductDetail(65)` 填充 `currentProduct`,无需新增接口调用。
|
||||
- 类型校验:vue-tsc 对 `shop/dashboard/index.vue` 行级错误 0 条;cms 那批历史错误与本次无关。
|
||||
|
||||
## Dashboard 到期时间续费功能 + 标签矛盾修复
|
||||
|
||||
- 背景:dashboard「到期时间」行原先在订阅已过期时同时显示绿色「已激活」标签 + 「(已过期)」红字,状态自相矛盾;且即将到期/已过期时无续费入口(用户反馈"没有按钮呢")。
|
||||
- 改动 `src/views/shop/dashboard/index.vue`:
|
||||
- 标签动态化:`a-tag :color="isExpired ? 'red' : 'green'"`,文案 `已过期/已激活`;已过期时显示「(已过期N天)」。
|
||||
- 新增续费按钮:`showRenewButton`(`daysToExpire <= 7` 即即将到期或已过期时显示),点击 `onRenew`。
|
||||
- 复用订阅 Modal:新增 `subscribeMode`('subscribe'|'renew')+ `renewing` 状态,Modal 的 title/okText/confirm-loading/@ok 按 mode 动态切换;免费应用 alert 文案也按 mode 区分。
|
||||
- `confirmRenew` 调 `subscriptionStore.renew(sub.id, selectedPeriod)`(POST /app/subscription/renew/{id}?period=month|year),成功后 `refreshSubscription()`。
|
||||
- `onSubscribe` 补设 `subscribeMode='subscribe'`。
|
||||
- 续费 API 语义:`renewSubscription` 返回 string(同步完成,疑似余额扣款/免费续期)。若后端续费需走微信扫码支付,需改为返回支付二维码结构并接 pay 流程——待后端确认。
|
||||
- 关键字段再确认(用户):开始时间用 `appSubscription.startTime`,到期时间用 `appSubscription.expireTime`,daysToExpire 与 `dayjs()`(浏览器本地时间)对比。
|
||||
|
||||
## Dashboard 续费改走 subscribe+pay 扫码(后端 renew 接口未实现)
|
||||
|
||||
- 触发:用户截图显示 `POST /api/app/subscription/renew/90` 返回 404,确认后端没有实现 renew 接口。
|
||||
- 方案:续费不再调 `store.renew` / `renewSubscription`,改为复用现有 `subscribe + pay` 扫码支付链路(与首次订阅完全一致)。后端 subscribe 对已有 active 订阅会处理续期/创建新记录。
|
||||
- 改动 `src/views/shop/dashboard/index.vue` 的 `confirmRenew`:
|
||||
- 删掉 `await subscriptionStore.renew(sub.id, ...)` 改为 `subscribe({ productId, subscriptionPeriod })`。
|
||||
- 免费应用走 `subResult.status === 'active'` 分支,提示「续费成功」并刷新。
|
||||
- 付费应用走 `pay(subscriptionId, 'wechat', envVersion)` → 拿到 `miniappQrcode` → 弹支付 Modal + `startPolling`。
|
||||
- `payProductName` fallback 文案改为「应用续费」(与订阅的「应用订阅」区分)。
|
||||
- `onRenew` 入口、`subscribeMode='renew'`、续费 Modal title/按钮复用不变,UI 上用户感知不到底层调的是 subscribe。
|
||||
- 教训/记录:调后端接口前最好先确认接口是否真实存在;前端 `api/app/appSubscription/index.ts` 的 `renewSubscription` 函数可保留(防止后端后续补上接口),但 dashboard 暂不调用。
|
||||
|
||||
## Dashboard 续费最终定稿:方案1 renew+pay(前端已就绪,等后端实现)
|
||||
|
||||
- 触发:上一步用 subscribe 走续费,后端返回「您已订阅该应用,无需重复购买」——subscribe 对已有 active 订阅是拒绝的,续费不能复用 subscribe。
|
||||
- 定稿方案(用户确认):方案1 —— 后端实现 `/renew/{id}?period=xxx` 返回 `SubscribeResult`(含 subscriptionId),前端再调 `pay` 生成小程序码。与 subscribe+pay 对称,职责清晰。
|
||||
- 三处改动(前端已全部就绪,等后端补 renew 接口即可联调通):
|
||||
1. `src/api/app/appSubscription/index.ts` `renewSubscription`:返回类型 `string` → `SubscribeResult`;取 `res.data.data`(原取 `res.data.message`)。
|
||||
2. `src/store/modules/appSubscription.ts` `renew`:返回类型 `string` → `SubscribeResult`,返回 result 而非 msg。
|
||||
3. `src/views/shop/dashboard/index.vue` `confirmRenew`:改回调 `subscriptionStore.renew(sub.id, period)` 拿 `renewResult` → 免费(status=active)直接成功;付费走 `pay(renewResult.subscriptionId, 'wechat', envVersion)` → 弹码 + `startPolling`。与 confirmSubscribe 完全对称,仅第一步 subscribe→renew、传 sub.id 而非 productId。
|
||||
- **后端接口约定(待实现)**:`POST /api/app/subscription/renew/{id}?period=month|year`
|
||||
- 入参:id=原订阅ID,period=month/year
|
||||
- 逻辑:校验原订阅归属 → 创建 pending 续费记录(startTime=原expireTime或now,expireTime=新到期)→ 返回 `{ subscriptionId, subscriptionNo, status, message, payPrice?, orderNo? }`(同 SubscribeResult)
|
||||
- 前端拿到后调 `pay/{subscriptionId}` 生成微信小程序码,与订阅流程一致
|
||||
- 续费按钮触发条件不变:`showRenewButton`(daysToExpire<=7,即将到期或已过期)。
|
||||
|
||||
@@ -221,18 +221,19 @@ export async function mpConfirm(
|
||||
}
|
||||
|
||||
/**
|
||||
* 续费
|
||||
* 续费:基于已有订阅创建续费订单(pending),返回新 subscriptionId
|
||||
* POST /app/subscription/renew/{id}?period=month|year
|
||||
* 返回结构与 subscribe 一致(含 subscriptionId),前端再调 pay 生成支付码
|
||||
*/
|
||||
export async function renewSubscription(
|
||||
id: number,
|
||||
period: 'month' | 'year' = 'month'
|
||||
): Promise<string> {
|
||||
const res = await request.post<ApiResult<string>>(`${BASE}/renew/${id}`, undefined, {
|
||||
): Promise<SubscribeResult> {
|
||||
const res = await request.post<ApiResult<SubscribeResult>>(`${BASE}/renew/${id}`, undefined, {
|
||||
params: { period }
|
||||
});
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message || '续费成功';
|
||||
return res.data.data as SubscribeResult;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
@@ -286,15 +286,15 @@ export const useAppSubscriptionStore = defineStore('appSubscription', {
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 续费
|
||||
* 续费:基于已有订阅创建续费订单,返回新 subscriptionId(再调 pay 发起支付)
|
||||
* @param id 订阅ID
|
||||
* @param period 周期:month/year
|
||||
*/
|
||||
async renew(id: number, period: 'month' | 'year' = 'month'): Promise<string> {
|
||||
async renew(id: number, period: 'month' | 'year' = 'month'): Promise<SubscribeResult> {
|
||||
try {
|
||||
const msg = await renewSubscription(id, period);
|
||||
const result = await renewSubscription(id, period);
|
||||
this.invalidateCache();
|
||||
return msg;
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('续费失败:', error);
|
||||
throw error;
|
||||
|
||||
@@ -185,13 +185,19 @@
|
||||
</template>
|
||||
<template v-else-if="currentSubscription && currentSubscription.status === 'active' && currentSubscription.expireTime">
|
||||
<span>{{ formatDateTime(currentSubscription.expireTime) }}</span>
|
||||
<a-tag color="green" style="margin-left: 8px">已激活</a-tag>
|
||||
<a-tag :color="isExpired ? 'red' : 'green'" style="margin-left: 8px">
|
||||
{{ isExpired ? '已过期' : '已激活' }}
|
||||
</a-tag>
|
||||
<span v-if="daysToExpire !== null && daysToExpire >= 0 && daysToExpire <= 7" class="expire-warn">
|
||||
({{ daysToExpire }}天后到期)
|
||||
</span>
|
||||
<span v-else-if="daysToExpire !== null && daysToExpire < 0" class="expire-warn">
|
||||
(已过期)
|
||||
(已过期{{ Math.abs(daysToExpire) }}天)
|
||||
</span>
|
||||
<!-- 续费按钮:即将到期(7天内)或已过期时显示 -->
|
||||
<a-button v-if="showRenewButton" type="link" size="small" @click="onRenew" style="padding: 0 0 0 8px">
|
||||
立即续费
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else-if="currentSubscription && currentSubscription.status === 'pending'">
|
||||
<span class="text-orange">待支付 ¥{{ Number(currentSubscription.payPrice || 0).toFixed(2) }}</span>
|
||||
@@ -263,15 +269,15 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 订阅 Modal:选择订阅周期 -->
|
||||
<!-- 订阅/续费 Modal:选择订阅周期(subscribe-新订阅 / renew-续费) -->
|
||||
<a-modal
|
||||
v-model:open="subscribeModalVisible"
|
||||
title="订阅应用"
|
||||
:confirm-loading="subscribing"
|
||||
ok-text="确认订阅"
|
||||
:title="subscribeMode === 'renew' ? '续费应用' : '订阅应用'"
|
||||
:confirm-loading="subscribeMode === 'renew' ? renewing : subscribing"
|
||||
:ok-text="subscribeMode === 'renew' ? '确认续费' : '确认订阅'"
|
||||
cancel-text="取消"
|
||||
:mask-closable="false"
|
||||
@ok="confirmSubscribe"
|
||||
@ok="subscribeMode === 'renew' ? confirmRenew() : confirmSubscribe()"
|
||||
>
|
||||
<div class="subscribe-modal-body">
|
||||
<div class="subscribe-product-name">
|
||||
@@ -280,7 +286,7 @@
|
||||
<a-alert
|
||||
v-if="isFreeProduct"
|
||||
type="success"
|
||||
message="该应用为免费应用,订阅后立即激活"
|
||||
:message="subscribeMode === 'renew' ? '该应用为免费应用,续费后立即激活' : '该应用为免费应用,订阅后立即激活'"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
@@ -416,6 +422,9 @@ const subscriptionLoading = ref(false);
|
||||
const subscribeModalVisible = ref(false);
|
||||
const subscribing = ref(false);
|
||||
const selectedPeriod = ref<'month' | 'year'>('month');
|
||||
// Modal 模式:subscribe-新订阅 / renew-续费(复用同一 Modal)
|
||||
const subscribeMode = ref<'subscribe' | 'renew'>('subscribe');
|
||||
const renewing = ref(false);
|
||||
|
||||
// 支付 Modal(小程序码扫码支付)
|
||||
const payModalVisible = ref(false);
|
||||
@@ -449,6 +458,17 @@ const daysToExpire = computed(() => {
|
||||
return dayjs(expire).startOf('day').diff(dayjs().startOf('day'), 'day');
|
||||
});
|
||||
|
||||
// 是否已过期(expireTime 早于今天)
|
||||
const isExpired = computed(
|
||||
() => daysToExpire.value !== null && daysToExpire.value < 0
|
||||
);
|
||||
|
||||
// 续费按钮显示条件:即将到期(7天内)或已过期
|
||||
const showRenewButton = computed(() => {
|
||||
if (daysToExpire.value === null) return false;
|
||||
return daysToExpire.value <= 7;
|
||||
});
|
||||
|
||||
// 格式化时间
|
||||
const formatDateTime = (dt?: string) => {
|
||||
if (!dt) return '-';
|
||||
@@ -509,9 +529,61 @@ const onSubscribe = () => {
|
||||
return;
|
||||
}
|
||||
selectedPeriod.value = 'month';
|
||||
subscribeMode.value = 'subscribe';
|
||||
subscribeModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 打开续费 Modal(复用订阅 Modal)
|
||||
const onRenew = () => {
|
||||
if (!currentSubscription.value?.id) {
|
||||
message.warning('订阅信息加载中,请稍后再试');
|
||||
return;
|
||||
}
|
||||
selectedPeriod.value = 'month';
|
||||
subscribeMode.value = 'renew';
|
||||
subscribeModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 确认续费(方案1:renew 创建续费订单 → pay 生成小程序码 → 扫码,与 confirmSubscribe 对称)
|
||||
const confirmRenew = async () => {
|
||||
const sub = currentSubscription.value;
|
||||
if (!sub?.id) return;
|
||||
renewing.value = true;
|
||||
try {
|
||||
// 1. 续费:基于原订阅创建续费订单,返回新 subscriptionId
|
||||
const renewResult = await subscriptionStore.renew(sub.id, selectedPeriod.value);
|
||||
// 免费应用:renew 直接激活,status=active
|
||||
if (renewResult.status === 'active') {
|
||||
message.success('续费成功');
|
||||
subscribeModalVisible.value = false;
|
||||
refreshSubscription();
|
||||
return;
|
||||
}
|
||||
// 2. 付费应用:用 pay 生成支付小程序码
|
||||
const payResult = await subscriptionStore.pay(
|
||||
renewResult.subscriptionId,
|
||||
'wechat',
|
||||
envVersion
|
||||
);
|
||||
if ('miniappQrcode' in payResult) {
|
||||
paySubscriptionNo.value =
|
||||
payResult.subscriptionNo || renewResult.subscriptionNo;
|
||||
payQrcode.value = payResult.miniappQrcode;
|
||||
payPrice.value = Number(payResult.payPrice) || 0;
|
||||
payProductName.value = currentProduct.value?.productName || '应用续费';
|
||||
subscribeModalVisible.value = false;
|
||||
payModalVisible.value = true;
|
||||
// 开始轮询支付状态
|
||||
startPolling(paySubscriptionNo.value);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('续费失败:', e);
|
||||
message.error(e?.message || '续费失败,请重试');
|
||||
} finally {
|
||||
renewing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 确认订阅
|
||||
const confirmSubscribe = async () => {
|
||||
const product = currentProduct.value;
|
||||
|
||||
Reference in New Issue
Block a user