mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
fix: 修复webUI刷新跳转登录页面的问题
This commit is contained in:
+3
-1
@@ -8,4 +8,6 @@ tmp/
|
||||
data/
|
||||
LICENSE
|
||||
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
|
||||
|
||||
# 配置npm使用淘宝源
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
COPY ZhiKongTaiWeb/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
@@ -12,7 +15,7 @@ COPY ZhiKongTaiWeb .
|
||||
RUN npm run build
|
||||
|
||||
# 第二阶段:构建Python依赖
|
||||
FROM python:3.10-slim as builder
|
||||
FROM python:3.10-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -58,8 +61,7 @@ RUN rm -rf /etc/apt/sources.list.d/* && \
|
||||
|
||||
# 从构建阶段复制虚拟环境和前端构建产物
|
||||
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"
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.10"
|
||||
"vue": "^3.5.10",
|
||||
"vue-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
|
||||
@@ -1,64 +1,19 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div class="app-content" :class="{ 'auth-layout': !isLoggedIn }">
|
||||
<div v-if="!isLoggedIn">
|
||||
<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 class="app-content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Login,
|
||||
Registration,
|
||||
panel,
|
||||
MainPage,
|
||||
RoleSetting,
|
||||
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>
|
||||
|
||||
@@ -12,148 +12,146 @@
|
||||
</div>
|
||||
<button type="submit" :disabled="isLoading">登录</button>
|
||||
</form>
|
||||
<p>还没注册账户? <a href="#" @click.prevent="$emit('show-registration')">点击注册</a></p>
|
||||
<p>还没注册账户? <router-link to="/register">点击注册</router-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { API_BASE_URL } from '../config/api';
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { API_BASE_URL } from '../config/api';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async handleLogin() {
|
||||
if (!this.username || !this.password) {
|
||||
alert('请输入用户名和密码!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
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;
|
||||
}
|
||||
const router = useRouter();
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const isLoading = ref(false);
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!username.value || !password.value) {
|
||||
alert('请输入用户名和密码!');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<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);
|
||||
}
|
||||
body: JSON.stringify({
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
});
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
const data = await response.json();
|
||||
|
||||
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 {
|
||||
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>
|
||||
};
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
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>
|
||||
import NavBar from './NavBar.vue';
|
||||
const emit = defineEmits(['enter-panel']);
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const openTutorial = () => {
|
||||
window.open('https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb', '_blank');
|
||||
@@ -48,12 +50,12 @@ const openGithubServer = () => {
|
||||
};
|
||||
|
||||
const enterPanel = () => {
|
||||
emit('enter-panel');
|
||||
router.push('/panel');
|
||||
};
|
||||
|
||||
const handleTabChange = (tab) => {
|
||||
if (tab === 'device') {
|
||||
emit('enter-panel');
|
||||
router.push('/panel');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<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">
|
||||
<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> 设备管理
|
||||
</div>
|
||||
</nav>
|
||||
@@ -10,14 +10,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
currentTab: String
|
||||
});
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
|
||||
const emit = defineEmits(['tab-change']);
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const switchTab = (tab) => {
|
||||
emit('tab-change', tab);
|
||||
const switchTab = (path) => {
|
||||
router.push(path);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,72 +18,66 @@
|
||||
</form>
|
||||
<p>
|
||||
已有账号?
|
||||
<a href="#" @click.prevent="$emit('show-login')">点击登录</a>
|
||||
<router-link to="/login">点击登录</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { API_BASE_URL } from '../config/api';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async handleRegistration() {
|
||||
if (this.password !== this.confirmPassword) {
|
||||
alert('密码不匹配,请重试!');
|
||||
return;
|
||||
}
|
||||
const router = useRouter();
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
const isLoading = ref(false);
|
||||
|
||||
if (!this.username || !this.password) {
|
||||
alert('用户名和密码不能为空!');
|
||||
return;
|
||||
}
|
||||
const handleRegistration = async () => {
|
||||
if (password.value !== confirmPassword.value) {
|
||||
alert('密码不匹配,请重试!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: this.username,
|
||||
password: this.password
|
||||
})
|
||||
});
|
||||
if (!username.value || !password.value) {
|
||||
alert('用户名和密码不能为空!');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('注册成功!即将跳转到登录页面。');
|
||||
// 清空表单数据
|
||||
this.username = '';
|
||||
this.password = '';
|
||||
this.confirmPassword = '';
|
||||
|
||||
// 延迟跳转到登录页面
|
||||
setTimeout(() => {
|
||||
this.$emit('show-login');
|
||||
}, 100);
|
||||
} else {
|
||||
alert(data.message || '注册失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
alert('注册失败,请检查网络连接');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('注册成功!即将跳转到登录页面。');
|
||||
// 清空表单数据
|
||||
username.value = '';
|
||||
password.value = '';
|
||||
confirmPassword.value = '';
|
||||
|
||||
// 使用路由导航到登录页面
|
||||
router.push('/login');
|
||||
} else {
|
||||
alert(data.message || '注册失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
alert('注册失败,请检查网络连接');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumb">
|
||||
<router-link to="/control-panel">控制台</router-link> /
|
||||
<router-link to="/device-management">设备管理</router-link> /
|
||||
<router-link to="/">首页</router-link> /
|
||||
<router-link to="/panel">设备管理</router-link> /
|
||||
<span>配置角色</span>
|
||||
</div>
|
||||
|
||||
@@ -107,12 +107,16 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import NavBar from './NavBar.vue';
|
||||
import RoleTemplates from '../utils/RoleTemplates';
|
||||
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 selectedTemplate = ref('');
|
||||
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) => {
|
||||
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 () => {
|
||||
try {
|
||||
// First try to load from cache
|
||||
@@ -208,7 +194,7 @@ const saveConfig = async () => {
|
||||
|
||||
// Prepare the configuration including full module settings
|
||||
const config = {
|
||||
id: props.deviceId,
|
||||
id: deviceId.value,
|
||||
config: {
|
||||
selected_module: selectedModules.value,
|
||||
prompt: processedDescription,
|
||||
@@ -233,7 +219,7 @@ const saveConfig = async () => {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
// 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,
|
||||
prompt: roleDescription.value,
|
||||
nickname: nickname.value
|
||||
@@ -251,7 +237,7 @@ const saveConfig = async () => {
|
||||
const loadDeviceConfig = async () => {
|
||||
try {
|
||||
// First try to get config from local storage
|
||||
const localConfig = localStorage.getItem(`deviceConfig_${props.deviceId}`);
|
||||
const localConfig = localStorage.getItem(`deviceConfig_${deviceId.value}`);
|
||||
if (localConfig) {
|
||||
const config = JSON.parse(localConfig);
|
||||
selectedModules.value = config.selected_module || {};
|
||||
@@ -264,14 +250,14 @@ const loadDeviceConfig = async () => {
|
||||
const response = await fetch(`${baseUrl}/api/config/devices`);
|
||||
const data = await response.json();
|
||||
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) {
|
||||
selectedModules.value = deviceConfig.config.selected_module || {};
|
||||
roleDescription.value = deviceConfig.config.prompt || '';
|
||||
nickname.value = deviceConfig.config.nickname || '小智'; // Load nickname from server
|
||||
|
||||
// Save to local storage
|
||||
localStorage.setItem(`deviceConfig_${props.deviceId}`, JSON.stringify({
|
||||
localStorage.setItem(`deviceConfig_${deviceId.value}`, JSON.stringify({
|
||||
selected_module: deviceConfig.config.selected_module,
|
||||
prompt: deviceConfig.config.prompt,
|
||||
nickname: deviceConfig.config.nickname
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<NavBar current-tab="device" @tab-change="handleTabChange"/>
|
||||
<main class="content">
|
||||
<div class="breadcrumb">
|
||||
<router-link to="/control-panel">控制台</router-link> /
|
||||
<router-link to="/">首页</router-link> /
|
||||
<span>设备管理</span>
|
||||
</div>
|
||||
|
||||
@@ -39,21 +39,20 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import NavBar from './NavBar.vue';
|
||||
import DeviceCard from './DeviceCard.vue';
|
||||
import { API_BASE_URL } from '../config/api';
|
||||
|
||||
const emit = defineEmits(['show-role', 'go-home']);
|
||||
|
||||
const router = useRouter();
|
||||
const baseUrl = API_BASE_URL;
|
||||
const devices = ref([]);
|
||||
|
||||
const formatLastActivity = (timestamp) => {
|
||||
console.log('Formatting timestamp:', timestamp);
|
||||
if (!timestamp) return '从未对话';
|
||||
|
||||
const now = Date.now();
|
||||
const lastChat = timestamp * 1000; // Convert to milliseconds
|
||||
const lastChat = timestamp * 1000;
|
||||
const diffMinutes = Math.floor((now - lastChat) / (1000 * 60));
|
||||
|
||||
if (diffMinutes < 60) {
|
||||
@@ -69,10 +68,8 @@ const formatLastActivity = (timestamp) => {
|
||||
return `${diffDays} 天前`;
|
||||
};
|
||||
|
||||
// Event handlers
|
||||
const handleRoleConfig = (device) => {
|
||||
console.log('Configure device:', device.id);
|
||||
emit('show-role', device.id);
|
||||
router.push(`/role-setting/${device.id}`);
|
||||
};
|
||||
|
||||
const handleVoiceprint = (device) => {
|
||||
@@ -108,7 +105,7 @@ const handleDelete = async (device) => {
|
||||
|
||||
const handleTabChange = (tab) => {
|
||||
if (tab === 'home') {
|
||||
emit('go-home');
|
||||
router.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,7 +115,6 @@ onMounted(async () => {
|
||||
const response = await fetch(`${baseUrl}/api/config/devices`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
console.log('Loaded devices:', data.data);
|
||||
devices.value = data.data.map(device => ({
|
||||
...device,
|
||||
type: '面包板(WiFi)',
|
||||
|
||||
@@ -1,5 +1,58 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import './style.css'
|
||||
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: {
|
||||
open: true, // 自动启动浏览器
|
||||
host: "0.0.0.0", // localhost
|
||||
port: 8001, // 端口号
|
||||
port: 8002, // 端口号
|
||||
https: false,
|
||||
hmr: {overlay: false},
|
||||
proxy: {
|
||||
@@ -16,5 +16,6 @@ export default defineConfig({
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
base: '/'
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user