fix: 修复Tool模块相关问题 - 优化签到、社区、食物和AI服务功能
This commit is contained in:
@@ -113,7 +113,13 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
todaySigned: false,
|
||||
currentPoints: 522,
|
||||
/** 防止 loadCheckinData 与打卡后刷新并发时,旧 user/info 响应覆盖新积分 */
|
||||
_userInfoFetchGen: 0,
|
||||
/** 打卡流程进行中:禁止 onShow 触发的 loadCheckinData 用「打卡前」发出的 user/info 写 currentPoints */
|
||||
_suppressStalePointsLoad: false,
|
||||
/** 防止连点导致多次打卡请求与积分展示乱序 */
|
||||
_checkinSubmitting: false,
|
||||
currentPoints: 0,
|
||||
streakDays: [
|
||||
{ day: 1, completed: false, special: false },
|
||||
{ day: 2, completed: false, special: false },
|
||||
@@ -183,17 +189,25 @@ export default {
|
||||
this.loadCheckinData();
|
||||
},
|
||||
onShow() {
|
||||
// 页面显示时刷新数据
|
||||
// 页面显示时刷新数据(打卡请求进行中时跳过,避免与 handleCheckin 内刷新竞态)
|
||||
if (this._checkinSubmitting) {
|
||||
return;
|
||||
}
|
||||
this.loadCheckinData();
|
||||
},
|
||||
methods: {
|
||||
async loadCheckinData() {
|
||||
async loadCheckinData(options = {}) {
|
||||
const includePointsRefresh = !!options.includePointsRefresh;
|
||||
const skipPoints = includePointsRefresh
|
||||
? false
|
||||
: (!!options.skipPoints || this._suppressStalePointsLoad);
|
||||
const pointsGen = skipPoints ? null : ++this._userInfoFetchGen;
|
||||
try {
|
||||
const { getUserPoints, getCheckinStreak, getCheckinTasks } = await import('@/api/tool.js');
|
||||
const { getSignGet } = await import('@/api/user.js');
|
||||
const { getCheckinStreak, getCheckinTasks } = await import('@/api/tool.js');
|
||||
const { getSignGet, getFrontUserInfo } = await import('@/api/user.js');
|
||||
|
||||
const [pointsRes, streakRes, tasksRes, signRes] = await Promise.all([
|
||||
getUserPoints().catch(() => ({ data: { points: 0 } })),
|
||||
const [userInfoRes, streakRes, tasksRes, signRes] = await Promise.all([
|
||||
skipPoints ? Promise.resolve(null) : getFrontUserInfo().catch(() => null),
|
||||
getCheckinStreak().catch(() => ({ data: { streakDays: 7, currentStreak: 0 } })),
|
||||
getCheckinTasks().catch(() => ({ data: { tasks: [] } })),
|
||||
getSignGet().catch(() => ({ data: { today: false } }))
|
||||
@@ -203,8 +217,14 @@ export default {
|
||||
this.todaySigned = true;
|
||||
}
|
||||
|
||||
if (pointsRes.data) {
|
||||
this.currentPoints = pointsRes.data.totalPoints ?? pointsRes.data.points ?? 0;
|
||||
// BUG-001-A:打卡请求进行中时,勿用并行的 user/info 回写积分(避免先显示旧分再跳变);显式 includePointsRefresh 时仍刷新
|
||||
const allowPointsFromThisLoad =
|
||||
!this._checkinSubmitting || includePointsRefresh;
|
||||
if (!skipPoints && userInfoRes && pointsGen === this._userInfoFetchGen && allowPointsFromThisLoad) {
|
||||
const p = this._parsePointsFromUserInfo(userInfoRes);
|
||||
if (p != null && !Number.isNaN(Number(p))) {
|
||||
this.currentPoints = Number(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (streakRes.data) {
|
||||
@@ -235,68 +255,107 @@ export default {
|
||||
url: '/pages/tool/points-rules'
|
||||
})
|
||||
},
|
||||
/**
|
||||
* BUG-001-A:打卡 API 成功前不得修改 currentPoints(禁止本地 +30 或与跳转前的乐观更新)。
|
||||
* BUG-001-B:仅 GET /api/front/user/info(getFrontUserInfo)解析账户 integral;打卡接口返回的 integral 为当日奖励分值,绝非总积分。
|
||||
*/
|
||||
async handleCheckin() {
|
||||
if (this._checkinSubmitting) {
|
||||
return;
|
||||
}
|
||||
if (this.todaySigned) {
|
||||
uni.showToast({ title: '今日已打卡', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this._checkinSubmitting = true;
|
||||
this._suppressStalePointsLoad = true;
|
||||
const pointsBeforeCheckin = Number(this.currentPoints) || 0;
|
||||
const { userCheckin, userCheckinDaily, getFrontUserInfo } = await import('@/api/user.js');
|
||||
uni.showLoading({ title: '打卡中...', mask: true });
|
||||
try {
|
||||
const { setSignIntegral, getFrontUserInfo, getUserInfo } = await import('@/api/user.js');
|
||||
const { getUserPoints } = await import('@/api/tool.js');
|
||||
|
||||
// 子问题 A:在 API 返回成功前不得修改 currentPoints,避免打卡前积分提前跳变(禁止前端本地 +30 等)
|
||||
// 打卡接口:GET /api/front/user/sign/integral(setSignIntegral),即本项目的签到/打卡接口
|
||||
await setSignIntegral();
|
||||
|
||||
// 子问题 B:打卡成功后必须用服务端数据更新积分。发 GET /api/front/user/info 刷新用户积分,禁止硬编码 +30
|
||||
const serverPoints = await this._fetchServerPoints(getFrontUserInfo, getUserInfo, getUserPoints);
|
||||
if (serverPoints != null) {
|
||||
this.currentPoints = Number(serverPoints);
|
||||
// 1) /api/front/user/checkin,失败时回退 /api/front/user/sign/integral;二者均不读取 body 更新积分
|
||||
await this._requestCheckin(userCheckin, userCheckinDaily);
|
||||
// 2) 成功后必须 GET user/info,用服务端账户积分更新展示
|
||||
let refreshed = await this._refreshCurrentPointsFromUserInfo(getFrontUserInfo).catch((err) => {
|
||||
console.warn('打卡后刷新积分失败:', err);
|
||||
return false;
|
||||
});
|
||||
if (refreshed && Number(this.currentPoints) === pointsBeforeCheckin) {
|
||||
await new Promise((r) => setTimeout(r, 250));
|
||||
refreshed =
|
||||
(await this._refreshCurrentPointsFromUserInfo(getFrontUserInfo).catch(() => false)) || refreshed;
|
||||
}
|
||||
if (!refreshed) {
|
||||
await this.loadCheckinData({ includePointsRefresh: true });
|
||||
}
|
||||
|
||||
this.todaySigned = true;
|
||||
await this.loadCheckinData({ skipPoints: true });
|
||||
uni.navigateTo({
|
||||
url: '/pages/tool/checkin-publish'
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = (typeof e === 'string' ? e : (e && (e.message || e.msg))) || '打卡失败';
|
||||
if (msg.includes('今日已签到') || msg.includes('不可重复')) {
|
||||
this.todaySigned = true;
|
||||
await this.loadCheckinData({ skipPoints: true });
|
||||
} else {
|
||||
await this.loadCheckinData({ includePointsRefresh: true }).catch(() => {});
|
||||
}
|
||||
uni.showToast({ title: msg, icon: 'none' });
|
||||
return;
|
||||
} finally {
|
||||
this._suppressStalePointsLoad = false;
|
||||
this._checkinSubmitting = false;
|
||||
uni.hideLoading();
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/tool/checkin-publish'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 从服务端拉取积分(用于打卡成功后刷新,积分值必须来自服务端,不可前端累加)
|
||||
* 优先 GET /api/front/user/info,失败时回退到 user 或 tool/points/info
|
||||
*/
|
||||
async _fetchServerPoints(getFrontUserInfo, getUserInfo, getUserPoints) {
|
||||
/** 优先 GET user/checkin,路由类失败时回退 user/sign/integral;返回值勿用于 currentPoints(data 为签到档位配置,integral 为奖励分非余额)。 */
|
||||
async _requestCheckin(userCheckin, userCheckinDaily) {
|
||||
try {
|
||||
const userRes = await getFrontUserInfo();
|
||||
if (userRes && userRes.data) {
|
||||
const d = userRes.data;
|
||||
const p = d.integral ?? d.points ?? d.totalPoints ?? (d.user && (d.user.integral ?? d.user.points ?? d.user.totalPoints));
|
||||
if (p != null) return p;
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
const userRes = await getUserInfo();
|
||||
if (userRes && userRes.data) {
|
||||
const d = userRes.data;
|
||||
const p = d.integral ?? d.points ?? d.totalPoints ?? (d.user && (d.user.integral ?? d.user.points ?? d.user.totalPoints));
|
||||
if (p != null) return p;
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
const pointsRes = await getUserPoints();
|
||||
if (pointsRes && pointsRes.data) {
|
||||
const d = pointsRes.data;
|
||||
const p = d.totalPoints ?? d.points ?? d.availablePoints;
|
||||
if (p != null) return p;
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
await userCheckin();
|
||||
} catch (err) {
|
||||
const msg = String((typeof err === 'string' ? err : (err && (err.message || err.msg))) || '');
|
||||
const m = msg.toLowerCase();
|
||||
// CRMEB 前端对 code=404 常 reject 文案「没有找到相关数据」,不含数字 404,须一并识别才能回退到 sign/integral
|
||||
const needFallback =
|
||||
m.includes('404') ||
|
||||
m.includes('405') ||
|
||||
m.includes('not found') ||
|
||||
m.includes('no static resource') ||
|
||||
msg.includes('没有找到相关数据') ||
|
||||
msg.includes('找不到');
|
||||
if (!needFallback) throw err;
|
||||
await userCheckinDaily();
|
||||
}
|
||||
},
|
||||
async _refreshCurrentPointsFromUserInfo(getFrontUserInfoFn) {
|
||||
const gen = ++this._userInfoFetchGen;
|
||||
const getInfo =
|
||||
typeof getFrontUserInfoFn === 'function'
|
||||
? getFrontUserInfoFn
|
||||
: (await import('@/api/user.js')).getFrontUserInfo;
|
||||
const infoRes = await getInfo({ _: Date.now() });
|
||||
if (gen !== this._userInfoFetchGen) {
|
||||
return false;
|
||||
}
|
||||
const serverPoints = this._parsePointsFromUserInfo(infoRes);
|
||||
if (serverPoints != null && !Number.isNaN(Number(serverPoints))) {
|
||||
this.currentPoints = Number(serverPoints);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
_parsePointsFromUserInfo(infoRes) {
|
||||
if (!infoRes) return null;
|
||||
const payload = infoRes.data;
|
||||
const d = (payload && payload.data != null && typeof payload === 'object') ? payload.data : payload;
|
||||
if (d == null || typeof d !== 'object') return null;
|
||||
// 勿把打卡/签到接口的 SystemGroupDataSignConfigVo(含 day + integral 奖励)当成个人中心 integral
|
||||
if (d.day != null && d.nickname == null && d.nowMoney == null) {
|
||||
return null;
|
||||
}
|
||||
const v = d.integral ?? d.points ?? d.totalPoints ?? (d.user && (d.user.integral ?? d.user.points ?? d.user.totalPoints));
|
||||
return v != null && v !== '' ? v : null;
|
||||
},
|
||||
getTodayIndex() {
|
||||
// 根据连续打卡数据计算当前是第几天
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user