feat(fe): 记录页 24 种 4 组,弹层加通用简易表单
## 通用简易表单
洗护/清洁那 15 种只需要「什么时候 + 备注 + 可选照片」。弹层现在是 28 个
wx:elif 手写分支(402 行),各写一个会推到 700 行,而且每加一种类型都要发版。
加一个 wx:elif="{{isSimple}}",isSimple 由 JS 按后端返回的 form 字段算,
和上面那 9 种永不重叠。插在 photo 之后、提醒中心之前,没动任何现有分支的顺序。
备注给了逐类型的具体占位提示(「用了什么沐浴露、有没有吹干」),
比「请输入备注」有用得多——用户看到提示才知道这栏该写什么。
## 一个不写就会静默出错的地方
dayToISO():后端用 time.Parse(time.RFC3339) 解 occurred_at,只给日期
("2026-07-30")它解不出来会**静默回退成 time.Now()**——用户选「昨天洗澡」
会存成今天,而且不报错。
时区必须带真实偏移:转成 UTC 的 Z 形式,落库再转回本地时会把边界日期挪一天
(首页任务重复生成那个 bug 就是这么来的)。取正午同样是为了远离日界。
## 四处写死的类型列表全删
RECORD_ITEMS record.js 记录页 9 宫格
QUICK_ITEMS home.js 首页 8 宫格
TYPE_TONE record.js 时间轴色调(9 种写死,新类型会全掉到橙色一片分不清)
TASK_SHEETS 弹层 任务关联记录类型的下拉(只有 6 种,加了类型选不到)
全部改成读 utils/recordTypes.js 这份共用缓存。缓存 load() 只真正请求一次,
并发调用共用同一个 promise;isSimple/label/groups 是同步的,弹层打开时
不该再等一次网络。
首页 8 宫格改成取后端前 8 个(按分组顺序再按 sort),后台调 sort 就能换首屏
露出哪几个;全部 24 种在记录页。
分组色系(daily橙/health绿/care紫/clean蓝)留在前端,后端只存 group key——
改配色不用动数据。
## 联调(预生产库实跑)
15 种新类型 全部落库,且选「昨天」没被存成今天(15/15)
原有 9 种 逐一回归,写库 emoji 原样;weight 回写宠物档案 → 4.5kg ✓
时间轴 24 条混排,色调分布 6/7/5/6,没有一条查不到类型
体重趋势 仍只有 1 个点(没被 24 条记录污染)
周报/首页汇总 正常
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ const store = require('../../utils/store.js');
|
||||
const api = require('../../utils/api.js');
|
||||
const { toastErr } = require('../../utils/ui.js');
|
||||
const { syncTabBar } = require('../../utils/tabbar.js');
|
||||
const recordTypes = require('../../utils/recordTypes.js');
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : '' + n;
|
||||
@@ -18,32 +19,22 @@ function fmtTime(iso) {
|
||||
}
|
||||
// 记录类型 → 配色。和首页快速记录入口用同一套分组:
|
||||
// 体重/疫苗=橙、便便/饮食=绿、异常/用药=蓝、消费/照片=紫
|
||||
const TYPE_TONE = {
|
||||
weight: 'tone-1', vaccine: 'tone-1', deworm: 'tone-1',
|
||||
poop: 'tone-2', food: 'tone-2',
|
||||
symptom: 'tone-3', medicine: 'tone-3',
|
||||
cost: 'tone-4', photo: 'tone-4',
|
||||
};
|
||||
// 时间轴每条记录的色调跟着它所属的分组走,和「记一笔」的宫格是同一套色。
|
||||
// 原来这里是 9 种写死的映射,新类型会全掉到 tone-1,一片橙色分不出类别
|
||||
function toneOf(type) {
|
||||
const t = recordTypes.get(type);
|
||||
return (t && t.tone) || 'tone-1';
|
||||
}
|
||||
|
||||
// 记一笔的九个入口
|
||||
const RECORD_ITEMS = [
|
||||
{ type: 'weight', icon: 'weight', label: '体重', tone: 'tone-1' },
|
||||
{ type: 'food', icon: 'food', label: '饮食', tone: 'tone-2' },
|
||||
{ type: 'poop', icon: 'poop', label: '便便', tone: 'tone-2' },
|
||||
{ type: 'symptom', icon: 'symptom', label: '异常', tone: 'tone-3' },
|
||||
{ type: 'medicine', icon: 'medicine', label: '用药', tone: 'tone-3' },
|
||||
{ type: 'cost', icon: 'cost', label: '消费', tone: 'tone-4' },
|
||||
{ type: 'vaccine', icon: 'vaccine', label: '疫苗', tone: 'tone-1' },
|
||||
{ type: 'deworm', icon: 'deworm', label: '驱虫', tone: 'tone-1' },
|
||||
{ type: 'photo', icon: 'photo', label: '照片', tone: 'tone-4' },
|
||||
];
|
||||
// 「记一笔」的入口不再写死在这里:类型和分组由后端 record_types 表提供
|
||||
// (后台可配),前端只负责渲染。utils/recordTypes.js 是两处共用的缓存。
|
||||
|
||||
function mapRecord(r) {
|
||||
return {
|
||||
id: r.id,
|
||||
// 图标按 type 取(9 值枚举,可靠);老数据 icon 字段里的 emoji 只作兜底
|
||||
type: r.type || r.icon || '',
|
||||
tone: TYPE_TONE[r.type] || 'tone-1',
|
||||
tone: toneOf(r.type),
|
||||
title: r.title,
|
||||
desc: fmtTime(r.occurred_at) + (r.description ? '|' + r.description : ''),
|
||||
image: r.image_url || '',
|
||||
@@ -111,7 +102,7 @@ Page({
|
||||
recHasMore: false,
|
||||
recFilters: REC_FILTERS,
|
||||
recFilterIdx: 0,
|
||||
recordItems: RECORD_ITEMS,
|
||||
typeGroups: [],
|
||||
insights: [],
|
||||
trendSvg: '',
|
||||
trendStats: null,
|
||||
@@ -133,6 +124,7 @@ Page({
|
||||
},
|
||||
onShow() {
|
||||
syncTabBar(this);
|
||||
this.loadTypes();
|
||||
store
|
||||
.ready()
|
||||
.then(() => {
|
||||
@@ -184,6 +176,15 @@ Page({
|
||||
const it = this.data.insights[e.currentTarget.dataset.index];
|
||||
if (it && it.action) this.openSheetType(it.action);
|
||||
},
|
||||
// 类型只在缓存没热时真正请求;弹层也读这份缓存判断走哪种表单,
|
||||
// 所以这一步必须在用户能点开弹层之前完成
|
||||
loadTypes() {
|
||||
if (this.data.typeGroups.length) return;
|
||||
recordTypes
|
||||
.load()
|
||||
.then((groups) => this.setData({ typeGroups: groups }))
|
||||
.catch(() => {});
|
||||
},
|
||||
loadRecords() {
|
||||
const id = store.currentPetId();
|
||||
if (!id) return;
|
||||
|
||||
@@ -11,13 +11,20 @@
|
||||
<view class="btn btn-dark" bindtap="onAddPet"><pt-icon name="plus" size="{{30}}"></pt-icon>建一份档案</view>
|
||||
</view>
|
||||
|
||||
<!-- 记一笔:分组来自后端 record_types,后台加一种不用发版。
|
||||
每组一个色系,靠浅色圆底 + 分组配色出彩,图标仍是线性的 -->
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">记一笔</view></view>
|
||||
<view class="quick-grid cols-3">
|
||||
<view wx:for="{{recordItems}}" wx:key="type" class="quick {{item.tone}}" data-type="{{item.type}}" bindtap="openSheet">
|
||||
<pt-icon name="{{item.icon}}" size="{{44}}"></pt-icon>{{item.label}}
|
||||
<view wx:for="{{typeGroups}}" wx:key="key" class="tg {{item.tone}}">
|
||||
<view class="tg-head"><view class="tg-bar"></view>{{item.label}}<text class="tg-n">{{item.items.length}}</text></view>
|
||||
<view class="quick-grid cols-3">
|
||||
<view wx:for="{{item.items}}" wx:for-item="t" wx:key="code" class="quick {{item.tone}}"
|
||||
data-type="{{t.code}}" bindtap="openSheet">
|
||||
<pt-icon name="{{t.icon}}" size="{{44}}" fallback="note"></pt-icon>{{t.label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{!typeGroups.length}}" class="empty">还没有配置可记事项</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{insights.length}}" class="card">
|
||||
|
||||
@@ -57,3 +57,19 @@
|
||||
.ins-title{flex:1;font-size:var(--fs-md);font-weight:var(--fw-b);line-height:1.4}
|
||||
.ins-detail{color:var(--text-2);font-size:var(--fs-sm);line-height:1.6;margin-top:var(--sp-2)}
|
||||
.ins-ev{color:var(--muted);font-size:var(--fs-cap);margin-top:var(--sp-2)}
|
||||
|
||||
/* ===== 记一笔的分组 =====
|
||||
四组各一个色系,色值全走 token 的 soft/ink 二件套,没有新色。
|
||||
.quick 的配色由 .tone-N 决定(app.wxss 里已有),这里只管组标题和留白 */
|
||||
.tg{margin-bottom:var(--sp-4)}
|
||||
.tg:last-of-type{margin-bottom:0}
|
||||
.tg-head{
|
||||
display:flex;align-items:center;gap:var(--sp-2);
|
||||
font-size:var(--fs-md);font-weight:var(--fw-b);margin-bottom:var(--sp-3);
|
||||
}
|
||||
.tg-bar{width:6rpx;height:26rpx;border-radius:3rpx;background:currentColor}
|
||||
.tg-n{margin-left:auto;font-size:var(--fs-cap);font-weight:var(--fw);color:var(--muted2)}
|
||||
.tg.tone-1 .tg-head{color:var(--primary-ink)}
|
||||
.tg.tone-2 .tg-head{color:var(--green-ink)}
|
||||
.tg.tone-3 .tg-head{color:var(--blue-ink)}
|
||||
.tg.tone-4 .tg-head{color:var(--purple-ink)}
|
||||
|
||||
Reference in New Issue
Block a user