refactor(diy-form&ns-newform): 重构表单组件样式与配置体系
1. 统一表单样式配置为中心化CSS变量方案,替换原有硬编码样式 2. 新增表单配置合并逻辑,兼容新旧数据格式与平台差异化配置 3. 拆分提交按钮与提示语样式,实现完全可配置化 4. 优化ns-newform组件的样式复用与响应式布局逻辑 5. 修复多端适配与占位符默认值问题
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user