feat(fe): Phase D —— 主页装扮,「我的」和「宠友主页」共用一套渲染
## profile-head 组件 头图 / 头像 / 昵称 / 签名 / 帖子关注粉丝 / 养宠数据 / 宠物名片墙, pages/mine 和 pages/user 用的是同一个组件、同一个接口 (GET /users/:id/profile)。 这件事必须一开始就做对:自己看和别人看要是两套渲染,会慢慢长歪, 装扮功能也就失去意义了——你调半天颜色,结果只有自己那页变了。 两边唯一的差别是底部按钮:自己看是「装扮我的主页」,别人看是「关注 TA」。 顺带把 mine 的头部从 /user/summary 切到了 /users/:id/profile。原来两边 字段对不上,装扮效果在「我的」页根本预览不到。 主题只在组件 wxss 里定义 --pf-a/--pf-b/--pf-ink 三个变量,下面所有着色 都走变量。后端存 key 不存色值,就是为了这套配色随时能在前端改。 ## pages/decorate 装扮页 头图上传 / 五套主题色 / 个性签名 60 字 / 宠物墙开关,顶部实时预览。 预览用的就是 profile-head 本身,不是仿一个——仿的迟早会和真的长得不一样。 喂给它一份改过的 card 就行,组件那边不需要知道自己在被预览。 头图有个坑单独处理了:bg_file_id 只在真动过的时候才传。新上传带新 id, 点「恢复默认渐变」带空串,都没动就不传字段。否则「只改个主题色」会把 已有头图冲掉。上传时先用本地临时路径顶上,不让用户对着转圈等。 联调验证(本地 9090,真上传了一张图走完存储链路): 上传 → file id + MinIO url 保存装扮 → bg_url 落库,主题 violet,签名落库 主页读回 → bio/theme/show_pets/bg_url/宠物墙/养宠数据 全对 只改主题 → theme 变 sky,头图还在(没被冲掉) 恢复默认渐变 → bg_url 清空,theme 和 bio 都没被动 别人视角 → is_self=false、followed=false,装扮和宠物墙都能看到 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"pages/learn/learn",
|
||||
"pages/article/article",
|
||||
"pages/mine/mine",
|
||||
"pages/decorate/decorate",
|
||||
"pages/settings/settings",
|
||||
"pages/ai/ai",
|
||||
"pages/user/user",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 个人主页头部。「我的」和「宠友主页」共用同一份渲染——自己看和别人看
|
||||
// 长得不一样的话,两边会慢慢长歪,装扮功能也就失去意义了。
|
||||
// 差别只有一处:自己看是「装扮主页」,别人看是「关注 TA」。
|
||||
Component({
|
||||
options: { addGlobalClass: true },
|
||||
properties: {
|
||||
card: { type: Object, value: null },
|
||||
// 装扮页的实时预览:只看效果,不要底部那排按钮
|
||||
preview: { type: Boolean, value: false },
|
||||
},
|
||||
data: { initial: '', theme: 'warm' },
|
||||
observers: {
|
||||
card(c) {
|
||||
if (!c) return;
|
||||
this.setData({
|
||||
// WXML 取不了字符串下标,首字得在 JS 里算
|
||||
initial: (c.nickname || '?').slice(0, 1),
|
||||
theme: c.theme || 'warm',
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onFollow() {
|
||||
this.triggerEvent('follow');
|
||||
},
|
||||
onDecorate() {
|
||||
this.triggerEvent('decorate');
|
||||
},
|
||||
onRelations(e) {
|
||||
this.triggerEvent('relations', { kind: e.currentTarget.dataset.kind });
|
||||
},
|
||||
onPet(e) {
|
||||
this.triggerEvent('pet', { id: e.currentTarget.dataset.id });
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"pt-icon": "/components/pt-icon/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<view class="pf th-{{theme}}">
|
||||
<!-- 头图。没传背景图就用主题色渐变,不留空白块 -->
|
||||
<view class="pf-bg">
|
||||
<image wx:if="{{card.bg_url}}" class="pf-bg-img" src="{{card.bg_url}}" mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
<view class="pf-body">
|
||||
<view class="pf-av">
|
||||
<image wx:if="{{card.avatar_url}}" class="pf-av-img" src="{{card.avatar_url}}" mode="aspectFill"></image>
|
||||
<block wx:else>{{initial}}</block>
|
||||
</view>
|
||||
<view class="pf-name">{{card.nickname || '毛孩子家长'}}<text wx:if="{{card.is_bot}}" class="ai-tag">AI</text></view>
|
||||
<view class="pf-bio">{{card.bio || '这个人很懒,还没写签名'}}</view>
|
||||
|
||||
<view class="pf-stats">
|
||||
<view class="pf-stat"><text class="pf-n">{{card.post_count || 0}}</text><text class="pf-l">帖子</text></view>
|
||||
<view class="pf-stat" data-kind="following" bindtap="onRelations">
|
||||
<text class="pf-n">{{card.following_count || 0}}</text><text class="pf-l">关注</text>
|
||||
</view>
|
||||
<view class="pf-stat" data-kind="followers" bindtap="onRelations">
|
||||
<text class="pf-n">{{card.follower_count || 0}}</text><text class="pf-l">粉丝</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 养宠数据。这是这个 App 独有的东西,比帖子数更能说明「这人是个认真的铲屎官」 -->
|
||||
<view wx:if="{{card.stats}}" class="pf-keep">
|
||||
<view class="pf-keep-i"><text class="pf-kn">{{card.stats.days || 0}}</text><text class="pf-kl">养宠天数</text></view>
|
||||
<view class="pf-keep-i"><text class="pf-kn">{{card.stats.records || 0}}</text><text class="pf-kl">累计记录</text></view>
|
||||
<view class="pf-keep-i"><text class="pf-kn">{{card.stats.streak || 0}}</text><text class="pf-kl">连续打卡</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 宠物名片墙。可以在装扮里关掉 -->
|
||||
<scroll-view wx:if="{{card.show_pets && card.pets.length}}" class="pf-wall"
|
||||
scroll-x="{{true}}" enhanced="{{true}}" show-scrollbar="{{false}}">
|
||||
<view wx:for="{{card.pets}}" wx:key="id" class="pf-pet" data-id="{{item.id}}" bindtap="onPet">
|
||||
<view class="pf-pet-av">
|
||||
<image wx:if="{{item.avatar_url}}" class="pf-pet-img" src="{{item.avatar_url}}" mode="aspectFill"></image>
|
||||
<block wx:else>{{item.emoji}}</block>
|
||||
</view>
|
||||
<view class="pf-pet-n">{{item.name}}</view>
|
||||
<view class="pf-pet-s">{{item.stage}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<block wx:if="{{!preview}}">
|
||||
<view wx:if="{{card.is_self}}" class="btn btn-ghost btn-block" bindtap="onDecorate">
|
||||
<pt-icon name="edit" size="{{30}}"></pt-icon>装扮我的主页
|
||||
</view>
|
||||
<view wx:else class="btn {{card.followed ? 'btn-ghost' : 'btn-dark'}} btn-block" bindtap="onFollow">
|
||||
<pt-icon wx:if="{{!card.followed}}" name="plus" size="{{28}}"></pt-icon>{{card.followed ? '已关注' : '关注 TA'}}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,59 @@
|
||||
/* 主题只在这里定义两个变量,下面所有着色都走变量。
|
||||
后端存的是 theme key 不是色值,就是为了这套配色能随时在前端改。 */
|
||||
.pf{--pf-a:#F5A947;--pf-b:#FFE6BA;--pf-ink:#8A5A18}
|
||||
.th-warm{--pf-a:#F5A947;--pf-b:#FFE6BA;--pf-ink:#8A5A18}
|
||||
.th-mint{--pf-a:#3FB98A;--pf-b:#D7F3E7;--pf-ink:#1F6B4E}
|
||||
.th-sky{--pf-a:#4A9BE0;--pf-b:#D8ECFB;--pf-ink:#245F94}
|
||||
.th-violet{--pf-a:#9B7BE0;--pf-b:#E8DFFA;--pf-ink:#5B3F9B}
|
||||
.th-ink{--pf-a:#5A5550;--pf-b:#E4DED7;--pf-ink:#2D2925}
|
||||
|
||||
.pf{
|
||||
background:#fff;border-radius:var(--r-lg);overflow:hidden;
|
||||
box-shadow:var(--sd-1);margin-bottom:var(--sp-5);
|
||||
}
|
||||
.pf-bg{height:220rpx;background:linear-gradient(135deg,var(--pf-a),var(--pf-b));position:relative}
|
||||
.pf-bg-img{width:100%;height:100%;display:block}
|
||||
|
||||
.pf-body{padding:0 var(--sp-5) var(--sp-5);text-align:center}
|
||||
/* 头像压在头图上,负 margin 提上去一半 */
|
||||
.pf-av{
|
||||
width:144rpx;height:144rpx;margin:-72rpx auto var(--sp-3);
|
||||
border-radius:50%;background:var(--pf-b);color:var(--pf-ink);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:var(--fs-xl);font-weight:var(--fw-b);
|
||||
border:6rpx solid #fff;overflow:hidden;box-shadow:var(--sd-1);
|
||||
}
|
||||
.pf-av-img{width:100%;height:100%;display:block}
|
||||
.pf-name{font-size:var(--fs-lg);font-weight:var(--fw-b);color:var(--text)}
|
||||
.pf-bio{
|
||||
font-size:var(--fs-sm);color:var(--muted);margin-top:6rpx;
|
||||
line-height:1.5;word-break:break-all;
|
||||
}
|
||||
|
||||
.pf-stats{display:flex;margin:var(--sp-4) 0}
|
||||
.pf-stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:4rpx}
|
||||
.pf-n{font-size:var(--fs-lg);font-weight:var(--fw-b);color:var(--text)}
|
||||
.pf-l{font-size:var(--fs-cap);color:var(--muted)}
|
||||
|
||||
.pf-keep{
|
||||
display:flex;background:var(--pf-b);border-radius:var(--r-md);
|
||||
padding:var(--sp-3) 0;margin-bottom:var(--sp-4);
|
||||
}
|
||||
.pf-keep-i{flex:1;display:flex;flex-direction:column;align-items:center;gap:2rpx}
|
||||
.pf-kn{font-size:var(--fs-md);font-weight:var(--fw-b);color:var(--pf-ink)}
|
||||
.pf-kl{font-size:var(--fs-cap);color:var(--pf-ink);opacity:.75}
|
||||
|
||||
.pf-wall{white-space:nowrap;margin-bottom:var(--sp-4)}
|
||||
.pf-pet{
|
||||
display:inline-block;width:150rpx;vertical-align:top;
|
||||
margin-right:var(--sp-3);white-space:normal;
|
||||
}
|
||||
.pf-pet:last-child{margin-right:0}
|
||||
.pf-pet-av{
|
||||
width:112rpx;height:112rpx;margin:0 auto 8rpx;border-radius:var(--r-md);
|
||||
background:var(--surface-2);display:flex;align-items:center;justify-content:center;
|
||||
font-size:52rpx;overflow:hidden;
|
||||
}
|
||||
.pf-pet-img{width:100%;height:100%;display:block}
|
||||
.pf-pet-n{font-size:var(--fs-cap);font-weight:var(--fw-b);color:var(--text);text-align:center}
|
||||
.pf-pet-s{font-size:var(--fs-cap);color:var(--muted);text-align:center}
|
||||
@@ -0,0 +1,129 @@
|
||||
const store = require('../../utils/store.js');
|
||||
const api = require('../../utils/api.js');
|
||||
const { toastErr } = require('../../utils/ui.js');
|
||||
|
||||
// key 要和后端 service.UserThemes 一致,色值只在 profile-head 的 wxss 里
|
||||
const THEMES = [
|
||||
{ key: 'warm', label: '暖橙' },
|
||||
{ key: 'mint', label: '薄荷' },
|
||||
{ key: 'sky', label: '天青' },
|
||||
{ key: 'violet', label: '藕紫' },
|
||||
{ key: 'ink', label: '墨灰' },
|
||||
];
|
||||
|
||||
Page({
|
||||
data: {
|
||||
themes: THEMES,
|
||||
form: { bio: '', bg_url: '', bg_file_id: '', theme: 'warm', show_pets: true },
|
||||
bioLen: 0,
|
||||
preview: null,
|
||||
saving: false,
|
||||
},
|
||||
onLoad() {
|
||||
store
|
||||
.ready()
|
||||
.then(() => this.loadCard())
|
||||
.catch((e) => toastErr(e));
|
||||
},
|
||||
loadCard() {
|
||||
const uid = (store.getUser() || {}).id;
|
||||
if (!uid) return;
|
||||
api
|
||||
.userCard(uid)
|
||||
.then((card) => {
|
||||
this.setData({
|
||||
form: {
|
||||
bio: card.bio || '',
|
||||
bg_url: card.bg_url || '',
|
||||
bg_file_id: '', // 没重新上传就不带这个字段,避免把已有头图覆盖成空
|
||||
theme: card.theme || 'warm',
|
||||
show_pets: card.show_pets !== false,
|
||||
},
|
||||
bioLen: (card.bio || '').length,
|
||||
});
|
||||
this._card = card;
|
||||
this.syncPreview();
|
||||
})
|
||||
.catch((e) => toastErr(e));
|
||||
},
|
||||
// 预览喂给 profile-head 的就是一份改过的 card,组件那边不用知道自己在被预览
|
||||
syncPreview() {
|
||||
const f = this.data.form;
|
||||
this.setData({
|
||||
preview: Object.assign({}, this._card || {}, {
|
||||
bio: f.bio,
|
||||
bg_url: f.bg_url,
|
||||
theme: f.theme,
|
||||
show_pets: f.show_pets,
|
||||
}),
|
||||
});
|
||||
},
|
||||
pickBg() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sizeType: ['compressed'],
|
||||
success: (res) => {
|
||||
const path = res.tempFiles[0].tempFilePath;
|
||||
// 先用本地路径顶上,别让用户对着转圈等上传
|
||||
this.setData({ 'form.bg_url': path });
|
||||
this.syncPreview();
|
||||
wx.showLoading({ title: '上传中…' });
|
||||
api
|
||||
.uploadFile(path)
|
||||
.then((file) => {
|
||||
wx.hideLoading();
|
||||
this.setData({ 'form.bg_file_id': file.id, 'form.bg_url': file.url });
|
||||
this.syncPreview();
|
||||
})
|
||||
.catch((e) => {
|
||||
wx.hideLoading();
|
||||
this.setData({ 'form.bg_url': (this._card && this._card.bg_url) || '' });
|
||||
this.syncPreview();
|
||||
toastErr(e, '上传失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
clearBg() {
|
||||
// 空字符串是「清掉头图」的显式信号,和「没传」不是一回事
|
||||
this.setData({ 'form.bg_url': '', 'form.bg_file_id': '' });
|
||||
this.syncPreview();
|
||||
},
|
||||
pickTheme(e) {
|
||||
this.setData({ 'form.theme': e.currentTarget.dataset.key });
|
||||
this.syncPreview();
|
||||
},
|
||||
onBio(e) {
|
||||
this.setData({ 'form.bio': e.detail.value, bioLen: e.detail.value.length });
|
||||
this.syncPreview();
|
||||
},
|
||||
onShowPets(e) {
|
||||
this.setData({ 'form.show_pets': e.detail.value });
|
||||
this.syncPreview();
|
||||
},
|
||||
save() {
|
||||
if (this.data.saving) return;
|
||||
const f = this.data.form;
|
||||
const body = { bio: f.bio, theme: f.theme, show_pets: f.show_pets };
|
||||
// 只有真的动过头图才带 bg_file_id:新上传带新 id,点了「恢复默认渐变」带空串,
|
||||
// 两者都没有就不传,让后端保持原样
|
||||
const had = (this._card && this._card.bg_url) || '';
|
||||
if (f.bg_file_id) body.bg_file_id = f.bg_file_id;
|
||||
else if (had && !f.bg_url) body.bg_file_id = '';
|
||||
|
||||
this.setData({ saving: true });
|
||||
api
|
||||
.updateDecoration(body)
|
||||
.then((user) => {
|
||||
this.setData({ saving: false });
|
||||
store.setUser(user); // 返回的就是改完的 user,别再多请求一次
|
||||
wx.showToast({ title: '已保存', icon: 'success' });
|
||||
setTimeout(() => wx.navigateBack(), 600);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.setData({ saving: false });
|
||||
toastErr(e, '保存失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/nav-bar",
|
||||
"pt-icon": "/components/pt-icon/index",
|
||||
"profile-head": "/components/profile-head/index"
|
||||
},
|
||||
"navigationBarTitleText": "装扮主页"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<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 no-fab">
|
||||
<!-- 预览用的就是主页那个组件本身,不是仿一个。仿的迟早会和真的长得不一样 -->
|
||||
<profile-head card="{{preview}}" preview="{{true}}"></profile-head>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">头图</view></view>
|
||||
<view class="dc-bg" bindtap="pickBg">
|
||||
<image wx:if="{{form.bg_url}}" class="dc-bg-img" src="{{form.bg_url}}" mode="aspectFill"></image>
|
||||
<view wx:else class="dc-bg-empty">
|
||||
<pt-icon name="photo" size="{{48}}"></pt-icon>
|
||||
<text class="dc-hint">点这里换一张头图</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{form.bg_url}}" class="link dc-clear" bindtap="clearBg">恢复默认渐变</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">主题色</view></view>
|
||||
<view class="dc-themes">
|
||||
<view wx:for="{{themes}}" wx:key="key"
|
||||
class="dc-theme {{form.theme === item.key ? 'on' : ''}}"
|
||||
data-key="{{item.key}}" bindtap="pickTheme">
|
||||
<view class="dc-swatch sw-{{item.key}}"></view>
|
||||
<text class="dc-tname">{{item.label}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head">
|
||||
<view class="sh-title">个性签名</view>
|
||||
<view class="dc-count">{{bioLen}}/60</view>
|
||||
</view>
|
||||
<textarea class="textarea" placeholder="介绍一下你和毛孩子" placeholder-class="placeholder"
|
||||
value="{{form.bio}}" maxlength="60" bindinput="onBio"></textarea>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="dc-row">
|
||||
<view class="dc-row-main">
|
||||
<view class="tt-b">展示我的毛孩子</view>
|
||||
<view class="tt-p">关掉后别人访问你的主页看不到宠物名片墙</view>
|
||||
</view>
|
||||
<switch checked="{{form.show_pets}}" color="#F5A947" bindchange="onShowPets"></switch>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn btn-dark btn-block" bindtap="save">{{saving ? '保存中…' : '保存'}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,30 @@
|
||||
.dc-bg{
|
||||
height:240rpx;border-radius:var(--r-md);overflow:hidden;
|
||||
background:var(--surface-2);display:flex;align-items:center;justify-content:center;
|
||||
}
|
||||
.dc-bg-img{width:100%;height:100%;display:block}
|
||||
.dc-bg-empty{display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);color:var(--muted)}
|
||||
.dc-hint{font-size:var(--fs-cap)}
|
||||
.dc-clear{margin-top:var(--sp-3);text-align:center}
|
||||
|
||||
.dc-themes{display:flex;gap:var(--sp-3)}
|
||||
.dc-theme{
|
||||
flex:1;display:flex;flex-direction:column;align-items:center;gap:8rpx;
|
||||
padding:var(--sp-3) 0;border-radius:var(--r-sm);border:2rpx solid transparent;
|
||||
}
|
||||
.dc-theme.on{border-color:var(--primary);background:var(--primary-soft)}
|
||||
.dc-swatch{width:64rpx;height:64rpx;border-radius:50%;box-shadow:var(--sd-1)}
|
||||
.dc-tname{font-size:var(--fs-cap);color:var(--muted)}
|
||||
.dc-theme.on .dc-tname{color:var(--primary-dark);font-weight:var(--fw-b)}
|
||||
|
||||
/* 色值和 profile-head 的主题保持一致,改那边这里也要跟着改 */
|
||||
.sw-warm{background:linear-gradient(135deg,#F5A947,#FFE6BA)}
|
||||
.sw-mint{background:linear-gradient(135deg,#3FB98A,#D7F3E7)}
|
||||
.sw-sky{background:linear-gradient(135deg,#4A9BE0,#D8ECFB)}
|
||||
.sw-violet{background:linear-gradient(135deg,#9B7BE0,#E8DFFA)}
|
||||
.sw-ink{background:linear-gradient(135deg,#5A5550,#E4DED7)}
|
||||
|
||||
.dc-count{font-size:var(--fs-cap);color:var(--muted)}
|
||||
|
||||
.dc-row{display:flex;align-items:center;gap:var(--sp-4)}
|
||||
.dc-row-main{flex:1;min-width:0}
|
||||
+18
-13
@@ -3,7 +3,7 @@ const api = require('../../utils/api.js');
|
||||
const { toastErr } = require('../../utils/ui.js');
|
||||
|
||||
Page({
|
||||
data: { user: {}, pets: [], summary: {}, initial: '', sheetShow: false, sheetType: '' },
|
||||
data: { user: {}, pets: [], card: null, sheetShow: false, sheetType: '' },
|
||||
onLoad() {
|
||||
this._unsub = store.subscribe(() => this.applyUser());
|
||||
},
|
||||
@@ -15,19 +15,21 @@ Page({
|
||||
.ready()
|
||||
.then(() => {
|
||||
this.applyUser();
|
||||
this.loadSummary();
|
||||
this.loadCard();
|
||||
})
|
||||
.catch((e) => toastErr(e));
|
||||
},
|
||||
applyUser() {
|
||||
const user = store.getUser() || {};
|
||||
// WXML 取不了字符串下标,首字在这里算
|
||||
this.setData({ pets: store.getPets(), user, initial: (user.nickname || '?').slice(0, 1) });
|
||||
this.setData({ pets: store.getPets(), user: store.getUser() || {} });
|
||||
},
|
||||
loadSummary() {
|
||||
// 头部数据走 /users/:id/profile,和别人看我时是同一个接口——
|
||||
// 换成 /user/summary 的话两边字段会对不上,装扮效果也预览不到
|
||||
loadCard() {
|
||||
const uid = (this.data.user && this.data.user.id) || '';
|
||||
if (!uid) return;
|
||||
api
|
||||
.userSummary()
|
||||
.then((summary) => this.setData({ summary }))
|
||||
.userCard(uid)
|
||||
.then((card) => this.setData({ card }))
|
||||
.catch(() => {});
|
||||
},
|
||||
// 看自己的公开主页,和别人看到的是同一个页面、同一套渲染
|
||||
@@ -41,16 +43,19 @@ Page({
|
||||
goRelations(e) {
|
||||
const uid = (this.data.user && this.data.user.id) || '';
|
||||
if (!uid) return wx.showToast({ title: '登录信息还没就绪', icon: 'none' });
|
||||
wx.navigateTo({ url: `/pages/relations/relations?id=${uid}&kind=${e.currentTarget.dataset.kind}` });
|
||||
wx.navigateTo({ url: `/pages/relations/relations?id=${uid}&kind=${e.detail.kind}` });
|
||||
},
|
||||
goDecorate() {
|
||||
wx.navigateTo({ url: '/pages/decorate/decorate' });
|
||||
},
|
||||
goPlan() {
|
||||
wx.navigateTo({ url: '/pages/plan/plan' });
|
||||
},
|
||||
goRecord() {
|
||||
wx.switchTab({ url: '/pages/record/record' });
|
||||
},
|
||||
// 列表行传 dataset,宠物名片墙传 detail,两边都进这里
|
||||
onPickPet(e) {
|
||||
store.switchPet(e.currentTarget.dataset.id);
|
||||
const id = (e.detail && e.detail.id) || e.currentTarget.dataset.id;
|
||||
if (!id) return;
|
||||
store.switchPet(id);
|
||||
wx.switchTab({ url: '/pages/home/home' });
|
||||
},
|
||||
openSheet(e) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"nav-bar": "/components/nav-bar/nav-bar",
|
||||
"bottom-sheet": "/components/bottom-sheet/bottom-sheet",
|
||||
"fab": "/components/fab/fab",
|
||||
"pt-icon": "/components/pt-icon/index"
|
||||
"pt-icon": "/components/pt-icon/index",
|
||||
"profile-head": "/components/profile-head/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,13 @@
|
||||
|
||||
<scroll-view class="page-scroll" scroll-y="{{true}}" enhanced="{{true}}" show-scrollbar="{{false}}">
|
||||
<view class="page-body">
|
||||
<!-- 个人名片。点进去看的是「别人眼里的我」,和 pages/user 同一个页面 -->
|
||||
<view class="card me-card">
|
||||
<view class="me-top">
|
||||
<view class="me-av">
|
||||
<image wx:if="{{user.avatar_url}}" class="me-av-img" src="{{user.avatar_url}}" mode="aspectFill"></image>
|
||||
<block wx:else>{{initial}}</block>
|
||||
</view>
|
||||
<view class="me-main">
|
||||
<view class="me-name">{{user.nickname || '毛孩子家长'}}</view>
|
||||
<view class="me-bio">{{user.bio || '还没有个性签名'}}</view>
|
||||
</view>
|
||||
<view class="me-gear" catchtap="goSettings"><pt-icon name="settings" size="{{36}}"></pt-icon></view>
|
||||
</view>
|
||||
<!-- 和「宠友主页」同一个组件。自己看到的就是别人看到的,装扮才有意义 -->
|
||||
<profile-head card="{{card}}" bind:relations="goRelations"
|
||||
bind:decorate="goDecorate" bind:pet="onPickPet"></profile-head>
|
||||
|
||||
<view class="me-stats">
|
||||
<view class="me-stat" data-kind="following" bindtap="goRelations">
|
||||
<text class="me-n">{{summary.following || 0}}</text><text class="me-l">关注</text>
|
||||
</view>
|
||||
<view class="me-stat" data-kind="followers" bindtap="goRelations">
|
||||
<text class="me-n">{{summary.followers || 0}}</text><text class="me-l">粉丝</text>
|
||||
</view>
|
||||
<view class="me-stat" bindtap="goRecord">
|
||||
<text class="me-n">{{summary.records || 0}}</text><text class="me-l">记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn btn-ghost btn-block" bindtap="previewPublic">
|
||||
<view class="btn btn-ghost btn-block me-preview" bindtap="previewPublic">
|
||||
<pt-icon name="user" size="{{30}}"></pt-icon>看看别人眼里的我
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的毛孩子 -->
|
||||
<view class="card">
|
||||
@@ -62,7 +39,7 @@
|
||||
</view>
|
||||
<view class="profile-row" data-type="managePets" bindtap="openSheet">
|
||||
<view class="pr-ic"><pt-icon name="community" size="{{34}}"></pt-icon></view>
|
||||
<text class="pr-label">多宠物管理</text><text class="pr-val">{{summary.pets || 0}} 只</text>
|
||||
<text class="pr-label">多宠物管理</text><text class="pr-val">{{card.stats.pet_count || 0}} 只</text>
|
||||
<view class="pr-arrow"><pt-icon name="next" size="{{28}}"></pt-icon></view>
|
||||
</view>
|
||||
<view class="profile-row" bindtap="goSettings">
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
.me-card{padding:var(--sp-5)}
|
||||
.me-top{display:flex;align-items:center;gap:var(--sp-3)}
|
||||
.me-av{
|
||||
width:110rpx;height:110rpx;border-radius:50%;flex:none;overflow:hidden;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
background:var(--primary-soft);color:var(--primary-ink);font-size:44rpx;font-weight:var(--fw-b);
|
||||
}
|
||||
.me-av-img{width:100%;height:100%}
|
||||
.me-main{flex:1;min-width:0}
|
||||
.me-name{font-size:var(--fs-lg);font-weight:var(--fw-b)}
|
||||
.me-bio{color:var(--muted);font-size:var(--fs-sm);margin-top:6rpx}
|
||||
.me-gear{flex:none;color:var(--muted)}
|
||||
.me-stats{display:flex;margin:var(--sp-4) 0}
|
||||
.me-stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:4rpx}
|
||||
.me-n{font-size:var(--fs-lg);font-weight:var(--fw-b)}
|
||||
.me-l{font-size:var(--fs-cap);color:var(--muted)}
|
||||
|
||||
.pet-row{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) 0;border-bottom:1rpx solid var(--line)}
|
||||
.pet-row:last-child{border-bottom:0}
|
||||
@@ -23,3 +7,5 @@
|
||||
background:var(--primary-soft);font-size:40rpx;
|
||||
}
|
||||
.pet-info{flex:1;min-width:0}
|
||||
|
||||
.me-preview{margin-bottom:var(--sp-5)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const api = require('../../utils/api.js');
|
||||
const store = require('../../utils/store.js');
|
||||
const { toastErr } = require('../../utils/ui.js');
|
||||
|
||||
const TAG_CLASS = { 求助: 'warn', 经验: 'blue', 精选: 'purple', 避坑: 'red', 晒宠: '' };
|
||||
@@ -28,12 +29,7 @@ Page({
|
||||
loadCard() {
|
||||
api
|
||||
.userCard(this.data.uid)
|
||||
.then((card) =>
|
||||
this.setData({
|
||||
card: { ...card, initial: (card.nickname || '?').slice(0, 1) },
|
||||
loading: false,
|
||||
}),
|
||||
)
|
||||
.then((card) => this.setData({ card, loading: false }))
|
||||
.catch((e) => {
|
||||
this.setData({ loading: false });
|
||||
toastErr(e);
|
||||
@@ -76,9 +72,20 @@ Page({
|
||||
});
|
||||
},
|
||||
goRelations(e) {
|
||||
const kind = e.currentTarget.dataset.kind;
|
||||
const kind = e.detail.kind;
|
||||
wx.navigateTo({ url: `/pages/relations/relations?id=${this.data.uid}&kind=${kind}` });
|
||||
},
|
||||
// 自己看自己的主页时,头部按钮是「装扮」而不是「关注」
|
||||
goDecorate() {
|
||||
wx.navigateTo({ url: '/pages/decorate/decorate' });
|
||||
},
|
||||
// 别人的宠物只是名片,点了不跳;自己的才切过去看
|
||||
goPet(e) {
|
||||
const c = this.data.card;
|
||||
if (!c || !c.is_self) return;
|
||||
store.switchPet(e.detail.id);
|
||||
wx.switchTab({ url: '/pages/home/home' });
|
||||
},
|
||||
previewImage(e) {
|
||||
const { urls, cur } = e.currentTarget.dataset;
|
||||
if (urls && urls.length) wx.previewImage({ urls, current: cur });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/nav-bar",
|
||||
"pt-icon": "/components/pt-icon/index"
|
||||
"pt-icon": "/components/pt-icon/index",
|
||||
"profile-head": "/components/profile-head/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,27 +9,9 @@ module.exports.isUrl = function (s) { return s && s.indexOf('http') === 0; }
|
||||
<view wx:if="{{loading}}" class="empty lg">加载中…</view>
|
||||
|
||||
<block wx:elif="{{card}}">
|
||||
<view class="card u-head">
|
||||
<view class="u-av">
|
||||
<image wx:if="{{card.avatar_url}}" class="u-av-img" src="{{card.avatar_url}}" mode="aspectFill"></image>
|
||||
<block wx:else>{{card.initial}}</block>
|
||||
</view>
|
||||
<view class="u-name">{{card.nickname}}<text wx:if="{{card.is_bot}}" class="ai-tag">AI</text></view>
|
||||
|
||||
<view class="u-stats">
|
||||
<view class="u-stat"><text class="u-n">{{card.post_count}}</text><text class="u-l">帖子</text></view>
|
||||
<view class="u-stat" data-kind="following" bindtap="goRelations">
|
||||
<text class="u-n">{{card.following_count}}</text><text class="u-l">关注</text>
|
||||
</view>
|
||||
<view class="u-stat" data-kind="followers" bindtap="goRelations">
|
||||
<text class="u-n">{{card.follower_count}}</text><text class="u-l">粉丝</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!card.is_self}}" class="btn {{card.followed ? 'btn-ghost' : 'btn-dark'}} btn-block" bindtap="toggleFollow">
|
||||
<pt-icon wx:if="{{!card.followed}}" name="plus" size="{{28}}"></pt-icon>{{card.followed ? '已关注' : '关注 TA'}}
|
||||
</view>
|
||||
</view>
|
||||
<profile-head card="{{card}}" bind:follow="toggleFollow"
|
||||
bind:relations="goRelations" bind:decorate="goDecorate"
|
||||
bind:pet="goPet"></profile-head>
|
||||
|
||||
<view wx:for="{{posts}}" wx:key="id" class="post-card">
|
||||
<view class="post-head">
|
||||
|
||||
@@ -1,12 +1,2 @@
|
||||
.u-head{text-align:center;padding:var(--sp-6) var(--sp-5)}
|
||||
.u-av{
|
||||
width:140rpx;height:140rpx;border-radius:50%;margin:0 auto var(--sp-3);overflow:hidden;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
background:var(--primary-soft);color:var(--primary-ink);font-size:56rpx;font-weight:var(--fw-b);
|
||||
}
|
||||
.u-av-img{width:100%;height:100%}
|
||||
.u-name{font-size:var(--fs-lg);font-weight:var(--fw-b)}
|
||||
.u-stats{display:flex;justify-content:center;gap:var(--sp-6);margin:var(--sp-4) 0}
|
||||
.u-stat{display:flex;flex-direction:column;align-items:center;gap:4rpx}
|
||||
.u-n{font-size:var(--fs-lg);font-weight:var(--fw-b)}
|
||||
.u-l{font-size:var(--fs-cap);color:var(--muted)}
|
||||
/* 头部整块(头像/昵称/签名/计数/养宠数据/宠物墙)已抽成 components/profile-head,
|
||||
帖子卡走 app.wxss 的 .post-card。这里没有页面独有样式了。 */
|
||||
|
||||
@@ -46,6 +46,7 @@ const api = {
|
||||
|
||||
// 用户
|
||||
userSummary: () => request({ url: '/api/user/summary' }),
|
||||
updateDecoration: (body) => request({ url: '/api/user/decoration', method: 'PUT', data: body }),
|
||||
|
||||
// 宠物
|
||||
getPets: () => request({ url: '/api/pets' }),
|
||||
|
||||
Reference in New Issue
Block a user