feat(form): add custom navigation bar for form page across multiple platforms
适配APP、微信小程序、华为快应用和鸿蒙元服务,新增自定义导航栏,支持返回和首页按钮,修正多端导航栏适配逻辑
This commit is contained in:
@@ -2152,7 +2152,9 @@
|
|||||||
{
|
{
|
||||||
"path": "form/formdata",
|
"path": "form/formdata",
|
||||||
"style": {
|
"style": {
|
||||||
// #ifdef APP-PLUS
|
// APP 端原本就是 custom;微信小程序 / 华为快应用 / 鸿蒙元服务
|
||||||
|
// 的原生 navbar 不支持自定义左侧 Home 按钮,需要切到 custom 自绘
|
||||||
|
// #ifdef APP-PLUS || MP-WEIXIN || QUICKAPP-WEBVIEW || HARMONY
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "custom",
|
||||||
// #endif
|
// #endif
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<view :style="themeColor">
|
<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">
|
<block v-if="detail">
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="system-form-wrap">
|
<view class="system-form-wrap">
|
||||||
@@ -23,13 +42,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import diyForm from '@/components-diy/diy-form.vue';
|
|
||||||
import scroll from '@/common/js/scroll-view.js';
|
import scroll from '@/common/js/scroll-view.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
diyForm
|
|
||||||
},
|
|
||||||
mixins: [scroll],
|
mixins: [scroll],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -41,8 +56,13 @@ export default {
|
|||||||
uniacid: 0,
|
uniacid: 0,
|
||||||
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
|
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
|
||||||
hideNavbar: false, // 查询参数中支持隐藏Navbar
|
hideNavbar: false, // 查询参数中支持隐藏Navbar
|
||||||
|
showHomeBtn: false, // 查询参数 show_home=1 时,在系统标题栏左侧显示 Home 按钮
|
||||||
urlParams: {}, // 通用 URL 透传参数收集
|
urlParams: {}, // 通用 URL 透传参数收集
|
||||||
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
|
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
|
||||||
|
navbarHeight: 44, // 自绘 navbar 内容区高度(不含状态栏),跨端兜底 44px
|
||||||
|
statusBarHeight: 20, // 系统状态栏高度,自绘 navbar 时需要预留顶部空隙
|
||||||
|
navbarTitle: '', // 自绘 navbar 中央显示的标题
|
||||||
|
hasBackStack: false, // 自绘 navbar 左上角是否显示返回箭头:页面栈 > 1 时显示
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
formTitle: '', // 查询参数传入的Title
|
formTitle: '', // 查询参数传入的Title
|
||||||
@@ -58,6 +78,20 @@ export default {
|
|||||||
this.handleSubmit = this.create.bind(this);
|
this.handleSubmit = this.create.bind(this);
|
||||||
},
|
},
|
||||||
computed: {
|
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 形态:
|
// 构造 diy-form raw 模式期望的 value 形态:
|
||||||
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
|
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
|
||||||
// - formConfig:后端返回 camelCase(如 formPaddingX / formBgColor / labelFontSize),
|
// - formConfig:后端返回 camelCase(如 formPaddingX / formBgColor / labelFontSize),
|
||||||
@@ -98,11 +132,30 @@ export default {
|
|||||||
this.uniacid = options.uniacid
|
this.uniacid = options.uniacid
|
||||||
this.id = options.id || 0
|
this.id = options.id || 0
|
||||||
this.isFromMpWebView = options.from === 'mp-webview';
|
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 参数收集:除业务保留字段外,其余全部透传给后端
|
// 通用 URL 参数收集:除业务保留字段外,其余全部透传给后端
|
||||||
// 后端会做卫生过滤并广播事件,供 recommend 等插件消费
|
// 后端会做卫生过滤并广播事件,供 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 = {};
|
this.urlParams = {};
|
||||||
for (let key in options) {
|
for (let key in options) {
|
||||||
if (reservedKeys.indexOf(key) !== -1) continue;
|
if (reservedKeys.indexOf(key) !== -1) continue;
|
||||||
@@ -145,6 +198,17 @@ export default {
|
|||||||
this.updateNavbarTitle();
|
this.updateNavbarTitle();
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
getData() {
|
||||||
this.$api.sendRequest({
|
this.$api.sendRequest({
|
||||||
url: '/form/api/form/info',
|
url: '/form/api/form/info',
|
||||||
@@ -172,7 +236,15 @@ export default {
|
|||||||
formName = this.formTitle;
|
formName = this.formTitle;
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
// H5 端:系统原生 navbar —— uni.setNavigationBarTitle 改原生标题
|
||||||
|
// #ifdef H5
|
||||||
uni.setNavigationBarTitle({ title: formName });
|
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(),
|
// 由 diy-form 内置提交按钮触发:diy-form handleSubmit 已先做 verify(),
|
||||||
// 仅在通过时才通过 :onSubmit 函数 prop 调用本方法(跨 H5 / MP 一致,不走 $emit / $listeners)。
|
// 仅在通过时才通过 :onSubmit 函数 prop 调用本方法(跨 H5 / MP 一致,不走 $emit / $listeners)。
|
||||||
@@ -223,6 +295,109 @@ page {
|
|||||||
background: #fff;
|
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 {
|
.system-form-wrap {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 0 60rpx 0;
|
margin: 0 0 60rpx 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user