feat 开放平台绑定功能优化, 用户注册

This commit is contained in:
xxm
2022-11-06 01:05:18 +08:00
parent 0cd8a528d0
commit f4b5fe0d60
24 changed files with 443 additions and 352 deletions

47
src/api/sys/login.ts Normal file
View File

@@ -0,0 +1,47 @@
import { defHttp } from '/@/utils/http/axios'
import { LoginParams } from './model/userModel'
import { Result } from '/#/axios'
/**
* 登录接口 返回token
*/
export function login(params: LoginParams) {
return defHttp.post<Result<string>>({
url: '/token/login',
params,
})
}
/**
* 获取微信扫码登录二维码
*/
export function applyQrCode() {
return defHttp.get<Result<WeChatLoginQrCode>>({
url: `/token/wechat/qr/applyQrCode`,
})
}
/**
* 获取扫码状态
*/
export function getQrStatus(qrCodeKey) {
return defHttp.get<Result<string>>({
url: `/token/wechat/qr/getStatus`,
params: { qrCodeKey },
})
}
/**
* 退出
*/
export function doLogout() {
return defHttp.post({ url: '/token/logout' })
}
/**
* 登录二维码
*/
export interface WeChatLoginQrCode {
qrCodeKey: string
qrCodeUrl: string
}

View File

@@ -1,17 +1,7 @@
import { defHttp } from '/@/utils/http/axios'
import { LoginParams, GetUserInfoModel } from './model/userModel'
import { GetUserInfoModel } from './model/userModel'
import { Result } from '/#/axios'
/**
* 登录接口 返回token
*/
export function loginApi(params: LoginParams) {
return defHttp.post<Result<string>>({
url: '/token/login',
params,
})
}
/**
* 登录后获取用户信息
*/
@@ -20,8 +10,21 @@ export function getUserInfo() {
}
/**
* 退出
* 注册
*/
export function doLogout() {
return defHttp.post({ url: '/token/logout' })
export function register(obj) {
return defHttp.post({
url: `/user/register`,
data: obj,
})
}
/**
* 重置密码
*/
export function forgetPasswordByPhone(obj) {
return defHttp.post({
url: `/user/forgetPasswordByPhone`,
data: obj,
})
}

View File

@@ -28,7 +28,7 @@
const { currentCount, isStart, start, reset } = useCountdown(props.count)
const { t } = useI18n()
// 优化
const getButtonText = computed(() => {
return !unref(isStart) ? t('component.countdown.normalText') : t('component.countdown.sendText', [unref(currentCount)])
})

View File

@@ -98,12 +98,13 @@ export function createPermissionGuard(router: Router) {
return
}
//TODO 添加 websocket连接.
console.log(`路由守卫`)
// 重载菜单
console.log('重载菜单')
const routes = await permissionStore.buildRoutesAction()
// 初始化字典
console.log('初始化字典')
await useDictStore.initDict()
routes.forEach((route) => {

View File

@@ -1,7 +1,7 @@
import type { RouteRecordRaw } from 'vue-router'
import type { App } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
import { basicRoutes } from './routes'
// 白名单应该包含基本静态路由
@@ -17,7 +17,7 @@ getRouteNames(basicRoutes)
// 创建一个可以被 Vue 应用程序使用的路由实例
export const router = createRouter({
// 创建一个 hash 历史记录。
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH),
// 应该添加到路由的初始路由列表。
routes: basicRoutes as unknown as RouteRecordRaw[],
// 是否应该禁止尾部斜杠。默认为假

View File

@@ -17,7 +17,7 @@ export const useDictStore = defineStore({
},
},
actions: {
async initDict() {
initDict() {
findAll().then(({ data }) => {
this.dict = data.map((o) => {
return {

View File

@@ -6,9 +6,8 @@ import { RoleEnum } from '/@/enums/roleEnum'
import { PageEnum } from '/@/enums/pageEnum'
import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum'
import { getAuthCache, setAuthCache } from '/@/utils/auth'
import { GetUserInfoModel, LoginParams } from '/@/api/sys/model/userModel'
import { doLogout, getUserInfo, loginApi } from '/@/api/sys/user'
import { useI18n } from '/@/hooks/web/useI18n'
import { LoginParams } from '/@/api/sys/model/userModel'
import { doLogout, login } from '/@/api/sys/login'
import { useMessage } from '/@/hooks/web/useMessage'
import { router } from '/@/router'
import { usePermissionStore } from '/@/store/modules/permission'
@@ -16,6 +15,8 @@ import { RouteRecordRaw } from 'vue-router'
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'
import { h } from 'vue'
import { getFilePreviewUrlPrefix } from '/@/api/common/FileUpload'
// @ts-ignore
import { getUserInfo } from '/@/api/sys/user'
interface UserState {
userInfo: Nullable<UserInfo>
@@ -89,7 +90,7 @@ export const useUserStore = defineStore({
*/
async login(params: LoginParams) {
try {
const { data: token } = await loginApi(params)
const { data: token } = await login(params)
// 保存token
this.setToken(token)
await this.afterLoginAction(true)

View File

@@ -18,20 +18,6 @@ export interface UserDetails {
clientIdList?: string[]
}
/**
* 登录后用户信息
*/
export interface GetUserInfoModel {
// 用户id
userId: number
// 名称
name: string
// 账号
username: string
// 头像
avatar: string
}
/**
* 用户基础消息
*/

View File

@@ -56,24 +56,25 @@
</a-col>
</a-row>
</a-spin>
<we-chat-qr-bind ref="weChatQrBind" @bind="bindWeChatCallback" />
</CollapseContainer>
</template>
<script lang="ts" setup>
import { List } from 'ant-design-vue'
import { defineComponent, onMounted } from 'vue'
import { onMounted } from 'vue'
import { CollapseContainer } from '/@/components/Container'
import {} from 'OpenIdLoginType'
import { $ref } from 'vue/macros'
import { DING_TALK, WE_CHAT, WE_CHAT_OPEN, QQ, WE_COM } from '/@/views/login/third/OpenIdLoginType'
import { bindThird, getThirdBindInfo, unbindThird } from '/@/views/account/account.api'
import { UserThirdBindInfo } from '/@/views/account/accountModel'
import { useMessage } from '/@/hooks/web/useMessage'
import { getAppEnvConfig } from "/@/utils/env";
import { getAppEnvConfig } from '/@/utils/env'
import WeChatQrBind from './WeChatQrBind.vue'
const { createMessage, createConfirm } = useMessage()
let loading = $ref(false)
let currentLoginType = $ref()
let weChatQrBind = $ref<any>()
let bindInfo = $ref({
weChat: {},
weChatOpen: {},
@@ -130,6 +131,27 @@
})
}
/**
* 微信公众平台绑定
*/
function bindWeChat() {
weChatQrBind.init()
}
/**
* 微信绑定回调
*/
function bindWeChatCallback(from) {
loading = true
bindThird(from)
.then(() => {
createMessage.success('绑定成功')
})
.finally(() => {
init()
})
}
/**
* 解绑账号
*/

View File

@@ -1,5 +1,101 @@
<template> </template>
<template>
<basic-modal
title="扫码绑定"
:footer="null"
:can-fullscreen="false"
v-bind="$attrs"
:width="250"
:height="250"
:visible="visible"
@cancel="handleCancel"
>
<a-spin :spinning="loading">
<qr-code :value="qrCodeUrl" />
</a-spin>
</basic-modal>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import BasicModal from '/@/components/Modal/src/BasicModal.vue'
import { $ref } from 'vue/macros'
import { QrCode } from '/@/components/Qrcode'
import { applyQrCode, getQrStatus } from '/@/api/sys/login'
import { onBeforeUnmount, reactive } from 'vue'
import { WE_CHAT } from '/@/views/login/third/OpenIdLoginType'
let visible = $ref(false)
let loading = $ref(false)
let qrCodeUrl = $ref('请稍等')
let interval: any
const emits = defineEmits(['bind'])
const form = reactive({
client: '',
loginType: WE_CHAT,
authCode: '',
})
function init() {
visible = true
getApplyQrCode()
}
/**
* 获取
*/
function getApplyQrCode() {
loading = true
applyQrCode().then((res) => {
form.authCode = res.data.qrCodeKey
qrCodeUrl = res.data.qrCodeUrl
loading = false
checkQrScanStatus()
})
}
/**
* 定时查询扫码情况
*/
function checkQrScanStatus() {
interval = setInterval(() => {
getQrStatus(form.authCode).then(({ data }) => {
// 成功 进行绑定
if (data === 'ok') {
bindWeChat()
} else if (data === 'expired') {
clearInterval(interval)
interval = null
getApplyQrCode()
}
})
}, 1000)
}
/**
* 绑定
*/
function bindWeChat() {
emits('bind', {
authCode: form.authCode,
clientCode: form.loginType,
})
handleCancel()
}
/**
* 关闭
*/
function handleCancel() {
clearInterval(interval)
interval = null
visible = false
}
onBeforeUnmount(() => {
handleCancel()
})
defineExpose({
init,
})
</script>
<style scoped></style>

View File

@@ -1,9 +1,9 @@
<template>
<basic-modal
title="邮箱绑定"
v-bind="$attrs"
:loading="confirmLoading"
:width="640"
title="邮箱绑定"
:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
@@ -17,10 +17,11 @@
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item label="邮箱号" name="email">
<a-form-item validateFirst label="邮箱号" name="email">
<a-input v-model:value="form.email" placeholder="邮箱" />
</a-form-item>
<a-form-item label="验证码" name="captcha">
<a-form-item validateFirst label="验证码" name="captcha">
<count-down-input v-model:value="form.captcha" :send-code-api="sendEmailCaptcha" :count="120">获取验证码</count-down-input>
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.captcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.newCaptcha" href="javascript:" @click="sendEmailCaptcha">
@@ -44,6 +45,7 @@
import { useMessage } from '/@/hooks/web/useMessage'
import { validateEmail } from '/@/utils/validate'
import { bindEmail } from '/@/views/account/account.api'
import CountDownInput from "/@/components/CountDown/src/CountdownInput.vue";
const emits = defineEmits(['ok'])
const { visible, confirmLoading, modalWidth, labelCol, wrapperCol, handleCancel } = useFormEdit()
@@ -95,9 +97,6 @@
*/
function validateEmailRule() {
const { email } = form
if (!email) {
return Promise.resolve()
}
const { msg, result } = validateEmail(email)
return result ? Promise.resolve() : Promise.reject(msg)
}
@@ -106,14 +105,6 @@
*/
async function validateBindEmail() {
const { email } = form
if (!email) {
return Promise.resolve()
}
const { msg, result } = validateEmail(email)
// 邮箱号验证
if (!result) {
return Promise.reject(msg)
}
const { data } = await existsEmail(email)
return data ? Promise.reject('邮箱已被使用') : Promise.resolve()
}
@@ -122,9 +113,6 @@
*/
async function validateCaptcha() {
const { captcha } = form
if (!captcha) {
return Promise.resolve()
}
const { data } = await validateEmailChangeCaptcha(form.email, captcha)
return data ? Promise.resolve() : Promise.reject('验证码错误')
}

View File

@@ -5,30 +5,25 @@
<a-step title="验证邮箱" />
<a-step title="绑定新邮箱" />
</a-steps>
<a-form ref="formRef" :model="form" :rules="rules" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item label="邮箱" v-show="currentTab === 0">
<a-form
ref="formRef"
:model="form"
:rules="rules"
:validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item label="邮箱" v-show="currentTab === 0" validateFirst>
<span>{{ email }}</span>
</a-form-item>
<a-form-item label="验证码" name="oldCaptcha" v-show="currentTab === 0">
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.oldCaptcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.oldCaptcha" href="javascript:" @click="sendOldEmailCaptcha">
{{ (!state.oldCaptcha && '获取验证码') || '请等待 ' + (state.oldCaptchaTime + ' s') }}
</a-button>
</template>
</a-input>
<a-form-item validate-first label="验证码" name="oldCaptcha" v-show="currentTab === 0">
<count-down-input v-model:value="form.oldCaptcha" :send-code-api="sendOldEmailCaptcha" :count="120">获取验证码1</count-down-input>
</a-form-item>
<a-form-item label="邮箱" name="email" v-show="currentTab === 1">
<a-form-item validate-first label="邮箱" name="email" v-show="currentTab === 1">
<a-input v-model:value="form.email" placeholder="邮箱" />
</a-form-item>
<a-form-item label="验证码" name="newCaptcha" v-show="currentTab === 1">
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.newCaptcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.newCaptcha" href="javascript:" @click="sendNewEmailCaptcha">
{{ (!state.newCaptcha && '获取验证码') || '请等待 ' + (state.newCaptchaTime + ' s') }}
</a-button>
</template>
</a-input>
<a-form-item validate-first label="验证码" name="newCaptcha" v-show="currentTab === 1">
<count-down-input v-model:value="form.newCaptcha" :send-code-api="sendNewEmailCaptcha" :count="120">获取验证码1</count-down-input>
</a-form-item>
</a-form>
</a-spin>
@@ -44,7 +39,7 @@
import BasicModal from '/@/components/Modal/src/BasicModal.vue'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { $ref } from 'vue/macros'
import { computed, nextTick, reactive } from 'vue'
import { computed, nextTick } from 'vue'
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import { useMessage } from '/@/hooks/web/useMessage'
import { validateEmail, validateMobile } from '/@/utils/validate'
@@ -56,6 +51,7 @@
validateEmailChangeCaptcha,
} from '/@/api/sys/userAssist'
import { updateEmail } from '/@/views/account/account.api'
import CountDownInput from '/@/components/CountDown/src/CountdownInput.vue'
const props = defineProps({
email: String,
@@ -74,7 +70,7 @@
return {
email: [
{ required: currentTab === 1, message: '请输入新邮箱!' },
{ validator: validateEmailRule, trigger: 'change' },
{ validator: validateEmailRule },
{ validator: validateNewEmail, trigger: 'blur' },
],
oldCaptcha: [
@@ -87,12 +83,6 @@
],
} as Record<string, Rule[]>
})
const state = reactive({
oldCaptcha: false,
newCaptcha: false,
oldCaptchaTime: 120,
newCaptchaTime: 120,
})
const formRef = $ref<FormInstance>()
function init() {
@@ -121,7 +111,7 @@
*/
function validateEmailRule() {
const { email } = form
if (currentTab !== 1 || !email) {
if (currentTab !== 1) {
return Promise.resolve()
}
const { msg, result } = validateEmail(email)
@@ -135,11 +125,6 @@
if (currentTab !== 1) {
return Promise.resolve()
}
const { msg, result } = validateMobile(email)
// 邮箱验证
if (!result) {
return Promise.reject(msg)
}
const { data } = await existsEmail(email)
return data ? Promise.reject('邮箱已被使用') : Promise.resolve()
}
@@ -148,9 +133,6 @@
*/
async function validateOldCaptcha() {
const { oldCaptcha } = form
if (!oldCaptcha) {
return Promise.resolve()
}
const { data } = await validateCurrentEmailChangeCaptcha(oldCaptcha)
return data ? Promise.resolve() : Promise.reject('验证码错误')
}
@@ -168,36 +150,30 @@
/**
* 发送验证码 旧
*/
function sendOldEmailCaptcha() {
sendCurrentEmailChangeCaptcha().then(() => {
createMessage.success('发送验证码成功')
state.oldCaptcha = true
const interval = window.setInterval(() => {
if (state.oldCaptchaTime-- <= 0) {
state.oldCaptchaTime = 120
state.oldCaptcha = false
window.clearInterval(interval)
}
}, 1000)
})
async function sendOldEmailCaptcha() {
try {
await sendCurrentEmailChangeCaptcha().then(() => {
createMessage.success('发送验证码成功')
})
} catch (e) {
return false
}
return true
}
/**
* 发送验证码 新手机
*/
function sendNewEmailCaptcha() {
formRef.validateFields('email').then(async () => {
sendEmailChangeCaptcha(form.email).then(() => {
createMessage.success('发送验证码成功')
state.newCaptcha = true
const interval = window.setInterval(() => {
if (state.newCaptchaTime-- <= 0) {
state.newCaptchaTime = 120
state.newCaptcha = false
window.clearInterval(interval)
}
}, 1000)
async function sendNewEmailCaptcha() {
try {
await formRef.validateFields('email').then(async () => {
sendEmailChangeCaptcha(form.email).then(() => {
createMessage.success('发送验证码成功')
})
})
})
} catch (e) {
return false
}
return true
}
/**

View File

@@ -2,6 +2,7 @@
<basic-modal v-bind="$attrs" :loading="confirmLoading" :width="640" title="密码修改" :visible="visible" @cancel="handleCancel">
<a-form
ref="formRef"
validateFirst
:validate-trigger="['blur', 'change']"
:model="form"
:rules="rules"

View File

@@ -11,6 +11,7 @@
<a-spin :spinning="confirmLoading">
<a-form
ref="formRef"
validateFirst
:model="form"
:rules="rules"
:validate-trigger="['blur', 'change']"
@@ -21,13 +22,7 @@
<a-input v-model:value="form.phone" placeholder="手机号" />
</a-form-item>
<a-form-item label="验证码" name="captcha">
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.captcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.captcha" href="javascript:" @click="sendPhoneCaptcha">
{{ (!state.captcha && '获取验证码') || '请等待 ' + (state.captchaTime + ' s') }}
</a-button>
</template>
</a-input>
<count-down-input v-model:value="form.captcha" :send-code-api="sendPhoneCaptcha" :count="120">获取验证码</count-down-input>
</a-form-item>
</a-form>
</a-spin>
@@ -44,6 +39,7 @@
import { useMessage } from '/@/hooks/web/useMessage'
import { validateMobile } from '/@/utils/validate'
import { bindPhone } from '/@/views/account/account.api'
import CountDownInput from '/@/components/CountDown/src/CountdownInput.vue'
const emits = defineEmits(['ok'])
const { visible, confirmLoading, modalWidth, labelCol, wrapperCol, handleCancel } = useFormEdit()
@@ -64,10 +60,6 @@
],
} as Record<string, Rule[]>
const state = reactive({
captcha: false,
captchaTime: 120,
})
const formRef = $ref<FormInstance>()
function init() {
visible.value = true
@@ -94,9 +86,6 @@
*/
function validatePhone() {
const { phone } = form
if (!phone) {
return Promise.resolve()
}
const { msg, result } = validateMobile(phone)
return result ? Promise.resolve() : Promise.reject(msg)
}
@@ -105,14 +94,6 @@
*/
async function validateBindPhone() {
const { phone } = form
if (!phone) {
return Promise.resolve()
}
const { msg, result } = validateMobile(phone)
// 手机号验证
if (!result) {
return Promise.reject(msg)
}
const { data } = await existsPhone(phone)
return data ? Promise.reject('手机号已被使用') : Promise.resolve()
}
@@ -121,29 +102,23 @@
*/
async function validateCaptcha() {
const { captcha } = form
if (!captcha) {
return Promise.resolve()
}
const { data } = await validatePhoneChangeCaptcha(form.phone, captcha)
return data ? Promise.resolve() : Promise.reject('验证码错误')
}
/**
* 发送验证码 绑定邮箱
*/
function sendPhoneCaptcha() {
formRef.validateFields('phone').then(async () => {
sendPhoneChangeCaptcha(form.phone).then(() => {
createMessage.success('发送验证码成功')
state.captcha = true
const interval = window.setInterval(() => {
if (state.captchaTime-- <= 0) {
state.captchaTime = 120
state.captcha = false
window.clearInterval(interval)
}
}, 1000)
async function sendPhoneCaptcha() {
try {
await formRef.validateFields('phone').then(async () => {
sendPhoneChangeCaptcha(form.phone).then(() => {
createMessage.success('发送验证码成功')
})
})
})
} catch (e) {
return false
}
return true
}
defineExpose({ init })
</script>

View File

@@ -1,34 +1,30 @@
<template>
<basic-modal v-bind="$attrs" :width="640" title="密码修改" :loading="confirmLoading" :visible="visible" @cancel="handleCancel">
<basic-modal v-bind="$attrs" :width="640" title="手机号修改" :loading="confirmLoading" :visible="visible" @cancel="handleCancel">
<a-spin :spinning="confirmLoading">
<a-steps class="steps" :current="currentTab">
<a-step title="验证手机" />
<a-step title="绑定新手机" />
</a-steps>
<a-form ref="formRef" :model="form" :rules="rules" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form
ref="formRef"
validateFirst
:model="form"
:rules="rules"
:validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item label="手机号" v-show="currentTab === 0">
<span>{{ phone }}</span>
</a-form-item>
<a-form-item label="验证码" name="oldCaptcha" v-show="currentTab === 0">
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.oldCaptcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.oldCaptcha" href="javascript:" @click="sendOldPhoneCaptcha">
{{ (!state.oldCaptcha && '获取验证码') || '请等待 ' + (state.oldCaptchaTime + ' s') }}
</a-button>
</template>
</a-input>
<a-form-item validateFirst label="验证码" name="oldCaptcha" v-show="currentTab === 0">
<count-down-input v-model:value="form.oldCaptcha" :send-code-api="sendOldPhoneCaptcha" :count="120">获取验证码</count-down-input>
</a-form-item>
<a-form-item label="手机号" name="phone" v-show="currentTab === 1">
<a-form-item validateFirst label="手机号" name="phone" v-show="currentTab === 1">
<a-input v-model:value="form.phone" placeholder="手机号" />
</a-form-item>
<a-form-item label="验证码" name="newCaptcha" v-show="currentTab === 1">
<a-input :maxLength="8" placeholder="验证码" v-model:value="form.newCaptcha">
<template #addonAfter>
<a-button size="small" type="link" :disabled="state.newCaptcha" href="javascript:" @click="sendNewPhoneCaptcha">
{{ (!state.newCaptcha && '获取验证码') || '请等待 ' + (state.newCaptchaTime + ' s') }}
</a-button>
</template>
</a-input>
<a-form-item validateFirst label="验证码" name="newCaptcha" v-show="currentTab === 1">
<count-down-input v-model:value="form.newCaptcha" :send-code-api="sendNewPhoneCaptcha" :count="120">获取验证码</count-down-input>
</a-form-item>
</a-form>
</a-spin>
@@ -44,7 +40,7 @@
import BasicModal from '/@/components/Modal/src/BasicModal.vue'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { $ref } from 'vue/macros'
import { computed, nextTick, reactive } from 'vue'
import { computed, nextTick } from 'vue'
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import {
existsPhone,
@@ -56,6 +52,7 @@
import { useMessage } from '/@/hooks/web/useMessage'
import { validateMobile } from '/@/utils/validate'
import { updatePhone } from '/@/views/account/account.api'
import CountDownInput from '/@/components/CountDown/src/CountdownInput.vue'
const props = defineProps({
phone: String,
@@ -64,7 +61,7 @@
const emits = defineEmits(['ok'])
const { visible, confirmLoading, modalWidth, labelCol, wrapperCol, handleCancel } = useFormEdit()
const { createMessage, createConfirm } = useMessage()
let currentTab = $ref(0)
let currentTab = $ref(1)
let form = $ref({
phone: '',
oldCaptcha: '',
@@ -87,12 +84,6 @@
],
} as Record<string, Rule[]>
})
const state = reactive({
oldCaptcha: false,
newCaptcha: false,
oldCaptchaTime: 120,
newCaptchaTime: 120,
})
const formRef = $ref<FormInstance>()
function init() {
@@ -131,14 +122,9 @@
*/
async function validateNewPhone() {
const { phone } = form
if (currentTab !== 1) {
if (!(currentTab === 1 && phone)) {
return Promise.resolve()
}
const { msg, result } = validateMobile(phone)
// 手机号验证
if (!result) {
return Promise.reject(msg)
}
const { data } = await existsPhone(phone)
return data ? Promise.reject('手机号已被使用') : Promise.resolve()
}
@@ -147,9 +133,6 @@
*/
async function validateOldCaptcha() {
const { oldCaptcha } = form
if (!oldCaptcha) {
return Promise.resolve()
}
const { data } = await validateCurrentPhoneChangeCaptcha(oldCaptcha)
return data ? Promise.resolve() : Promise.reject('验证码错误')
}
@@ -158,45 +141,35 @@
*/
async function validateNewCaptcha() {
const { newCaptcha } = form
if (!(currentTab === 1 && newCaptcha)) {
return Promise.resolve()
}
const { data } = await validatePhoneChangeCaptcha(form.phone, newCaptcha)
return data ? Promise.resolve() : Promise.reject('验证码错误')
}
/**
* 发送验证码 旧
*/
function sendOldPhoneCaptcha() {
sendCurrentPhoneChangeCaptcha().then(() => {
createMessage.success('发送验证码成功')
state.oldCaptcha = true
const interval = window.setInterval(() => {
if (state.oldCaptchaTime-- <= 0) {
state.oldCaptchaTime = 120
state.oldCaptcha = false
window.clearInterval(interval)
}
}, 1000)
})
async function sendOldPhoneCaptcha() {
try {
await sendCurrentPhoneChangeCaptcha().then(() => {
createMessage.success('发送验证码成功')
})
} catch (e) {
return false
}
return true
}
/**
* 发送验证码 新手机
*/
function sendNewPhoneCaptcha() {
formRef.validateFields('phone').then(async () => {
sendPhoneChangeCaptcha(form.phone).then(() => {
async function sendNewPhoneCaptcha() {
try {
await formRef.validateFields('phone')
await sendPhoneChangeCaptcha(form.phone).then(() => {
createMessage.success('发送验证码成功')
state.newCaptcha = true
const interval = window.setInterval(() => {
if (state.newCaptchaTime-- <= 0) {
state.newCaptchaTime = 120
state.newCaptcha = false
window.clearInterval(interval)
}
}, 1000)
})
})
} catch (e) {
return false
}
return true
}
/**
* 下一步

View File

@@ -30,38 +30,6 @@ export const settingList = [
name: '账号绑定',
component: AccountBind,
},
{
key: '4',
name: '新消息通知',
component: MsgNotify,
},
]
export const accountBindList: ListItem[] = [
{
key: '1',
title: '绑定淘宝',
description: '当前未绑定淘宝账号',
extra: '绑定',
avatar: 'ri:taobao-fill',
color: '#ff4000',
},
{
key: '2',
title: '绑定支付宝',
description: '当前未绑定支付宝账号',
extra: '绑定',
avatar: 'fa-brands:alipay',
color: '#2eabff',
},
{
key: '3',
title: '绑定钉钉',
description: '当前未绑定钉钉账号',
extra: '绑定',
avatar: 'ri:dingding-fill',
color: '#2eabff',
},
]
// 新消息通知 list

View File

@@ -28,9 +28,13 @@
>
<!-- 账号密码登录 -->
<LoginForm />
<!-- 找回密码 -->
<ForgetPasswordForm />
<!-- 注册 -->
<RegisterForm />
<!-- 手机登录 -->
<MobileForm />
<!-- 扫码登录 -->
<QrCodeForm />
</div>
</div>
@@ -43,8 +47,8 @@
import { AppLogo } from '/@/components/Application'
import { AppDarkModeToggle } from '/@/components/Application'
import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue'
import RegisterForm from './RegisterForm.vue'
import ForgetPasswordForm from './user/ForgetPasswordForm.vue'
import RegisterForm from './user/RegisterForm.vue'
import MobileForm from './MobileForm.vue'
import QrCodeForm from './QrCodeForm.vue'
import { useGlobSetting } from '/@/hooks/setting'

View File

@@ -86,7 +86,7 @@
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import { LoginParams } from '/@/api/sys/model/userModel'
const { notification, createErrorModal } = useMessage()
const { notification } = useMessage()
const { prefixCls } = useDesign('login')
// 用户信息存储
const userStore = useUserStore()
@@ -157,8 +157,8 @@
* 登录处理
*/
async function handleLogin() {
const data = await formRef.validate()
try {
await formRef.validate()
loading = true
const token = await userStore.login(form)
if (token) {

View File

@@ -5,20 +5,17 @@
</template>
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { useI18n } from '/@/hooks/web/useI18n'
import { LoginStateEnum, useLoginState } from './useLogin'
const { t } = useI18n()
const { getLoginState } = useLoginState()
const getFormTitle = computed(() => {
const titleObj = {
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
[LoginStateEnum.RESET_PASSWORD]: '重置密码',
[LoginStateEnum.LOGIN]: '登录',
[LoginStateEnum.REGISTER]: '注册',
[LoginStateEnum.MOBILE]: '手机登录',
[LoginStateEnum.QR_CODE]: '二维码登录',
}
return titleObj[unref(getLoginState)]
})

View File

@@ -1,78 +0,0 @@
<template>
<template v-if="getShow">
<LoginFormTitle class="enter-x" />
<Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef">
<FormItem name="account" class="enter-x">
<Input class="fix-auto-fill" size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" />
</FormItem>
<FormItem name="mobile" class="enter-x">
<Input size="large" v-model:value="formData.mobile" :placeholder="t('sys.login.mobile')" class="fix-auto-fill" />
</FormItem>
<FormItem name="sms" class="enter-x">
<CountdownInput size="large" class="fix-auto-fill" v-model:value="formData.sms" :placeholder="t('sys.login.smsCode')" />
</FormItem>
<FormItem name="password" class="enter-x">
<StrengthMeter size="large" v-model:value="formData.password" :placeholder="t('sys.login.password')" />
</FormItem>
<FormItem name="confirmPassword" class="enter-x">
<InputPassword
size="large"
visibilityToggle
v-model:value="formData.confirmPassword"
:placeholder="t('sys.login.confirmPassword')"
/>
</FormItem>
<FormItem class="enter-x" name="policy">
<!-- No logic, you need to deal with it yourself -->
<Checkbox v-model:checked="formData.policy" size="small">
{{ t('sys.login.policy') }}
</Checkbox>
</FormItem>
<Button type="primary" class="enter-x" size="large" block @click="handleRegister" :loading="loading">
{{ t('sys.login.registerButton') }}
</Button>
<Button size="large" block class="mt-4 enter-x" @click="handleBackLogin">
{{ t('sys.login.backSignIn') }}
</Button>
</Form>
</template>
</template>
<script lang="ts" setup>
import { reactive, ref, unref, computed } from 'vue'
import LoginFormTitle from './LoginFormTitle.vue'
import { Form, Input, Button, Checkbox } from 'ant-design-vue'
import { StrengthMeter } from '/@/components/StrengthMeter'
import { CountdownInput } from '/@/components/CountDown'
import { useI18n } from '/@/hooks/web/useI18n'
import { useLoginState, useFormRules, useFormValid, LoginStateEnum } from './useLogin'
const FormItem = Form.Item
const InputPassword = Input.Password
const { t } = useI18n()
const { handleBackLogin, getLoginState } = useLoginState()
const formRef = ref()
const loading = ref(false)
const formData = reactive({
account: '',
password: '',
confirmPassword: '',
mobile: '',
sms: '',
policy: false,
})
const { getFormRules } = useFormRules(formData)
const { validForm } = useFormValid(formRef)
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
async function handleRegister() {
const data = await validForm()
if (!data) return
console.log(data)
}
</script>

View File

@@ -1,7 +1,7 @@
<template>
<template v-if="getShow">
<LoginFormTitle class="enter-x" />
<Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef">
<login-form-title class="enter-x" />
<a-form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef">
<FormItem name="account" class="enter-x">
<Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" />
</FormItem>
@@ -21,16 +21,16 @@
{{ t('sys.login.backSignIn') }}
</Button>
</FormItem>
</Form>
</a-form>
</template>
</template>
<script lang="ts" setup>
import { reactive, ref, computed, unref } from 'vue'
import LoginFormTitle from './LoginFormTitle.vue'
import LoginFormTitle from '../LoginFormTitle.vue'
import { Form, Input, Button } from 'ant-design-vue'
import { CountdownInput } from '/@/components/CountDown'
import { useI18n } from '/@/hooks/web/useI18n'
import { useLoginState, useFormRules, LoginStateEnum } from './useLogin'
import { useLoginState, useFormRules, LoginStateEnum } from '../useLogin'
const FormItem = Form.Item
const { t } = useI18n()

View File

@@ -0,0 +1,126 @@
<template>
<template v-if="getShow">
<login-form-title class="enter-x" />
<a-spin :spinning="loading">
<a-form class="p-4 enter-x" :model="form" :validate-trigger="['blur', 'change']" :rules="rules" ref="formRef">
<a-form-item validate-first name="username" class="enter-x">
<a-input class="fix-auto-fill" size="large" v-model:value="form.username" placeholder="账号" />
</a-form-item>
<a-form-item validate-first name="password" class="enter-x">
<strength-meter size="large" v-model:value="form.password" placeholder="密码" />
</a-form-item>
<a-form-item validate-first name="confirmPassword" class="enter-x">
<a-input-password size="large" visibilityToggle v-model:value="form.confirmPassword" placeholder="确认密码" />
</a-form-item>
<a-row :span="12" class="enter-x">
<a-col :span="16">
<a-form-item name="captcha" class="enter-x">
<a-input size="large" placeholder="验证码" v-model:value="form.captcha" style="min-width: 100px" />
</a-form-item>
</a-col>
<a-col :span="8" style="text-align: right">
<a-form-item :style="{ 'text-align': 'right', 'margin-left': '20px' }" class="enter-x">
<img style="margin-top: 2px" :src="captchaData" @click="getCaptcha" alt="验证码" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-spin>
<a-button type="primary" class="enter-x" size="large" block @click="handleSubmit" :loading="loading"> 注册 </a-button>
<a-button size="large" block class="mt-4 enter-x" @click="handleBackLogin"> 返回 </a-button>
</template>
</template>
<script lang="ts" setup>
import { reactive, ref, unref, computed, onMounted } from 'vue'
import LoginFormTitle from '../LoginFormTitle.vue'
import { StrengthMeter } from '/@/components/StrengthMeter'
import { useLoginState, LoginStateEnum } from '../useLogin'
import { $ref } from 'vue/macros'
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import { imgCaptcha } from '/@/api/common/Captcha'
import { existsUsername } from '/@/api/sys/userAssist'
import { register } from '/@/api/sys/user'
import { useMessage } from '/@/hooks/web/useMessage'
const { handleBackLogin, getLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
const { createMessage } = useMessage()
const formRef = $ref<FormInstance>()
let loading = $ref(false)
let captchaData = $ref('')
let confirmDirty = $ref(false)
const form = reactive({
username: '',
password: '',
confirmPassword: '',
captchaKey: '',
captcha: '',
})
const rules = {
username: [{ required: true, message: '请输入登录账号!' }, { validator: checkUsername }],
password: [
{ required: true, message: '请输入登录密码!' },
{ validator: validateToNextPassword, trigger: 'change' },
],
confirmPassword: [{ required: true, message: '请重新输入登录密码!' }, { validator: compareToFirstPassword }],
captcha: [{ required: true, message: '请输入验证码!' }],
} as Record<string, Rule[]>
onMounted(() => {
getCaptcha()
})
/**
* 获取验证码
*/
function getCaptcha() {
imgCaptcha().then(({ data }) => {
captchaData = data.captchaData
form.captchaKey = data.captchaKey
})
}
/**
* 用户名检查
*/
async function checkUsername() {
const { data } = await existsUsername(form.username)
return data ? Promise.reject('用户名已存在!') : Promise.resolve()
}
/**
* 密码检查
*/
function validateToNextPassword() {
if (confirmDirty) {
formRef.validateFields(['confirmPassword'])
}
return Promise.resolve()
}
function compareToFirstPassword() {
if (form.confirmPassword !== form.password) {
confirmDirty = true
return Promise.reject('密码不一致')
} else {
return Promise.resolve()
}
}
/**
* 注册
*/
async function handleSubmit() {
await formRef.validate()
loading = true
try {
await register(form).then(() => {
createMessage.success('注册成功')
handleBackLogin()
})
formRef.resetFields()
} finally {
loading = false
}
}
</script>

View File

@@ -53,7 +53,7 @@
</div>
</template>
<script lang="ts" setup>
import { ref, Ref, onMounted } from 'vue'
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
import { useECharts } from '/@/hooks/web/useECharts'
import { getRedisInfo } from '/@/views/modules/monitor/redis/RedisInfoMonitor.api'
import { $ref } from 'vue/macros'
@@ -128,6 +128,11 @@
}, 1000 * 5)
})
onBeforeUnmount(() => {
clearInterval(interval)
interval = null
})
/**
* 获取Redis服务信息
*/

View File

@@ -139,7 +139,7 @@
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'
import { onBeforeUnmount, onMounted, onUnmounted } from 'vue'
import { getSystemInfo } from './SystemInfo.api'
import { $ref } from 'vue/macros'
@@ -169,7 +169,7 @@
})
}
onUnmounted(() => {
onBeforeUnmount(() => {
clearInterval(interval)
interval = null
})