Files
YGChatCS/components/FormCard/index.vue
2025-09-06 16:24:44 +08:00

180 lines
4.4 KiB
Vue

<template>
<view class="form-wrapper">
<view class="form-header">
<uni-icons class="minus" color="#00A6FF" size="22" type="minus" />
<text class="form-title">{{ title }}</text>
<uni-icons
v-if="showDeleteIcon"
class="delete"
color="#00A6FF"
size="22"
type="trash"
@click="handleDelete"
/>
</view>
<view class="form-item-wrapper">
<view class="form-item">
<view class="form-item-row">
<text class="form-label"> </text>
<input
class="form-input"
:class="{ 'form-input-error': nameError }"
v-model="nameValue"
placeholder="请输入姓名"
@blur="validateName"
/>
</view>
<text v-if="nameError" class="form-error">{{ nameError }}</text>
</view>
<view class="form-item">
<view class="form-item-row">
<text class="form-label">手机号</text>
<input
class="form-input"
:class="{ 'form-input-error': phoneError }"
v-model="phoneValue"
placeholder="请输入手机号"
type="tel"
maxlength="11"
@blur="validatePhone"
/>
</view>
<text v-if="phoneError" class="form-error">{{ phoneError }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from "vue";
// 常量定义
const PHONE_REGEX = /^1[3-9]\d{9}$/;
const ERROR_MESSAGES = {
NAME_REQUIRED: "请输入姓名",
PHONE_REQUIRED: "手机号不能为空",
PHONE_INVALID: "请输入正确的手机号",
};
/**
* FormCard 组件 Props
* @typedef {Object} FormCardProps
* @property {string} title - 表单标题
* @property {Object} form - 表单数据对象
* @property {string} form.visitorName - 姓名
* @property {string} form.contactPhone - 手机号
* @property {boolean} showDeleteIcon - 是否显示删除图标
*/
const props = defineProps({
title: {
type: String,
default: "游客1",
},
form: {
type: Object,
default: () => ({
visitorName: "",
contactPhone: "",
}),
validator: (value) => {
return (
value &&
typeof value === "object" &&
"visitorName" in value &&
"contactPhone" in value
);
},
},
showDeleteIcon: {
type: Boolean,
default: true,
},
});
/**
* FormCard 组件事件
* @typedef {Object} FormCardEmits
* @property {Function} update:visitorName - 更新姓名事件
* @property {Function} update:contactPhone - 更新手机号事件
* @property {Function} delete - 删除表单事件
*/
const emit = defineEmits([
"update:visitorName",
"update:contactPhone",
"delete",
]);
// 响应式状态
const nameError = ref("");
const phoneError = ref("");
// 计算属性 - 双向绑定
const nameValue = computed({
get: () => props.form?.visitorName || "",
set: (value) => emit("update:visitorName", value?.trim() || ""),
});
const phoneValue = computed({
get: () => props.form?.contactPhone || "",
set: (value) => {
// 只允许数字输入
const numericValue = value.replace(/\D/g, "");
emit("update:contactPhone", numericValue);
},
});
// 工具函数
/**
* 验证姓名是否有效
* @param {string} name - 姓名
* @returns {string} 错误信息,空字符串表示验证通过
*/
const getNameError = (name) => {
if (!name || name.trim() === "") {
return ERROR_MESSAGES.NAME_REQUIRED;
}
return "";
};
/**
* 验证手机号是否有效
* @param {string} phone - 手机号
* @returns {string} 错误信息,空字符串表示验证通过
*/
const getPhoneError = (phone) => {
if (!phone) {
return ERROR_MESSAGES.PHONE_REQUIRED;
}
if (!PHONE_REGEX.test(phone)) {
return ERROR_MESSAGES.PHONE_INVALID;
}
return "";
};
// 验证方法
const validateName = () => {
nameError.value = getNameError(props.form?.visitorName);
};
const validatePhone = () => {
phoneError.value = getPhoneError(props.form?.contactPhone);
};
// 事件处理
const handleDelete = () => {
emit("delete");
};
// 暴露给模板的方法(用于测试或外部调用)
defineExpose({
validateName,
validatePhone,
getNameError,
getPhoneError,
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>