This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
<view class="expiry-dialog" :class="`expiry-${reminder.level}`" role="dialog"
|
||||
aria-modal="true" aria-label="套餐临期提醒" @keydown.esc="$emit('close')">
|
||||
<view class="reminder-header">
|
||||
<view class="reminder-level">{{ reminder.level_name || '套餐临期提醒' }}</view>
|
||||
<button class="reminder-close" aria-label="关闭临期提醒" @tap="$emit('close')">×</button>
|
||||
<view class="clock-halo" aria-hidden="true"><view class="clock-face"></view></view>
|
||||
<view class="reminder-title">套餐即将到期</view>
|
||||
@@ -56,15 +55,6 @@
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, var(--reminder-tint), var(--bg-primary));
|
||||
}
|
||||
.reminder-level {
|
||||
display: inline-block;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: var(--reminder-color);
|
||||
background: var(--reminder-tint);
|
||||
}
|
||||
.reminder-close {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { popupApi, notificationApi } from '@/api/index.js';
|
||||
import { handledPopupNotificationIds } from '@/utils/popup.js';
|
||||
import {
|
||||
@@ -333,6 +333,8 @@
|
||||
emit('close', 'done');
|
||||
};
|
||||
|
||||
let mounted = true;
|
||||
onUnmounted(() => { mounted = false; });
|
||||
onMounted(async () => {
|
||||
if (!props.identifier) {
|
||||
emit('close', 'empty');
|
||||
@@ -341,6 +343,7 @@
|
||||
|
||||
try {
|
||||
const data = await popupApi.getCandidate(props.page, props.identifier);
|
||||
if (!mounted) return;
|
||||
const item = (data && data.candidate) || null;
|
||||
if (!item || !item.notification_id || handledPopupNotificationIds.has(item.notification_id)) {
|
||||
emit('close', 'empty');
|
||||
@@ -353,6 +356,7 @@
|
||||
resetForm();
|
||||
visible.value = true;
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
// 资源不可见(400/code=1180)与接口异常统一静默关闭,不做 UI 分支
|
||||
console.error('查询弹窗候选失败', e);
|
||||
emit('close', 'invisible');
|
||||
|
||||
@@ -7,24 +7,27 @@
|
||||
- 客户手机号、资产是否绑号、号码归属、绑定提示分别保存;登录字段按当前令牌和资产隔离,退出/过期/切换时清理,旧 phoneBindStatus 不再作为权威状态。
|
||||
- 四种绑定模式使用原 bind-phone 入参;资产首次绑号可输入与客户号不同的号码,1192 提示管理员并禁用当前提交,1184 保留后端错误。按用户最新确认,强制绑定成功保留有效 token 和资产标识,直接进入首页,不再要求重登。
|
||||
- 客户换号只更新后端确认的客户号码,不改资产关联;归属事实失效,下一次真实登录重新确认。换号的原号来源不再放进 URL。
|
||||
- asset/info 的共享返回入口接收一次性临期提醒,首页使用可关闭横幅。0 天正常显示,使用服务器等级和上海时区。绑定页可以通过 asset/info 预填,提前收到的提醒保留在当前会话中,进入首页后展示。
|
||||
- asset/info 的共享返回入口接收一次性临期提醒,首页按最新要求使用居中、可关闭弹窗,已移除旧横幅组件。0 天正常显示,使用服务器等级和上海时区;与风险换货/运营候选、普通通知依次展示。绑定页预填收到的提醒留在当前会话,进入首页展示。
|
||||
- 旧首页通知弹窗排除临期项,通知中心保留。全部已读改 POST,单条已读保持 PUT;成功后重新读取列表和计数,失败不乐观清空,延迟响应不能覆盖新的资产上下文。
|
||||
- 通知卡片移除左侧未读强调边框和小圆点,标题右侧显示“未读/已读”小按钮。点击卡片或按钮成功标记后更新,失败仍显示未读。
|
||||
|
||||
## 验证证据
|
||||
|
||||
| 证据 | 结果 | 说明 |
|
||||
|---|---|---|
|
||||
| `npm run test:h5-integration` | 15/15 通过 | 实际 store/API 模块及页面 setup,使用 Vue 响应性;原生 API、生命周期和后端响应为模拟 |
|
||||
| `npm run test:h5-integration` | 17/17 通过 | 实际 store/API 模块及页面 setup,使用 Vue 响应性;原生 API、生命周期和后端响应为模拟 |
|
||||
| `npm run build:h5` | 通过 | uni-app H5 开发配置构建 |
|
||||
| `npm run build:h5:production` | 通过 | uni-app H5 生产配置构建 |
|
||||
| `openspec validate update-october-batch1-h5-integration --strict` | 通过 | 提案、需求和场景结构校验 |
|
||||
| `git diff --check` | 通过 | 默认 Git 换行配置下未发现补丁空白错误;Windows 换行提示不影响构建 |
|
||||
| 本地浏览器,390×844 | 通过 | 0 天红色横幅、上海到期时间、关闭、绑定客户号预填、编辑不同号码、1192 联系管理员弹窗及禁用提交,无页面运行错误 |
|
||||
| 本地浏览器,390×844 / 320×640 | 通过 | 居中临期弹窗、0 天/上海时间、关闭、通知标题右侧未读按钮、点击卡片变已读;此前绑定表单及 1192 检查保留,无页面运行错误 |
|
||||
|
||||
测试文件:[tests/october-h5.test.mjs](../../../tests/october-h5.test.mjs)。覆盖绑定模式/成功后的分类更新、登录提示开关、状态持久化/隔离、换号不迁移资产、1192/1184、强制绑定直接进入首页且 token/客户信息/提醒保留、预填失败、共享接收/并发、0~15 天/上海边界、旧弹窗过滤、POST/PUT 请求方法、通知失败与切换、模拟 A/B 全部已读以及 401 状态清理。
|
||||
|
||||
本次修订删除了绑定成功后的登录态清理和“绑定手机号成功,请重新登录”提示,替代旧手动重登提案;真正的退出或 401 登录过期处理不变。
|
||||
|
||||
最新界面修订:`ExpiryReminderPopup.vue` 替代横幅;候选请求结束后才消费临期弹窗,普通通知显示时收到临期提醒会排队。候选组件卸载后的延迟响应不再影响新页面。`notifications.vue` 标题右侧使用阅读状态按钮,原标记接口不变。
|
||||
|
||||
本地渲染复验:先完成生产构建,再运行以下命令。使用独立浏览器会话和虚构号码,所有业务接口改写到本地模拟服务。
|
||||
|
||||
```powershell
|
||||
@@ -42,6 +45,8 @@ agent-browser --session october-h5-ui close
|
||||
|
||||
模拟服务固定在 `127.0.0.1:48138`,init 脚本仅在该地址注入;检查后使用 Ctrl+C 停止服务。该模拟接口故意重复返回提醒用于检查前端防重复,不能证明后端每日一次规则。
|
||||
|
||||
需要复验通知按钮时,在启动服务的终端先设置 `$env:H5_MOCK_NOTIFICATIONS='1'`;进入首页关闭临期弹窗,再点击站内通知。模拟通知初始为未读,点击卡片后服务端模拟状态变已读。
|
||||
|
||||
## 仍需真实联调
|
||||
|
||||
1. 微信公众号真实登录应返回三个布尔字段;尤其验证客户无号/资产有号时只给客户绑定,以及客户已有号时资产可绑定不同号码。
|
||||
|
||||
@@ -52,9 +52,11 @@
|
||||
|
||||
原资产仍可保持已绑定,但其号码可能不再属于客户;例如原 `asset_phone_is_own=true` 在客户改成不同号码后可能为 false。若响应不提供更新后的归属事实,不能把旧 true 留作最新事实,应失效该归属状态,等下一次真实登录响应确认;不借此要求资产改绑。
|
||||
|
||||
### 4. 一次性临期提醒使用横幅及共享接收
|
||||
### 4. 一次性临期提醒使用弹窗及共享接收
|
||||
|
||||
首页就是当前 H5 的资产信息展示页,本次不新增资产详情路由。选择可关闭横幅展示临期信息,避免与已有风险换货、运营及支付弹窗争抢显示。
|
||||
首页就是当前 H5 的资产信息展示页,本次不新增资产详情路由。按用户最新要求,移除横幅,使用 `ExpiryReminderPopup` 居中弹窗:等级色、小型时钟图形、突出显示剩余天数和预计到期时间,提供右上关闭及“我知道了”操作。
|
||||
|
||||
首页按现有风险换货/运营候选 → 临期提醒 → 普通通知的顺序展示,一次只显示一个。asset/info 和候选请求未完成时不消费待展示提醒;通知已显示时才收到临期提醒,则等通知关闭后显示。候选按钮立即导航时保持提醒待展示;候选组件卸载后的异步响应不得回调新页面。临期关闭不再占首页布局,也不因此创建新的业务请求。
|
||||
|
||||
原样保留 `expiry_reminder.remaining_days`、`expires_at`、`level`、`level_name`;使用服务端等级映射 red / purple / pink 的样式,保留文本提示,不能只靠颜色传达紧急程度。`remaining_days=0` 必须正常展示。日期展示遵循 Asia/Shanghai,不能用客户端当前时区重算剩余自然日或等级。
|
||||
|
||||
@@ -62,7 +64,7 @@
|
||||
|
||||
后端负责“客户 + 资产 + 上海自然日”的每日一次;前端只防止同一次接收被多个组件重复展示,不增加每日定时推送、补发请求或长期缓存复播。关闭/已展示的提醒在当天后续无字段响应时不得重新出现。审核所有 `asset/info` 调用方,特别是绑定前预填及首页 TrafficCard 的请求顺序;绑定成功使用同一 token,提醒无需跨登录传递。真实退出、登录过期或切换上下文仍清理旧提醒。
|
||||
|
||||
横幅展示/关闭不自动标记通知已读:提醒不含 notification_id,不能按标题、日期或列表位置猜测 ID,也不能调用 read-all 消掉新通知。临期通知继续由通知中心管理阅读状态。
|
||||
临期弹窗展示/关闭不自动标记通知已读:提醒不含 notification_id,不能按标题、日期或列表位置猜测 ID,也不能调用 read-all 消掉新通知。临期通知继续由通知中心管理阅读状态。
|
||||
|
||||
### 5. 通知仅调整临期来源与全部已读方法
|
||||
|
||||
@@ -70,7 +72,9 @@
|
||||
|
||||
列表、未读数保持原入参,不增加文档未定义的 identifier/asset_id;后端基于当前认证资产过滤临期通知,并非所有通知都只属于当前资产。切换资产后刷新列表和计数,不将 A 的未读结果拼接到 B。
|
||||
|
||||
全部已读使用 POST,保留通知中心主动触发;失败不提前清除未读状态。成功后以刷新结果对齐本地列表/未读数,不能把其他资产的临期通知置已读。单条已读继续 PUT。风险换货、运营弹窗及临期横幅均不得自动调用全部已读。
|
||||
全部已读使用 POST,保留通知中心主动触发;失败不提前清除未读状态。成功后以刷新结果对齐本地列表/未读数,不能把其他资产的临期通知置已读。单条已读继续 PUT。风险换货、运营及临期弹窗均不得自动调用全部已读。
|
||||
|
||||
通知卡片保留常规边框,移除左侧未读强调边框和小圆点。标题右侧显示绿色“未读”或灰色“已读”小按钮;点击整张卡片或状态按钮调用现有单条 PUT,成功后显示“已读”并刷新计数,失败保留“未读”。按钮阻止事件冒泡,避免一次点击触发两次标记。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
@@ -82,7 +86,7 @@
|
||||
## Migration Plan
|
||||
|
||||
1. 提案批准后,先完成状态拆分及旧缓存失效,再调整绑定/换号行为。
|
||||
2. 加入临期提醒共享接收和横幅,排除首页旧临期弹窗来源。
|
||||
2. 加入临期提醒共享接收和居中弹窗及排队显示,排除首页旧临期通知弹窗来源,调整通知阅读状态按钮。
|
||||
3. 切换全部已读为 POST,回归当前资产的列表与未读数。
|
||||
4. 完成任务中的场景验证及 H5 构建,记录实际结果;不以静态构建代替真实接口联调。
|
||||
5. 需要回退时只回退本次前端改动;服务端新契约不变,不能恢复“客户换号即资产迁移”的旧假设作为兼容策略。
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
- 对 `bind-phone` 的 `1192` 提示“该资产已绑定手机号,如需更换请联系管理员”,不提供重试当前绑定请求的操作;保留 `1184` 的既有错误处理。
|
||||
- 绑定成功保留有效 token 和资产标识;登录引导的绑定页直接进入首页,不再清除登录态、提示或要求重新登录。普通绑定成功仍返回上一页。
|
||||
- **BREAKING(替代旧前端假设)**:`change-phone` 只修改客户手机号,不更新任何资产手机号关联;停止将新客户手机号写入资产绑定缓存。
|
||||
- 接收 `asset/info.expiry_reminder`,在首页资产信息区展示可关闭横幅,展示后端提供的剩余天数、到期时间和等级;无字段时不自行计算或补发提醒。
|
||||
- 接收 `asset/info.expiry_reminder`,在首页展示居中、可关闭的临期弹窗,展示后端提供的剩余天数、到期时间和等级;无字段时不自行计算或补发提醒。
|
||||
- 统一接收各 `asset/info` 调用返回的一次性提醒,防止首页子组件或其他调用方先获得字段后将其丢弃;不为提醒新增轮询或额外请求。
|
||||
- 首页旧未读通知弹窗不再以临期通知作为弹窗依据;临期通知仍保留在通知中心,其他通知和既有运营/风险换货弹窗不变。
|
||||
- 临期弹窗与风险换货/运营、普通通知弹窗依次展示,不叠加;通知卡片去掉左侧未读边框,标题右侧使用“未读/已读”小按钮,点击卡片或按钮后按单条已读响应更新。
|
||||
- **BREAKING(请求方法切换)**:全部已读改为 `POST /api/c/v1/notifications/read-all`,不再发送旧 `PUT` 或自动回退;通知列表和未读数沿用现有入参,由后端过滤当前资产的临期通知。
|
||||
|
||||
## Scope and confirmed decisions
|
||||
@@ -24,6 +25,8 @@
|
||||
|
||||
后续用户补充确认:绑定手机号不会使 token 失效,绑定成功不得再跳转登录页。本次修订覆盖旧强制绑定后手动重登流程。
|
||||
|
||||
后续界面修订:临期提醒必须使用弹窗而非横幅;通知卡片用标题右侧的小按钮表达“未读/已读”,点击卡片完成已读。
|
||||
|
||||
| 接口 | 本次处理 | 不变部分 |
|
||||
|---|---|---|
|
||||
| `POST /api/c/v1/auth/wechat-login` | 消费两个资产绑定布尔字段及新的绑定提示语义 | OAuth、令牌、资产校验流程 |
|
||||
@@ -55,6 +58,6 @@
|
||||
- [update-post-bind-wechat-login-recheck](../update-post-bind-wechat-login-recheck/proposal.md):根据用户最新确认,替代其“强制绑定后清除 token 并要求手动重登”的约定;改为保留登录态直接进入首页,不改写历史提案。
|
||||
- [update-july-h5-c-iteration](../update-july-h5-c-iteration/proposal.md):仅替代临期通知作为首页弹窗来源的约定,保留通知中心、其他通知和套餐到期信息展示。
|
||||
- [add-personal-notifications](../add-personal-notifications/proposal.md):仅切换全部已读方法及遵循当前资产临期通知范围,不把单条已读的 PUT 一并改为 POST。
|
||||
- [add-h5-popup-candidate-flow](../add-h5-popup-candidate-flow/proposal.md):保留候选弹窗与单条已读链路;临期横幅独立展示,不调用全部已读来关闭任何弹窗。
|
||||
- [add-h5-popup-candidate-flow](../add-h5-popup-candidate-flow/proposal.md):保留候选弹窗与单条已读链路;候选结束后再显示临期弹窗,不调用全部已读来关闭任何弹窗。
|
||||
|
||||
技术选择、字段依据及风险见 [design.md](design.md),执行及验收清单见 [tasks.md](tasks.md)。
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
### Requirement: Expiry reminders SHALL use the optional asset-info response
|
||||
|
||||
H5 SHALL 仅以 `GET /api/c/v1/asset/info` 返回的可选 `expiry_reminder` 作为本次临期主动提醒来源,在首页资产信息区展示可关闭横幅。剩余天数、到期时间、等级及等级名称 SHALL 使用 `remaining_days`、`expires_at`、`level`、`level_name`,SHALL NOT 用旧未读临期通知或客户端计算补发提醒。
|
||||
H5 SHALL 仅以 `GET /api/c/v1/asset/info` 返回的可选 `expiry_reminder` 作为本次临期主动提醒来源,在首页展示居中、可关闭的临期弹窗,而不是内联横幅。剩余天数、到期时间、等级及等级名称 SHALL 使用 `remaining_days`、`expires_at`、`level`、`level_name`,SHALL NOT 用旧未读临期通知或客户端计算补发提醒。
|
||||
|
||||
#### Scenario: Asset info includes a reminder
|
||||
|
||||
- **WHEN** 当前资产响应含有效 expiry_reminder
|
||||
- **THEN** H5 SHALL 展示该资产临期横幅及后端提供的提醒信息
|
||||
- **THEN** H5 SHALL 展示该资产临期弹窗及后端提供的提醒信息
|
||||
|
||||
#### Scenario: Asset info omits the optional field
|
||||
|
||||
- **WHEN** 响应无 expiry_reminder,且当前进入没有已接收的待展示提醒
|
||||
- **THEN** H5 SHALL 不显示临期横幅,也不从 is_expiring 或通知列表补发
|
||||
- **THEN** H5 SHALL 不显示临期弹窗,也不从 is_expiring 或通知列表补发
|
||||
|
||||
### Requirement: Reminder presentation SHALL preserve server calendar and levels
|
||||
|
||||
@@ -49,7 +49,7 @@ H5 SHALL 在 asset/info 的共享成功返回入口接收一次性提醒,按
|
||||
|
||||
### Requirement: Expiry notifications SHALL remain in the notification center without driving popups
|
||||
|
||||
H5 SHALL 从首页旧未读通知弹窗来源排除临期项,同时保留通知中心临期列表、未读数和既有阅读操作。临期横幅展示或关闭 SHALL NOT 自动标记通知已读,其他通知及候选弹窗链路 SHALL 保持不变。
|
||||
H5 SHALL 从首页旧未读通知弹窗来源排除临期项,同时保留通知中心临期列表、未读数和既有阅读操作。临期弹窗展示或关闭 SHALL NOT 自动标记通知已读,其他通知及候选弹窗链路 SHALL 保持不变。
|
||||
|
||||
#### Scenario: Unread expiry notification exists without a new reminder
|
||||
|
||||
@@ -57,7 +57,21 @@ H5 SHALL 从首页旧未读通知弹窗来源排除临期项,同时保留通
|
||||
- **THEN** H5 SHALL 不以该项触发旧首页临期弹窗
|
||||
- **AND** 通知中心 SHALL 仍可展示和阅读该项
|
||||
|
||||
#### Scenario: User closes an expiry banner
|
||||
#### Scenario: User closes an expiry popup
|
||||
|
||||
- **WHEN** 用户关闭临期横幅
|
||||
- **WHEN** 用户关闭临期弹窗
|
||||
- **THEN** H5 SHALL 不猜测 notification_id、不发送自动单条已读或全部已读请求
|
||||
|
||||
### Requirement: Home popups SHALL display sequentially without overlapping
|
||||
|
||||
H5 SHALL 在风险换货/运营候选结束后展示待处理临期提醒,再继续既有普通通知流程,一次只显示一个弹窗。提醒 SHALL 仅在实际显示时标记本次已展示;导航或其他弹窗占用时 SHALL 保持待展示状态。
|
||||
|
||||
#### Scenario: Candidate and expiry reminder are both present
|
||||
|
||||
- **WHEN** 候选弹窗尚未结束且已收到 expiry_reminder
|
||||
- **THEN** H5 SHALL 先展示候选,关闭后再展示临期提醒,不能叠加两个弹窗
|
||||
|
||||
#### Scenario: Notification is already open when reminder arrives
|
||||
|
||||
- **WHEN** 普通通知弹窗正在显示,随后收到 expiry_reminder
|
||||
- **THEN** H5 SHALL 暂存临期提醒,等通知关闭后再展示
|
||||
|
||||
@@ -37,5 +37,19 @@ H5 SHALL 在用户主动点击通知中心“全部已读”时调用 `POST /api
|
||||
|
||||
#### Scenario: Reminder or popup is displayed
|
||||
|
||||
- **WHEN** 临期横幅、运营候选或风险换货弹窗展示或关闭
|
||||
- **WHEN** 临期、运营候选或风险换货弹窗展示或关闭
|
||||
- **THEN** H5 SHALL 不自动调用 read-all 来结束展示链路
|
||||
|
||||
### Requirement: Notification cards SHALL show a right-aligned read-status button
|
||||
|
||||
H5 SHALL 在通知标题右侧显示小型“未读/已读”按钮,SHALL NOT 用左侧强调边框或小圆点表示未读。点击卡片或按钮 SHALL 对未读项调用现有单条已读 PUT,成功后切换为“已读”并刷新计数;失败 SHALL 保留未读状态,已读项不重复请求。
|
||||
|
||||
#### Scenario: User taps an unread card
|
||||
|
||||
- **WHEN** 用户点击未读通知卡片或标题右侧“未读”按钮,标记接口成功
|
||||
- **THEN** 小按钮 SHALL 切换为“已读”,卡片不新增左侧边框
|
||||
|
||||
#### Scenario: Mark-read fails
|
||||
|
||||
- **WHEN** 点击未读通知后标记接口失败
|
||||
- **THEN** 小按钮 SHALL 保持“未读”,用户仍可再次操作
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
- [x] 3.1 保留 expiry_reminder 的可选语义及四个字段,并在 assetApi.getInfo 的共同返回入口接收一次性提醒。
|
||||
- [x] 3.2 实现当前账号/资产隔离的临时共享提醒状态;同次进入的后续空字段响应不能覆盖已接收的提醒。
|
||||
- [x] 3.3 在首页资产信息区显示可关闭横幅,按后端 red / purple / pink 展示,正确处理 0 天及上海时区的到期时间。
|
||||
- [x] 3.3 按最新要求,在首页显示可关闭的居中临期弹窗,按后端 red / purple / pink 展示,正确处理 0 天及上海时区的到期时间;与其他弹窗排队展示。
|
||||
- [x] 3.4 检查 TrafficCard、绑定预填、RenewalPaymentPopup 及其他 getInfo 调用方;绑定页恢复 asset/info 预填,提醒保留在同一有效会话内,进入首页展示。
|
||||
- [x] 3.5 从首页旧通知弹窗来源排除临期通知,通知中心保留;不影响其他通知及风险换货/运营弹窗。
|
||||
- [x] 3.6 不为临期提醒新增轮询、每日补发、无字段补弹或自动已读请求。
|
||||
@@ -34,13 +34,14 @@
|
||||
- [x] 5.1 通过自动/模拟测试验证四种绑定模式及强制开关关闭;真实微信授权验收另见 IMPLEMENTATION.md。
|
||||
- [x] 5.2 通过模拟测试和本地浏览器验证不同号码输入、1192 联系管理员且不重试、1184 提示、失败状态不变。
|
||||
- [x] 5.3 通过自动/模拟测试验证换号只更新客户、资产关联保持、归属失效及旧缓存/账号/资产隔离。
|
||||
- [x] 5.4 通过自动/模拟测试验证字段缺省、0/3/4/7/8/15 天及等级、上海日期边界、重复进入和 A/B 上下文;浏览器检查 0 天及关闭行为。
|
||||
- [x] 5.4 通过自动/模拟测试验证字段缺省、0/3/4/7/8/15 天及等级、上海日期边界、重复进入和 A/B 上下文;浏览器检查 0 天弹窗及关闭行为。
|
||||
- [x] 5.5 通过共享接收和首页脚本测试验证并发/其他调用方先返回提醒不丢失,以及旧首页弹窗只排除临期通知。
|
||||
- [ ] 5.6 用同一客户的 A/B 两资产验证列表、未读数与 A 上 POST 全部已读;B 的临期通知仍保持未读,普通通知范围不被前端扩大过滤。
|
||||
- [ ] 5.7 验证单条已读仍 PUT、read-all 失败不提前清空、运营/风险换货弹窗和支付/实名/套餐展示不回归。
|
||||
- [x] 5.8 执行 npm run build:h5、npm run build:h5:production,均通过;实施记录明确区分模拟验证与待真实联调场景。
|
||||
- [x] 5.9 执行 openspec validate update-october-batch1-h5-integration --strict,校验通过;仅按实际完成结果更新本任务清单。
|
||||
- [x] 5.10 新增并执行 npm run test:h5-integration,15 组自动/模拟测试全部通过,覆盖强制绑定直接进入首页、token 保留、提醒保留及预填失败处理。
|
||||
- [x] 5.11 使用本地模拟预览检查移动端临期横幅、绑定表单预填/不同号输入及 1192 弹窗/禁用操作;不发送真实业务请求。
|
||||
- [x] 5.10 新增并执行 npm run test:h5-integration,17 组自动/模拟测试全部通过,覆盖强制绑定、token 保留、弹窗排队、通知未读按钮及失败状态。
|
||||
- [x] 5.11 使用本地模拟预览检查移动端居中临期弹窗、绑定表单预填/不同号输入及 1192 弹窗/禁用操作;不发送真实业务请求。
|
||||
- [x] 5.12 去掉通知卡片左侧未读边框,改为标题右侧未读/已读小按钮;验证点击卡片成功后更新、失败不提前显示已读。
|
||||
|
||||
5.6 的客户端 A/B 隔离逻辑、5.7 的单条 PUT 和失败不提前清空均已模拟测试通过;真实后端范围及完整支付/实名/候选弹窗回归未完成,不勾选。
|
||||
|
||||
@@ -961,6 +961,9 @@
|
||||
const onPopupCandidateClose = (reason) => {
|
||||
if (!homeActive.value) return;
|
||||
popupCandidateShow.value = false;
|
||||
// The candidate action navigates away immediately. Keep any unshown
|
||||
// expiry reminder pending rather than consuming it before that navigation.
|
||||
if (reason === 'action') return;
|
||||
if (reason === 'empty' || reason === 'invisible') {
|
||||
notificationFallbackPending = true;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,13 @@ test('expiry boundaries and Shanghai rendering preserve server values, including
|
||||
assert.equal(formatShanghaiExpiry('2026-10-13T12:30:00'), '2026-10-13 12:30:00');
|
||||
});
|
||||
|
||||
test('homepage expiry popup never displays backend level labels', () => {
|
||||
const source = readFileSync(resolve(root, 'components/ExpiryReminderPopup.vue'), 'utf8');
|
||||
assert.doesNotMatch(source, /level_name|reminder-level/);
|
||||
assert.match(source, /reminder\.remaining_days/);
|
||||
assert.match(source, /formatShanghaiExpiry\(reminder\.expires_at\)/);
|
||||
});
|
||||
|
||||
test('shared asset-info receiver keeps a child-first reminder and rejects stale sessions/phones', async () => {
|
||||
const r = runtime();
|
||||
const { assetApi } = await r.load('api/modules/asset.js');
|
||||
|
||||
Reference in New Issue
Block a user