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:
Blizzard
2026-07-03 15:33:31 +08:00
commit 609f7d06cf
180 changed files with 15259 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
const store = require('../../utils/store.js');
const api = require('../../utils/api.js');
Page({
data: {
pet: {},
report: null,
gainText: '+0.0',
bill: null,
summary: null,
sheetShow: false,
sheetType: '',
},
onLoad() {
this._unsub = store.subscribe((pet) => {
this.setData({ pet });
this.loadData();
});
},
onUnload() {
if (this._unsub) this._unsub();
},
onShow() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 3 });
}
store
.ready()
.then(() => {
this.setData({ pet: store.getPet() });
this.loadData();
})
.catch((e) => wx.showToast({ title: e.message || '加载失败', icon: 'none' }));
},
loadData() {
const id = store.currentPetId();
if (!id) return;
Promise.all([api.weeklyReport(id), api.getBill(id, 'month'), api.healthSummary(id)])
.then(([report, bill, summary]) => {
const g = report.weight_gain || 0;
this.setData({
report,
gainText: (g >= 0 ? '+' : '') + g.toFixed(1),
bill,
summary,
});
})
.catch(() => {});
},
openSheet(e) {
this.setData({ sheetType: e.currentTarget.dataset.type, sheetShow: true });
},
closeSheet() {
this.setData({ sheetShow: false });
},
onAddPet() {
this.setData({ sheetType: 'addPet', sheetShow: true });
},
onFab() {
this.setData({ sheetType: 'ai', sheetShow: true });
},
});
+8
View File
@@ -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"
}
}
+59
View File
@@ -0,0 +1,59 @@
<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="card report-card">
<view class="section-head"><view class="sh-title">本周成长报告</view><view class="tag">{{report.health_status || '稳定成长'}}</view></view>
<view class="rc-text">{{report.summary || '暂无本周数据'}}</view>
<view class="stats-grid">
<view class="stat"><text class="stat-b">{{report.tasks_completed || 0}}</text><text class="stat-s">完成任务</text></view>
<view class="stat"><text class="stat-b">{{gainText}}</text><text class="stat-s">kg 增长</text></view>
<view class="stat"><text class="stat-b">{{report.high_risk_count || 0}}</text><text class="stat-s">高风险异常</text></view>
</view>
<view class="sheet-actions">
<button class="btn btn-ghost" data-type="reportDetail" bindtap="openSheet">查看报告</button>
<button class="btn btn-primary" data-type="poster" bindtap="openSheet">分享卡片</button>
</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="record-row"><text class="bold">疫苗进度</text><text class="muted">{{summary.vaccine_progress || '—'}}</text></view>
<view class="record-row"><text class="bold">驱虫状态</text><text class="muted">{{summary.deworm_status || '—'}}</text></view>
<view class="record-row"><text class="bold">体重趋势</text><text class="muted">{{summary.weight_trend || '—'}}</text></view>
<view class="record-row"><text class="bold">异常记录</text><text class="muted">{{summary.anomaly_count || 0}} 次</text></view>
</view>
<view class="card">
<view class="section-head"><view class="sh-title">养宠账单</view><view class="link" data-type="cost" bindtap="openSheet">记一笔</view></view>
<view class="stats-grid">
<view class="stat"><text class="stat-b">¥{{bill.total || 0}}</text><text class="stat-s">本月花费</text></view>
<view class="stat"><text class="stat-b">¥{{bill.max_single || 0}}</text><text class="stat-s">最大支出</text></view>
<view class="stat"><text class="stat-b">{{bill.categories.length || 0}}</text><text class="stat-s">消费类别</text></view>
</view>
<view class="bill-bars">
<view wx:for="{{bill.categories}}" wx:key="category" class="bill-row">
<text class="bill-cat">{{item.category}}</text>
<view class="mini-bar"><view class="mini-bar-fill" style="width:{{item.percent}}%"></view></view>
<text class="bill-amt">¥{{item.amount}}</text>
</view>
</view>
</view>
<view class="card">
<view class="section-head"><view class="sh-title">Pro 会员</view></view>
<view class="rc-text">解锁 365 天计划、PDF 健康档案、月度报告、多宠物管理与年度账单。</view>
<button class="btn btn-primary btn-block" style="margin-top:24rpx" data-type="pro" bindtap="openSheet">查看 Pro 权益</button>
</view>
</view>
</scroll-view>
<fab bind:tap="onFab"></fab>
<bottom-sheet show="{{sheetShow}}" type="{{sheetType}}" bind:close="closeSheet"></bottom-sheet>
+19
View File
@@ -0,0 +1,19 @@
.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}
.report-card{background:linear-gradient(135deg,#FFFFFF,#F2FAF6)}
.rc-text{color:var(--muted);font-size:28rpx;line-height:1.6}
.stat-b{display:block;font-size:36rpx;margin-bottom:8rpx;font-weight:800}
.stat-s{font-size:22rpx;color:var(--muted)}
.bill-bars{display:flex;flex-direction:column;gap:20rpx;margin-top:24rpx}
.bill-row{display:grid;grid-template-columns:88rpx 1fr 108rpx;gap:16rpx;align-items:center;font-size:24rpx}
.bill-cat{color:var(--text)}
.bill-amt{font-weight:900;text-align:right}
.mini-bar{height:18rpx;border-radius:999rpx;background:#F0E7DD;overflow:hidden}
.mini-bar-fill{display:block;height:100%;border-radius:999rpx;background:linear-gradient(90deg,var(--blue),#A9C8F6)}
/* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/
page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
.page-scroll{flex:1;min-height:0}