mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 21:40:50 +00:00
feat(移动端): 订单自定义表单支持全部管理端类型与格式校验
支持 text/number/email/data/time/id/phone/img 八类型(键盘按类型切换、日期时间选择器、图片上传预览),兼容历史 input/textarea/picker;数字/邮箱/手机号/身份证格式校验
This commit is contained in:
parent
6b6be74959
commit
cf78510816
@ -2,6 +2,9 @@
|
||||
订单自定义表单
|
||||
根据后端 custom_form 动态渲染必填信息字段
|
||||
字段结构:{ label, field, type, options, required }
|
||||
type 为管理端「自定义表单」类型:text 文本框 / number 数字 / email 邮件 /
|
||||
data 日期 / time 时间 / id 身份证 / phone 手机号 / img 图片,
|
||||
兼容历史值 input / textarea / picker
|
||||
-->
|
||||
<template>
|
||||
<view class="custom-form-section" v-if="visible && fields.length > 0">
|
||||
@ -13,15 +16,16 @@
|
||||
<text class="required" v-if="field.required">*</text>
|
||||
</view>
|
||||
<view class="form-input-wrap">
|
||||
<!-- 单行文本 -->
|
||||
<!-- 文本类:文本框/数字/邮件/身份证/手机号,按类型切换键盘 -->
|
||||
<input
|
||||
v-if="field.type === 'input'"
|
||||
v-if="isTextInput(field)"
|
||||
class="form-input"
|
||||
:type="textInputKeyboard(field)"
|
||||
:placeholder="$t('请输入') + field.label"
|
||||
:value="formData[field.field]"
|
||||
@input="onInput(field.field, $event)"
|
||||
/>
|
||||
<!-- 下拉选择 -->
|
||||
<!-- 下拉选择(自定义 options) -->
|
||||
<picker
|
||||
v-else-if="field.type === 'picker'"
|
||||
:range="field.options"
|
||||
@ -33,6 +37,36 @@
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 日期(管理端类型值 data,兼容 date) -->
|
||||
<picker
|
||||
v-else-if="field.type === 'data' || field.type === 'date'"
|
||||
mode="date"
|
||||
@change="onValueChange(field.field, $event)"
|
||||
>
|
||||
<view class="form-picker">
|
||||
{{ formData[field.field] || $t('请选择') + field.label }}
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 时间 -->
|
||||
<picker v-else-if="field.type === 'time'" mode="time" @change="onValueChange(field.field, $event)">
|
||||
<view class="form-picker">
|
||||
{{ formData[field.field] || $t('请选择') + field.label }}
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 图片上传 -->
|
||||
<view v-else-if="field.type === 'img'" class="form-upload">
|
||||
<image
|
||||
v-if="formData[field.field]"
|
||||
class="upload-preview"
|
||||
:src="formData[field.field]"
|
||||
mode="aspectFill"
|
||||
@click="previewUpload(formData[field.field])"
|
||||
/>
|
||||
<view v-else class="upload-add" @click="uploadImage(field.field)">+</view>
|
||||
<view v-if="formData[field.field]" class="upload-del" @click.stop="removeImage(field.field)">×</view>
|
||||
</view>
|
||||
<!-- 多行文本 -->
|
||||
<textarea
|
||||
v-else-if="field.type === 'textarea'"
|
||||
@ -50,6 +84,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import i18n from '@/utils/lang.js';
|
||||
import util from '@/utils/util.js';
|
||||
|
||||
defineOptions({ name: 'OrderCustomForm' });
|
||||
|
||||
@ -67,30 +102,75 @@ const emit = defineEmits(['update:modelValue', 'change']);
|
||||
/** 当前表单数据(基于 value 拷贝避免直接修改 prop) */
|
||||
const formData = computed(() => props.modelValue || {});
|
||||
|
||||
/** 文本类输入事件,更新对应字段并通知父组件 */
|
||||
function onInput(field, e) {
|
||||
var val = e.detail.value;
|
||||
/** 文本类输入类型(input 为历史值,其余为管理端类型) */
|
||||
var TEXT_INPUT_TYPES = ['input', 'text', 'number', 'email', 'id', 'phone'];
|
||||
/** 各类型格式校验规则,返回错误提示文案,通过返回空串 */
|
||||
var FORMAT_RULES = {
|
||||
number: /^[+-]?\d+(\.\d+)?$/,
|
||||
email: /^[\w.+-]+@[\w-]+(\.[\w-]+)+$/,
|
||||
phone: /^1[3-9]\d{9}$/,
|
||||
id: /(^\d{15}$)|(^\d{17}[\dXx]$)/,
|
||||
};
|
||||
|
||||
/** 是否文本类输入框 */
|
||||
function isTextInput(field) {
|
||||
return TEXT_INPUT_TYPES.indexOf(field.type) !== -1;
|
||||
}
|
||||
/** 文本类输入键盘:数字/手机号用数字键盘,其余默认键盘 */
|
||||
function textInputKeyboard(field) {
|
||||
return field.type === 'number' || field.type === 'phone' ? 'number' : 'text';
|
||||
}
|
||||
|
||||
/** 更新字段值并通知父组件 */
|
||||
function setFieldValue(field, val) {
|
||||
var next = { ...formData.value, [field]: val };
|
||||
emit('update:modelValue', next);
|
||||
emit('change', next);
|
||||
}
|
||||
|
||||
/** 文本类输入事件 */
|
||||
function onInput(field, e) {
|
||||
setFieldValue(field, e.detail.value);
|
||||
}
|
||||
/** picker 选择事件,根据 options 取出 value */
|
||||
function onPickerChange(field, e) {
|
||||
var option = field.options[e.detail.value] || {};
|
||||
var next = { ...formData.value, [field.field]: option.value };
|
||||
emit('update:modelValue', next);
|
||||
emit('change', next);
|
||||
setFieldValue(field.field, option.value);
|
||||
}
|
||||
/** 校验必填字段是否已填写 */
|
||||
/** 日期/时间选择事件,直接取选中值 */
|
||||
function onValueChange(field, e) {
|
||||
setFieldValue(field, e.detail.value);
|
||||
}
|
||||
/** 上传图片字段:选择并上传后存图片 URL */
|
||||
function uploadImage(field) {
|
||||
util.uploadImageOne('upload/image', function (res) {
|
||||
setFieldValue(field, res.data.url);
|
||||
});
|
||||
}
|
||||
/** 删除已上传图片 */
|
||||
function removeImage(field) {
|
||||
setFieldValue(field, '');
|
||||
}
|
||||
/** 预览已上传图片 */
|
||||
function previewUpload(url) {
|
||||
uni.previewImage({ urls: [url] });
|
||||
}
|
||||
|
||||
/** 校验必填与格式,通过返回 true */
|
||||
function validate() {
|
||||
for (var i = 0; i < props.fields.length; i++) {
|
||||
var field = props.fields[i];
|
||||
if (field.required) {
|
||||
var val = formData.value[field.field];
|
||||
if (!val || (typeof val === 'string' && val.trim() === '')) {
|
||||
uni.showToast({ title: i18n.t('请填写') + field.label, icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
var val = formData.value[field.field];
|
||||
var isEmpty = !val || (typeof val === 'string' && val.trim() === '');
|
||||
if (field.required && isEmpty) {
|
||||
uni.showToast({ title: i18n.t('请填写') + field.label, icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
if (isEmpty) continue;
|
||||
var rule = FORMAT_RULES[field.type];
|
||||
if (rule && !rule.test(String(val).trim())) {
|
||||
uni.showToast({ title: i18n.t('请输入正确的') + field.label, icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -165,4 +245,38 @@ defineExpose({ validate });
|
||||
.picker-arrow {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
.form-upload {
|
||||
position: relative;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
}
|
||||
.upload-add {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px dashed #ddd;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 60rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
.upload-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.upload-del {
|
||||
position: absolute;
|
||||
top: -14rpx;
|
||||
right: -14rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background: #666;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
line-height: 36rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user