Files
lucky_shop/components-diy/diy-form.vue
ZF sun 25636f8133 style(ns-newform, diy-form): 对齐后端样式规范优化表单组件
1. 调整ns-newform的图片上传框样式,匹配后端design.css的.img-box样式参数
2. 简化diy-form的样式变量绑定,移除冗余的cssVars计算属性
3. 重构diy-form提交按钮样式,支持宽度百分比和尺寸预设,对齐后端设计规范
4. 优化表单布局类名逻辑,简化样式绑定代码
2026-06-11 15:55:11 +08:00

325 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>