feat: add dynamic navigation bar title support for webview and form pages
1. add title and enableSetTitle data fields for webview and form pages 2. implement auto set navigation bar title when enabled 3. add URL normalize and query params append utilities 4. pass title and enable_set_title params across pages and external links
This commit is contained in:
@@ -634,13 +634,23 @@ export default {
|
||||
|
||||
// 外部链接
|
||||
if (link.wap_url && link.wap_url.indexOf('http') != -1) {
|
||||
// 公共参数
|
||||
const params = {
|
||||
title: link?.title ?? '',
|
||||
enable_set_title: Boolean(link?.enable_set_title)
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
window.location.href = link.wap_url;
|
||||
let url = this.normalizeUrl(decodeEntity(link.wap_url));
|
||||
url = this.appendQueryParams(url, params);
|
||||
// console.log(`url ---->`, url)
|
||||
window.location.href = url;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
this.redirectTo('/pages_tool/webview/webview', {
|
||||
src: encodeURIComponent(link.wap_url)
|
||||
src: encodeURIComponent(link.wap_url),
|
||||
...params
|
||||
});
|
||||
// #endif
|
||||
|
||||
@@ -1253,5 +1263,172 @@ export default {
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
},
|
||||
|
||||
/**
|
||||
* 适用于 UniApp 的全平台 URL 标准化函数
|
||||
* @param {string} urlString - 需要标准化的原始 URL 字符串
|
||||
* @returns {string} 标准化后的 URL
|
||||
*/
|
||||
normalizeUrl(urlString) {
|
||||
if (!urlString || typeof urlString !== 'string') return '';
|
||||
|
||||
let url = urlString.trim();
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = 'https://' + url;
|
||||
}
|
||||
|
||||
// ==================== H5 端处理 (利用原生 URL API) ====================
|
||||
// #ifdef H5
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
|
||||
urlObj.protocol = urlObj.protocol.toLowerCase();
|
||||
urlObj.hostname = urlObj.hostname.toLowerCase();
|
||||
|
||||
if ((urlObj.protocol === 'http:' && urlObj.port === '80') ||
|
||||
(urlObj.protocol === 'https:' && urlObj.port === '443')) {
|
||||
urlObj.port = '';
|
||||
}
|
||||
|
||||
const tempUrl = new URL(urlObj.pathname, urlObj.origin);
|
||||
urlObj.pathname = tempUrl.pathname;
|
||||
|
||||
if (urlObj.search) {
|
||||
const searchParams = new URLSearchParams(urlObj.search);
|
||||
const sortedParams = new URLSearchParams();
|
||||
Array.from(searchParams.keys()).sort().forEach(key => {
|
||||
sortedParams.append(key, searchParams.get(key));
|
||||
});
|
||||
urlObj.search = sortedParams.toString();
|
||||
}
|
||||
|
||||
if (urlObj.hash === '#') urlObj.hash = '';
|
||||
|
||||
return urlObj.toString();
|
||||
} catch (e) {
|
||||
console.error('H5端 URL 解析失败:', e);
|
||||
return url;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// ==================== 非 H5 端处理 (小程序 / App) ====================
|
||||
// #ifndef H5
|
||||
try {
|
||||
const protocolMatch = url.match(/^(https?):\/\//i);
|
||||
const protocol = protocolMatch ? protocolMatch[1].toLowerCase() + '://' : 'https://';
|
||||
const withoutProtocol = url.replace(/^https?:\/\//i, '');
|
||||
|
||||
const hashIndex = withoutProtocol.indexOf('#');
|
||||
let hash = '';
|
||||
let mainPart = withoutProtocol;
|
||||
if (hashIndex !== -1) {
|
||||
hash = withoutProtocol.substring(hashIndex);
|
||||
mainPart = withoutProtocol.substring(0, hashIndex);
|
||||
}
|
||||
|
||||
const pathQuerySplit = mainPart.split('/');
|
||||
const hostPort = pathQuerySplit.shift();
|
||||
let pathname = '/' + pathQuerySplit.join('/');
|
||||
|
||||
const [hostname, port] = hostPort.split(':');
|
||||
const lowerHostname = hostname.toLowerCase();
|
||||
|
||||
let finalPort = port;
|
||||
if ((protocol === 'http://' && port === '80') || (protocol === 'https://' && port === '443')) {
|
||||
finalPort = '';
|
||||
}
|
||||
|
||||
let pathParts = pathname.split('/').filter(p => p !== '' && p !== '.');
|
||||
const resolvedPath = [];
|
||||
for (const part of pathParts) {
|
||||
if (part === '..') {
|
||||
resolvedPath.pop();
|
||||
} else {
|
||||
resolvedPath.push(part);
|
||||
}
|
||||
}
|
||||
pathname = '/' + resolvedPath.join('/');
|
||||
|
||||
const queryIndex = pathname.indexOf('?');
|
||||
let query = '';
|
||||
if (queryIndex !== -1) {
|
||||
const queryString = pathname.substring(queryIndex + 1);
|
||||
pathname = pathname.substring(0, queryIndex);
|
||||
|
||||
const params = queryString.split('&').map(pair => {
|
||||
const [k, v = ''] = pair.split('=');
|
||||
return { k, v };
|
||||
});
|
||||
params.sort((a, b) => a.k.localeCompare(b.k));
|
||||
query = '?' + params.map(p => `${p.k}=${p.v}`).join('&');
|
||||
}
|
||||
|
||||
const portStr = finalPort ? `:${finalPort}` : '';
|
||||
return `${protocol}//${lowerHostname}${portStr}${pathname}${query}${hash}`;
|
||||
} catch (e) {
|
||||
console.error('非H5端 URL 解析失败:', e);
|
||||
return url;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* UniApp 全平台通用的 URL 追加查询参数函数(自动处理中文编码)
|
||||
* @param {string} url - 原始 URL 字符串
|
||||
* @param {object} params - 需要追加的参数对象,例如 { name: '张三', city: '苏州' }
|
||||
* @returns {string} 追加参数后的完整 URL
|
||||
*/
|
||||
appendQueryParams(url, params) {
|
||||
if (!url || typeof url !== 'string') return '';
|
||||
|
||||
// ==================== H5 端处理 (利用原生 URL API) ====================
|
||||
// #ifdef H5
|
||||
try {
|
||||
let targetUrl = url.trim();
|
||||
if (!/^https?:\/\//i.test(targetUrl)) {
|
||||
targetUrl = 'https://' + targetUrl;
|
||||
}
|
||||
const urlObj = new URL(targetUrl);
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
urlObj.searchParams.append(key, String(value));
|
||||
}
|
||||
}
|
||||
return urlObj.toString();
|
||||
} catch (e) {
|
||||
console.error('H5端 URL 参数追加失败:', e);
|
||||
return url;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// ==================== 非 H5 端处理 (小程序 / App) ====================
|
||||
// #ifndef H5
|
||||
try {
|
||||
let targetUrl = url.trim();
|
||||
// 分离 hash (#部分),避免干扰参数拼接
|
||||
const hashIndex = targetUrl.indexOf('#');
|
||||
let hash = '';
|
||||
if (hashIndex !== -1) {
|
||||
hash = targetUrl.substring(hashIndex);
|
||||
targetUrl = targetUrl.substring(0, hashIndex);
|
||||
}
|
||||
|
||||
// 判断当前 URL 是否已经包含查询参数,决定使用 ? 还是 &
|
||||
const separator = targetUrl.includes('?') ? '&' : '?';
|
||||
|
||||
// 遍历参数对象,手动进行 encodeURIComponent 编码并拼接
|
||||
const queryString = Object.entries(params)
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
||||
.join('&');
|
||||
|
||||
// 重新拼接成完整的 URL
|
||||
return `${targetUrl}${separator}${queryString}${hash}`;
|
||||
} catch (e) {
|
||||
console.error('非H5端 URL 参数追加失败:', e);
|
||||
return url;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ export default {
|
||||
scroll: true,
|
||||
uniacid: 0,
|
||||
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
|
||||
|
||||
// #ifdef H5
|
||||
title: '', // 查询参数传入的Title
|
||||
enableSetTitle: false, // 查询参数传入的是否可以强制设置Title
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -66,6 +71,11 @@ export default {
|
||||
|
||||
uni.setStorageSync('skipPrivacyCheck', true);
|
||||
|
||||
// #ifdef H5
|
||||
this.title = options?.title ?? '';
|
||||
this.enableSetTitle = Boolean(options?.enable_set_title);
|
||||
// #endif
|
||||
|
||||
this.uniacid = options.uniacid
|
||||
this.id = options.id || 0
|
||||
this.isFromMpWebView = options.from === 'mp-webview';
|
||||
@@ -109,8 +119,17 @@ export default {
|
||||
if (res.code == 0 && res.data) {
|
||||
this.detail = res.data
|
||||
console.log(`form data =>`, this.detail)
|
||||
|
||||
let formName = res.data?.form_name ?? '';
|
||||
|
||||
// #ifdef H5
|
||||
if (this.enableSetTitle) {
|
||||
formName = this.title;
|
||||
}
|
||||
// #endif
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: res.data.form_name
|
||||
title: formName
|
||||
})
|
||||
}
|
||||
this.$refs.loadingCover && this.$refs.loadingCover.hide()
|
||||
|
||||
@@ -14,10 +14,15 @@ import { decodeEntity } from '@/common/js/util.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
src: ''
|
||||
src: '',
|
||||
title: '', // 标题栏标题
|
||||
enableSetTitle: false, // 是否强制设置标题,用来替换Webview中的默认标题
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
this.title = option?.title ?? '';
|
||||
this.enableSetTitle = Boolean(option?.enable_set_title);
|
||||
|
||||
// #ifdef H5
|
||||
this.src = decodeEntity(decodeURIComponent(option.src));
|
||||
// #endif
|
||||
@@ -26,6 +31,13 @@ export default {
|
||||
this.src = decodeEntity(decodeURIComponent(`${option.src}&from=mp-webview`));
|
||||
// #endif
|
||||
},
|
||||
onReady() {
|
||||
if (this.enableSetTitle) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: this.title
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigateBack() {
|
||||
uni.navigateBack({
|
||||
|
||||
Reference in New Issue
Block a user