- 重构勘察业务:移除SOP相关表,新增勘察明细表支持建筑/楼层/房间/照片管理 - 新增定时任务人性化频率配置,支持分钟/小时/天/周/月/自定义六种模式 - 新增频率预览接口,支持配置转换为cron表达式与执行时间预览 - 更新系统品牌名称为鑫潼智电,修改全局标题与登录页文案
342 lines
7.2 KiB
Vue
342 lines
7.2 KiB
Vue
<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">XINTONG SMART POWER 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="handlePasswordEnter">
|
||
<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="handleLogin">
|
||
<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 handlePasswordEnter() {
|
||
passwordInputRef.value?.focus()
|
||
}
|
||
|
||
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('登录成功!')
|
||
|
||
const redirect = (route.query.redirect as string) || '/'
|
||
router.push(redirect)
|
||
} catch (error: any) {
|
||
console.error('登录失败:', error)
|
||
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> |