Gate record features and storage entry points with invitation guidance; enforce record access in the backend. Add homepage greetings, right-swipe profile dismissal, and move the smoking switch to settings. Simplify API base configuration and remove the unused legacy tab bar component.
329 lines
10 KiB
JavaScript
329 lines
10 KiB
JavaScript
const { withRecordAccess } = require('../../utils/record-access');
|
|
const { tryLoginRenewal } = require('../../utils/subscribe');
|
|
const { withDataCache } = require('../../utils/page-cache');
|
|
const { getLayout } = require('../../utils/layout');
|
|
// pages/pet/pet.js
|
|
const { api } = require('../../utils/request');
|
|
|
|
const BREED_NAMES = {
|
|
'golden': '金毛',
|
|
'husky': '哈士奇',
|
|
'corgi': '柯基',
|
|
'shiba': '柴犬'
|
|
};
|
|
|
|
const BREED_EMOJIS = {
|
|
'golden': '🐕',
|
|
'husky': '🐺',
|
|
'corgi': '🦊',
|
|
'shiba': '🐶'
|
|
};
|
|
|
|
const STAGE_NAMES = ['', '幼犬期', '少年期', '青年期', '成年期', '终极期'];
|
|
|
|
Page(withRecordAccess(withDataCache({
|
|
onLoginSubscribeTap: function() { return tryLoginRenewal(); },
|
|
onResize: function() { this.setData(getLayout()); },
|
|
data: { loading: true, loadError: false, needsBinding: false,
|
|
hasPet: false,
|
|
pet: null,
|
|
daily: null,
|
|
achievements: [],
|
|
dynamics: [],
|
|
displayDynamics: [],
|
|
equipped: [],
|
|
|
|
// 领养表单
|
|
breedSelect: 'golden',
|
|
genderSelect: 1,
|
|
petName: '',
|
|
adopting: false,
|
|
|
|
// 宠物状态
|
|
dailyStatus: {},
|
|
partnerDaily: {},
|
|
allDone: false,
|
|
expProgress: 0,
|
|
expForNextLevel: 0,
|
|
breedName: '',
|
|
petEmoji: '',
|
|
stageName: '',
|
|
equippedCollar: false,
|
|
|
|
// 最近成就
|
|
recentAchievements: [],
|
|
achievementCount: 0,
|
|
|
|
// 改名
|
|
showRename: false,
|
|
renameValue: '',
|
|
renaming: false,
|
|
// 动态折叠(默认收起,收起时显示一条)
|
|
showAllDynamics: false
|
|
},
|
|
|
|
onLoad: function() {
|
|
this.setData(getLayout());
|
|
this.loadPetInfo();
|
|
},
|
|
|
|
onShow: function() {
|
|
this.loadPetInfo();
|
|
},
|
|
|
|
loadPetInfo: async function() {
|
|
if (this._pageLoading) return;
|
|
this._pageLoading = true;
|
|
this.setData({loading: !this._hasCachedData, loadError:false});
|
|
try {
|
|
const relationship = await api.coupleInfo();
|
|
if (relationship.not_bound) {
|
|
this.setData({needsBinding:true,hasPet:false,pet:null,daily:null,achievements:[],dynamics:[],displayDynamics:[],showRename:false});
|
|
return;
|
|
}
|
|
this.setData({needsBinding:false});
|
|
const res = await api.petInfo();
|
|
if (res.has_pet && res.pet) {
|
|
const pet = res.pet;
|
|
const dailyStatus = {};
|
|
const partnerDaily = {};
|
|
if (res.daily) {
|
|
const d = res.daily;
|
|
const suffix = res.is_user1 ? 'u1' : 'u2';
|
|
const partnerSuffix = res.is_user1 ? 'u2' : 'u1';
|
|
['feed', 'water', 'walk', 'bath', 'sleep', 'play'].forEach(a => {
|
|
dailyStatus[a] = d[a + '_' + suffix] == 1;
|
|
partnerDaily[a] = d[a + '_' + partnerSuffix] == 1;
|
|
});
|
|
}
|
|
// 获取已装备物品
|
|
const equipped = res.equipped || [];
|
|
const equippedCollar = equipped.some(e => e.item_type === 'collar');
|
|
// 计算最近成就
|
|
const allAch = this.getAllAchievementDefs();
|
|
const recentAch = allAch.filter(a => res.achievements.indexOf(a.key) >= 0).slice(0, 5);
|
|
|
|
// 计算领养天数
|
|
var adoptDays = 0;
|
|
if (pet.adopted_at) {
|
|
var adoptDate = new Date(pet.adopted_at.replace(/-/g, '/'));
|
|
if (!isNaN(adoptDate.getTime())) {
|
|
adoptDays = Math.floor((Date.now() - adoptDate.getTime()) / 86400000);
|
|
}
|
|
}
|
|
var pageData = {
|
|
hasPet: true,
|
|
pet: pet,
|
|
daily: res.daily,
|
|
achievements: res.achievements,
|
|
dynamics: this._formatDynamics(res.dynamics || []),
|
|
showAllDynamics: false,
|
|
equipped: equipped,
|
|
dailyStatus: dailyStatus,
|
|
partnerDaily: partnerDaily,
|
|
allDone: res.daily ? res.daily.all_done == 1 : false,
|
|
expProgress: res.exp_progress || 0,
|
|
expForNextLevel: res.exp_for_next_level || 0,
|
|
breedName: BREED_NAMES[pet.breed] || '金毛',
|
|
petEmoji: BREED_EMOJIS[pet.breed] || '🐕',
|
|
stageName: STAGE_NAMES[pet.stage] || '幼犬期',
|
|
equippedCollar: equippedCollar,
|
|
recentAchievements: recentAch,
|
|
achievementCount: (res.achievements || []).length,
|
|
adoptDays: adoptDays
|
|
};
|
|
|
|
this.setData(pageData);
|
|
this._buildDisplayDynamics();
|
|
|
|
} else {
|
|
this.setData({ hasPet: false, pet:null, daily:null, achievements:[], dynamics:[], displayDynamics:[] });
|
|
}
|
|
} catch (e) {
|
|
if (e && e.error === '请先绑定情侣') {
|
|
this.setData({needsBinding:true,hasPet:false,pet:null,showRename:false});
|
|
} else {
|
|
this.setData({loadError:true});
|
|
console.error(e);
|
|
}
|
|
}
|
|
finally { this._pageLoading = false; this.setData({loading:false}); }
|
|
},
|
|
|
|
// 格式化动态数据:时间转为友好显示,昵称取首字
|
|
_formatDynamics: function(list) {
|
|
var now = Date.now();
|
|
return list.map(function(d) {
|
|
var timeText = '';
|
|
if (d.created_at) {
|
|
var t = new Date(String(d.created_at).replace(/-/g, '/').replace('T', ' '));
|
|
if (!isNaN(t.getTime())) {
|
|
var diff = Math.floor((now - t.getTime()) / 1000);
|
|
if (diff < 60) timeText = '刚刚';
|
|
else if (diff < 3600) timeText = Math.floor(diff / 60) + '分钟前';
|
|
else if (diff < 86400) timeText = Math.floor(diff / 3600) + '小时前';
|
|
else if (diff < 172800) timeText = '昨天';
|
|
else timeText = (t.getMonth() + 1) + '月' + t.getDate() + '日';
|
|
}
|
|
}
|
|
var initial = d.user_id == 0 ? '🐾' : (d.nickname ? String(d.nickname).trim()[0] : '?');
|
|
return {
|
|
user_id: d.user_id,
|
|
nickname: d.nickname,
|
|
avatar: d.avatar || '',
|
|
action: d.action,
|
|
message: d.message,
|
|
time_text: timeText,
|
|
initial: initial
|
|
};
|
|
});
|
|
},
|
|
|
|
getAllAchievementDefs: function() {
|
|
return [
|
|
{ key: 'first_adopt', name: '初次相遇', icon: '🐣' },
|
|
{ key: 'level_5', name: '成长之路', icon: '⭐' },
|
|
{ key: 'level_10', name: '小有成就', icon: '🌟' },
|
|
{ key: 'level_20', name: '资深铲屎官', icon: '👑' },
|
|
{ key: 'level_30', name: '终极陪伴', icon: '💎' },
|
|
{ key: 'stage_2', name: '少年初长成', icon: '🐕' },
|
|
{ key: 'stage_3', name: '青春活力', icon: '🦮' },
|
|
{ key: 'stage_4', name: '成熟稳重', icon: '🐩' },
|
|
{ key: 'stage_5', name: '终极形态', icon: '🦁' },
|
|
{ key: 'all_done_7', name: '一周全勤', icon: '📅' },
|
|
{ key: 'all_done_30', name: '月度之星', icon: '🏆' },
|
|
{ key: 'feed_100', name: '大胃王', icon: '🍖' },
|
|
{ key: 'walk_50', name: '散步达人', icon: '🚶' },
|
|
{ key: 'bath_30', name: '爱干净', icon: '🛁' },
|
|
{ key: 'play_50', name: '玩伴', icon: '🎾' },
|
|
{ key: 'happiness_100', name: '快乐满分', icon: '😊' },
|
|
{ key: 'rename_first', name: '取名大师', icon: '✏️' },
|
|
{ key: 'adopt_7days', name: '一周陪伴', icon: '📆' },
|
|
{ key: 'adopt_30days', name: '一月相伴', icon: '🗓️' },
|
|
{ key: 'adopt_100days', name: '百日纪念', icon: '💯' }
|
|
];
|
|
},
|
|
|
|
// 选择品种
|
|
selectBreed: function(e) {
|
|
this.setData({ breedSelect: e.currentTarget.dataset.breed });
|
|
},
|
|
|
|
// 选择性别
|
|
selectGender: function(e) {
|
|
this.setData({ genderSelect: parseInt(e.currentTarget.dataset.gender) });
|
|
},
|
|
|
|
// 输入名字
|
|
onNameInput: function(e) {
|
|
this.setData({ petName: e.detail.value });
|
|
},
|
|
|
|
// 领养
|
|
doAdopt: async function() {
|
|
var name = this.data.petName.trim();
|
|
if (!name) {
|
|
wx.showToast({ title: '请给宠物取个名字', icon: 'none' });
|
|
return;
|
|
}
|
|
this.setData({ adopting: true });
|
|
try {
|
|
const res = await api.petAdopt({
|
|
name: name,
|
|
breed: this.data.breedSelect,
|
|
gender: this.data.genderSelect
|
|
});
|
|
wx.showToast({ title: '🎉 领养成功!', icon: 'success' });
|
|
this.loadPetInfo();
|
|
} catch (e) {
|
|
wx.showToast({ title: e.error || '领养失败', icon: 'none' });
|
|
} finally {
|
|
this.setData({ adopting: false });
|
|
}
|
|
},
|
|
|
|
// 执行日常任务
|
|
doDailyAction: async function(e) {
|
|
var action = e.currentTarget.dataset.action;
|
|
if (this.data.dailyStatus[action]) {
|
|
wx.showToast({ title: '已完成', icon: 'none' });
|
|
return;
|
|
}
|
|
try {
|
|
const res = await api.petDailyAction({ action: action });
|
|
wx.showToast({ title: '✅ +' + (res.exp_gain || 0) + '经验', icon: 'success', duration: 1500 });
|
|
this.loadPetInfo();
|
|
} catch (e) {
|
|
if (e.already_done) {
|
|
wx.showToast({ title: '今天已完成啦', icon: 'none' });
|
|
this.loadPetInfo();
|
|
} else {
|
|
wx.showToast({ title: e.error || '操作失败', icon: 'none' });
|
|
}
|
|
}
|
|
},
|
|
|
|
// 改名
|
|
showRenameModal: function() {
|
|
this.setData({
|
|
showRename: true,
|
|
renameValue: this.data.pet ? this.data.pet.name : ''
|
|
});
|
|
},
|
|
hideRenameModal: function() {
|
|
this.setData({ showRename: false });
|
|
},
|
|
onRenameInput: function(e) {
|
|
this.setData({ renameValue: e.detail.value });
|
|
},
|
|
doRename: async function() {
|
|
var name = this.data.renameValue.trim();
|
|
if (!name) {
|
|
wx.showToast({ title: '请输入名字', icon: 'none' });
|
|
return;
|
|
}
|
|
this.setData({ renaming: true });
|
|
try {
|
|
await api.petRename({ name: name });
|
|
wx.showToast({ title: '改名成功 ✏️', icon: 'success' });
|
|
this.setData({ showRename: false });
|
|
this.loadPetInfo();
|
|
} catch (e) {
|
|
wx.showToast({ title: e.error || '改名失败', icon: 'none' });
|
|
} finally {
|
|
this.setData({ renaming: false });
|
|
}
|
|
},
|
|
|
|
// 展开/收起动态
|
|
toggleDynamics: function() {
|
|
this.setData({ showAllDynamics: !this.data.showAllDynamics }, () => {
|
|
this._buildDisplayDynamics();
|
|
});
|
|
},
|
|
|
|
// 根据折叠状态计算要展示的动态列表(收起时只显示一条)
|
|
_buildDisplayDynamics: function() {
|
|
var list = this.data.dynamics || [];
|
|
var show = this.data.showAllDynamics;
|
|
this.setData({ displayDynamics: show ? list : list.slice(0, 1) });
|
|
},
|
|
|
|
// 跳转到成就页
|
|
goToAchievements: function() {
|
|
wx.navigateTo({ url: '/pages/pet/achievements' });
|
|
},
|
|
|
|
bindPartner: function() {
|
|
getApp().globalData.openProfileBind = true;
|
|
wx.switchTab({url:'/pages/profile/profile'});
|
|
},
|
|
|
|
// 返回
|
|
goBack: function() {
|
|
wx.navigateBack();
|
|
},
|
|
|
|
stopPropagation: function() {}
|
|
}, {"loadPetInfo": ["coupleInfo", "petInfo"]})));
|