This commit is contained in:
JayJiaJun 2025-02-08 15:27:48 +08:00
commit 042ea75847
31 changed files with 9150 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer"
]
}

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# change
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
npm run test:unit
```

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>可变车道管理系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

9
jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"]
}

7346
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "change",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --open",
"build": "vite build",
"preview": "vite preview",
"test:unit": "vitest"
},
"dependencies": {
"axios": "^1.7.9",
"element-plus": "^2.9.2",
"pinia": "^2.3.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/test-utils": "^2.4.6",
"code-inspector-plugin": "^0.19.2",
"compression-webpack-plugin": "^11.1.0",
"jsdom": "^25.0.1",
"unplugin-auto-import": "^19.0.0",
"unplugin-vue-components": "^28.0.0",
"vite": "^6.0.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "^7.6.8",
"vitest": "^2.1.8"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

14
src/App.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<router-view />
</template>
<script>
export default {
name: 'App',
//
};
</script>
<style>
/* 全局样式 */
</style>

86
src/assets/base.css Normal file
View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

1
src/assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

41
src/assets/main.css Normal file
View File

@ -0,0 +1,41 @@
@import './base.css';
#app {
max-width: 2000px;
/* margin: 0 auto; */
/* padding: 2rem; */
font-weight: normal;
width: 100%; /* 确保宽度撑满整个视口 */
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: flex; /* 改为 flex 布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
padding: 0; /* 去除多余的 padding */
width: 100%; /* 确保宽度撑满整个视口 */
}
}

View File

@ -0,0 +1,86 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
})
})

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

14
src/main.js Normal file
View File

@ -0,0 +1,14 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

39
src/router/index.js Normal file
View File

@ -0,0 +1,39 @@
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '../stores/auth.js'; // 确保路径正确
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'login',
component: () => import('../views/Login.vue'),
},
{
path: '/home',
name: 'home',
component: () => import('../views/Home.vue'),
meta: { requiresAuth: true },
},
{
path: '/change-password',
name: 'changePassword',
component: () => import('../views/ChangePassword.vue')
}
],
});
// 路由守卫
router.beforeEach((to, from, next) => {
const authStore = useAuthStore(); // 获取登录状态
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
// 如果目标路由需要认证且未登录,则跳转到登录页
next({ name: 'login' });
} else {
// 否则允许进入
next();
}
});
export default router;

16
src/stores/auth.js Normal file
View File

@ -0,0 +1,16 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', () => {
const isAuthenticated = ref(false);
function login() {
isAuthenticated.value = true;
}
function logout() {
isAuthenticated.value = false;
}
return { isAuthenticated, login, logout };
});

16
src/stores/counter.js Normal file
View File

@ -0,0 +1,16 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', () => {
const isAuthenticated = ref(false);
function login() {
isAuthenticated.value = true;
}
function logout() {
isAuthenticated.value = false;
}
return { isAuthenticated, login, logout };
});

7
src/utils/jsonCounter.js Normal file
View File

@ -0,0 +1,7 @@
// 创建一个 JSON ID 计数器
let currentId = 0;
export const getNextJsonId = () => {
currentId += 1;
return currentId;
};

View File

@ -0,0 +1,152 @@
<template>
<div class="change-password-container">
<el-card class="change-password-card">
<h2 class="change-password-title">修改密码</h2>
<el-form :model="form" :rules="rules" ref="passwordForm" label-position="top">
<el-form-item label="原密码" prop="oldPassword">
<el-input v-model="form.oldPassword" type="password" placeholder="请输入原密码" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="form.newPassword" type="password" placeholder="请输入新密码" show-password />
</el-form-item>
<el-form-item label="确认新密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" type="password" placeholder="请再次输入新密码" show-password />
</el-form-item>
<div class="button-group">
<el-button type="primary" @click="handleChangePassword">确认修改</el-button>
<el-button @click="$router.push('/login')">返回登录</el-button>
</div>
</el-form>
</el-card>
</div>
</template>
<script>
import axios from 'axios';
import { getNextJsonId } from '../utils/jsonCounter.js';
import { ElMessage } from 'element-plus';
export default {
data() {
//
const validateConfirmPassword = (rule, value, callback) => {
if (value !== this.form.newPassword) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
};
return {
form: {
oldPassword: '',
newPassword: '',
confirmPassword: ''
},
rules: {
oldPassword: [
{ required: true, message: '请输入原密码', trigger: 'blur' },
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认新密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' }
]
}
};
},
methods: {
handleChangePassword() {
this.$refs.passwordForm.validate((valid) => {
if (valid) {
const changePasswordData = {
JSON_id: getNextJsonId(),
command: "change_password",
parameters: {
old_password: this.form.oldPassword,
new_password: this.form.newPassword
}
};
console.log('发送修改密码请求 >>>>>>>>>>>>');
console.log('请求数据:', JSON.stringify(changePasswordData, null, 2));
axios.post('/communication', changePasswordData, {
headers: {
"content-type": "application/json"
}
})
.then((response) => {
console.log('收到修改密码响应 <<<<<<<<<<');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
if (response.data?.parameters?.status === 0) {
ElMessage({
message: response.data?.parameters?.message || '密码修改成功',
type: 'success'
});
this.$router.push('/login');
} else {
ElMessage({
message: response.data?.parameters?.message || '密码修改失败',
type: 'error'
});
}
})
.catch((error) => {
console.error('修改密码失败:', error);
ElMessage({
message: '修改密码请求失败',
type: 'error'
});
});
} else {
ElMessage({
message: '请填写完整的密码信息',
type: 'error'
});
}
});
}
}
};
</script>
<style scoped>
.change-password-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100%;
background: linear-gradient(to right, #2c3e50, #4ca1af);
}
.change-password-card {
width: 400px;
padding: 24px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.change-password-title {
text-align: center;
font-size: 24px;
margin-bottom: 24px;
font-weight: bold;
color: #333;
}
.button-group {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
</style>

776
src/views/Home.vue Normal file
View File

@ -0,0 +1,776 @@
<template>
<el-container style="height: 100vh;">
<!-- 顶部栏 -->
<el-header height="60px" class="header">
<div class="header-content">
<img src="../assets/img/车道配置.png" alt="Logo" class="logo" />
<span class="device-name">可变车道管理系统</span>
</div>
</el-header>
<el-container>
<!-- 侧边导航栏 -->
<el-aside width="200px" class="menu-aside">
<el-menu :default-active="activeMenu" class="el-menu-vertical-demo" @select="handleMenuSelect">
<el-sub-menu index="1">
<template #title>系统管理</template>
<el-menu-item index="1-1">基本设置</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>方案管理</template>
<el-menu-item index="2-1">模式选择</el-menu-item>
<el-menu-item index="2-2">方案列表</el-menu-item>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>关于</template>
<el-menu-item index="3-1">基本信息</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<!-- 内容区域 -->
<el-main class="content-main">
<!-- 基本管理部分 -->
<el-tabs v-if="activeMenu === '1-1'">
<el-tab-pane label="系统时间">
<h3 style="background-color: #edf4fd;">系统时间设置</h3>
<p>您必须先确保本机连接到 Internet从而才能获取到正确时间</p>
<!-- 时间设置选项 -->
<el-radio-group v-model="timeSetting">
<el-col :span="24">
<el-radio :value="'network'">通过本机获取系统时间</el-radio>
<div v-if="timeSetting === 'network'" class="network-time">
<el-card>
<h3>本地当前时间</h3>
<p>{{ currentTime }}</p>
</el-card>
</div>
</el-col>
<el-col :span="24">
<el-radio :value="'manual'">手动设置系统时间</el-radio>
<div v-if="timeSetting === 'manual'" class="manual-time">
<el-form label-width="100px">
<el-form-item label="日期时间:">
<el-date-picker v-model="manualDateTime" type="datetime"
placeholder="选择日期和时间" :default-time="defaultTime"></el-date-picker>
</el-form-item>
</el-form>
</div>
</el-col>
</el-radio-group>
<!-- 操作按钮 -->
<div class="notice">
<el-button size="large" type="primary" @click="applyTimeSettings">应用</el-button>
</div>
</el-tab-pane>
</el-tabs>
<el-tabs v-if="activeMenu === '2-1'">
<el-tab-pane label="模式选择">
<h3 style="background-color: #edf4fd;">模式设置</h3>
<p>模式选择后请点击下发按钮更新配置</p>
<!-- 模式设置 -->
<el-radio-group v-model="modeSetting">
<el-col :span="24">
<el-radio :value="'Smart_Mode'">智能模式</el-radio>
<div v-if="modeSetting === 'Smart_Mode'" class="Smart_Mode">
<el-card>
<h3
style="font-size: 18px; color: #333; text-align: center; margin-bottom: 1rem;">
智能模式配置
</h3>
<el-form label-width="70px">
<el-form-item label="参数1">
<el-input v-model="param1" placeholder="请输入参数1"></el-input>
</el-form-item>
<el-form-item label="参数2">
<el-input v-model="param2" placeholder="请输入参数2"></el-input>
</el-form-item>
<el-form-item label="参数3">
<el-input v-model="param3" placeholder="请输入参数3"></el-input>
</el-form-item>
</el-form>
</el-card>
</div>
</el-col>
<el-col :span="24">
<el-radio :value="'Solution_Mode'">定时模式</el-radio>
<div v-if="modeSetting === 'Solution_Mode'" class="Solution_Mode">
<p style="font-size: 15px; color: #333;">前往方案列表页面配置方案</p>
</div>
</el-col>
</el-radio-group>
<!-- 操作按钮 -->
<div class="notice">
<el-button size="large" type="primary" @click="applySettings">应用</el-button>
</div>
</el-tab-pane>
</el-tabs>
<el-tabs v-if="activeMenu === '3-1'">
<el-tab-pane label="版本信息">
<el-descriptions title="设备信息">
<el-descriptions-item label="版本号">{{ version }}</el-descriptions-item>
<el-descriptions-item label="编译时间">{{ compile_time }}</el-descriptions-item>
<el-descriptions-item label="设备当前时间">{{ device_time }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
<el-tabs v-if="activeMenu === '2-2'">
<!-- 新增方案 -->
<el-tab-pane label="新增方案">
<el-form :model="newPlan" label-width="120px">
<el-form-item label="方案名称">
<el-input v-model="newPlan.name" placeholder="请输入方案名称" style="width: 30rem"></el-input>
</el-form-item>
<el-form-item label="车道模式">
<el-select v-model="newPlan.selectedMode" placeholder="请选择车道模式" style="width: 30rem">
<el-option label="左转" value="leftTurn"></el-option>
<el-option label="右转" value="rightTurn"></el-option>
<el-option label="直行" value="straight"></el-option>
<el-option label="掉头" value="uTurn"></el-option>
</el-select>
</el-form-item>
<el-form-item label="时间段设置">
<div style="width: 30rem;">
<el-time-picker v-model="newPlan.timeRange" is-range range-separator="To"
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 30rem;" />
</div>
</el-form-item>
<el-form-item label="开放车道">
<el-checkbox-group v-model="newPlan.lanes">
<el-checkbox :label="'车道1'">车道1</el-checkbox>
<el-checkbox :label="'车道2'">车道2</el-checkbox>
<el-checkbox :label="'车道3'">车道3</el-checkbox>
<el-checkbox :label="'车道4'">车道4</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<div style="margin-left: 120px;">
<el-button type="primary" @click="addPlan">保存方案</el-button>
<el-button @click="resetNewPlan">重置</el-button>
</div>
</el-form-item>
</el-form>
<el-table :data="plans" border style="width: 58%">
<el-table-column prop="name" label="方案名称" width="200" align="center"></el-table-column>
<el-table-column prop="timeRange" label="时间段" width="210" align="center"></el-table-column>
<el-table-column prop="selectedMode" label="车道模式" width="210"
align="center"></el-table-column>
<el-table-column prop="lanes" label="开放车道" width="200" align="center">
<template #default="scope">
<span v-for="(lane, index) in scope.row.lanes" :key="index">
{{ lane }}<span v-if="index < scope.row.lanes.length - 1">, </span>
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="scope">
<el-button type="primary" size="small" @click="editPlan(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="deletePlan(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 编辑方案弹窗 -->
<el-dialog title="编辑方案" :visible.sync="editDialogVisible">
<el-form :model="editPlanData" label-width="120px">
<el-form-item label="方案名称">
<el-input v-model="editPlanData.name"></el-input>
</el-form-item>
<el-form-item label="时间段设置">
<el-time-picker v-model="editPlanData.startTime" placeholder="开始时间">
</el-time-picker>
<span></span>
<el-time-picker v-model="editPlanData.endTime" placeholder="结束时间">
</el-time-picker>
</el-form-item>
<el-form-item label="开放车道">
<el-checkbox-group v-model="editPlanData.lanes">
<el-checkbox :label="'车道1'">车道1</el-checkbox>
<el-checkbox :label="'车道2'">车道2</el-checkbox>
<el-checkbox :label="'车道3'">车道3</el-checkbox>
<el-checkbox :label="'车道4'">车道4</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveEditPlan">保存</el-button>
</div>
</el-dialog>
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</el-container>
</template>
<script>
import axios from 'axios' // axios
import { ElMessage } from 'element-plus'
import { getNextJsonId } from '../utils/jsonCounter.js'
export default {
data() {
return {
currentTime: '', // currentTime
//
newPlan: {
name: '',
startTime: '',
endTime: '',
lanes: [],
timeRange: [], //
selectedMode: '', //
},
plans: [
],
editDialogVisible: false,
editPlanData: {},
compile_time: '', //
version: "1.0.0", //
defaultTime: new Date(2000, 1, 1, 12, 0, 0), //
timeSetting: "network", //
modeSetting: "Smart_Mode", //
manualDateTime: null,
activeMenu: "1-1", //
param1: '', // 1
param2: '', // 2
param3: '', // 3
device_time: '', //
modeMap: {
'straight': 1,
'leftTurn': 2,
'rightTurn': 3,
'uTurn': 4
},
};
},
methods: {
//
resetNewPlan() {
this.newPlan = {
name: '',
startTime: '',
endTime: '',
lanes: [],
timeRange: [],
selectedMode: ''
};
},
//
async applySettings() {
try {
let modeData;
if (this.modeSetting === 'Smart_Mode') {
modeData = {
JSON_id: getNextJsonId(),
command: "set_mode",
parameters: {
mode: 0, //
data: {
param1: this.param1,
param2: this.param2,
param3: this.param3
}
}
};
} else {
modeData = {
JSON_id: getNextJsonId(),
command: "set_mode",
parameters: {
mode: 1 //
}
};
}
console.log('发送模式设置请求:', JSON.stringify(modeData, null, 2));
const response = await axios.post('/communication', modeData, {
headers: {
"content-type": "application/json"
}
});
console.log('收到模式设置响应:', JSON.stringify(response.data, null, 2));
if (response.data?.parameters?.status === 0) {
ElMessage({
message: response.data?.parameters?.message || '设置成功',
type: 'success'
});
} else {
ElMessage({
message: response.data?.parameters?.message || '设置失败',
type: 'error'
});
}
} catch (error) {
console.error('模式设置错误:', error);
ElMessage({
message: '模式设置失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
//
async addPlan() {
try {
//
if (!this.newPlan.name) {
ElMessage({
message: '请输入方案名称',
type: 'error'
});
return;
}
if (!this.newPlan.selectedMode) {
ElMessage({
message: '请选择车道模式',
type: 'error'
});
return;
}
const [startTime, endTime] = this.newPlan.timeRange || [null, null];
if (!startTime || !endTime) {
ElMessage({
message: '请选择时间范围',
type: 'error'
});
return;
}
if (!this.newPlan.lanes || this.newPlan.lanes.length === 0) {
ElMessage({
message: '请选择至少一个车道',
type: 'error'
});
return;
}
const formatTime = (date) => {
return date.toTimeString().slice(0, 5);
};
const lanes = this.newPlan.lanes.map(lane => {
return lane.replace('车道', '') - 1;
});
const planData = {
JSON_id: getNextJsonId(),
command: "set_scheme",
parameters: {
scheme: [{
id: this.plans.length + 1,
name: this.newPlan.name,
selectedMode: String(this.modeMap[this.newPlan.selectedMode]),
timeRange: {
start: formatTime(startTime),
end: formatTime(endTime)
},
lanes: lanes.map(String)
}]
}
};
console.log('发送方案添加请求:', JSON.stringify(planData, null, 2));
const response = await axios.post('/communication', planData, {
headers: {
"content-type": "application/json"
}
});
console.log('收到方案添加响应:', JSON.stringify(response.data, null, 2));
if (response.data?.parameters?.status === 0) {
this.plans.push({ ...this.newPlan });
this.resetNewPlan();
ElMessage({
message: response.data?.parameters?.message || '方案添加成功',
type: 'success'
});
} else {
ElMessage({
message: response.data?.parameters?.message || '方案添加失败',
type: 'error'
});
}
} catch (error) {
console.error('添加方案错误:', error);
ElMessage({
message: '方案添加失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
//
async getDeviceInfo() {
try {
const infoRequest = {
JSON_id: getNextJsonId(),
command: "get_device_info",
parameters: {}
};
const response = await axios.post('/communication', infoRequest, {
headers: {
"content-type": "application/json"
}
});
if (response.data?.parameters) {
this.version = response.data.parameters.version;
this.compile_time = response.data.parameters.compileTime;
}
} catch (error) {
console.error('获取设备信息失败:', error);
ElMessage({
message: '获取设备信息失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
//
async getAllInfo() {
try {
const allInfoRequest = {
JSON_id: getNextJsonId(),
command: "get_all_info",
parameters: {}
};
const response = await axios.post('/communication', allInfoRequest, {
headers: {
"content-type": "application/json"
}
});
if (response.data?.parameters) {
this.modeSetting = response.data.parameters.set_mode === 0 ? 'Smart_Mode' : 'Solution_Mode';
if (response.data.parameters.scheme) {
this.plans = response.data.parameters.scheme.map(scheme => ({
name: scheme.name,
timeRange: `${scheme.timeRange.start} - ${scheme.timeRange.end}`,
selectedMode: scheme.selectedMode,
lanes: scheme.lanes.map(lane => `车道${parseInt(lane) + 1}`)
}));
}
if (response.data.parameters.deviceInfo) {
this.version = response.data.parameters.deviceInfo.version;
this.compile_time = response.data.parameters.deviceInfo.compileTime;
}
}
} catch (error) {
console.error('获取所有信息失败:', error);
ElMessage({
message: '获取所有信息失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
handleMenuSelect(key) {
//
this.activeMenu = key;
},
getCurrentTime() {
const now = new Date();
return now.toLocaleString(); //
},
updateCurrentTime() {
this.currentTime = this.getCurrentTime();
},
//
async applyTimeSettings() {
try {
let timestamp;
if (this.timeSetting === 'network') {
// YYYY-MM-DDTHH:mm:ss
const now = new Date();
timestamp = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' +
String(now.getDate()).padStart(2, '0') + 'T' + // T
String(now.getHours()).padStart(2, '0') + ':' +
String(now.getMinutes()).padStart(2, '0') + ':' +
String(now.getSeconds()).padStart(2, '0');
} else {
if (!this.manualDateTime) {
ElMessage({
message: '请先选择时间!',
type: 'error'
});
return;
}
// YYYY-MM-DDTHH:mm:ss
const date = this.manualDateTime;
timestamp = date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + 'T' + // T
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}
const timeData = {
JSON_id: getNextJsonId(),
command: "set_time",
parameters: {
timestamp: timestamp
}
}
console.log('发送的时间设置数据:', JSON.stringify(timeData, null, 2));
const response = await axios.post('/communication', timeData, {
headers: {
"content-type": "application/json"
}
});
console.log('收到时间设置响应:', JSON.stringify(response.data, null, 2));
if (response.data?.parameters?.status === 0) {
ElMessage({
message: response.data.parameters.message || '时间设置成功',
type: 'success'
});
} else {
ElMessage({
message: response.data?.parameters?.message || '时间设置失败',
type: 'error'
});
}
} catch (error) {
console.error('时间设置错误:', error);
ElMessage({
message: '时间设置失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
//
async deletePlan(plan) {
try {
const deleteData = {
JSON_id: getNextJsonId(),
command: "set_scheme",
parameters: {
scheme: [{
id: plan.id,
name: plan.name,
selectedMode: "0", //
timeRange: {
start: "00:00",
end: "00:00"
},
lanes: []
}]
}
};
const response = await axios.post('/communication', deleteData, {
headers: {
"content-type": "application/json"
}
});
if (response.data?.parameters?.status === 0) {
this.plans = this.plans.filter(p => p !== plan);
ElMessage({
message: response.data?.parameters?.message || '方案删除成功',
type: 'success'
});
} else {
ElMessage({
message: response.data?.parameters?.message || '方案删除失败',
type: 'error'
});
}
} catch (error) {
console.error('删除失败:', error);
ElMessage({
message: '方案删除失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
//
editPlan(plan) {
this.editPlanData = { ...plan };
this.editDialogVisible = true;
},
//
async saveEditPlan() {
try {
const editedPlan = {
JSON_id: getNextJsonId(),
command: "set_scheme",
parameters: {
scheme: [{
id: this.editPlanData.id,
name: this.editPlanData.name,
selectedMode: String(this.editPlanData.selectedMode),
timeRange: {
start: this.editPlanData.startTime,
end: this.editPlanData.endTime
},
lanes: this.editPlanData.lanes.map(lane => lane.replace('车道', '') - 1).map(String)
}]
}
};
const response = await axios.post('/communication', editedPlan, {
headers: {
"content-type": "application/json"
}
});
if (response.data?.parameters?.status === 0) {
const index = this.plans.findIndex(p => p.id === this.editPlanData.id);
if (index !== -1) {
this.plans.splice(index, 1, { ...this.editPlanData });
}
this.editDialogVisible = false;
ElMessage({
message: response.data?.parameters?.message || '方案编辑成功',
type: 'success'
});
} else {
ElMessage({
message: response.data?.parameters?.message || '方案编辑失败',
type: 'error'
});
}
} catch (error) {
console.error('编辑失败:', error);
ElMessage({
message: '方案编辑失败:' + (error.message || '未知错误'),
type: 'error'
});
}
},
},
mounted() {
//
this.getDeviceInfo();
this.getAllInfo();
//
this.timer = setInterval(this.updateCurrentTime, 1000);
},
beforeDestroy() {
//
clearInterval(this.timer);
},
};
</script>
<style scoped>
.header {
background-color: #428bca;
color: white;
display: flex;
align-items: center;
padding: 0 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
align-items: center;
}
.logo {
height: 40px;
margin-right: 10px;
}
.device-name {
font-size: 18px;
font-weight: bold;
}
.menu-aside {
background-color: #c8ddf0;
color: #333;
/* 修改字体颜色为黑色 */
}
.el-menu-item {
color: #333;
/* 确保菜单项文字为黑色 */
}
.el-menu-item.is-active {
color: #428bca;
/* 设置选中菜单项的字体颜色 */
}
.content-main {
padding: 20px;
background: #f0f2f5;
color: #333;
/* 修改内容区域字体颜色为黑色 */
}
.device-name {
color: #fff;
/* 保持顶部设备名称为白色 */
}
.network-time,
.manual-time {
margin-left: 20px;
margin-bottom: 20px;
}
.notice {
margin-left: 200px;
margin-top: 20px;
font-size: 14px;
color: #888;
}
.el-button {
margin-top: 20px;
}
.network-time {
text-align: center;
font-size: 18px;
}
</style>

149
src/views/Login.vue Normal file
View File

@ -0,0 +1,149 @@
<template>
<div class="login-container">
<el-card class="login-card">
<h2 class="login-title">登录到 可变车道管理系统</h2>
<el-form :model="form" :rules="rules" ref="loginForm" label-position="top">
<el-form-item label="账号名称" prop="username">
<el-input v-model="form.username" placeholder="请输入账号名称" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
</el-form-item>
<el-button type="primary" block @click="handleLogin"
style="margin: 0 auto; display: block; text-align: center;">
登录
</el-button>
<div class="login-footer">
<el-link type="primary" @click="$router.push('/change-password')">修改密码?</el-link>
</div>
</el-form>
</el-card>
</div>
</template>
<script>
import axios from 'axios'; // Axios
import { useAuthStore } from '../stores/auth.js'; // 使
import { getNextJsonId } from '../utils/jsonCounter.js';
import { useRouter } from 'vue-router'; //
import { ElMessage } from 'element-plus'; //
export default {
setup() {
const router = useRouter();
const authStore = useAuthStore(); // auth store
return { router, authStore }; // auth store
},
data() {
return {
form: {
username: '',
password: '',
},
showPassword: false,
rules: {
username: [
{ required: true, message: '请输入账号名称', trigger: 'blur' },
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
},
};
},
methods: {
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
const loginData = {
JSON_id: getNextJsonId(),
command: "login",
parameters: {
username: this.form.username,
password: this.form.password
}
};
console.log('发送登录请求 >>>>>>>>>>>>');
console.log('请求数据:', JSON.stringify(loginData, null, 2));
axios.post(`/communication`, loginData, {
headers: {
"content-type": "application/json"
}
})
.then((response) => {
console.log('收到登录响应 <<<<<<<<<<');
console.log('响应数据:', JSON.stringify(response.data, null, 2));
if (response.data?.parameters?.status === 0) {
//
this.authStore.login();
ElMessage({
message: response.data?.parameters?.message || '登录成功',
type: 'success'
});
this.router.push('/home').catch(err => {
console.error('路由跳转错误:', err);
});
} else {
ElMessage({
message: response.data?.parameters?.message || '登录失败',
type: 'error'
});
}
})
.catch((error) => {
console.error('登录失败:', error);
ElMessage({
message: '登录请求失败',
type: 'error'
});
});
} else {
ElMessage({
message: '请填写完整的登录信息',
type: 'error'
});
}
});
},
},
};
</script>
<style scoped>
.login-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100%;
background: linear-gradient(to right, #2c3e50, #4ca1af);
}
.login-card {
width: 400px;
padding: 24px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.login-title {
text-align: center;
font-size: 24px;
margin-bottom: 24px;
font-weight: bold;
color: #333;
}
.login-footer {
text-align: center;
margin-top: 16px;
color: #666;
}
</style>

42
vite.config.js Normal file
View File

@ -0,0 +1,42 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { codeInspectorPlugin } from 'code-inspector-plugin';
// 引入 vite-plugin-compression 插件
// import viteCompression from 'vite-plugin-compression';
// https://vite.dev/config/
export default defineConfig({
plugins: [
codeInspectorPlugin({
bundler: 'vite',
}),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
vue(),
vueJsx(),
vueDevTools(),
// 使用 vite-plugin-compression 插件
// viteCompression({
// algorithm: 'gzip',
// filter: /\.(js|css|html|svg)$/,
// threshold: 10240,
// minRatio: 0.8,
// deleteOriginFile: true // 新增配置项,删除源文件
// })
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})

14
vitest.config.js Normal file
View File

@ -0,0 +1,14 @@
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)),
},
}),
)

167
可变车道JSON协议.json Normal file
View File

@ -0,0 +1,167 @@
JSON
(Web )
{
"command": "login",
"parameters": {
"username": "string",
"password": "string"
}
}
( Web)
{
"command": "login_response",
"parameters": {
"status": 0, // 0: , 1:
"message": "Login successful" //
}
}
()
(Web )
{
"JSON_id": 2,
"command": "change_password",
"parameters": {
"old_password": "",
"new_password": ""
}
}
( Web)
{
"command": "change_password_response",
"parameters": {
"status": 0, // 0: , 1:
"message": "Psword changed successfully"
}
}
JSON
(Web )
{
"command": "set_time",
"parameters": {
"timestamp": "2025-01-13T12:00:00Z"
}
}
( Web)
{
"command": "set_time_response",
"parameters": {
"status": 0, // 0: , 1:
"message": "Time set successfully"
}
}
(Web
{
"command": "set_mode",
"parameters": {
"mode": 0, //
"data": {
"param1": "value1",
"param2": "value2",
"param3": "value3"
}
}
}
( Web)
{
"command": "set_mode_response",
"parameters": {
"status": 0, // 0: , 1:
"message": "Mode set successfully"
}
}
(Web )
{
"command": "set_scheme",
"parameters": {
"scheme": [
{
"id": 1,
"name": "方案1",
"selectedMode": "1", // 1:2:3:4:
"timeRange": {
"start": "08:00",
"end": "10:00"
},
"lanes": [
"0",
"1"
]
}
]
}
}
( Web)
{
"command": "set_scheme_response",
"parameters": {
"status": 0, // 0: , 1:
"message": "Add Plan successfully"
}
}
(Web )
{
"command": "get_device_info",
"parameters": {}
}
( Web)
{
"command": "get_device_info_response",
"parameters": {
"version": "1.0.3", //
"compileTime": "2025-01-14T10:30:00Z"
}
}
(Web )
{
"command": "get_all_info",
"parameters": {}
}
( Web)
{
"command": "get_all_info_response",
"parameters": {
"set_mode": 0, //
"scheme": [
{
"id": 1,
"name": "方案1",
"selectedMode": "1", // 1:2:3:4:
"timeRange": {
"start": "08:00",
"end": "10:00"
},
"lanes": [
"0",
"1"
]
}
],
"deviceInfo": {
"version": "1.0.3",
"compileTime": "2025-01-14 10:30:00"
}
}
}