Files
lucky_shop/components/ns-form/ns-form.vue
ZF sun eed766366d fix(ns-form,ns-newform): 优化文本输入框默认值处理逻辑
针对Text和Textarea组件,当placeholder为空字符串时不再自动设置默认空值,保留用户输入的空状态
2026-07-31 10:19:47 +08:00

1163 lines
51 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 class="form-wrap form-component" :style="formWrapStyle">
<block v-for="(item, index) in formData" :key="index">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Text'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<input
type="text"
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
/>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Textarea'">
<view class="order-cell flex-box textarea" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<!-- #ifdef MP-HARMONY -->
<!-- 鸿蒙元服务渲染为原生 ArkUI TextArea,必须用 :rows 控制初始显示行数;CSS height 在原生组件上不生效 -->
<textarea
:rows="item.value.rows || 2"
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
></textarea>
<!-- #endif -->
<!-- #ifndef MP-HARMONY -->
<textarea
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
></textarea>
<!-- #endif -->
</view>
</view>
</view>
<picker mode="selector" :range="item.value.options" v-if="item.controller == 'Select'" @change="pickerChange($event, index)">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<text v-if="item.val != ''">{{ item.val }}</text>
<text v-else class="color-tip">{{ $lang('components.nsForm.selectPlaceholder') }}</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
</picker>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Checkbox'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box check-group-box">
<checkbox-group @change="checkboxChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k">
<checkbox :value="v.value" :checked="v.checked" />
<view class="checkbox">
<text class="iconfont" :class="{ 'icon-fuxuankuang2': !v.checked, 'icon-fuxuankuang1 color-base-text': v.checked }" :style="{ color: v.checked ? checkedColor : '' }"></text>
{{ v.value }}
</view>
</label>
</checkbox-group>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Radio'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box radio-group-box">
<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" />
<view class="radio-box">
<text class="iconfont" :class="{ 'icon-yuan_checkbox': item.val != v.value, 'icon-yuan_checked color-base-text': item.val == v.value }" :style="{ color: item.val == v.value ? checkedColor : '' }"></text>
{{ v.value }}
</view>
</label>
</radio-group>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Img'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box img-boxs">
<view class="img-box" v-for="(v, k) in item.img_lists" :key="k" @click="uploadImg(index)">
<image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image>
<text class="iconfont icon-guanbi" @click.stop="delImg(k, index)"></text>
</view>
<view class="img-box" @click="addImg(index)"><text class="iconfont icon-add1"></text></view>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Date'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box box-flex">
<picker mode="date" :value="item.val" @change="bindDateChange($event, index)">
<view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view>
</picker>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Datelimit'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box date-boxs">
<view class="date-box">
<picker mode="date" :value="item.start_date" @change="bindStartDateChange($event, index)">
<view class="picker-box">
<view class="uni-input" :class="{ 'color-tip': !item.start_date }">{{ item.start_date ? item.start_date : item.value.placeholder_start }}</view>
</view>
</picker>
</view>
<view class="interval iconfont icon-jian"></view>
<view class="date-box">
<picker mode="date" :value="item.end_date" @change="bindEndDateChange($event, index)">
<view class="picker-box">
<view class="uni-input" :class="{ 'color-tip': !item.end_date }">{{ item.end_date ? item.end_date : item.value.placeholder_end }}</view>
</view>
</picker>
</view>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Time'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box box-flex">
<picker mode="time" :value="item.val" @change="bindTimeChange($event, index)">
<view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view>
</picker>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Timelimit'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box date-boxs">
<view class="date-box">
<picker mode="time" :value="item.start_time" @change="bindStartTimeChange($event, index)">
<view class="picker-box">
<view class="uni-input" :class="{ 'color-tip': !item.start_time }">{{ item.start_time ? item.start_time : item.value.placeholder_start }}</view>
</view>
</picker>
</view>
<view class="interval iconfont icon-jian"></view>
<view class="date-box">
<picker mode="time" :value="item.end_time" @change="bindEndTimeChange($event, index)">
<view class="picker-box">
<view class="uni-input" :class="{ 'color-tip': !item.end_time }">{{ item.end_time ? item.end_time : item.value.placeholder_end }}</view>
</view>
</picker>
</view>
</view>
</view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'City'">
<view class="order-cell box-flex" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<pick-regions :default-regions="item.default_regions" :select-arr="item.select_arr" @getRegions="handleGetRegions($event, index)">
<view class="select-address " :class="{ empty: !item.val, 'color-tip': !item.val }">
{{ item.val ? item.val : (item.select_arr == '2' ? $lang('components.nsForm.selectProvinceCity') : $lang('components.nsForm.selectProvinceCityDistrict')) }}
</view>
</pick-regions>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
</block>
</view>
</template>
<script>
import formPlatformMixin from '@/common/js/form-platform-mixin.js';
export default {
name: 'ns-form',
mixins: [formPlatformMixin],
props: {
data: {
type: Array,
default: () => []
},
customAttr: {
type: Object,
default: function() {
return {};
}
},
/**
* 表单样式配置(由后端 designer 通过 API 传入)。
* 字段名同时支持 camelCase(如 formPaddingX / inputHeight / labelColor)
* 与 snake_case(如 form_padding_x / input_height / label_color),camelCase 优先。
* 不传则使用组件内置默认(与重构前硬编码值一致,无视觉回归)。
*
* 支持的键:
* formPaddingX, formBgColor, rowPaddingY, rowMinHeight, labelGap,
* inputHeight, inputRadius, labelWidthMode ('auto'|'fixed'), labelFixedWidth,
* labelFontSize, labelColor, requiredColor, checkedColor,
* placeholderColor, showRowDivider, rowDividerColor
*
* 平台差异化:formConfig.terminal.{h5|weapp|harmony|app|quickapp} 只放差异字段
*/
formConfig: {
type: Object,
default: () => ({})
}
},
data() {
return {
formData: [],
maxLabelWidth: 0
};
},
computed: {
// 合并配置:内置默认 < 全局 formConfig < 当前平台 formConfig.terminal[platform]
// 同时兼容 snake_case 与 camelCase 字段名
config() {
// 1) 内置默认值(最低优先级,与重构前的硬编码值一一对应,确保未传 formConfig 时零回归)
const defaults = {
formPaddingX: 0,
formBgColor: '#ffffff',
rowPaddingY: 20,
rowMinHeight: 60,
labelGap: 16,
inputHeight: 60,
inputRadius: 0,
labelWidthMode: 'fixed', // 旧版 width: 80vw 用 fixed 180rpx 等价替代
labelFixedWidth: 180,
labelFontSize: 28,
labelColor: '#1f2937',
requiredColor: '#ef4444',
checkedColor: '#ff4544',
placeholderColor: '#909399',
placeholderFontSize: 28,
showRowDivider: true,
rowDividerColor: '#f0f0f0'
};
const fc = this.formConfig || {};
// 同时读 camelCase 与 snake_case,camelCase 优先
const pick = (camel, snake) => {
const v1 = fc[camel];
if (v1 !== undefined && v1 !== null && v1 !== '') return v1;
return fc[snake];
};
const base = {
formPaddingX: pick('formPaddingX', 'form_padding_x'),
formBgColor: pick('formBgColor', 'form_bg_color'),
rowPaddingY: pick('rowPaddingY', 'row_padding_y'),
rowMinHeight: pick('rowMinHeight', 'row_min_height'),
labelGap: pick('labelGap', 'label_gap'),
inputHeight: pick('inputHeight', 'input_height'),
inputRadius: pick('inputRadius', 'input_radius'),
labelWidthMode: pick('labelWidthMode', 'label_width_mode'),
labelFixedWidth: pick('labelFixedWidth', 'label_fixed_width'),
labelFontSize: pick('labelFontSize', 'label_font_size'),
labelColor: pick('labelColor', 'label_color'),
requiredColor: pick('requiredColor', 'required_color'),
checkedColor: pick('checkedColor', 'checked_color'),
placeholderColor: pick('placeholderColor', 'placeholder_color'),
placeholderFontSize: pick('placeholderFontSize', 'placeholder_font_size'),
showRowDivider: pick('showRowDivider', 'show_row_divider'),
rowDividerColor: pick('rowDividerColor', 'row_divider_color')
};
// 移除 undefined / 空字符串(保留 0 / false)
Object.keys(base).forEach(k => {
if (base[k] === undefined || base[k] === null || base[k] === '') delete base[k];
});
// showRowDivider 在前后端可能传数字 0/1,统一转 boolean
if ('showRowDivider' in base) {
base.showRowDivider = base.showRowDivider === true || base.showRowDivider === 1 || base.showRowDivider === '1';
}
// 2) 当前平台差异化配置(最高优先级),从 formConfig.terminal 取出当前平台 + 所有别名键,合并
const termBlock = fc.terminal || {};
const platformKeys = this.platformConfigKeys || [];
const platformCfg = Object.assign(
{},
...platformKeys.map(k => termBlock[k] || fc[k] || {})
);
// 平台块可能也是 snake_case,统一转 camelCase
const platformCfgCamel = {};
if (this.$util && typeof this.$util.snakeToCamel === 'function') {
Object.keys(platformCfg).forEach(k => {
platformCfgCamel[this.$util.snakeToCamel(k)] = platformCfg[k];
});
} else {
Object.assign(platformCfgCamel, platformCfg);
}
// 3) 三层合并:defaults < base < platformCfgCamel
return Object.assign({}, defaults, base, platformCfgCamel);
},
// 容器样式:内边距 + 背景 + 16 个 CSS 自定义属性
formWrapStyle() {
const cfg = this.config;
const num = (v, fb) => (typeof v === 'number' ? v : parseInt(v) || fb);
// 后端 formConfig 一律以 rpx 传递;
// _rpx() 按端分支:H5 输出 'Npx'(uni.upx2px),其它端输出 'Nrpx'
return this.$util.objToStyle({
padding: `0 ${this.$util.rpx(cfg.formPaddingX)}`,
background: cfg.formBgColor,
'--form-padding-x': this.$util.rpx(cfg.formPaddingX),
'--form-bg-color': cfg.formBgColor,
'--row-padding-y': this.$util.rpx(cfg.rowPaddingY),
'--row-min-height': this.$util.rpx(cfg.rowMinHeight),
'--label-gap': this.$util.rpx(cfg.labelGap),
'--input-height': this.$util.rpx(cfg.inputHeight),
'--input-radius': this.$util.rpx(cfg.inputRadius),
'--label-fixed-width': this.$util.rpx(cfg.labelFixedWidth),
'--label-font-size': this.$util.rpx(num(cfg.labelFontSize, 28)),
'--label-color': cfg.labelColor,
'--required-color': cfg.requiredColor,
'--checked-color': cfg.checkedColor,
'--placeholder-color': cfg.placeholderColor,
'--placeholder-font-size': this.$util.rpx(num(cfg.placeholderFontSize, 28)),
'--row-divider-color': cfg.rowDividerColor,
'--show-row-divider': cfg.showRowDivider === false ? '0' : '1'
});
},
rowWrapStyle() {
return this.$util.objToStyle({
padding: `${this.$util.rpx(this.config.rowPaddingY)} 0`
});
},
rowWrapClass() {
return this.config.showRowDivider === false ? 'order-wrap no-divider' : 'order-wrap';
},
rowCellStyle() {
return this.$util.objToStyle({
'min-height': this.$util.rpx(this.config.rowMinHeight)
});
},
// 标签宽度:根据 labelWidthMode 决定;fixed 用 --label-fixed-width,auto 用 maxLabelWidth
nameWidthStyle() {
const cfg = this.config;
if (cfg.labelWidthMode === 'fixed') {
return { width: this.$util.rpx(cfg.labelFixedWidth) };
}
return this.maxLabelWidth ? { width: this.$util.rpx(this.maxLabelWidth) } : { width: 'auto' };
},
// 输入框样式(input / textarea 共用)
inputStyle() {
return this.$util.objToStyle({
'min-height': this.$util.rpx(this.config.inputHeight),
'border-radius': this.$util.rpx(this.config.inputRadius),
'font-size': this.$util.rpx(this.config.labelFontSize),
'color': this.config.labelColor,
'line-height': this.$util.rpx(this.config.inputHeight)
});
},
// 必填星号颜色
requiredColor() {
return this.config.requiredColor;
},
// 复选 / 单选 选中色
checkedColor() {
return this.config.checkedColor;
}
},
created() {
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
},
watch: {
data: {
deep: true,
handler() {
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
}
},
formConfig: {
deep: true,
handler() {
this.$nextTick(() => this.calculateMaxLabelWidth());
}
}
},
methods: {
initFormData() {
// 深拷贝 prop 数据到本地状态,避免 mutate 上游
this.formData = JSON.parse(JSON.stringify(this.data || []));
this.processFormData();
},
setFormData() {
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
},
processFormData() {
this.formData.forEach(item => {
// Text / Textarea:当 placeholder 为空字符串或没有值时,不设置默认值
const skipDefault = (item.controller == 'Text' || item.controller == 'Textarea') && !item.value.placeholder;
if (!item.val && !skipDefault) item.val = item.value.default ? item.value.default : '';
if (item.value.options) {
item.option_lists = [];
item.value.options.forEach((v, k) => {
var obj = {};
obj.value = v;
obj.checked = false;
if (item.controller == 'Radio' && ((!item.val && k == 0) || (item.val &&
item.val == v))) {
obj.checked = true;
item.val = v;
}
if (item.controller == 'Checkbox' && item.val) {
let val = item.val.split(',');
obj.checked = val.indexOf(v) != -1;
}
item.option_lists.push(obj);
});
}
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) {
item.val = this.getDate();
} else {
item.val = item.value.default;
}
} else {
item.val = '';
}
}
if (item.controller == 'Datelimit') {
if (item.val) {
let date = item.val.split(' - ');
item.start_date = date[0];
item.end_date = date[1];
} else {
item.val = '';
if (item.value.is_show_default_start) {
if (item.value.is_current_start) {
item.start_date = this.getDate();
} else {
item.start_date = item.value.default_start;
}
} else {
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) {
item.val = item.start_date + ' - ' + item.end_date;
}
}
}
if (item.controller == 'Time' && !item.val) {
if (item.value.is_show_default) {
if (item.value.is_current) {
item.val = this.getTime();
} else {
item.val = item.value.default;
}
} else {
item.val = '';
}
}
if (item.controller == 'Timelimit') {
if (item.val) {
let time = item.val.split(' - ');
item.start_time = time[0];
item.end_time = time[1];
} else {
item.val = '';
if (item.value.is_show_default_start) {
if (item.value.is_current_start) {
item.start_time = this.getTime();
} else {
item.start_time = item.value.default_start;
}
} else {
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) {
item.val = item.start_time + ' - ' + item.end_time;
}
}
}
if (item.controller == 'City') {
item.full_address = '';
item.select_arr = item.value.default_type == 1 ? '2' : '3';
if (item.val) item.default_regions = item.val.split('-');
else item.default_regions = [];
}
});
},
// 计算最长 label 宽度(封装自 util.calculateMaxLabelWidth)
// 用于 labelWidthMode === 'auto' 时把 .name 的 width 撑开,避免长短 label 错位
calculateMaxLabelWidth() {
this.maxLabelWidth = this.$util.calculateMaxLabelWidth(
this.formData,
this.config.labelFontSize
);
},
verify() {
let verify = true;
for (let i = 0; i < this.formData.length; i++) {
let item = this.formData[i];
if (item.controller == 'Text') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseInput', { title: item.value.title })
});
break;
}
var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
if (item.name == 'ID_CARD' && reg.test(item.val) === false) {
verify = !item.value.required;
if (verify == false) {
this.$util.showToast({
title: this.$lang('components.nsForm.idCardInvalid')
});
return false;
}
if (verify == true && item.val != '') {
this.$util.showToast({
title: this.$lang('components.nsForm.idCardInvalid')
});
return false;
}
}
// 验证手机号
if (item.name == 'MOBILE' && this.$util.verifyMobile(item.val) === false) {
verify = !item.value.required;
if (verify == false) {
this.$util.showToast({
title: this.$lang('components.nsForm.mobileInvalid')
});
return false;
}
if (verify == true && item.val != '') {
this.$util.showToast({
title: this.$lang('components.nsForm.mobileInvalid')
});
return false;
}
}
}
if (item.controller == 'Textarea') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseInput', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Select') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Checkbox') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelectAtLeastOne', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Img') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseUploadAtLeastOne', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Date') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Datelimit') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
if (this.$util.timeTurnTimeStamp(item.start_date) > this.$util.timeTurnTimeStamp(item.end_date)) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.endDateBeforeStartDate')
});
break;
}
}
if (item.controller == 'Time') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
}
if (item.controller == 'Timelimit') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
if (item.start_time >= item.end_time) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.endTimeBeforeStartTime')
});
break;
}
}
if (item.controller == 'City') {
if (item.value.required && !item.val) {
verify = false;
this.$util.showToast({
title: this.$lang('components.nsForm.pleaseSelect', { title: item.value.title })
});
break;
}
}
}
if (verify) {
return this.formData;
} else return verify;
},
pickerChange(e, index) {
this.formData[index].val = this.data[index].value.options[e.detail.value];
this.$forceUpdate();
},
checkboxChange: function(e, index) {
this.formData[index].val = e.detail.value.toString();
this.formData[index].option_lists.forEach(item => {
item.checked = e.detail.value.indexOf(item.value) != -1;
});
this.$forceUpdate();
},
radioChange: function(e, index) {
this.formData[index].val = e.detail.value;
this.$forceUpdate();
},
// 修改图片
uploadImg(index) {
this.$util.upload(
Number(this.formData[index].value.max_count), {
path: 'evaluateimg'
},
res => {
if (res.length > 0) {
res.forEach(v => {
if (this.formData[index].img_lists.length >= Number(this.formData[index].value.max_count)) {
this.$util.showToast({
title: this.$lang('components.nsForm.maxUploadImages', { max: this.formData[index].value.max_count })
});
return false;
} else {
this.formData[index].img_lists.push(v);
}
});
}
this.formData[index].val = this.formData[index].img_lists.toString();
this.$forceUpdate();
}
);
},
// 添加图片
addImg(index) {
if (this.formData[index].img_lists.length >= Number(this.formData[index].value.max_count)) {
this.$util.showToast({
title: this.$lang('components.nsForm.maxUploadImages', { max: this.formData[index].value.max_count })
});
return false;
}
this.$util.upload(
Number(this.formData[index].value.max_count), {
path: 'evaluateimg'
},
res => {
if (res.length > 0) {
res.forEach(v => {
this.formData[index].img_lists.push(v);
});
}
this.formData[index].val = this.formData[index].img_lists.toString();
this.$forceUpdate();
}
);
},
// index 当前数据的下标 k 当前图片的下标
delImg(k, index) {
this.formData[index].img_lists.splice(k, 1);
this.formData[index].val = this.formData[index].img_lists.toString();
this.$forceUpdate();
},
getDate() {
const date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
month = month > 9 ? month : '0' + month;
day = day > 9 ? day : '0' + day;
return `${year}-${month}-${day}`;
},
getTime() {
const date = new Date();
let hour = date.getHours(); //得到小时
let min = date.getMinutes(); //得到分钟
hour = hour > 9 ? hour : '0' + hour;
min = min > 9 ? min : '0' + min;
return `${hour}:${min}`;
},
bindDateChange(e, index) {
this.formData[index].val = e.detail.value;
this.$forceUpdate();
},
bindStartDateChange(e, index) {
this.$set(this.formData[index], 'start_date', e.detail.value);
this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date);
this.$forceUpdate();
},
bindEndDateChange(e, index) {
this.$set(this.formData[index], 'end_date', e.detail.value);
this.$set(this.formData[index], 'val', this.formData[index].start_date + ' - ' + this.formData[index].end_date);
this.$forceUpdate();
},
bindTimeChange(e, index) {
this.formData[index].val = e.detail.value;
this.$forceUpdate();
},
bindStartTimeChange(e, index) {
this.formData[index].start_time = e.detail.value;
this.$forceUpdate();
},
bindEndTimeChange(e, index) {
this.formData[index].end_time = e.detail.value;
this.formData[index].val = this.formData[index].start_time + ' - ' + this.formData[index].end_time;
this.$forceUpdate();
},
// 获取选择的地区
handleGetRegions(e, index) {
this.formData[index].val = '';
this.formData[index].val += e[0] != undefined ? e[0].label : '';
this.formData[index].val += e[1] != undefined ? '-' + e[1].label : '';
this.formData[index].val += e[2] != undefined ? '-' + e[2].label : '';
this.$forceUpdate();
}
}
};
</script>
<style lang="scss" scoped>
/* CSS 自定义属性:默认值,与组件内 config.defaults 一一对应,
formConfig 未传时作为兜底;运行时会被 formWrapStyle 注入的 inline style 覆盖。
后端 formConfig 一律以 rpx 传递,前端按 rpx 写入;
仅 H5 端需要在 JS computed(formWrapStyle 等)里通过 _rpx() 转 px。 */
.form-wrap {
--form-padding-x: 0rpx;
--form-bg-color: #ffffff;
--row-padding-y: 20rpx;
--row-min-height: 60rpx;
--label-gap: 16rpx;
--input-height: 60rpx;
--input-radius: 0rpx;
--label-fixed-width: 180rpx;
--label-font-size: 28rpx;
--label-color: #1f2937;
--required-color: #ef4444;
--placeholder-font-size: 28rpx;
--checked-color: #ff4544;
--placeholder-color: #909399;
--row-divider-color: #f0f0f0;
--show-row-divider: 1;
}
.order-wrap {
padding: var(--row-padding-y, 20rpx) 0;
&.no-divider {
border-bottom: 0;
}
&:last-child {
margin-bottom: 0;
border-bottom: 0;
}
}
.order-cell {
display: flex;
align-items: center;
background: #fff;
position: relative;
min-height: var(--row-min-height, 60rpx);
&.textarea {
align-items: flex-start;
}
&.clear-flex {
display: block;
.box {
margin-top: 16rpx;
text-align: left;
}
}
&:last-child {
margin-bottom: 0;
// calc + 0/1 控制分割线显隐(formConfig.showRowDivider = false 时整条隐藏)
border-bottom: calc(var(--show-row-divider, 1) * 2rpx) solid var(--row-divider-color, #f0f0f0);
}
&.align-top {
align-items: flex-start;
}
text {
font-size: var(--label-font-size, $font-size-base);
}
.name {
width: var(--label-fixed-width, 180rpx);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--label-gap, 16rpx);
}
.tit {
text-align: left;
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
text {
font-size: var(--label-font-size, $font-size-base);
}
}
.required {
color: var(--required-color, #ef4444);
font-size: var(--label-font-size, 28rpx);
margin-left: 4rpx;
width: 14rpx;
text-align: left;
display: inline-block;
}
.box {
flex: 1;
padding: 0 10rpx;
line-height: inherit;
text-align: left;
input {
font-size: var(--label-font-size, $font-size-base);
text-align: left;
color: var(--label-color, #1f2937);
// 显式 height / line-height / min-height 同步覆盖 WebView 默认与鸿蒙原生 TextInput 的固有高度,
// 避免 placeholder / 文字下半部分被裁切
line-height: var(--input-height, 60rpx);
height: var(--input-height, 60rpx);
min-height: var(--input-height, 60rpx);
border-radius: var(--input-radius, 0rpx);
box-sizing: border-box;
padding: 0;
background: transparent;
// 原生 placeholder 样式(WebView / H5 生效;小程序/鸿蒙的 placeholder
// 通过 placeholder-class="placeholder" 钩子走 .placeholder 规则)
&::placeholder {
color: var(--placeholder-color, #909399);
font-size: var(--placeholder-font-size, 28rpx);
}
}
// placeholder-class 钩子:在鸿蒙原生 TextInput / WebView 中都生效,
// 限定在 .form-wrap 内避免污染全局 .placeholder / .color-tip
.placeholder {
color: var(--placeholder-color, #909399);
font-size: var(--placeholder-font-size, 28rpx);
line-height: var(--input-height, 60rpx);
}
textarea {
font-size: var(--label-font-size, $font-size-base);
width: 100%;
color: var(--label-color, #1f2937);
// 鸿蒙元服务渲染为原生 ArkUI TextArea,不识别 CSS height,由 :rows 控制行数;
// 这里给所有端都设一个最小高度基线,避免空内容时塌缩
min-height: 88rpx;
height: 88rpx;
line-height: 44rpx;
text-align: left;
border-radius: var(--input-radius, 0rpx);
box-sizing: border-box;
padding: 0;
background: transparent;
}
.uni-input {
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
line-height: var(--input-height, 60rpx);
min-height: var(--input-height, 60rpx);
}
.select-address {
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
}
.color-tip {
color: var(--placeholder-color, #909399);
}
checkbox-group {
display: flex;
flex-wrap: wrap;
}
radio-group {
display: flex;
flex-wrap: wrap;
}
label {
display: flex;
align-items: center;
line-height: 1;
margin-right: 30rpx;
}
&.img-boxs {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.img-box {
margin: 10rpx 20rpx 10rpx 0;
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
border: 1rpx solid $color-line;
border-radius: 4rpx;
position: relative;
.icon-guanbi {
position: absolute;
top: -14rpx;
right: -14rpx;
display: inline-block;
width: 28rpx;
height: 28rpx;
line-height: 28rpx;
color: $color-tip;
}
.icon-add1 {
font-size: 40rpx;
}
image {
width: 100%;
height: 100%;
}
}
&.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: bold;
}
.date-box {
.picker-box {
display: flex;
align-items: center;
justify-content: flex-end;
}
}
}
.radio-group-box {
radio {
display: none;
}
.radio-box {
display: flex;
align-items: center;
line-height: 1;
.iconfont {
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: $color-tip;
font-size: var(--label-font-size, $font-size-base);
}
.box-flex picker {
display: block;
width: 100%;
}
.icon-right {
line-height: 1;
position: unset;
}
}
</style>