This commit is contained in:
zhangtao 2022-07-26 11:08:10 +08:00
parent dbb5c5d2bc
commit 398deb691c
12 changed files with 303 additions and 112 deletions

View File

@ -2,7 +2,7 @@
VITE_PUBLIC_PATH = '/'
# 是否启用MOCK
VITE_APP_USE_MOCK = true
VITE_APP_USE_MOCK = false
# proxy
VITE_PROXY = [["/api-local","http://127.0.0.1:8002/api"],["/api-mock","http://127.0.0.1:8003"]]

View File

@ -13,6 +13,17 @@ export function userLogin(data = {}) {
})
}
/**
* params
* @returns 当前登录人信息
*/
export function getUser() {
return request({
url: '/index/getUserInfo',
method: 'get'
})
}
/**
* 获取验证码
* @returns 验证码图片

BIN
src/assets/back/form.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
src/assets/back/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

BIN
src/assets/icon/verify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

View File

@ -1,17 +1,79 @@
<template>
<n-layout-header class="layout__header" bordered>
<!-- <SideMenu menu-mode="horizontal" /> -->
<log-out />
<div class="header__logo">
<n-image height="18" src="/logo.png" preview-disabled />
</div>
<n-dropdown trigger="hover" :options="options" @select="handleSelect">
<div class="user_msg">
<n-image
class="user_avatar"
:src="userInfo.avatar"
preview-disabled
/>
<span class="user_name">{{ userInfo.realname }}</span>
</div>
</n-dropdown>
</n-layout-header>
</template>
<script>
import SideMenu from '@/layout/components/Menu/index.vue'
import LogOut from '../Logout/index.vue'
import { defineComponent } from 'vue'
import { defineComponent, reactive, toRefs, computed } from 'vue'
import { useRouter } from 'vue-router'
import { EditOutlined, LogoutOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils'
import { useUserStore } from '@/store/modules/user.js'
import { useSettingStore } from '@/store/modules/setting.js'
export default defineComponent({
name: 'LayoutHeader',
components: { SideMenu, LogOut }
setup() {
const router = useRouter()
const userStore = useUserStore()
const settingStore = useSettingStore()
const data = reactive({
options: [
{
label: '修改密码',
key: 'edit',
icon: renderIcon(EditOutlined)
},
{
label: '退出登录',
key: 'out',
icon: renderIcon(LogoutOutlined)
}
],
userInfo: {
avatar: '',
realname: '管理员'
}
})
const getLogoWidth = computed(() => {
return settingStore.getSidebarSetting.width - 30
})
async function handleSelect(key) {
switch (key) {
case 'out':
await logOut()
}
}
async function logOut() {
const res = await userStore.userLogout()
if (res.code === 0) {
router.replace('/login')
}
}
return {
...toRefs(data),
getLogoWidth,
handleSelect
}
}
})
</script>
@ -19,8 +81,11 @@ export default defineComponent({
.layout__header {
padding: 0 20px;
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
}
.header__logo{
height: 18px;
}
.user_msg {
display: flex;

View File

@ -1,6 +1,10 @@
import { defineStore } from 'pinia'
import { getUser } from '@/api/user'
import { removeToken } from '@/utils/token'
import { userLogin, loginOut } from '@/api/login'
import { getUser } from '@/api/login/index.js'
import { setToken, removeToken } from '@/utils/token'
import { useTagsMenuStore } from './tagsMenu.js'
import { usePermissionStore } from './permission.js'
export const useUserStore = defineStore('user', {
persist: true,
@ -15,24 +19,51 @@ export const useUserStore = defineStore('user', {
}
},
actions: {
async getUserInfo() {
/* 登录 */
async getLoginToken(form) {
try {
const res = await getUser()
const res = await userLogin(form)
if (res.code === 0) {
this.userInfo = res.data
return Promise.resolve(res.msg)
/* 设置token */
setToken(res.data.access_token)
this.getUserInfo()
} else {
return Promise.reject(res.msg)
return Promise.reject(res)
}
} catch (error) {
console.error(error)
return Promise.reject(error.msg)
return Promise.reject(error)
}
},
logout() {
removeToken()
this.userInfo = {}
/* 获取用户信息 */
async getUserInfo() {
const res = await getUser()
if (res.code === 0) {
this.setUserInfo(res.data)
}
},
async userLogout() {
try {
const res = await loginOut()
if (res.code === 0) {
removeToken()
this.reset()
return Promise.resolve(res)
} else {
return Promise.reject(res)
}
} catch (error) {
return Promise.reject(error)
}
},
reset() {
this.$reset()
const tagsMenuStore = useTagsMenuStore()
const permissionStore = usePermissionStore()
tagsMenuStore.$reset()
permissionStore.$reset()
},
setUserInfo(userInfo = {}) {
this.userInfo = { ...this.userInfo, ...userInfo }
}

View File

@ -34,11 +34,12 @@ export function setupInterceptor(service) {
service.interceptors.response.use(
(response) => {
const { method } = response?.config
const { code } = response?.data
const { currentRoute } = router
switch (code) {
case 0:
if (!response.config.hideMessage) {
if (method !== 'get') {
$message.success(response.data.msg)
}
break

View File

@ -1,47 +1,68 @@
<template>
<div class="login_bg">
<n-form
ref="formRef"
:model="loginForm"
:rules="rules"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
:style="{
maxWidth: '640px',
backgroundColor: '#fff',
padding: '20px',
borderRadius: '10px'
}"
@keyup.enter="handleLogin"
>
<n-form-item label="用户名" path="username">
<n-input v-model:value="loginForm.username" placeholder="请输入用户名" />
</n-form-item>
<n-form-item label="密码" path="password">
<n-input v-model:value="loginForm.password" type="password" placeholder="请输入用户名" />
</n-form-item>
<n-form-item label="验证码" path="captcha">
<n-input v-model:value="loginForm.captcha" placeholder="请输入验证码" />
<img v-if="captcha" :src="captcha" alt="" @click="changeCode">
</n-form-item>
<n-form-item path="remember">
<n-checkbox v-model:checked="loginForm.remember" size="medium" label="记住密码" />
</n-form-item>
<n-form-item>
<n-button @click="handleLogin">登录</n-button>
</n-form-item>
</n-form>
<div class="login">
<div class="login__container">
<div class="container__left" />
<div class="container__right">
<div class="right__title">汤山林场巡检管理平台</div>
<div class="right__form">
<n-form
ref="formRef"
:model="loginForm"
:rules="rules"
label-placement="left"
require-mark-placement="right-hanging"
:style="{maxWidth: '300px'}"
@keyup.enter="handleLogin"
>
<n-form-item path="username">
<n-input v-model:value="loginForm.username" placeholder="请输入用户名">
<template #prefix>
<img src="@/assets/icon/username.png" alt="">
</template>
</n-input>
</n-form-item>
<n-form-item path="password">
<n-input v-model:value="loginForm.password" type="password" placeholder="请输入用户名">
<template #prefix>
<img src="@/assets/icon/password.png" alt="">
</template>
</n-input>
</n-form-item>
<n-form-item path="captcha">
<n-input v-model:value="loginForm.captcha" placeholder="请输入验证码">
<template #prefix>
<img src="@/assets/icon/verify.png" alt="">
</template>
<template #suffix>
<img v-if="captcha" class="captcha_img" :src="captcha" @click="changeCode">
</template>
</n-input>
</n-form-item>
<n-form-item path="remember">
<n-checkbox v-model:checked="loginForm.remember" size="medium" label="记住密码" />
</n-form-item>
</n-form>
</div>
<div class="right__button" @click="handleLogin">登录</div>
</div>
</div>
<div class="login__copyright">copyright©2022拓恒技术有限公司</div>
</div>
</template>
<script>
import { userLogin, userCaptcha } from '@/api/login/index.js'
import { setToken } from '@/utils/token'
import { ref, toRefs, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { userCaptcha } from '@/api/login/index.js'
import { useUserStore } from '@/store/modules/user.js'
export default {
name: 'LoginPage',
setup() {
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
console.log(userStore)
const data = reactive({
loginForm: {
username: '',
@ -50,78 +71,140 @@ export default {
captcha: '',
remember: false
},
captcha: '',
rules: {
username: {
required: true,
trigger: ['blur', 'input'],
message: '请输入用户名'
},
password: {
required: true,
trigger: ['blur', 'input'],
message: '请输入密码'
},
captcha: {
required: true,
trigger: ['blur', 'input'],
message: '请输入验证码'
}
username: { required: true, message: '请输入用户名', trigger: ['blur', 'input'] },
password: { required: true, message: '请输入密码', trigger: ['blur', 'input'] },
captcha: { required: true, message: '请输入验证码', trigger: ['blur', 'input'] }
}
})
onMounted(() => {
changeCode(data.loginForm)
changeCode()
data.loginForm = Object.assign(
data.loginForm,
JSON.parse(localStorage.getItem('login-form'))
)
})
//
const captcha = ref('')
async function changeCode(form) {
const params = {
username: form.username,
password: form.password,
captcha: form.captcha
}
const captForm = await userCaptcha(params)
captcha.value = captForm.data.captcha
async function changeCode() {
const captForm = await userCaptcha()
data.captcha = captForm.data.captcha
data.loginForm.key = captForm.data.key
}
const formRef = ref('')
function handleLogin() {
formRef.value.validate((errors) => {
if (!errors) {
const { username, password, remember } = data.loginForm
if (remember) {
localStorage.setItem('login-form', JSON.stringify({ username, password, remember }))
} else {
localStorage.removeItem('login-form')
}
/* 调用store中的方法登录以及存储token和用户信息 */
userStore.getLoginToken(this.loginForm)
const toPath = decodeURIComponent((route.query?.redirect || '/'))
route.name === '/login' ? router.replace('/') : router.replace(toPath)
}
})
}
return {
...toRefs(data),
captcha,
formRef,
handleLogin,
changeCode
}
},
methods: {
handleLogin() {
userLogin(this.loginForm).then((res) => {
if (res.code === 0) {
// token
setToken(res.data.access_token)
this.goPage()
} else {
console.log(res)
}
})
},
//
goPage() {
// const query = this.$route.query
// const path = query && query.from ? query.from : '/'
// console.log(path)
// this.$router.replace(path)
this.$router.replace('/home')
}
}
}
</script>
<style lang="scss" scoped>
.login_bg {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.login{
box-sizing: border-box;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-image: url("../../assets/back/login.png");
background-size: 100% 100%;
}
.login__container {
width: 1460px;
background-color: #fff;
display: flex;
border-radius: 20px;
.container__left{
width: 50%;
align-self: stretch;
background-image: url("../../assets/back/form.png");
background-size: 100% 100%;
}
</style>
.container__right{
flex: 1;
padding: 137px 0 236px 0;
.right__title{
font-size: 44px;
font-family: PingFang SC-Semibold, PingFang SC;
font-weight: 600;
color: #1890FF;
text-align: center;
}
.right__form{
padding: 34px 0 20px 0;
.n-form{
margin: 0 auto;
}
.n-input:not(.n-input--autosize) {
width: 100%;
height: 40px;
line-height: 40px;
background-color: transparent;
}
::v-deep(.n-input__suffix){
cursor: pointer;
transform: translate(12px);
}
.captcha_img {
width: 90px;
height: 100%;
border-left: 1px solid rgb(224, 224, 230);
border-radius: 4px;
}
}
.right__button{
width: 300px;
height: 40px;
border-radius: 4px;
cursor: pointer;
background-color: #1890ff;
line-height: 40px;
text-align: center;
color: #fff;
font-size: 16px;
margin: 0 auto;
}
}
}
.login__copyright {
font-size: 20px;
color: #fff;
position: absolute;
bottom: 16px;
}
@media screen and (max-width: 1200px) {
.login__container{
max-width: 730px;
}
.container__left{
display: none;
}
}
</style>

View File

@ -6,7 +6,7 @@ const getRolesOption = async function() {
const res = await getRoleAll()
rolesOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}
getRolesOption()
// getRolesOption()
const data = [
{
label: '用户账号',