1. 调整ns-newform的图片上传框样式,匹配后端design.css的.img-box样式参数 2. 简化diy-form的样式变量绑定,移除冗余的cssVars计算属性 3. 重构diy-form提交按钮样式,支持宽度百分比和尺寸预设,对齐后端设计规范 4. 优化表单布局类名逻辑,简化样式绑定代码
325 lines
11 KiB
Vue
325 lines
11 KiB
Vue
<template>
|
||
<view data-component-name="diy-form" class="diy-from" :style="diyFormStyle">
|
||
<!-- 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;
|
||
},
|
||
// 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'
|
||
};
|
||
},
|
||
// 提交按钮样式:颜色 / 背景色 / 宽度(百分比)/ 上下内边距(控制高度)/ 圆角 / 字号
|
||
// 与后端 design.css .form-preview-submit button 对齐:
|
||
// - 高度由 --submit-padding-y + 字号决定,避免硬编码 line-height
|
||
// - 宽度由 --submit-width-percent 控制(10-100,clamp)
|
||
submitStyle() {
|
||
const cfg = this.mergedFormConfig || {};
|
||
// 宽度百分比:clamp 到 10-100
|
||
const widthPct = Math.min(100, Math.max(10, this._num(cfg.submitWidthPercent, 100)));
|
||
// 上下内边距:与后端 design.js submitSizeMap 对齐(small/medium/large → 8/16/28 rpx)
|
||
const submitPaddingMap = { small: 8, medium: 16, large: 28 };
|
||
const submitPaddingY = submitPaddingMap[cfg.submitSize] != null
|
||
? submitPaddingMap[cfg.submitSize]
|
||
: submitPaddingMap.medium;
|
||
return {
|
||
width: widthPct + '%',
|
||
padding: `${submitPaddingY}rpx 0`,
|
||
color: cfg.submitColor || '#ffffff',
|
||
'background-color': cfg.submitBgColor || '#ff4544',
|
||
'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 || '提交';
|
||
},
|
||
// 后端把表单样式配置统一存放在 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,
|
||
// 提交按钮尺寸:宽度百分比 + 尺寸预设(与后端 design.js 对齐)
|
||
submitWidthPercent: fc.submit_width_percent,
|
||
submitSize: fc.submit_size,
|
||
// 提示语
|
||
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 + flex 居中)
|
||
// padding/background 由 submitWrapStyle 内联写入
|
||
// display: flex + justify-content: center 是让宽度小于 100% 的按钮水平居中的关键
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.form-submit {
|
||
// 提交按钮:颜色 / 背景色 / 宽度(百分比)/ 上下内边距(控制高度)/ 圆角 / 字号
|
||
// 全部由 submitStyle 内联写入
|
||
// 宽度不再写死 100%,由内联 style 的 width: <pct>% 决定;max-width: 100% 防止溢出
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
max-width: 100%;
|
||
font-weight: 500;
|
||
letter-spacing: 1rpx;
|
||
transition: opacity 0.2s ease;
|
||
box-sizing: border-box;
|
||
|
||
&:active {
|
||
opacity: 0.85;
|
||
}
|
||
}
|
||
}
|
||
</style>
|