fix: prevent duplicate titles on Taobao login page
Keep a single native title on the entry page even when the host ignores title clearing. Preserve centered headers elsewhere and normalize manifest line endings during shared-code checks.
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
## 2026-11-04 登录页固定只显示原生标题
|
||||
|
||||
用户真机再次确认未登录页仍出现两个标题,说明不能根据 setNavigationBar 的成功回调认定原生首页标题已经清空。本轮登录页明确启用 nativeTitleOnly,首次 Page 数据即 showTitle=false,进入、返回、旋转和成功/失败回调都不再启用自绘标题。登录页 JSON 和运行时都保留 1'me肌肤状态检测 原生标题,公共白色页眉背景、安全区及页面高度不变。其余五个业务页仍使用居中标题,登录页采用淘宝原生对齐方式,不承诺居中。
|
||||
|
||||
143 项相关回归通过,新增“平台返回成功但实际未隐藏标题”、API 不存在、失败和生命周期重入场景,保证登录页不再次绘制第二个标题。IDE 重新编译后实际登录页只看到一个左侧原生标题;手机效果仍待新预览确认。共享逻辑检查同时发现 Git 在 Windows 检出 manifest.json 为 CRLF 后误报,已仅对清单文本比较归一化换行;实际五份共享 JS 仍逐字节校验,不放宽业务代码一致性。
|
||||
|
||||
## 2026-11-04 未登录首页重复标题的动态清理
|
||||
|
||||
用户反馈未登录登录页出现两个标题。此前仅 JSON defaultTitle=零宽空格,尚未在生命周期调用 setNavigationBar,不能证明首页平台标题已在真机隐藏。本轮在公共页眉包装器的 onLoad/onShow/onReady 调用 my.setNavigationBar({title:零宽空格}),不修改导航背景及返回/胶囊;保留原页面生命周期、Promise 返回值与业务登录流程。回调用独立序号及可见性隔离,离页及旧回调不能更改现页标题状态。
|
||||
|
||||
@@ -47,4 +47,4 @@ Page(require('../../utils/page-header').page({
|
||||
onTaobaoLogin: function () { this.enter(false); },
|
||||
onGuest: function () { this.enter(true); },
|
||||
noop: function () {}
|
||||
}, typeof my === 'undefined' ? {} : my, { nativeTitleFallback: true }));
|
||||
}, typeof my === 'undefined' ? {} : my, { nativeTitleOnly: true }));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"defaultTitle": "",
|
||||
"defaultTitle": "1'me肌肤状态检测",
|
||||
"titleBarColor": "#FFFFFF",
|
||||
"backgroundColor": "#142632",
|
||||
"disableScroll": true,
|
||||
|
||||
@@ -13,6 +13,6 @@ for (const name of files) {
|
||||
} else { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, source); }
|
||||
}
|
||||
const dest = path.join(root, 'vendor/manifest.json'), expected = JSON.stringify(manifest, null, 2) + '\n';
|
||||
if (check) { if (fs.readFileSync(dest, 'utf8') !== expected) throw Error('Shared manifest drift'); }
|
||||
if (check) { if (fs.readFileSync(dest, 'utf8').replace(/\r\n/g, '\n') !== expected) throw Error('Shared manifest drift'); }
|
||||
else fs.writeFileSync(dest, expected);
|
||||
console.log(check ? 'PASS: scoring, report UI, copy and capture gates match H5 byte for byte' : 'Synced shared logic from H5 (source files unchanged)');
|
||||
|
||||
@@ -39,6 +39,23 @@ test('native errors cannot block page loading and rejected promises are observed
|
||||
assert.equal(loaded, 2);
|
||||
});
|
||||
|
||||
test('native-only entry never draws its own title even when the host reports success without changing the native title', () => {
|
||||
for (const mode of ['ignored-success', 'failure', 'missing-api']) {
|
||||
const calls = [];
|
||||
const platform = mode === 'missing-api' ? {} : { setNavigationBar(options) {
|
||||
calls.push(options.title);
|
||||
if (mode === 'ignored-success') options.success({}); else options.fail({});
|
||||
} };
|
||||
const p = header.page({ data: {} }, platform, { nativeTitleOnly: true });
|
||||
p.setData = patch => Object.assign(p.data, patch);
|
||||
assert.equal(p.data.brandHeader.showTitle, false, 'no duplicate on the first frame');
|
||||
for (const lifecycle of ['onLoad', 'onShow', 'onReady', 'onResize', 'onHide', 'onShow']) {
|
||||
p[lifecycle](); assert.equal(p.data.brandHeader.showTitle, false, lifecycle);
|
||||
}
|
||||
assert(calls.every(title => title === "1'me肌肤状态检测"), 'entry keeps the native title instead of trying to clear it');
|
||||
}
|
||||
});
|
||||
|
||||
test('actual header template centers on the full screen and keeps clear of status and native buttons', async () => {
|
||||
const fs = require('node:fs'), path = require('node:path'), { chromium } = require('playwright');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
@@ -63,7 +80,8 @@ test('actual header template centers on the full screen and keeps clear of statu
|
||||
}
|
||||
for (const route of JSON.parse(fs.readFileSync(path.join(root, 'app.json'), 'utf8')).pages) {
|
||||
const config = JSON.parse(fs.readFileSync(path.join(root, route + '.json'), 'utf8'));
|
||||
assert.equal(config.transparentTitle, 'always'); assert.equal(config.defaultTitle, '\u200b');
|
||||
assert.equal(config.transparentTitle, 'always');
|
||||
assert.equal(config.defaultTitle, route === 'pages/login/login' ? header.layout().title : '\u200b');
|
||||
assert.match(fs.readFileSync(path.join(root, route + '.axml'), 'utf8'), /template is="brand-header"/);
|
||||
}
|
||||
} finally { await browser.close(); }
|
||||
|
||||
@@ -11,32 +11,37 @@ function layout(platform) {
|
||||
function page(definition, platform, options) {
|
||||
options = options || {};
|
||||
definition.data = Object.assign({ brandHeader: layout() }, definition.data);
|
||||
// Taobao may report success yet keep its entry-page app title visible.
|
||||
// Login must never draw a second title, including its very first frame.
|
||||
if (options.nativeTitleOnly) definition.data.brandHeader.showTitle = false;
|
||||
var load = definition.onLoad, resize = definition.onResize, show = definition.onShow,
|
||||
ready = definition.onReady, hide = definition.onHide, unload = definition.onUnload;
|
||||
function refresh(instance) {
|
||||
var next = layout(platform);
|
||||
if (instance.data.brandHeader) next.showTitle = instance.data.brandHeader.showTitle;
|
||||
if (options.nativeTitleOnly) next.showTitle = false;
|
||||
instance.setData({ brandHeader: next });
|
||||
}
|
||||
function clearNativeTitle(instance) {
|
||||
function syncNativeTitle(instance) {
|
||||
if (!platform || typeof platform.setNavigationBar !== 'function') return;
|
||||
var seq = instance.brandHeaderSeq = (instance.brandHeaderSeq || 0) + 1, settled = false;
|
||||
function result(ok) {
|
||||
if (settled) return; settled = true;
|
||||
if (!instance.brandHeaderVisible || instance.brandHeaderSeq !== seq) return;
|
||||
if (options.nativeTitleOnly) return;
|
||||
// The entry page can retain a platform-owned app name. If clearing is
|
||||
// explicitly rejected, show one native title rather than two titles.
|
||||
if (!ok && !options.nativeTitleFallback) return;
|
||||
instance.setData({ brandHeader: Object.assign({}, instance.data.brandHeader, { showTitle: ok }) });
|
||||
}
|
||||
try {
|
||||
var pending = platform.setNavigationBar({ title: '\u200b', success: function () { result(true); }, fail: function () { result(false); } });
|
||||
var pending = platform.setNavigationBar({ title: options.nativeTitleOnly ? TITLE : '\u200b', success: function () { result(true); }, fail: function () { result(false); } });
|
||||
if (pending && typeof pending.then === 'function') Promise.resolve(pending).then(function () { result(true); }, function () { result(false); });
|
||||
} catch (_) { result(false); }
|
||||
}
|
||||
definition.onLoad = function () { this.brandHeaderVisible = true; refresh(this); clearNativeTitle(this); if (load) return load.apply(this, arguments); };
|
||||
definition.onShow = function () { this.brandHeaderVisible = true; clearNativeTitle(this); if (show) return show.apply(this, arguments); };
|
||||
definition.onReady = function () { clearNativeTitle(this); if (ready) return ready.apply(this, arguments); };
|
||||
definition.onLoad = function () { this.brandHeaderVisible = true; refresh(this); syncNativeTitle(this); if (load) return load.apply(this, arguments); };
|
||||
definition.onShow = function () { this.brandHeaderVisible = true; syncNativeTitle(this); if (show) return show.apply(this, arguments); };
|
||||
definition.onReady = function () { syncNativeTitle(this); if (ready) return ready.apply(this, arguments); };
|
||||
definition.onHide = function () { this.brandHeaderVisible = false; this.brandHeaderSeq = (this.brandHeaderSeq || 0) + 1; if (hide) return hide.apply(this, arguments); };
|
||||
definition.onUnload = function () { this.brandHeaderVisible = false; this.brandHeaderSeq = (this.brandHeaderSeq || 0) + 1; if (unload) return unload.apply(this, arguments); };
|
||||
definition.onResize = function () { refresh(this); if (resize) return resize.apply(this, arguments); };
|
||||
|
||||
Reference in New Issue
Block a user