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