677 lines
24 KiB
Vue
677 lines
24 KiB
Vue
<template>
|
||
<!-- #ifdef MP-HARMONY -->
|
||
<view :style="themeColor" class="harmony-root">
|
||
<!-- #endif -->
|
||
<!-- #ifndef MP-HARMONY -->
|
||
<view :style="themeColor">
|
||
<!-- #endif -->
|
||
<!--
|
||
自绘导航栏(仅在 navigationStyle:custom 的端渲染:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务)
|
||
- pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar
|
||
- 左侧:hasBackStack 为 true 时显示返回箭头(点击 navigateBack),showHomeBtn=true 时再叠加 Home 胶囊
|
||
- 中央:表单标题(form_name)
|
||
- 整体高度 = 状态栏高度 + 44px 内容区,由 customNavbarStyle 动态算
|
||
H5 端保留系统原生 navbar,不渲染此块
|
||
-->
|
||
<!-- #ifndef H5 -->
|
||
<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>
|
||
<!-- #endif -->
|
||
<block v-if="detail">
|
||
<!-- 鸿蒙元服务:用原生 scroll-view 包裹表单,替代 CSS overflow 滚动(ArkUI 不识别 WebKit overflow 语义)。
|
||
根容器 harmony-root 占满 100vh 并 flex 列布局,navbar 占顶部固定高度,page flex:1 占剩余,scroll-view 在其内滚动。
|
||
提交按钮由 diy-form 内部渲染,处于 scroll-view 内,用户可滚到末尾点击。 -->
|
||
<!-- #ifdef MP-HARMONY -->
|
||
<view class="page harmony-page">
|
||
<scroll-view scroll-y="true" :show-scrollbar="false" class="form-scroll">
|
||
<view class="system-form-wrap">
|
||
<diy-form ref="form" :value="diyValue" dataMode="raw" :showSubmit="!showCaptcha" :onSubmit="handleSubmit" />
|
||
<!-- 行为设置:提交验证码开启时,隐藏 diy-form 内置按钮,由本页渲染验证码+提交按钮 -->
|
||
<view v-if="showCaptcha" class="form-extra-wrap">
|
||
<view class="captcha-wrap">
|
||
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
|
||
<image class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||
</view>
|
||
<view class="custom-submit" @click="submitFromPage">{{ submitBtnText }}</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
<!-- #endif -->
|
||
<!-- #ifndef MP-HARMONY -->
|
||
<view class="page">
|
||
<view class="system-form-wrap">
|
||
<!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。
|
||
:onSubmit 接管提交逻辑(带 uniacid + showModal);跨 H5 / MP 一致,不依赖 $listeners。
|
||
注意:create 重度依赖本组件 this(id/uniacid/memberInfo/$api 等),必须在 created 阶段 bind this,
|
||
否则 diy-form 通过 this.onSubmit(formData) 调用时 this 会漂移到子组件实例 -->
|
||
<diy-form ref="form" :value="diyValue" dataMode="raw" :showSubmit="!showCaptcha" :onSubmit="handleSubmit" />
|
||
<!-- 行为设置:提交验证码开启时,隐藏 diy-form 内置按钮,由本页渲染验证码+提交按钮 -->
|
||
<view v-if="showCaptcha" class="form-extra-wrap">
|
||
<view class="captcha-wrap">
|
||
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
|
||
<image class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||
</view>
|
||
<view class="custom-submit" @click="submitFromPage">{{ submitBtnText }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
</block>
|
||
<block v-else>
|
||
<ns-empty :text="emptyText" :isIndex="false"></ns-empty>
|
||
</block>
|
||
<loading-cover ref="loadingCover"></loading-cover>
|
||
<ns-login ref="login"></ns-login>
|
||
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
|
||
import scroll from '@/common/js/scroll-view.js';
|
||
|
||
export default {
|
||
mixins: [scroll],
|
||
data() {
|
||
return {
|
||
id: 0,
|
||
detail: null,
|
||
isRepeat: false,
|
||
complete: false,
|
||
scroll: true,
|
||
uniacid: 0,
|
||
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
|
||
hideNavbar: false, // 查询参数中支持隐藏Navbar
|
||
showHomeBtn: false, // 查询参数 show_home=1 时,在系统标题栏左侧显示 Home 按钮
|
||
urlParams: {}, // 通用 URL 透传参数收集
|
||
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
|
||
// 行为设置(后端 __form_config__.behavior 下发,含默认值)
|
||
behavior: {},
|
||
// 非成功状态的空态文案(表单未开放/需登录/加载失败等)
|
||
formError: '',
|
||
// 图形验证码(behavior.captcha=1 时展示,由本页渲染而非 diy-form)
|
||
captcha: { id: '', img: '', code: '' },
|
||
// 登录后回跳地址(onLoad 时由原始 options 重建)
|
||
backUrl: '',
|
||
|
||
// #ifndef H5
|
||
navbarHeight: 44, // 自绘 navbar 内容区高度(不含状态栏),跨端兜底 44px
|
||
statusBarHeight: 20, // 系统状态栏高度,自绘 navbar 时需要预留顶部空隙
|
||
navbarTitle: '', // 自绘 navbar 中央显示的标题
|
||
hasBackStack: false, // 自绘 navbar 左上角是否显示返回箭头:页面栈 > 1 时显示
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
formTitle: '', // 查询参数传入的Title
|
||
enableSetTitle: false, // 查询参数传入的是否可以强制设置Title
|
||
// #endif
|
||
}
|
||
},
|
||
created() {
|
||
// 预绑定 this:create 重度依赖本组件 this(id/uniacid/memberInfo/$api/$lang/$util 等)。
|
||
// 若直接传 :onSubmit="create",diy-form 通过 this.onSubmit(formData) 调用时,
|
||
// 普通函数调用的 this 会指向调用者(子组件实例),导致字段全部读不到。
|
||
// bind 后无论子组件如何调用,this 始终是 formdata 实例。
|
||
this.handleSubmit = this.create.bind(this);
|
||
},
|
||
computed: {
|
||
// #ifndef H5
|
||
// 自绘 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;';
|
||
}
|
||
},
|
||
// #endif
|
||
// 构造 diy-form raw 模式期望的 value 形态:
|
||
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
|
||
// - formConfig:后端返回 camelCase(如 formPaddingX / formBgColor / labelFontSize),
|
||
// diy-form 内部 mergedFormConfig 同时兼容 snake_case 后再交给 ns-newform
|
||
// - show_banner / form_banner:交给 diy-form 渲染 banner,样式走 form_config.bannerPaddingX;form_banner 给保底图
|
||
// - show_tip / form_tip:交给 diy-form 渲染 tip,样式走 form_config;form_tip 给保底文案
|
||
// 注:本 computed 只在 <block v-if="detail"> 内被消费,无需 null 守卫
|
||
diyValue() {
|
||
return {
|
||
jsonData: this.detail.json_data,
|
||
formConfig: this.detail.form_config,
|
||
show_banner: this.detail.show_banner,
|
||
form_banner: this.detail.form_banner || 'public/uniapp/form/banner.png',
|
||
show_tip: this.detail.show_tip,
|
||
form_tip: this.detail.form_tip || this.$lang('form.formdata.formTipPlaceholder')
|
||
}
|
||
},
|
||
// 行为设置:提交验证码开启时 diy-form 隐藏内置提交按钮,本页渲染验证码 + 自有提交按钮
|
||
showCaptcha() {
|
||
return this.behavior.captcha == 1;
|
||
},
|
||
// 自有提交按钮文案:跟随表单配置的提交按钮文字(form_config 为 camelCase)
|
||
submitBtnText() {
|
||
return (this.detail && this.detail.form_config && this.detail.form_config.submitText) || this.$lang('common.submit');
|
||
},
|
||
// 空状态文案:提交成功 / 表单未开放 / 需登录 / 加载失败
|
||
emptyText() {
|
||
return this.complete ? this.$lang('form.formdata.submitSuccess')
|
||
: (this.formError || this.$lang('form.formdata.noFormInfo'));
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
console.log("=====表单页面====", options)
|
||
if (!options.uniacid) {
|
||
uni.showModal({
|
||
title: this.$lang('form.formdata.tip'),
|
||
content: this.$lang('form.formdata.paramError'),
|
||
showCancel: false,
|
||
success: () => { }
|
||
})
|
||
return false
|
||
}
|
||
|
||
uni.setStorageSync('skipPrivacyCheck', true);
|
||
|
||
// #ifdef H5
|
||
this.formTitle = options?.title ?? '';
|
||
this.enableSetTitle = options?.enable_set_title == 1;
|
||
// #endif
|
||
|
||
this.uniacid = options.uniacid
|
||
this.id = options.id || 0
|
||
this.isFromMpWebView = options.from === 'mp-webview';
|
||
this.hideNavbar = this.$util.coerceBool(options?.hide_navbar);
|
||
// show_home=1 时在系统标题栏左侧显示 Home 按钮,点击回到首页 INDEX_PAGE_URL
|
||
// 解析规则统一走 this.$util.coerceBool:支持 '1' / 'true' / 1 / true 四种写法
|
||
// 仅在 navigationStyle:custom 端生效(APP / 微信小程序 / 华为快应用 / 鸿蒙元服务);
|
||
// H5 保留系统原生 navbar,不支持 show_home,参数被忽略(这是预期行为)
|
||
// 注:H5 嵌入微信小程序 webview(from=mp-webview)的场景也属于 H5 范畴,
|
||
// 此场景下也没有 Home 入口——后续如需支持,需要单独处理(导航靠宿主小程序接管)
|
||
this.showHomeBtn = this.$util.coerceBool(options?.show_home);
|
||
|
||
// #ifndef H5
|
||
// 自绘 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 */ }
|
||
// #endif
|
||
|
||
// 通用 URL 参数收集:除业务保留字段外,其余全部透传给后端
|
||
// 后端会做卫生过滤并广播事件,供 recommend 等插件消费
|
||
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;
|
||
this.urlParams[key] = options[key];
|
||
}
|
||
|
||
if (options.scene) {
|
||
const scene = decodeURIComponent(options.scene)
|
||
const params = scene.split('&')
|
||
params.length && params.forEach(item => {
|
||
if (item.indexOf('id') != -1) {
|
||
this.id = item.split('-')[1]
|
||
}
|
||
// scene 中也可能携带其他参数
|
||
let pair = item.split('=');
|
||
if (pair.length === 2 && pair[0] !== 'id') {
|
||
this.urlParams[pair[0]] = pair[1];
|
||
}
|
||
})
|
||
}
|
||
|
||
// #ifdef H5
|
||
if (this.isFromMpWebView || this.hideNavbar) {
|
||
this.$nextTick(() => {
|
||
const navbar = document.querySelector('uni-page-head')
|
||
if (navbar) navbar.style.display = 'none'
|
||
})
|
||
}
|
||
// #endif
|
||
|
||
// 登录后回跳地址:由原始 options 重建(供 FORM_LOGIN_REQUIRED 时 ns-login 使用)
|
||
const backQuery = [];
|
||
for (let key in options) {
|
||
if (key === 'scene') continue;
|
||
backQuery.push(key + '=' + encodeURIComponent(options[key]));
|
||
}
|
||
this.backUrl = '/pages_tool/form/formdata' + (backQuery.length ? '?' + backQuery.join('&') : '');
|
||
|
||
this.getData()
|
||
},
|
||
|
||
watch: {
|
||
storeToken(newVal, oldVal) {
|
||
newVal && this.getData()
|
||
}
|
||
},
|
||
onShow() {
|
||
this.updateNavbarTitle();
|
||
},
|
||
methods: {
|
||
// #ifndef H5
|
||
// 返回上一页;用 navigateBack,栈深度>1 时由 hasBackStack 控制按钮可见性
|
||
goBack() {
|
||
uni.navigateBack({ delta: 1 });
|
||
},
|
||
// #endif
|
||
// 返回首页;
|
||
// INDEX_PAGE_URL 是 TabBar 根页面,不能用裸 uni.redirectTo(会报"can not redirectTo a tabBar page")
|
||
// 必须用 this.$util.redirectTo,内部对 TabBar 页面自动走 uni.switchTab
|
||
// (鸿蒙元服务下还会用 reLaunch 兜底,见 common/js/util.js)
|
||
// H5 端保留 show_home / goHome(虽然模板里不渲染自绘 navbar,但保留方法调用不报错)
|
||
goHome() {
|
||
this.$util.redirectTo(this.$util.INDEX_PAGE_URL, {}, 'redirectTo');
|
||
},
|
||
getData() {
|
||
this.$api.sendRequest({
|
||
url: '/form/api/form/info',
|
||
data: {
|
||
form_id: this.id,
|
||
uniacid: this.uniacid
|
||
},
|
||
success: res => {
|
||
if (res.code == 0 && res.data) {
|
||
this.detail = res.data
|
||
this.updateNavbarTitle();
|
||
// 行为设置(后端已合并默认值):验证码开启时拉取图形验证码
|
||
this.behavior = (res.data.form_config && res.data.form_config.behavior) || {}
|
||
if (this.behavior.captcha == 1) this.getCaptcha()
|
||
} else if (res.message == 'FORM_LOGIN_REQUIRED') {
|
||
// 打开需登录:弹登录组件,登录成功后 watch storeToken 重新拉取
|
||
this.$refs.login ? this.$refs.login.open(this.backUrl)
|
||
: (this.formError = this.$lang('form.form.loginRequired'))
|
||
} else if (res.message == 'FORM_NOT_OPEN') {
|
||
this.formError = this.$lang('form.form.notOpen')
|
||
} else {
|
||
this.formError = res.message || ''
|
||
}
|
||
this.$refs.loadingCover && this.$refs.loadingCover.hide()
|
||
},
|
||
fail: () => {
|
||
this.$refs.loadingCover && this.$refs.loadingCover.hide()
|
||
}
|
||
})
|
||
},
|
||
updateNavbarTitle() {
|
||
if (!this.detail?.form_name) return;
|
||
let formName = this.detail.form_name;
|
||
// #ifdef H5
|
||
if (this.enableSetTitle && this.formTitle) {
|
||
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
|
||
},
|
||
// 图形验证码(点击图片刷新;提交失败后自动刷新)
|
||
getCaptcha() {
|
||
this.$api.sendRequest({
|
||
url: '/api/captcha/captcha',
|
||
success: res => {
|
||
if (res.code == 0 && res.data) {
|
||
this.captcha.id = res.data.id
|
||
this.captcha.img = res.data.img
|
||
this.captcha.code = ''
|
||
}
|
||
}
|
||
})
|
||
},
|
||
// 验证码模式下本页自有提交按钮:先走 diy-form 校验与取数,再进 create
|
||
submitFromPage() {
|
||
if (this.isRepeat) return;
|
||
if (!this.$refs.form || !this.$refs.form.verify()) return;
|
||
const formData = this.$refs.form.getFormData();
|
||
this.create(formData);
|
||
},
|
||
// 填完跳转:消费统一链接选择器的完整对象(wap_url 外链/页面路径、外部小程序、拨打电话)
|
||
doRedirect(link) {
|
||
const fallback = () => { this.complete = true; this.detail = null; };
|
||
if (!link) return fallback();
|
||
if (link.appid) {
|
||
// 跳转其他微信小程序(仅小程序端可用;H5 场景直接回退完成态)
|
||
// #ifdef MP-WEIXIN
|
||
wx.navigateToMiniProgram({ appId: link.appid, path: link.page || '', fail: fallback });
|
||
return;
|
||
// #endif
|
||
fallback();
|
||
} else if (link.mobile) {
|
||
uni.makePhoneCall({ phoneNumber: String(link.mobile), fail: fallback });
|
||
fallback();
|
||
} else if (link.wap_url) {
|
||
if (/^https?:\/\//i.test(link.wap_url)) {
|
||
uni.navigateTo({ url: '/pages_tool/webview/webview?src=' + encodeURIComponent(link.wap_url), fail: fallback });
|
||
} else {
|
||
uni.navigateTo({ url: link.wap_url, fail: fallback });
|
||
}
|
||
} else {
|
||
fallback();
|
||
}
|
||
},
|
||
// 由 diy-form 内置提交按钮触发:diy-form handleSubmit 已先做 verify(),
|
||
// 仅在通过时才通过 :onSubmit 函数 prop 调用本方法(跨 H5 / MP 一致,不走 $emit / $listeners)。
|
||
// 注意:本方法通过 created 中 this.handleSubmit = this.create.bind(this) 预绑定 this,
|
||
// 因此内部所有 this.xxx 都指向 formdata 实例,不会漂移到子组件。
|
||
create(formData) {
|
||
// 防重复提交:console 在拦截之前,避免日志噪声;拦截后立即置位
|
||
if (this.isRepeat) return;
|
||
|
||
const behavior = this.behavior || {};
|
||
// 填写需登录 / 限填(后端强制,前端提前拦截优化体验)
|
||
if ((behavior.require_login_submit == 1 || behavior.submit_limit > 0) && !this.storeToken) {
|
||
this.$refs.login && this.$refs.login.open(this.backUrl);
|
||
return;
|
||
}
|
||
// 验证码必填
|
||
if (behavior.captcha == 1 && !this.captcha.code) {
|
||
this.$util.showToast({ title: this.$lang('form.form.captchaRequired') });
|
||
return;
|
||
}
|
||
|
||
this.isRepeat = true;
|
||
this.$api.sendRequest({
|
||
url: '/form/api/form/create',
|
||
data: {
|
||
form_id: this.id,
|
||
form_data: typeof formData === 'string' ? formData : JSON.stringify(formData),
|
||
uniacid: this.uniacid,
|
||
url_params: JSON.stringify(this.urlParams),
|
||
member_id: this.memberInfo ? this.memberInfo.id : 0,
|
||
captcha_id: this.captcha.id || '',
|
||
captcha_code: this.captcha.code || ''
|
||
},
|
||
success: res => {
|
||
this.isRepeat = false;
|
||
if (res.code == 0) {
|
||
const link = behavior.redirect;
|
||
if (link) {
|
||
this.$util.showToast({ title: this.$lang('form.formdata.submitSuccess') });
|
||
setTimeout(() => this.doRedirect(link), 800);
|
||
} else {
|
||
this.complete = true
|
||
uni.showModal({
|
||
title: this.$lang('form.formdata.tip'),
|
||
content: this.$lang('form.formdata.submitSuccess'),
|
||
showCancel: false,
|
||
success: () => { }
|
||
})
|
||
}
|
||
} else {
|
||
if (res.message == 'FORM_LOGIN_REQUIRED') {
|
||
this.$refs.login && this.$refs.login.open(this.backUrl)
|
||
} else if (res.message == 'CAPTCHA_ERROR' || res.message == 'CAPTCHA_FAILURE') {
|
||
this.$util.showToast({ title: this.$lang('form.form.captchaError') })
|
||
if (behavior.captcha == 1) this.getCaptcha()
|
||
} else if (res.message == 'FORM_SUBMIT_LIMIT') {
|
||
this.$util.showToast({ title: this.$lang('form.form.submitLimit') })
|
||
this.complete = true
|
||
} else if (res.message == 'FORM_NOT_OPEN') {
|
||
this.$util.showToast({ title: this.$lang('form.form.notOpen') })
|
||
} else {
|
||
this.$util.showToast({
|
||
title: res.message
|
||
})
|
||
}
|
||
}
|
||
},
|
||
fail: () => {
|
||
// 网络失败时复位防重复标记,允许用户重试
|
||
this.isRepeat = false
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/*
|
||
* 自绘 navbar(非 H5 端:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务)
|
||
* - pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar
|
||
* - 整体高度由 inline style 注入:statusBarHeight + 44px
|
||
* - 左侧 Home 胶囊 + 中央标题 + 右侧占位,标题过长自动省略
|
||
*/
|
||
/* #ifndef H5 */
|
||
|
||
/* --- 自绘 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;
|
||
}
|
||
|
||
/* #endif */
|
||
</style>
|
||
|
||
<style lang="scss" scoped>
|
||
page {
|
||
background: #fff;
|
||
}
|
||
|
||
// ===== 非鸿蒙端(H5 / 微信小程序 / App):保留原有 WebKit 滚动 / 安全区处理 =====
|
||
// 鸿蒙走下方 MP-HARMONY 专属的 scroll-view 方案,不编译这些 WebKit 属性
|
||
/* #ifdef H5 || MP-WEIXIN || APP-PLUS */
|
||
.page {
|
||
min-height: 100vh;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
box-sizing: border-box;
|
||
}
|
||
.system-form-wrap {
|
||
overflow: visible;
|
||
margin: 0 0 60rpx 0;
|
||
padding-bottom: env(safe-area-inset-bottom, 40rpx);
|
||
}
|
||
/* #endif */
|
||
|
||
// ===== 鸿蒙元服务:用原生 scroll-view 做滚动容器 =====
|
||
// ArkUI 不识别 WebKit 的 overflow / -webkit-overflow-scrolling,必须用 scroll-view 组件(编译为 ArkUI Scroll)。
|
||
// 根容器 harmony-root 占满 100vh 并 flex 列布局,navbar 占顶部固定高度,page flex:1 占剩余空间,
|
||
// scroll-view 在 page 内 flex:1 获得确定高度后内部滚动。提交按钮由 diy-form 渲染在 scroll-view 内,可滚动到。
|
||
/* #ifdef MP-HARMONY */
|
||
.harmony-root {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.harmony-page {
|
||
flex: 1;
|
||
min-height: 0; // 允许内容收缩,内部 scroll-view 才能拿到确定高度并正确滚动
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.form-scroll {
|
||
flex: 1;
|
||
height: 100%;
|
||
}
|
||
.system-form-wrap {
|
||
// 不再用 overflow:hidden 裁剪;提交按钮在 scroll-view 内,滚动可见
|
||
overflow: visible;
|
||
margin: 0 0 60rpx 0;
|
||
padding-bottom: 40rpx; // 鸿蒙不识别 env(safe-area-inset-bottom),固定值兜底
|
||
}
|
||
/* #endif */
|
||
|
||
// 穿透 diy-form 包装层:消除其自带的 10rpx 圆角与白色背景,
|
||
// 让 form 内容区域保持与原始 ns-newform 接入时一致的视觉
|
||
.system-form-wrap ::v-deep .diy-from {
|
||
background: transparent;
|
||
border-radius: 0;
|
||
}
|
||
|
||
// ===== 行为设置:验证码行 + 自有提交按钮(diy-form 内置按钮被 showSubmit=false 隐藏时接管) =====
|
||
.form-extra-wrap {
|
||
padding: 0 28rpx 20rpx;
|
||
}
|
||
.captcha-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 16rpx 20rpx;
|
||
margin-top: 24rpx;
|
||
|
||
.captcha-input {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
font-size: 28rpx;
|
||
border: 2rpx solid #e5e7eb;
|
||
border-radius: 12rpx;
|
||
padding: 0 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.captcha-img {
|
||
width: 200rpx;
|
||
height: 72rpx;
|
||
margin-left: 20rpx;
|
||
border-radius: 12rpx;
|
||
border: 2rpx solid #e5e7eb;
|
||
}
|
||
}
|
||
// 提交按钮:视觉对齐 diy-form 内置按钮(红底白字圆角,medium 内边距)
|
||
.custom-submit {
|
||
margin-top: 30rpx;
|
||
padding: 24rpx 0;
|
||
text-align: center;
|
||
color: #ffffff;
|
||
background-color: #ff4544;
|
||
border-radius: 12rpx;
|
||
font-size: 30rpx;
|
||
}
|
||
|
||
::v-deep .mescroll-totop {
|
||
/* #ifdef H5 */
|
||
right: 27rpx !important;
|
||
bottom: 120rpx !important;
|
||
/* #endif */
|
||
/* #ifdef MP */
|
||
right: 27rpx !important;
|
||
bottom: 180rpx !important;
|
||
/* #endif */
|
||
}
|
||
</style>
|