save
This commit is contained in:
@@ -62,7 +62,23 @@ export interface WechatLoginParams {
|
||||
|
||||
// API 函数定义
|
||||
export function login(params: LoginParams) {
|
||||
return http<LoginResponse>('auth/login', params, null, 'post')
|
||||
// 暂时直接返回成功响应,等待后端接入
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
code: 200,
|
||||
msg: '登录成功',
|
||||
data: {
|
||||
token: 'mock_token_' + Date.now(),
|
||||
user: {
|
||||
id: '1',
|
||||
email: params.email,
|
||||
phone: params.phone,
|
||||
nickname: '测试用户',
|
||||
avatar: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
// return http<LoginResponse>('auth/login', params, null, 'post')
|
||||
}
|
||||
|
||||
export function register(params: RegisterParams) {
|
||||
@@ -70,6 +86,11 @@ export function register(params: RegisterParams) {
|
||||
}
|
||||
|
||||
export function sendCode(params: SendCodeParams) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
code: 200,
|
||||
msg: '登录成功',
|
||||
})
|
||||
return http<boolean>('auth/sendCode', params, null, 'post')
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
--color-progress-bar: #d1d5df !important;
|
||||
|
||||
--color-label-bg: whitesmoke;
|
||||
--color-link: rgb(64, 158, 255)
|
||||
--color-link: #2563EB;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -219,6 +219,11 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link{
|
||||
color: var(--color-link);
|
||||
@apply hover:opacity-80;
|
||||
}
|
||||
|
||||
.cp{
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ defineEmits(['click'])
|
||||
padding: 0 1.3rem;
|
||||
height: 2.4rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
& > span {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs, watch } from 'vue';
|
||||
import {defineComponent, ref, useAttrs, watch, computed} from 'vue';
|
||||
import Close from "@/components/icon/Close.vue";
|
||||
import { useDisableEventListener } from "@/hooks/event.ts";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
|
||||
defineOptions({
|
||||
name: "BaseInput",
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number],
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
autofocus: Boolean,
|
||||
error: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
@@ -32,34 +37,31 @@ const emit = defineEmits(['update:modelValue', 'input', 'change', 'focus', 'blur
|
||||
const attrs = useAttrs();
|
||||
|
||||
const inputValue = ref(props.modelValue);
|
||||
const errorMsg = ref('');
|
||||
let focus = $ref(false)
|
||||
let inputEl = $ref<HTMLDivElement>()
|
||||
const passwordVisible = ref(false)
|
||||
|
||||
const inputType = computed(() => {
|
||||
if (props.type === 'password') {
|
||||
return passwordVisible.value ? 'text' : 'password'
|
||||
}
|
||||
return props.type
|
||||
})
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
passwordVisible.value = !passwordVisible.value
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
inputValue.value = val;
|
||||
validate(val);
|
||||
});
|
||||
|
||||
const validate = (val: string | number | null | undefined) => {
|
||||
let err = '';
|
||||
const strVal = val == null ? '' : String(val);
|
||||
if (props.required && !strVal.trim()) {
|
||||
err = '不能为空';
|
||||
} else if (props.maxLength && strVal.length > props.maxLength) {
|
||||
err = `长度不能超过 ${props.maxLength} 个字符`;
|
||||
}
|
||||
errorMsg.value = err;
|
||||
emit('validation', err === '', err);
|
||||
return err === '';
|
||||
};
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
inputValue.value = target.value;
|
||||
validate(target.value);
|
||||
emit('update:modelValue', target.value);
|
||||
emit('input', e);
|
||||
emit('change', e);
|
||||
};
|
||||
|
||||
const onChange = (e: Event) => {
|
||||
@@ -73,14 +75,11 @@ const onFocus = (e: FocusEvent) => {
|
||||
|
||||
const onBlur = (e: FocusEvent) => {
|
||||
focus = false
|
||||
validate(inputValue.value);
|
||||
emit('blur', e);
|
||||
};
|
||||
|
||||
const clearInput = () => {
|
||||
|
||||
inputValue.value = '';
|
||||
validate('');
|
||||
emit('update:modelValue', '');
|
||||
};
|
||||
|
||||
@@ -99,50 +98,63 @@ const vFocus = {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="base-input2"
|
||||
<div class="base-input"
|
||||
ref="inputEl"
|
||||
:class="{ 'is-disabled': disabled, 'has-error': errorMsg,focus, [`base-input2--${size}`]: true }">
|
||||
:class="{ 'is-disabled': disabled, 'error': props.error, focus, [`base-input--${size}`]: true }">
|
||||
<slot name="subfix"></slot>
|
||||
<input
|
||||
v-bind="attrs"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
class="inner"
|
||||
v-focus="autofocus"
|
||||
:maxlength="maxLength"
|
||||
v-bind="attrs"
|
||||
:type="inputType"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
class="inner"
|
||||
v-focus="autofocus"
|
||||
:maxlength="maxLength"
|
||||
/>
|
||||
<slot name="prefix"></slot>
|
||||
<Close
|
||||
v-if="clearable && inputValue && !disabled"
|
||||
@click="clearInput"/>
|
||||
<div v-if="errorMsg" class="base-input2__error">{{ errorMsg }}</div>
|
||||
v-if="clearable && inputValue && !disabled"
|
||||
@click="clearInput"/>
|
||||
<!-- Password visibility toggle -->
|
||||
<div
|
||||
v-if="type === 'password' && !disabled"
|
||||
class="password-toggle"
|
||||
@click="togglePasswordVisibility"
|
||||
:title="passwordVisible ? '隐藏密码' : '显示密码'">
|
||||
<IconFluentEye16Regular v-if="!passwordVisible"/>
|
||||
<IconFluentEyeOff16Regular v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.base-input2 {
|
||||
.base-input {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
border: 1px solid var(--color-input-border);
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
padding: .2rem .3rem;
|
||||
transition: all .3s;
|
||||
align-items: center;
|
||||
background: var(--color-input-bg);
|
||||
|
||||
::placeholder {
|
||||
font-size: 0.9rem;
|
||||
color: darkgray;
|
||||
}
|
||||
|
||||
// normal size (default)
|
||||
&--normal {
|
||||
padding: .2rem .3rem;
|
||||
|
||||
|
||||
.inner {
|
||||
height: 1.5rem;
|
||||
font-size: 1rem;
|
||||
@@ -151,8 +163,9 @@ const vFocus = {
|
||||
|
||||
// large size
|
||||
&--large {
|
||||
padding: .6rem .8rem;
|
||||
|
||||
padding: .4rem .6rem;
|
||||
border-radius: .5rem;
|
||||
|
||||
.inner {
|
||||
height: 2rem;
|
||||
font-size: 1.125rem;
|
||||
@@ -163,16 +176,9 @@ const vFocus = {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
.base-input2__inner {
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
|
||||
.base-input2__error {
|
||||
color: #f56c6c;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
&.error {
|
||||
border-color: #f56c6c;
|
||||
background: rgba(245, 108, 108, 0.07);
|
||||
}
|
||||
|
||||
&.focus {
|
||||
@@ -184,10 +190,6 @@ const vFocus = {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&__error {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.inner {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
@@ -200,5 +202,22 @@ const vFocus = {
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--color-input-color);
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, provide, watch, toRef} from 'vue'
|
||||
import {provide, ref, toRef} from 'vue'
|
||||
import type {FormField, FormModel, FormRules} from './types'
|
||||
|
||||
interface Field {
|
||||
prop: string
|
||||
@@ -14,8 +15,8 @@ interface Field {
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
model: Object,
|
||||
rules: Object // { word: [{required:true,...}, ...], name: [...] }
|
||||
model: Object as () => FormModel,
|
||||
rules: Object as () => FormRules
|
||||
})
|
||||
|
||||
const fields = ref<Field[]>([])
|
||||
@@ -25,7 +26,7 @@ const registerField = (field: Field) => {
|
||||
}
|
||||
|
||||
// 校验整个表单
|
||||
const validate = (cb): boolean => {
|
||||
function validate(cb) {
|
||||
let valid = true
|
||||
fields.value.forEach(f => {
|
||||
const fieldRules = props.rules?.[f.prop] || []
|
||||
@@ -35,10 +36,23 @@ const validate = (cb): boolean => {
|
||||
cb(valid)
|
||||
}
|
||||
|
||||
// 校验指定字段
|
||||
function validateField(fieldName: string, cb?: (valid: boolean) => void): boolean {
|
||||
const field = fields.value.find(f => f.prop === fieldName)
|
||||
if (field) {
|
||||
const fieldRules = props.rules?.[fieldName] || []
|
||||
const valid = field.validate(fieldRules)
|
||||
if (cb) cb(valid)
|
||||
return valid
|
||||
}
|
||||
if (cb) cb(true)
|
||||
return true
|
||||
}
|
||||
|
||||
provide('registerField', registerField)
|
||||
provide('formModel', toRef(props, 'model'))
|
||||
provide('formValidate', validate)
|
||||
provide('formRules', props.rules)
|
||||
|
||||
defineExpose({validate})
|
||||
defineExpose({validate, validateField})
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,7 @@ let error = $ref('')
|
||||
|
||||
// 拿到 form 的 model 和注册函数
|
||||
const formModel = inject<ref>('formModel')
|
||||
const registerField = inject('registerField')
|
||||
const registerField = inject<Function>('registerField')
|
||||
const formRules = inject('formRules', {})
|
||||
|
||||
const myRules = $computed(() => {
|
||||
@@ -31,43 +31,94 @@ const validate = (rules) => {
|
||||
error = rule.message
|
||||
return false
|
||||
}
|
||||
if (rule.min && val && val.toString().length < rule.min) {
|
||||
error = rule.message
|
||||
return false
|
||||
}
|
||||
if (rule.max && val && val.toString().length > rule.max) {
|
||||
error = rule.message
|
||||
return false
|
||||
}
|
||||
if (rule.validator) {
|
||||
try {
|
||||
rule.validator(rule, val)
|
||||
return true
|
||||
} catch (e) {
|
||||
error = e.message
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 自动触发 blur 校验
|
||||
const handleBlur = () => {
|
||||
function handleBlur() {
|
||||
const blurRules = myRules.filter((r) => r.trigger === 'blur')
|
||||
if (blurRules.length) validate(blurRules)
|
||||
}
|
||||
|
||||
function handChange() {
|
||||
error = ''
|
||||
}
|
||||
|
||||
// 注册到 Form
|
||||
onMounted(() => {
|
||||
registerField && registerField({prop: props.prop, modelValue: value, validate})
|
||||
})
|
||||
|
||||
let slot = useSlots()
|
||||
|
||||
|
||||
function patchVNode(vnode, patchFn) {
|
||||
if (!vnode) return vnode
|
||||
|
||||
// 如果当前节点就是我们要找的 BaseInput
|
||||
if (vnode.type && vnode.type.name) {
|
||||
return patchFn(vnode)
|
||||
}
|
||||
|
||||
// 如果有子节点,则递归修改
|
||||
if (Array.isArray(vnode.children)) {
|
||||
vnode.children = vnode.children.map(child => patchVNode(child, patchFn))
|
||||
}
|
||||
|
||||
return vnode
|
||||
}
|
||||
|
||||
|
||||
defineRender(() => {
|
||||
let DefaultNode = slot.default()[0]
|
||||
return <div class="form-item mb-6 flex gap-space">
|
||||
let DefaultNode: any = slot.default()[0]
|
||||
|
||||
// 对 DefaultNode 深度查找 BaseInput 并加上 onBlur / error
|
||||
DefaultNode = patchVNode(DefaultNode, vnode => {
|
||||
return {
|
||||
...vnode,
|
||||
props: {
|
||||
...vnode.props,
|
||||
error: !!error,
|
||||
onBlur: handleBlur,
|
||||
onChange: handChange
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return <div class="form-item flex gap-space">
|
||||
{props.label &&
|
||||
<label class="w-20 flex items-start mt-1 justify-end">
|
||||
{myRules.length ? <span class="form-error">*</span> : null} {props.label}
|
||||
</label>}
|
||||
<label class="w-20 flex items-start mt-1 justify-end">
|
||||
{myRules.length ? <span class="form-error">*</span> : null} {props.label}
|
||||
</label>}
|
||||
<div class="flex-1 relative">
|
||||
<DefaultNode onBlur={handleBlur}/>
|
||||
<div class="form-error absolute top-[100%] anim" style={{opacity: error ? 1 : 0}}>{error}</div>
|
||||
<DefaultNode/>
|
||||
<div class="form-error my-0.5 anim" style={{opacity: error ? 1 : 0}}>{error} </div>
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-item {
|
||||
|
||||
.form-error {
|
||||
color: #f56c6c;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.form-error {
|
||||
color: #f56c6c;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
65
src/components/base/form/types.ts
Normal file
65
src/components/base/form/types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// Form 组件的 TypeScript 类型定义
|
||||
|
||||
// 表单字段接口
|
||||
export interface FormField {
|
||||
prop: string
|
||||
modelValue: any
|
||||
validate: (rules: FormRule[]) => boolean
|
||||
}
|
||||
|
||||
// 表单规则接口
|
||||
export interface FormRule {
|
||||
required?: boolean
|
||||
message?: string
|
||||
pattern?: RegExp
|
||||
validator?: (rule: FormRule, value: any, callback: (error?: Error) => void) => void
|
||||
min?: number
|
||||
max?: number
|
||||
len?: number
|
||||
type?: string
|
||||
}
|
||||
|
||||
// 表单规则对象类型
|
||||
export type FormRules = Record<string, FormRule[]>
|
||||
|
||||
// 表单模型对象类型
|
||||
export type FormModel = Record<string, any>
|
||||
|
||||
// Form 组件的 Props 接口
|
||||
export interface FormProps {
|
||||
model?: FormModel
|
||||
rules?: FormRules
|
||||
}
|
||||
|
||||
// Form 组件的实例接口
|
||||
export interface FormInstance {
|
||||
/**
|
||||
* 校验整个表单
|
||||
* @param callback 校验完成后的回调函数,接收校验结果
|
||||
*/
|
||||
validate: (callback: (valid: boolean) => void) => void
|
||||
|
||||
/**
|
||||
* 校验指定字段
|
||||
* @param fieldName 要校验的字段名称
|
||||
* @param callback 可选的回调函数,接收校验结果
|
||||
* @returns 校验是否通过
|
||||
*/
|
||||
validateField: (fieldName: string, callback?: (valid: boolean) => void) => boolean
|
||||
}
|
||||
|
||||
// 注入的上下文类型
|
||||
export interface FormContext {
|
||||
registerField: (field: FormField) => void
|
||||
formModel: FormModel
|
||||
formValidate: (callback: (valid: boolean) => void) => void
|
||||
formRules: FormRules
|
||||
}
|
||||
|
||||
// 验证状态枚举
|
||||
export enum ValidateStatus {
|
||||
Success = 'success',
|
||||
Error = 'error',
|
||||
Validating = 'validating',
|
||||
Pending = 'pending'
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export const PHONE_CONFIG = {
|
||||
sendInterval: 60,
|
||||
|
||||
// 手机号正则表达式(中国大陆)
|
||||
phoneRegex: /^1[3-9]\d{9}$/
|
||||
phoneRegex: /^1[2-9]\d{9}$/
|
||||
}
|
||||
|
||||
// 邮箱配置
|
||||
@@ -45,7 +45,7 @@ export const EMAIL_CONFIG = {
|
||||
// 密码配置
|
||||
export const PASSWORD_CONFIG = {
|
||||
// 密码最小长度
|
||||
minLength: 6,
|
||||
minLength: 9,
|
||||
|
||||
// 密码最大长度
|
||||
maxLength: 20
|
||||
|
||||
@@ -44,10 +44,10 @@ function goHome() {
|
||||
<span v-if="settingStore.sideExpand">设置</span>
|
||||
<div class="red-point" :class="!settingStore.sideExpand && 'top-1 right-0'" v-if="runtimeStore.isNew"></div>
|
||||
</div>
|
||||
<!-- <div class="row" @click="router.push('/user')">-->
|
||||
<!-- <IconFluentPerson20Regular/>-->
|
||||
<!-- <span v-if="settingStore.sideExpand">用户</span>-->
|
||||
<!-- </div>-->
|
||||
<div class="row" @click="router.push('/user')">
|
||||
<IconFluentPerson20Regular/>
|
||||
<span v-if="settingStore.sideExpand">用户</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom flex justify-evenly ">
|
||||
<BaseIcon
|
||||
|
||||
15
src/pages/user/Notice.vue
Normal file
15
src/pages/user/Notice.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-12 text-xs text-gray-400">
|
||||
<span>
|
||||
继续操作即表示你阅读并同意我们的
|
||||
<router-link to="/user-agreement" className="link">用户协议</router-link>
|
||||
与
|
||||
<router-link to="/privacy-policy" className="link">隐私政策</router-link>
|
||||
</span>
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,8 @@ const router = VueRouter.createRouter({
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to: any, from: any) => {
|
||||
return true
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 公共路由,不需要登录验证
|
||||
@@ -78,7 +80,7 @@ router.beforeEach(async (to: any, from: any) => {
|
||||
// 尝试初始化认证状态
|
||||
const isInitialized = await authStore.initAuth()
|
||||
if (!isInitialized) {
|
||||
return { path: '/login', query: { redirect: to.fullPath } }
|
||||
return {path: '/login', query: {redirect: to.fullPath}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
|
||||
import { login as loginApi, register as registerApi, logout as logoutApi, getUserInfo, resetPassword as resetPasswordApi, type LoginParams, type RegisterParams } from '@/apis/auth'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import router from '@/router.ts'
|
||||
import {sleep} from "@/utils";
|
||||
export interface User {
|
||||
id: string
|
||||
email?: string
|
||||
|
||||
@@ -1,166 +1,10 @@
|
||||
// 邮箱验证
|
||||
import {EMAIL_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
|
||||
export const validateEmail = (email: string): boolean => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailRegex.test(email)
|
||||
return EMAIL_CONFIG.emailRegex.test(email)
|
||||
}
|
||||
// 手机号验证(中国大陆)
|
||||
export const validatePhone = (phone: string): boolean => {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
return phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
// 密码验证
|
||||
export const validatePassword = (password: string): { isValid: boolean; message?: string } => {
|
||||
if (!password) {
|
||||
return { isValid: false, message: '密码不能为空' }
|
||||
}
|
||||
if (password.length < 6) {
|
||||
return { isValid: false, message: '密码长度不能少于6位' }
|
||||
}
|
||||
if (password.length > 20) {
|
||||
return { isValid: false, message: '密码长度不能超过20位' }
|
||||
}
|
||||
return { isValid: true }
|
||||
}
|
||||
|
||||
// 验证码验证
|
||||
export const validateCode = (code: string): boolean => {
|
||||
return /^\d{6}$/.test(code)
|
||||
}
|
||||
|
||||
// 用户名验证
|
||||
export const validateNickname = (nickname: string): { isValid: boolean; message?: string } => {
|
||||
if (!nickname) {
|
||||
return { isValid: true } // 昵称可以为空
|
||||
}
|
||||
if (nickname.length < 2) {
|
||||
return { isValid: false, message: '昵称长度不能少于2位' }
|
||||
}
|
||||
if (nickname.length > 20) {
|
||||
return { isValid: false, message: '昵称长度不能超过20位' }
|
||||
}
|
||||
return { isValid: true }
|
||||
}
|
||||
|
||||
// 综合验证函数
|
||||
export const validateLoginForm = (data: {
|
||||
email?: string
|
||||
phone?: string
|
||||
password: string
|
||||
code?: string
|
||||
type: 'email' | 'phone' | 'wechat'
|
||||
}): { isValid: boolean; errors: Record<string, string> } => {
|
||||
const errors: Record<string, string> = {}
|
||||
|
||||
if (data.type === 'email') {
|
||||
if (!data.email) {
|
||||
errors.email = '邮箱不能为空'
|
||||
} else if (!validateEmail(data.email)) {
|
||||
errors.email = '请输入有效的邮箱地址'
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'phone') {
|
||||
if (!data.phone) {
|
||||
errors.phone = '手机号不能为空'
|
||||
} else if (!validatePhone(data.phone)) {
|
||||
errors.phone = '请输入有效的手机号'
|
||||
}
|
||||
|
||||
if (!data.code) {
|
||||
errors.code = '验证码不能为空'
|
||||
} else if (!validateCode(data.code)) {
|
||||
errors.code = '请输入6位数字验证码'
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type !== 'phone' && !data.password) {
|
||||
errors.password = '密码不能为空'
|
||||
} else if (data.type !== 'phone' && data.password) {
|
||||
const passwordValidation = validatePassword(data.password)
|
||||
if (!passwordValidation.isValid) {
|
||||
errors.password = passwordValidation.message
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: Object.keys(errors).length === 0,
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
export const validateRegisterForm = (data: {
|
||||
email?: string
|
||||
phone: string
|
||||
password: string
|
||||
code: string
|
||||
nickname?: string
|
||||
}): { isValid: boolean; errors: Record<string, string> } => {
|
||||
const errors: Record<string, string> = {}
|
||||
|
||||
if (!data.phone) {
|
||||
errors.phone = '手机号不能为空'
|
||||
} else if (!validatePhone(data.phone)) {
|
||||
errors.phone = '请输入有效的手机号'
|
||||
}
|
||||
|
||||
if (!data.code) {
|
||||
errors.code = '验证码不能为空'
|
||||
} else if (!validateCode(data.code)) {
|
||||
errors.code = '请输入6位数字验证码'
|
||||
}
|
||||
|
||||
const passwordValidation = validatePassword(data.password)
|
||||
if (!passwordValidation.isValid) {
|
||||
errors.password = passwordValidation.message
|
||||
}
|
||||
|
||||
const nicknameValidation = validateNickname(data.nickname || '')
|
||||
if (!nicknameValidation.isValid) {
|
||||
errors.nickname = nicknameValidation.message
|
||||
}
|
||||
|
||||
if (data.email && !validateEmail(data.email)) {
|
||||
errors.email = '请输入有效的邮箱地址'
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: Object.keys(errors).length === 0,
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
export const validateResetPasswordForm = (data: {
|
||||
email?: string
|
||||
phone: string
|
||||
code: string
|
||||
newPassword: string
|
||||
}): { isValid: boolean; errors: Record<string, string> } => {
|
||||
const errors: Record<string, string> = {}
|
||||
|
||||
if (!data.phone) {
|
||||
errors.phone = '手机号不能为空'
|
||||
} else if (!validatePhone(data.phone)) {
|
||||
errors.phone = '请输入有效的手机号'
|
||||
}
|
||||
|
||||
if (!data.code) {
|
||||
errors.code = '验证码不能为空'
|
||||
} else if (!validateCode(data.code)) {
|
||||
errors.code = '请输入6位数字验证码'
|
||||
}
|
||||
|
||||
const passwordValidation = validatePassword(data.newPassword)
|
||||
if (!passwordValidation.isValid) {
|
||||
errors.newPassword = passwordValidation.message
|
||||
}
|
||||
|
||||
if (data.email && !validateEmail(data.email)) {
|
||||
errors.email = '请输入有效的邮箱地址'
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: Object.keys(errors).length === 0,
|
||||
errors
|
||||
}
|
||||
return PHONE_CONFIG.phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user