save
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { CodeType } from "@/types/types.ts";
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
|
||||
// 用户登录接口
|
||||
export interface LoginParams {
|
||||
@@ -88,6 +88,21 @@ export function getUserInfo() {
|
||||
}
|
||||
|
||||
// 设置密码
|
||||
export function setPassword(password: string) {
|
||||
return http('user/setPassword', {password}, null, 'post')
|
||||
export function setPassword(data) {
|
||||
return http('user/setPassword', data, null, 'post')
|
||||
}
|
||||
|
||||
// 修改邮箱
|
||||
export function changeEmailApi(data) {
|
||||
return http('user/changeEmail', data, null, 'post')
|
||||
}
|
||||
|
||||
// 修改手机号
|
||||
export function changePhoneApi(data) {
|
||||
return http('user/changePhone', data, null, 'post')
|
||||
}
|
||||
|
||||
// 修改用户信息
|
||||
export function updateUserInfoApi(data) {
|
||||
return http('user/updateUserInfo', data, null, 'post')
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ html, body {
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: .9rem;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,15 @@ const vFocus = {
|
||||
ref="inputEl"
|
||||
:class="{ 'is-disabled': disabled, 'error': props.error, focus, [`base-input--${size}`]: true }">
|
||||
<slot name="subfix"></slot>
|
||||
<!-- PreIcon slot -->
|
||||
<div v-if="$slots.preIcon" class="pre-icon">
|
||||
<slot name="preIcon"></slot>
|
||||
</div>
|
||||
<IconFluentLockClosed20Regular class="pre-icon" v-if="type === 'password'"/>
|
||||
<IconFluentMail20Regular class="pre-icon" v-if="type === 'email'"/>
|
||||
<IconFluentPhone20Regular class="pre-icon" v-if="type === 'tel'"/>
|
||||
<IconFluentNumberSymbol20Regular class="pre-icon" v-if="type === 'code'"/>
|
||||
|
||||
<input
|
||||
v-bind="attrs"
|
||||
:type="inputType"
|
||||
@@ -195,6 +204,24 @@ const vFocus = {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// PreIcon styling
|
||||
&.has-preicon {
|
||||
.inner {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.pre-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-input-color);
|
||||
opacity: 0.6;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.inner {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -46,7 +46,6 @@ const validate = (rules, isBlur = false) => {
|
||||
if (rule.validator) {
|
||||
try {
|
||||
rule.validator(rule, val)
|
||||
return true
|
||||
} catch (e) {
|
||||
error = e.message
|
||||
return false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const GITHUB = 'https://github.com/zyronon/TypeWords'
|
||||
export const Host = '2study.top'
|
||||
export const EMAIL = 'zyronon@163.com'
|
||||
export const Origin = `https://${Host}`
|
||||
export const APP_NAME = 'Type Words'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {
|
||||
APP_NAME, APP_VERSION,
|
||||
APP_NAME, APP_VERSION, EMAIL,
|
||||
EXPORT_DATA_KEY, GITHUB,
|
||||
LOCAL_FILE_KEY,
|
||||
Origin,
|
||||
@@ -824,7 +824,7 @@ function importOldData() {
|
||||
反馈:<a :href="`${GITHUB}/issues`" target="_blank">{{ GITHUB }}/issues</a>
|
||||
</p>
|
||||
<p>
|
||||
作者邮箱:<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
作者邮箱:<a :href="`mailto:${EMAIL}`">{{ EMAIL }}</a>
|
||||
</p>
|
||||
<div class="text-md color-gray mt-10">
|
||||
Build {{ gitLastCommitHash }}
|
||||
|
||||
66
src/pages/user/Code.vue
Normal file
66
src/pages/user/Code.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {sendCode} from "@/apis/user.ts";
|
||||
import {PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
|
||||
let isSendingCode = $ref(false)
|
||||
let codeCountdown = $ref(0)
|
||||
|
||||
interface IProps {
|
||||
validateField: Function,
|
||||
type: CodeType
|
||||
val: any
|
||||
size?: any
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
size: 'large',
|
||||
})
|
||||
|
||||
// 发送验证码
|
||||
async function sendVerificationCode() {
|
||||
let res = props.validateField()
|
||||
if (res) {
|
||||
try {
|
||||
isSendingCode = true
|
||||
const res = await sendCode({val: props.val, type: props.type})
|
||||
if (res.success) {
|
||||
codeCountdown = PHONE_CONFIG.sendInterval
|
||||
const timer = setInterval(() => {
|
||||
codeCountdown--
|
||||
if (codeCountdown <= 0) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
} else {
|
||||
Toast.error(res.msg || '发送失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send code error:', error)
|
||||
Toast.error('发送验证码失败')
|
||||
} finally {
|
||||
isSendingCode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseButton
|
||||
@click="sendVerificationCode"
|
||||
:disabled="isSendingCode || codeCountdown > 0"
|
||||
type="info"
|
||||
:size="props.size"
|
||||
style="border: 1px solid var(--color-input-border)"
|
||||
>
|
||||
{{ codeCountdown > 0 ? `${codeCountdown}s` : (isSendingCode ? '发送中' : '发送验证码') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,14 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { Calendar, CreditCard, Crown } from 'lucide-vue-next'
|
||||
import { useAuthStore } from '@/stores/auth.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {computed, ref} from 'vue'
|
||||
import {Calendar, CreditCard, Crown} from 'lucide-vue-next'
|
||||
import {useAuthStore} from '@/stores/auth.ts'
|
||||
import {useRouter} from 'vue-router'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { APP_NAME, GITHUB } from "@/config/env.ts";
|
||||
import {APP_NAME, EMAIL, GITHUB} from "@/config/env.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { PASSWORD_CONFIG } from "@/config/auth.ts";
|
||||
import { setPassword } from "@/apis/user.ts";
|
||||
import {PASSWORD_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import {changeEmailApi, changePhoneApi, setPassword, updateUserInfoApi} from "@/apis/user.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import {FormInstance} from "@/components/base/form/types.ts";
|
||||
import {codeRules, emailRules, passwordRules, phoneRules, validatePhone} from "@/utils/validation.ts";
|
||||
import {cloneDeep} from "@/utils";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import Code from "@/pages/user/Code.vue";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
@@ -16,11 +26,6 @@ const router = useRouter()
|
||||
// Check login state
|
||||
const isLoggedIn = computed(() => authStore.isLogin)
|
||||
|
||||
// Form data
|
||||
const username = ref('Brian W')
|
||||
const email = ref('ttentau@gmail.com')
|
||||
const receiveNotifications = ref(false)
|
||||
|
||||
// Mock subscription data (you can replace with real data from your API)
|
||||
const subscriptionData = ref({
|
||||
plan: 'Premium',
|
||||
@@ -31,47 +36,17 @@ const subscriptionData = ref({
|
||||
})
|
||||
|
||||
// UI state
|
||||
const isEditingUsername = ref(false)
|
||||
const isEditingEmail = ref(false)
|
||||
const showPasswordSection = ref(false)
|
||||
let showChangePwd = $ref(false)
|
||||
let showChangeEmail = $ref(false)
|
||||
let showChangeUsername = $ref(false)
|
||||
let showChangePhone = $ref(false)
|
||||
let loading = $ref(false)
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const editUsername = () => {
|
||||
isEditingUsername.value = true
|
||||
}
|
||||
|
||||
const saveUsername = () => {
|
||||
isEditingUsername.value = false
|
||||
// Here you would typically save to backend
|
||||
}
|
||||
|
||||
const editEmail = () => {
|
||||
isEditingEmail.value = true
|
||||
}
|
||||
|
||||
const saveEmail = () => {
|
||||
isEditingEmail.value = false
|
||||
// Here you would typically save to backend
|
||||
}
|
||||
|
||||
const toggleNotifications = () => {
|
||||
receiveNotifications.value = !receiveNotifications.value
|
||||
}
|
||||
|
||||
const downloadPersonalInfo = () => {
|
||||
console.log('Download personal info')
|
||||
}
|
||||
|
||||
const deleteAccount = () => {
|
||||
if (confirm('确定要删除您的账户吗?此操作无法撤销。')) {
|
||||
console.log('Delete account')
|
||||
}
|
||||
}
|
||||
|
||||
const contactSupport = () => {
|
||||
console.log('Contact support')
|
||||
}
|
||||
@@ -80,18 +55,202 @@ const leaveTrustpilotReview = () => {
|
||||
window.open(GITHUB + '/issues', '_blank')
|
||||
}
|
||||
|
||||
async function changePassword(e) {
|
||||
let res = await setPassword(e.target.value)
|
||||
//todo
|
||||
|
||||
|
||||
// 修改手机号
|
||||
// 修改手机号
|
||||
// 修改手机号
|
||||
let changePhoneFormRef = $ref<FormInstance>()
|
||||
let defaultFrom = {oldCode: '', phone: '', code: '', pwd: '',}
|
||||
let changePhoneForm = $ref(cloneDeep(defaultFrom))
|
||||
let changePhoneFormRules = {
|
||||
oldCode: codeRules,
|
||||
phone: [...phoneRules, {
|
||||
validator: (rule: any, value: any) => {
|
||||
if (authStore.user?.phone && value === authStore.user?.phone) {
|
||||
throw new Error('新手机号与原手机号一致')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},],
|
||||
code: codeRules,
|
||||
pwd: passwordRules
|
||||
}
|
||||
|
||||
function showChangePhoneForm() {
|
||||
showChangePhone = showChangeUsername = showChangeEmail = showChangePwd = false
|
||||
showChangePhone = true
|
||||
changePhoneForm = cloneDeep(defaultFrom)
|
||||
}
|
||||
|
||||
function changePhone() {
|
||||
changePhoneFormRef.validate(async valid => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading = true
|
||||
const res = await changePhoneApi(changePhoneForm)
|
||||
if (res.success) {
|
||||
Toast.success('修改成功')
|
||||
await authStore.fetchUserInfo()
|
||||
showChangePhone = false
|
||||
} else {
|
||||
Toast.error(res.msg || '修改失败')
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error || '修改失败,请重试')
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 修改用户名
|
||||
// 修改用户名
|
||||
// 修改用户名
|
||||
let changeUsernameFormRef = $ref<FormInstance>()
|
||||
let changeUsernameForm = $ref({username: ''})
|
||||
let changeUsernameFormRules = {
|
||||
username: [{required: true, message: '请输入用户名', trigger: 'blur'}],
|
||||
}
|
||||
|
||||
function showChangeUsernameForm() {
|
||||
showChangePhone = showChangeUsername = showChangeEmail = showChangePwd = false
|
||||
showChangeUsername = true
|
||||
changeUsernameForm = cloneDeep({username: authStore.user?.username ?? '',})
|
||||
}
|
||||
|
||||
function changeUsername() {
|
||||
changeUsernameFormRef.validate(async valid => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading = true
|
||||
const res = await updateUserInfoApi(changeUsernameForm)
|
||||
if (res.success) {
|
||||
Toast.success('修改成功')
|
||||
await authStore.fetchUserInfo()
|
||||
showChangeUsername = false
|
||||
} else {
|
||||
Toast.error(res.msg || '修改失败')
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error || '修改失败,请重试')
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 修改邮箱
|
||||
// 修改邮箱
|
||||
// 修改邮箱
|
||||
let changeEmailFormRef = $ref<FormInstance>()
|
||||
|
||||
let changeEmailForm = $ref({
|
||||
email: '',
|
||||
pwd: '',
|
||||
code: '',
|
||||
})
|
||||
let changeEmailFormRules = {
|
||||
email: [
|
||||
...emailRules, {
|
||||
validator: (rule: any, value: any) => {
|
||||
if (authStore.user?.email && value === authStore.user?.email) {
|
||||
throw new Error('该邮箱与当前一致')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
}
|
||||
],
|
||||
pwd: passwordRules,
|
||||
code: codeRules,
|
||||
}
|
||||
|
||||
function showChangeEmailForm() {
|
||||
showChangePhone = showChangeUsername = showChangeEmail = showChangePwd = false
|
||||
showChangeEmail = true
|
||||
changeEmailForm = cloneDeep({email: authStore.user?.email ?? '', pwd: '', code: '',})
|
||||
}
|
||||
|
||||
function changeEmail() {
|
||||
changeEmailFormRef.validate(async valid => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading = true
|
||||
const res = await changeEmailApi(changeEmailForm)
|
||||
if (res.success) {
|
||||
Toast.success('修改成功')
|
||||
await authStore.fetchUserInfo()
|
||||
showChangeEmail = false
|
||||
} else {
|
||||
Toast.error(res.msg || '修改失败')
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error || '修改失败,请重试')
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
// 修改密码
|
||||
// 修改密码
|
||||
let changePwdFormRef = $ref<FormInstance>()
|
||||
const defaultChangePwdForm = {
|
||||
oldPwd: '',
|
||||
newPwd: '',
|
||||
confirmPwd: '',
|
||||
}
|
||||
let changePwdForm = $ref(cloneDeep(defaultChangePwdForm))
|
||||
let changePwdFormRules = {
|
||||
oldPwd: passwordRules,
|
||||
newPwd: passwordRules,
|
||||
confirmPwd: [
|
||||
{required: true, message: '请再次输入新密码', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value !== changePwdForm.newPwd) {
|
||||
throw new Error('两次密码输入不一致')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
function showChangePwdForm() {
|
||||
showChangePhone = showChangeUsername = showChangeEmail = showChangePwd = false
|
||||
showChangePwd = true
|
||||
changePwdForm = cloneDeep(defaultChangePwdForm)
|
||||
}
|
||||
|
||||
function changePwd() {
|
||||
changePwdFormRef.validate(async valid => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading = true
|
||||
const res = await setPassword(changePwdForm)
|
||||
if (res.success) {
|
||||
Toast.success('密码设置成功,请重新登录')
|
||||
showChangePwd = false
|
||||
authStore.logout()
|
||||
} else {
|
||||
Toast.error(res.msg || '设置失败')
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error || '设置密码失败,请重试')
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<!-- Unauthenticated View -->
|
||||
<div v-if="isLoggedIn" class="center h-screen">
|
||||
<div v-if="!isLoggedIn" class="center h-screen">
|
||||
<div class="card shadow-lg text-center flex-col gap-6 w-100 ">
|
||||
<div class="w-20 h-20 bg-blue-100 rounded-full center mx-auto">
|
||||
<IconFluentPerson20Regular class="text-3xl text-blue-600"/>
|
||||
@@ -99,9 +258,9 @@ async function changePassword(e) {
|
||||
<h1 class="text-2xl font-bold">欢迎使用</h1>
|
||||
<p class="">请登录以管理您的账户</p>
|
||||
<BaseButton
|
||||
@click="router.push('/login')"
|
||||
size="large"
|
||||
class="w-full mt-4"
|
||||
@click="router.push('/login')"
|
||||
size="large"
|
||||
class="w-full mt-4"
|
||||
>
|
||||
登录
|
||||
</BaseButton>
|
||||
@@ -113,120 +272,271 @@ async function changePassword(e) {
|
||||
</div>
|
||||
|
||||
<!-- Authenticated View -->
|
||||
<div v-else class="w-full flex items-start gap-4">
|
||||
<div v-else class="w-full flex gap-4">
|
||||
<!-- Main Account Settings -->
|
||||
<div class="card flex-1 flex flex-col gap-2 px-8">
|
||||
<h1 class="text-xl font-bold">帐户</h1>
|
||||
<!-- Username Section -->
|
||||
<div class="flex items-center justify-between ">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-gray-700 mb-1">用户名</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconFluentPerson20Regular class="text-base text-gray-500"/>
|
||||
<BaseInput
|
||||
v-if="isEditingUsername"
|
||||
v-model="username"
|
||||
type="text"
|
||||
size="normal"
|
||||
@blur="saveUsername"
|
||||
@keyup.enter="saveUsername"
|
||||
class="flex-1 max-w-xs"
|
||||
autofocus
|
||||
/>
|
||||
<span v-else class="text-gray-900">{{ username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card flex-1 flex flex-col gap-2 px-6">
|
||||
<h1 class="text-2xl font-bold mt-0">帐户</h1>
|
||||
|
||||
<IconFluentTextEditStyle20Regular
|
||||
@click="isEditingUsername ? saveUsername() : editUsername()"
|
||||
class="text-xl"/>
|
||||
<!-- 用户名-->
|
||||
<div class="item">
|
||||
<div class="flex-1">
|
||||
<div class="mb-2">用户名</div>
|
||||
<div class="flex items-center gap-2" v-if="authStore.user?.username">
|
||||
<IconFluentPerson20Regular class="text-base"/>
|
||||
<span>{{ authStore.user?.username }}</span>
|
||||
</div>
|
||||
<div v-else class="text-xs">在此设置用户名</div>
|
||||
</div>
|
||||
<BaseIcon @click="showChangeUsernameForm">
|
||||
<IconFluentTextEditStyle20Regular/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
<div v-if="showChangeUsername">
|
||||
<Form
|
||||
ref="changeUsernameFormRef"
|
||||
:rules="changeUsernameFormRules"
|
||||
:model="changeUsernameForm">
|
||||
<FormItem prop="username">
|
||||
<BaseInput
|
||||
v-model="changeUsernameForm.username"
|
||||
type="text"
|
||||
size="large"
|
||||
placeholder="请输入用户名"
|
||||
autofocus
|
||||
>
|
||||
<template #preIcon>
|
||||
<IconFluentPerson20Regular class="text-base"/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="text-align-end mb-2">
|
||||
<BaseButton type="info" @click="showChangeUsername = false">取消</BaseButton>
|
||||
<BaseButton @click="changeUsername">保存</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- 手机号-->
|
||||
<div class="item">
|
||||
<div class="flex-1">
|
||||
<div class="mb-2">手机号</div>
|
||||
<div class="flex items-center gap-2" v-if="authStore.user?.phone">
|
||||
<IconFluentMail20Regular class="text-base"/>
|
||||
<span>{{ authStore.user?.phone }}</span>
|
||||
</div>
|
||||
<div v-else class="text-xs">在此设置手机号</div>
|
||||
</div>
|
||||
<BaseIcon @click="showChangePhoneForm">
|
||||
<IconFluentTextEditStyle20Regular/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
<div v-if="showChangePhone">
|
||||
<Form
|
||||
ref="changePhoneFormRef"
|
||||
:rules="changePhoneFormRules"
|
||||
:model="changePhoneForm">
|
||||
<FormItem prop="oldCode" v-if="authStore.user?.phone">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="changePhoneForm.oldCode"
|
||||
type="code"
|
||||
autofocus
|
||||
placeholder="请输入原手机号验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<Code :validate-field="() => true"
|
||||
:type="CodeType.ChangePhoneOld"
|
||||
:val="authStore.user.phone"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="phone">
|
||||
<BaseInput
|
||||
v-model="changePhoneForm.phone"
|
||||
type="tel"
|
||||
size="large"
|
||||
placeholder="请输入新手机号"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="code">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="changePhoneForm.code"
|
||||
type="code"
|
||||
placeholder="请输入新手机号验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<Code :validate-field="() => changePhoneFormRef.validateField('phone')"
|
||||
:type="CodeType.ChangePhoneNew"
|
||||
:val="changePhoneForm.phone"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="pwd" v-if="!authStore.user?.phone">
|
||||
<BaseInput
|
||||
v-model="changePhoneForm.pwd"
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请输入原密码"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="flex justify-between items-end mb-2">
|
||||
<span class="link text-sm cp"
|
||||
@click="MessageBox.notice(`请提供证明信息发送邮件到 ${EMAIL} 进行申诉`,'人工申诉')"
|
||||
v-if="authStore.user?.phone">原手机号不可用,点此申诉</span>
|
||||
<span v-else></span>
|
||||
<div>
|
||||
<BaseButton type="info" @click="showChangePhone = false">取消</BaseButton>
|
||||
<BaseButton @click="changePhone">保存</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- Email Section -->
|
||||
<div class="flex items-center justify-between ">
|
||||
<div class="item">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-gray-700 mb-1">电子邮箱</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconFluentMail20Regular class="text-base text-gray-500"/>
|
||||
<BaseInput
|
||||
v-if="isEditingEmail"
|
||||
v-model="email"
|
||||
type="email"
|
||||
size="normal"
|
||||
@blur="saveEmail"
|
||||
@keyup.enter="saveEmail"
|
||||
class="flex-1 max-w-xs"
|
||||
autofocus
|
||||
/>
|
||||
<span v-else class="text-gray-900">{{ email }}</span>
|
||||
<div class="mb-2">电子邮箱</div>
|
||||
<div class="flex items-center gap-2" v-if="authStore.user?.email">
|
||||
<IconFluentMail20Regular class="text-base"/>
|
||||
<span>{{ authStore.user?.email }}</span>
|
||||
</div>
|
||||
<div v-else class="text-xs">在此设置邮箱</div>
|
||||
</div>
|
||||
<BaseIcon @click="showChangeEmailForm">
|
||||
<IconFluentTextEditStyle20Regular/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
<div v-if="showChangeEmail">
|
||||
<Form
|
||||
ref="changeEmailFormRef"
|
||||
:rules="changeEmailFormRules"
|
||||
:model="changeEmailForm">
|
||||
<FormItem prop="email">
|
||||
<BaseInput
|
||||
v-model="changeEmailForm.email"
|
||||
type="email"
|
||||
size="large"
|
||||
placeholder="请输入邮箱地址"
|
||||
autofocus
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="code">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="changeEmailForm.code"
|
||||
type="code"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<Code :validate-field="() => changeEmailFormRef.validateField('email')"
|
||||
:type="CodeType.ChangeEmail"
|
||||
:val="changeEmailForm.email"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="pwd" v-if="authStore.user?.hasPwd">
|
||||
<BaseInput
|
||||
v-model="changePwdForm.pwd"
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="text-align-end mb-2">
|
||||
<BaseButton type="info" @click="showChangeEmail = false">取消</BaseButton>
|
||||
<BaseButton @click="changeEmail">保存</BaseButton>
|
||||
</div>
|
||||
<IconFluentTextEditStyle20Regular
|
||||
@click="isEditingEmail ? saveEmail() : editEmail()"
|
||||
class="text-xl"/>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
|
||||
<!-- Password Section -->
|
||||
<div class="flex items-center justify-between cp"
|
||||
@click="showPasswordSection = !showPasswordSection"
|
||||
>
|
||||
<div class="item cp" @click="showChangePwdForm">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-gray-700 mb-1">设置密码</div>
|
||||
<div class="text-xs text-gray-500">在此输入密码</div>
|
||||
<div class="mb-2">设置密码</div>
|
||||
<div class="text-xs">在此输入密码</div>
|
||||
</div>
|
||||
<IconFluentChevronLeft28Filled
|
||||
class="transition-transform"
|
||||
:class="['rotate-270','rotate-180'][showPasswordSection?0:1]"/>
|
||||
class="transition-transform"
|
||||
:class="['rotate-270','rotate-180'][showChangePwd?0:1]"/>
|
||||
</div>
|
||||
<div v-if="showPasswordSection">
|
||||
<BaseInput placeholder="新密码"
|
||||
type="password"
|
||||
autofocus
|
||||
:min="PASSWORD_CONFIG.minLength"
|
||||
:max="PASSWORD_CONFIG.maxLength"/>
|
||||
<div class="text-align-end mt-4">
|
||||
<BaseButton type="info" @click="showPasswordSection = !showPasswordSection">取消</BaseButton>
|
||||
<BaseButton @click="changePassword">保存</BaseButton>
|
||||
<div v-if="showChangePwd">
|
||||
<Form
|
||||
ref="changePwdFormRef"
|
||||
:rules="changePwdFormRules"
|
||||
:model="changePwdForm">
|
||||
<FormItem prop="oldPwd" v-if="authStore.user.hasPwd">
|
||||
<BaseInput
|
||||
v-model="changePwdForm.oldPwd"
|
||||
placeholder="旧密码"
|
||||
type="password"
|
||||
size="large"
|
||||
autofocus
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem prop="newPwd">
|
||||
<BaseInput
|
||||
v-model="changePwdForm.newPwd"
|
||||
type="password"
|
||||
size="large"
|
||||
:placeholder="`请输入新密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength}位)`"
|
||||
:min="PASSWORD_CONFIG.minLength"
|
||||
:max="PASSWORD_CONFIG.maxLength"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="confirmPwd">
|
||||
<BaseInput
|
||||
v-model="changePwdForm.confirmPwd"
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请再次输入新密码"
|
||||
:min="PASSWORD_CONFIG.minLength"
|
||||
:max="PASSWORD_CONFIG.maxLength"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div class="text-align-end mb-2">
|
||||
<BaseButton type="info" @click="showChangePwd = false">取消</BaseButton>
|
||||
<BaseButton :loading="loading" @click="changePwd">保存</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
|
||||
<!-- Contact Support -->
|
||||
<div class="flex py-2 items-center justify-between cp"
|
||||
<div class="item cp"
|
||||
@click="contactSupport">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-gray-700 mb-1">联系 {{ APP_NAME }} 客服</div>
|
||||
联系 {{ APP_NAME }} 客服
|
||||
</div>
|
||||
<IconFluentChevronLeft28Filled class="rotate-180"/>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- Trustpilot Review -->
|
||||
<div class="flex py-2 items-center justify-between cp"
|
||||
<div class="item cp"
|
||||
@click="leaveTrustpilotReview">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-gray-700 mb-1">在 {{ APP_NAME }} 上留下评论</div>
|
||||
给 {{ APP_NAME }} 提交意见
|
||||
</div>
|
||||
<IconFluentChevronLeft28Filled class="rotate-180"/>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<div class="center w-full">
|
||||
<div class="center w-full mt-4">
|
||||
<BaseButton
|
||||
@click="handleLogout"
|
||||
size="large"
|
||||
class="w-[80%]"
|
||||
@click="handleLogout"
|
||||
size="large"
|
||||
class="w-[80%]"
|
||||
>
|
||||
登出
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-center">
|
||||
<div class="text-xs text-center mt-2">
|
||||
<a href="/user-agreement.html" target="_blank" class="text-gray-500 hover:text-gray-700">用户协议</a>
|
||||
、
|
||||
<a href="/privacy-policy.html" target="_blank" class="text-gray-500 hover:text-gray-700">隐私政策</a>
|
||||
@@ -291,4 +601,9 @@ async function changePassword(e) {
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</template>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.item {
|
||||
@apply flex items-center justify-between min-h-14;
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@ import BaseButton from "@/components/BaseButton.vue";
|
||||
import {APP_NAME} from "@/config/env.ts";
|
||||
import {useAuthStore} from "@/stores/auth.ts";
|
||||
import {loginApi, LoginParams, registerApi, resetPasswordApi, sendCode} from "@/apis/user.ts";
|
||||
import {validateEmail, validatePhone} from "@/utils/validation.ts";
|
||||
import {accountRules, codeRules, passwordRules, phoneRules, validateEmail, validatePhone} from "@/utils/validation.ts";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
@@ -15,6 +15,7 @@ import {FormInstance} from "@/components/base/form/types.ts";
|
||||
import {PASSWORD_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
import router from "@/router.ts";
|
||||
import Code from "@/pages/user/Code.vue";
|
||||
|
||||
// 状态管理
|
||||
const authStore = useAuthStore()
|
||||
@@ -34,40 +35,6 @@ let qrExpireTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let qrCheckInterval: ReturnType<typeof setInterval> | null = null
|
||||
const QR_EXPIRE_TIME = 5 * 60 * 1000 // 5分钟过期
|
||||
|
||||
const codeRules = [
|
||||
{required: true, message: '请输入验证码', trigger: 'blur'},
|
||||
{min: PHONE_CONFIG.codeLength, message: `请输入 ${PHONE_CONFIG.codeLength} 位验证码`, trigger: 'blur'},
|
||||
]
|
||||
const accountRules = [
|
||||
{required: true, message: '请输入手机号/邮箱地址', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!validatePhone(value) && !validateEmail(value)) {
|
||||
throw new Error('请输入有效的手机号或邮箱地址')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
]
|
||||
const phoneRules = [
|
||||
{required: true, message: '请输入手机号', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!validatePhone(value)) {
|
||||
throw new Error('请输入有效的手机号')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
]
|
||||
const passwordRules = [
|
||||
{required: true, message: '请输入密码', trigger: 'blur'},
|
||||
{
|
||||
min: PASSWORD_CONFIG.minLength,
|
||||
max: PASSWORD_CONFIG.maxLength,
|
||||
message: `密码长度为 ${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位`,
|
||||
trigger: 'blur'
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
let phoneLoginForm = $ref({phone: '', code: ''})
|
||||
let phoneLoginFormRef = $ref<FormInstance>()
|
||||
@@ -178,18 +145,10 @@ async function handleLogin() {
|
||||
let data = {}
|
||||
//手机号登录
|
||||
if (loginType === 'code') {
|
||||
data = {
|
||||
phone: phoneLoginForm.phone,
|
||||
code: phoneLoginForm.code,
|
||||
type: 'code'
|
||||
}
|
||||
data = {...phoneLoginForm, type: 'code'}
|
||||
} else {
|
||||
//密码登录
|
||||
data = {
|
||||
account: loginForm2.account,
|
||||
password: loginForm2.password,
|
||||
type: 'pwd'
|
||||
}
|
||||
data = {...loginForm2, type: 'pwd'}
|
||||
}
|
||||
let res = await loginApi(data as LoginParams)
|
||||
if (res.success) {
|
||||
@@ -215,11 +174,7 @@ async function handleRegister() {
|
||||
if (!valid) return
|
||||
try {
|
||||
loading = true
|
||||
let res = await registerApi({
|
||||
account: registerForm.account,
|
||||
password: registerForm.password,
|
||||
code: registerForm.code,
|
||||
})
|
||||
let res = await registerApi(registerForm)
|
||||
if (res.success) {
|
||||
authStore.setToken(res.data.token)
|
||||
authStore.setUser(res.data.user)
|
||||
@@ -243,11 +198,7 @@ async function handleForgotPassword() {
|
||||
if (!valid) return
|
||||
try {
|
||||
loading = true
|
||||
const res = await resetPasswordApi({
|
||||
account: forgotForm.account,
|
||||
code: forgotForm.code,
|
||||
newPassword: forgotForm.newPassword
|
||||
})
|
||||
const res = await resetPasswordApi(forgotForm)
|
||||
if (res.success) {
|
||||
Toast.success('密码重置成功,请重新登录')
|
||||
switchMode('login')
|
||||
@@ -405,6 +356,8 @@ onBeforeUnmount(() => {
|
||||
<FormItem prop="phone">
|
||||
<BaseInput v-model="phoneLoginForm.phone"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="tel"
|
||||
size="large"
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
@@ -413,20 +366,14 @@ onBeforeUnmount(() => {
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="phoneLoginForm.code"
|
||||
type="text"
|
||||
type="code"
|
||||
size="large"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
placeholder="请输入验证码"
|
||||
/>
|
||||
<BaseButton
|
||||
@click="sendVerificationCode(phoneLoginForm.phone, CodeType.Login,'phone')"
|
||||
:disabled="isSendingCode || codeCountdown > 0"
|
||||
type="info"
|
||||
size="large"
|
||||
style="border: 1px solid var(--color-input-border)"
|
||||
>
|
||||
{{ codeCountdown > 0 ? `${codeCountdown}s` : (isSendingCode ? '发送中' : '发送验证码') }}
|
||||
</BaseButton>
|
||||
<Code :validate-field="() => phoneLoginFormRef.validateField('phone')"
|
||||
:type="CodeType.Login"
|
||||
:val="phoneLoginForm.phone"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
@@ -439,7 +386,9 @@ onBeforeUnmount(() => {
|
||||
:model="loginForm2">
|
||||
<FormItem prop="account">
|
||||
<BaseInput v-model="loginForm2.account"
|
||||
type="text"
|
||||
type="email"
|
||||
name="username"
|
||||
autocomplete="email"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
@@ -449,6 +398,8 @@ onBeforeUnmount(() => {
|
||||
<BaseInput
|
||||
v-model="loginForm2.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
@@ -487,6 +438,8 @@ onBeforeUnmount(() => {
|
||||
<BaseInput
|
||||
v-model="registerForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
@@ -495,26 +448,22 @@ onBeforeUnmount(() => {
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="registerForm.code"
|
||||
type="text"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<BaseButton
|
||||
@click="sendVerificationCode(registerForm.account, CodeType.Register,'phone')"
|
||||
:disabled="isSendingCode || codeCountdown > 0"
|
||||
type="info"
|
||||
size="large"
|
||||
style="border: 1px solid var(--color-input-border)"
|
||||
>
|
||||
{{ codeCountdown > 0 ? `${codeCountdown}s` : (isSendingCode ? '发送中' : '获取验证码') }}
|
||||
</BaseButton>
|
||||
<Code :validate-field="() => registerFormRef.validateField('account')"
|
||||
:type="CodeType.Register"
|
||||
:val="registerForm.account"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="password">
|
||||
<BaseInput
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
:placeholder="`请设置密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
/>
|
||||
@@ -523,6 +472,8 @@ onBeforeUnmount(() => {
|
||||
<BaseInput
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入密码"
|
||||
/>
|
||||
@@ -557,6 +508,8 @@ onBeforeUnmount(() => {
|
||||
<BaseInput
|
||||
v-model="forgotForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
@@ -565,26 +518,22 @@ onBeforeUnmount(() => {
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="forgotForm.code"
|
||||
type="text"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<BaseButton
|
||||
@click="sendVerificationCode(forgotForm.account, CodeType.ResetPwd,'account')"
|
||||
:disabled="isSendingCode || codeCountdown > 0"
|
||||
type="info"
|
||||
size="large"
|
||||
style="border: 1px solid var(--color-input-border)"
|
||||
>
|
||||
{{ codeCountdown > 0 ? `${codeCountdown}s` : (isSendingCode ? '发送中' : '获取验证码') }}
|
||||
</BaseButton>
|
||||
<Code :validate-field="() => forgotFormRef.validateField('account')"
|
||||
:type="CodeType.ResetPwd"
|
||||
:val="forgotForm.account"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="newPassword">
|
||||
<BaseInput
|
||||
v-model="forgotForm.newPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
:placeholder="`请输入新密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
/>
|
||||
@@ -593,6 +542,8 @@ onBeforeUnmount(() => {
|
||||
<BaseInput
|
||||
v-model="forgotForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入新密码"
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,9 @@ export interface User {
|
||||
id: string
|
||||
email?: string
|
||||
phone?: string
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
username?: string
|
||||
avatar?: string,
|
||||
hasPwd?: boolean
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
@@ -37,15 +38,15 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
}
|
||||
|
||||
// 登出
|
||||
const logout = async () => {
|
||||
function logout() {
|
||||
clearToken()
|
||||
Toast.success('已退出登录')
|
||||
// Toast.success('已退出登录')
|
||||
//这行会引起hrm失效
|
||||
// router.push('/')
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const fetchUserInfo = async () => {
|
||||
async function fetchUserInfo() {
|
||||
try {
|
||||
const res = await getUserInfo()
|
||||
if (res.success) {
|
||||
@@ -61,7 +62,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
|
||||
// 初始化用户状态
|
||||
const init = async () => {
|
||||
async function init() {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
const success = await fetchUserInfo()
|
||||
if (!success) {
|
||||
|
||||
@@ -235,4 +235,7 @@ export enum CodeType {
|
||||
Login = 0,
|
||||
Register = 1,
|
||||
ResetPwd = 2,
|
||||
ChangeEmail = 3,
|
||||
ChangePhoneNew = 4,
|
||||
ChangePhoneOld = 5
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 邮箱验证
|
||||
import {EMAIL_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import {EMAIL_CONFIG, PASSWORD_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
|
||||
export const validateEmail = (email: string): boolean => {
|
||||
return EMAIL_CONFIG.emailRegex.test(email)
|
||||
@@ -8,3 +8,47 @@ export const validateEmail = (email: string): boolean => {
|
||||
export const validatePhone = (phone: string): boolean => {
|
||||
return PHONE_CONFIG.phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
export const codeRules = [
|
||||
{required: true, message: '请输入验证码', trigger: 'blur'},
|
||||
{min: PHONE_CONFIG.codeLength, message: `请输入 ${PHONE_CONFIG.codeLength} 位验证码`, trigger: 'blur'},
|
||||
]
|
||||
export const accountRules = [
|
||||
{required: true, message: '请输入手机号/邮箱地址', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!validatePhone(value) && !validateEmail(value)) {
|
||||
throw new Error('请输入有效的手机号或邮箱地址')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
]
|
||||
export const emailRules = [
|
||||
{required: true, message: '请输入邮箱地址', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!validateEmail(value)) {
|
||||
throw new Error('请输入有效的邮箱地址')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
]
|
||||
export const phoneRules = [
|
||||
{required: true, message: '请输入手机号', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!validatePhone(value)) {
|
||||
throw new Error('请输入有效的手机号')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
]
|
||||
export const passwordRules = [
|
||||
{required: true, message: '请输入密码', trigger: 'blur'},
|
||||
{
|
||||
min: PASSWORD_CONFIG.minLength,
|
||||
max: PASSWORD_CONFIG.maxLength,
|
||||
message: `密码长度为 ${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位`,
|
||||
trigger: 'blur'
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user