This commit is contained in:
Your Name
2026-03-01 14:17:59 +08:00
parent 65aa12f146
commit a07e844c47
6071 changed files with 777651 additions and 0 deletions
@@ -0,0 +1,112 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">
{{ hasMobile ? '更换手机号' : '绑定手机号' }}
</span>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="mobile">
<ElInput
v-model="formData.mobile"
placeholder="请输入手机号码"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
@click="handleConfirmLock"
:loading="isLock"
>
确认
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { smsSend } from '~~/api/app'
import { userBindMobile } from '~~/api/user'
import { SMSEnum } from '~~/enums/appEnums'
import { useAccount } from './useAccount'
import { useUserStore } from '@/stores/user'
const { toggleShowPopup } = useAccount()
const userStore = useUserStore()
const formRef = shallowRef<FormInstance>()
const verificationCodeRef = shallowRef()
const formRules: FormRules = {
mobile: [
{
required: true,
message: '请输入手机号码',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
]
}
const hasMobile = computed(() => !!userStore.userInfo.mobile)
const formData = reactive({
type: hasMobile.value ? 'change' : 'bind',
mobile: '',
code: ''
})
const sendSms = async () => {
await formRef.value?.validateField(['mobile'])
await smsSend({
scene: hasMobile.value ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
mobile: formData.mobile
})
verificationCodeRef.value?.start()
}
const handleConfirm = async () => {
await formRef.value?.validate()
if (userStore.isLogin) {
await userBindMobile(formData)
} else {
await userBindMobile(formData, { token: userStore.temToken })
userStore.login(userStore.temToken)
await userStore.getUser()
}
toggleShowPopup(false)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,162 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">忘记登录密码</span>
<ElButton
type="primary"
link
@click="setPopupType(PopupTypeEnum.LOGIN)"
v-if="!userStore.isLogin"
>
返回登录
</ElButton>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="mobile">
<ElInput
v-model="formData.mobile"
placeholder="请输入手机号码"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
placeholder="请输入6-20位数字+字母或符号组合"
type="password"
show-password
/>
</ElFormItem>
<ElFormItem prop="password_confirm">
<ElInput
v-model="formData.password_confirm"
placeholder="请再次输入密码"
type="password"
show-password
/>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
@click="handleConfirmLock"
:loading="isLock"
>
确认
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { smsSend } from '~~/api/app'
import { forgotPassword } from '~~/api/user'
import { SMSEnum } from '~~/enums/appEnums'
import { useUserStore } from '~~/stores/user'
import { useAccount, PopupTypeEnum } from './useAccount'
const userStore = useUserStore()
const { setPopupType, toggleShowPopup } = useAccount()
const formRef = shallowRef<FormInstance>()
const verificationCodeRef = shallowRef()
const formRules: FormRules = {
mobile: [
{
required: true,
message: '请输入手机号码',
trigger: ['change', 'blur']
},
{
min: 3,
max: 12,
message: '账号长度应为3-12',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入6-20位数字+字母或符号组合',
trigger: ['change', 'blur']
},
{
min: 6,
max: 20,
message: '密码长度应为6-20',
trigger: ['change', 'blur']
}
],
password_confirm: [
{
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== formData.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
mobile: '',
password: '',
code: '',
password_confirm: ''
})
const sendSms = async () => {
await formRef.value?.validateField(['mobile'])
await smsSend({
scene: SMSEnum.FIND_PASSWORD,
mobile: formData.mobile
})
verificationCodeRef.value?.start()
}
const handleConfirm = async () => {
await formRef.value?.validate()
await forgotPassword(formData)
userStore.logout()
setPopupType(PopupTypeEnum.LOGIN)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>
+36
View File
@@ -0,0 +1,36 @@
<template>
<div class="account" v-if="showPopup">
<ClientOnly>
<ElDialog
v-model="showPopup"
:width="400"
:close-on-click-modal="false"
>
<div class="px-5 text-tx-primary">
<Login v-show="popupType == PopupTypeEnum.LOGIN" />
<Register v-show="popupType == PopupTypeEnum.REGISTER" />
<ForgotPwd v-show="popupType == PopupTypeEnum.FORGOT_PWD" />
<BindMobile
v-show="popupType == PopupTypeEnum.BIND_MOBILE"
/>
</div>
</ElDialog>
</ClientOnly>
</div>
</template>
<script lang="ts" setup>
import { ElDialog } from 'element-plus'
import Login from './login.vue'
import { useAccount, PopupTypeEnum } from './useAccount'
import Register from './register.vue'
import ForgotPwd from './forgot-pwd.vue'
import BindMobile from './bind-mobile.vue'
import { useUserStore } from '~~/stores/user'
const { popupType, showPopup } = useAccount()
const userStore = useUserStore()
watch(showPopup, (value) => {
if (!value) userStore.temToken = null
})
</script>
<style lang="scss" scoped></style>
+310
View File
@@ -0,0 +1,310 @@
<template>
<div class="login">
<div class="text-4xl">欢迎登录</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<template
v-if="isAccountLogin && includeLoginWay(LoginWayEnum.ACCOUNT)"
>
<ElFormItem prop="account">
<ElInput
v-model="formData.account"
placeholder="请输入账号/手机号"
/>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
type="password"
show-password
placeholder="请输入密码"
/>
</ElFormItem>
</template>
<template
v-if="isMobileLogin && includeLoginWay(LoginWayEnum.MOBILE)"
>
<ElFormItem prop="account">
<ElInput
v-model="formData.account"
placeholder="请输入手机号"
/>
</ElFormItem>
<ElFormItem prop="code">
<ElInput v-model="formData.code" placeholder="请输入验证码">
<template #suffix>
<div
class="flex justify-center leading-5 w-[90px] pl-2.5 border-l border-br"
>
<VerificationCode
ref="verificationCodeRef"
@click-get="sendSms"
/>
</div>
</template>
</ElInput>
</ElFormItem>
</template>
<div class="flex">
<div class="flex-1">
<ElButton
v-if="
isAccountLogin &&
includeLoginWay(LoginWayEnum.MOBILE)
"
type="primary"
link
@click="changeLoginWay"
>
手机验证码登录
</ElButton>
<ElButton
v-if="
isMobileLogin &&
includeLoginWay(LoginWayEnum.ACCOUNT)
"
type="primary"
link
@click="changeLoginWay"
>
账号密码登录
</ElButton>
</div>
<ElButton
v-if="isAccountLogin"
link
@click="setPopupType(PopupTypeEnum.FORGOT_PWD)"
>
忘记密码?
</ElButton>
</div>
<ElFormItem class="mt-[30px]">
<ElButton
class="w-full"
type="primary"
:loading="isLock"
@click="loginLock"
>
登录
</ElButton>
</ElFormItem>
<div class="mt-[40px]" v-if="isOpenOtherAuth">
<ElDivider>
<span class="text-tx-secondary font-normal">
第三方登录
</span>
</ElDivider>
<div class="flex justify-center">
<ElButton link @click="getWxCodeLock" v-if="inWxAuth">
<img
class="w-[48px] h-[48px]"
src="@/assets/images/icon/icon_wx.png"
/>
</ElButton>
</div>
</div>
<div
class="mb-[-15px] mx-[-40px] mt-[30px] bg-primary-light-9 rounded-b-md px-[15px] flex leading-10"
>
<div class="flex-1">
<ElCheckbox v-if="isOpenAgreement" v-model="isAgreement">
<span class="text-tx-secondary text-sm">
已阅读并同意
<NuxtLink
:to="`/policy/${PolicyAgreementEnum.SERVICE}`"
custom
v-slot="{ href }"
>
<a
class="text-tx-primary"
:href="href"
target="_blank"
>
《服务协议》
</a>
</NuxtLink>
<NuxtLink
class="text-tx-primary"
:to="`/policy/${PolicyAgreementEnum.PRIVACY}`"
custom
v-slot="{ href }"
>
<a
class="text-tx-primary"
:href="href"
target="_blank"
>
《隐私政策》
</a>
</NuxtLink>
</span>
</ElCheckbox>
</div>
<div>
<ElButton
link
type="primary"
@click="setPopupType(PopupTypeEnum.REGISTER)"
>
<span class="text-sm">注册账号</span>
</ElButton>
</div>
</div>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
ElDivider,
ElCheckbox,
FormInstance,
FormRules
} from 'element-plus'
import { useAccount, PopupTypeEnum } from './useAccount'
import { getWxCodeUrl, login } from '@/api/account'
import { useAppStore } from '@/stores/app'
import { useUserStore } from '@/stores/user'
import { smsSend } from '~~/api/app'
import { PolicyAgreementEnum, SMSEnum } from '~~/enums/appEnums'
import feedback from '~~/utils/feedback'
const appStore = useAppStore()
const userStore = useUserStore()
const { setPopupType, toggleShowPopup } = useAccount()
enum LoginWayEnum {
ACCOUNT = 1,
MOBILE = 2
}
const isAgreement = ref(false)
const formRef = shallowRef<FormInstance>()
const formRules: FormRules = {
account: [
{
required: true,
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(
new Error(
formData.scene == LoginWayEnum.ACCOUNT
? '请输入账号/手机号'
: '请输入手机号'
)
)
return
}
callback()
},
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['change', 'blur']
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
code: '',
account: '',
password: '',
scene: 0
})
const isAccountLogin = computed(() => formData.scene == LoginWayEnum.ACCOUNT)
const isMobileLogin = computed(() => formData.scene == LoginWayEnum.MOBILE)
const includeLoginWay = (way: LoginWayEnum) =>
appStore.getLoginConfig.login_way?.includes(String(way))
const inWxAuth = computed(() => {
return appStore.getLoginConfig.wechat_auth
})
const isOpenAgreement = computed(
() => appStore.getLoginConfig.login_agreement == 1
)
const isOpenOtherAuth = computed(() => appStore.getLoginConfig.third_auth == 1)
const isForceBindMobile = computed(
() => appStore.getLoginConfig.coerce_mobile == 1
)
const changeLoginWay = () => {
if (formData.scene == LoginWayEnum.ACCOUNT) {
formData.scene = LoginWayEnum.MOBILE
} else {
formData.scene = LoginWayEnum.ACCOUNT
}
}
const verificationCodeRef = shallowRef()
const sendSms = async () => {
await formRef.value?.validateField(['account'])
await smsSend({
scene: SMSEnum.LOGIN,
mobile: formData.account
})
verificationCodeRef.value?.start()
}
const handleLogin = async () => {
await formRef.value?.validate()
const data = await login(formData)
if (isForceBindMobile.value && !data.mobile) {
userStore.temToken = data.token
setPopupType(PopupTypeEnum.BIND_MOBILE)
return
}
userStore.login(data.token)
await userStore.getUser()
toggleShowPopup(false)
}
const { lockFn: handleLoginLock, isLock } = useLockFn(handleLogin)
const agreementConfirm = async () => {
if (isAgreement.value) {
return
}
await feedback.confirm('确认已阅读并同意《服务协议》和《隐私政策》')
isAgreement.value = true
}
const loginLock = async () => {
await agreementConfirm()
await handleLoginLock()
}
const getWxCode = async () => {
await agreementConfirm()
const { url } = await getWxCodeUrl()
window.location.href = url
}
const { lockFn: getWxCodeLock } = useLockFn(getWxCode)
watch(
() => appStore.getLoginConfig,
(value) => {
const { login_way } = value
if (login_way && login_way.length) {
formData.scene = login_way.at(0)
}
},
{
immediate: true
}
)
</script>
<style lang="scss" scoped></style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="login">
<div class="flex justify-between">
<span class="text-4xl">注册账号</span>
<ElButton
type="primary"
link
@click="setPopupType(PopupTypeEnum.LOGIN)"
>
返回登录
</ElButton>
</div>
<ElForm
ref="formRef"
class="mt-[35px]"
size="large"
:model="formData"
:rules="formRules"
>
<ElFormItem prop="account">
<ElInput
v-model="formData.account"
placeholder="请输入创建的账号"
/>
</ElFormItem>
<ElFormItem prop="password">
<ElInput
v-model="formData.password"
type="password"
show-password
placeholder="请输入6-20位数字+字母或符号组合"
/>
</ElFormItem>
<ElFormItem prop="password_confirm">
<ElInput
v-model="formData.password_confirm"
type="password"
show-password
placeholder="请再次输入密码"
/>
</ElFormItem>
<ElFormItem class="mt-[60px]">
<ElButton
class="w-full"
type="primary"
:loading="isLock"
@click="handleConfirmLock"
>
注册
</ElButton>
</ElFormItem>
</ElForm>
</div>
</template>
<script lang="ts" setup>
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
FormInstance,
FormRules
} from 'element-plus'
import { register } from '~~/api/account'
import { useAccount, PopupTypeEnum } from './useAccount'
const { setPopupType } = useAccount()
const formRef = shallowRef<FormInstance>()
const formRules: FormRules = {
account: [
{
required: true,
message: '请输入创建的账号',
trigger: ['change', 'blur']
},
{
min: 3,
max: 12,
message: '账号长度应为3-12',
trigger: ['change', 'blur']
}
],
password: [
{
required: true,
message: '请输入6-20位数字+字母或符号组合',
trigger: ['change', 'blur']
},
{
min: 6,
max: 20,
message: '密码长度应为6-20',
trigger: ['change', 'blur']
}
],
password_confirm: [
{
validator(rule: any, value: any, callback: any) {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== formData.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: ['change', 'blur']
}
]
}
const formData = reactive({
account: '',
password: '',
password_confirm: ''
})
const handleConfirm = async () => {
await formRef.value?.validate()
await register(formData)
setPopupType(PopupTypeEnum.LOGIN)
}
const { lockFn: handleConfirmLock, isLock } = useLockFn(handleConfirm)
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,17 @@
<template>
<div class="flex flex-col justify-center items-center">
<div class="text-tx-regular mb-4">您还未登录请先登录</div>
<ElButton @click="toLogin">登录</ElButton>
</div>
</template>
<script lang="ts" setup>
import { useAccount, PopupTypeEnum } from './useAccount'
import { ElButton } from 'element-plus'
const { setPopupType, toggleShowPopup } = useAccount()
const toLogin = () => {
setPopupType(PopupTypeEnum.LOGIN)
toggleShowPopup(true)
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,23 @@
export enum PopupTypeEnum {
LOGIN,
FORGOT_PWD,
REGISTER,
BIND_MOBILE
}
export const useAccount = () => {
const popupType = useState<PopupTypeEnum>(() => PopupTypeEnum.LOGIN)
const setPopupType = (type: PopupTypeEnum = PopupTypeEnum.LOGIN) => {
popupType.value = type
}
const showPopup = useState(() => false)
const toggleShowPopup = (toggle: boolean) => {
showPopup.value = toggle ?? !showPopup.value
}
return {
popupType,
setPopupType,
showPopup,
toggleShowPopup
}
}