feat(card): 增加有效期展示功能,支持独立成块样式与简介样式配置
This commit is contained in:
@@ -177,12 +177,13 @@
|
|||||||
<text v-if="showQrTip" class="qr-tip" :style="labelStyle" @tap="previewQrImg"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
<text v-if="showQrTip" class="qr-tip" :style="labelStyle" @tap="previewQrImg"><text v-if="qrTipEm" class="qr-tip-em" :style="qrTipEmStyle">{{ qrTipEm }}</text>{{ qrTipLine1 }}</text><text v-if="qrTipSub" class="qr-tip-sub" :style="labelStyle">{{ qrTipSub }}</text><text v-if="qrUrlShow && qrUrlText" class="qr-url" :style="qrUrlStyle">{{ qrUrlText }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 名片标签(2.22) + 来源标注(2.22) -->
|
<!-- 名片标签(2.22) + 来源标注(2.22) + 有效期(2.24); 2.24 起可独立成块/逐向间距 -->
|
||||||
<view v-if="blk === 'meta' && (showTagsRow || showSourceRow)" class="mini-meta" :key="blk">
|
<view v-if="blk === 'meta' && (showTagsRow || showSourceRow || showExpireRow)" class="mini-meta" :style="metaFinalStyle" :key="blk">
|
||||||
<view v-if="showTagsRow" class="mini-tags">
|
<view v-if="showTagsRow" class="mini-tags">
|
||||||
<text v-for="(t, i) in cardTags" :key="'t' + i" class="mini-tag">{{ t }}</text>
|
<text v-for="(t, i) in cardTags" :key="'t' + i" class="mini-tag">{{ t }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="showSourceRow" class="mini-source">{{ sourceName }}</text>
|
<text v-if="showSourceRow" class="mini-source">{{ sourceName }}</text>
|
||||||
|
<text v-if="showExpireRow" class="mini-source">有效期至 {{ expireDateText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1163,6 +1164,21 @@ export default {
|
|||||||
showSourceRow() {
|
showSourceRow() {
|
||||||
return flagOn(this.c.showSource, false) && !!this.sourceName;
|
return flagOn(this.c.showSource, false) && !!this.sourceName;
|
||||||
},
|
},
|
||||||
|
// ---- 2.24 有效期(自动展示: 名片设了 expire_at 就显示, 无独立开关) ----
|
||||||
|
showExpireRow() {
|
||||||
|
return !!(this.card && num(this.card.expire_at, 0) > 0);
|
||||||
|
},
|
||||||
|
expireDateText() {
|
||||||
|
const ts = num(this.card && this.card.expire_at, 0);
|
||||||
|
if (ts <= 0) return '';
|
||||||
|
const d = new Date(ts * 1000);
|
||||||
|
const p = (n) => (n < 10 ? '0' + n : '' + n);
|
||||||
|
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
|
||||||
|
},
|
||||||
|
// ---- 2.24 标签与来源(meta)区块: 可独立成块 + 逐向间距(与 bio/social 同款) ----
|
||||||
|
metaFinalStyle() {
|
||||||
|
return blockFinalStyle(this.c, 'meta');
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 二维码是否「独立成块」: 后台「二维码独立样式」开关(与资料区同套逻辑)。
|
* 二维码是否「独立成块」: 后台「二维码独立样式」开关(与资料区同套逻辑)。
|
||||||
* 跟随卡片 → false(与上方资料同底连为一体, 无白卡/描边/阴影);
|
* 跟随卡片 → false(与上方资料同底连为一体, 无白卡/描边/阴影);
|
||||||
|
|||||||
@@ -115,7 +115,34 @@ test('A6: pickShareCardId 无 dataset 时回落 storage(朋友圈通道)', () =>
|
|||||||
assert.equal(pickShareCardId({ from: 'menu' }), 'C_FROM_STORAGE');
|
assert.equal(pickShareCardId({ from: 'menu' }), 'C_FROM_STORAGE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('A7: pickShareCardId 两路都没有 → 空', () => {
|
test('A6b: 第三级兜底 — storage 空且落地 query 带 card_id → 续传(右上角转发 S5-2)', () => {
|
||||||
|
// 场景: 访客经分享落地(query 带 card_id), 未点过任何分享按钮(storage 空),
|
||||||
|
// 直接右上角「···」转发 → 应自动续传落地页的源头卡
|
||||||
|
const uni = makeUni();
|
||||||
|
const { pickShareCardId } = loadCardShare(uni, () => ([{
|
||||||
|
route: 'pages_tool/index/diy',
|
||||||
|
options: { id: '3', card_id: 'C_LANDING' },
|
||||||
|
}]));
|
||||||
|
assert.equal(pickShareCardId({ from: 'menu' }), 'C_LANDING');
|
||||||
|
assert.equal(pickShareCardId(null), 'C_LANDING');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('A6c: 三级优先级 dataset > storage > 落地 query', () => {
|
||||||
|
const uni = makeUni();
|
||||||
|
const { rememberShareCardId, pickShareCardId } = loadCardShare(uni, () => ([{
|
||||||
|
route: 'pages_tool/index/diy',
|
||||||
|
options: { card_id: 'C_LANDING' },
|
||||||
|
}]));
|
||||||
|
// query 在, 但 dataset 最准 → dataset 胜
|
||||||
|
assert.equal(pickShareCardId({ target: { dataset: { cardId: 'C_DATASET' } } }), 'C_DATASET');
|
||||||
|
// storage 在(刚点过按钮), dataset 无 → storage 胜(按钮意图更近)
|
||||||
|
rememberShareCardId('C_FROM_BTN');
|
||||||
|
assert.equal(pickShareCardId(null), 'C_FROM_BTN');
|
||||||
|
// 都没有 → 落地 query
|
||||||
|
assert.equal(pickShareCardId({ from: 'menu' }), 'C_LANDING');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('A7: pickShareCardId 三路都没有 → 空', () => {
|
||||||
const uni = makeUni();
|
const uni = makeUni();
|
||||||
const { pickShareCardId } = loadCardShare(uni, () => []);
|
const { pickShareCardId } = loadCardShare(uni, () => []);
|
||||||
assert.equal(pickShareCardId({ from: 'menu' }), '');
|
assert.equal(pickShareCardId({ from: 'menu' }), '');
|
||||||
@@ -280,6 +307,80 @@ test('B9: 行为级 — 朋友圈无 card_id 时原样回落', () => {
|
|||||||
assert.equal(hooks.onShareTimeline.call(ctx), ctx.mpShareData.timeLine);
|
assert.equal(hooks.onShareTimeline.call(ctx), ctx.mpShareData.timeLine);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// =============================================================
|
||||||
|
// B+. H5 公众号分享链接补 card_id(setPublicShare, S10-1)
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
/** 把 diy.js 的 setPublicShare 抠出来真跑(同 loadDiyShareHooks 思路) */
|
||||||
|
function loadDiySetPublicShare(share) {
|
||||||
|
const src = read('common/js/diy.js');
|
||||||
|
const start = src.indexOf('setPublicShare() {');
|
||||||
|
const end = src.indexOf('onPageScroll(e) {');
|
||||||
|
assert.ok(start > 0 && end > start, '未能定位 diy.js 的 setPublicShare');
|
||||||
|
// 剥注释后, 丢掉切片尾部的外层 methods 闭括号(setPublicShare 自己的 '}' 是倒数第二个)
|
||||||
|
let body = strip(src.slice(start, end));
|
||||||
|
body = body.slice(0, body.lastIndexOf('}')).trim();
|
||||||
|
const factory = new Function('routeQueryValue', 'appendPathParam', 'return {' + body + '};');
|
||||||
|
// 工厂返回 { setPublicShare: fn } 对象, 这里直接交出方法本体
|
||||||
|
return factory(share.routeQueryValue, share.appendPathParam).setPublicShare;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('B10: diy.js 引入 routeQueryValue 且 setPublicShare 补 card_id', () => {
|
||||||
|
const src = read('common/js/diy.js');
|
||||||
|
assert.match(src, /import\s*\{[^}]*routeQueryValue[^}]*\}\s*from\s*['"]@\/common\/js\/card-share\.js['"]/,
|
||||||
|
'未引入 card-share.js 的 routeQueryValue');
|
||||||
|
const stripped = strip(src);
|
||||||
|
const start = stripped.indexOf('setPublicShare()');
|
||||||
|
const end = stripped.indexOf('onPageScroll');
|
||||||
|
assert.ok(start > 0 && end > start, '未定位到 setPublicShare');
|
||||||
|
const seg = stripped.slice(start, end);
|
||||||
|
assert.match(seg, /routeQueryValue\('card_id'\)/, 'setPublicShare 未从落地 query 取 card_id');
|
||||||
|
assert.match(seg, /appendPathParam\(shareUrl, 'card_id', shareCardId\)/, '分享链接未追加 card_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('B11: 行为级 — H5 落地带 card_id 时公众号分享链接续传', () => {
|
||||||
|
const uni = makeUni();
|
||||||
|
const share = loadCardShare(uni, () => ([{
|
||||||
|
route: 'pages_tool/index/diy',
|
||||||
|
options: { id: '3', card_id: 'C_1_shop_x' },
|
||||||
|
}]));
|
||||||
|
const setPublicShare = loadDiySetPublicShare(share);
|
||||||
|
let captured = null;
|
||||||
|
const ctx = {
|
||||||
|
$config: { h5Domain: 'https://h5.example.com' },
|
||||||
|
diyRoute: '/pages_tool/index/diy',
|
||||||
|
id: 3,
|
||||||
|
name: '',
|
||||||
|
$util: { setPublicShare: d => { captured = d; }, img: s => s || '' },
|
||||||
|
$langConfig: { getField: () => '标题' },
|
||||||
|
diyData: { global: {} },
|
||||||
|
siteInfo: null,
|
||||||
|
};
|
||||||
|
setPublicShare.call(ctx);
|
||||||
|
assert.equal(captured.link, 'https://h5.example.com/pages_tool/index/diy?id=3&card_id=C_1_shop_x',
|
||||||
|
'H5 分享链接未续传落地 card_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('B12: 行为级 — 裸 Tab(无 card_id)时 H5 分享链接行为零变化', () => {
|
||||||
|
const uni = makeUni();
|
||||||
|
const share = loadCardShare(uni, () => ([]));
|
||||||
|
const setPublicShare = loadDiySetPublicShare(share);
|
||||||
|
let captured = null;
|
||||||
|
const ctx = {
|
||||||
|
$config: { h5Domain: 'https://h5.example.com' },
|
||||||
|
diyRoute: '/pages_tool/index/diy',
|
||||||
|
id: 3,
|
||||||
|
name: '',
|
||||||
|
$util: { setPublicShare: d => { captured = d; }, img: s => s || '' },
|
||||||
|
$langConfig: { getField: () => '标题' },
|
||||||
|
diyData: { global: {} },
|
||||||
|
siteInfo: null,
|
||||||
|
};
|
||||||
|
setPublicShare.call(ctx);
|
||||||
|
assert.equal(captured.link, 'https://h5.example.com/pages_tool/index/diy?id=3',
|
||||||
|
'无 card_id 时链接应保持原样(不得追加空参数)');
|
||||||
|
});
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// C. 名片基件的分享按钮(diy-card-mini.vue)
|
// C. 名片基件的分享按钮(diy-card-mini.vue)
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|||||||
@@ -104,3 +104,36 @@ test('多语言: 请求带 lang 参数(后端按 _en 优先下发)', () => {
|
|||||||
f + ' 请求未带 lang 参数');
|
f + ' 请求未带 lang 参数');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- 2.24: 简介文字样式可配 + meta 区块独立成块 + 有效期展示 ----
|
||||||
|
|
||||||
|
test('2.24 简介样式: 消费 bioSize/bioColor/bioLineHeight(后端补 3 键后可配)', () => {
|
||||||
|
assert.match(miniSrc, /bioTextStyle\(\)\s*\{[\s\S]*?num\(c\.bioSize, 13\)/,
|
||||||
|
'简介字号未读 bioSize(回退 13)');
|
||||||
|
assert.match(miniSrc, /bioTextStyle\(\)\s*\{[\s\S]*?c\.bioColor \|\| c\.infoColor/,
|
||||||
|
'简介颜色未读 bioColor(应回退 infoColor)');
|
||||||
|
assert.match(miniSrc, /bioTextStyle\(\)\s*\{[\s\S]*?num\(c\.bioLineHeight, 22\)/,
|
||||||
|
'简介行高未读 bioLineHeight(回退 22)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('2.24 meta 区块: 与 bio/social 同款, 可独立成块 + 逐向间距', () => {
|
||||||
|
assert.match(miniSrc, /metaFinalStyle\(\)\s*\{[\s\S]*?blockFinalStyle\(this\.c, 'meta'\)/,
|
||||||
|
'meta 块未走通用独立成块样式(blockFinalStyle(this.c, "meta"))');
|
||||||
|
// 模板挂上 metaFinalStyle
|
||||||
|
assert.match(miniSrc, /class="mini-meta"[\s\S]*?:style="metaFinalStyle"/,
|
||||||
|
'meta 块模板未挂 metaFinalStyle');
|
||||||
|
// 区块流 token 含 meta(可参与排序)
|
||||||
|
assert.match(miniSrc, /BLOCK_TOKENS = \['bio', 'rows', 'acts', 'social', 'qr', 'meta'\]/,
|
||||||
|
'BLOCK_TOKENS 应含 meta token');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('2.24 有效期: expire_at 自动展示「有效期至」(无独立开关)', () => {
|
||||||
|
assert.match(miniSrc, /showExpireRow\(\)\s*\{[\s\S]*?this\.card && num\(this\.card\.expire_at, 0\) > 0/,
|
||||||
|
'有效期行未按 card.expire_at>0 判定');
|
||||||
|
assert.match(miniSrc, /expireDateText\(\)\s*\{[\s\S]*?this\.card && this\.card\.expire_at/,
|
||||||
|
'缺少有效期日期格式化');
|
||||||
|
// 模板渲染「有效期至」且计入 meta 块可见性
|
||||||
|
assert.match(miniSrc, /有效期至 \{\{ expireDateText \}\}/, '模板未渲染「有效期至」');
|
||||||
|
assert.match(miniSrc, /showTagsRow \|\| showSourceRow \|\| showExpireRow/,
|
||||||
|
'meta 块可见性未纳入有效期行');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user