Files
nianxx-h5/src/components/FormCard/index.vue
duanshuwen 1a5a2ae6a9 refactor: clean up codebase and add new features
Replace SCSS variable usages with explicit pixel/hex values for consistent styling across all components
Fix broken template syntax including missing class spaces and incorrect closing tags
Migrate constant and API imports to centralized @/constants and @/api modules
Add new utility classes: IdUtils, CallbackUtils, and TimerUtils
Add new chat conversation API endpoints for recent conversations and message lists
Add new Discovery page components (FindTabs, QuickQuestions, CardSwiper) and their styles
Update app store config to use environment variables for base API and WebSocket URLs
Add new selected tab icon assets
2026-05-26 23:50:37 +08:00

162 lines
4.1 KiB
Vue

<template>
<div class="form-wrapper">
<div class="form-header">
<uni-icons class="minus uni-color" color="opacity" size="22" type="minus" />
<span class="form-title">{{ title }}</span>
<uni-icons v-if="showDeleteIcon" class="delete uni-color" color="opacity" size="22" type="trash"
@click="handleDelete" />
</div>
<div class="form-item-wrapper">
<div class="form-item">
<div class="form-item-row">
<span class="form-label"> </span>
<input class="form-input" :class="{ 'form-input-error': nameError }" v-model="nameValue" placeholder="请输入姓名"
@blur="validateName" />
</div>
<span v-if="nameError" class="form-error">{{ nameError }}</span>
</div>
<div class="form-item">
<div class="form-item-row">
<span class="form-label">手机号</span>
<input class="form-input" :class="{ 'form-input-error': phoneError }" v-model="phoneValue"
placeholder="请输入手机号" type="tel" maxlength="11" @blur="validatePhone" />
</div>
<span v-if="phoneError" class="form-error">{{ phoneError }}</span>
</div>
</div>
</div>
</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>