mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 00:53:54 +08:00
fix: 修复webUI刷新跳转登录页面的问题
This commit is contained in:
+3
-1
@@ -8,4 +8,6 @@ tmp/
|
|||||||
data/
|
data/
|
||||||
LICENSE
|
LICENSE
|
||||||
README.md
|
README.md
|
||||||
README_en.md
|
README_en.md
|
||||||
|
manager/static
|
||||||
|
manager/static/webui/
|
||||||
+6
-4
@@ -1,8 +1,11 @@
|
|||||||
# 第一阶段:前端构建
|
# 第一阶段:前端构建
|
||||||
FROM node:18 as frontend-builder
|
FROM node:18 AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /app/ZhiKongTaiWeb
|
WORKDIR /app/ZhiKongTaiWeb
|
||||||
|
|
||||||
|
# 配置npm使用淘宝源
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
COPY ZhiKongTaiWeb/package*.json ./
|
COPY ZhiKongTaiWeb/package*.json ./
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
@@ -12,7 +15,7 @@ COPY ZhiKongTaiWeb .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# 第二阶段:构建Python依赖
|
# 第二阶段:构建Python依赖
|
||||||
FROM python:3.10-slim as builder
|
FROM python:3.10-slim AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -58,8 +61,7 @@ RUN rm -rf /etc/apt/sources.list.d/* && \
|
|||||||
|
|
||||||
# 从构建阶段复制虚拟环境和前端构建产物
|
# 从构建阶段复制虚拟环境和前端构建产物
|
||||||
COPY --from=builder /opt/venv /opt/venv
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
COPY --from=frontend-builder /app/ZhiKongTaiWeb/dist /opt/xiaozhi-esp32-server/manager/static
|
COPY --from=frontend-builder /app/ZhiKongTaiWeb/dist /opt/xiaozhi-esp32-server/manager/static/webui
|
||||||
|
|
||||||
# 设置虚拟环境路径
|
# 设置虚拟环境路径
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.10"
|
"vue": "^3.5.10",
|
||||||
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
|||||||
@@ -1,64 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="app-content" :class="{ 'auth-layout': !isLoggedIn }">
|
<div class="app-content">
|
||||||
<div v-if="!isLoggedIn">
|
<router-view></router-view>
|
||||||
<Login v-if="!showRegistration" @show-registration="showRegistration = true" @login-success="handleLoginSuccess" />
|
|
||||||
<Registration v-else @show-login="showRegistration = false" />
|
|
||||||
</div>
|
|
||||||
<div v-else class="main-layout">
|
|
||||||
<role-setting v-if="currentView === 'RoleSetting'"
|
|
||||||
:device-id="selectedDeviceId"
|
|
||||||
@back-to-panel="switchView('panel')"/>
|
|
||||||
<panel v-else-if="currentView === 'panel'"
|
|
||||||
@go-home="switchView('main')"
|
|
||||||
@show-role="handleShowRole"
|
|
||||||
/>
|
|
||||||
<main-page v-else @enter-panel="switchView('panel')"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Login from './components/Login.vue';
|
|
||||||
import Registration from './components/Registration.vue';
|
|
||||||
import panel from './components/panel.vue';
|
|
||||||
import MainPage from './components/Main.vue';
|
|
||||||
import RoleSetting from './components/RoleSetting.vue';
|
|
||||||
import Footer from './components/Footer.vue';
|
import Footer from './components/Footer.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
Login,
|
|
||||||
Registration,
|
|
||||||
panel,
|
|
||||||
MainPage,
|
|
||||||
RoleSetting,
|
|
||||||
Footer
|
Footer
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showRegistration: false,
|
|
||||||
isLoggedIn: false,
|
|
||||||
currentView: 'main',
|
|
||||||
selectedDeviceId: null // 添加selectedDeviceId状态
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleLoginSuccess() {
|
|
||||||
this.isLoggedIn = true;
|
|
||||||
this.currentView = 'panel';
|
|
||||||
},
|
|
||||||
switchView(view) {
|
|
||||||
console.log('Switching to view:', view); // 添加调试日志
|
|
||||||
this.currentView = view;
|
|
||||||
},
|
|
||||||
handleShowRole(deviceId) {
|
|
||||||
this.selectedDeviceId = deviceId;
|
|
||||||
this.switchView('RoleSetting');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,148 +12,146 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" :disabled="isLoading">登录</button>
|
<button type="submit" :disabled="isLoading">登录</button>
|
||||||
</form>
|
</form>
|
||||||
<p>还没注册账户? <a href="#" @click.prevent="$emit('show-registration')">点击注册</a></p>
|
<p>还没注册账户? <router-link to="/register">点击注册</router-link></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { API_BASE_URL } from '../config/api';
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
export default {
|
const router = useRouter();
|
||||||
data() {
|
const username = ref('');
|
||||||
return {
|
const password = ref('');
|
||||||
username: '',
|
const isLoading = ref(false);
|
||||||
password: '',
|
|
||||||
isLoading: false
|
const handleLogin = async () => {
|
||||||
};
|
if (!username.value || !password.value) {
|
||||||
},
|
alert('请输入用户名和密码!');
|
||||||
methods: {
|
return;
|
||||||
async handleLogin() {
|
}
|
||||||
if (!this.username || !this.password) {
|
|
||||||
alert('请输入用户名和密码!');
|
isLoading.value = true;
|
||||||
return;
|
try {
|
||||||
}
|
const response = await fetch(`${API_BASE_URL}/api/login`, {
|
||||||
|
method: 'POST',
|
||||||
this.isLoading = true;
|
headers: {
|
||||||
try {
|
'Content-Type': 'application/json',
|
||||||
const response = await fetch(`${API_BASE_URL}/api/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.username,
|
|
||||||
password: this.password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
// 登录成功
|
|
||||||
this.$emit('login-success');
|
|
||||||
} else {
|
|
||||||
// 登录失败
|
|
||||||
alert(data.message || '登录失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login error:', error);
|
|
||||||
alert('登录失败,请检查网络连接');
|
|
||||||
} finally {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
body: JSON.stringify({
|
||||||
};
|
username: username.value,
|
||||||
</script>
|
password: password.value
|
||||||
|
})
|
||||||
<style scoped>
|
});
|
||||||
.login-container {
|
|
||||||
max-width: 420px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 40px 50px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #2c3e50;
|
|
||||||
font-size: 28px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #4a5568;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #f8fafc;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #28a745;
|
|
||||||
box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
background-color: #28a745;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #218838;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
const data = await response.json();
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
if (data.success) {
|
||||||
|
// 存储token和登录状态
|
||||||
|
localStorage.setItem('token', data.token);
|
||||||
|
localStorage.setItem('isLoggedIn', 'true');
|
||||||
|
// 使用路由导航到panel页面
|
||||||
|
router.push('/panel');
|
||||||
|
} else {
|
||||||
|
alert(data.message || '登录失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
alert('登录失败,请检查网络连接');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
p {
|
</script>
|
||||||
margin-top: 20px;
|
|
||||||
color: #666;
|
<style scoped>
|
||||||
font-size: 14px;
|
.login-container {
|
||||||
}
|
max-width: 420px;
|
||||||
|
margin: 20px auto;
|
||||||
a {
|
padding: 40px 50px;
|
||||||
color: #28a745;
|
border: none;
|
||||||
text-decoration: none;
|
border-radius: 12px;
|
||||||
font-weight: 500;
|
background-color: #ffffff;
|
||||||
transition: color 0.3s ease;
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
}
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
a:hover {
|
|
||||||
color: #218838;
|
h1 {
|
||||||
text-decoration: underline;
|
text-align: center;
|
||||||
}
|
color: #2c3e50;
|
||||||
</style>
|
font-size: 28px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #28a745;
|
||||||
|
box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #28a745;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #218838;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,7 +33,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import NavBar from './NavBar.vue';
|
import NavBar from './NavBar.vue';
|
||||||
const emit = defineEmits(['enter-panel']);
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const openTutorial = () => {
|
const openTutorial = () => {
|
||||||
window.open('https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb', '_blank');
|
window.open('https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb', '_blank');
|
||||||
@@ -48,12 +50,12 @@ const openGithubServer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const enterPanel = () => {
|
const enterPanel = () => {
|
||||||
emit('enter-panel');
|
router.push('/panel');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (tab) => {
|
const handleTabChange = (tab) => {
|
||||||
if (tab === 'device') {
|
if (tab === 'device') {
|
||||||
emit('enter-panel');
|
router.push('/panel');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="nav-item" :class="{ active: currentTab === 'home' }" @click="switchTab('home')">小智 AI</div>
|
<div class="nav-item" :class="{ active: $route.path === '/' }" @click="switchTab('/')">小智 AI</div>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<div class="nav-item" :class="{ active: currentTab === 'device' }" @click="switchTab('device')">
|
<div class="nav-item" :class="{ active: $route.path === '/panel' }" @click="switchTab('/panel')">
|
||||||
<i class="icon-device"></i> 设备管理
|
<i class="icon-device"></i> 设备管理
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -10,14 +10,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
currentTab: String
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['tab-change']);
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const switchTab = (tab) => {
|
const switchTab = (path) => {
|
||||||
emit('tab-change', tab);
|
router.push(path);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -18,72 +18,66 @@
|
|||||||
</form>
|
</form>
|
||||||
<p>
|
<p>
|
||||||
已有账号?
|
已有账号?
|
||||||
<a href="#" @click.prevent="$emit('show-login')">点击登录</a>
|
<router-link to="/login">点击登录</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { API_BASE_URL } from '../config/api';
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
export default {
|
const router = useRouter();
|
||||||
data() {
|
const username = ref('');
|
||||||
return {
|
const password = ref('');
|
||||||
username: '',
|
const confirmPassword = ref('');
|
||||||
password: '',
|
const isLoading = ref(false);
|
||||||
confirmPassword: '',
|
|
||||||
isLoading: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async handleRegistration() {
|
|
||||||
if (this.password !== this.confirmPassword) {
|
|
||||||
alert('密码不匹配,请重试!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.username || !this.password) {
|
const handleRegistration = async () => {
|
||||||
alert('用户名和密码不能为空!');
|
if (password.value !== confirmPassword.value) {
|
||||||
return;
|
alert('密码不匹配,请重试!');
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.isLoading = true;
|
if (!username.value || !password.value) {
|
||||||
try {
|
alert('用户名和密码不能为空!');
|
||||||
const response = await fetch(`${API_BASE_URL}/api/register`, {
|
return;
|
||||||
method: 'POST',
|
}
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: this.username,
|
|
||||||
password: this.password
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
if (data.success) {
|
const response = await fetch(`${API_BASE_URL}/api/register`, {
|
||||||
alert('注册成功!即将跳转到登录页面。');
|
method: 'POST',
|
||||||
// 清空表单数据
|
headers: {
|
||||||
this.username = '';
|
'Content-Type': 'application/json',
|
||||||
this.password = '';
|
},
|
||||||
this.confirmPassword = '';
|
body: JSON.stringify({
|
||||||
|
username: username.value,
|
||||||
// 延迟跳转到登录页面
|
password: password.value
|
||||||
setTimeout(() => {
|
})
|
||||||
this.$emit('show-login');
|
});
|
||||||
}, 100);
|
|
||||||
} else {
|
const data = await response.json();
|
||||||
alert(data.message || '注册失败');
|
|
||||||
}
|
if (data.success) {
|
||||||
} catch (error) {
|
alert('注册成功!即将跳转到登录页面。');
|
||||||
console.error('Registration error:', error);
|
// 清空表单数据
|
||||||
alert('注册失败,请检查网络连接');
|
username.value = '';
|
||||||
} finally {
|
password.value = '';
|
||||||
this.isLoading = false;
|
confirmPassword.value = '';
|
||||||
}
|
|
||||||
},
|
// 使用路由导航到登录页面
|
||||||
},
|
router.push('/login');
|
||||||
|
} else {
|
||||||
|
alert(data.message || '注册失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Registration error:', error);
|
||||||
|
alert('注册失败,请检查网络连接');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb">
|
||||||
<router-link to="/control-panel">控制台</router-link> /
|
<router-link to="/">首页</router-link> /
|
||||||
<router-link to="/device-management">设备管理</router-link> /
|
<router-link to="/panel">设备管理</router-link> /
|
||||||
<span>配置角色</span>
|
<span>配置角色</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,12 +107,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import NavBar from './NavBar.vue';
|
import NavBar from './NavBar.vue';
|
||||||
import RoleTemplates from '../utils/RoleTemplates';
|
import RoleTemplates from '../utils/RoleTemplates';
|
||||||
import { API_BASE_URL } from '../config/api';
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
const roleTemplates = RoleTemplates.getTemplates();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const deviceId = ref(route.params.deviceId);
|
||||||
|
|
||||||
|
const roleTemplates = RoleTemplates.getTemplates();
|
||||||
const nickname = ref('小智');
|
const nickname = ref('小智');
|
||||||
const selectedTemplate = ref('');
|
const selectedTemplate = ref('');
|
||||||
const selectedVoice = ref('qingchun');
|
const selectedVoice = ref('qingchun');
|
||||||
@@ -144,30 +148,12 @@ const selectTemplate = (templateId) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const emit = defineEmits(['back-to-panel']);
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
console.log('Triggering back-to-panel event'); // 添加调试日志
|
|
||||||
emit('back-to-panel');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabChange = (tab) => {
|
const handleTabChange = (tab) => {
|
||||||
if (tab === 'device' || tab === 'home') {
|
if (tab === 'device' || tab === 'home') {
|
||||||
emit('back-to-panel');
|
router.push('/panel');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改deviceId的定义,接收props
|
|
||||||
const props = defineProps({
|
|
||||||
deviceId: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 使用props中的deviceId替换原来的静态值
|
|
||||||
const deviceId = ref(props.deviceId);
|
|
||||||
|
|
||||||
const loadModuleOptions = async () => {
|
const loadModuleOptions = async () => {
|
||||||
try {
|
try {
|
||||||
// First try to load from cache
|
// First try to load from cache
|
||||||
@@ -208,7 +194,7 @@ const saveConfig = async () => {
|
|||||||
|
|
||||||
// Prepare the configuration including full module settings
|
// Prepare the configuration including full module settings
|
||||||
const config = {
|
const config = {
|
||||||
id: props.deviceId,
|
id: deviceId.value,
|
||||||
config: {
|
config: {
|
||||||
selected_module: selectedModules.value,
|
selected_module: selectedModules.value,
|
||||||
prompt: processedDescription,
|
prompt: processedDescription,
|
||||||
@@ -233,7 +219,7 @@ const saveConfig = async () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Save the original description and nickname to local storage
|
// Save the original description and nickname to local storage
|
||||||
localStorage.setItem(`deviceConfig_${props.deviceId}`, JSON.stringify({
|
localStorage.setItem(`deviceConfig_${deviceId.value}`, JSON.stringify({
|
||||||
selected_module: selectedModules.value,
|
selected_module: selectedModules.value,
|
||||||
prompt: roleDescription.value,
|
prompt: roleDescription.value,
|
||||||
nickname: nickname.value
|
nickname: nickname.value
|
||||||
@@ -251,7 +237,7 @@ const saveConfig = async () => {
|
|||||||
const loadDeviceConfig = async () => {
|
const loadDeviceConfig = async () => {
|
||||||
try {
|
try {
|
||||||
// First try to get config from local storage
|
// First try to get config from local storage
|
||||||
const localConfig = localStorage.getItem(`deviceConfig_${props.deviceId}`);
|
const localConfig = localStorage.getItem(`deviceConfig_${deviceId.value}`);
|
||||||
if (localConfig) {
|
if (localConfig) {
|
||||||
const config = JSON.parse(localConfig);
|
const config = JSON.parse(localConfig);
|
||||||
selectedModules.value = config.selected_module || {};
|
selectedModules.value = config.selected_module || {};
|
||||||
@@ -264,14 +250,14 @@ const loadDeviceConfig = async () => {
|
|||||||
const response = await fetch(`${baseUrl}/api/config/devices`);
|
const response = await fetch(`${baseUrl}/api/config/devices`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
const deviceConfig = data.data.find(d => d.id === props.deviceId);
|
const deviceConfig = data.data.find(d => d.id === deviceId.value);
|
||||||
if (deviceConfig) {
|
if (deviceConfig) {
|
||||||
selectedModules.value = deviceConfig.config.selected_module || {};
|
selectedModules.value = deviceConfig.config.selected_module || {};
|
||||||
roleDescription.value = deviceConfig.config.prompt || '';
|
roleDescription.value = deviceConfig.config.prompt || '';
|
||||||
nickname.value = deviceConfig.config.nickname || '小智'; // Load nickname from server
|
nickname.value = deviceConfig.config.nickname || '小智'; // Load nickname from server
|
||||||
|
|
||||||
// Save to local storage
|
// Save to local storage
|
||||||
localStorage.setItem(`deviceConfig_${props.deviceId}`, JSON.stringify({
|
localStorage.setItem(`deviceConfig_${deviceId.value}`, JSON.stringify({
|
||||||
selected_module: deviceConfig.config.selected_module,
|
selected_module: deviceConfig.config.selected_module,
|
||||||
prompt: deviceConfig.config.prompt,
|
prompt: deviceConfig.config.prompt,
|
||||||
nickname: deviceConfig.config.nickname
|
nickname: deviceConfig.config.nickname
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<NavBar current-tab="device" @tab-change="handleTabChange"/>
|
<NavBar current-tab="device" @tab-change="handleTabChange"/>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb">
|
||||||
<router-link to="/control-panel">控制台</router-link> /
|
<router-link to="/">首页</router-link> /
|
||||||
<span>设备管理</span>
|
<span>设备管理</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -39,21 +39,20 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import NavBar from './NavBar.vue';
|
import NavBar from './NavBar.vue';
|
||||||
import DeviceCard from './DeviceCard.vue';
|
import DeviceCard from './DeviceCard.vue';
|
||||||
import { API_BASE_URL } from '../config/api';
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
const emit = defineEmits(['show-role', 'go-home']);
|
const router = useRouter();
|
||||||
|
|
||||||
const baseUrl = API_BASE_URL;
|
const baseUrl = API_BASE_URL;
|
||||||
const devices = ref([]);
|
const devices = ref([]);
|
||||||
|
|
||||||
const formatLastActivity = (timestamp) => {
|
const formatLastActivity = (timestamp) => {
|
||||||
console.log('Formatting timestamp:', timestamp);
|
|
||||||
if (!timestamp) return '从未对话';
|
if (!timestamp) return '从未对话';
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const lastChat = timestamp * 1000; // Convert to milliseconds
|
const lastChat = timestamp * 1000;
|
||||||
const diffMinutes = Math.floor((now - lastChat) / (1000 * 60));
|
const diffMinutes = Math.floor((now - lastChat) / (1000 * 60));
|
||||||
|
|
||||||
if (diffMinutes < 60) {
|
if (diffMinutes < 60) {
|
||||||
@@ -69,10 +68,8 @@ const formatLastActivity = (timestamp) => {
|
|||||||
return `${diffDays} 天前`;
|
return `${diffDays} 天前`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
const handleRoleConfig = (device) => {
|
const handleRoleConfig = (device) => {
|
||||||
console.log('Configure device:', device.id);
|
router.push(`/role-setting/${device.id}`);
|
||||||
emit('show-role', device.id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVoiceprint = (device) => {
|
const handleVoiceprint = (device) => {
|
||||||
@@ -108,7 +105,7 @@ const handleDelete = async (device) => {
|
|||||||
|
|
||||||
const handleTabChange = (tab) => {
|
const handleTabChange = (tab) => {
|
||||||
if (tab === 'home') {
|
if (tab === 'home') {
|
||||||
emit('go-home');
|
router.push('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,7 +115,6 @@ onMounted(async () => {
|
|||||||
const response = await fetch(`${baseUrl}/api/config/devices`);
|
const response = await fetch(`${baseUrl}/api/config/devices`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log('Loaded devices:', data.data);
|
|
||||||
devices.value = data.data.map(device => ({
|
devices.value = data.data.map(device => ({
|
||||||
...device,
|
...device,
|
||||||
type: '面包板(WiFi)',
|
type: '面包板(WiFi)',
|
||||||
|
|||||||
@@ -1,5 +1,58 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import Login from './components/Login.vue'
|
||||||
|
import Panel from './components/panel.vue'
|
||||||
|
import MainPage from './components/Main.vue'
|
||||||
|
import RoleSetting from './components/RoleSetting.vue'
|
||||||
|
import Registration from './components/Registration.vue'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: Login,
|
||||||
|
name: 'login'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
component: Registration,
|
||||||
|
name: 'register'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/panel',
|
||||||
|
component: Panel,
|
||||||
|
name: 'panel',
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/role-setting/:deviceId',
|
||||||
|
component: RoleSetting,
|
||||||
|
name: 'role-setting',
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: MainPage,
|
||||||
|
name: 'main',
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'
|
||||||
|
if (to.meta.requiresAuth && !isLoggedIn) {
|
||||||
|
next('/login')
|
||||||
|
} else if (to.path === '/login' && isLoggedIn) {
|
||||||
|
next('/panel')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
open: true, // 自动启动浏览器
|
open: true, // 自动启动浏览器
|
||||||
host: "0.0.0.0", // localhost
|
host: "0.0.0.0", // localhost
|
||||||
port: 8001, // 端口号
|
port: 8002, // 端口号
|
||||||
https: false,
|
https: false,
|
||||||
hmr: {overlay: false},
|
hmr: {overlay: false},
|
||||||
proxy: {
|
proxy: {
|
||||||
@@ -16,5 +16,6 @@ export default defineConfig({
|
|||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
base: '/'
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user