Files
WonderQ-Project/docs/superpowers/plans/2026-08-27-mine-records-plan.md
2026-08-27 11:45:01 +08:00

10 KiB
Raw Blame History

我的出行记录合并 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:

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:

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:

<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:

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:

      <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:

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:

          @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:

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:

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:

yarn test

Expected: all existing tests and mine-records.test.ts pass.

  • Step 2: Run the MiniAPP typecheck and H5 build
yarn build:h5

Expected: vue-tsc --noEmit and the H5 build complete successfully.

  • Step 3: Run the WeChat Mini Program build
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:

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
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"