| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- <template>
- <view class="form">
- <block v-for="item in formConfig" :key="item.key">
- <!-- 文本 -->
- <view v-if="item.type === 'input'" v-show="item.visible !== false" class="form_block">
- <gInput
- :ref="(el) => setComponentRef(el, item.key)"
- :label="item.label"
- :readOnly="item.readOnly"
- :placeholder="item.placeholder"
- :required="item.required"
- :errorMsg="item.errorMsg"
- :value="formData[item.key]"
- @inputChange="(e) => onInputChange(e, item.key)"
- @validateRequest="(e) => onValidateRequest({ detail: { key: item.key, value: e.value } })"
- />
- </view>
- <!-- 文本域 -->
- <view v-if="item.type === 'textArea'" v-show="item.visible !== false" class="form_block">
- <gTextArea
- :ref="(el) => setComponentRef(el, item.key)"
- :label="item.label"
- :readOnly="item.readOnly"
- :placeholder="item.placeholder"
- :required="item.required"
- :errorMsg="item.errorMsg"
- :value="formData[item.key]"
- @inputChange="(e) => onInputChange(e, item.key)"
- @validateRequest="(e) => onValidateRequest({ detail: { key: item.key, value: e.value } })"
- />
- </view>
- <!-- 选择 -->
- <view v-if="item.type === 'radio'" v-show="item.visible !== false" class="form_block">
- <gRadio
- :ref="(el) => setComponentRef(el, item.key)"
- :label="item.label"
- :options="item.options"
- :inline="item.inline"
- :readOnly="item.readOnly"
- :required="item.required"
- :errorMsg="item.errorMsg"
- :value="formData[item.key]"
- @radioChange="(e) => onRadioChange(e, item.key)"
- @validateRequest="(e) => onValidateRequest({ detail: { key: item.key, value: e.value } })"
- />
- </view>
- <!-- 短信验证 -->
- <view v-if="item.type === 'smsCode'" v-show="item.visible !== false" class="form_block">
- <gSmsCode
- :ref="(el) => setComponentRef(el, item.key)"
- :phoneLabel="item.phoneLabel"
- :codeLabel="item.codeLabel"
- :sendCodeText="item.sendCodeText"
- :countdownText="item.countdownText"
- :readOnly="item.readOnly"
- :required="item.required"
- :errorMsg="item.errorMsg"
- :phoneValue="formData[item.key + '_phone']"
- :codeValue="formData[item.key + '_code']"
- @phoneInputChange="(e) => onSmsCodePhoneChange(e, item.key)"
- @codeInputChange="(e) => onSmsCodeCodeChange(e, item.key)"
- @sendCodeRequest="(e) => onSmsCodeSendRequest(e, item.key)"
- @validationResult="(e) => handleSmsValidationResult(e, item.key)"
- />
- </view>
- <!-- 省市区选择器 -->
- <view v-if="item.type === 'regionPicker'" v-show="item.visible !== false" class="form_block">
- <gRegionPicker
- :ref="(el) => setComponentRef(el, item.key)"
- :label="item.label"
- :level="item.level"
- :readOnly="item.readOnly"
- :placeholder="item.placeholder"
- :required="item.required"
- :value="formData[item.key]"
- @regionPickerChange="(e) => onRegionPickerChange(e, item.key)"
- />
- </view>
- <!-- 事件触发器 -->
- <view v-if="item.type === 'action'" v-show="item.visible !== false" class="form_block">
- <gAction
- :ref="(el) => setComponentRef(el, item.key)"
- :label="item.label"
- :readOnly="item.readOnly"
- :placeholder="item.placeholder"
- :required="item.required"
- :errorMsg="item.errorMsg"
- :value="formData[item.key]"
- @actionClick="(e) => onActionClick(e, item.key)"
- />
- </view>
- </block>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, defineProps, defineEmits, watch, nextTick, defineExpose } from 'vue';
- import gInput from './input/index.vue';
- import gTextArea from './textArea/index.vue';
- import gRadio from './radio/index.vue';
- import gSmsCode from './smsCode/index.vue';
- import gRegionPicker from './regionPicker/index.vue';
- import gAction from './action/index.vue';
- const props = defineProps({
- formConfig: {
- type: Array,
- default: () => []
- },
- formData: {
- type: Object,
- default: () => ({})
- }
- });
- const emit = defineEmits(['formDataChange', 'smsCodeSendSuccess', 'smsCodeSendFail', 'actionClick', 'errorReport', 'formSubmit']);
- const formData = ref({});
- const hasError = ref(false);
- const componentRefs = ref({});
- const setComponentRef = (el, key) => {
- if (el) {
- componentRefs.value[key] = el;
- }
- };
- watch(() => props.formData, (newVal) => {
- formData.value = { ...newVal };
- }, { deep: true, immediate: true });
- // [组件]输入框
- const onInputChange = (e, key) => {
- const { value } = e;
- const newFormData = { ...formData.value, [key]: value };
- formData.value = newFormData;
- emit('formDataChange', { formData: newFormData, target: { [key]: value } });
- };
- // [组件]单选框
- const onRadioChange = (e, key) => {
- const { value } = e;
- const newFormData = { ...formData.value, [key]: value };
- formData.value = newFormData;
- emit('formDataChange', { formData: newFormData, target: { [key]: value } });
- };
- // [组件]发送短信验证码
- const onSmsCodePhoneChange = (e, key) => {
- const { phoneValue } = e;
- const newFormData = {
- ...formData.value,
- [`${key}_phone`]: phoneValue,
- [`${key}`]: phoneValue
- };
- formData.value = newFormData;
- emit('formDataChange', {
- formData: newFormData,
- target: {
- [`${key}_phone`]: phoneValue,
- [`${key}`]: phoneValue }
- }
- );
- };
- const onSmsCodeCodeChange = (e, key) => {
- const { codeValue } = e;
- const newFormData = { ...formData.value, [`${key}_code`]: codeValue };
- formData.value = newFormData;
- emit('formDataChange', { formData: newFormData, target: { [`${key}_code`]: codeValue } });
- };
- const onSmsCodeSendRequest = (e, key) => {
- const { phoneValue } = e;
- const config = props.formConfig.find(item => item.key === key);
- const sendCodeApi = config.sendCodeApi;
- if (sendCodeApi) {
- const validatePhone = config.validatePhone;
- if(validatePhone && !validatePhone(phoneValue)) {
- uni.showModal({
- title: "温馨提示",
- content: config.errorMsg,
- showCancel: false,
- });
- return;
- }
- sendCodeApi(phoneValue)
- .then((res) => {
- const component = componentRefs.value[key];
- if (component && component.startCountdown) {
- component.startCountdown();
- }
- emit('smsCodeSendSuccess', { key, res });
- })
- .catch((err) => {
- emit('smsCodeSendFail', { key, err });
- });
- }
- };
- const handleSmsValidationResult = (e, key) => {
- const component = componentRefs.value[key];
- if (component && component.handleValidationResult) {
- component.handleValidationResult(e);
- }
- }
- // [组件]省市区选择器
- const onRegionPickerChange = (e, key) => {
- const { code, postcode, value } = e;
- const config = props.formConfig.find(item => item.key === key);
- const newFormData = {
- ...formData.value,
- [`${key}`]: e[config.currentBindKey || value],
- [`${key}_regionCode`]: code,
- [`${key}_regionPostCode`]: postcode,
- [`${key}_regionValue`]: value,
- };
- formData.value = newFormData;
- emit('formDataChange', {
- formData: newFormData,
- target: {
- [`${key}`]: e[config.currentBindKey || value],
- [`${key}_regionCode`]: code,
- [`${key}_regionPostCode`]: postcode,
- [`${key}_regionValue`]: value,
- }
- });
- };
- // [组件]事件触发器
- const onActionClick = (e, key) => {
- const { value } = e;
- const newFormData = { ...formData.value, [key]: value };
- emit('actionClick', { formData: newFormData, target: { [key]: value } })
- };
- // [辅助]校验
- const onValidateRequest = (e) => {
- const { key, value } = e.detail;
- let config;
- let fieldKey;
- if (key.includes('_phone') || key.includes('_code')) {
- const [baseKey, field] = key.split('_');
- config = props.formConfig.find(form => form.key === baseKey);
- fieldKey = field;
- } else {
- config = props.formConfig.find(form => form.key === key);
- }
-
- if (!config) return;
- let isValid = true;
- let validateFunc;
- if (fieldKey === 'phone') {
- validateFunc = config.validatePhone;
- } else if (fieldKey === 'code') {
- validateFunc = config.validateCode;
- } else {
- validateFunc = config.validate;
- }
-
- if (config.required && (
- value === void(0)
- || value === null
- || (typeof value === 'string' && value.trim() === '')
- || (Array.isArray(value) && value.length === 0)
- )) {
- isValid = false;
- } else if (value && typeof validateFunc === 'function' && !validateFunc(value)) {
- isValid = false;
- }
-
- const errorMsg = config.errorMsg;
- const component = componentRefs.value[config.key];
-
- if (component) {
- if (fieldKey) {
- // smsCode 组件内部处理
- component.handleValidationResult({ key: fieldKey, isValid });
- } else {
- // 其他组件暂无直接设置 error 的方法,通常通过 props 或事件反馈,这里简化处理,假设组件不需要显式 error 状态
- // 如果组件需要显示 error 状态,需要在组件内增加 error prop 并传递
- }
- }
-
- if (!isValid) {
- hasError.value = true;
- emit('errorReport', { errorMsg });
- }
- };
- // [动作]提交
- const submitForm = () => {
- hasError.value = false;
- let allValid = true;
-
- props.formConfig.forEach((config) => {
- if (config.enabled === false) { // Assuming explicit false check or check logic
- return;
- }
-
- // 模拟校验逻辑,实际项目中可能需要更完善的校验
- // 这里简单重用 onValidateRequest 的逻辑,但要注意它依赖 componentRefs 和事件
- // 简化:直接检查数据
-
- let isValid = true;
- if (config.type === 'smsCode') {
- const phoneVal = formData.value[`${config.key}_phone`];
- const codeVal = formData.value[`${config.key}_code`];
-
- // Check Phone
- let phoneValid = true;
- if(config.required && !phoneVal) phoneValid = false;
- if(phoneVal && config.validatePhone && !config.validatePhone(phoneVal)) phoneValid = false;
-
- // Check Code
- let codeValid = true;
- if(config.required && !codeVal) codeValid = false;
- if(codeVal && config.validateCode && !config.validateCode(codeVal)) codeValid = false;
-
- if(!phoneValid || !codeValid) isValid = false;
-
- // Trigger validation visual update if needed
- const component = componentRefs.value[config.key];
- if(component) {
- component.handleValidationResult({ key: 'phone', isValid: phoneValid });
- component.handleValidationResult({ key: 'code', isValid: codeValid });
- }
- } else {
- const val = formData.value[config.key];
- if (config.required && (
- val === void(0) ||
- val === null ||
- (typeof val === 'string' && val.trim() === '') ||
- (Array.isArray(val) && val.length === 0)
- )) {
- isValid = false;
- } else if (val && config.validate && !config.validate(val)) {
- isValid = false;
- }
- }
-
- if (!isValid) {
- allValid = false;
- // Trigger visual error if needed (omitted for simplicity as component props usually handle this or errorMsg toast is enough)
- emit('errorReport', { errorMsg: config.errorMsg });
- }
- });
- if (allValid) {
- emit('formSubmit', { formData: formData.value });
- }
- };
- defineExpose({
- submitForm
- });
- </script>
- <style>
- .form {
- width: 100%;
- height: fit-content;
- background-color: #fff;
- padding: 0 20upx;
- }
- .form .form_block + .form_block {
- border-top: 2upx solid #e5e5e5;
- }
- </style>
|