feat(bazi): 八字页重构——空态示例排盘+字段术语表+文化声明,生辰档案绑定
- 八字页:未绑定生辰时展示虚构示例排盘(1990-08-08 10:30男)与15条字段说明, 附传统文化声明文案;已绑定时支持多档案切换,排盘增强(十神/藏干/纳音/五行配色/命盘概览) - 个人中心:新增生辰管理卡片,每用户免费绑定3个出生年月;登录改为真实微信登录 - 服务端:新增 BirthProfile 模型与 /api/user/birth-profiles 增删查接口,AutoMigrate 建表 - 前端:新增 birth-profile.js 本地存储优先+登录后云端双向同步 - 修复:个人中心 tabBar 页面入口误用 navigateTo 改为 switchTab
This commit is contained in:
+163
-15
@@ -1,14 +1,28 @@
|
||||
const versionInfo = require('../../utils/version.js');
|
||||
const { request } = require('../../utils/request.js');
|
||||
const birthProfile = require('../../utils/birth-profile.js');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
userInfo: null,
|
||||
loggedIn: false,
|
||||
logging: false,
|
||||
weekStartDay: 0,
|
||||
ziHourSect: 'lateZiNextDay',
|
||||
solarTime: true,
|
||||
showBuddhist: false,
|
||||
highlightLunar: true,
|
||||
bookmarks: [],
|
||||
// 生辰档案
|
||||
profiles: [],
|
||||
activeProfileId: '',
|
||||
maxFree: birthProfile.MAX_PROFILES,
|
||||
canAdd: true,
|
||||
showAddForm: false,
|
||||
formName: '',
|
||||
formGender: 'male',
|
||||
formDate: '1990-01-01',
|
||||
formTime: '12:00',
|
||||
version: versionInfo.version,
|
||||
buildNumber: versionInfo.buildNumber,
|
||||
buildTime: versionInfo.buildTime,
|
||||
@@ -22,6 +36,7 @@ Page({
|
||||
|
||||
onShow() {
|
||||
this.loadBookmarks();
|
||||
this.loadProfiles();
|
||||
},
|
||||
|
||||
loadSettings() {
|
||||
@@ -45,6 +60,146 @@ Page({
|
||||
this.setData({ bookmarks });
|
||||
},
|
||||
|
||||
// ==================== 生辰档案 ====================
|
||||
|
||||
loadProfiles() {
|
||||
const render = () => {
|
||||
const profiles = birthProfile.getProfiles();
|
||||
const active = birthProfile.getActiveProfile();
|
||||
this.setData({
|
||||
profiles,
|
||||
activeProfileId: active ? active.id : '',
|
||||
canAdd: profiles.length < this.data.maxFree
|
||||
});
|
||||
};
|
||||
// 已登录时先从服务端同步,失败静默回退本地
|
||||
if (birthProfile.isLoggedIn()) {
|
||||
birthProfile.syncFromServer().then(render).catch(render);
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
},
|
||||
|
||||
toggleAddForm() {
|
||||
if (!this.data.showAddForm && !this.data.canAdd) {
|
||||
wx.showToast({ title: `最多可免费绑定 ${this.data.maxFree} 个生辰`, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.setData({ showAddForm: !this.data.showAddForm });
|
||||
},
|
||||
|
||||
onFormName(e) {
|
||||
this.setData({ formName: e.detail.value });
|
||||
},
|
||||
|
||||
setFormGender(e) {
|
||||
this.setData({ formGender: e.currentTarget.dataset.g });
|
||||
},
|
||||
|
||||
onFormDate(e) {
|
||||
this.setData({ formDate: e.detail.value });
|
||||
},
|
||||
|
||||
onFormTime(e) {
|
||||
this.setData({ formTime: e.detail.value });
|
||||
},
|
||||
|
||||
submitProfile() {
|
||||
const { formName, formGender, formDate, formTime } = this.data;
|
||||
if (!formDate || !formTime) {
|
||||
wx.showToast({ title: '请选择出生日期和时间', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const result = birthProfile.addProfile({
|
||||
name: formName.trim() || '未命名',
|
||||
gender: formGender,
|
||||
birthday: formDate,
|
||||
birthTime: formTime
|
||||
});
|
||||
if (!result.ok) {
|
||||
wx.showToast({ title: result.msg, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// 已登录时推送到服务端(静默失败,不影响本地)
|
||||
birthProfile.pushProfileToServer(result.profile);
|
||||
this.setData({ showAddForm: false, formName: '' });
|
||||
wx.showToast({ title: '绑定成功', icon: 'success' });
|
||||
this.loadProfiles();
|
||||
},
|
||||
|
||||
removeProfile(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
const profile = this.data.profiles.find(p => p.id === id);
|
||||
if (!profile) return;
|
||||
wx.showModal({
|
||||
title: '删除生辰',
|
||||
content: `确定删除「${profile.name}」的生辰信息吗?`,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
birthProfile.removeProfile(id);
|
||||
birthProfile.removeProfileFromServer(profile);
|
||||
wx.showToast({ title: '已删除', icon: 'success' });
|
||||
this.loadProfiles();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 登录 ====================
|
||||
|
||||
login() {
|
||||
if (this.data.logging) return;
|
||||
this.setData({ logging: true });
|
||||
wx.login({
|
||||
success: (loginRes) => {
|
||||
if (!loginRes.code) {
|
||||
this.setData({ logging: false });
|
||||
wx.showToast({ title: '微信登录失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
request({
|
||||
url: '/api/user/login',
|
||||
method: 'POST',
|
||||
data: { code: loginRes.code }
|
||||
}).then((res) => {
|
||||
if (res && res.code === 0 && res.data && res.data.token) {
|
||||
wx.setStorageSync('token', res.data.token);
|
||||
wx.setStorageSync('lunar-user-info', res.data.user);
|
||||
this.setData({ userInfo: res.data.user, loggedIn: true, logging: false });
|
||||
wx.showToast({ title: '登录成功', icon: 'success' });
|
||||
// 登录后同步服务端生辰档案
|
||||
this.loadProfiles();
|
||||
} else {
|
||||
this.setData({ logging: false });
|
||||
wx.showToast({ title: (res && res.msg) || '登录失败', icon: 'none' });
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setData({ logging: false });
|
||||
wx.showToast({ title: '登录失败,请稍后重试', icon: 'none' });
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
this.setData({ logging: false });
|
||||
wx.showToast({ title: '微信登录失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
logout() {
|
||||
wx.showModal({
|
||||
title: '退出登录',
|
||||
content: '退出后生辰档案仅保存在本机,重新登录可恢复云端档案',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
wx.removeStorageSync('token');
|
||||
wx.removeStorageSync('lunar-user-info');
|
||||
this.setData({ userInfo: null, loggedIn: false });
|
||||
wx.showToast({ title: '已退出', icon: 'success' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 偏好设置 ====================
|
||||
|
||||
setWeekStart(e) {
|
||||
this.setData({ weekStartDay: Number(e.currentTarget.dataset.v) });
|
||||
this.saveSettings();
|
||||
@@ -79,20 +234,13 @@ Page({
|
||||
},
|
||||
|
||||
navTo(e) {
|
||||
wx.navigateTo({ url: e.currentTarget.dataset.url });
|
||||
},
|
||||
|
||||
login() {
|
||||
wx.getUserProfile({
|
||||
desc: '用于完善用户资料',
|
||||
success: (res) => {
|
||||
this.setData({ userInfo: res.userInfo });
|
||||
wx.setStorageSync('lunar-user-info', res.userInfo);
|
||||
wx.showToast({ title: '登录成功', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '登录失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
const url = e.currentTarget.dataset.url;
|
||||
// tabBar 页面必须用 switchTab 打开
|
||||
const tabPages = ['/pages/home/home', '/pages/wish-tree/wish-tree', '/pages/bazi/bazi', '/pages/wiki/wiki', '/pages/settings/settings', '/pages/fortune/fortune'];
|
||||
if (tabPages.includes(url)) {
|
||||
wx.switchTab({ url });
|
||||
} else {
|
||||
wx.navigateTo({ url });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,85 @@
|
||||
<!-- 用户信息 -->
|
||||
<view class="card">
|
||||
<view class="section-title">用户信息</view>
|
||||
<view class="flex items-center gap-2" wx:if="{{userInfo}}">
|
||||
<image src="{{userInfo.avatarUrl}}" class="avatar" mode="aspectFill"/>
|
||||
<view>
|
||||
<text class="text-base font-medium">{{userInfo.nickName}}</text>
|
||||
<text class="text-xs text-muted block">已登录</text>
|
||||
<view class="flex items-center gap-2 justify-between" wx:if="{{loggedIn}}">
|
||||
<view class="flex items-center gap-2">
|
||||
<image src="{{userInfo.avatar}}" class="avatar" mode="aspectFill" wx:if="{{userInfo.avatar}}"/>
|
||||
<view class="avatar avatar-placeholder" wx:else>
|
||||
<text>客</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="text-base font-medium">{{userInfo.nickname || '微信用户'}}</text>
|
||||
<text class="text-xs text-muted block">已登录,档案云端同步</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-ghost text-muted" bindtap="logout">退出</view>
|
||||
</view>
|
||||
<block wx:else>
|
||||
<view class="text-sm text-muted mb-2">登录后可将生辰档案同步到云端,换设备不丢失</view>
|
||||
<view class="btn-primary text-center" bindtap="login">{{logging ? '登录中…' : '微信登录'}}</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 生辰管理 -->
|
||||
<view class="card">
|
||||
<view class="section-title flex justify-between items-center">
|
||||
<text>生辰管理</text>
|
||||
<text class="text-xs text-muted">{{profiles.length}}/{{maxFree}}</text>
|
||||
</view>
|
||||
<view class="text-xs text-muted mb-2">八字排盘基于生辰信息,每位用户可免费绑定 {{maxFree}} 个出生年月(如本人、家人)。</view>
|
||||
|
||||
<view class="profile-list" wx:if="{{profiles.length > 0}}">
|
||||
<view class="profile-item" wx:for="{{profiles}}" wx:key="id">
|
||||
<view class="flex justify-between items-center">
|
||||
<view>
|
||||
<view class="flex items-center gap-1">
|
||||
<text class="text-base font-medium">{{item.name}}</text>
|
||||
<text class="badge badge-lucky" wx:if="{{item.id === activeProfileId}}">使用中</text>
|
||||
</view>
|
||||
<text class="text-xs text-muted block">{{item.gender === 'female' ? '女' : '男'}} · {{item.birthday}} {{item.birthTime}}</text>
|
||||
</view>
|
||||
<view class="btn-ghost text-unlucky" bindtap="removeProfile" data-id="{{item.id}}">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-primary" wx:else bindtap="login">微信登录</view>
|
||||
<view class="text-center text-muted p-2" wx:else>
|
||||
<text>暂未绑定生辰,请到下方添加</text>
|
||||
</view>
|
||||
|
||||
<view class="btn-outline text-center mt-2" bindtap="toggleAddForm" wx:if="{{canAdd}}">
|
||||
{{showAddForm ? '收起' : '+ 添加生辰'}}
|
||||
</view>
|
||||
<view class="text-center text-xs text-muted mt-2" wx:else>
|
||||
免费名额已用完,可删除旧档案后添加
|
||||
</view>
|
||||
|
||||
<!-- 添加表单 -->
|
||||
<block wx:if="{{showAddForm}}">
|
||||
<view class="form-item mt-2">
|
||||
<text class="form-label">备注名(如:本人、爸爸)</text>
|
||||
<input class="form-input" placeholder="未命名" value="{{formName}}" bindinput="onFormName"/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">性别</text>
|
||||
<view class="flex gap-2">
|
||||
<view class="btn-outline {{formGender === 'male' ? 'active' : ''}}" bindtap="setFormGender" data-g="male">男</view>
|
||||
<view class="btn-outline {{formGender === 'female' ? 'active' : ''}}" bindtap="setFormGender" data-g="female">女</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">出生日期(公历)</text>
|
||||
<picker mode="date" value="{{formDate}}" start="1900-01-01" end="2100-12-31" bindchange="onFormDate">
|
||||
<view class="picker-input">{{formDate}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">出生时间</text>
|
||||
<picker mode="time" value="{{formTime}}" bindchange="onFormTime">
|
||||
<view class="picker-input">{{formTime}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="btn-primary text-center" bindtap="submitProfile">保存生辰</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 偏好设置 -->
|
||||
|
||||
@@ -15,6 +15,51 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #C41E3A;
|
||||
color: #FFFFFF;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
/* 生辰档案 */
|
||||
.profile-item {
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #E8D5C4;
|
||||
}
|
||||
|
||||
.profile-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 添加生辰表单 */
|
||||
.form-item {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 24rpx;
|
||||
color: #6B5E53;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding: 20rpx;
|
||||
background: #FFFBF5;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8D5C4;
|
||||
}
|
||||
|
||||
.picker-input {
|
||||
padding: 20rpx;
|
||||
background: #FFFBF5;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8D5C4;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
Reference in New Issue
Block a user