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.
96 lines
8.4 KiB
JavaScript
96 lines
8.4 KiB
JavaScript
const { mergeUnique } = require('../../utils/list-pagination');
|
|
const { api, getImageUrl } = require('../../utils/request');
|
|
const cache = require('../../utils/data-cache');
|
|
const titles = {consume:'抽一支 · 打卡',purchase:'买烟',receive:'别人递烟',receive_smoke:'收到并抽一支',adjust:'调整库存',give:'递给别人一支',undo:'撤销记录'};
|
|
Component({
|
|
properties: { detailed: Boolean },
|
|
data: { loading:true, error:false, info:null, saving:false, sheet:false, mode:'purchase', quantity:'1', packSize:'20', amount:'', note:'', viewUserId:"", lastLabel:'还没有记录', daily:[], logs:[], periodStats:[], logsPage:1, logsTotal:0, logsHasMore:false, logsLoadingMore:false, logsMoreError:false, ownerName:'', ownerAvatar:'', ownerInitial:'我' },
|
|
lifetimes: { attached(){this.load();} },
|
|
pageLifetimes: { show(){this.load();} },
|
|
methods: {
|
|
openDisplaySettings(){wx.navigateTo({url:'/pages/settings/settings'});},
|
|
async load(force) {
|
|
if(this._loading) return this._loading;
|
|
if(force)cache.invalidate(['smokingInfo']);
|
|
this.setData({error:false});
|
|
const target = this.data.viewUserId;
|
|
this._loading=Promise.all([api.smokingInfo(target), api.coupleInfo().catch(()=>null)]).then(([info,profile])=>{if(target===this.data.viewUserId){this._profile=profile;this.applyInfo(info);}}).catch(()=>this.setData({error:true})).finally(()=>{this._loading=null;if(target===this.data.viewUserId)this.setData({loading:false});});
|
|
return this._loading;
|
|
},
|
|
applyInfo(info) {
|
|
this._logsGeneration = (this._logsGeneration || 0) + 1;
|
|
const logs=(info.logs||[]).map(log=>({...log,undone:log.undone === true || Number(log.undone) === 1,title:titles[log.kind]||log.kind,change:Number(log.delta)>0?'+'+log.delta:String(log.delta),time:String(log.created_at||'').slice(5,16),money:Number(log.amount_cents)>0?(Number(log.amount_cents)/100).toFixed(2):''}));
|
|
let lastLabel='还没有记录';
|
|
if(info.last_time){const minutes=Math.max(0,Math.floor((Date.now()-new Date(String(info.last_time).replace(/-/g,'/')).getTime())/60000));lastLabel=minutes<60?'距上次 '+minutes+' 分钟':minutes<1440?'距上次 '+Math.floor(minutes/60)+' 小时':'距上次 '+Math.floor(minutes/1440)+' 天';}
|
|
const map={};(info.daily||[]).forEach(row=>{map[row.day]=Number(row.count);});const daily=[];
|
|
for(let ago=6;ago>=0;ago--){const day=new Date();day.setDate(day.getDate()-ago);const key=day.getFullYear()+'-'+String(day.getMonth()+1).padStart(2,'0')+'-'+String(day.getDate()).padStart(2,'0');daily.push({day:key,label:ago===0?'今天':(day.getMonth()+1)+'/'+day.getDate(),count:map[key]||0});}
|
|
const max=Math.max(1,...daily.map(day=>day.count));daily.forEach(day=>day.height=Math.max(4,day.count/max*100));
|
|
const periodStats=[{key:'week',title:'本周',range:'周一至今'},{key:'month',title:'本月',range:'本月1日至今'}].map(period=>({
|
|
...period,
|
|
count:info[period.key+'_count'] == null ? '—' : Number(info[period.key+'_count']),
|
|
money:info[period.key+'_purchase_cents'] == null ? '—' : (Number(info[period.key+'_purchase_cents'])/100).toFixed(2)
|
|
}));
|
|
const profile=this._profile||{};
|
|
const ownerId=String(info.user_id || this.data.viewUserId || wx.getStorageSync('userId') || '');
|
|
const owner=[profile.partner1,profile.partner2].find(user=>user && String(user.id)===ownerId)||{};
|
|
const ownerName=owner.nickname||info.owner_name||owner.username||(info.read_only?'TA':'我');
|
|
const ownerAvatar=getImageUrl(owner.avatar||info.owner_avatar||'');
|
|
this.setData({info,logs,daily,periodStats,logsPage:Number(info.logs_page)||1,logsTotal:Number(info.logs_total)||logs.length,logsHasMore:info.logs_has_more===true,logsLoadingMore:false,logsMoreError:false,lastLabel,ownerName,ownerAvatar,ownerInitial:ownerName.slice(0,1),error:false,loading:false});
|
|
},
|
|
async loadMoreLogs(){
|
|
if(this._loading || this.data.loading || this.data.saving || this.data.logsLoadingMore || !this.data.logsHasMore)return;
|
|
const generation=this._logsGeneration,target=this.data.viewUserId,page=this.data.logsPage+1;
|
|
this.setData({logsLoadingMore:true,logsMoreError:false});
|
|
try{
|
|
const info=await api.smokingInfo(target,{page,page_size:20});
|
|
if(generation!==this._logsGeneration || target!==this.data.viewUserId)return;
|
|
this.applyInfo({...info,logs:mergeUnique(this.data.info.logs||[],info.logs||[])});
|
|
}catch(e){if(generation===this._logsGeneration)this.setData({logsMoreError:true});}
|
|
finally{if(generation===this._logsGeneration)this.setData({logsLoadingMore:false});}
|
|
},
|
|
avatarFailed(){this.setData({ownerAvatar:''});},
|
|
retry(){this.load(true);},
|
|
openDetail(){if(!this.properties.detailed)wx.navigateTo({url:'/pages/smoking-detail/smoking-detail'});},
|
|
async switchOwner(e){if(this.data.saving)return;this._logsGeneration=(this._logsGeneration||0)+1;this.setData({viewUserId:String(e.currentTarget.dataset.user || ""),loading:true,info:null});if(this._loading)await this._loading;await this.load();},
|
|
replenish(){if(this.data.saving)return;this.setData({sheet:true,mode:'purchase',quantity:'1',packSize:'20',amount:'',note:''});},
|
|
mode(e){if(this.data.saving)return;const mode=e.currentTarget.dataset.mode;this.setData({mode,quantity:mode==='adjust'?String(this.data.info.stock):'1',amount:'',note:''});},
|
|
input(e){if(!this.data.saving)this.setData({[e.currentTarget.dataset.field]:e.detail.value});},
|
|
close(){if(!this.data.saving)this.setData({sheet:false});},
|
|
preventMove(){},
|
|
smoke(){return this.act({kind:'consume',quantity:1});},
|
|
receiveSmoke(){return this.act({kind:'receive_smoke',quantity:1});},
|
|
giveSmoke(){
|
|
if(this.data.saving || !this.data.info || this.data.info.read_only || !this.data.info.available)return;
|
|
if(Number(this.data.info.stock)<1)return wx.showToast({title:'库存不足,请先补充库存',icon:'none'});
|
|
return this.act({kind:'give',quantity:1});
|
|
},
|
|
submit() {
|
|
const mode=this.data.mode;
|
|
const quantityText=this.data.quantity.trim(),packText=this.data.packSize.trim();
|
|
if(!/^\d+$/.test(quantityText)||(mode==='purchase'&&!/^\d+$/.test(packText)))return wx.showToast({title:'请填写整数数量',icon:'none'});
|
|
let quantity=Number(quantityText);if(mode==='purchase'){if(Number(packText)<1||Number(packText)>100)return wx.showToast({title:'每包数量应为1到100支',icon:'none'});quantity*=Number(packText);}
|
|
if(quantity>(mode==='adjust'?1000000:10000)||(mode!=='adjust'&&quantity<1))return wx.showToast({title:'数量超出范围',icon:'none'});
|
|
const money=this.data.amount.trim();if(mode==='purchase'&&money&&!/^\d+(\.\d{1,2})?$/.test(money))return wx.showToast({title:'金额最多两位小数',icon:'none'});
|
|
const amount=mode==='purchase'?Math.round(Number(money||0)*100):0;
|
|
if(amount>100000000)return wx.showToast({title:'金额超出范围',icon:'none'});
|
|
return this.act({kind:mode,quantity,amount_cents:amount,note:this.data.note.trim()});
|
|
},
|
|
undo(e){if(this.data.saving)return;wx.showModal({title:'撤销这条记录?',content:'恢复该操作前的库存变化和抽烟次数。赠出一支仅扣库存,撤销会加回一支;“收到并抽一支”会同时撤销收到和抽烟。',success:res=>{if(res.confirm)this.act({kind:'undo',original_id:String(e.currentTarget.dataset.id),quantity:0});}});},
|
|
async act(payload) {
|
|
if(this.data.saving||!this.data.info||this.data.info.read_only||!this.data.info.available)return;
|
|
const signature=JSON.stringify(payload);
|
|
if(!this._pendingAction||this._pendingAction.signature!==signature)this._pendingAction={signature,key:'smoke-'+Date.now()+'-'+Math.random().toString(36).slice(2)};
|
|
this.setData({saving:true});
|
|
try {
|
|
const result=await api.smokingAction({...payload,request_key:this._pendingAction.key});
|
|
if(!result.success)throw result;
|
|
this._pendingAction=null;this.applyInfo(result.info);this.setData({sheet:false});this.triggerEvent('updated');wx.showToast({title:'已记录',icon:'none'});
|
|
} catch(error) {
|
|
// 明确的业务拒绝不会提交;网络不确定时保留请求标识,重试不会重复扣减。
|
|
if(error&&error._statusCode&&error._statusCode!==500)this._pendingAction=null;
|
|
wx.showToast({title:error&&error.error||'请求失败,请重试',icon:'none'});
|
|
} finally {this.setData({saving:false});}
|
|
}
|
|
}
|
|
});
|