feat: 初始化智能物联网设备管理系统前后端基础项目
完成项目整体结构搭建,包含: 1. 前端Vue3+TS+Vite+Antd项目基础配置与核心依赖 2. 后端MidwayJS+TypeORM+JWT权限认证框架 3. 完整的用户、角色、权限、日志数据库表结构 4. 核心业务模块API与基础服务实现 5. 全局样式、路由、状态管理配置
This commit is contained in:
commit
3d7eb12103
89 files changed
+16085
No files matched your search
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="bg-decoration">
|
||||
<div class="circle circle-1"></div>
|
||||
<div class="circle circle-2"></div>
|
||||
<div class="circle circle-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="login-card" :class="{ 'fade-in': true }">
|
||||
<div class="brand-section">
|
||||
<div class="logo-icon">
|
||||
<ThunderboltOutlined />
|
||||
</div>
|
||||
<h1 class="title">智能电网设备管理系统</h1>
|
||||
<p class="subtitle">Smart Grid IoT Management System</p>
|
||||
</div>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" size="large" @finish="handleLogin">
|
||||
<a-form-item name="username">
|
||||
<a-input ref="usernameInputRef" v-model:value="formState.username" placeholder="请输入用户名" allow-clear
|
||||
:disabled="loading" @pressEnter="focusPassword">
|
||||
<template #prefix>
|
||||
<UserOutlined class="input-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="password">
|
||||
<a-input-password ref="passwordInputRef" v-model:value="formState.password" placeholder="请输入密码" allow-clear
|
||||
:disabled="loading" @pressEnter="submitForm">
|
||||
<template #prefix>
|
||||
<LockOutlined class="input-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="options-row">
|
||||
<a-checkbox v-model:checked="formState.rememberMe" :disabled="loading">
|
||||
记住我
|
||||
</a-checkbox>
|
||||
<a class="forgot-link" href="javascript:void(0)">忘记密码?</a>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" :loading="loading" block class="login-btn">
|
||||
<template #icon v-if="!loading">
|
||||
<span></span>
|
||||
</template>
|
||||
{{ loading ? '登录中...' : '登 录' }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="footer-info">
|
||||
<p>默认账户:admin / admin123456</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="copyright">
|
||||
<p>© 2026 智能电网设备管理系统 All Rights Reserved</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const formRef = ref()
|
||||
const usernameInputRef = ref()
|
||||
const passwordInputRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
rememberMe: false,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '用户名长度为3-20个字符', trigger: 'blur' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度为6-20个字符', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const savedUsername = localStorage.getItem('remembered_username')
|
||||
if (savedUsername) {
|
||||
formState.username = savedUsername
|
||||
formState.rememberMe = true
|
||||
}
|
||||
usernameInputRef.value?.focus()
|
||||
})
|
||||
})
|
||||
|
||||
function focusPassword() {
|
||||
passwordInputRef.value?.focus()
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formRef.value?.submit()
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
await permissionStore.login(formState.username, formState.password)
|
||||
|
||||
if (formState.rememberMe) {
|
||||
localStorage.setItem('remembered_username', formState.username)
|
||||
} else {
|
||||
localStorage.removeItem('remembered_username')
|
||||
}
|
||||
|
||||
message.success('登录成功!')
|
||||
|
||||
setTimeout(() => {
|
||||
const redirect = (route.query.redirect as string) || '/'
|
||||
router.push(redirect)
|
||||
}, 500)
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '登录失败,请检查用户名和密码')
|
||||
formState.password = ''
|
||||
nextTick(() => {
|
||||
passwordInputRef.value?.focus()
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.08;
|
||||
}
|
||||
|
||||
.circle-1 {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
top: -200px;
|
||||
right: -100px;
|
||||
background: #fff;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.circle-2 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
bottom: -150px;
|
||||
left: -100px;
|
||||
background: #fff;
|
||||
animation: float 15s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
.circle-3 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 50%;
|
||||
left: 10%;
|
||||
background: #fff;
|
||||
animation: float 12s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-30px) rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 420px;
|
||||
max-width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: slideUp 0.6s ease-out;
|
||||
|
||||
&.fade-in {
|
||||
animation: slideUp 0.6s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.brand-section {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.logo-icon {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.anticon) {
|
||||
font-size: 48px;
|
||||
color: #1890ff;
|
||||
filter: drop-shadow(0 2px 4px rgba(24, 144, 255, 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
margin: 0 0 8px 0;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.options-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.forgot-link {
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #bbb;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.copyright {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-card {
|
||||
width: 100%;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.brand-section .title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
margin-top: 20px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user