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
162 lines
4.1 KiB
Vue
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>
|