feat(form): add custom navigation bar for form page across multiple platforms

适配APP、微信小程序、华为快应用和鸿蒙元服务,新增自定义导航栏,支持返回和首页按钮,修正多端导航栏适配逻辑
This commit is contained in:
2026-08-07 16:22:50 +08:00
parent bbef0ecf55
commit ab8625ebaf
2 changed files with 184 additions and 7 deletions

View File

@@ -2152,7 +2152,9 @@
{
"path": "form/formdata",
"style": {
// #ifdef APP-PLUS
// APP 端原本就是 custom;微信小程序 / 华为快应用 / 鸿蒙元服务
// 的原生 navbar 不支持自定义左侧 Home 按钮,需要切到 custom 自绘
// #ifdef APP-PLUS || MP-WEIXIN || QUICKAPP-WEBVIEW || HARMONY
"navigationStyle": "custom",
// #endif
"navigationBarTitleText": "",

View File

@@ -1,5 +1,24 @@
<template>
<view :style="themeColor">
<!--
自绘导航栏(仅在 navigationStyle:custom 的端渲染:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务)
- pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar
- 左侧:hasBackStack 为 true 时显示返回箭头(点击 navigateBack),showHomeBtn=true 时再叠加 Home 胶囊
- 中央:表单标题(form_name)
- 整体高度 = 状态栏高度 + 44px 内容区,由 customNavbarStyle 动态算
-->
<view class="form-home-navbar" :style="customNavbarStyle">
<view class="form-home-navbar__left">
<view v-if="hasBackStack" class="form-home-navbar__back" @tap="goBack" hover-class="form-home-navbar__btn--hover">
<text class="form-home-navbar__back-icon">‹</text>
</view>
<view v-if="showHomeBtn" class="form-home-capsule" @tap="goHome" hover-class="form-home-capsule--hover">
<text class="iconfont icon-shouye1 form-home-capsule__icon"></text>
</view>
</view>
<view class="form-home-navbar__title">{{ navbarTitle }}</view>
<view class="form-home-navbar__placeholder" />
</view>
<block v-if="detail">
<view class="page">
<view class="system-form-wrap">
@@ -23,13 +42,9 @@
<script>
import diyForm from '@/components-diy/diy-form.vue';
import scroll from '@/common/js/scroll-view.js';
export default {
components: {
diyForm
},
mixins: [scroll],
data() {
return {
@@ -41,8 +56,13 @@ export default {
uniacid: 0,
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
hideNavbar: false, // 查询参数中支持隐藏Navbar
showHomeBtn: false, // 查询参数 show_home=1 时,在系统标题栏左侧显示 Home 按钮
urlParams: {}, // 通用 URL 透传参数收集
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
navbarHeight: 44, // 自绘 navbar 内容区高度(不含状态栏),跨端兜底 44px
statusBarHeight: 20, // 系统状态栏高度,自绘 navbar 时需要预留顶部空隙
navbarTitle: '', // 自绘 navbar 中央显示的标题
hasBackStack: false, // 自绘 navbar 左上角是否显示返回箭头:页面栈 > 1 时显示
// #ifdef H5
formTitle: '', // 查询参数传入的Title
@@ -58,6 +78,20 @@ export default {
this.handleSubmit = this.create.bind(this);
},
computed: {
// 自绘 navbar 的高度样式
// - 内容区高度 = navbarHeight(兜底 44px)
// - 整体高度 = 状态栏高度 + 内容区高度
// - padding-top 把内容区下移到状态栏之下,避免被刘海/水滴遮挡
customNavbarStyle() {
try {
const sys = uni.getSystemInfoSync();
const sb = sys.statusBarHeight || 0;
const h = this.navbarHeight || 44;
return `height: ${sb + h}px; padding-top: ${sb}px;`;
} catch (e) {
return 'height: 64px; padding-top: 20px;';
}
},
// 构造 diy-form raw 模式期望的 value 形态:
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
// - formConfig:后端返回 camelCase(如 formPaddingX / formBgColor / labelFontSize),
@@ -98,11 +132,30 @@ export default {
this.uniacid = options.uniacid
this.id = options.id || 0
this.isFromMpWebView = options.from === 'mp-webview';
this.hideNavbar = Boolean(options?.hide_navbar);
this.hideNavbar = this.$util.coerceBool(options?.hide_navbar);
// show_home=1 时在系统标题栏左侧显示 Home 按钮,点击回到首页 INDEX_PAGE_URL
// 解析规则统一走 this.$util.coerceBool:支持 '1' / 'true' / 1 / true 四种写法
this.showHomeBtn = this.$util.coerceBool(options?.show_home);
// 自绘 navbar 需要用到状态栏高度 / navbar 内容区高度(跨端兜底)
try {
const sys = uni.getSystemInfoSync();
if (sys.statusBarHeight) this.statusBarHeight = sys.statusBarHeight;
// uniapp 在不同端 menuButton / navigationBar 高度略有差异,给个安全兜底即可
if (typeof sys.navbarHeight === 'number' && sys.navbarHeight > 0) {
this.navbarHeight = sys.navbarHeight;
}
} catch (e) { /* keep defaults */ }
// 自绘 navbar 是否显示返回箭头:getCurrentPages().length > 1 即页面有栈
try {
const pages = getCurrentPages ? getCurrentPages() : [];
this.hasBackStack = Array.isArray(pages) && pages.length > 1;
} catch (e) { /* keep default */ }
// 通用 URL 参数收集:除业务保留字段外,其余全部透传给后端
// 后端会做卫生过滤并广播事件,供 recommend 等插件消费
const reservedKeys = ['uniacid', 'id', 'scene', 'from', 'hide_navbar', 'title', 'enable_set_title'];
const reservedKeys = ['uniacid', 'id', 'scene', 'from', 'hide_navbar', 'show_home', 'title', 'enable_set_title'];
this.urlParams = {};
for (let key in options) {
if (reservedKeys.indexOf(key) !== -1) continue;
@@ -145,6 +198,17 @@ export default {
this.updateNavbarTitle();
},
methods: {
// 返回上一页;用 navigateBack,栈深度>1 时由 hasBackStack 控制按钮可见性
goBack() {
uni.navigateBack({ delta: 1 });
},
// 返回首页;
// INDEX_PAGE_URL 是 TabBar 根页面,不能用裸 uni.redirectTo(会报"can not redirectTo a tabBar page")
// 必须用 this.$util.redirectTo,内部对 TabBar 页面自动走 uni.switchTab
// (鸿蒙元服务下还会用 reLaunch 兜底,见 common/js/util.js)
goHome() {
this.$util.redirectTo(this.$util.INDEX_PAGE_URL, {}, 'redirectTo');
},
getData() {
this.$api.sendRequest({
url: '/form/api/form/info',
@@ -172,7 +236,15 @@ export default {
formName = this.formTitle;
}
// #endif
// H5 端:系统原生 navbar —— uni.setNavigationBarTitle 改原生标题
// #ifdef H5
uni.setNavigationBarTitle({ title: formName });
// #endif
// 非 H5 端:custom 模式下没有原生 navbar,直接写 data.navbarTitle,
// 由模板里的 .form-home-navbar__title 显示
// #ifndef H5
this.navbarTitle = formName;
// #endif
},
// 由 diy-form 内置提交按钮触发:diy-form handleSubmit 已先做 verify(),
// 仅在通过时才通过 :onSubmit 函数 prop 调用本方法(跨 H5 / MP 一致,不走 $emit / $listeners)。
@@ -223,6 +295,109 @@ page {
background: #fff;
}
/*
* 自绘 navbar(非 H5 端:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务)
* - pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar
* - 整体高度由 inline style 注入:statusBarHeight + 44px
* - 左侧 Home 胶囊 + 中央标题 + 右侧占位,标题过长自动省略
*/
/* --- 自绘 navbar 容器 --- */
.form-home-navbar {
position: relative;
width: 100%;
box-sizing: border-box;
background: #ffffff;
/* 高度由 inline style 通过 customNavbarStyle 注入:statusBarHeight + 44px */
/* padding-top 把内容区下移到状态栏之下,避免被刘海/水滴遮挡 */
/* 浮在表单内容之上,避免被 diy-form 等组件覆盖 */
z-index: 100;
/* 让内容靠 flex 自动落到 padding-top 之下的区域 */
display: flex;
flex-direction: row;
align-items: center;
}
.form-home-navbar__left {
/* 不再用 absolute,跟着 navbar 的 padding-top 下移 */
flex: none;
/* 宽度自适应:返回按钮 56rpx + Home 胶囊 56rpx + 间距 12rpx + 左 padding 16rpx = 最多 ~140rpx */
min-width: 88rpx;
max-width: 200rpx;
display: flex;
align-items: center;
justify-content: flex-start;
padding-left: 16rpx;
box-sizing: border-box;
gap: 12rpx;
}
.form-home-navbar__back {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
}
.form-home-navbar__back-icon {
font-size: 48rpx;
line-height: 1;
color: #000;
font-weight: 300;
}
.form-home-navbar__btn--hover {
background: rgba(0, 0, 0, 0.06);
}
.form-home-navbar__title {
flex: 1;
height: 44px;
line-height: 44px;
text-align: center;
font-size: 32rpx;
color: #000;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
box-sizing: border-box;
}
.form-home-navbar__placeholder {
/* 与 __left 对称,保持标题视觉居中 */
flex: none;
min-width: 88rpx;
max-width: 200rpx;
}
/* --- Home 胶囊主体 --- */
.form-home-capsule {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
}
.form-home-capsule--hover {
background: rgba(0, 0, 0, 0.06);
}
/*
* Home 图标 —— 使用项目统一的 iconfont icon-shouye1(unicode \e02d)
* 与 pages_goods/detail.vue 的首页按钮共用同一个图标,跨端视觉一致
*/
.form-home-capsule__icon {
font-size: 32rpx;
line-height: 1;
color: #000;
display: block;
}
.system-form-wrap {
overflow: hidden;
margin: 0 0 60rpx 0;