refactor(diy-form&ns-newform): 重构表单组件样式与配置体系

1.  统一表单样式配置为中心化CSS变量方案,替换原有硬编码样式
2.  新增表单配置合并逻辑,兼容新旧数据格式与平台差异化配置
3.  拆分提交按钮与提示语样式,实现完全可配置化
4.  优化ns-newform组件的样式复用与响应式布局逻辑
5.  修复多端适配与占位符默认值问题
This commit is contained in:
2026-06-11 09:45:09 +08:00
parent 2f5f23d21a
commit fae1d77ba5
2 changed files with 667 additions and 204 deletions

View File

@@ -1,25 +1,23 @@
<template>
<view data-component-name="diy-form" class="diy-from" :class="[formLayoutClass]" :style="style">
<view data-component-name="diy-form" class="diy-from" :class="[formLayoutClass]" :style="[diyFormStyle, cssVars]">
<!-- Banner -->
<view v-if="value.show_banner && value.form_banner" class="form-banner">
<view v-if="value.show_banner && value.form_banner" class="form-banner" :style="bannerStyle">
<image :src="$util.img(value.form_banner)" mode="widthFix" class="banner-img" />
</view>
<!-- 提示语 -->
<view v-if="value.show_tip && value.form_tip" class="form-tip">{{ value.form_tip }}</view>
<view v-if="value.show_tip && value.form_tip" class="form-tip" :style="tipStyle">{{ value.form_tip }}</view>
<!-- 表单组件 -->
<ns-newform
:data="formComponents"
:customAttr="value"
:mode="value.form_layout === 'vertical' ? 'vertical' : 'horizontal'"
:labelWidthMode="value.label_width_mode"
:labelFixedWidth="value.label_fixed_width"
:labelFontSize="value.label_font_size"
:labelColor="value.label_color"
:requiredColor="value.required_color"
:mode="formCfg.form_layout === 'vertical' ? 'vertical' : 'horizontal'"
:formConfig="mergedFormConfig"
ref="nsNewform"
></ns-newform>
<!-- 提交按钮 -->
<view @click.stop="submitForm" class="fui-btn btn-danger block mtop">提交信息</view>
<!-- 提交按钮(外层 = form-bg + form-padding-x;按钮 = submit-bg + submit-color + input-height + input-radius) -->
<view class="form-submit-wrap" :style="submitWrapStyle">
<view @click.stop="submitForm" class="form-submit" :style="submitStyle">{{ submitText }}</view>
</view>
</view>
</template>
@@ -65,8 +63,148 @@ export default {
}
return css;
},
// 兼容旧 style 别名(部分页面用 :style="style" 引用)
diyFormStyle() {
return this.style;
},
// 将后端 formConfig 写入 CSS 变量:与后端 design.css :root 对齐,
// 让 banner / tip / submit 与 ns-newform 子组件共享同一套样式变量
cssVars() {
const cfg = this.mergedFormConfig || {};
const rpx = (v) => {
if (v === '' || v === null || v === undefined) return '';
return typeof v === 'number' ? `${v}rpx` : `${v}`;
};
const showDivider = this._coerceBool(cfg.showRowDivider);
return {
'--form-padding-x': rpx(cfg.formPaddingX || 14),
'--banner-padding-x': rpx(cfg.bannerPaddingX || 0),
'--tip-padding-x': rpx(cfg.tipPaddingX || 14),
'--tip-font-size': rpx(cfg.tipFontSize || 24),
'--tip-color': cfg.tipColor || '#666666',
'--tip-bg-color': cfg.tipBgColor || '#ffffff',
'--row-padding-y': rpx(cfg.rowPaddingY || 4),
'--row-min-height': rpx(cfg.rowMinHeight || 60),
'--label-gap': rpx(cfg.labelGap || 16),
'--form-bg-color': cfg.formBgColor || '#ffffff',
'--row-divider-color': cfg.rowDividerColor || '#f0f0f0',
'--input-height': rpx(cfg.inputHeight || 60),
'--input-radius': rpx(cfg.inputRadius || 10),
'--label-fixed-width': rpx(cfg.labelFixedWidth || 180),
'--label-font-size': rpx(cfg.labelFontSize || 28),
'--label-color': cfg.labelColor || '#1f2937',
'--required-color': cfg.requiredColor || '#ef4444',
'--checked-color': cfg.checkedColor || '#ff4544',
'--submit-color': cfg.submitColor || '#ffffff',
'--submit-bg-color': cfg.submitBgColor || '#ff4544',
'--placeholder-color': '#ddd',
'--show-row-divider': showDivider ? '1' : '0'
};
},
// Banner 容器左右内边距(与后端 .form-preview-banner padding-x 对齐)
bannerStyle() {
return {
padding: `0 ${this._num(this.mergedFormConfig && this.mergedFormConfig.bannerPaddingX, 0)}rpx`
};
},
// 提示语样式:padding-x、font-size、color、bg-color(与后端 .form-preview-tip 对齐)
tipStyle() {
const cfg = this.mergedFormConfig || {};
return {
padding: `${this._num(cfg.rowPaddingY, 4)}rpx ${this._num(cfg.tipPaddingX, 14)}rpx`,
'font-size': `${this._num(cfg.tipFontSize, 24)}rpx`,
'color': cfg.tipColor || '#666666',
'background-color': cfg.tipBgColor || 'transparent'
};
},
// 提交按钮外层:与后端 .form-preview-submit 对齐(padding: row-padding-y form-padding-x;background: form-bg-color)
submitWrapStyle() {
const cfg = this.mergedFormConfig || {};
return {
padding: `${this._num(cfg.rowPaddingY, 4)}rpx ${this._num(cfg.formPaddingX, 14)}rpx`,
'background-color': cfg.formBgColor || '#ffffff'
};
},
// 提交按钮样式:颜色 / 背景色 / 高度 / 圆角 / 字号(与后端 .form-preview-submit button 对齐)
submitStyle() {
const cfg = this.mergedFormConfig || {};
return {
color: cfg.submitColor || '#ffffff',
'background-color': cfg.submitBgColor || '#ff4544',
'line-height': `${this._num(cfg.inputHeight, 60)}rpx`,
'border-radius': `${this._num(cfg.inputRadius, 10)}rpx`,
'font-size': `${this._num(cfg.labelFontSize, 28)}rpx`
};
},
// 提交按钮文字:与后端 design.html <button>提交</button> 默认对齐,开放给 formConfig 覆盖
submitText() {
return this.formCfg.submit_text || '提交';
},
formLayoutClass() {
return this.value.form_layout === 'vertical' ? 'form-vertical' : 'form-horizontal';
return this.formCfg.form_layout === 'vertical' ? 'form-vertical' : 'form-horizontal';
},
// 后端把表单样式配置统一存放在 data.formConfig 子对象中(与后端 design.js getDefaultFormData().formConfig 字段名一致)。
// 这里做一次归一化:优先取 formConfig;如果历史数据是扁平结构(直接挂在 value 上),也做兼容。
formCfg() {
const v = this.value || {};
if (v.formConfig && typeof v.formConfig === 'object') {
return v.formConfig;
}
return v;
},
// 合并后的表单配置(支持平台差异化)
mergedFormConfig() {
const v = this.value || {};
const fc = this.formCfg;
// 通用全局配置(与后端 design.js getDefaultFormData().formConfig 字段名一致)
const base = {
formPaddingX: fc.form_padding_x,
bannerPaddingX: fc.banner_padding_x,
tipPaddingX: fc.tip_padding_x,
rowPaddingY: fc.row_padding_y,
rowMinHeight: fc.row_min_height,
labelGap: fc.label_gap,
formBgColor: fc.form_bg_color,
rowDividerColor: fc.row_divider_color,
showRowDivider: this._coerceBool(fc.show_row_divider),
inputHeight: fc.input_height,
inputRadius: fc.input_radius,
labelWidthMode: fc.label_width_mode,
labelFixedWidth: fc.label_fixed_width,
labelFontSize: fc.label_font_size,
labelColor: fc.label_color,
requiredColor: fc.required_color,
checkedColor: fc.checked_color,
submitColor: fc.submit_color,
submitBgColor: fc.submit_bg_color,
tipFontSize: fc.tip_font_size,
tipColor: fc.tip_color,
tipBgColor: fc.tip_bg_color
};
// 移除 undefined / 空字符串(保留 0/false)
Object.keys(base).forEach(function(k) {
if (base[k] === undefined || base[k] === null || base[k] === '') {
delete base[k];
}
});
// 平台差异化配置(与后端 formConfig.terminal 字段名对齐)
// 优先级:value.form_config_terminal > value.terminal > value.form_config_platform
const result = Object.assign({}, base);
const termBlock = v.form_config_terminal || v.terminal || v.form_config_platform;
if (termBlock && typeof termBlock === 'object') {
Object.keys(termBlock).forEach(function(platform) {
var src = termBlock[platform];
if (!src) return;
// 嵌套值若为 snake_case(部分序列化路径),在此统一转 camelCase
var translated = {};
Object.keys(src).forEach(function(k) {
var camel = k.replace(/_([a-z])/g, function(_, c) { return c.toUpperCase(); });
translated[camel] = src[k];
});
result[platform] = translated;
});
}
return result;
}
},
methods: {
@@ -87,6 +225,20 @@ export default {
});
},
// 数字保底:undefined/空串/非数字时返回兜底值
_num(v, fb) {
if (v === '' || v === null || v === undefined) return fb;
var n = parseInt(v);
return isNaN(n) ? fb : n;
},
// 布尔保底:0/false/'0'/'false' → false;其它有值 → true
_coerceBool(v) {
if (v === false || v === 0 || v === '0' || v === 'false' || v === null || v === undefined) {
return false;
}
return true;
},
// 提交表单
submitForm() {
var self = this;
@@ -141,13 +293,14 @@ export default {
<style lang="scss">
.diy-from {
background: #fff;
padding: 0 30rpx 20rpx;
padding: 0;
border-radius: 10rpx;
.form-banner {
width: 100%;
overflow: hidden;
margin: 0 -30rpx;
// 默认左右无边距(与后端 --banner-padding-x 对齐;由内联 bannerStyle 覆盖)
margin: 0;
.banner-img {
width: 100%;
@@ -156,14 +309,33 @@ export default {
}
.form-tip {
padding: 30rpx 0;
// 提示语:上下内边距 / 左右内边距 / 字号 / 颜色 / 背景 全部由 tipStyle 内联写入
padding: 0;
text-align: center;
color: #666;
font-size: 28rpx;
line-height: 1.5;
}
.fui-btn {
margin: 30rpx 0 0;
.form-submit-wrap {
// 外层:与后端 .form-preview-submit 对齐(padding-y + form-padding-x + form-bg-color)
// 全部由 submitWrapStyle 内联写入
box-sizing: border-box;
width: 100%;
}
.form-submit {
// 提交按钮:颜色 / 背景色 / 行高 / 圆角 / 字号 全部由 submitStyle 内联写入
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-weight: 500;
letter-spacing: 1rpx;
transition: opacity 0.2s ease;
box-sizing: border-box;
&:active {
opacity: 0.85;
}
}
}
</style>