Files
sundynix-pets/pets-fe/pages/expense/expense.wxml
T
Blizzard 15b52eebd0 feat: 首页改成记录时间轴,计划可自己管,花销独立成页
## 首页
原来是任务卡 + 日期带/日历 + 接下来 + AI建议 + 快速记录 + 新手内容的堆叠,
六张聚合卡,唯独看不到「我记了什么」。改成:

  宠物名片(品种·年龄·星座 + 性别徽章叠头像 + 一起生活的第 N 天)
  两个入口(管理计划 / 记花销)
  今日任务(有才显示)
  记录时间轴 —— 竖线 + 圆点 + 卡片,往下翻,长按删

日历挪回计划页了:它本来就属于那儿。Phase B 当时把它并进首页是因为计划还是
个 tab;现在计划是二级页,日历跟着回去更合理。

情绪点那几样字段后端早就返回了,只是没人用。抽了 utils/petmeta.js:
星座七个边界(含 12/22 跨年那个)+ 天数(到家日优先于建档日,
「一起生活」说的是它到你家之后的日子)+ 性别徽章,都在 node 里跑过。

顺带 stat-ring 组件和 homeSummary 接口现在没人用了,先留着没删。

## 两个入口取代竞品那一排
竞品是买商品/上豪车/记花销/领猫砂盆——除了记花销全是电商和广告位。
副标题给了真信息:「还有 5 件没做」「本月 ¥128」,不然就是两个没信息量的图标。

## 计划页:用户自己能管了
后端原来只有 toggle,节点全靠内置模板生成,用户加不了自己的事。补了

  POST   /api/pets/:id/plan-tasks
  PUT    /api/plan-tasks/:id
  DELETE /api/plan-tasks/:id

三件事值得说:

**30 天边界硬拦在服务端**(dayOffset)。只在前端拦的话,改个请求就能塞一条
第 200 天的节点,日历上没有那格、它就永远不显示也删不掉。

**归属校验单独抽了 ownedPlanTask**。PUT/DELETE /plan-tasks/:id 这种按资源 id
的路由最容易漏——不校验的话拿到别人的 task id 就能改删别人的计划。验过:
别人的 token 改和删都返回「无权操作」。

**边界差一天,是验证时撞出来的**:内置模板生成到 day=30(第 31 天),
我一开始按 0-29 卡,结果模板自己的最后一个节点(生成月度报告)落在窗口外——
日历上没有那一格,用户看得到却改不动也删不掉。前后端都改成 [0,30],
两边各留了一句注释指向对方,别再单方面改。

日历只画计划覆盖的 31 天,不画整月:整月会多出一堆空白格子、跨月还要翻页。

## 花销独立成页
写的是同一张表(type='cost',金额进 num_value、类别进 category),
报告页的账单聚合照样认——独立页的价值在专门的录入体验和当页就有月度汇总,
不是另存一份数据。

类别预置读 record_types 里 cost 的字段配置,后台改一处这页和 addrecord
同时生效,不会出现两套类别把账单切开。另外允许自定义类别,验过「寄养」
(不在预置里)能落库、账单也认。

## 检查器又抓到一次跨页借用
我在计划页复用了花销页的 ex-ph(placeholder 类),检查器直接报了位置。
顺手把三个页面的私有 placeholder 类统一到 app.wxss 已有的 .placeholder。
cal-* 现在首页没有了、计划页在用,也提到了 app.wxss。

## 验证(预生产库实跑)
  计划 CRUD    加/改/删 全过;空标题拒;越权改删拒
  30 天边界     day=30 通过、day=31 拒、day=-1 拒
  模板 day=30   现在能原地改了(修边界前会被自己的规则拒掉)
  首页时间轴    8 条记录按 occurred_at 倒序,type/tone/图标都取到
  两个入口副标题 「还有 5 件没做」「本月 ¥128」
  花销自定义类别 「寄养」落库,账单分类变成 [(医疗,128),(寄养,60)]
  petmeta      星座 7 个边界 + 天数 3 种情况,node 单测全过

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:43:08 +08:00

72 lines
3.2 KiB
Plaintext
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.
<nav-bar title="养宠花销" show-back="{{true}}"></nav-bar>
<scroll-view class="page-scroll" scroll-y="{{true}}" enhanced="{{true}}" show-scrollbar="{{false}}">
<view class="page-body ex-body">
<!-- 本月合计 + 分类占比。走 /bill 接口,和报告页的账单是同一份数据 -->
<view class="card ex-sum">
<view class="ex-sum-k">{{bill.period || '本月'}}花销</view>
<view class="ex-sum-n">¥{{bill.total || 0}}</view>
<view wx:if="{{bill.max_single}}" class="ex-sum-s">单笔最高 ¥{{bill.max_single}}</view>
<view wx:if="{{bill.categories.length}}" class="ex-cats">
<view wx:for="{{bill.categories}}" wx:key="category" class="ex-cat">
<view class="ex-cat-top"><text>{{item.category}}</text><text>¥{{item.amount}}</text></view>
<view class="bar"><view class="bar-fill" style="width:{{item.percent}}%"></view></view>
</view>
</view>
<view wx:else class="empty">这个月还没记过花销</view>
</view>
<!-- 记一笔 -->
<view class="card">
<view class="section-head"><view class="sh-title">记一笔</view></view>
<view class="ex-amt">
<text class="ex-cur">¥</text>
<input class="ex-in" type="digit" placeholder="0.00" placeholder-class="placeholder"
value="{{amount}}" bindinput="onAmount"/>
</view>
<view class="ex-label">类别</view>
<view class="ex-chips">
<view wx:for="{{cats}}" wx:key="*this"
class="ex-chip {{cat === item ? 'on' : ''}}" data-val="{{item}}" bindtap="onPickCat">{{item}}</view>
<!-- 自定义类别:预置那几个盖不住所有花法(寄养、驱虫药、玩具…) -->
<view class="ex-chip ex-chip-add {{customOn ? 'on' : ''}}" bindtap="toggleCustom">
<pt-icon name="plus" size="{{22}}"></pt-icon>自定义
</view>
</view>
<input wx:if="{{customOn}}" class="input ex-custom" placeholder="填一个类别名,比如 寄养"
placeholder-class="placeholder" value="{{customCat}}" bindinput="onCustomCat"/>
<view class="ex-label">日期</view>
<picker mode="date" value="{{date}}" bindchange="onDate">
<view class="picker-box">{{date}}</view>
</picker>
<view class="ex-label">备注</view>
<input class="input" placeholder="买了什么、在哪买的" placeholder-class="placeholder"
value="{{note}}" bindinput="onNote"/>
<view class="btn btn-primary btn-block ex-save {{saving ? 'ex-busy' : ''}}" bindtap="onSave">
{{saving ? '保存中…' : '记下这笔'}}
</view>
</view>
<!-- 本月流水 -->
<view class="card">
<view class="section-head">
<view class="sh-title">本月流水</view>
<view wx:if="{{list.length}}" class="tiny">{{list.length}} 笔</view>
</view>
<view wx:for="{{list}}" wx:key="id" class="ex-row" data-id="{{item.id}}" bindlongpress="onDelete">
<view class="ex-row-main">
<view class="tt-b">{{item.category || '未分类'}}</view>
<view class="tt-p">{{item.timeText}}{{item.description ? '' + item.description : ''}}</view>
</view>
<view class="ex-row-amt">¥{{item.num_value}}</view>
</view>
<view wx:if="{{!list.length}}" class="empty">还没有流水。长按某笔可以删除。</view>
</view>
</view>
</scroll-view>