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>
<view data-component-name="diy-form" class="diy-from" :style="style">
<view class="fui-cell-group">
<view class="fui-cell ">
<view class="fui-cell-label ">您的姓名</view>
<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 data-component-name="diy-form" class="diy-from" :class="[formLayoutClass]" :style="style">
<!-- Banner -->
<view v-if="value.show_banner && value.form_banner" class="form-banner">
<image :src="$util.img(value.form_banner)" mode="widthFix" class="banner-img" />
</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>
</template>
<script>
// 自定义表单
import DiyMinx from './minx.js'
import NsNewform from '../pages_tool/_components/ns-newform/ns-newform.vue'
export default {
name: 'diy-from',
components: { NsNewform },
props: {
value: {
type: Object
@@ -50,54 +39,94 @@ export default {
mixins: [DiyMinx],
data() {
return {
loading: true,
markers: [],
Form: {
realname: '',
mobile: '',
mailbox: '',
citys: '',
remark: '',
}
formComponents: [] // 转换后的表单组件
};
},
created() {
this.initForm();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
// this.getDataList();
'value.formComponents': {
deep: true,
handler() {
this.initForm();
}
}
},
computed: {
markerst() {
return [{
id: 1,
latitude: this.value.list[0].lat,
longitude: this.value.list[0].lng
}]
},
style() {
var css = '';
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-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;
},
formLayoutClass() {
return this.value.form_layout === 'vertical' ? 'form-vertical' : 'form-horizontal';
}
},
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({
url: '/api/member/information',
data: this.Form,
success: res => {
this.$util.showToast({
title: res.message
});
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'
});
}
}
});
}
@@ -108,7 +137,29 @@ export default {
<style lang="scss">
.diy-from {
background: #fff;
padding-bottom: 20rpx;
padding: 0 30rpx 20rpx;
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-if="item.controller == 'Text'" class="order-wrap">
<view class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box">
<view :class="['box', 'align-left']">
<input
type="text"
:placeholder="item.value.placeholder"
@@ -21,12 +21,12 @@
<!-- 多行文本输入框 -->
<view v-if="item.controller == 'Textarea'" class="order-wrap">
<view class="order-cell flex-box textarea">
<view class="name">
<view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box">
<view :class="['box', 'align-left']">
<textarea
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
@@ -43,12 +43,12 @@
:range="item.value.options"
@change="pickerChange($event, index)"
>
<view class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box">
<view :class="['box', 'align-right']">
<text v-if="item.val != ''">{{ item.val }}</text>
<text v-else class="color-tip">请选择</text>
</view>
@@ -59,12 +59,12 @@
<!-- 复选框 -->
<view v-if="item.controller == 'Checkbox'" class="order-wrap">
<view class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box check-group-box">
<view :class="['box', 'check-group-box', 'align-left']">
<checkbox-group @change="checkboxChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k">
<checkbox :value="v.value" :checked="v.checked"></checkbox>
@@ -86,12 +86,12 @@
<!-- 单选框 -->
<view v-if="item.controller == 'Radio'" class="order-wrap">
<view class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box radio-group-box">
<view :class="['box', 'radio-group-box', 'align-left']">
<radio-group @change="radioChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k">
<radio :value="v.value" :checked="item.val == v.value"></radio>
@@ -113,12 +113,12 @@
<!-- 图片上传 -->
<view v-if="item.controller == 'Img'" class="order-wrap">
<view class="order-cell flex-box">
<view class="name">
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 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)">
<image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image>
<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 class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box box-flex">
<view :class="['box', 'box-flex', 'align-right']">
<picker
mode="date"
:value="item.val"
@@ -154,12 +154,12 @@
<!-- 日期范围选择器 -->
<view v-if="item.controller == 'Datelimit'" class="order-wrap">
<view class="order-cell flex-box">
<view class="name">
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box date-boxs">
<view :class="['box', 'date-boxs', 'align-center']">
<view class="date-box">
<picker
mode="date"
@@ -193,12 +193,12 @@
<!-- 时间选择器 -->
<view v-if="item.controller == 'Time'" class="order-wrap">
<view class="order-cell">
<view class="name">
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box box-flex">
<view :class="['box', 'box-flex', 'align-right']">
<picker
mode="time"
:value="item.val"
@@ -215,12 +215,12 @@
<!-- 时间范围选择器 -->
<view v-if="item.controller == 'Timelimit'" class="order-wrap">
<view class="order-cell flex-box">
<view class="name">
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box date-boxs">
<view :class="['box', 'date-boxs', 'align-center']">
<view class="date-box">
<picker
mode="time"
@@ -254,12 +254,12 @@
<!-- 城市选择器 -->
<view v-if="item.controller == 'City'" class="order-wrap">
<view class="order-cell box-flex">
<view class="name">
<view :class="['order-cell', 'box-flex', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="[nameWidthStyle, labelStyle]">
<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 class="box">
<view :class="['box', 'align-right']">
<pick-regions
:defaultRegions="item.default_regions"
:selectArr="item.select_arr"
@@ -288,34 +288,129 @@ export default {
customAttr: {
type: Object,
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() {
return {
formData: this.data
formData: this.data,
maxLabelWidth: 0
}
},
created() {
this.setFormData();
},
mounted() {
this.$nextTick(() => {
this.calculateMaxLabelWidth();
});
},
watch: {
data: {
handler() {
this.setFormData();
this.$nextTick(() => {
this.calculateMaxLabelWidth();
});
},
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: {
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() {
this.formData = this.data;
this.formData.forEach((item) => {
// 初始化默认值
if (!item.val) {
item.val = item.value.default ? item.value.default : '';
}
// 处理选项列表
if (item.value.options) {
item.option_lists = [];
item.value.options.forEach((option, index) => {
@@ -339,12 +434,10 @@ export default {
});
}
// 处理图片列表
if (item.controller == 'Img') {
item.img_lists = item.val ? item.val.split(',') : [];
}
// 处理日期
if (item.controller == 'Date' && !item.val) {
if (item.value.is_show_default) {
if (item.value.is_current) {
@@ -357,7 +450,6 @@ export default {
}
}
// 处理日期范围
if (item.controller == 'Datelimit') {
if (item.val) {
let dateArray = item.val.split(' - ');
@@ -366,7 +458,6 @@ export default {
} else {
item.val = '';
// 开始日期
if (item.value.is_show_default_start) {
if (item.value.is_current_start) {
item.start_date = this.getDate();
@@ -377,15 +468,12 @@ export default {
item.start_date = '';
}
// 结束日期
if (item.value.is_show_default_end) {
if (item.value.is_current_end) {
item.end_date = this.getDate();
} else {
item.end_date = item.value.default_end;
}
} else {
item.end_date = '';
}
if (item.start_date && item.end_date) {
@@ -394,7 +482,6 @@ export default {
}
}
// 处理时间
if (item.controller == 'Time' && !item.val) {
if (item.value.is_show_default) {
if (item.value.is_current) {
@@ -407,7 +494,6 @@ export default {
}
}
// 处理时间范围
if (item.controller == 'Timelimit') {
if (item.val) {
let timeArray = item.val.split(' - ');
@@ -416,7 +502,6 @@ export default {
} else {
item.val = '';
// 开始时间
if (item.value.is_show_default_start) {
if (item.value.is_current_start) {
item.start_time = this.getTime();
@@ -427,15 +512,12 @@ export default {
item.start_time = '';
}
// 结束时间
if (item.value.is_show_default_end) {
if (item.value.is_current_end) {
item.end_time = this.getTime();
} else {
item.end_time = item.value.default_end;
}
} else {
item.end_time = '';
}
if (item.start_time && item.end_time) {
@@ -444,7 +526,6 @@ export default {
}
}
// 处理城市选择
if (item.controller == 'City') {
item.full_address = '';
item.select_arr = item.value.default_type == 1 ? '2' : '3';
@@ -457,12 +538,10 @@ export default {
});
},
// 表单验证
verify() {
for (let i = 0; i < this.formData.length; i++) {
let item = this.formData[i];
// 文本验证
if (item.controller == 'Text') {
if (item.value.required && !item.val) {
this.$util.showToast({ title: '请输入' + item.value.title });
@@ -492,37 +571,31 @@ export default {
}
}
// 多行文本验证
if (item.controller == 'Textarea' && item.value.required && !item.val) {
this.$util.showToast({ title: '请输入' + item.value.title });
return false;
}
// 下拉选择验证
if (item.controller == 'Select' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
return false;
}
// 复选框验证
if (item.controller == 'Checkbox' && item.value.required && !item.val) {
this.$util.showToast({ title: '请至少选择一个' + item.value.title });
return false;
}
// 图片上传验证
if (item.controller == 'Img' && item.value.required && !item.val) {
this.$util.showToast({ title: '请至少上传一张' + item.value.title });
return false;
}
// 日期验证
if (item.controller == 'Date' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
return false;
}
// 日期范围验证
if (item.controller == 'Datelimit') {
if (item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
@@ -535,13 +608,11 @@ export default {
}
}
// 时间验证
if (item.controller == 'Time' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
return false;
}
// 时间范围验证
if (item.controller == 'Timelimit') {
if (item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
@@ -554,7 +625,6 @@ export default {
}
}
// 城市选择验证
if (item.controller == 'City' && item.value.required && !item.val) {
this.$util.showToast({ title: '请选择' + item.value.title });
return false;
@@ -564,13 +634,11 @@ export default {
return this.formData;
},
// 下拉选择改变
pickerChange(event, index) {
this.formData[index].val = this.data[index].value.options[event.detail.value];
this.$forceUpdate();
},
// 复选框改变
checkboxChange(event, index) {
this.formData[index].val = event.detail.value.toString();
this.formData[index].option_lists.forEach((option) => {
@@ -579,13 +647,11 @@ export default {
this.$forceUpdate();
},
// 单选框改变
radioChange(event, index) {
this.formData[index].val = event.detail.value;
this.$forceUpdate();
},
// 上传图片
uploadImg(index) {
let self = this;
this.$util.upload(Number(this.formData[index].value.max_count), { path: 'evaluateimg' }, function(res) {
@@ -603,7 +669,6 @@ export default {
});
},
// 添加图片
addImg(index) {
let self = this;
if (this.formData[index].img_lists.length >= Number(this.formData[index].value.max_count)) {
@@ -622,14 +687,12 @@ export default {
});
},
// 删除图片
delImg(imgIndex, formIndex) {
this.formData[formIndex].img_lists.splice(imgIndex, 1);
this.formData[formIndex].val = this.formData[formIndex].img_lists.toString();
this.$forceUpdate();
},
// 获取当前日期
getDate() {
let date = new Date();
let year = date.getFullYear();
@@ -640,7 +703,6 @@ export default {
return `${year}-${month}-${day}`;
},
// 获取当前时间
getTime() {
let date = new Date();
let hours = date.getHours();
@@ -650,46 +712,39 @@ export default {
return `${hours}:${minutes}`;
},
// 日期改变
bindDateChange(event, index) {
this.formData[index].val = event.detail.value;
this.$forceUpdate();
},
// 开始日期改变
bindStartDateChange(event, index) {
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.$forceUpdate();
},
// 结束日期改变
bindEndDateChange(event, index) {
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.$forceUpdate();
},
// 时间改变
bindTimeChange(event, index) {
this.formData[index].val = event.detail.value;
this.$forceUpdate();
},
// 开始时间改变
bindStartTimeChange(event, index) {
this.formData[index].start_time = event.detail.value;
this.$forceUpdate();
},
// 结束时间改变
bindEndTimeChange(event, index) {
this.formData[index].end_time = event.detail.value;
this.formData[index].val = this.formData[index].start_time + ' - ' + this.formData[index].end_time;
this.$forceUpdate();
},
// 处理地区选择
handleGetRegions(regions, index) {
this.formData[index].val = '';
this.formData[index].val += regions[0] != undefined ? regions[0].label : '';
@@ -702,21 +757,67 @@ export default {
</script>
<style lang="scss" scoped>
.form-wrap {
background: #ffffff;
}
.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 {
margin-bottom: 0;
border-bottom: 0;
&::after {
display: none;
}
}
.order-cell {
display: flex;
align-items: center;
background: #fff;
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 {
align-items: unset;
align-items: flex-start;
}
&.clear-flex {
@@ -730,7 +831,6 @@ export default {
&:last-child {
margin-bottom: 0;
border-bottom: solid 1px #eee;
}
&.align-top {
@@ -742,16 +842,16 @@ export default {
}
.name {
width: 80vw;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.tit {
text-align: left;
font-size: 32rpx;
color: #888;
font-size: 28rpx;
color: #1f2937;
font-weight: 500;
letter-spacing: 0.3rpx;
text {
font-size: 28rpx;
@@ -759,169 +859,208 @@ export default {
}
.required {
color: red;
font-size: 28rpx;
margin-left: 8rpx;
width: 14rpx;
text-align: left;
color: #ef4444;
font-size: 32rpx;
margin-left: 4rpx;
display: inline-block;
vertical-align: middle;
}
}
.box {
flex: 1;
padding: 0 0rpx;
padding: 0;
line-height: inherit;
text-align: left;
// 对齐方式
&.align-left {
text-align: left;
}
&.align-right {
text-align: right;
}
&.align-center {
text-align: center;
}
input {
font-size: 28rpx;
text-align: left;
height: 70rpx;
border: solid 2rpx #eee;
line-height: 70rpx;
padding: 0 16rpx;
height: 80rpx;
border: 2rpx solid #f3f4f6;
border-radius: 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 {
font-size: 28rpx;
width: 100%;
height: 88rpx;
min-height: 160rpx;
line-height: 44rpx;
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 {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
radio-group {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
label {
display: flex;
align-items: center;
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 {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16rpx;
.img-box {
margin: 10rpx 20rpx 10rpx 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
border: 1rpx solid #eee;
border-radius: 4rpx;
width: 140rpx;
height: 140rpx;
border: 2rpx dashed #d1d5db;
border-radius: 16rpx;
position: relative;
background: #f9fafb;
transition: all 0.3s ease;
overflow: hidden;
&:active {
background: #f3f4f6;
border-color: #3b82f6;
}
.icon-guanbi {
position: absolute;
top: -14rpx;
right: -14rpx;
display: inline-block;
width: 28rpx;
height: 28rpx;
line-height: 28rpx;
color: #909399;
top: -10rpx;
right: -10rpx;
display: flex;
align-items: center;
justify-content: center;
width: 40rpx;
height: 40rpx;
line-height: 1;
color: #fff;
background: #ef4444;
border-radius: 50%;
font-size: 20rpx;
z-index: 10;
}
.icon-add1 {
font-size: 40rpx;
}
image {
width: 100%;
height: 100%;
font-size: 48rpx;
color: #9ca3af;
}
}
}
&.box-flex {
display: flex;
align-items: center;
justify-content: space-between;
}
&.date-boxs {
padding: 0 10rpx;
display: flex;
align-items: center;
.interval {
margin: 0 12rpx;
color: #000;
font-weight: 700;
}
.date-box {
flex: 1;
.picker-box {
display: flex;
align-items: center;
justify-content: flex-end;
.uni-input {
width: 100%;
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;
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 {
line-height: 1;
position: unset;
color: #9ca3af;
font-size: 32rpx;
margin-left: 12rpx;
}
}
}