init: 毛孩子计划 小程序 + Go 后端 + 内嵌后台
- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导), 服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar - pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页, 微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台 - 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
const store = require('../../utils/store.js');
|
||||
const api = require('../../utils/api.js');
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : '' + n;
|
||||
}
|
||||
function fmtTime(iso) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
const now = new Date();
|
||||
const hm = pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
if (d.toDateString() === now.toDateString()) return '今天 ' + hm;
|
||||
const y = new Date(now.getTime() - 86400000);
|
||||
if (d.toDateString() === y.toDateString()) return '昨天 ' + hm;
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日';
|
||||
}
|
||||
function mapRecord(r) {
|
||||
return {
|
||||
icon: r.icon || '✍️',
|
||||
title: r.title,
|
||||
desc: fmtTime(r.occurred_at) + (r.description ? '|' + r.description : ''),
|
||||
};
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
pet: {},
|
||||
timeline: [],
|
||||
sheetShow: false,
|
||||
sheetType: '',
|
||||
},
|
||||
onLoad() {
|
||||
this._unsub = store.subscribe((pet) => {
|
||||
this.setData({ pet });
|
||||
this.loadRecords();
|
||||
});
|
||||
},
|
||||
onUnload() {
|
||||
if (this._unsub) this._unsub();
|
||||
},
|
||||
onShow() {
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
this.getTabBar().setData({ selected: 2 });
|
||||
}
|
||||
store
|
||||
.ready()
|
||||
.then(() => {
|
||||
this.setData({ pet: store.getPet() });
|
||||
this.loadRecords();
|
||||
})
|
||||
.catch((e) => wx.showToast({ title: e.message || '加载失败', icon: 'none' }));
|
||||
},
|
||||
loadRecords() {
|
||||
const id = store.currentPetId();
|
||||
if (!id) return;
|
||||
api
|
||||
.getRecords(id)
|
||||
.then((records) => this.setData({ timeline: (records || []).map(mapRecord) }))
|
||||
.catch(() => {});
|
||||
},
|
||||
openSheet(e) {
|
||||
this.openSheetType(e.currentTarget.dataset.type);
|
||||
},
|
||||
openSheetType(type) {
|
||||
this.setData({ sheetType: type, sheetShow: true });
|
||||
},
|
||||
closeSheet() {
|
||||
this.setData({ sheetShow: false });
|
||||
},
|
||||
onSheetSaved() {
|
||||
this.loadRecords();
|
||||
},
|
||||
onAddPet() {
|
||||
this.openSheetType('addPet');
|
||||
},
|
||||
onFab() {
|
||||
this.openSheetType('ai');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/nav-bar",
|
||||
"pet-switch": "/components/pet-switch/pet-switch",
|
||||
"bottom-sheet": "/components/bottom-sheet/bottom-sheet",
|
||||
"fab": "/components/fab/fab"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<nav-bar title="快速记录"></nav-bar>
|
||||
|
||||
<scroll-view class="page-scroll" scroll-y="{{true}}" enhanced="{{true}}" show-scrollbar="{{false}}">
|
||||
<view class="page-body">
|
||||
<view class="page-title">
|
||||
<view class="pt-h2">快速记录</view>
|
||||
<view class="pt-p">先选择宠物,再记录体重、饮食、异常和消费。</view>
|
||||
</view>
|
||||
|
||||
<pet-switch bind:add="onAddPet"></pet-switch>
|
||||
|
||||
<view class="record-grid">
|
||||
<view class="record-item" data-type="weight" bindtap="openSheet"><text class="ri-ic">⚖️</text>体重</view>
|
||||
<view class="record-item" data-type="food" bindtap="openSheet"><text class="ri-ic">🍽️</text>饮食</view>
|
||||
<view class="record-item" data-type="poop" bindtap="openSheet"><text class="ri-ic">💩</text>便便</view>
|
||||
<view class="record-item" data-type="symptom" bindtap="openSheet"><text class="ri-ic">🤒</text>异常</view>
|
||||
<view class="record-item" data-type="medicine" bindtap="openSheet"><text class="ri-ic">💊</text>用药</view>
|
||||
<view class="record-item" data-type="cost" bindtap="openSheet"><text class="ri-ic">💰</text>消费</view>
|
||||
<view class="record-item" data-type="vaccine" bindtap="openSheet"><text class="ri-ic">💉</text>疫苗</view>
|
||||
<view class="record-item" data-type="deworm" bindtap="openSheet"><text class="ri-ic">🛡️</text>驱虫</view>
|
||||
<view class="record-item" data-type="photo" bindtap="openSheet"><text class="ri-ic">📷</text>照片</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">体重趋势</view><view class="link" data-type="weight" bindtap="openSheet">记录</view></view>
|
||||
<view class="mini-chart">
|
||||
<image class="chart-line" mode="scaleToFill" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48cG9seWxpbmUgcG9pbnRzPSIwLDgwIDUwLDcyIDEwMCw2NiAxNTAsNTQgMjAwLDQ1IDI1MCwzMiAzMDAsMjYiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzczQkU5RCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48ZyBmaWxsPSIjNzNCRTlEIj48Y2lyY2xlIGN4PSIwIiBjeT0iODAiIHI9IjQiLz48Y2lyY2xlIGN4PSI1MCIgY3k9IjcyIiByPSI0Ii8+PGNpcmNsZSBjeD0iMTAwIiBjeT0iNjYiIHI9IjQiLz48Y2lyY2xlIGN4PSIxNTAiIGN5PSI1NCIgcj0iNCIvPjxjaXJjbGUgY3g9IjIwMCIgY3k9IjQ1IiByPSI0Ii8+PGNpcmNsZSBjeD0iMjUwIiBjeT0iMzIiIHI9IjQiLz48Y2lyY2xlIGN4PSIzMDAiIGN5PSIyNiIgcj0iNCIvPjwvZz48L3N2Zz4="></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">健康时间轴</view><view class="link" data-type="vetSummary" bindtap="openSheet">摘要</view></view>
|
||||
<view class="health-timeline">
|
||||
<view wx:for="{{timeline}}" wx:key="index" class="health-event">
|
||||
<view class="event-dot">{{item.icon}}</view>
|
||||
<view><text class="he-b">{{item.title}}</text><view class="he-p">{{item.desc}}</view></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<fab bind:tap="onFab"></fab>
|
||||
<bottom-sheet show="{{sheetShow}}" type="{{sheetType}}" bind:close="closeSheet" bind:saved="onSheetSaved"></bottom-sheet>
|
||||
@@ -0,0 +1,32 @@
|
||||
.page-body{padding:8rpx 40rpx calc(210rpx + env(safe-area-inset-bottom))}
|
||||
.pt-h2{font-size:46rpx;letter-spacing:-.6rpx;font-weight:800}
|
||||
.pt-p{color:var(--muted);font-size:26rpx;margin-top:10rpx}
|
||||
|
||||
.record-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24rpx;margin-bottom:28rpx}
|
||||
.record-item{
|
||||
height:184rpx;background:#fff;border-radius:44rpx;box-shadow:var(--shadow);
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16rpx;
|
||||
font-weight:900;color:var(--text);font-size:28rpx;
|
||||
}
|
||||
.ri-ic{font-size:56rpx}
|
||||
|
||||
.mini-chart{
|
||||
height:256rpx;border-radius:36rpx;margin-top:24rpx;position:relative;overflow:hidden;
|
||||
background:
|
||||
linear-gradient(180deg,rgba(115,190,157,.16),rgba(115,190,157,0)),
|
||||
repeating-linear-gradient(0deg,#fff,#fff 60rpx,#EFE8DE 62rpx);
|
||||
}
|
||||
.chart-line{position:absolute;left:36rpx;right:36rpx;top:44rpx;bottom:44rpx;width:auto;height:auto}
|
||||
|
||||
.health-timeline{display:flex;flex-direction:column;gap:20rpx}
|
||||
.health-event{display:flex;gap:20rpx;background:#FBFAF8;border-radius:32rpx;padding:24rpx}
|
||||
.event-dot{
|
||||
width:68rpx;height:68rpx;border-radius:26rpx;flex:none;
|
||||
display:flex;align-items:center;justify-content:center;background:#FFF0D7;font-size:34rpx;
|
||||
}
|
||||
.he-b{font-size:28rpx;font-weight:700}
|
||||
.he-p{color:var(--muted);font-size:24rpx;line-height:1.45;margin-top:6rpx}
|
||||
|
||||
/* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/
|
||||
page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
|
||||
.page-scroll{flex:1;min-height:0}
|
||||
Reference in New Issue
Block a user