Browse Source

feat:登录功能实现

feature/three
guoapeng 3 months ago
parent
commit
a48ae2a149
  1. 50
      increment-version.js
  2. 2
      src/apis/login.ts
  3. 83
      src/app.vue
  4. 1
      src/assets/images/login.svg
  5. 1
      src/assets/images/password_icon.svg
  6. 1
      src/assets/images/user_icon.svg
  7. 13
      src/layouts/default.vue
  8. 12
      src/stores/systemStore.ts
  9. 5
      src/types/system.d.ts
  10. 157
      src/views/login/index.vue

50
increment-version.js

@ -1,41 +1,43 @@
import fs from 'node:fs';
import path, { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import semver from 'semver';
import { execSync } from 'child_process'; // 引入 child_process 模块用于执行 Git 命令
import { execSync } from 'node:child_process' // 引入 child_process 模块用于执行 Git 命令
import fs from 'node:fs'
import path, { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import semver from 'semver'
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const packagePath = path.resolve(__dirname, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
const packagePath = path.resolve(__dirname, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
// 读取命令行参数(默认使用 'patch') // 读取命令行参数(默认使用 'patch')
const versionType = process.argv[2] || 'patch';
// eslint-disable-next-line node/prefer-global/process
const versionType = process.argv[2] || 'patch'
// 递增版本 // 递增版本
const newVersion = semver.inc(packageJson.version, versionType);
const newVersion = semver.inc(packageJson.version, versionType)
if (!newVersion) { if (!newVersion) {
throw new Error(`Invalid version type: ${versionType}`);
throw new Error(`Invalid version type: ${versionType}`)
} }
packageJson.version = newVersion;
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log(`Version updated to: ${newVersion}`);
packageJson.version = newVersion
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2))
console.log(`Version updated to: ${newVersion}`)
// 新增:自动提交 package.json 到远程仓库 // 新增:自动提交 package.json 到远程仓库
try { try {
// 将 package.json 添加到暂存区 // 将 package.json 添加到暂存区
execSync('git add package.json');
console.log('Added package.json to staging area.');
execSync('git add package.json')
console.log('Added package.json to staging area.')
// 提交更改 // 提交更改
execSync(`git commit -m "fix: Update version to V${newVersion}"`);
console.log(`Committed changes with message: Update version to ${newVersion}`);
execSync(`git commit -m "fix: Update version to V${newVersion}"`)
console.log(`Committed changes with message: Update version to ${newVersion}`)
// 推送到远程仓库 // 推送到远程仓库
execSync('git push');
console.log('Pushed changes to remote repository.');
} catch (error) {
console.error('Failed to commit and push changes:', error.message);
}
execSync('git push')
console.log('Pushed changes to remote repository.')
}
catch (error) {
console.error('Failed to commit and push changes:', error.message)
}

2
src/apis/login.ts

@ -1,4 +1,4 @@
import http from 'libs/http' import http from 'libs/http'
export const login = (params: User.Login): Promise<User.User> => http.post('/auth/login', params) export const login = (params: User.Login): Promise<User.User> => http.post('/auth/login', params)
export const logout = (params: User.Login): Promise<null> => http.post('/auth/logout', params)
export const logout = (): Promise<null> => http.post('/auth/logout')

83
src/app.vue

@ -1,17 +1,52 @@
<script setup lang="ts"> <script setup lang="ts">
import { socket } from 'libs/socket' import { socket } from 'libs/socket'
import { onMounted } from 'vue'
import { onBeforeUnmount, onMounted, ref } from 'vue'
onMounted(() => { onMounted(() => {
document.body.style.setProperty('--el-color-primary', '#1989fa') document.body.style.setProperty('--el-color-primary', '#1989fa')
}) })
socket.init(() => {}, 'app') socket.init(() => {}, 'app')
// const router = useRouter()
const progress = ref(0)
let timer: any = null
const version = __APP_VERSION__
const startProgress = () => {
timer = setInterval(() => {
const randomStep = Math.floor(Math.random() * 9 + 1)
progress.value = Math.min(progress.value + randomStep, 100)
if (progress.value >= 100) {
clearInterval(timer)
}
}, 50)
}
onMounted(() => {
startProgress()
})
//
onBeforeUnmount(() => {
clearInterval(timer)
})
</script> </script>
<template> <template>
<router-view v-slot="{ Component }" class="main-content">
<!-- 进度条容器 -->
<div v-if="progress < 100" class="main-content">
<div class="progress-container">
<div class="progress-bar" :style="{ width: `${progress}%` }" />
<div class="progress-text">
v{{ version }}系统初始化中 {{ progress }}%
</div>
</div>
</div>
<router-view v-else v-slot="{ Component }" class="main-content">
<transition name="el-zoom-in-center"> <transition name="el-zoom-in-center">
<component :is="Component" /> <component :is="Component" />
</transition> </transition>
@ -26,4 +61,46 @@ socket.init(() => {}, 'app')
background-size: cover; background-size: cover;
overflow: hidden; overflow: hidden;
} }
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: #f0f2f5;
}
img {
width: 600px;
position: absolute;
top: 40%;
right: 20%;
}
.progress-container {
width: 50%;
height: 20px;
background: #e4e7ed;
border-radius: 30px;
position: relative;
top: 90%;
margin: 0 auto;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #1989FA, #096ae0);
border-radius: 30px;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 12px;
line-height: 12px;
font-weight: 500;
}
</style> </style>

1
src/assets/images/login.svg
File diff suppressed because it is too large
View File

1
src/assets/images/password_icon.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="32.66561508178711" height="39.13572692871094" viewBox="0 0 32.66561508178711 39.13572692871094"><g><path d="M29.768,16.829C29.4345,16.829,27.5584,16.8498,27.204,16.8498L27.204,10.9347C27.204,4.37387,22.9722,0.0208277,16.3641,0C9.75591,0,5.4408,4.58215,5.4408,10.9347L5.4408,16.7873C5.21149,16.7873,2.8142,16.8082,2.60574,16.8082C1.02145,16.8082,0,18.162,0,19.6199L0,36.2615C0,37.9069,0.87553,39.1357,2.56405,39.1357L29.8514,39.1357C31.1647,39.1357,32.6656,37.9485,32.6656,35.8033L32.6656,19.9948C32.6656,18.3703,32.1236,16.829,29.768,16.829ZM19.9704,31.7626C19.9704,33.8246,18.3444,35.4908,16.3432,35.4908C14.342,35.4908,12.716,33.8246,12.716,31.7626L12.716,31.7001C12.716,30.3255,13.4456,29.1175,14.5296,28.4718L14.5296,21.5986C14.5296,20.6197,15.3009,20.5155,16.2598,20.5155C17.2188,20.5155,18.1568,20.6197,18.1568,21.5986L18.1568,28.4718C19.2408,29.1175,19.9704,30.3255,19.9704,31.7001L19.9704,31.7626ZM23.6185,16.7873L9.08884,16.7873L9.08884,9.28927C9.08884,5.68603,13.1121,3.7282,16.1348,3.7282L16.8018,3.7282C19.8245,3.7282,23.6185,5.54024,23.6185,9.28927L23.6185,16.7873Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/user_icon.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="33.87468338012695" height="33.84542465209961" viewBox="0 0 33.87468338012695 33.84542465209961"><g><path d="M33.8747,32.442C33.8747,25.5468,28.9971,19.6889,22.2628,17.6265C24.8808,15.9057,26.6152,12.9605,26.6152,9.61666C26.6152,4.31204,22.275,0,16.9373,0C11.5996,0,7.25944,4.31204,7.25944,9.61259C7.25944,12.9565,8.99389,15.9017,11.6118,17.6224C4.87763,19.6889,0,25.5427,0,32.442L0,33.8454L33.8747,33.8454L33.8747,32.4623L33.8747,32.442Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

13
src/layouts/default.vue

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { logout } from 'apis/login'
import { isClose } from 'libs/socket' import { isClose } from 'libs/socket'
import { delToken } from 'libs/token'
import { formatDateTime } from 'libs/utils' import { formatDateTime } from 'libs/utils'
import { authRoutes } from 'router/routes' import { authRoutes } from 'router/routes'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
@ -18,6 +20,13 @@ const timeInterval = setInterval(() => {
onUnmounted(() => { onUnmounted(() => {
clearInterval(timeInterval) clearInterval(timeInterval)
}) })
const logoutHandle = () => {
logout().then(() => {
delToken()
router.push('/login').then(() => {})
})
}
</script> </script>
<template> <template>
@ -44,7 +53,7 @@ onUnmounted(() => {
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="systemStore.logout()">
<el-dropdown-item @click="logoutHandle">
退出登录 退出登录
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
@ -68,7 +77,7 @@ onUnmounted(() => {
</el-aside> </el-aside>
<el-main> <el-main>
<router-view v-slot="{ Component }" class="content"> <router-view v-slot="{ Component }" class="content">
<transition name="el-zoom-in-center">
<transition name="el-fade-in-linear">
<component :is="Component" /> <component :is="Component" />
</transition> </transition>
</router-view> </router-view>

12
src/stores/systemStore.ts

@ -1,5 +1,3 @@
import router from '@/router'
import { delToken } from 'libs/token'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
export const useSystemStore = defineStore('system', { export const useSystemStore = defineStore('system', {
@ -9,8 +7,12 @@ export const useSystemStore = defineStore('system', {
systemUser: { systemUser: {
username: '', username: '',
}, },
loginForm: {
username: import.meta.env.FT_NODE_ENV !== 'prod' ? 'admin' : '',
password: import.meta.env.FT_NODE_ENV !== 'prod' ? '123456' : '',
},
menuExpand: true, menuExpand: true,
isDebug: import.meta.env.FT_NODE_ENV === 'dev',
isDebug: import.meta.env.FT_NODE_ENV !== 'prod',
streamVisible: false, streamVisible: false,
systemList: [], systemList: [],
}), }),
@ -27,10 +29,6 @@ export const useSystemStore = defineStore('system', {
pushSystemList(text: any) { pushSystemList(text: any) {
this.systemList.push(text) this.systemList.push(text)
}, },
logout() {
delToken()
router.push('/login').then(() => {})
},
}, },
persist: true, persist: true,
}) })

5
src/types/system.d.ts

@ -6,6 +6,7 @@ declare namespace System {
isDebug: boolean isDebug: boolean
menuExpand: boolean menuExpand: boolean
systemUser: SystemUser systemUser: SystemUser
loginForm: LoginForm
} }
interface CmdControlParams<T> { interface CmdControlParams<T> {
commandId: string commandId: string
@ -15,6 +16,10 @@ declare namespace System {
interface SystemUser { interface SystemUser {
username: string username: string
} }
interface LoginForm {
username: string
password: string
}
interface ApiResponse<T> { interface ApiResponse<T> {
code: string code: string
data: T data: T

157
src/views/login/index.vue

@ -1,102 +1,113 @@
<script setup lang="ts"> <script setup lang="ts">
import { login } from 'apis/login' import { login } from 'apis/login'
import logo from 'assets/images/logo.svg'
import password from 'assets/images/password_icon.svg'
import user from 'assets/images/user_icon.svg'
import { setToken } from 'libs/token' import { setToken } from 'libs/token'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const progress = ref(0)
let timer: any = null
const version = __APP_VERSION__
const sys = useSystemStore()
const startProgress = () => {
timer = setInterval(() => {
const randomStep = Math.floor(Math.random() * 9 + 1)
const formRef = ref()
progress.value = Math.min(progress.value + randomStep, 100)
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
],
}
if (progress.value >= 100) {
clearInterval(timer)
setToken('111')
useSystemStore().updateSystemUser({ username: '管理员' })
router.push('/')
login({ username: 'admin', password: '12345' }).then((res) => {
setToken('111')
useSystemStore().updateSystemUser({ username: res.username })
router.push('/')
})
const loginHandle = async () => {
formRef.value.validate(async (valid: any) => {
if (!valid) {
return
} }
}, 100)
const res = await login(sys.loginForm)
setToken('login success')
sys.updateSystemUser({ username: res.username })
await router.push('/')
})
} }
onMounted(() => {
startProgress()
})
// login({ username: 'admin', password: '12345' }).then((res: any) => {
// setToken(res)
//
// })
//
onBeforeUnmount(() => {
clearInterval(timer)
})
</script> </script>
<template> <template>
<div>
<!-- 进度条容器 -->
<div class="progress-container">
<div class="progress-bar" :style="{ width: `${progress}%` }" />
<div class="progress-text">
v{{ version }}系统初始化中 {{ progress }}%
<div class="login-box">
<div class="login-box-content">
<img :src="logo" alt="">
<div class="title">
长春黄金研究院有限公司
</div> </div>
<el-form ref="formRef" :model="sys.loginForm" :rules="rules" style="width: 100%">
<div class="input-title">
用户名
</div>
<el-form-item>
<el-input v-model="sys.loginForm.username" size="large" placeholder="请输入用户名">
<template #prepend>
<img class="input-icon" :src="user" alt="">
</template>
</el-input>
</el-form-item>
<div class="input-title">
密码
</div>
<el-form-item>
<el-input v-model="sys.loginForm.password" size="large" placeholder="请输入密码" type="password">
<template #prepend>
<img class="input-icon" :src="password" alt="">
</template>
</el-input>
</el-form-item>
<el-form-item>
<ft-button type="primary" style="width: 100%; margin: 20px 0 0;" :click-handle="loginHandle">
登录
</ft-button>
</el-form-item>
</el-form>
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.login-container {
.login-box {
background: url("assets/images/login.svg") no-repeat center;
display: flex; display: flex;
flex-direction: column;
align-items: center;
justify-content: center; justify-content: center;
height: 100vh;
background: #f0f2f5;
}
img {
width: 600px;
position: absolute;
top: 40%;
right: 20%;
}
.progress-container {
width: 50%;
height: 20px;
background: #e4e7ed;
border-radius: 30px;
position: relative;
top: 90%;
margin: 0 auto;
align-items: center;
.login-box-content {
padding: 50px 50px;
background: linear-gradient(180deg, rgba(250, 252, 255, 0.51) 0%, rgba(255, 255, 255, 0.52) 100%);
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
img {
width: 50px;
}
.title{
color: #8799AB;
font-weight: 500;
font-size: 18px;
margin: 15px 0 30px;
}
}
} }
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #1989FA, #096ae0);
border-radius: 30px;
transition: width 0.3s ease;
.input-icon {
width: 15px !important;
} }
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
.input-title {
font-size: 14px; font-size: 14px;
font-weight: 500;
color: #2C3E59;
margin-bottom: 10px;
} }
</style> </style>
Loading…
Cancel
Save