refactor(diy-form): 重构自定义表单组件,替换为通用表单组件

1.  替换硬编码表单项为可配置的ns-newform组件
2.  支持横向/纵向两种布局模式
3.  添加表单Banner、提示语配置能力
4.  统一表单提交逻辑,新增表单校验和重置功能
5.  优化ns-newform组件样式与交互细节
This commit is contained in:
2026-06-08 16:47:02 +08:00
parent 4f44b4f574
commit 14ba88b645
2 changed files with 439 additions and 249 deletions

View File

@@ -1,47 +1,36 @@
<template> <template>
<view data-component-name="diy-form" class="diy-from" :style="style"> <view data-component-name="diy-form" class="diy-from" :class="[formLayoutClass]" :style="style">
<view class="fui-cell-group"> <!-- Banner -->
<view class="fui-cell "> <view v-if="value.show_banner && value.form_banner" class="form-banner">
<view class="fui-cell-label ">您的姓名</view> <image :src="$util.img(value.form_banner)" mode="widthFix" class="banner-img" />
<view class="fui-cell-info">
<input v-model="Form.realname" class="fui-input" placeholder="请输入您的姓名" value=""></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">手机号码</view>
<view class="fui-cell-info">
<input v-model="Form.mobile" class="fui-input" maxlength="11" placeholder="请输入您的手机号"
type="number"></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">您的邮箱</view>
<view class="fui-cell-info">
<input v-model="Form.mailbox" class="fui-input" placeholder="请输入您的邮箱" type="text"></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">所在城市</view>
<view class="fui-cell-info">
<input v-model="Form.citys" class="fui-input" placeholder="请输入您的所在地" value=""></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">备注</view>
<view class="fui-cell-info">
<input v-model="Form.remark" class="fui-input" placeholder="请输入备注" value=""></input>
</view>
</view>
</view> </view>
<view @tap.stop="submitform" class="fui-btn btn-danger block mtop">提交信息</view> <!-- 提示语 -->
<view v-if="value.show_tip && value.form_tip" class="form-tip">{{ 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"
ref="nsNewform"
/>
<!-- 提交按钮 -->
<view @click.stop="submitForm" class="fui-btn btn-danger block mtop">提交信息</view>
</view> </view>
</template> </template>
<script> <script>
// 自定义表单 // 自定义表单
import DiyMinx from './minx.js' import DiyMinx from './minx.js'
import NsNewform from '../pages_tool/_components/ns-newform/ns-newform.vue'
export default { export default {
name: 'diy-from', name: 'diy-from',
components: { NsNewform },
props: { props: {
value: { value: {
type: Object type: Object
@@ -50,54 +39,94 @@ export default {
mixins: [DiyMinx], mixins: [DiyMinx],
data() { data() {
return { return {
loading: true, formComponents: [] // 转换后的表单组件
markers: [],
Form: {
realname: '',
mobile: '',
mailbox: '',
citys: '',
remark: '',
}
}; };
}, },
created() { created() {
this.initForm();
}, },
watch: { watch: {
// 组件刷新监听 'value.formComponents': {
componentRefresh: function (nval) { deep: true,
// this.getDataList(); handler() {
this.initForm();
}
} }
}, },
computed: { computed: {
markerst() {
return [{
id: 1,
latitude: this.value.list[0].lat,
longitude: this.value.list[0].lng
}]
},
style() { style() {
var css = ''; var css = '';
css += 'background-color:' + this.value.contentBgColor + ';'; css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') { if (this.value.elementAngle === 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;'; css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-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-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;'; css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
} }
return css; return css;
},
formLayoutClass() {
return this.value.form_layout === 'vertical' ? 'form-vertical' : 'form-horizontal';
} }
}, },
methods: { methods: {
submitform() { // 初始化表单 - 将 diy-form 的数据格式转换成 ns-newform 的格式
initForm() {
var components = this.value.formComponents || [];
// 转换格式:type -> controller
this.formComponents = components.map(function(item) {
return {
controller: item.type,
value: item.value,
val: item.value.default || '' // 添加初始值
};
});
},
// 提交表单
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({ this.$api.sendRequest({
url: '/api/member/information', url: '/form/api/form/create',
data: this.Form, data: {
success: res => { form_id: this.value.form_id || 0,
this.$util.showToast({ form_data: JSON.stringify(submitData),
title: res.message 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'
});
}
} }
}); });
} }
@@ -108,7 +137,29 @@ export default {
<style lang="scss"> <style lang="scss">
.diy-from { .diy-from {
background: #fff; background: #fff;
padding-bottom: 20rpx; padding: 0 30rpx 20rpx;
border-radius: 10rpx; border-radius: 10rpx;
.form-banner {
width: 100%;
overflow: hidden;
margin: 0 -30rpx;
.banner-img {
width: 100%;
display: block;
}
}
.form-tip {
padding: 30rpx 0;
text-align: center;
color: #666;
font-size: 28rpx;
}
.fui-btn {
margin: 30rpx 0 0;
}
} }
</style> </style>

View File

@@ -3,12 +3,12 @@
<view v-for="(item, index) in formData" :key="index"> <view v-for="(item, index) in formData" :key="index">
<!-- 文本输入框 --> <!-- 文本输入框 -->
<view v-if="item.controller == 'Text'" class="order-wrap"> <view v-if="item.controller == 'Text'" class="order-wrap">
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'align-left']">
<input <input
type="text" type="text"
:placeholder="item.value.placeholder" :placeholder="item.value.placeholder"
@@ -21,12 +21,12 @@
<!-- 多行文本输入框 --> <!-- 多行文本输入框 -->
<view v-if="item.controller == 'Textarea'" class="order-wrap"> <view v-if="item.controller == 'Textarea'" class="order-wrap">
<view class="order-cell flex-box textarea"> <view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'align-left']">
<textarea <textarea
:placeholder="item.value.placeholder" :placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip" placeholder-class="placeholder color-tip"
@@ -43,12 +43,12 @@
:range="item.value.options" :range="item.value.options"
@change="pickerChange($event, index)" @change="pickerChange($event, index)"
> >
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'align-right']">
<text v-if="item.val != ''">{{ item.val }}</text> <text v-if="item.val != ''">{{ item.val }}</text>
<text v-else class="color-tip">请选择</text> <text v-else class="color-tip">请选择</text>
</view> </view>
@@ -59,12 +59,12 @@
<!-- 复选框 --> <!-- 复选框 -->
<view v-if="item.controller == 'Checkbox'" class="order-wrap"> <view v-if="item.controller == 'Checkbox'" class="order-wrap">
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box check-group-box"> <view :class="['box', 'check-group-box', 'align-left']">
<checkbox-group @change="checkboxChange($event, index)"> <checkbox-group @change="checkboxChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k"> <label v-for="(v, k) in item.option_lists" :key="k">
<checkbox :value="v.value" :checked="v.checked"></checkbox> <checkbox :value="v.value" :checked="v.checked"></checkbox>
@@ -86,12 +86,12 @@
<!-- 单选框 --> <!-- 单选框 -->
<view v-if="item.controller == 'Radio'" class="order-wrap"> <view v-if="item.controller == 'Radio'" class="order-wrap">
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box radio-group-box"> <view :class="['box', 'radio-group-box', 'align-left']">
<radio-group @change="radioChange($event, index)"> <radio-group @change="radioChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k"> <label v-for="(v, k) in item.option_lists" :key="k">
<radio :value="v.value" :checked="item.val == v.value"></radio> <radio :value="v.value" :checked="item.val == v.value"></radio>
@@ -113,12 +113,12 @@
<!-- 图片上传 --> <!-- 图片上传 -->
<view v-if="item.controller == 'Img'" class="order-wrap"> <view v-if="item.controller == 'Img'" class="order-wrap">
<view class="order-cell flex-box"> <view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box img-boxs"> <view :class="['box', 'img-boxs', 'align-left']">
<view v-for="(v, k) in item.img_lists" :key="k" class="img-box" @tap="uploadImg(index)"> <view v-for="(v, k) in item.img_lists" :key="k" class="img-box" @tap="uploadImg(index)">
<image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image> <image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image>
<text class="iconfont icon-guanbi" @tap.stop="delImg(k, index)"></text> <text class="iconfont icon-guanbi" @tap.stop="delImg(k, index)"></text>
@@ -132,12 +132,12 @@
<!-- 日期选择器 --> <!-- 日期选择器 -->
<view v-if="item.controller == 'Date'" class="order-wrap"> <view v-if="item.controller == 'Date'" class="order-wrap">
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box box-flex"> <view :class="['box', 'box-flex', 'align-right']">
<picker <picker
mode="date" mode="date"
:value="item.val" :value="item.val"
@@ -154,12 +154,12 @@
<!-- 日期范围选择器 --> <!-- 日期范围选择器 -->
<view v-if="item.controller == 'Datelimit'" class="order-wrap"> <view v-if="item.controller == 'Datelimit'" class="order-wrap">
<view class="order-cell flex-box"> <view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box date-boxs"> <view :class="['box', 'date-boxs', 'align-center']">
<view class="date-box"> <view class="date-box">
<picker <picker
mode="date" mode="date"
@@ -193,12 +193,12 @@
<!-- 时间选择器 --> <!-- 时间选择器 -->
<view v-if="item.controller == 'Time'" class="order-wrap"> <view v-if="item.controller == 'Time'" class="order-wrap">
<view class="order-cell"> <view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box box-flex"> <view :class="['box', 'box-flex', 'align-right']">
<picker <picker
mode="time" mode="time"
:value="item.val" :value="item.val"
@@ -215,12 +215,12 @@
<!-- 时间范围选择器 --> <!-- 时间范围选择器 -->
<view v-if="item.controller == 'Timelimit'" class="order-wrap"> <view v-if="item.controller == 'Timelimit'" class="order-wrap">
<view class="order-cell flex-box"> <view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box date-boxs"> <view :class="['box', 'date-boxs', 'align-center']">
<view class="date-box"> <view class="date-box">
<picker <picker
mode="time" mode="time"
@@ -254,12 +254,12 @@
<!-- 城市选择器 --> <!-- 城市选择器 -->
<view v-if="item.controller == 'City'" class="order-wrap"> <view v-if="item.controller == 'City'" class="order-wrap">
<view class="order-cell box-flex"> <view :class="['order-cell', 'box-flex', { 'vertical-layout': mode === 'vertical' }]">
<view class="name"> <view class="name" :style="[nameWidthStyle, labelStyle]">
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'align-right']">
<pick-regions <pick-regions
:defaultRegions="item.default_regions" :defaultRegions="item.default_regions"
:selectArr="item.select_arr" :selectArr="item.select_arr"
@@ -288,34 +288,129 @@ export default {
customAttr: { customAttr: {
type: Object, type: Object,
default: () => ({}) default: () => ({})
},
mode: {
type: String,
default: 'horizontal',
validator: (value) => ['horizontal', 'vertical'].includes(value)
},
labelWidthMode: {
type: String,
default: 'auto',
validator: (value) => ['auto', 'fixed'].includes(value)
},
labelFixedWidth: {
type: [String, Number],
default: 180
},
labelFontSize: {
type: [String, Number],
default: 30
},
labelColor: {
type: String,
default: '#303133'
},
requiredColor: {
type: String,
default: '#F56C6C'
} }
}, },
data() { data() {
return { return {
formData: this.data formData: this.data,
maxLabelWidth: 0
} }
}, },
created() { created() {
this.setFormData(); this.setFormData();
}, },
mounted() {
this.$nextTick(() => {
this.calculateMaxLabelWidth();
});
},
watch: { watch: {
data: { data: {
handler() { handler() {
this.setFormData(); this.setFormData();
this.$nextTick(() => {
this.calculateMaxLabelWidth();
});
}, },
deep: true deep: true
} }
}, },
computed: {
nameWidthStyle() {
if (this.mode === 'vertical') {
return {};
}
let width;
if (this.labelWidthMode === 'fixed') {
width = `${this.labelFixedWidth}rpx`;
} else {
width = this.maxLabelWidth ? `${this.maxLabelWidth}rpx` : 'auto';
}
return {
width: width
};
},
labelStyle() {
let style = {};
let fontSize = this.labelFontSize;
if (typeof fontSize === 'number') {
fontSize = `${fontSize}rpx`;
} else if (typeof fontSize === 'string' && !fontSize.endsWith('rpx') && !fontSize.endsWith('px')) {
fontSize = `${fontSize}rpx`;
}
style.fontSize = fontSize;
style.color = this.labelColor;
return style;
}
},
methods: { methods: {
calculateMaxLabelWidth() {
if (this.mode === 'vertical' || !this.formData || this.formData.length === 0) {
return;
}
let maxWidth = 0;
this.formData.forEach((item) => {
if (item.value && item.value.title) {
let text = item.value.title;
if (item.value.required) {
text += '*';
}
let width = 0;
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (/[\u4e00-\u9fa5]/.test(char)) {
width += 1;
} else {
width += 0.5;
}
}
const fontSize = parseInt(this.labelFontSize) || 30;
width = width * fontSize * 0.85 + 30;
maxWidth = Math.max(maxWidth, width);
}
});
maxWidth = Math.max(maxWidth, 80);
maxWidth = Math.min(maxWidth, 200);
this.maxLabelWidth = maxWidth;
},
setFormData() { setFormData() {
this.formData = this.data; this.formData = this.data;
this.formData.forEach((item) => { this.formData.forEach((item) => {
// 初始化默认值
if (!item.val) { if (!item.val) {
item.val = item.value.default ? item.value.default : ''; item.val = item.value.default ? item.value.default : '';
} }
// 处理选项列表
if (item.value.options) { if (item.value.options) {
item.option_lists = []; item.option_lists = [];
item.value.options.forEach((option, index) => { item.value.options.forEach((option, index) => {
@@ -339,12 +434,10 @@ export default {
}); });
} }
// 处理图片列表
if (item.controller == 'Img') { if (item.controller == 'Img') {
item.img_lists = item.val ? item.val.split(',') : []; item.img_lists = item.val ? item.val.split(',') : [];
} }
// 处理日期
if (item.controller == 'Date' && !item.val) { if (item.controller == 'Date' && !item.val) {
if (item.value.is_show_default) { if (item.value.is_show_default) {
if (item.value.is_current) { if (item.value.is_current) {
@@ -357,7 +450,6 @@ export default {
} }
} }
// 处理日期范围
if (item.controller == 'Datelimit') { if (item.controller == 'Datelimit') {
if (item.val) { if (item.val) {
let dateArray = item.val.split(' - '); let dateArray = item.val.split(' - ');
@@ -366,7 +458,6 @@ export default {
} else { } else {
item.val = ''; item.val = '';
// 开始日期
if (item.value.is_show_default_start) { if (item.value.is_show_default_start) {
if (item.value.is_current_start) { if (item.value.is_current_start) {
item.start_date = this.getDate(); item.start_date = this.getDate();
@@ -377,15 +468,12 @@ export default {
item.start_date = ''; item.start_date = '';
} }
// 结束日期
if (item.value.is_show_default_end) { if (item.value.is_show_default_end) {
if (item.value.is_current_end) { if (item.value.is_current_end) {
item.end_date = this.getDate(); item.end_date = this.getDate();
} else { } else {
item.end_date = item.value.default_end; item.end_date = item.value.default_end;
} }
} else {
item.end_date = '';
} }
if (item.start_date && item.end_date) { if (item.start_date && item.end_date) {
@@ -394,7 +482,6 @@ export default {
} }
} }
// 处理时间
if (item.controller == 'Time' && !item.val) { if (item.controller == 'Time' && !item.val) {
if (item.value.is_show_default) { if (item.value.is_show_default) {
if (item.value.is_current) { if (item.value.is_current) {
@@ -407,7 +494,6 @@ export default {
} }
} }
// 处理时间范围
if (item.controller == 'Timelimit') { if (item.controller == 'Timelimit') {
if (item.val) { if (item.val) {
let timeArray = item.val.split(' - '); let timeArray = item.val.split(' - ');
@@ -416,7 +502,6 @@ export default {
} else { } else {
item.val = ''; item.val = '';
// 开始时间
if (item.value.is_show_default_start) { if (item.value.is_show_default_start) {
if (item.value.is_current_start) { if (item.value.is_current_start) {
item.start_time = this.getTime(); item.start_time = this.getTime();
@@ -427,15 +512,12 @@ export default {
item.start_time = ''; item.start_time = '';
} }
// 结束时间
if (item.value.is_show_default_end) { if (item.value.is_show_default_end) {
if (item.value.is_current_end) { if (item.value.is_current_end) {
item.end_time = this.getTime(); item.end_time = this.getTime();
} else { } else {
item.end_time = item.value.default_end; item.end_time = item.value.default_end;
} }
} else {
item.end_time = '';
} }
if (item.start_time && item.end_time) { if (item.start_time && item.end_time) {
@@ -444,7 +526,6 @@ export default {
} }
} }
// 处理城市选择
if (item.controller == 'City') { if (item.controller == 'City') {
item.full_address = ''; item.full_address = '';
item.select_arr = item.value.default_type == 1 ? '2' : '3'; item.select_arr = item.value.default_type == 1 ? '2' : '3';
@@ -457,12 +538,10 @@ export default {
}); });
}, },
// 表单验证
verify() { verify() {
for (let i = 0; i < this.formData.length; i++) { for (let i = 0; i < this.formData.length; i++) {
let item = this.formData[i]; let item = this.formData[i];
// 文本验证
if (item.controller == 'Text') { if (item.controller == 'Text') {
if (item.value.required && !item.val) { if (item.value.required && !item.val) {
this.$util.showToast({ title: '请输入' + item.value.title }); this.$util.showToast({ title: '请输入' + item.value.title });
@@ -492,37 +571,31 @@ export default {
} }
} }
// 多行文本验证
if (item.controller == 'Textarea' && item.value.required && !item.val) { if (item.controller == 'Textarea' && item.value.required && !item.val) {
this.$util.showToast({ title: '请输入' + item.value.title }); this.$util.showToast({ title: '请输入' + item.value.title });
return false; return false;
} }
// 下拉选择验证
if (item.controller == 'Select' && item.value.required && !item.val) { if (item.controller == 'Select' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
return false; return false;
} }
// 复选框验证
if (item.controller == 'Checkbox' && item.value.required && !item.val) { if (item.controller == 'Checkbox' && item.value.required && !item.val) {
this.$util.showToast({ title: '请至少选择一个' + item.value.title }); this.$util.showToast({ title: '请至少选择一个' + item.value.title });
return false; return false;
} }
// 图片上传验证
if (item.controller == 'Img' && item.value.required && !item.val) { if (item.controller == 'Img' && item.value.required && !item.val) {
this.$util.showToast({ title: '请至少上传一张' + item.value.title }); this.$util.showToast({ title: '请至少上传一张' + item.value.title });
return false; return false;
} }
// 日期验证
if (item.controller == 'Date' && item.value.required && !item.val) { if (item.controller == 'Date' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
return false; return false;
} }
// 日期范围验证
if (item.controller == 'Datelimit') { if (item.controller == 'Datelimit') {
if (item.value.required && !item.val) { if (item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
@@ -535,13 +608,11 @@ export default {
} }
} }
// 时间验证
if (item.controller == 'Time' && item.value.required && !item.val) { if (item.controller == 'Time' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
return false; return false;
} }
// 时间范围验证
if (item.controller == 'Timelimit') { if (item.controller == 'Timelimit') {
if (item.value.required && !item.val) { if (item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
@@ -554,7 +625,6 @@ export default {
} }
} }
// 城市选择验证
if (item.controller == 'City' && item.value.required && !item.val) { if (item.controller == 'City' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title }); this.$util.showToast({ title: '请选择' + item.value.title });
return false; return false;
@@ -564,13 +634,11 @@ export default {
return this.formData; return this.formData;
}, },
// 下拉选择改变
pickerChange(event, index) { pickerChange(event, index) {
this.formData[index].val = this.data[index].value.options[event.detail.value]; this.formData[index].val = this.data[index].value.options[event.detail.value];
this.$forceUpdate(); this.$forceUpdate();
}, },
// 复选框改变
checkboxChange(event, index) { checkboxChange(event, index) {
this.formData[index].val = event.detail.value.toString(); this.formData[index].val = event.detail.value.toString();
this.formData[index].option_lists.forEach((option) => { this.formData[index].option_lists.forEach((option) => {
@@ -579,13 +647,11 @@ export default {
this.$forceUpdate(); this.$forceUpdate();
}, },
// 单选框改变
radioChange(event, index) { radioChange(event, index) {
this.formData[index].val = event.detail.value; this.formData[index].val = event.detail.value;
this.$forceUpdate(); this.$forceUpdate();
}, },
// 上传图片
uploadImg(index) { uploadImg(index) {
let self = this; let self = this;
this.$util.upload(Number(this.formData[index].value.max_count), { path: 'evaluateimg' }, function(res) { this.$util.upload(Number(this.formData[index].value.max_count), { path: 'evaluateimg' }, function(res) {
@@ -603,7 +669,6 @@ export default {
}); });
}, },
// 添加图片
addImg(index) { addImg(index) {
let self = this; let self = this;
if (this.formData[index].img_lists.length >= Number(this.formData[index].value.max_count)) { if (this.formData[index].img_lists.length >= Number(this.formData[index].value.max_count)) {
@@ -622,14 +687,12 @@ export default {
}); });
}, },
// 删除图片
delImg(imgIndex, formIndex) { delImg(imgIndex, formIndex) {
this.formData[formIndex].img_lists.splice(imgIndex, 1); this.formData[formIndex].img_lists.splice(imgIndex, 1);
this.formData[formIndex].val = this.formData[formIndex].img_lists.toString(); this.formData[formIndex].val = this.formData[formIndex].img_lists.toString();
this.$forceUpdate(); this.$forceUpdate();
}, },
// 获取当前日期
getDate() { getDate() {
let date = new Date(); let date = new Date();
let year = date.getFullYear(); let year = date.getFullYear();
@@ -640,7 +703,6 @@ export default {
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
}, },
// 获取当前时间
getTime() { getTime() {
let date = new Date(); let date = new Date();
let hours = date.getHours(); let hours = date.getHours();
@@ -650,46 +712,39 @@ export default {
return `${hours}:${minutes}`; return `${hours}:${minutes}`;
}, },
// 日期改变
bindDateChange(event, index) { bindDateChange(event, index) {
this.formData[index].val = event.detail.value; this.formData[index].val = event.detail.value;
this.$forceUpdate(); this.$forceUpdate();
}, },
// 开始日期改变
bindStartDateChange(event, index) { bindStartDateChange(event, index) {
this.$set(this.formData[index], 'start_date', event.detail.value); this.$set(this.formData[index], 'start_date', event.detail.value);
this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date); this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date);
this.$forceUpdate(); this.$forceUpdate();
}, },
// 结束日期改变
bindEndDateChange(event, index) { bindEndDateChange(event, index) {
this.$set(this.formData[index], 'end_date', event.detail.value); this.$set(this.formData[index], 'end_date', event.detail.value);
this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date); this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date);
this.$forceUpdate(); this.$forceUpdate();
}, },
// 时间改变
bindTimeChange(event, index) { bindTimeChange(event, index) {
this.formData[index].val = event.detail.value; this.formData[index].val = event.detail.value;
this.$forceUpdate(); this.$forceUpdate();
}, },
// 开始时间改变
bindStartTimeChange(event, index) { bindStartTimeChange(event, index) {
this.formData[index].start_time = event.detail.value; this.formData[index].start_time = event.detail.value;
this.$forceUpdate(); this.$forceUpdate();
}, },
// 结束时间改变
bindEndTimeChange(event, index) { bindEndTimeChange(event, index) {
this.formData[index].end_time = event.detail.value; this.formData[index].end_time = event.detail.value;
this.formData[index].val = this.formData[index].start_time + ' - ' + this.formData[index].end_time; this.formData[index].val = this.formData[index].start_time + ' - ' + this.formData[index].end_time;
this.$forceUpdate(); this.$forceUpdate();
}, },
// 处理地区选择
handleGetRegions(regions, index) { handleGetRegions(regions, index) {
this.formData[index].val = ''; this.formData[index].val = '';
this.formData[index].val += regions[0] != undefined ? regions[0].label : ''; this.formData[index].val += regions[0] != undefined ? regions[0].label : '';
@@ -702,21 +757,67 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.form-wrap {
background: #ffffff;
}
.order-wrap { .order-wrap {
padding: 20rpx 0; padding: 12rpx 0;
transition: all 0.3s ease;
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 1rpx;
background: linear-gradient(90deg, transparent, #f0f0f0, transparent);
}
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
border-bottom: 0; border-bottom: 0;
&::after {
display: none;
}
} }
.order-cell { .order-cell {
display: flex;
align-items: center; align-items: center;
background: #fff; background: #fff;
position: relative; position: relative;
padding: 12rpx 0;
// 水平布局(默认):标签自动宽度(以最大为准),根据组件特性对齐
&:not(.vertical-layout) {
.name {
flex-shrink: 0;
margin-bottom: 0;
margin-right: 20rpx;
}
}
&.vertical-layout {
flex-direction: column;
align-items: flex-start;
.name {
width: 100%;
margin-bottom: 16rpx;
margin-right: 0;
}
.box {
width: 100%;
}
}
&.textarea { &.textarea {
align-items: unset; align-items: flex-start;
} }
&.clear-flex { &.clear-flex {
@@ -730,7 +831,6 @@ export default {
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
border-bottom: solid 1px #eee;
} }
&.align-top { &.align-top {
@@ -742,16 +842,16 @@ export default {
} }
.name { .name {
width: 80vw;
margin-bottom: 10rpx;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
.tit { .tit {
text-align: left; text-align: left;
font-size: 32rpx; font-size: 28rpx;
color: #888; color: #1f2937;
font-weight: 500;
letter-spacing: 0.3rpx;
text { text {
font-size: 28rpx; font-size: 28rpx;
@@ -759,169 +859,208 @@ export default {
} }
.required { .required {
color: red; color: #ef4444;
font-size: 28rpx; font-size: 32rpx;
margin-left: 8rpx; margin-left: 4rpx;
width: 14rpx;
text-align: left;
display: inline-block; display: inline-block;
vertical-align: middle;
} }
} }
.box { .box {
flex: 1; flex: 1;
padding: 0 0rpx; padding: 0;
line-height: inherit; line-height: inherit;
text-align: left;
// 对齐方式
&.align-left {
text-align: left;
}
&.align-right {
text-align: right;
}
&.align-center {
text-align: center;
}
input { input {
font-size: 28rpx; font-size: 28rpx;
text-align: left; text-align: left;
height: 70rpx; height: 80rpx;
border: solid 2rpx #eee; border: 2rpx solid #f3f4f6;
line-height: 70rpx; border-radius: 16rpx;
padding: 0 16rpx; line-height: 80rpx;
padding: 0 24rpx;
background: #f9fafb;
color: #1f2937;
transition: all 0.3s ease;
&:focus {
background: #fff;
border-color: #3b82f6;
box-shadow: 0 0 0 6rpx rgba(59, 130, 246, 0.08);
}
&::placeholder {
color: #9ca3af;
}
} }
textarea { textarea {
font-size: 28rpx; font-size: 28rpx;
width: 100%; width: 100%;
height: 88rpx; min-height: 160rpx;
line-height: 44rpx; line-height: 44rpx;
text-align: left; text-align: left;
border: 2rpx solid #f3f4f6;
border-radius: 16rpx;
padding: 20rpx 24rpx;
background: #f9fafb;
color: #1f2937;
transition: all 0.3s ease;
&:focus {
background: #fff;
border-color: #3b82f6;
box-shadow: 0 0 0 6rpx rgba(59, 130, 246, 0.08);
}
&::placeholder {
color: #9ca3af;
}
}
.uni-input, .select-address {
display: inline-block;
padding: 16rpx 24rpx;
border-radius: 16rpx;
background: #f9fafb;
border: 2rpx solid #f3f4f6;
color: #1f2937;
font-size: 28rpx;
transition: all 0.3s ease;
min-height: 80rpx;
box-sizing: border-box;
&.color-tip {
color: #9ca3af;
}
&.empty {
color: #9ca3af;
}
&:active {
background: #f3f4f6;
}
} }
checkbox-group { checkbox-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12rpx;
} }
radio-group { radio-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12rpx;
} }
label { label {
display: flex; display: flex;
align-items: center; align-items: center;
line-height: 1; line-height: 1;
margin-right: 30rpx; margin: 0;
padding: 12rpx 20rpx;
border: 2rpx solid #e5e7eb;
border-radius: 40rpx;
background: #fff;
transition: all 0.3s ease;
&:active {
background: #f3f4f6;
}
} }
&.img-boxs { &.img-boxs {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 16rpx;
.img-box { .img-box {
margin: 10rpx 20rpx 10rpx 0; margin: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100rpx; width: 140rpx;
height: 100rpx; height: 140rpx;
border: 1rpx solid #eee; border: 2rpx dashed #d1d5db;
border-radius: 4rpx; border-radius: 16rpx;
position: relative; position: relative;
background: #f9fafb;
transition: all 0.3s ease;
overflow: hidden;
&:active {
background: #f3f4f6;
border-color: #3b82f6;
}
.icon-guanbi { .icon-guanbi {
position: absolute; position: absolute;
top: -14rpx; top: -10rpx;
right: -14rpx; right: -10rpx;
display: inline-block; display: flex;
width: 28rpx; align-items: center;
height: 28rpx; justify-content: center;
line-height: 28rpx; width: 40rpx;
color: #909399; height: 40rpx;
line-height: 1;
color: #fff;
background: #ef4444;
border-radius: 50%;
font-size: 20rpx;
z-index: 10;
} }
.icon-add1 { .icon-add1 {
font-size: 40rpx; font-size: 48rpx;
} color: #9ca3af;
image {
width: 100%;
height: 100%;
} }
} }
} }
&.box-flex {
display: flex;
align-items: center;
justify-content: space-between;
}
&.date-boxs { &.date-boxs {
padding: 0 10rpx;
display: flex; display: flex;
align-items: center; align-items: center;
.interval {
margin: 0 12rpx;
color: #000;
font-weight: 700;
}
.date-box { .date-box {
flex: 1;
.picker-box { .picker-box {
display: flex; .uni-input {
align-items: center; width: 100%;
justify-content: flex-end; box-sizing: border-box;
}
} }
} }
}
}
.radio-group-box {
radio {
display: none;
}
.radio-box {
display: flex;
align-items: center;
line-height: 1;
.iconfont { .interval {
padding: 0 16rpx;
color: #9ca3af;
font-size: 32rpx; font-size: 32rpx;
margin-right: 10rpx;
} }
} }
} }
.check-group-box {
checkbox {
display: none;
}
.checkbox {
display: flex;
align-items: center;
line-height: 1;
.iconfont {
font-size: 32rpx;
margin-right: 10rpx;
}
}
}
.iconfont {
color: #909399;
font-size: 28rpx;
}
.box-flex {
picker {
display: block;
width: 100%;
}
}
.icon-right { .icon-right {
line-height: 1; color: #9ca3af;
position: unset; font-size: 32rpx;
margin-left: 12rpx;
} }
} }
} }