Files
WonderQ-Project/docs/superpowers/plans/2026-08-27-mine-records-plan.md
T

292 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 我的出行记录合并 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在 MiniAPP「我的」页面中,把固定的“用车需求”入口和本地“最近浏览”列表收敛到一个“我的出行记录”模块。
**Architecture:** 保留 `RecentlyViewedItem` 和本地存储结构不变,在现有 `MineRecentViews.vue` 内把“用车需求”作为第一条固定操作项,再渲染最近浏览项或空态。通过 `MineMember.vue` 透传一个 `open-vehicle` 事件,由 `mine/index.vue` 调用现有 `goVehicleDemand()`,不新增 API、后端查询或数据库字段。
**Tech Stack:** Vue 3 `<script setup lang="ts">`、uni-app、TypeScript、TailwindCSS、Vitest。
---
### Task 1: Add the failing integration contract test
**Files:**
- Create: `WonderQ-MiniAPP/tests/mine-records.test.ts`
- Read: `WonderQ-MiniAPP/src/pages/mine/components/MineRecentViews.vue`
- Read: `WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue`
- Read: `WonderQ-MiniAPP/src/pages/mine/index.vue`
- [ ] **Step 1: Write the failing test**
Create `WonderQ-MiniAPP/tests/mine-records.test.ts` with this exact content:
```ts
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
function read(relativePath: string) {
return readFileSync(resolve(process.cwd(), relativePath), "utf8");
}
describe("mine travel records integration contract", () => {
it("keeps vehicle demand and recent views in one list", () => {
const component = read("src/pages/mine/components/MineRecentViews.vue");
expect(component).toContain("我的出行记录");
expect(component).toContain("用车需求与最近浏览集中在这里");
expect(component).toContain("items.length + 1");
expect(component).toContain("open-vehicle");
expect(component).toContain("暂无浏览记录");
});
it("wires the vehicle demand action through MineMember to the existing page", () => {
const member = read("src/pages/mine/components/MineMember.vue");
const page = read("src/pages/mine/index.vue");
expect(member).toContain('@open-vehicle="emit(\'open-vehicle\')"');
expect(page).toContain("goVehicleDemand");
expect(page).toContain('@open-vehicle="goVehicleDemand"');
});
});
```
- [ ] **Step 2: Run the test to verify it fails**
Run from `D:\www\znkj\WonderQ-Project\WonderQ-MiniAPP`:
```powershell
yarn test tests/mine-records.test.ts
```
Expected: FAIL because the current component still uses “继续探索”, has no fixed vehicle row, and the parent chain does not expose `open-vehicle`.
### Task 2: Implement the unified list in MineRecentViews
**Files:**
- Modify: `WonderQ-MiniAPP/src/pages/mine/components/MineRecentViews.vue`
- [ ] **Step 1: Replace the component template and emits with the unified-list implementation**
Keep the existing `RecentlyViewedItem` and `RecentlyViewedType` imports and type-label helper. The complete component should be:
```vue
<template>
<view class="mt-6">
<view class="mb-3 flex items-end justify-between">
<view>
<text class="block text-[18px] font-semibold tracking-[0.02em] text-[#24211f]">我的出行记录</text>
<text class="mt-1 block text-[11px] leading-5 text-[#9a9189]">用车需求与最近浏览集中在这里</text>
</view>
<text class="text-[11px] text-[#b18a76]">{{ items.length + 1 }} 项</text>
</view>
<view class="overflow-hidden rounded-[16px] border border-[#e2d9d0] bg-white shadow-[0_8px_20px_rgba(80,58,45,0.05)]">
<button
class="m-0 flex w-full items-center gap-3 border-b border-[#eee7e0] px-3 py-3 text-left"
@click="emit('open-vehicle')"
>
<view class="flex h-14 w-16 shrink-0 items-center justify-center rounded-[10px] bg-[#f4e5dc]">
<uni-icons type="car" :size="24" color="#a45a3d" />
</view>
<view class="min-w-0 flex-1">
<text class="block truncate text-[14px] font-medium text-[#2b2724]">用车需求</text>
<text class="mt-1 block text-[10px] leading-4 text-[#a19890]">填写专属出行方案</text>
</view>
<uni-icons type="right" :size="15" color="#b3a79e" />
</button>
<template v-if="items.length">
<button
v-for="(item, index) in items"
:key="`${item.type}-${item.id}`"
class="m-0 flex w-full items-center gap-3 px-3 py-3 text-left"
:class="index < items.length - 1 ? 'border-b border-[#eee7e0]' : ''"
@click="emit('select', item)"
>
<view class="h-14 w-16 shrink-0 overflow-hidden rounded-[10px] bg-[#f1ece6]">
<image v-if="item.image" class="h-full w-full" :src="item.image" mode="aspectFill" />
<view v-else class="flex h-full w-full items-center justify-center">
<uni-icons type="image" :size="20" color="#b8aaa0" />
</view>
</view>
<view class="min-w-0 flex-1">
<text class="block truncate text-[14px] font-medium text-[#2b2724]">{{ item.title }}</text>
<text class="mt-1 block text-[10px] leading-4 text-[#a19890]">{{ typeLabel(item.type) }}</text>
</view>
<uni-icons type="right" :size="15" color="#b3a79e" />
</button>
</template>
<view v-else class="border-t border-dashed border-[#d8cec5] bg-white/70 px-5 py-6 text-center">
<view class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-[#f4eee8]">
<uni-icons type="eye" :size="20" color="#a45a3d" />
</view>
<text class="mt-3 block text-[14px] font-medium text-[#4a413a]">暂无浏览记录</text>
<text class="mt-1 block text-[11px] leading-5 text-[#9a9189]">去看看玩法,收藏下一段旅程</text>
<button
class="m-0 mx-auto mt-4 flex min-h-[36px] items-center justify-center rounded-full bg-[#a45a3d] px-5 text-[12px] font-medium text-white"
@click="emit('explore')"
>
去看看玩法
</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import type { RecentlyViewedItem, RecentlyViewedType } from "@/lib/recentlyViewed";
defineProps<{
items: RecentlyViewedItem[];
}>();
const emit = defineEmits<{
"open-vehicle": [];
select: [item: RecentlyViewedItem];
explore: [];
}>();
function typeLabel(type: RecentlyViewedType) {
if (type === "wanfa-route") return "玩法路线";
if (type === "team-building") return "团队共创";
return "客片案例";
}
</script>
```
- [ ] **Step 2: Run the focused test to verify the component contract passes**
Run:
```powershell
yarn test tests/mine-records.test.ts
```
Expected: the first test passes; the parent wiring test remains red until Task 3.
### Task 3: Wire the vehicle action through the existing navigation guard
**Files:**
- Modify: `WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue`
- Modify: `WonderQ-MiniAPP/src/pages/mine/index.vue`
- Read: `WonderQ-MiniAPP/src/lib/navigation.ts`
- [ ] **Step 1: Expose the event from MineMember**
In `MineMember.vue`, add the listener to the existing `MineRecentViews` instance:
```vue
<MineRecentViews
:items="props.recentViews"
@open-vehicle="emit('open-vehicle')"
@select="(item) => emit('select-recent', item)"
@explore="emit('open-play')"
/>
```
Add the matching event to `defineEmits`:
```ts
const emit = defineEmits<{
"request-avatar": [];
"open-concierge": [];
"open-play": [];
"open-vehicle": [];
"select-recent": [item: RecentlyViewedItem];
logout: [];
}>();
```
- [ ] **Step 2: Bind the event to goVehicleDemand in the page**
In `mine/index.vue`, add `@open-vehicle="goVehicleDemand"` to `MineMember`:
```vue
@open-concierge="goConcierge"
@open-play="goPlay"
@open-vehicle="goVehicleDemand"
@select-recent="openRecent"
@logout="logout" />
```
Add the existing navigation function to the import list without changing its implementation:
```ts
import {
goConcierge,
goPlay,
goVehicleDemand,
goTeamBuildingDetail,
goWanfaRouteDetail,
goWildArchiveDetail,
} from "@/lib/navigation";
```
This preserves `goVehicleDemand()`'s current `requireLogin()` behavior and `/pages/vehicle-demand` target.
- [ ] **Step 3: Run the focused integration test**
Run:
```powershell
yarn test tests/mine-records.test.ts
```
Expected: PASS with both tests passing.
### Task 4: Verify the MiniAPP change and review the diff
**Files:**
- Verify: `WonderQ-MiniAPP/src/pages/mine/components/MineRecentViews.vue`
- Verify: `WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue`
- Verify: `WonderQ-MiniAPP/src/pages/mine/index.vue`
- Verify: `WonderQ-MiniAPP/tests/mine-records.test.ts`
- [ ] **Step 1: Run all MiniAPP unit tests**
Run from `D:\www\znkj\WonderQ-Project\WonderQ-MiniAPP`:
```powershell
yarn test
```
Expected: all existing tests and `mine-records.test.ts` pass.
- [ ] **Step 2: Run the MiniAPP typecheck and H5 build**
```powershell
yarn build:h5
```
Expected: `vue-tsc --noEmit` and the H5 build complete successfully.
- [ ] **Step 3: Run the WeChat Mini Program build**
```powershell
yarn build:mp-weixin
```
Expected: the Mini Program build completes and emits `dist/build/mp-weixin`.
- [ ] **Step 4: Inspect the final diff and confirm scope**
Run from `D:\www\znkj\WonderQ-Project`:
```powershell
git diff -- WonderQ-MiniAPP/src/pages/mine/components/MineRecentViews.vue WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue WonderQ-MiniAPP/src/pages/mine/index.vue WonderQ-MiniAPP/tests/mine-records.test.ts
git status --short
```
Expected: only the three Mine-related source files and the new focused test are part of this implementation; existing unrelated backend and home-page changes remain untouched. Confirm no new SFC `<style>` block, API path, storage key, database change, or hardcoded sensitive value was introduced.
- [ ] **Step 5: Commit the implementation**
```powershell
git add -- WonderQ-MiniAPP/src/pages/mine/components/MineRecentViews.vue WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue WonderQ-MiniAPP/src/pages/mine/index.vue WonderQ-MiniAPP/tests/mine-records.test.ts
git commit -m "feat: merge vehicle demand and recent views"
```