1. 统一表单样式配置为中心化CSS变量方案,替换原有硬编码样式 2. 新增表单配置合并逻辑,兼容新旧数据格式与平台差异化配置 3. 拆分提交按钮与提示语样式,实现完全可配置化 4. 优化ns-newform组件的样式复用与响应式布局逻辑 5. 修复多端适配与占位符默认值问题
342 lines
11 KiB
Vue
342 lines
11 KiB
Vue
<template>
|
||
<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" :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" :style="tipStyle">{{ value.form_tip }}</view>
|
||
<!-- 表单组件 -->
|
||
<ns-newform
|
||
:data="formComponents"
|
||
:customAttr="value"
|
||
:mode="formCfg.form_layout === 'vertical' ? 'vertical' : 'horizontal'"
|
||
:formConfig="mergedFormConfig"
|
||
ref="nsNewform"
|
||
></ns-newform>
|
||
<!-- 提交按钮(外层 = 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>
|
||
|
||
<script>
|
||
// 自定义表单
|
||
import DiyMinx from './minx.js'
|
||
import NsNewform from '@/components/ns-newform/ns-newform.vue'
|
||
|
||
export default {
|
||
name: 'diy-from',
|
||
components: { NsNewform },
|
||
props: {
|
||
value: {
|
||
type: Object
|
||
}
|
||
},
|
||
mixins: [DiyMinx],
|
||
data() {
|
||
return {
|
||
formComponents: [] // 转换后的表单组件
|
||
};
|
||
},
|
||
created() {
|
||
this.initForm();
|
||
},
|
||
watch: {
|
||
'value': {
|
||
deep: true,
|
||
handler() {
|
||
this.initForm();
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
style() {
|
||
var css = '';
|
||
css += 'background-color:' + this.value.contentBgColor + ';';
|
||
if (this.value.elementAngle === 'round') {
|
||
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
|
||
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
|
||
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
|
||
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
|
||
}
|
||
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.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: {
|
||
// 初始化表单 - 将 diy-form 的数据格式转换成 ns-newform 的格式
|
||
initForm() {
|
||
if (!this.value) {
|
||
this.formComponents = [];
|
||
return;
|
||
}
|
||
var components = this.value.formComponents || this.value.form_components || [];
|
||
// 转换格式:type -> controller
|
||
this.formComponents = (components || []).map(function(item) {
|
||
return {
|
||
controller: item.type,
|
||
value: item.value || {},
|
||
val: (item.value && item.value.default) ? item.value.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;
|
||
// 使用 ns-newform 的验证
|
||
if (this.$refs.nsNewform && this.$refs.nsNewform.verify) {
|
||
var verifyResult = this.$refs.nsNewform.verify();
|
||
if (!verifyResult) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 获取表单数据
|
||
var submitData = {};
|
||
if (this.$refs.nsNewform && this.$refs.nsNewform.formData) {
|
||
this.$refs.nsNewform.formData.forEach(function(item, index) {
|
||
submitData[index] = item.val;
|
||
});
|
||
}
|
||
|
||
// 提交到后端
|
||
this.$api.sendRequest({
|
||
url: '/form/api/form/create',
|
||
data: {
|
||
form_id: this.value.form_id || 0,
|
||
form_data: JSON.stringify(submitData),
|
||
component_id: this.value.id || '',
|
||
form_name: this.value.form_name || '默认表单'
|
||
},
|
||
success: function(res) {
|
||
if (res.code === 0) {
|
||
uni.showToast({
|
||
title: '提交成功',
|
||
icon: 'success'
|
||
});
|
||
// 重置表单
|
||
if (self.$refs.nsNewform && self.$refs.nsNewform.setFormData) {
|
||
self.$refs.nsNewform.setFormData();
|
||
}
|
||
} else {
|
||
uni.showToast({
|
||
title: res.message || '提交失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.diy-from {
|
||
background: #fff;
|
||
padding: 0;
|
||
border-radius: 10rpx;
|
||
|
||
.form-banner {
|
||
width: 100%;
|
||
overflow: hidden;
|
||
// 默认左右无边距(与后端 --banner-padding-x 对齐;由内联 bannerStyle 覆盖)
|
||
margin: 0;
|
||
|
||
.banner-img {
|
||
width: 100%;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.form-tip {
|
||
// 提示语:上下内边距 / 左右内边距 / 字号 / 颜色 / 背景 全部由 tipStyle 内联写入
|
||
padding: 0;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.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>
|