fix: 沉默期提醒每次登录都弹,有未支付延期订单时提示去支付或作废
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Has been cancelled
- 频率从每天一次改为每次登录一次:重新登录后会再次提醒,同一次登录内回首页不重复 - 已过期套餐有未支付延期订单时,弹窗提示继续支付或作废
This commit is contained in:
@@ -2,10 +2,13 @@
|
||||
<view class="silence-overlay" @tap.stop>
|
||||
<view class="silence-dialog" :class="`silence-${reminder.kind}`" role="dialog" aria-modal="true" aria-label="套餐沉默期提醒">
|
||||
<button class="silence-close" aria-label="关闭沉默期提醒" @tap="$emit('close')">×</button>
|
||||
<view class="silence-title">{{ reminder.kind === 'expired' ? '套餐沉默期已过期' : '请尽快完成实名' }}</view>
|
||||
<view class="silence-title">{{ title }}</view>
|
||||
<view v-if="reminder.packageName" class="silence-package">{{ reminder.packageName }}</view>
|
||||
<view class="silence-body">
|
||||
<template v-if="reminder.kind === 'expired'">
|
||||
<template v-if="reminder.pendingOrderNo">
|
||||
套餐沉默期已于 <text class="silence-strong">{{ deadline }}</text> 过期,您有一笔未支付的延期订单({{ reminder.pendingOrderNo }}),请继续支付;不需要了可以作废后重新办理。
|
||||
</template>
|
||||
<template v-else-if="reminder.kind === 'expired'">
|
||||
套餐需在 <text class="silence-strong">{{ deadline }}</text> 前完成实名,现已过期。办理延期后可继续实名激活使用。
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -13,7 +16,7 @@
|
||||
<text class="silence-strong">{{ reminder.remainingDays }}</text> 天,过期后需办理延期。
|
||||
</template>
|
||||
</view>
|
||||
<button class="silence-confirm" @tap="$emit('action')">{{ reminder.kind === 'expired' ? '去延期' : '查看套餐' }}</button>
|
||||
<button class="silence-confirm" @tap="$emit('action')">{{ actionText }}</button>
|
||||
<button class="silence-later" @tap="$emit('close')">我知道了</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -26,6 +29,14 @@
|
||||
const props = defineProps({ reminder: { type: Object, required: true } });
|
||||
defineEmits(['close', 'action']);
|
||||
const deadline = computed(() => formatSilenceDate(props.reminder.silence?.silence_deadline_at));
|
||||
const title = computed(() => {
|
||||
if (props.reminder.pendingOrderNo) return '延期订单未支付';
|
||||
return props.reminder.kind === 'expired' ? '套餐沉默期已过期' : '请尽快完成实名';
|
||||
});
|
||||
const actionText = computed(() => {
|
||||
if (props.reminder.pendingOrderNo) return '去支付或作废';
|
||||
return props.reminder.kind === 'expired' ? '去延期' : '查看套餐';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -151,7 +151,8 @@
|
||||
assetApi,
|
||||
deviceApi,
|
||||
hasActiveOrPendingPackage,
|
||||
notificationApi
|
||||
notificationApi,
|
||||
packageSilenceApi
|
||||
} from '@/api/index.js';
|
||||
import {
|
||||
useUserStore,
|
||||
@@ -201,11 +202,20 @@
|
||||
const data = await assetApi.getPackageHistory(identifier, 1, 20, { status: 0 });
|
||||
if (entryId !== homeEntryId || !homeActive.value || identifier !== String(userStore.state.identifier || '').trim()) return;
|
||||
const reminder = pickSilenceReminder(data?.items);
|
||||
if (reminder && !uni.getStorageSync(silenceReminderKey(identifier, reminder, getShanghaiDay()))) {
|
||||
silenceReminderIdentifier = identifier;
|
||||
silenceReminder.value = reminder;
|
||||
advanceHomePopupQueue();
|
||||
if (!reminder || uni.getStorageSync(silenceReminderKey(identifier, reminder, getSessionContext().token))) return;
|
||||
// 已过期的套餐再查一次延期资格:有未支付的延期订单时,弹窗改为提醒去支付或作废
|
||||
if (reminder.kind === 'expired') {
|
||||
try {
|
||||
const eligibility = await packageSilenceApi.getEligibility(reminder.packageUsageId);
|
||||
if (eligibility?.pending_order_no) reminder.pendingOrderNo = eligibility.pending_order_no;
|
||||
} catch (error) {
|
||||
// 资格查询失败不影响提醒本身
|
||||
}
|
||||
}
|
||||
if (entryId !== homeEntryId || !homeActive.value || identifier !== String(userStore.state.identifier || '').trim()) return;
|
||||
silenceReminderIdentifier = identifier;
|
||||
silenceReminder.value = reminder;
|
||||
advanceHomePopupQueue();
|
||||
} catch (error) {
|
||||
// 提醒失败不影响首页其他功能
|
||||
console.error('加载沉默期提醒失败', error);
|
||||
@@ -218,7 +228,7 @@
|
||||
silenceReminder.value = null;
|
||||
return false;
|
||||
}
|
||||
uni.setStorageSync(silenceReminderKey(silenceReminderIdentifier, reminder, getShanghaiDay()), 1);
|
||||
uni.setStorageSync(silenceReminderKey(silenceReminderIdentifier, reminder, getSessionContext().token), 1);
|
||||
silenceReminderShow.value = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ function runtime(initial = {}) {
|
||||
onLoad() {}, onReachBottom() {}
|
||||
});
|
||||
if (specifier === '@/api/index.js') return synthetic('api-index', {
|
||||
...api, deviceApi: {}, hasActiveOrPendingPackage: async () => true
|
||||
packageSilenceApi: {}, ...api, deviceApi: {}, hasActiveOrPendingPackage: async () => true
|
||||
});
|
||||
if (specifier === '@/utils/payment.js') return synthetic('payment', {
|
||||
...payment, consumePendingPaymentRefresh: (...args) => payment.consumePendingPaymentRefresh(...args)
|
||||
@@ -934,7 +934,7 @@ test('order completion and payment return reread actual asset state, not a presu
|
||||
assert.equal(reads, 4);
|
||||
});
|
||||
|
||||
test('home shows the silence reminder after other popups, once per day, and opens the extension page', async () => {
|
||||
test('home shows the silence reminder after other popups, once per login, and opens the extension page', async () => {
|
||||
const r = runtime();
|
||||
r.api.assetApi = {
|
||||
getInfo: async () => ({ identifier: 'A' }),
|
||||
@@ -944,20 +944,31 @@ test('home shows the silence reminder after other popups, once per day, and open
|
||||
children: []
|
||||
}] })
|
||||
};
|
||||
let pendingOrderNo = '';
|
||||
r.api.packageSilenceApi = { getEligibility: async () => ({ can_extend: !pendingOrderNo, pending_order_no: pendingOrderNo }) };
|
||||
r.api.notificationApi = { getUnreadCount: async () => ({ count: 0 }), getList: async () => ({ items: [] }) };
|
||||
const page = await r.load('pages/index/index.vue', ['silenceReminder', 'silenceReminderShow', 'onPopupCandidateClose', 'openSilenceReminderAction', 'closeSilenceReminder']);
|
||||
await r.shown[0](); await r.tick(); await r.tick();
|
||||
const settle = async () => { for (let i = 0; i < 6; i++) await r.tick(); };
|
||||
await r.shown[0](); await settle();
|
||||
// 运营弹窗仍在时不弹沉默期提醒
|
||||
assert.equal(page.silenceReminderShow.value, false);
|
||||
page.onPopupCandidateClose('empty'); await r.tick();
|
||||
page.onPopupCandidateClose('empty'); await settle();
|
||||
assert.equal(page.silenceReminderShow.value, true);
|
||||
assert.equal(page.silenceReminder.value.kind, 'expired');
|
||||
assert.equal(page.silenceReminder.value.pendingOrderNo, undefined);
|
||||
page.openSilenceReminderAction();
|
||||
assert.equal(page.silenceReminderShow.value, false);
|
||||
assert.equal(r.calls.at(-1).kind, 'navigateTo');
|
||||
assert.equal(r.calls.at(-1).url, '/pages/silence-extension/silence-extension?package_usage_id=44961');
|
||||
// 同一天再次回到首页不再弹
|
||||
r.hidden[0](); await r.shown[0](); await r.tick(); await r.tick();
|
||||
page.onPopupCandidateClose('empty'); await r.tick();
|
||||
// 同一次登录内再回到首页不重复弹
|
||||
r.hidden[0](); await r.shown[0](); await settle();
|
||||
page.onPopupCandidateClose('empty'); await settle();
|
||||
assert.equal(page.silenceReminderShow.value, false);
|
||||
// 重新登录(凭证变化)后再次提醒;有未支付延期订单时提示去支付或作废
|
||||
pendingOrderNo = 'SE20261107200004975908';
|
||||
r.storage.set('token', 'session-B');
|
||||
r.hidden[0](); await r.shown[0](); await settle();
|
||||
page.onPopupCandidateClose('empty'); await settle();
|
||||
assert.equal(page.silenceReminderShow.value, true);
|
||||
assert.equal(page.silenceReminder.value.pendingOrderNo, 'SE20261107200004975908');
|
||||
});
|
||||
|
||||
@@ -90,5 +90,8 @@ test('首页沉默期提醒:优先已过期可延期,其次 7 天内到期
|
||||
assert.equal(waiting.remainingDays, 3);
|
||||
|
||||
assert.equal(pickSilenceReminder([node(7, { silence_status: 'ended' })], now), null);
|
||||
assert.equal(silenceReminderKey('A', { packageUsageId: 3, kind: 'expired' }, '2026-11-07'), 'silence_reminder:A:3:expired:2026-11-07');
|
||||
const keyA = silenceReminderKey('A', { packageUsageId: 3, kind: 'expired' }, 'token-1');
|
||||
assert.match(keyA, /^silence_reminder:A:3:expired:/);
|
||||
assert.equal(keyA, silenceReminderKey('A', { packageUsageId: 3, kind: 'expired' }, 'token-1'));
|
||||
assert.notEqual(keyA, silenceReminderKey('A', { packageUsageId: 3, kind: 'expired' }, 'token-2'));
|
||||
});
|
||||
|
||||
@@ -164,6 +164,18 @@ export const pickSilenceReminder = (items, now = Date.now()) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
/** 每个资产、每笔套餐、每种提醒每天最多弹一次的存储键 */
|
||||
export const silenceReminderKey = (identifier, reminder, day) =>
|
||||
`silence_reminder:${identifier}:${reminder.packageUsageId}:${reminder.kind}:${day}`;
|
||||
const shortHash = (value) => {
|
||||
let hash = 0;
|
||||
const text = String(value || '');
|
||||
for (let index = 0; index < text.length; index++) {
|
||||
hash = (hash * 31 + text.charCodeAt(index)) | 0;
|
||||
}
|
||||
return (hash >>> 0).toString(36);
|
||||
};
|
||||
|
||||
/**
|
||||
* 每次登录、每个资产、每笔套餐、每种提醒最多弹一次的存储键:
|
||||
* 带上登录凭证摘要,重新登录后会再次提醒;同一次登录内反复回首页不重复弹。
|
||||
*/
|
||||
export const silenceReminderKey = (identifier, reminder, sessionToken) =>
|
||||
`silence_reminder:${identifier}:${reminder.packageUsageId}:${reminder.kind}:${shortHash(sessionToken)}`;
|
||||
|
||||
Reference in New Issue
Block a user