mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update:优化页面样式
This commit is contained in:
+2
-1
@@ -21,7 +21,8 @@ manager:
|
||||
enabled: false
|
||||
ip: 0.0.0.0
|
||||
port: 8001
|
||||
token: password
|
||||
# 请把密码设置10位数,且不能包含xiaozhi这几个字符
|
||||
password: 123456
|
||||
|
||||
xiaozhi:
|
||||
type: hello
|
||||
|
||||
+41
-11
@@ -1,35 +1,61 @@
|
||||
import logging
|
||||
import os
|
||||
from aiohttp import web
|
||||
from core.utils.util import get_local_ip, get_project_dir
|
||||
from manager.api.prompt import PromptHandler
|
||||
from core.utils.util import get_local_ip, get_project_dir, check_password
|
||||
from manager.api.prompt import PromptApi
|
||||
from manager.api.auth import AuthApi
|
||||
from aiohttp.web_middlewares import middleware
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_options(request):
|
||||
headers = {
|
||||
@middleware
|
||||
async def cors_middleware(request, handler):
|
||||
# 预检请求处理
|
||||
if request.method == 'OPTIONS':
|
||||
return web.Response(
|
||||
status=204,
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Token',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
response = await handler(request)
|
||||
except web.HTTPException as ex:
|
||||
response = ex
|
||||
|
||||
# 添加CORS头到所有响应
|
||||
cors_headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
'Access-Control-Expose-Headers': 'Content-Type, Authorization, Token',
|
||||
'Vary': 'Origin' # 避免缓存问题
|
||||
}
|
||||
return web.Response(headers=headers)
|
||||
response.headers.update(cors_headers)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class ConfigServer:
|
||||
def __init__(self, config: dict):
|
||||
self.config = config
|
||||
self.app = web.Application()
|
||||
self.app = web.Application(middlewares=[cors_middleware]) # 注册中间件
|
||||
|
||||
# 初始化接口处理器
|
||||
self.prompt_handler = PromptHandler(config)
|
||||
self.prompt_handler = PromptApi(config)
|
||||
self.auth_handler = AuthApi(config)
|
||||
self.setup_routes()
|
||||
|
||||
def setup_routes(self):
|
||||
# 注册prompt接口
|
||||
self.app.router.add_get('/api/prompt', self.prompt_handler.get_prompt)
|
||||
self.app.router.add_post('/api/prompt', self.prompt_handler.update_prompt)
|
||||
self.app.router.add_options('/api/prompt', handle_options)
|
||||
|
||||
# 注册auth接口
|
||||
self.app.router.add_post('/api/login', self.auth_handler.login)
|
||||
|
||||
# 注册静态文件路由
|
||||
static_dir = os.path.join(get_project_dir(), 'manager/static') # 获取static目录绝对路径
|
||||
@@ -49,13 +75,17 @@ class ConfigServer:
|
||||
if not http_config.get('enabled', False):
|
||||
logger.info("HTTP server is disabled")
|
||||
return
|
||||
token = self.config['manager']['token']
|
||||
if not check_password(token):
|
||||
logger.info("您设置的后台密码太弱了,启动后台管理失败!")
|
||||
return
|
||||
|
||||
runner = web.AppRunner(self.app)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, http_config['ip'], http_config['port'])
|
||||
await site.start()
|
||||
logger.info(
|
||||
f"Config HTTP server is running at http://{get_local_ip()}:{http_config['port']}/manager/index.html")
|
||||
f"Config HTTP server is running at http://{get_local_ip()}:{http_config['port']}/manager/login.html")
|
||||
return runner # 返回runner以便后续清理
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start HTTP server: {e}")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import yaml
|
||||
import socket
|
||||
@@ -91,3 +92,32 @@ def remove_punctuation_and_length(text):
|
||||
if result == "Yeah":
|
||||
return 0
|
||||
return len(result), result
|
||||
|
||||
|
||||
def check_password(password):
|
||||
"""
|
||||
检查密码是否满足以下条件:
|
||||
1. 密码长度大于八位。
|
||||
2. 密码包含英文和数字。
|
||||
3. 密码不能包含“xiaozhi”字符。
|
||||
|
||||
:param password: 要检查的密码
|
||||
:return: 如果密码满足条件,则返回True;否则返回False。
|
||||
"""
|
||||
# 检查密码长度
|
||||
if len(password) < 10:
|
||||
return False
|
||||
|
||||
# 检查是否包含英文字符和数字
|
||||
if not re.search(r'[A-Za-z]', password) or not re.search(r'[0-9]', password):
|
||||
return False
|
||||
|
||||
# 检查是否包含“xiaozhi”字符
|
||||
if "xiaozhi" in password:
|
||||
return False
|
||||
|
||||
if "123456" in password:
|
||||
return False
|
||||
|
||||
# 如果满足所有条件,则返回True
|
||||
return True
|
||||
+21
-1
@@ -1,10 +1,30 @@
|
||||
import uuid
|
||||
from manager.api.response import response_success, response_error
|
||||
|
||||
|
||||
async def verify_token(config, request):
|
||||
if 'token' not in config['manager']:
|
||||
return True
|
||||
|
||||
expected_token = config['manager']['token']
|
||||
token = request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||
|
||||
if not token or token != expected_token:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class AuthApi:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
async def login(self, request):
|
||||
try:
|
||||
data = await request.json()
|
||||
if 'password' not in data:
|
||||
return response_error("密码不能为空")
|
||||
# 通过config参数回传修改能力
|
||||
if self.config['manager']['token'] == data['password']:
|
||||
return response_success(data=str(uuid.uuid4()))
|
||||
return response_error("密码不正确")
|
||||
except Exception as e:
|
||||
return response_error(str(e))
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import logging
|
||||
from aiohttp import web
|
||||
from manager.api.auth import verify_token
|
||||
from manager.api.response import response_unauthorized, response_success, response_error
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PromptHandler:
|
||||
class PromptApi:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
async def get_prompt(self, request):
|
||||
if not await verify_token(self.config, request):
|
||||
return web.json_response({'error': 'Unauthorized'}, status=401)
|
||||
return response_unauthorized()
|
||||
|
||||
return web.json_response({
|
||||
'prompt': self.config['prompt'],
|
||||
@@ -20,17 +21,16 @@ class PromptHandler:
|
||||
|
||||
async def update_prompt(self, request):
|
||||
if not await verify_token(self.config, request):
|
||||
return web.json_response({'error': 'Unauthorized'}, status=401)
|
||||
return response_unauthorized()
|
||||
|
||||
try:
|
||||
data = await request.json()
|
||||
if 'prompt' not in data:
|
||||
return web.json_response({'error': 'Missing prompt field'}, status=400)
|
||||
return response_success()
|
||||
|
||||
# 通过config参数回传修改能力
|
||||
self.config['prompt'] = data['prompt']
|
||||
return web.json_response({'success': True}, headers={'Access-Control-Allow-Origin': '*'})
|
||||
return response_success()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update prompt: {e}")
|
||||
return web.json_response({'error': str(e)}, status=500)
|
||||
return response_error(str(e))
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from aiohttp import web
|
||||
from typing import Optional, Dict, Any # 导入Optional用于表示可选类型
|
||||
|
||||
|
||||
def response_error(msg):
|
||||
return web.json_response({"code": -1, 'msg': msg}, status=200)
|
||||
|
||||
|
||||
def response_success(msg: str = '', data: Optional[Any] = None):
|
||||
if data is None:
|
||||
data = {}
|
||||
return web.json_response({"code": 0, 'msg': msg, 'data': data}, status=200)
|
||||
|
||||
|
||||
def response_unauthorized():
|
||||
return web.json_response({"code": 401}, status=200)
|
||||
File diff suppressed because one or more lines are too long
+185
-51
@@ -1,53 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>小智提示词配置</title>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { max-width: 800px; margin: 20px auto; padding: 0 20px; font-family: Arial, sans-serif; }
|
||||
textarea { width: 100%; height: 300px; margin: 20px 0; padding: 10px; }
|
||||
button { padding: 10px 20px; font-size: 16px; }
|
||||
.success { color: green; }
|
||||
.error { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>小智提示词配置</h1>
|
||||
<textarea id="prompt"></textarea>
|
||||
<div>
|
||||
<button onclick="updatePrompt()">更新提示词</button>
|
||||
<span id="status"></span>
|
||||
</div>
|
||||
<script>
|
||||
// 加载当前prompt
|
||||
fetch('/api/prompt')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('prompt').value = data.prompt;
|
||||
});
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<script src="js/vue3.5.13.js"></script>
|
||||
<link rel="stylesheet" href="css/element-plus2.9.4.css">
|
||||
<script src="js/element-plus2.9.4.js"></script>
|
||||
<script src="js/icons-vue2.3.1.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<title>小智-server</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #1a1f25 0%, #0d1117 100%);
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
function updatePrompt() {
|
||||
const prompt = document.getElementById('prompt').value;
|
||||
fetch('/api/prompt', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({prompt: prompt})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const status = document.getElementById('status');
|
||||
if(data.success) {
|
||||
status.textContent = '更新成功!';
|
||||
status.className = 'success';
|
||||
} else {
|
||||
status.textContent = '更新失败: ' + data.error;
|
||||
status.className = 'error';
|
||||
}
|
||||
setTimeout(() => status.textContent = '', 3000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
.header {
|
||||
background: rgba(13, 17, 23, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
color: #fff;
|
||||
padding: 1.5rem 2rem;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(45deg, #00c6fb 0%, #005bea 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 2rem;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.role-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.role-id {
|
||||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.role-description {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
text-align: right;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.note {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 13px;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="animated-bg"></div>
|
||||
<div class="header">
|
||||
<div class="header-logo">AI</div>
|
||||
xiaozhi-esp32-server
|
||||
</div>
|
||||
|
||||
<div class="content-container">
|
||||
<div class="role-title">配置角色:</div>
|
||||
<div class="role-id">cc:ba:97:11:a6:ac</div>
|
||||
|
||||
<div class="role-description">角色介绍</div>
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="form.prompt"
|
||||
:rows="6"
|
||||
resize="vertical"
|
||||
@input="updateCharCount"
|
||||
></el-input>
|
||||
<div class="character-count">{{ charCount }} / 2000</div>
|
||||
|
||||
<div class="button-group">
|
||||
<el-button type="primary" @click="savePrompt">保存配置</el-button>
|
||||
<el-button @click="resetPrompt">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="note">注意:保存配置后,需要重启设备,新的配置才会生效。</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
© 2024 小智 AI 控制面板 2.0 粤ICP备2022121736号-2
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const {ElMessage} = ElementPlus;
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
prompt: ''
|
||||
},
|
||||
charCount: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
loadPrompt() {
|
||||
let that = this;
|
||||
that.form.prompt = '';
|
||||
get('/api/prompt', function (data) {
|
||||
if (data.code === -1) {
|
||||
ElMessage({
|
||||
message: data.msg,
|
||||
type: 'error'
|
||||
});
|
||||
} else {
|
||||
that.form.prompt = data.prompt;
|
||||
that.updateCharCount();
|
||||
}
|
||||
})
|
||||
},
|
||||
updateCharCount() {
|
||||
this.charCount = this.form.prompt.length;
|
||||
},
|
||||
savePrompt() {
|
||||
post('/api/prompt', {prompt: this.form.prompt}, (data) => {
|
||||
if (data.code === 0) {
|
||||
ElMessage({
|
||||
message: '保存成功',
|
||||
type: 'success'
|
||||
});
|
||||
this.loadPrompt();
|
||||
} else {
|
||||
ElMessage({
|
||||
message: data.msg || '保存失败',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadPrompt();
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(App);
|
||||
app.use(ElementPlus);
|
||||
app.mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
function post(api, bodyData, fn) {
|
||||
let basePath = 'http://localhost:8001';
|
||||
let token = localStorage.getItem('token');
|
||||
fetch(basePath + api, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token,
|
||||
},
|
||||
body: bodyData==null ? null : JSON.stringify(bodyData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if(data.code === 401){
|
||||
window.location = 'login.html';
|
||||
return
|
||||
}
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
|
||||
function get(api, fn) {
|
||||
let basePath = 'http://localhost:8001';
|
||||
let token = localStorage.getItem('token');
|
||||
fetch(basePath + api, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token,
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if(data.code === 401){
|
||||
window.location = 'login.html';
|
||||
return
|
||||
}
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+196
-3
@@ -1,13 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>login</title>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<script src="js/vue3.5.13.js"></script>
|
||||
<link rel="stylesheet" href="css/element-plus2.9.4.css">
|
||||
<script src="js/element-plus2.9.4.js"></script>
|
||||
<script src="js/icons-vue2.3.1.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<title>小智-server</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #1a1f25 0%, #0d1117 100%);
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(13, 17, 23, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
color: #fff;
|
||||
padding: 1.5rem 2rem;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(45deg, #00c6fb 0%, #005bea 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 440px;
|
||||
margin: 60px auto;
|
||||
padding: 2.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
margin-bottom: 40px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.custom-input :deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.custom-input :deep(.el-input__inner) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
background: linear-gradient(45deg, #00c6fb 0%, #005bea 100%) !important;
|
||||
border: none !important;
|
||||
height: 44px !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: 500 !important;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.custom-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 198, 251, 0.3) !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 13px;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
}
|
||||
|
||||
.animated-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background: linear-gradient(-45deg, #1a1f25, #0d1117, #162030, #1c1c1c);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
login
|
||||
<div id="app">
|
||||
<div class="animated-bg"></div>
|
||||
<div class="header">
|
||||
<div class="header-logo">AI</div>
|
||||
xiaozhi-esp32-server
|
||||
</div>
|
||||
<div class="login-container">
|
||||
<h2 class="login-title">安全验证</h2>
|
||||
<div class="login-form">
|
||||
<el-form :model="form" size="large">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
class="custom-input"
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入访问密码"
|
||||
show-password>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="custom-button" type="primary" style="width: 100%" @click="handleLogin">
|
||||
验证并进入系统
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© 2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const {ElMessage} = ElementPlus;
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
password: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleLogin() {
|
||||
let that = this;
|
||||
post('/api/login', that.form, function (data) {
|
||||
if (data.code === -1) {
|
||||
// 弹出错误提示
|
||||
ElMessage({
|
||||
message: '登录失败,请检查您的密码',
|
||||
type: 'error'
|
||||
});
|
||||
localStorage.removeItem('token');
|
||||
} else {
|
||||
// 登录成功时的处理逻辑
|
||||
ElMessage({
|
||||
message: '登录成功',
|
||||
type: 'success'
|
||||
});
|
||||
localStorage.setItem('token', that.form.password); // 设置登录状态为已登录
|
||||
location.href = 'index.html'; // 跳转到主页
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 在页面加载时自动调用 handleLogin 方法
|
||||
let token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
this.form.password = token;
|
||||
this.handleLogin();
|
||||
}
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(App);
|
||||
app.use(ElementPlus);
|
||||
app.mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user