v26110461 孕周专属福利资料改 3 兜豆兑换:详情页加「这份资料里有什么」、兑换后弹网盘地址可复制;去掉顾问二维码入口;修详情页正文被接口清空
This commit is contained in:
132
js/app.js
132
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26110460';
|
||||
var VER = '26110461';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -2729,6 +2729,41 @@
|
||||
}
|
||||
};
|
||||
const RES_ORDER = ['book', 'food', 'music', 'report', 'sport', 'diet', 'sleep', 'med'];
|
||||
/* 2026-11-05 用户:福利资料改「3 兜豆兑换 → 立刻弹网盘地址」,详情页要说清「这份资料里有什么」。
|
||||
PACK = 文件形式 + 内容清单(AI 助手起草,用户待核对;网盘里实际文件变了要同步改这里)。
|
||||
价格以接口 claim-status.price 为准,RES_PRICE 只是没拉到时的默认值。 */
|
||||
let RES_PRICE = 3;
|
||||
const PACK = {
|
||||
book: { fmt: ['PDF · 18 页', '可打印'], items: ['糖耐检查流程图:3 次抽血的时间点一张图看懂', '检查前 3 天饮食清单(每天碳水够 150g 的吃法)', '检查当天携带物品清单', '结果对照表:空腹 / 1 小时 / 2 小时三个数怎么看', '万一超标:一周控糖食谱示例', '妈妈们最常问的 12 个问题'] },
|
||||
food: { fmt: ['PDF · 86 页', '按孕周分章'], items: ['孕早 / 中 / 晚期分章节,每章先讲这阶段缺什么', '40 周每周一日三餐参考', '钙、铁、DHA、叶酸常见食物含量表', '孕期要少碰的食物清单', '加餐搭配 30 例'] },
|
||||
music: { fmt: ['MP3 · 30 首', '共 96 分钟', '附听法说明'], items: ['古典小品 10 首', '轻钢琴 10 首', '自然音 10 首(雨声、溪流、鸟鸣)', '每日播放安排表:什么时候听、听多久', '音量和距离的注意事项'] },
|
||||
report: { fmt: ['PDF · 6 页', '附可打印表格'], items: ['全孕期 13 次产检:时间 + 检查项目一览', '每次产检要带的东西', '「问医生」问题清单模板', '产检报告存放登记表'] },
|
||||
sport: { fmt: ['PDF · 22 页', '动作图解'], items: ['快走、游泳、孕妇瑜伽、固定单车的要点', '12 个孕期瑜伽动作分步图解', '运动强度怎么把握', '必须避开的动作清单', '出现哪些情况要立刻停下'] },
|
||||
diet: { fmt: ['PDF · 16 页', '附体重记录表'], items: ['孕前 BMI 对应的增重范围对照表', '每周体重记录表(可打印)', '正确称重的方法', '长得太快 / 太慢怎么调整'] },
|
||||
sleep: { fmt: ['PDF · 14 页', '附助眠音频 5 段'], items: ['孕期失眠的 7 个改善方法', '左侧卧 + 垫枕头的睡姿图解', '睡前放松呼吸练习(音频)', '一周睡眠记录表'] },
|
||||
med: { fmt: ['PDF · 12 页', '附自评量表'], items: ['情绪自评量表 + 计分说明', '需要警惕的信号清单', '情绪日记模板', '可以求助的渠道'] }
|
||||
};
|
||||
/* 已兑换的 key(来自 claim-status.resGot);兑换成功后本地追加 */
|
||||
const resGot = [];
|
||||
function loadResGot() {
|
||||
const oid = hashParam('openid');
|
||||
if (!oid) return Promise.resolve();
|
||||
return fetch('/api/mp/public/mama-box/resources/claim-status?openid=' + encodeURIComponent(oid))
|
||||
.then(r => r.ok ? r.json() : null).then(j => {
|
||||
const x = j && j.result;
|
||||
if (!x) return;
|
||||
if (typeof x.price === 'number' && x.price > 0) RES_PRICE = x.price;
|
||||
resGot.length = 0;
|
||||
(x.resGot || []).forEach(k => resGot.push(k));
|
||||
syncResPrice();
|
||||
}).catch(() => {});
|
||||
}
|
||||
/* 首页书架 / 列表 / 详情页上所有「几兜豆」文案 */
|
||||
function syncResPrice() {
|
||||
$$('.shelf.gift2 .sb-row .pill-free').forEach(el => { el.textContent = RES_PRICE + ' 豆兑'; });
|
||||
$$('.res-price-n').forEach(el => { el.textContent = RES_PRICE; });
|
||||
setRdGet();
|
||||
}
|
||||
|
||||
/* ⚠️ 2026-09-19(9.19 需求 1.2):这里原来有 PAN_DEFAULT / PAN 两张**百度网盘
|
||||
链接表**、panOf()、以及企业微信兑换码 WX_CODE / wxCodeOK()。
|
||||
@@ -2752,7 +2787,7 @@
|
||||
'<span class="ri-row">' +
|
||||
'<em class="ri-cat">' + r.cat + '</em>' +
|
||||
'<em class="ri-ga">' + r.meta[2] + '</em>' +
|
||||
'<em class="pill-free">免费</em>' +
|
||||
'<em class="pill-free">' + (resGot.indexOf(k) >= 0 ? '已兑换' : RES_PRICE + ' 兜豆') + '</em>' +
|
||||
'</span></span></button>';
|
||||
}
|
||||
function bindResCards(root) {
|
||||
@@ -2800,6 +2835,12 @@
|
||||
$('#rdTitle').textContent = r.title;
|
||||
$('#rdDesc').textContent = r.desc;
|
||||
$('#rdMeta').innerHTML = r.meta.map(m => '<span>' + m + '</span>').join('');
|
||||
const pk = PACK[state.resKey];
|
||||
$('#rdPack').innerHTML = pk ? '<h3 class="d-sec">这份资料里有什么</h3>' +
|
||||
'<div class="rp-box"><div class="rp-fmt">' + pk.fmt.map(f => '<span>' + f + '</span>').join('') + '</div>' +
|
||||
'<ul class="rp-list">' + pk.items.map(t => '<li>' + t + '</li>').join('') + '</ul>' +
|
||||
'<p class="rp-how"><b class="res-price-n">' + RES_PRICE + '</b> 兜豆兑换,兑换后马上给你网盘地址和提取码,复制去下载;兑换过的再点也能看,不重复扣豆。</p></div>' +
|
||||
'<h3 class="d-sec">内容抢先看</h3>' : '';
|
||||
$('#rdBody').innerHTML = r.body
|
||||
.map(p => '<h4>' + p[0] + '</h4><p>' + p[1] + '</p>').join('');
|
||||
$('#rdMore').innerHTML = RES_ORDER
|
||||
@@ -2814,7 +2855,7 @@
|
||||
/* 底部左键已从「收藏」改成「资源库」,是个固定文案的导航按钮,不再需要回显。
|
||||
⚠️ 右键「立即领取」原来会按 state.resGot 回显成「再拿一次链接」,
|
||||
9.19 需求取消「已领」概念后改回**固定文案**(点它只是弹顾问二维码)。 */
|
||||
$('#rdGet').textContent = '立即领取';
|
||||
setRdGet();
|
||||
$('#detailRes .detail-body').scrollTop = 0;
|
||||
}
|
||||
$('#resStar').addEventListener('click', function () {
|
||||
@@ -2857,7 +2898,85 @@
|
||||
顾问直接发资料」之后,闸门、兑换码、网盘弹窗整条链路都删了(连同 givePan()、
|
||||
state.wxUnlocked、wxPending),这颗按钮现在只做一件事:弹二维码。
|
||||
不存在「已解锁 / 未解锁」两种起点,所以这里也没有任何状态判断 —— 别再加回来。 */
|
||||
$('#rdGet').addEventListener('click', () => { openWx(); });
|
||||
/* 2026-11-05 用户改:「立即领取」→ 用兜豆兑换,兑换完立刻弹网盘地址(顾问二维码不要了)。
|
||||
后端 POST /resources/{key}/claim:同份同人只扣一次,兑换过的再点直接回链接(spent=0)。 */
|
||||
function setRdGet() {
|
||||
const b = $('#rdGet');
|
||||
if (!b) return;
|
||||
const got = resGot.indexOf(state.resKey) >= 0;
|
||||
b.textContent = got ? '查看网盘地址' : RES_PRICE + ' 兜豆兑换';
|
||||
b.classList.toggle('is-got', got);
|
||||
}
|
||||
let resBusy = false;
|
||||
function redeemRes(key) {
|
||||
const oid = hashParam('openid'), uid = hashParam('unionid');
|
||||
if (!oid) { toast('请从小程序里打开后再兑换'); return; }
|
||||
if (resBusy) return;
|
||||
resBusy = true;
|
||||
const btn = $('#rbOk'); if (btn) btn.textContent = '兑换中…';
|
||||
fetch('/api/mp/public/mama-box/resources/' + key + '/claim?openid=' + encodeURIComponent(oid) +
|
||||
(uid ? '&unionid=' + encodeURIComponent(uid) : ''), { method: 'POST' })
|
||||
.then(r => r.json().catch(() => null))
|
||||
.then(j => {
|
||||
const x = (j && j.result) || {};
|
||||
if (j && j.code === 200 && x.pan && x.pan.url) {
|
||||
if (resGot.indexOf(key) < 0) resGot.push(key);
|
||||
setRdGet();
|
||||
renderResList(state.resCat);
|
||||
showPan(key, x.pan, x.spent > 0 ? x.spent : 0);
|
||||
if (x.spent > 0) loadDou(); // 刷新「我的」页兜豆余额
|
||||
} else if (j && j.code === 409 && x.insufficient) {
|
||||
closeModal();
|
||||
$('#rsShort').textContent = x.short || RES_PRICE;
|
||||
$('#rsBal').textContent = typeof x.balance === 'number' ? x.balance : 0;
|
||||
openModal('modalResShort');
|
||||
} else {
|
||||
closeModal();
|
||||
toast((j && j.message) || '网络不太好,稍后再试');
|
||||
}
|
||||
})
|
||||
.catch(() => { closeModal(); toast('网络不太好,稍后再试'); })
|
||||
.then(() => { resBusy = false; if (btn) btn.textContent = '确认兑换'; });
|
||||
}
|
||||
function showPan(key, pan, spent) {
|
||||
const r = RES[key] || {};
|
||||
$('#rpTitle').textContent = spent ? '兑换成功' : '你的网盘地址';
|
||||
$('#rpLead').textContent = (spent ? '已扣 ' + spent + ' 兜豆,' : '') + '复制下面的地址,打开浏览器或百度网盘 App 就能下载《' + String(r.title || '').replace(/[《》]/g, '') + '》';
|
||||
$('#rpUrl').textContent = pan.url;
|
||||
$('#rpCode').textContent = pan.code || '无';
|
||||
$('#rpCodeRow').classList.toggle('hide', !pan.code);
|
||||
openModal('modalResPan');
|
||||
}
|
||||
function copyText(t) {
|
||||
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(t).catch(() => legacyCopy(t));
|
||||
return Promise.resolve(legacyCopy(t));
|
||||
}
|
||||
function legacyCopy(t) {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = t; ta.setAttribute('readonly', ''); ta.style.cssText = 'position:fixed;top:-99px;opacity:0';
|
||||
document.body.appendChild(ta); ta.select(); ta.setSelectionRange(0, t.length);
|
||||
try { document.execCommand('copy'); } catch (e) { /* 复制不了也给提示,用户可长按地址手动复制 */ }
|
||||
ta.remove();
|
||||
}
|
||||
$('#rpCopy').addEventListener('click', () => {
|
||||
const code = $('#rpCodeRow').classList.contains('hide') ? '' : $('#rpCode').textContent;
|
||||
copyText($('#rpUrl').textContent + (code ? ' 提取码: ' + code : '')).then(() => toast('已复制,去浏览器或网盘 App 粘贴打开'));
|
||||
haptic();
|
||||
});
|
||||
$('#rdGet').addEventListener('click', () => {
|
||||
const key = state.resKey;
|
||||
if (!hashParam('openid')) { toast('请从小程序里打开后再兑换'); return; }
|
||||
if (resGot.indexOf(key) >= 0) { redeemRes(key); return; } // 兑换过:直接拿链接,不扣豆
|
||||
const r = RES[key] || {};
|
||||
$('#rbName').textContent = '《' + String(r.title || '').replace(/[《》]/g, '') + '》';
|
||||
$('#rbPrice').textContent = RES_PRICE;
|
||||
const n = $('#meDouN') && !$('#meDouBal').classList.contains('hide') ? $('#meDouN').textContent : '';
|
||||
$('#rbBal').textContent = n ? '你现在有 ' + n + ' 兜豆' : '';
|
||||
openModal('modalResBuy');
|
||||
});
|
||||
$('#rbOk').addEventListener('click', () => redeemRes(state.resKey));
|
||||
$('#rsGo').addEventListener('click', () => { closeModal(); openExchange(); });
|
||||
loadResGot();
|
||||
|
||||
/* ================= 企业微信顾问弹窗(单态) =================
|
||||
三个入口共用这一个壳:「我的」页服务卡 #meWxCard、资源库顶部试用装 banner #rlGift、
|
||||
@@ -2877,6 +2996,7 @@
|
||||
⚠️ 走 openWx() 而不是挂 data-open="modalWx":那条通用通道直接调 openModal,
|
||||
不经过这里 —— 今天两者等价,但留一个入口,以后要统一加埋点/清理只改一处。 */
|
||||
$('#rlGift').addEventListener('click', () => openWx());
|
||||
$('#rlGift').classList.add('hide'); // 2026-11-05 用户:不保留顾问二维码,资源库顶部这条 banner 一起藏(DOM 留着)
|
||||
|
||||
/* ⚠️ 2026-09-19(9.19 需求):这里原来有一个 syncUnlockCopy(),按「已解锁 / 未解锁」
|
||||
把首页书架与资源库的两句引导语改成两个版本,并给资源库顶部试用装 banner 挂 .hide。
|
||||
@@ -3961,10 +4081,12 @@
|
||||
渲染(用 r.img)无需改动;正文(body)仍用本地(接口未下发,保证详情不空)。 */
|
||||
function __applyResources(items) {
|
||||
if (!items || !items.length) return;
|
||||
/* 2026-11-05 修:原来先清空 RES 再取 old,内置正文 body 全丢了,详情页「内容」一直是空的 —— 先留一份旧表 */
|
||||
var prev = Object.assign({}, RES);
|
||||
Object.keys(RES).forEach(function (k) { delete RES[k]; });
|
||||
var order = [];
|
||||
items.forEach(function (it) {
|
||||
var old = RES[it.key] || { body: [] };
|
||||
var old = prev[it.key] || { body: [] };
|
||||
RES[it.key] = {
|
||||
img: it.imgUrl || old.img || '', cat: it.cat, tint: it.tint,
|
||||
title: it.title, desc: it.desc, meta: it.meta || old.meta || [],
|
||||
|
||||
Reference in New Issue
Block a user