Update .gitignore and add files
This commit is contained in:
52
platform/src/views/HomeView.vue
Normal file
52
platform/src/views/HomeView.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="dashboard-root">
|
||||
<StatCards
|
||||
:user-count="userCount"
|
||||
:device-count="deviceCount"
|
||||
:temperature="temperature"
|
||||
:fault-count="faultCount"
|
||||
/>
|
||||
<ChartSection :current-time="currentTime" />
|
||||
<MapSection />
|
||||
<AISection />
|
||||
<AIAssistantSection />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatCards from '../components/dashboard/StatCards.vue';
|
||||
import ChartSection from '../components/dashboard/ChartSection.vue';
|
||||
import MapSection from '../components/dashboard/MapSection.vue';
|
||||
import AISection from '../components/dashboard/AISection.vue';
|
||||
import AIAssistantSection from '../components/dashboard/AIAssistantSection.vue';
|
||||
const userCount = 0;
|
||||
const deviceCount = 0;
|
||||
const temperature = 0;
|
||||
const faultCount = 0;
|
||||
const currentTime = '';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:root {
|
||||
--dashboard-main-bg: #f5f7fa;
|
||||
--dashboard-card-bg: #fff;
|
||||
--dashboard-radius: 12px;
|
||||
--dashboard-shadow: 0 2px 8px rgba(24,144,255,0.04);
|
||||
--dashboard-primary: #1890ff;
|
||||
}
|
||||
.dashboard-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
padding: 32px 40px;
|
||||
background: var(--dashboard-main-bg);
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.dashboard-root {
|
||||
padding: 16px 8px;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
223
platform/src/views/LoginView.vue
Normal file
223
platform/src/views/LoginView.vue
Normal file
@ -0,0 +1,223 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios, { type AxiosError } from 'axios';
|
||||
|
||||
const router = useRouter();
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const rememberMe = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
// 跳转到注册页面
|
||||
const register = () => {
|
||||
router.push('/register');
|
||||
};
|
||||
|
||||
// 登录功能
|
||||
const login = async () => {
|
||||
try {
|
||||
const response = await axios.post('http://localhost:5000/auth/login', {
|
||||
username: username.value,
|
||||
password: password.value
|
||||
}, {
|
||||
withCredentials: true // 发送跨域请求时携带cookie
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
const { username: userUsername, is_admin } = response.data;
|
||||
|
||||
localStorage.setItem('user', JSON.stringify({
|
||||
username: userUsername,
|
||||
is_admin,
|
||||
token: response.data.token
|
||||
}));
|
||||
|
||||
router.push('/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
if (axiosError.response) {
|
||||
errorMessage.value = (axiosError.response.data as { message?: string })?.message || '登录失败';
|
||||
} else {
|
||||
errorMessage.value = '无法连接到服务器';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="all">
|
||||
<div class="login-top">
|
||||
<img src="../assets/oo.png" alt="" class="fullscreen-bg">
|
||||
<div class="scroll-container">
|
||||
<div class="login-top-box">
|
||||
<h1>禾境智联后台管理系统</h1>
|
||||
<h2>登录</h2>
|
||||
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
<!-- 用户名输入 -->
|
||||
<div class="input-group">
|
||||
<img src="../assets/人像.png" alt="" class="input-icon">
|
||||
<input v-model="username" type="text" placeholder="请输入账号" class="input-field">
|
||||
</div>
|
||||
<!-- 密码输入 -->
|
||||
<div class="input-group">
|
||||
<img src="../assets/suo.png" alt="" class="input-icon">
|
||||
<input v-model="password" type="password" placeholder="请输入密码" class="input-field">
|
||||
</div>
|
||||
<button @click="login" class="login-button">登录</button>
|
||||
<p class="login-p" style="padding-left:5px" @click.prevent="register">没有帐户?去注册</p>
|
||||
<p class="login-p1" style="padding-left:5px">
|
||||
<input type="checkbox" v-model="rememberMe" id="remember">
|
||||
<label for="remember">记住密码</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 全局重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#all {
|
||||
min-height: 98vh;
|
||||
min-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
overflow: hidden;
|
||||
height: 80vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-top {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullscreen-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-top-box {
|
||||
width: 500px;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
padding-top: 90px;
|
||||
padding-left: 105px;
|
||||
color: #11659a;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 17px;
|
||||
|
||||
}
|
||||
|
||||
.login-top-box h2 {
|
||||
padding-top: 85px;
|
||||
font-size: 30px;
|
||||
font-weight: bolder;
|
||||
color: #3c3c3c;
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* ========== 图标与输入框定位关键样式 ========== */
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
height: 50px; /* 与 input 高度一致 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 17px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 21px;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 340px;
|
||||
padding-left: 50px;
|
||||
height: 50px;
|
||||
border-radius: 23px;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid transparent;
|
||||
font-size: 17px;
|
||||
color: #3c3c3c;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* 均匀的渐变蓝色阴影(四周均匀分布) */
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 8px rgba(0, 123, 255, 0.2);
|
||||
border-color: rgba(5, 90, 186, 0.3);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.login-button {
|
||||
width: 125px;
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
background: linear-gradient(to right, #055aba, #007bff);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
margin-top: 7px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 10px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.login-p {
|
||||
font-size: 17px;
|
||||
padding-top: 40px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.login-p:hover {
|
||||
color: #007bff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.login-p1 {
|
||||
position: relative;
|
||||
top: -94px;
|
||||
left: 244px;
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
293
platform/src/views/RegisterView.vue
Normal file
293
platform/src/views/RegisterView.vue
Normal file
@ -0,0 +1,293 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import axios, { type AxiosError } from 'axios'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const verificationCode = ref('')
|
||||
const errorMessage = ref('')
|
||||
|
||||
let countdownInterval: number | undefined;
|
||||
|
||||
const login = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const sendVerificationCode = async () => {
|
||||
if (!username.value) {
|
||||
errorMessage.value = "请输入邮箱"
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post('http://localhost:5000/captcha/email', {
|
||||
email: username.value
|
||||
})
|
||||
|
||||
let timeLeft = 60
|
||||
const button = document.querySelector('.get-code-button') as HTMLButtonElement
|
||||
|
||||
if (button) {
|
||||
button.disabled = true
|
||||
button.textContent = `${timeLeft}s 后重试`
|
||||
|
||||
if (countdownInterval !== undefined) {
|
||||
clearInterval(countdownInterval)
|
||||
}
|
||||
|
||||
countdownInterval = window.setInterval(() => {
|
||||
timeLeft--
|
||||
button.textContent = `${timeLeft}s 后重试`
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(countdownInterval)
|
||||
countdownInterval = undefined
|
||||
button.disabled = false
|
||||
button.textContent = "获取验证码"
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
alert('验证码已发送,请检查您的邮箱')
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError
|
||||
if (axiosError.response) {
|
||||
const data = axiosError.response.data as { message?: string }
|
||||
errorMessage.value = data.message || '发送验证码失败'
|
||||
} else {
|
||||
errorMessage.value = '无法连接到服务器,请确保后端正在运行。'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const register = async () => {
|
||||
if (!verificationCode.value) {
|
||||
errorMessage.value = "请输入验证码"
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('http://localhost:5000/register', {
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
code: verificationCode.value
|
||||
})
|
||||
|
||||
if (response.status === 201) {
|
||||
alert('注册成功!即将跳转到登录页面')
|
||||
router.push('/login')
|
||||
}
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError
|
||||
if (axiosError.response) {
|
||||
const data = axiosError.response.data as { message?: string }
|
||||
errorMessage.value = data.message || '注册失败,请检查输入内容'
|
||||
} else {
|
||||
errorMessage.value = '无法连接到服务器,请确保后端正在运行。'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="all">
|
||||
<div class="login-top">
|
||||
<img src="../assets/oo.png" alt="" class="fullscreen-bg">
|
||||
<div class="scroll-container">
|
||||
<div class="login-top-box">
|
||||
<h1>禾境智联后台管理系统</h1>
|
||||
<h2>注册</h2>
|
||||
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
||||
<!-- 用户名输入 -->
|
||||
<div class="input-group">
|
||||
<img src="../assets/信件.png" alt="" class="input-icon">
|
||||
<input v-model="username" type="text" placeholder="请输入邮箱" class="reinput-field">
|
||||
</div>
|
||||
<!-- 密码输入 -->
|
||||
<div class="input-group">
|
||||
<img src="../assets/suo.png" alt="" class="input-icon">
|
||||
<input v-model="password" type="password" placeholder="请输入密码" class="reinput-field">
|
||||
</div>
|
||||
<!-- 验证码输入 -->
|
||||
<div class="input-group code-input-group">
|
||||
<img src="../assets/验证码.png" alt="" class="input-icon">
|
||||
<input type="text" v-model="verificationCode" placeholder="短信验证码" class="reinput-field01">
|
||||
<button class="get-code-button" @click.prevent="sendVerificationCode">获取验证码</button>
|
||||
</div>
|
||||
<button @click="register" class="login-button">注册</button>
|
||||
<p class="login-p" style="padding-left:5px" @click.prevent="login">已有帐户?去登录</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.scroll-container {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
#all {
|
||||
min-height: 98vh;
|
||||
min-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.login-top {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullscreen-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 登录盒子样式 */
|
||||
.login-top-box {
|
||||
width: 500px;
|
||||
position: relative;
|
||||
z-index: 100000;
|
||||
padding-top: 90px;
|
||||
padding-left: 105px;
|
||||
color: #11659a;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: 17px;
|
||||
|
||||
}
|
||||
.login-top-box h2{
|
||||
padding-top: 85px;
|
||||
font-size: 30px;
|
||||
font-weight: bolder;
|
||||
color: #3c3c3c;
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* ========== 图标与输入框定位关键样式 ========== */
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
width: 344px;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 19px;
|
||||
top: 65%;
|
||||
transform: translateY(-50%);
|
||||
width: 22px;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.reinput-field,
|
||||
.reinput-field01 {
|
||||
width: 340px;
|
||||
height: 50px;
|
||||
border-radius: 23px;
|
||||
margin-top: 10px;
|
||||
font-size: 17px;
|
||||
color: #3c3c3c;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid transparent;
|
||||
padding-left: 50px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* 添加均匀的聚焦阴影效果 */
|
||||
.reinput-field:focus,
|
||||
.reinput-field01:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 8px rgba(0, 123, 255, 0.2);
|
||||
border-color: rgba(5, 90, 186, 0.3);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.code-input-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.get-code-button {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 69%;
|
||||
transform: translateY(-50%);
|
||||
width: 100px;
|
||||
height: 48px;
|
||||
background: linear-gradient(to right, #055aba, #007bff);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
z-index: 10;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.get-code-button:hover {
|
||||
transform: translateY(-50%) scale(1.02);
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.get-code-button:disabled {
|
||||
background: #ccc;
|
||||
transform: translateY(-50%) scale(1);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.reinput-field01 {
|
||||
padding-right: 100px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 338px;
|
||||
height: 42px;
|
||||
padding: 10px;
|
||||
background: linear-gradient(to right, #055aba, #007bff);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
margin-top: 22px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 10px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.login-p {
|
||||
font-size: 17px;
|
||||
padding-top: 20px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.login-p:hover {
|
||||
color: #007bff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user