Browse Source

Merge branch 'master' into test

flowtest
zhouhaibin 5 months ago
parent
commit
51e8829cfa
  1. 9
      src/api/login/index.ts
  2. 18
      src/components/TagsView/src/TagsView.vue
  3. 13
      src/modules/system/api/index.ts
  4. 2
      src/permission.ts
  5. 10
      src/router/index.ts
  6. 6
      src/store/modules/app.ts
  7. 10
      src/utils/index.ts
  8. 16
      src/views/Login/Login.vue
  9. 87
      src/views/Login/components/LoginForm.vue
  10. 57
      src/views/Login/components/RegisterForm.vue
  11. 73
      src/views/Login/components/RetrievePassword.vue
  12. 82
      src/views/Login/components/SelfResetPassword.vue
  13. 3
      src/views/Login/components/index.ts

9
src/api/login/index.ts

@ -4,13 +4,20 @@ import type { UserType } from './types'
interface RoleParams { interface RoleParams {
roleName: string roleName: string
} }
// 登录
export const loginApi = (data: UserType) => { export const loginApi = (data: UserType) => {
return request.post({ return request.post({
url: '/system/user/login?username=' + data.username + '&password=' + data.password, url: '/system/user/login?username=' + data.username + '&password=' + data.password,
data data
}) })
} }
// 注册
export const registerApi = (data) => {
return request.post({
url: '/system/user/register',
data
})
}
export const loginOutApi = (): Promise<IResponse> => { export const loginOutApi = (): Promise<IResponse> => {
return request.get({ url: '/system/user/logout' }) return request.get({ url: '/system/user/logout' })

18
src/components/TagsView/src/TagsView.vue

@ -18,6 +18,12 @@ const { emitter } = useEmitt()
emitter.on('closeCurrentTab', () => { emitter.on('closeCurrentTab', () => {
closeSelectedTag(unref(selectedTag) as RouteLocationNormalizedLoaded) closeSelectedTag(unref(selectedTag) as RouteLocationNormalizedLoaded)
}) })
emitter.on('refreshCurrentTab', () => {
refreshSelectedTag(unref(selectedTag) as RouteLocationNormalizedLoaded)
})
emitter.on('refreshSelectedTagWithQuery', (query) => {
refreshSelectedTagWithQuery(unref(selectedTag) as RouteLocationNormalizedLoaded, query)
})
emitter.on('closeTab', (view) => { emitter.on('closeTab', (view) => {
closeSelectedTag(view as RouteLocationNormalizedLoaded) closeSelectedTag(view as RouteLocationNormalizedLoaded)
@ -102,6 +108,18 @@ const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
}) })
} }
//
const refreshSelectedTagWithQuery = async (view?: RouteLocationNormalizedLoaded, query) => {
if (!view) return
tagsViewStore.delCachedView()
const { path } = view
await nextTick()
replace({
path: '/redirect' + path,
query: query
})
}
// //
const closeLeftTags = () => { const closeLeftTags = () => {
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded) tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)

13
src/modules/system/api/index.ts

@ -59,9 +59,14 @@ export const user = Object.assign({}, COMMON_METHOD, {
disable(id) { disable(id) {
return request.put({ url: this.serveUrl + id + '/disable' }) return request.put({ url: this.serveUrl + id + '/disable' })
}, },
// 管理员重置密码
resetPassword(id) { resetPassword(id) {
return request.put({ url: this.serveUrl + id + '/resetPassword' }) return request.put({ url: this.serveUrl + id + '/resetPassword' })
}, },
// 自助重置密码,用于忘记密码
selfResetPassword(code, password) {
return request.put({ url: this.serveUrl + 'selfResetPassword', params: { code, password } })
},
unlock(id) { unlock(id) {
return request.put({ url: this.serveUrl + id + '/unlock' }) return request.put({ url: this.serveUrl + id + '/unlock' })
}, },
@ -111,6 +116,14 @@ export const user = Object.assign({}, COMMON_METHOD, {
// 导出 // 导出
export(params) { export(params) {
return request.download({ url: this.serveUrl + 'exportExcel', params }) return request.download({ url: this.serveUrl + 'exportExcel', params })
},
// 找回密码
retrievePassword(email) {
return request.post({ url: this.serveUrl + 'retrievePassword', params: { email } })
},
//根据授权码获取账号
getAccoutByCode(code) {
return request.get({ url: this.serveUrl + 'getAccoutByCode', params: { code } })
} }
// // 高级查询初始化 // // 高级查询初始化
// initAdvanceQuery() { // initAdvanceQuery() {

2
src/permission.ts

@ -18,7 +18,7 @@ const { start, done } = useNProgress()
const { loadStart, loadDone } = usePageLoading() const { loadStart, loadDone } = usePageLoading()
const whiteList = ['/login'] // 不重定向白名单 const whiteList = ['/login', '/selfResetPassword'] // 不重定向白名单
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
start() start()

10
src/router/index.ts

@ -43,6 +43,16 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
noTagsView: true noTagsView: true
} }
}, },
{
path: '/selfResetPassword',
component: () => import('@/views/Login/components/SelfResetPassword.vue'),
name: 'SelfResetPassword',
meta: {
hidden: true,
title: '重设密码',
noTagsView: true
}
},
{ {
path: '/404', path: '/404',
component: () => import('@/views/Error/404.vue'), component: () => import('@/views/Error/404.vue'),

6
src/store/modules/app.ts

@ -35,6 +35,7 @@ interface AppState {
footer: boolean footer: boolean
theme: ThemeTypes theme: ThemeTypes
fixedMenu: boolean fixedMenu: boolean
enableRegister: boolean
} }
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
@ -61,7 +62,7 @@ export const useAppStore = defineStore('app', {
footer: true, // 显示页脚 footer: true, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日 greyMode: false, // 是否开始灰色模式,用于特殊悼念日
fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单 fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
enableRegister: false, // 是否启用注册
layout: wsCache.get('layout') || 'classic', // layout布局 layout: wsCache.get('layout') || 'classic', // layout布局
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
currentSize: wsCache.get('default') || 'default', // 组件尺寸 currentSize: wsCache.get('default') || 'default', // 组件尺寸
@ -169,6 +170,9 @@ export const useAppStore = defineStore('app', {
}, },
getFooter(): boolean { getFooter(): boolean {
return this.footer return this.footer
},
getEnableRegister(): boolean {
return this.enableRegister
} }
}, },
actions: { actions: {

10
src/utils/index.ts

@ -153,6 +153,16 @@ export function closeCurrentTab() {
emitter.emit('closeCurrentTab') emitter.emit('closeCurrentTab')
} }
/**
* tab页
*/
export function refreshCurrentTab() {
emitter.emit('refreshCurrentTab')
}
export function refreshSelectedTagWithQuery(query) {
emitter.emit('refreshCurrentTab', query)
}
export function closeTab(view) { export function closeTab(view) {
emitter.emit('closeTab', view) emitter.emit('closeTab', view)
} }

16
src/views/Login/Login.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { LoginForm } from './components' import { LoginForm, RegisterForm } from './components'
import { ThemeSwitch } from '@/components/ThemeSwitch' import { ThemeSwitch } from '@/components/ThemeSwitch'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
@ -17,6 +17,14 @@ const appStore = useAppStore()
const { t } = useI18n() const { t } = useI18n()
const isLogin = ref(true) const isLogin = ref(true)
const toRegister = () => {
isLogin.value = false
}
const toLogin = () => {
isLogin.value = true
}
</script> </script>
<template> <template>
@ -64,6 +72,12 @@ const isLogin = ref(true)
<LoginForm <LoginForm
v-if="isLogin" v-if="isLogin"
class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)"
@to-register="toRegister"
/>
<RegisterForm
v-else
class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)"
@to-login="toLogin"
/> />
</div> </div>
</Transition> </Transition>

87
src/views/Login/components/LoginForm.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, unref, watch } from 'vue' import { reactive, ref, unref, watch, computed } from 'vue'
import { Form } from '@/components/Form' import { Form } from '@/components/Form'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ElButton } from 'element-plus' import { ElButton } from 'element-plus'
@ -9,17 +9,18 @@ import { loginApi } from '@/api/login'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router' import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
import { useRouter } from 'vue-router'
import { UserType } from '@/api/login/types' import { UserType } from '@/api/login/types'
import { useValidator } from '@/hooks/web/useValidator' import { useValidator } from '@/hooks/web/useValidator'
import { FormSchema } from '@/types/form' import { FormSchema } from '@/types/form'
import useGetGlobalProperties from '@/hooks/useGlobal' import useGetGlobalProperties from '@/hooks/useGlobal'
const globalProperties = useGetGlobalProperties() const globalProperties = useGetGlobalProperties()
const { required } = useValidator() const { required } = useValidator()
const appStore = useAppStore() const emit = defineEmits(['to-register'])
const userStore = useUserStore() const userStore = useUserStore()
@ -34,6 +35,9 @@ const rules = {
password: [required()] password: [required()]
} }
//
const appStore = useAppStore()
const enableRegister = computed(() => appStore.getEnableRegister)
const schema = reactive<FormSchema[]>([ const schema = reactive<FormSchema[]>([
{ {
field: 'title', field: 'title',
@ -50,6 +54,9 @@ const schema = reactive<FormSchema[]>([
span: 24 span: 24
}, },
componentProps: { componentProps: {
style: {
width: '100%'
},
placeholder: t('login.usernamePlaceholder') placeholder: t('login.usernamePlaceholder')
} }
}, },
@ -73,13 +80,25 @@ const schema = reactive<FormSchema[]>([
colProps: { colProps: {
span: 24 span: 24
} }
},
{
field: 'tool',
colProps: {
span: 24
}
} }
]) ])
const iconSize = 30
// const remember = ref(false)
const { register, elFormRef, methods } = useForm() const { register, elFormRef, methods } = useForm()
const loading = ref(false) const loading = ref(false)
const iconColor = '#999'
const redirect = ref<string>('') const redirect = ref<string>('')
watch( watch(
@ -108,6 +127,7 @@ const signIn = async () => {
try { try {
const res = await loginApi(formData) const res = await loginApi(formData)
if (res) { if (res) {
// //
userStore.setUserAction(res.data) userStore.setUserAction(res.data)
@ -131,6 +151,18 @@ const signIn = async () => {
} }
}) })
} }
//
const toRegister = () => {
emit('to-register')
}
//
import RetrievePasswordForm from './RetrievePassword.vue'
const retrievePasswordForm = ref(null)
const retrievePassword = () => {
retrievePasswordForm.value.init()
}
</script> </script>
<template> <template>
@ -142,20 +174,63 @@ const signIn = async () => {
size="large" size="large"
class="dark:(border-1 border-[var(--el-border-color)] border-solid)" class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
@register="register" @register="register"
@keyup="keyUp"
> >
<template #title> <template #title>
<h2 class="text-2xl font-bold text-center w-[100%]">{{ t('login.login') }}</h2> <h2 class="text-2xl font-bold text-center w-[100%]">{{ t('login.login') }}</h2>
</template> </template>
<template #tool>
<div class="flex justify-end items-center w-[100%]" v-show="enableRegister">
<!-- <ElCheckbox v-model="remember" :label="t('login.remember')" size="small" /> -->
<ElLink type="primary" :underline="false" @click="retrievePassword">{{
t('login.forgetPassword')
}}</ElLink>
</div>
</template>
<template #login> <template #login>
<div class="w-[100%]"> <div class="w-[100%]">
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="signIn"> <ElButton :loading="loading" type="primary" class="w-[100%]" @click="signIn">
{{ t('login.login') }} {{ t('login.login') }}
</ElButton> </ElButton>
</div> </div>
<div class="w-[100%] mt-15px" v-show="enableRegister">
<ElButton class="w-[100%]" @click="toRegister">
{{ t('login.register') }}
</ElButton>
</div>
</template>
<template #otherIcon>
<div class="flex justify-between w-[100%]">
<Icon
icon="ant-design:github-filled"
:size="iconSize"
class="cursor-pointer anticon"
:color="iconColor"
/>
<Icon
icon="ant-design:wechat-filled"
:size="iconSize"
class="cursor-pointer anticon"
:color="iconColor"
/>
<Icon
icon="ant-design:alipay-circle-filled"
:size="iconSize"
:color="iconColor"
class="cursor-pointer anticon"
/>
<Icon
icon="ant-design:weibo-circle-filled"
:size="iconSize"
:color="iconColor"
class="cursor-pointer anticon"
/>
</div>
</template> </template>
</Form> </Form>
<RetrievePasswordForm ref="retrievePasswordForm" />
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@ -164,8 +239,4 @@ const signIn = async () => {
color: var(--el-color-primary) !important; color: var(--el-color-primary) !important;
} }
} }
:deep(.el-input) {
width: 100%;
}
</style> </style>

57
src/views/Login/components/RegisterForm.vue

@ -3,13 +3,14 @@ import { Form } from '@/components/Form'
import { reactive, ref, unref } from 'vue' import { reactive, ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useForm } from '@/hooks/web/useForm' import { useForm } from '@/hooks/web/useForm'
import { ElButton, ElInput, FormRules } from 'element-plus' import { ElButton, ElInput, ElMessage, FormRules } from 'element-plus'
import { useValidator } from '@/hooks/web/useValidator' import { useValidator } from '@/hooks/web/useValidator'
import { FormSchema } from '@/types/form' import { FormSchema } from '@/types/form'
import { registerApi } from '@/api/login'
const emit = defineEmits(['to-login']) const emit = defineEmits(['to-login'])
const { register, elFormRef } = useForm() const { register, elFormRef, methods } = useForm()
const { t } = useI18n() const { t } = useI18n()
@ -23,17 +24,34 @@ const schema = reactive<FormSchema[]>([
} }
}, },
{ {
field: 'username', field: 'account',
label: t('login.username'), label: '账号',
value: '', value: '',
component: 'Input', component: 'Input',
colProps: { colProps: {
span: 24 span: 24
}, },
componentProps: { componentProps: {
style: {
width: '100%'
},
placeholder: t('login.usernamePlaceholder') placeholder: t('login.usernamePlaceholder')
} }
}, },
{
field: 'name',
label: '昵称',
value: '',
component: 'Input',
colProps: {
span: 24
},
componentProps: {
style: {
width: '100%'
}
}
},
{ {
field: 'password', field: 'password',
label: t('login.password'), label: t('login.password'),
@ -51,7 +69,7 @@ const schema = reactive<FormSchema[]>([
} }
}, },
{ {
field: 'check_password', field: 'checkPassword',
label: t('login.checkPassword'), label: t('login.checkPassword'),
value: '', value: '',
component: 'InputPassword', component: 'InputPassword',
@ -66,11 +84,19 @@ const schema = reactive<FormSchema[]>([
placeholder: t('login.passwordPlaceholder') placeholder: t('login.passwordPlaceholder')
} }
}, },
{ {
field: 'code', field: 'email',
label: t('login.code'), label: '邮箱',
value: '',
component: 'Input',
colProps: { colProps: {
span: 24 span: 24
},
componentProps: {
style: {
width: '100%'
}
} }
}, },
{ {
@ -82,10 +108,11 @@ const schema = reactive<FormSchema[]>([
]) ])
const rules: FormRules = { const rules: FormRules = {
username: [required()], account: [required()],
password: [required()], password: [required()],
check_password: [required()], checkPassword: [required()],
code: [required()] name: [required()],
email: [required()]
} }
const toLogin = () => { const toLogin = () => {
@ -100,6 +127,16 @@ const loginRegister = async () => {
if (valid) { if (valid) {
try { try {
loading.value = true loading.value = true
//
const { getFormData } = methods
const formData = await getFormData()
//
if (formData.password != formData.checkPassword) {
ElMessage.error('两次密码不一致')
return
}
//
await registerApi(formData)
toLogin() toLogin()
} finally { } finally {
loading.value = false loading.value = false

73
src/views/Login/components/RetrievePassword.vue

@ -0,0 +1,73 @@
<template>
<Dialog title="找回密码" v-model="visible" width="300px">
<el-form
ref="form"
:model="entityData"
:rules="rules"
label-width="70px"
label-position="right"
style="width: 90%; margin: 0px auto"
v-loading="loading"
>
<!--表单区域 -->
<el-form-item label="邮箱" prop="email">
<el-input v-model="entityData.email" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="confirm">确定</el-button>
<el-button @click="close">关闭</el-button>
</template>
</Dialog>
</template>
<script>
import { Dialog } from '@/components/abc/Dialog'
export default {
name: 'RetrievePassword',
components: { Dialog },
data() {
return {
//
loading: false,
//
visible: false,
entityData: {},
rules: {
//
email: [{ required: true, message: '【邮箱】不能为空', trigger: 'blur' }]
}
}
},
methods: {
init() {
this.visible = true
},
confirm() {
this.$refs.form.validate((valid) => {
if (valid) {
this.loading = true
this.$api.system.user
.retrievePassword(this.entityData.email)
.then((res) => {
this.$message.info('密码重置邮件已发送,请查收')
this.visible = false
})
.finally(() => {
this.loading = false
})
} else {
return false
}
})
},
close() {
this.visible = false
}
}
}
</script>
<style></style>

82
src/views/Login/components/SelfResetPassword.vue

@ -0,0 +1,82 @@
<template>
<div class="div-center">
<el-form
ref="form"
:model="entityData"
:rules="rules"
label-width="120px"
label-position="right"
style="width: 90%; margin: 0 auto"
autocomplete="new-password"
>
<el-form-item label="账号" prop="account">
<el-input v-model="entityData.account" disabled />
</el-form-item>
<el-form-item label="新密码" prop="newPassword" class="form-item">
<el-input v-model="entityData.newPassword" show-password />
</el-form-item>
<el-form-item label="重复新密码" prop="newPasswordRepeat">
<el-input v-model="entityData.newPasswordRepeat" show-password />
</el-form-item>
</el-form>
<br />
<el-button style="float: right" type="primary" @click="confirm">确认</el-button>
</div>
</template>
<script>
export default {
components: {},
emits: ['hidden'],
data() {
return {
entityData: {
account: '',
newPassword: '',
newPasswordRepeat: ''
},
rules: {
//
newPassword: [{ required: true, message: '【新密码】不能为空', trigger: 'blur' }],
newPasswordRepeat: [{ required: true, message: '【重复新密码】不能为空', trigger: 'blur' }]
}
}
},
mounted() {
this.init()
},
methods: {
init() {
const code = this.$route.query.code
this.$api.system.user.getAccoutByCode(code).then((res) => {
this.entityData.account = res.data
})
},
confirm() {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.entityData.newPassword !== this.entityData.newPasswordRepeat) {
this.$message({
type: 'warning',
message: '两次输入的新密码不一致'
})
return
}
const code = this.$route.query.code
this.$api.system.user.selfResetPassword(code, this.entityData.newPassword).then(() => {
//
this.$router.push({ name: 'Login' })
})
}
})
}
}
}
</script>
<style scoped>
.div-center {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -20%); /* 偏移元素自身宽度和高度的50% */
}
</style>

3
src/views/Login/components/index.ts

@ -1,3 +1,4 @@
import LoginForm from './LoginForm.vue' import LoginForm from './LoginForm.vue'
import RegisterForm from './RegisterForm.vue'
export { LoginForm } export { LoginForm, RegisterForm }

Loading…
Cancel
Save