feat: 补齐五块缺失功能(记录丢数据/文章正文/提醒管理/我的页入口/分享/关注)
P0 修数据问题 - 记录弹层有 5 个输入框从未绑定,用户填的便便备注、饮食内容、药品名称、 剂量、消费备注全部丢弃;用药记录因此只存下「用药记录」四个字。已全部绑定并入库 - 异常观察保存时写死「风险中」,AI 实际判定的等级被丢掉。改为使用 riskData 的真实 risk_level,并把成因与建议存进备注 文章正文 - 早期 seed 的 4 篇文章 Content 全空,点开只是打开关联记录弹层,内容运营等于白做 - 补写 4 篇真实正文(疫苗驱虫/软便判断/换粮方案/养宠花销),遵循不诊断、 异常必提就医的护栏;对已存在的空正文做幂等回填 - 新增文章详情页,学习页与首页卡片改为打开正文 提醒管理 - 后端 CRUD 早已具备但小程序只读,用户改不了疫苗驱虫日期。提醒弹层支持增删改 - 修复后端 UpdateReminder 漏掉 type 字段导致改类型不生效 「我的」页 7 个死入口接活 - 提醒设置、健康记录、多宠物管理、会员权益分别落到对应功能 - 数据导出:汇总档案与记录为文本,可复制发给医生 - 意见反馈:新增 feedback 表与提交接口,后台加「意见反馈」管理页 - 关于我们:应用说明与免责声明 分享 - 各页面补 onShareAppMessage/onShareTimeline - 帖子分享按钮改用微信原生 open-type="share",删除原来点了没反应的空壳弹层 社区关注 - 新增 follows 表与关注/取关接口,「关注」tab 此前等同「推荐」, 现只显示已关注用户的帖子;帖子头部可直接关注,标记 followed / is_self Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
const api = require('../../utils/api.js');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
article: null,
|
||||
loading: true,
|
||||
error: '',
|
||||
},
|
||||
onLoad(options) {
|
||||
const id = options.id;
|
||||
if (!id) {
|
||||
this.setData({ loading: false, error: '文章不存在' });
|
||||
return;
|
||||
}
|
||||
api
|
||||
.getArticle(id)
|
||||
.then((a) => this.setData({ article: a, loading: false }))
|
||||
.catch((e) => this.setData({ loading: false, error: e.message || '加载失败' }));
|
||||
},
|
||||
// 从文章跳到对应的记录入口
|
||||
goRecord() {
|
||||
wx.switchTab({ url: '/pages/record/record' });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
const a = this.data.article;
|
||||
return {
|
||||
title: a ? a.title : '肉垫计划 · 新手养宠知识',
|
||||
path: a ? `/pages/article/article?id=${a.id}` : '/pages/home/home',
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/nav-bar"
|
||||
},
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "新手知识"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<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">
|
||||
<view wx:if="{{loading}}" class="hint">加载中…</view>
|
||||
<view wx:elif="{{error}}" class="hint">{{error}}</view>
|
||||
|
||||
<block wx:elif="{{article}}">
|
||||
<view class="art-head">
|
||||
<view class="art-icon">{{article.icon}}</view>
|
||||
<view class="art-title">{{article.title}}</view>
|
||||
<view wx:if="{{article.description}}" class="art-desc">{{article.description}}</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view wx:if="{{article.content}}" class="art-content">{{article.content}}</view>
|
||||
<view wx:else class="hint">这篇文章还没有正文。</view>
|
||||
</view>
|
||||
|
||||
<button class="btn btn-primary btn-block" bindtap="goRecord">去记一笔</button>
|
||||
<view class="art-foot">内容仅供日常照护参考,不能替代医生诊断。</view>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,17 @@
|
||||
page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
|
||||
.page-scroll{flex:1;min-height:0}
|
||||
.page-body{padding:8rpx 36rpx calc(60rpx + env(safe-area-inset-bottom))}
|
||||
|
||||
.hint{padding:60rpx 0;text-align:center;color:var(--muted);font-size:28rpx}
|
||||
|
||||
.art-head{padding:12rpx 0 28rpx}
|
||||
.art-icon{font-size:72rpx;line-height:1;margin-bottom:16rpx}
|
||||
.art-title{font-size:44rpx;font-weight:800;line-height:1.35;letter-spacing:-.5rpx}
|
||||
.art-desc{margin-top:14rpx;color:var(--muted);font-size:27rpx;line-height:1.6}
|
||||
|
||||
.art-content{
|
||||
font-size:29rpx;line-height:1.85;color:#3D352E;
|
||||
white-space:pre-line; /* 正文用真实换行排版 */
|
||||
}
|
||||
|
||||
.art-foot{margin-top:28rpx;text-align:center;color:var(--muted);font-size:23rpx}
|
||||
@@ -18,6 +18,9 @@ function mapPost(p) {
|
||||
tag,
|
||||
tagClass: TAG_CLASS[tag] || '',
|
||||
is_ai: !!p.is_ai,
|
||||
user_id: p.user_id,
|
||||
followed: !!p.followed,
|
||||
is_self: !!p.is_self,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,4 +95,33 @@ Page({
|
||||
onFab() {
|
||||
this.setData({ sheetType: 'ai', sheetPostId: 0, sheetShow: true });
|
||||
},
|
||||
// 关注 / 取关帖子作者
|
||||
toggleFollow(e) {
|
||||
const i = e.currentTarget.dataset.index;
|
||||
const p = this.data.posts[i];
|
||||
if (!p || !p.user_id || p.is_self) return;
|
||||
const fn = p.followed ? api.unfollowUser : api.followUser;
|
||||
fn(p.user_id)
|
||||
.then(() => {
|
||||
// 同一作者的所有帖子一起更新状态
|
||||
const posts = this.data.posts.map((x) =>
|
||||
x.user_id === p.user_id ? { ...x, followed: !p.followed } : x,
|
||||
);
|
||||
this.setData({ posts });
|
||||
wx.showToast({ title: p.followed ? '已取消关注' : '已关注', icon: 'none' });
|
||||
if (this.data.feedTabs[this.data.feedIdx] === '关注') this.loadPosts();
|
||||
})
|
||||
.catch((err) => wx.showToast({ title: err.message || '操作失败', icon: 'none' }));
|
||||
},
|
||||
onShareAppMessage(e) {
|
||||
// 从帖子上的分享按钮转发:带上该帖内容做标题
|
||||
if (e && e.from === 'button') {
|
||||
const c = (e.target.dataset.content || '').slice(0, 40);
|
||||
return { title: c ? '宠友圈:' + c : '来宠友圈看看大家的毛孩子', path: '/pages/community/community' };
|
||||
}
|
||||
return { title: '宠友圈 · 和铲屎官们交流养宠经验', path: '/pages/community/community' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '宠友圈 · 和铲屎官们交流养宠经验' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,8 @@ module.exports.isUrl = function (s) { return s && s.indexOf('http') === 0; }
|
||||
<view class="post-head">
|
||||
<view class="post-avatar">{{item.author_emoji}}</view>
|
||||
<view class="post-user"><text class="pu-b">{{item.author_name}}<text wx:if="{{item.is_ai}}" class="ai-tag">AI</text></text><text class="pu-s">宠友</text></view>
|
||||
<view wx:if="{{!item.is_self && item.user_id}}" class="follow-btn {{item.followed ? 'on' : ''}}"
|
||||
catchtap="toggleFollow" data-index="{{index}}">{{item.followed ? '已关注' : '+ 关注'}}</view>
|
||||
<view wx:if="{{item.tag}}" class="tag {{item.tagClass}}">{{item.tag}}</view>
|
||||
</view>
|
||||
<view class="post-content">{{item.content}}</view>
|
||||
@@ -35,7 +37,7 @@ module.exports.isUrl = function (s) { return s && s.indexOf('http') === 0; }
|
||||
<view class="post-actions">
|
||||
<view class="pa-btn {{item.liked ? 'liked' : ''}}" data-index="{{index}}" bindtap="likePost">{{item.liked ? '♥' : '♡'}} {{item.like_count}}</view>
|
||||
<view class="pa-btn" data-id="{{item.id}}" bindtap="openComments">💬 {{item.comment_count}}</view>
|
||||
<view class="pa-btn" data-type="sharePost" bindtap="openSheet">↗ 分享</view>
|
||||
<button class="pa-btn pa-share" open-type="share" data-content="{{item.content}}">↗ 分享</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -39,3 +39,17 @@
|
||||
/* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/
|
||||
page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
|
||||
.page-scroll{flex:1;min-height:0}
|
||||
|
||||
/* 帖子分享按钮:用微信原生 open-type=share,需抹掉 button 默认外观 */
|
||||
.pa-share{
|
||||
background:transparent;border:none;padding:0;margin:0;line-height:inherit;
|
||||
font-size:26rpx;font-weight:900;color:var(--muted);
|
||||
}
|
||||
.pa-share::after{border:none}
|
||||
|
||||
/* 关注按钮 */
|
||||
.follow-btn{
|
||||
flex:none;font-size:22rpx;font-weight:700;padding:6rpx 18rpx;border-radius:999rpx;
|
||||
color:var(--primary-dark);background:#FFF2DC;border:1rpx solid #FFD39A;
|
||||
}
|
||||
.follow-btn.on{color:var(--muted);background:#F4F1EC;border-color:var(--line)}
|
||||
|
||||
@@ -60,7 +60,7 @@ Page({
|
||||
.listArticles()
|
||||
.then((list) => {
|
||||
if (list && list[0]) {
|
||||
this.setData({ firstArticle: { icon: list[0].icon, title: list[0].title, desc: list[0].description } });
|
||||
this.setData({ firstArticle: { id: list[0].id, icon: list[0].icon, title: list[0].title, desc: list[0].description } });
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -152,7 +152,19 @@ Page({
|
||||
goLearn() {
|
||||
wx.navigateTo({ url: '/pages/learn/learn' });
|
||||
},
|
||||
// 首页那张卡片直接打开这篇文章正文
|
||||
openFirstArticle() {
|
||||
const a = this.data.firstArticle;
|
||||
if (a && a.id) wx.navigateTo({ url: `/pages/article/article?id=${a.id}` });
|
||||
else this.goLearn();
|
||||
},
|
||||
goRecord() {
|
||||
wx.switchTab({ url: '/pages/record/record' });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '肉垫计划 · 每天照顾好毛孩子', path: '/pages/home/home' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '肉垫计划 · 每天照顾好毛孩子' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<view class="sh-title">新手内容</view>
|
||||
<view class="link" bindtap="goLearn">查看</view>
|
||||
</view>
|
||||
<view wx:if="{{firstArticle}}" class="article-card no-shadow" style="margin-bottom:0" bindtap="goLearn">
|
||||
<view wx:if="{{firstArticle}}" class="article-card no-shadow" style="margin-bottom:0" bindtap="openFirstArticle">
|
||||
<view class="article-icon">{{firstArticle.icon}}</view>
|
||||
<view><text class="ac-b">{{firstArticle.title}}</text><view class="ac-p">{{firstArticle.desc}}</view></view>
|
||||
</view>
|
||||
|
||||
@@ -14,15 +14,20 @@ Page({
|
||||
.then((list) => {
|
||||
this.setData({
|
||||
articles: (list || []).map((a) => ({
|
||||
id: a.id,
|
||||
icon: a.icon,
|
||||
title: a.title,
|
||||
desc: a.description,
|
||||
type: a.related_sheet_type || 'vaccine',
|
||||
})),
|
||||
});
|
||||
})
|
||||
.catch((e) => wx.showToast({ title: e.message || '加载失败', icon: 'none' }));
|
||||
},
|
||||
// 打开文章正文详情页
|
||||
openArticle(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
if (id) wx.navigateTo({ url: `/pages/article/article?id=${id}` });
|
||||
},
|
||||
openSheet(e) {
|
||||
this.setData({ sheetType: e.currentTarget.dataset.type, sheetShow: true });
|
||||
},
|
||||
@@ -32,4 +37,10 @@ Page({
|
||||
onFab() {
|
||||
this.setData({ sheetType: 'ai', sheetShow: true });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '新手养宠知识合集', path: '/pages/learn/learn' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '新手养宠知识合集' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<view class="pt-p">新手养宠知识,看完顺手生成你的计划。</view>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{articles}}" wx:key="type" class="article-card" data-type="{{item.type}}" bindtap="openSheet">
|
||||
<view wx:for="{{articles}}" wx:key="id" class="article-card" data-id="{{item.id}}" bindtap="openArticle">
|
||||
<view class="article-icon">{{item.icon}}</view>
|
||||
<view><text class="ac-b">{{item.title}}</text><view class="ac-p">{{item.desc}}</view></view>
|
||||
</view>
|
||||
|
||||
@@ -172,4 +172,10 @@ Page({
|
||||
onFab() {
|
||||
this.setData({ sheetType: 'ai', sheetShow: true });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '我给毛孩子做了份养护计划', path: '/pages/plan/plan' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '我给毛孩子做了份养护计划' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@ Page({
|
||||
openSheet(e) {
|
||||
this.setData({ sheetType: e.currentTarget.dataset.type, sheetShow: true });
|
||||
},
|
||||
goRecord() {
|
||||
wx.switchTab({ url: '/pages/record/record' });
|
||||
},
|
||||
onPickAvatar() {
|
||||
upload
|
||||
.chooseAndUploadImage()
|
||||
@@ -67,4 +70,7 @@ Page({
|
||||
onFab() {
|
||||
this.setData({ sheetType: 'ai', sheetShow: true });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '肉垫计划 · 新手养宠助手', path: '/pages/home/home' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
|
||||
<view class="profile-list">
|
||||
<view class="profile-row" bindtap="onEditPet">宠物档案(编辑)<text class="pr-val">已完善 {{profilePct}}% ›</text></view>
|
||||
<view class="profile-row">提醒设置 <text class="pr-val">{{summary.reminders}} 条</text></view>
|
||||
<view class="profile-row">健康记录 <text class="pr-val">{{summary.records}} 条</text></view>
|
||||
<view class="profile-row">多宠物管理 <text class="pr-val">{{summary.pets}} 只</text></view>
|
||||
<view class="profile-row" data-type="reminders" bindtap="openSheet">提醒设置 <text class="pr-val">{{summary.reminders}} 条 ›</text></view>
|
||||
<view class="profile-row" bindtap="goRecord">健康记录 <text class="pr-val">{{summary.records}} 条 ›</text></view>
|
||||
<view class="profile-row" data-type="managePets" bindtap="openSheet">多宠物管理 <text class="pr-val">{{summary.pets}} 只 ›</text></view>
|
||||
</view>
|
||||
|
||||
<view class="profile-list">
|
||||
<view class="profile-row">会员权益 <text class="pr-val">{{summary.pro_status === 'active' ? '已开通' : '未开通'}}</text></view>
|
||||
<view class="profile-row">数据导出 <text class="pr-val">PDF</text></view>
|
||||
<view class="profile-row">意见反馈 <text class="pr-val">去填写</text></view>
|
||||
<view class="profile-row">关于我们 <text class="pr-val">v0.2</text></view>
|
||||
<view class="profile-row" data-type="pro" bindtap="openSheet">会员权益 <text class="pr-val">{{summary.pro_status === 'active' ? '已开通' : '未开通'}} ›</text></view>
|
||||
<view class="profile-row" data-type="exportData" bindtap="openSheet">数据导出 <text class="pr-val">文本 ›</text></view>
|
||||
<view class="profile-row" data-type="feedback" bindtap="openSheet">意见反馈 <text class="pr-val">去填写 ›</text></view>
|
||||
<view class="profile-row" data-type="about" bindtap="openSheet">关于我们 <text class="pr-val">v0.2 ›</text></view>
|
||||
</view>
|
||||
|
||||
<button class="btn btn-primary btn-block" data-type="pro" bindtap="openSheet">开通 Pro</button>
|
||||
|
||||
@@ -174,4 +174,10 @@ Page({
|
||||
onFab() {
|
||||
this.openSheetType('ai');
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '用肉垫计划记录毛孩子的成长', path: '/pages/record/record' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '用肉垫计划记录毛孩子的成长' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -59,4 +59,10 @@ Page({
|
||||
onFab() {
|
||||
this.setData({ sheetType: 'ai', sheetShow: true });
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return { title: '看看我家毛孩子的成长报告', path: '/pages/report/report' };
|
||||
},
|
||||
onShareTimeline() {
|
||||
return { title: '看看我家毛孩子的成长报告' };
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user