chore:表单样式修改表单浮于图片上方

This commit is contained in:
2026-09-01 08:54:26 +08:00
parent f66b70c087
commit 278d8c94f9
3 changed files with 182 additions and 20 deletions

View File

@@ -202,7 +202,7 @@ const localDevConfig = ({
// })['2724']; // 生物菌肥 // })['2724']; // 生物菌肥
// })['2811']; // 选择要使用的环境配置 // })['2811']; // 选择要使用的环境配置
// })['local-11']; // 选择要使用的环境配置 // })['local-11']; // 选择要使用的环境配置
})['2482']; // 本地 })['2811']; // 本地
// })['local-2']; // 本地 // })['local-2']; // 本地
// })['local-11']; // 本地 // })['local-11']; // 本地
// })['1-test']; // test // })['1-test']; // test

View File

@@ -6,24 +6,44 @@
否则会触发 MP 编译器注入的 propsToData 观察者(对应编译产物里 否则会触发 MP 编译器注入的 propsToData 观察者(对应编译产物里
r=function(){var o=this.value.show_banner...this.$mp.data...}) r=function(){var o=this.value.show_banner...this.$mp.data...})
报 "Cannot read properties of undefined (reading 'show_banner')"。 --> 报 "Cannot read properties of undefined (reading 'show_banner')"。 -->
<view v-if="value && value.show_banner && value.form_banner" class="form-banner" :style="bannerStyle"> <view v-if="value && value.show_banner && value.form_banner" class="form-banner is-card-overlap" :style="bannerStyle">
<image :src="$util.img(value.form_banner)" mode="widthFix" class="banner-img" /> <image :src="$util.img(value.form_banner)" mode="widthFix" class="banner-img" />
</view> </view>
<!-- 提示语 --> <!-- 卡片容器:仅在后台配置了 banner 图片时启用,让表单浮在 banner 之上(参考左图布局)。
<view v-if="value && value.show_tip && value.form_tip" class="form-tip" :style="tipStyle">{{ value && value.form_tip }}</view> 无 banner 时不渲染 .form-card,保持原有平铺行为,零外溢。 -->
<!-- 表单组件 --> <view v-if="hasBannerMode" class="form-card" :style="cardWrapStyle">
<ns-newform <!-- 提示语 -->
:data="formComponents" <view v-if="value && value.show_tip && value.form_tip" class="form-tip" :style="tipStyle">{{ value && value.form_tip }}</view>
:customAttr="value" <!-- 表单组件 -->
:mode="mergedFormConfig.formLayout || 'vertical'" <ns-newform
:formConfig="mergedFormConfig" :data="formComponents"
ref="nsNewform" :customAttr="value"
></ns-newform> :mode="mergedFormConfig.formLayout || 'vertical'"
<!-- 提交按钮(外层 = form-bg + form-padding-x;按钮 = submit-bg + submit-color + input-height + input-radius)。 :formConfig="mergedFormConfig"
showSubmit=false 时由父级页面渲染自己的提交按钮并通过 ref 调用本组件的 verify()/getFormData()。 --> ref="nsNewform"
<view v-if="showSubmit" class="form-submit-wrap" :style="submitWrapStyle"> ></ns-newform>
<view @click.stop="handleSubmit" class="form-submit" :style="submitStyle">{{ submitText }}</view> <!-- 提交按钮(外层 = form-bg + form-padding-x;按钮 = submit-bg + submit-color + input-height + input-radius)。
showSubmit=false 时由父级页面渲染自己的提交按钮并通过 ref 调用本组件的 verify()/getFormData()。 -->
<view v-if="showSubmit" class="form-submit-wrap" :style="submitWrapStyle">
<view @click.stop="handleSubmit" class="form-submit" :style="submitStyle">{{ submitText }}</view>
</view>
</view> </view>
<!-- 无 banner 时仍按旧布局直接渲染 tip / ns-newform / submit,避免影响其他使用方(diy-group / diy-article 等) -->
<template v-else>
<!-- 提示语 -->
<view v-if="value && value.show_tip && value.form_tip" class="form-tip" :style="tipStyle">{{ value && value.form_tip }}</view>
<!-- 表单组件 -->
<ns-newform
:data="formComponents"
:customAttr="value"
:mode="mergedFormConfig.formLayout || 'vertical'"
:formConfig="mergedFormConfig"
ref="nsNewform"
></ns-newform>
<view v-if="showSubmit" class="form-submit-wrap" :style="submitWrapStyle">
<view @click.stop="handleSubmit" class="form-submit" :style="submitStyle">{{ submitText }}</view>
</view>
</template>
</view> </view>
</template> </template>
@@ -108,6 +128,30 @@ export default {
padding: `0 ${this._num(this.mergedFormConfig && this.mergedFormConfig.bannerPaddingX, 0)}rpx` padding: `0 ${this._num(this.mergedFormConfig && this.mergedFormConfig.bannerPaddingX, 0)}rpx`
}); });
}, },
// 后台配了 banner 图片时启用卡片浮起布局(与参考设计左图一致);无 banner 走原平铺,零外溢
hasBannerMode() {
const v = this.value || {};
return !!(v.show_banner && v.form_banner);
},
// 卡片容器内联样式:maring-top(负偏移让卡片向上压住 banner 底部)+ border-radius + padding-x(与 bannerPaddingX 对齐)+ box-shadow。
// 数值由 formConfig 走 camelCase/snake_case 双读,缺省走默认值,使后端零配置时也能看到卡片浮起效果。
cardWrapStyle() {
const cfg = this.mergedFormConfig || {};
const sidePad = this._num(cfg.cardSidePad, this._num(cfg.bannerPaddingX, 28));
// overlap:负值,让卡片向上"压"住 banner 底部(与设计图一致)
const overlap = -Math.abs(this._num(cfg.cardOverlap, 48));
// 卡片圆角:缺省 24rpx
const radius = this._num(cfg.cardRadius, 24);
// 阴影:缺省中等阴影(与设计方案一致)
const shadow = cfg.cardShadow || '0 8rpx 24rpx rgba(0,0,0,0.08)';
return this.$util.objToStyle({
marginTop: overlap + 'rpx',
paddingLeft: sidePad + 'rpx',
paddingRight: sidePad + 'rpx',
borderRadius: radius + 'rpx',
boxShadow: shadow
});
},
// 提示语样式:padding-x、font-size、color、bg-color、border-bottom(与后端 .form-preview-tip 对齐) // 提示语样式:padding-x、font-size、color、bg-color、border-bottom(与后端 .form-preview-tip 对齐)
tipStyle() { tipStyle() {
const cfg = this.mergedFormConfig || {}; const cfg = this.mergedFormConfig || {};
@@ -229,7 +273,12 @@ export default {
tipFontSize: pick('tipFontSize', 'tip_font_size'), tipFontSize: pick('tipFontSize', 'tip_font_size'),
tipColor: pick('tipColor', 'tip_color'), tipColor: pick('tipColor', 'tip_color'),
tipBgColor: pick('tipBgColor', 'tip_bg_color'), tipBgColor: pick('tipBgColor', 'tip_bg_color'),
tipBorderColor: pick('tipBorderColor', 'tip_border_color') tipBorderColor: pick('tipBorderColor', 'tip_border_color'),
// 卡片浮起(仅 show_banner && form_banner 启用)
cardOverlap: pick('cardOverlap', 'card_overlap'),
cardRadius: pick('cardRadius', 'card_radius'),
cardShadow: pick('cardShadow', 'card_shadow'),
cardSidePad: pick('cardSidePad', 'card_side_pad')
}; };
// 移除 undefined / 空字符串(保留 0/false) // 移除 undefined / 空字符串(保留 0/false)
Object.keys(base).forEach(function(k) { Object.keys(base).forEach(function(k) {
@@ -392,12 +441,35 @@ export default {
// 默认左右无边距(与后端 --banner-padding-x 对齐;由内联 bannerStyle 覆盖) // 默认左右无边距(与后端 --banner-padding-x 对齐;由内联 bannerStyle 覆盖)
margin: 0; margin: 0;
// banner 图片作为卡片上方的"底图":建立层叠上下文,让 .form-card 通过 z-index 浮在其上。
// 仅在卡片浮起模式(.is-card-overlap)启用,不影响其它使用方(diy-group 等)的旧布局。
&.is-card-overlap {
position: relative;
z-index: 1;
}
.banner-img { .banner-img {
width: 100%; width: 100%;
display: block; display: block;
} }
} }
// 卡片容器:浮在 banner 图片上方(参考设计左图)。
// 仅在 hasBannerMode=true 时渲染(见 template v-if);
// 外边距 / 圆角 / 阴影 / 左右内边距 全部由 cardWrapStyle 内联写入(读取后台 formConfig.card*)。
.form-card {
position: relative;
z-index: 2;
background-color: #ffffff;
// 兜底圆角:与后端未配置时内联写入的 24rpx 一致;后台配置时由内联覆盖
border-radius: 24rpx;
// 兜底阴影:与后端未配置时内联写入的中等阴影一致
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
// 顶部负 margin 由内联 cardWrapStyle 写入(cardOverlap,缺省 -48rpx 把卡片向上压住 banner 底部)
// 底部留白让卡片与后面内容(如推荐码、其它 diy 组件)有间距
padding-bottom: 24rpx;
}
.form-tip { .form-tip {
// 提示语:上下内边距 / 左右内边距 / 字号 / 颜色 / 背景 全部由 tipStyle 内联写入 // 提示语:上下内边距 / 左右内边距 / 字号 / 颜色 / 背景 全部由 tipStyle 内联写入
padding: 0; padding: 0;

View File

@@ -5,6 +5,10 @@
<!-- #ifndef MP-HARMONY --> <!-- #ifndef MP-HARMONY -->
<view :style="themeColor"> <view :style="themeColor">
<!-- #endif --> <!-- #endif -->
<!-- 顶部图条:位于 navbar 之下、表单卡片之上。宽度 calc(100% - 2*containerInset) 水平居中,
留下与卡片等宽的左右对称留白(默认容器内缩 50rpx ≈ 屏宽 6.7%,即内容宽 87%)。
图片由后台 form_config.bg_image / bgImage 下发;未配置时不渲染,保持原来的纯白布局。 -->
<view v-if="bgImage" class="formdata-bg" :style="bgLayerStyle"></view>
<!-- <!--
自绘导航栏(仅在 navigationStyle:custom 的端渲染:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务) 自绘导航栏(仅在 navigationStyle:custom 的端渲染:APP / 微信小程序 / 华为快应用 / 鸿蒙元服务)
- pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar - pages.json 已为这几端开启 navigationStyle:custom,系统不再提供原生 navbar
@@ -34,7 +38,7 @@
<!-- #ifdef MP-HARMONY --> <!-- #ifdef MP-HARMONY -->
<view class="page harmony-page"> <view class="page harmony-page">
<scroll-view scroll-y="true" :show-scrollbar="false" class="form-scroll"> <scroll-view scroll-y="true" :show-scrollbar="false" class="form-scroll">
<view class="system-form-wrap"> <view class="system-form-wrap" :style="containerStyle">
<diy-form ref="form" :value="diyValue" dataMode="raw" :showSubmit="!showCaptcha" :onSubmit="handleSubmit" /> <diy-form ref="form" :value="diyValue" dataMode="raw" :showSubmit="!showCaptcha" :onSubmit="handleSubmit" />
<!-- 行为设置:提交验证码开启时,隐藏 diy-form 内置按钮,由本页渲染验证码+提交按钮 --> <!-- 行为设置:提交验证码开启时,隐藏 diy-form 内置按钮,由本页渲染验证码+提交按钮 -->
<view v-if="showCaptcha" class="form-extra-wrap"> <view v-if="showCaptcha" class="form-extra-wrap">
@@ -56,7 +60,7 @@
<!-- #endif --> <!-- #endif -->
<!-- #ifndef MP-HARMONY --> <!-- #ifndef MP-HARMONY -->
<view class="page"> <view class="page">
<view class="system-form-wrap"> <view class="system-form-wrap" :style="containerStyle">
<!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。 <!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。
:onSubmit 接管提交逻辑(带 uniacid + showModal);跨 H5 / MP 一致,不依赖 $listeners。 :onSubmit 接管提交逻辑(带 uniacid + showModal);跨 H5 / MP 一致,不依赖 $listeners。
注意:create 重度依赖本组件 this(id/uniacid/memberInfo/$api 等),必须在 created 阶段 bind this, 注意:create 重度依赖本组件 this(id/uniacid/memberInfo/$api 等),必须在 created 阶段 bind this,
@@ -210,12 +214,60 @@ export default {
return { return {
jsonData: this.detail.json_data, jsonData: this.detail.json_data,
formConfig: Object.assign({}, this.detail.form_config || {}, styleDefaults), formConfig: Object.assign({}, this.detail.form_config || {}, styleDefaults),
show_banner: this.detail.show_banner, // 全屏底图模式下隐藏卡片内 banner,避免与底图重复(见 hideInnerBanner)
show_banner: this.hideInnerBanner ? 0 : this.detail.show_banner,
form_banner: this.detail.form_banner || 'public/uniapp/form/banner.png', form_banner: this.detail.form_banner || 'public/uniapp/form/banner.png',
show_tip: this.detail.show_tip, show_tip: this.detail.show_tip,
form_tip: this.detail.form_tip || this.$lang('form.formdata.formTipPlaceholder') form_tip: this.detail.form_tip || this.$lang('form.formdata.formTipPlaceholder')
} }
}, },
// ===== 容器左右内缩(默认开启,不依赖 bgImage) =====
// 图条图片:后台 form_config.bg_image / bgImage 下发(兼容 snake_case / camelCase)。
// 未配置时不渲染图条,但表单卡片本身已有内缩效果(见 .system-form-wrap 默认 padding)
bgImage() {
if (!this.detail) return '';
const fc = this.detail.form_config || {};
const raw = fc.bgImage || fc.bg_image || fc.backgroundImage || fc.background_image || '';
return raw ? this.$util.img(raw) : '';
},
// 容器左右内缩(rpx)。后台 form_config.container_inset / containerInset 可调;
// 默认 50rpx,在 750rpx 屏宽下左右各留 50rpx ≈ 6.7%,内容宽 ≈ 87%,
// 与参考图一致;CSS .system-form-wrap 中也写了这个默认 padding(防 H5 computed 未及时就绪时也生效)
containerInset() {
const fc = (this.detail && this.detail.form_config) || {};
const raw = fc.containerInset !== undefined ? fc.containerInset : fc.container_inset;
const n = parseInt(raw);
return isNaN(n) ? 50 : Math.min(150, Math.max(0, n));
},
// 顶部图条高度(rpx)。后台 form_config.bg_image_height 可调;clamp 80-600
bgImageHeight() {
const fc = (this.detail && this.detail.form_config) || {};
const raw = fc.bgImageHeight !== undefined ? fc.bgImageHeight : fc.bg_image_height;
const n = parseInt(raw);
return isNaN(n) ? 200 : Math.min(600, Math.max(80, n));
},
// 图条与表单卡片之间的间距(rpx)。后台 form_config.card_gap 可调;clamp 0-80
cardGap() {
const fc = (this.detail && this.detail.form_config) || {};
const raw = fc.cardGap !== undefined ? fc.cardGap : fc.card_gap;
const n = parseInt(raw);
return isNaN(n) ? 24 : Math.min(80, Math.max(0, n));
},
// 图条内联样式:水平居中(width=100%-2*inset + 左右各 inset 边距),高度由配置控制
bgLayerStyle() {
const inset = this.containerInset;
return 'width:calc(100% - ' + (inset * 2) + 'rpx);margin:0 ' + inset + 'rpx;height:' + this.bgImageHeight + 'rpx;background-image:url(' + this.bgImage + ');';
},
// 表单容器内联样式:覆盖 .system-form-wrap 默认 padding,使后台可配。始终返回有效样式(含默认值),
// 不再依赖 bgImage 开关 —— 表单默认就有左右内缩居中(不需后台任何配置)
containerStyle() {
return 'padding-left:' + this.containerInset + 'rpx;padding-right:' + this.containerInset + 'rpx;';
},
// 图条模式下隐藏 diy-form 卡片顶部的 banner,避免与图条重复
// 如需保留卡片内 banner:把下面这行改成 `return false;` 即可
hideInnerBanner() {
return !!this.bgImage;
},
// 行为设置:提交验证码开启时 diy-form 隐藏内置提交按钮,本页渲染验证码 + 自有提交按钮 // 行为设置:提交验证码开启时 diy-form 隐藏内置提交按钮,本页渲染验证码 + 自有提交按钮
showCaptcha() { showCaptcha() {
return this.behavior.captcha == 1; return this.behavior.captcha == 1;
@@ -905,16 +957,40 @@ page {
// ===== 非鸿蒙端(H5 / 微信小程序 / App):保留原有 WebKit 滚动 / 安全区处理 ===== // ===== 非鸿蒙端(H5 / 微信小程序 / App):保留原有 WebKit 滚动 / 安全区处理 =====
// 鸿蒙走下方 MP-HARMONY 专属的 scroll-view 方案,不编译这些 WebKit 属性 // 鸿蒙走下方 MP-HARMONY 专属的 scroll-view 方案,不编译这些 WebKit 属性
/*
* ===== 顶部图条 =====
* 位于 navbar 之下、表单卡片之上。宽度由内联 bgLayerStyle 注入(calc(100% - 2*inset)),
* 与下方表单卡片等宽、左右各留 inset 对称留白。圆角 + overflow 防止图片溢出。
* 未配置 form_config.bg_image 时该节点不渲染(v-if="bgImage"),页面保持原来的纯白样式。
*/
.formdata-bg {
position: relative;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
}
/* #ifdef H5 || MP-WEIXIN || APP-PLUS */ /* #ifdef H5 || MP-WEIXIN || APP-PLUS */
.page { .page {
min-height: 100vh; min-height: 100vh;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
box-sizing: border-box; box-sizing: border-box;
/* 页面容器不再垫白底 —— 只有红色框区域(.system-form-wrap 表单卡片)保留白底,
图条上下、左右留白等区域透出页面默认底色 */
} }
.system-form-wrap { .system-form-wrap {
overflow: visible; overflow: visible;
margin: 0 0 60rpx 0; margin: 0 0 60rpx 0;
/* ===== 表单卡片:唯一保留白底的区域 =====
87% 宽居中 + 16rpx 圆角 + 白底,对应红色框范围(从图条底部到页面底部) */
padding: 0 50rpx;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 16rpx;
padding-bottom: env(safe-area-inset-bottom, 40rpx); padding-bottom: env(safe-area-inset-bottom, 40rpx);
} }
/* #endif */ /* #endif */
@@ -934,6 +1010,7 @@ page {
min-height: 0; // 允许内容收缩,内部 scroll-view 才能拿到确定高度并正确滚动 min-height: 0; // 允许内容收缩,内部 scroll-view 才能拿到确定高度并正确滚动
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* 鸿蒙端与 .page 保持一致:去掉白底,只有 .system-form-wrap 是白底卡片 */
} }
.form-scroll { .form-scroll {
flex: 1; flex: 1;
@@ -943,10 +1020,23 @@ page {
// 不再用 overflow:hidden 裁剪;提交按钮在 scroll-view 内,滚动可见 // 不再用 overflow:hidden 裁剪;提交按钮在 scroll-view 内,滚动可见
overflow: visible; overflow: visible;
margin: 0 0 60rpx 0; margin: 0 0 60rpx 0;
// ===== 表单卡片:默认就是「白底圆角、左右内缩居中」(参考图效果),不依赖后台配置 =====
padding: 0 50rpx;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 16rpx;
padding-bottom: 40rpx; // 鸿蒙不识别 env(safe-area-inset-bottom),固定值兜底 padding-bottom: 40rpx; // 鸿蒙不识别 env(safe-area-inset-bottom),固定值兜底
} }
/* #endif */ /* #endif */
/*
* ===== .form-card 兼容占位(旧模板/旧调用残留) =====
* 旧实现用 .form-card 做条件类加强化,现已将白底/圆角合并到 .system-form-wrap 默认样式中。
* 保留此类名是为了不破坏潜在的 :class="formCardClass" 引用(已从模板移除,但旧页面缓存可能用到);
* 这里只做 noop,不影响任何视觉。
*/
.form-card {}
// 穿透 diy-form 包装层:消除其自带的 10rpx 圆角与白色背景, // 穿透 diy-form 包装层:消除其自带的 10rpx 圆角与白色背景,
// 让 form 内容区域保持与原始 ns-newform 接入时一致的视觉 // 让 form 内容区域保持与原始 ns-newform 接入时一致的视觉
.system-form-wrap ::v-deep .diy-from { .system-form-wrap ::v-deep .diy-from {