Files
WonderQ-Project/docs/superpowers/plans/2026-08-27-leads-detail-dialog-plan.md
T

9.8 KiB

线索详情弹窗视觉优化 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: 将线索详情弹窗调整为 A 方案「信息分组卡片」,提升字段层级、长文本可读性和窄屏适配,同时保持现有业务行为不变。

Architecture: 继续使用 LeadsPage.vue 的现有 selectedLead 数据和状态更新逻辑,只为详情模板增加基础信息容器、字段项和区块标题语义。所有视觉变化集中在 styles.css,不增加组件、接口或数据层。

Tech Stack: Vue 3、TypeScript、Element Plus、Vitest、Vite、CSS Grid/Flexbox。


Task 1: Add a regression test for the A-layout structure

Files:

  • Modify: WonderQ-Admin-UI-Vue/src/pages/LeadsPage.test.ts

  • Step 1: Add assertions for the required semantic hooks before changing production code

Append this test to the existing describe block:

  it("keeps the grouped lead detail layout hooks", async () => {
    const source = await readFile(fileURLToPath(new URL("./LeadsPage.vue", import.meta.url)), "utf8");

    expect(source).toContain('class="lead-detail-card"');
    expect(source).toContain('class="lead-detail-item"');
    expect(source).toContain('class="lead-detail-section-title"');
    expect(source).toContain('class="lead-detail-section lead-detail-note"');
    expect(source.match(/class="lead-detail-item"/g)).toHaveLength(6);
    expect(source).toContain("v-permission=\"'admin:leads:update'\"");
  });
  • Step 2: Run the focused test and confirm it fails for the missing hooks

Run from WonderQ-Admin-UI-Vue:

yarn vitest run src/pages/LeadsPage.test.ts

Expected: the existing initial-loading test passes and the new grouped-layout test fails because the current template does not contain the new wrapper/item/title classes.

Task 2: Implement the grouped detail markup and styles

Files:

  • Modify: WonderQ-Admin-UI-Vue/src/pages/LeadsPage.vue:118-123

  • Modify: WonderQ-Admin-UI-Vue/src/styles.css:64-72,193-195

  • Step 1: Wrap the detail fields without changing their data expressions

Replace the current detail content after <template v-if="selectedLead"> with this structure:

        <div class="lead-detail-toolbar">
          <el-tag :type="statusTagType(selectedLead.status)" effect="plain">{{ leadStatusLabel(selectedLead.status) }}</el-tag>
          <el-select v-permission="'admin:leads:update'" :model-value="selectedLead.status" :loading="updatingId === selectedLead.id" size="small" @update:model-value="changeStatus(selectedLead, $event)">
            <el-option v-for="option in leadStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
          </el-select>
        </div>
        <div class="lead-detail-card">
          <dl class="lead-detail-grid">
            <div class="lead-detail-item"><dt>联系人</dt><dd>{{ selectedLead.contactName || "未填写" }}</dd></div>
            <div class="lead-detail-item"><dt>联系方式</dt><dd>{{ selectedLead.phone || "未填写" }}</dd></div>
            <div class="lead-detail-item"><dt>需求类型</dt><dd>{{ selectedLead.leadType === "vehicle" ? "用车需求" : "一般需求" }}</dd></div>
            <div class="lead-detail-item"><dt>提交时间</dt><dd>{{ formatDate(selectedLead.createdAt) }}</dd></div>
            <div class="lead-detail-item"><dt>目的地</dt><dd>{{ selectedLead.destination || "未填写" }}</dd></div>
            <div class="lead-detail-item"><dt>出行日期</dt><dd>{{ formatDate(selectedLead.vehicleDemand?.travelDate || selectedLead.travelDate) }}</dd></div>
          </dl>
        </div>
        <section v-if="selectedLead.vehicleDemand" class="lead-detail-section lead-detail-trip">
          <div class="lead-detail-section-title"><strong>用车行程</strong></div>
          <p>{{ selectedLead.vehicleDemand.pickupLocation }} → {{ selectedLead.vehicleDemand.dropoffLocation }}</p>
          <p>{{ selectedLead.vehicleDemand.peopleCount }} 人 · 行李 {{ selectedLead.vehicleDemand.luggageCount ?? "未填写" }} 件 · {{ selectedLead.vehicleDemand.vehicleOptionTitle || "未指定车型" }}</p>
        </section>
        <section class="lead-detail-section lead-detail-note">
          <div class="lead-detail-section-title"><strong>备注</strong></div>
          <p>{{ selectedLead.vehicleDemand?.specialRequirements || selectedLead.note || "暂无备注" }}</p>
        </section>
  • Step 2: Replace the detail-specific CSS with the two-column card treatment

Replace the rules for .lead-detail-toolbar through .lead-detail-section p with:

.lead-detail-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px; background: #f8fafc; }
.lead-detail-toolbar .el-tag { flex: 0 0 auto; }
.lead-detail-toolbar .el-select { min-width: 0; flex: 1 1 auto; max-width: 310px; }
.lead-summary { display: grid; gap: 4px; min-width: 0; }
.lead-summary span { overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.lead-detail-card { overflow: hidden; margin-top: 0; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); }
.lead-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin: 0; background: var(--line); }
.lead-detail-item { min-width: 0; padding: 15px 16px; background: var(--surface); }
.lead-detail-grid dt { color: var(--muted); font-size: 12px; line-height: 1.4; }
.lead-detail-grid dd { min-width: 0; margin: 5px 0 0; overflow-wrap: anywhere; color: var(--ink); font-weight: 650; line-height: 1.45; }
.lead-detail-section { margin-top: 16px; padding: 14px 16px; border: 1px solid transparent; border-radius: 9px; background: #f8fafc; }
.lead-detail-section-title { display: flex; align-items: center; gap: 8px; color: #475569; font-size: 13px; line-height: 1.4; }
.lead-detail-section-title::before { width: 3px; height: 14px; border-radius: 3px; background: var(--blue); content: ""; }
.lead-detail-section p { margin: 7px 0 0; overflow-wrap: anywhere; color: #475569; line-height: 1.6; }
.lead-detail-note { border-color: #e4eaf2; background: #f5f8fc; }
  • Step 3: Keep responsive rules aligned with the new grid

Change the existing lead-detail media rules to:

@media (max-width: 980px) { .media-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px) { .concierge-row { align-items: flex-start; flex-direction: column; } .concierge-actions { justify-content: flex-start; } .lead-filters { grid-template-columns: 1fr 1fr; } .lead-filters .el-input { grid-column: 1 / -1; } .operations-grid { grid-template-columns: 1fr; } .media-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .site-config-intro { align-items: flex-start; } .site-config-intro .el-button, .page-intro-actions { width: 100%; } .page-intro-actions { justify-content: stretch; } .page-intro-actions .el-button { flex: 1; } .editor-options { grid-template-columns: 1fr; gap: 0; } .site-config-panel .panel-heading { align-items: flex-start; flex-direction: column; } .site-config-panel .panel-heading .el-button { width: 100%; } .concierge-detail-row { grid-template-columns: 1fr; } .lead-filters { grid-template-columns: 1fr; } .lead-filters .el-input { grid-column: auto; } .lead-detail-grid { grid-template-columns: 1fr; } .lead-detail-toolbar { align-items: stretch; flex-direction: column; } .lead-detail-toolbar .el-select { width: 100%; max-width: none; } .media-toolbar { align-items: stretch; flex-direction: column; width: 100%; } .media-toolbar .el-input { width: 100%; } .media-grid { grid-template-columns: 1fr; } }
  • Step 4: Run the focused test and confirm it passes

Run:

yarn vitest run src/pages/LeadsPage.test.ts

Expected: both tests pass with no new warnings.

Task 3: Run project verification and inspect the rendered dialog

Files:

  • Verify: WonderQ-Admin-UI-Vue/src/pages/LeadsPage.vue

  • Verify: WonderQ-Admin-UI-Vue/src/styles.css

  • Verify: WonderQ-Admin-UI-Vue/src/pages/LeadsPage.test.ts

  • Step 1: Run the complete frontend test suite

Run from WonderQ-Admin-UI-Vue:

yarn test

Expected: Vitest exits with code 0.

  • Step 2: Run the production build

Run:

yarn build

Expected: Vite completes successfully and produces dist/ without TypeScript or template errors.

  • Step 3: Check the final diff and repository status

Run:

git diff --check
git diff -- WonderQ-Admin-UI-Vue/src/pages/LeadsPage.vue WonderQ-Admin-UI-Vue/src/styles.css WonderQ-Admin-UI-Vue/src/pages/LeadsPage.test.ts
git status --short

Expected: only the planned frontend files are modified, with no whitespace errors, secrets, build artifacts, or environment-file changes.

  • Step 4: Manually inspect the dialog in the local browser

Open http://localhost:5602, go to 需求线索, open a detail dialog, and verify:

  1. Status tag and status select remain usable and permission-controlled.
  2. Six fields appear in a bordered two-column card.
  3. A long contact value wraps inside its card without horizontal overflow.
  4. Vehicle demand shows its own highlighted section, while ordinary leads show only the note section.
  5. At a narrow viewport the six fields become one column and the status controls stack vertically.
  • Step 5: Commit the implementation as one atomic change

Run:

git add -- WonderQ-Admin-UI-Vue/src/pages/LeadsPage.vue WonderQ-Admin-UI-Vue/src/styles.css WonderQ-Admin-UI-Vue/src/pages/LeadsPage.test.ts
git commit -m "fix: improve lead detail dialog hierarchy"