topfans/docs/superpowers/plans/2026-06-08-income-curve-default-tooltip.md
2026-06-09 00:37:42 +08:00

347 lines
12 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.

# IncomeCurve.vue Default Tooltip 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:** Make `IncomeCurve.vue` (七日收益曲线) show its tooltip on the last data point by default after first render, while preserving the existing tap-to-switch behavior.
**Architecture:** Listen to `qiun-data-charts`'s `@complete` event, read the last data point's actual screen coordinates from `opts.chartData.calPoints[0][last]`, construct a fake touch event, and invoke the chart component's private `_showTooltip(e)` to render the default tooltip. Guard against re-firing and missing data with a `lastShownLen` cursor.
**Tech Stack:** Vue 3 Composition API, uni-app (vite), `qiun-data-charts` 1.5.x (u-charts under the hood)
**Spec:** `docs/superpowers/specs/2026-06-08-income-curve-default-tooltip-design.md`
---
## File Structure
**Modified (1 file):**
- `frontend/pages/dashboard/components/IncomeCurve.vue` — single component; add `chartRef`, `onChartComplete` handler, `lastShownLen` cursor; remove dead code (`currentIndex`, its `watch`, `onChartTap`, `@tap` binding)
**Created (0 files):**
No new files. No new dependencies. No new test files (project has no frontend test framework — uni-app + HBuilderX/WeChat DevTools manual verification, consistent with the rest of the codebase).
---
## Task 1: Add `chartRef` and `onChartComplete` handler (minimal, no behavior change yet)
**Files:**
- Modify: `frontend/pages/dashboard/components/IncomeCurve.vue:1-78`
**Why first:** Establishes the new state without changing visible behavior. Lets us verify the ref wiring works before adding the actual `_showTooltip` call.
- [ ] **Step 1: Add `chartRef` and `lastShownLen` to the script block**
Open `frontend/pages/dashboard/components/IncomeCurve.vue` and replace the existing import line (line 43):
```js
import { computed, ref, watch } from "vue";
```
with:
```js
import { computed, nextTick, ref, watch } from "vue";
```
Then immediately after `defineEmits(["retry"]);` (line 52), insert two new refs:
```js
// [新增] 拿到 qiun-data-charts 实例,用于首次渲染后触发默认 tooltip
const chartRef = ref(null);
// [新增] 上次已展示 tooltip 的数据点数量,避免重复触发
const lastShownLen = ref(0);
```
- [ ] **Step 2: Bind the ref on the chart component**
In the template `<qiun-data-charts ...>` opening tag (line 23), add `ref="chartRef"` as the first attribute:
```vue
<qiun-data-charts
ref="chartRef"
type="area"
:opts="chartOpts"
:chartData="chartData"
:ontouch="true"
:onmovetip="true"
:in-scroll-view="true"
:tooltipShow="true"
:canvas2d="false"
canvasId="incomeCurveCanvas"
:canvasHeight="240"
ontap
@tap="onChartTap"
/>
```
- [ ] **Step 3: Add a placeholder `onChartComplete` handler (no behavior yet)**
In the script block, after the two new refs added in Step 1, insert this empty placeholder:
```js
// [新增] 图表首次渲染完成回调占位Task 2 填充真实逻辑)
const onChartComplete = (_e) => {
// 故意留空 — Task 2 接入 fake event 触发
};
```
Also bind it on the template — change line 35 from `@tap="onChartTap"` to:
```vue
ontap
@tap="onChartTap"
@complete="onChartComplete"
/>
```
- [ ] **Step 4: Verify the chart still renders normally**
Run H5 preview (HBuilderX → 运行 → 运行到浏览器 → Chrome) **or** the dev server:
```bash
cd frontend && npm run dev:h5
```
Expected: dashboard page loads; chart renders the 7-day curve; no console errors. Touching a point still updates tooltip as before. The placeholder handler is a no-op, so visible behavior is unchanged.
- [ ] **Step 5: Commit**
```bash
git add frontend/pages/dashboard/components/IncomeCurve.vue
git commit -m "feat(income-curve): wire chartRef + onChartComplete placeholder"
```
---
## Task 2: Implement the default tooltip trigger
**Files:**
- Modify: `frontend/pages/dashboard/components/IncomeCurve.vue` (replace the placeholder from Task 1)
- [ ] **Step 1: Replace the placeholder `onChartComplete` with the real implementation**
Find the placeholder block added in Task 1 Step 3:
```js
// [新增] 图表首次渲染完成回调占位Task 2 填充真实逻辑)
const onChartComplete = (_e) => {
// 故意留空 — Task 2 接入 fake event 触发
};
```
Replace it with:
```js
// [新增] 图表渲染完成回调:
// 从 e.opts.chartData.calPoints[0] 拿最后一点的真实屏幕坐标,
// 构造 fake event 喂给 _showTooltip让 u-charts 在最后一点渲染 tooltip
// ⚠️ _showTooltip 是 qiun-data-charts 私有方法(下划线开头)
// 风险与回退见 spec: "风险与回退" 章节
const onChartComplete = async (e) => {
const len = props.points.length;
if (!len || !chartRef.value) return;
// 数据点数量未变化则不重复触发(防 complete 事件多次触发)
if (lastShownLen.value === len) return;
await nextTick();
const series = e?.opts?.chartData?.calPoints?.[0];
if (!series || !series[len - 1]) return;
const { x, y } = series[len - 1];
const fakeE = { changedTouches: [{ x, y }] };
try {
chartRef.value._showTooltip(fakeE);
} catch (err) {
console.warn("[IncomeCurve] show default tooltip failed:", err);
}
lastShownLen.value = len;
};
```
- [ ] **Step 2: Verify default tooltip shows on last point**
Reload the dashboard page in H5 preview (HBuilderX). Watch the income-curve chart.
Expected: Within ~1 frame after the chart's curve animates in, a tooltip appears above the **rightmost** data point showing `+<income>` (e.g. `+1234`) and `MM-DD` (e.g. `06-08`). No hollow dot should appear (u-charts draws the active point only after a real touch).
- [ ] **Step 3: Verify tap-to-switch still works**
On the same chart, tap a point in the middle (e.g. day 4 from the left).
Expected: Tooltip moves to that touched point. Tap the rightmost point → tooltip returns to the rightmost. Tap empty space below the curve → tooltip should disappear (u-charts default behavior). All these should work as before, because the chart's internal `_tap` handler is untouched.
- [ ] **Step 4: Verify guard against repeated complete events**
Without leaving the page, trigger a re-render (e.g. dev tools → toggle a `points` prop in Vue devtools, or just resize the window — u-charts emits `complete` on resize).
Expected: Tooltip stays on the rightmost point; no flicker / no duplicate tooltip drawn. Open devtools console; no errors.
- [ ] **Step 5: Verify empty-data fallback**
Temporarily change the parent (or use dev tools) to pass `points: []`.
Expected: Component enters skeleton state (the `v-else-if="loading || !points || points.length === 0"` branch); the `qiun-data-charts` is not mounted, so `onChartComplete` is never called; no console errors.
Restore the data after verification.
- [ ] **Step 6: Commit**
```bash
git add frontend/pages/dashboard/components/IncomeCurve.vue
git commit -m "feat(income-curve): show default tooltip on last data point"
```
---
## Task 3: Remove dead code (`currentIndex`, `watch`, `onChartTap`, `@tap` binding)
**Files:**
- Modify: `frontend/pages/dashboard/components/IncomeCurve.vue` (script + template)
**Why:** `currentIndex` ref and its watcher are unused (u-charts feeds the actual touch index to `tooltipCustom` directly, not from the parent). `onChartTap` is a dead handler. Cleaning these up keeps the component lean and prevents future readers from being misled.
- [ ] **Step 1: Remove the `currentIndex` ref + watcher + `onChartTap` function**
In the script block, find and delete these lines (originally around line 54-69 of the unmodified file):
```js
// 当前选中的数据点索引默认指向最后一条tap 时更新
const currentIndex = ref(0);
watch(
() => props.points.length,
(len) => {
currentIndex.value = Math.max(0, len - 1);
},
{ immediate: true },
);
const onChartTap = (e) => {
const idx = e?.index;
if (typeof idx === "number" && idx >= 0 && idx < props.points.length) {
currentIndex.value = idx;
}
};
```
- [ ] **Step 2: Remove `watch` from the import line (still need `nextTick` + `ref`)**
Change:
```js
import { computed, nextTick, ref, watch } from "vue";
```
to:
```js
import { computed, nextTick, ref } from "vue";
```
(`watch` is no longer used after Step 1.)
- [ ] **Step 3: Remove `@tap="onChartTap"` from the template**
In the `<qiun-data-charts>` tag, find:
```vue
ontap
@tap="onChartTap"
@complete="onChartComplete"
/>
```
Change to:
```vue
ontap
@complete="onChartComplete"
/>
```
(`ontap` (no value) is the native event binding that lets u-charts' internal `_tap` still handle real touches; we just drop the parent-side listener.)
- [ ] **Step 4: Verify nothing regressed**
Reload dashboard in H5 preview.
Expected:
- Tooltip still shows on the rightmost point by default
- Tapping any other point still moves the tooltip to that point
- No console errors
- No unused-variable warnings (if eslint is configured — check with `cd frontend && npx eslint pages/dashboard/components/IncomeCurve.vue 2>/dev/null || echo "no eslint config"`; project has no eslint, so this is just a no-op check)
- [ ] **Step 5: Commit**
```bash
git add frontend/pages/dashboard/components/IncomeCurve.vue
git commit -m "refactor(income-curve): remove dead currentIndex/onChartTap"
```
---
## Task 4: Cross-platform smoke test (WeChat mini-program / H5 / App)
**Files:** none
**Why:** The fake event and `_showTooltip` rely on canvas event behavior that can differ across uni-app's compile targets. Verify before declaring done.
- [ ] **Step 1: Build for WeChat mini-program and verify**
In HBuilderX: 发行 → 微信小程序(仅编译,不上传).
Open the built `unpackage/dist/dev/mp-weixin/` in WeChat DevTools. Navigate to the dashboard page.
Expected: Same as H5 — tooltip on rightmost by default, tap-to-switch works, no errors in WeChat console.
- [ ] **Step 2: If an App build is in use, verify there too**
In HBuilderX: 运行 → 运行到 App 基座.
Expected: Same behavior. (Skip this step if App build is not part of the dev workflow for this project.)
- [ ] **Step 3: Document any platform-specific quirk**
If you observe different behavior on any platform, add a one-line note in the spec's "跨端验证" section and a defensive comment in `IncomeCurve.vue` near the `_showTooltip` call. If all platforms pass, no action needed.
- [ ] **Step 4: Final commit (if any notes were added)**
```bash
git add frontend/pages/dashboard/components/IncomeCurve.vue docs/superpowers/specs/2026-06-08-income-curve-default-tooltip-design.md
git commit -m "docs(income-curve): note cross-platform verification results"
```
---
## Self-Review
**1. Spec coverage:**
| Spec section | Task |
|--------------|------|
| 核心机制 (calPoints + fake event) | Task 1 + Task 2 |
| 模板改动 (ref + @complete) | Task 1 Steps 2-3 |
| 脚本改动 (onChartComplete + lastShownLen) | Task 1 Step 1, Task 2 Step 1 |
| 关键边界条件 (空数据 / calPoints 未填充 / 多次 complete / 私有方法不可用) | Task 2 Step 5 (empty) + Task 2 Step 4 (re-entry) + Task 2 Step 1 (try/catch + 守卫) |
| 模板清理 (@tap="onChartTap" 移除) | Task 3 Step 3 |
| 死代码清理 (currentIndex, watch, onChartTap) | Task 3 Steps 1-2 |
| 手动验证 (默认显示 / 触摸切换 / 数据切换) | Task 2 Steps 2-3 |
| 空态/错误态 | Task 2 Step 5 |
| 跨端验证 | Task 4 |
| 风险与回退 | Implicit in code (try/catch + 守卫); not a separate task |
**2. Placeholder scan:**
- "verify" steps: each has explicit Expected output ✅
- No "TBD" / "TODO" / "implement later" ✅
- All code blocks contain complete code (no "// similar to..." shortcuts) ✅
**3. Type / naming consistency:**
- `chartRef` — declared Task 1 Step 1, used Task 1 Step 2 (template) + Task 2 Step 1 (script). ✅
- `lastShownLen` — declared Task 1 Step 1, used Task 2 Step 1. ✅
- `onChartComplete` — declared Task 1 Step 3 (placeholder), updated Task 2 Step 1, bound in template Task 1 Step 3. ✅
- `e.opts.chartData.calPoints[0]` — referenced in Task 2 Step 1, matches the spec's data flow diagram and u-charts source (line 572 of `u-charts.js`: `var calPoints = opts.chartData.calPoints?opts.chartData.calPoints:[]`). ✅
- `_showTooltip` — called Task 2 Step 1, defined in `qiun-data-charts.vue:1080`. ✅
**Gaps:** None. All spec requirements are covered by tasks above.