This commit is contained in:
MR-ZC 2025-03-23 15:16:30 +08:00
parent 115f669655
commit 492df1cb5f
25 changed files with 1389 additions and 237 deletions

6
.cursor/rules/init.mdc Normal file
View File

@ -0,0 +1,6 @@
---
description:
globs:
alwaysApply: true
---
请使用中文回答我的问题

View File

@ -1 +1 @@
VITE_BASE_URL = "192.168.100.1" VITE_BASE_URL = "https://169.254.92.7:5173"

1102
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,14 +11,17 @@
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
"dependencies": { "dependencies": {
"@types/crypto-js": "^4.2.2",
"@vitejs/plugin-basic-ssl": "^2.0.0", "@vitejs/plugin-basic-ssl": "^2.0.0",
"await-to-js": "^3.0.0", "await-to-js": "^3.0.0",
"axios": "^1.8.1", "axios": "^1.8.1",
"cesium": "^1.127.0", "cesium": "^1.127.0",
"crypto-js": "^4.2.0",
"element-plus": "^2.9.5", "element-plus": "^2.9.5",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"less": "^4.2.2", "less": "^4.2.2",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
@ -28,6 +31,7 @@
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1", "@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"esbuild": "^0.25.1",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"vite": "^6.1.0", "vite": "^6.1.0",

14
src/api/task.ts Normal file
View File

@ -0,0 +1,14 @@
import http from "@/http"
import { to } from "await-to-js"
/**航点数据 */
export const queryPointRoutePreviewParam = (taskId: number) => to<any>(
http.get("/pro_api/foreign/task/queryPointRoutePreviewParam", {
params: { taskId }
}))
/**航面数据 */
export const queryRegionalPlanningPreviewParam = (taskId: number) => to<any>(
http.get('/pro_api/foreign/task/queryRegionalPlanningPreviewParam', {
params: { taskId }
}))

View File

@ -1,10 +1,10 @@
import request from "@/http"; import http from "@/http";
import to from "await-to-js" import to from "await-to-js"
/**登录 */ /**登录 */
export const login = (data: any) => to( export const login = (data: any) => to<any>(
request.post("/login", { http.post("/api/login", {
username: data.username,
} password: data.password
)) }))

View File

@ -8,7 +8,7 @@
<div class="header_main"> <div class="header_main">
<div class="tabs"> <div class="tabs">
<div v-for="tab in props.tabs" @click="changeType(tab)" class="tab" <div v-for="tab in props.tabs" @click="changeType(tab)" class="tab"
:class="{ checked: tab.id == props.id }"> :class="{ checked: tab.id == id }">
<span>{{ tab.label }}</span> <span>{{ tab.label }}</span>
</div> </div>
</div> </div>
@ -18,13 +18,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue" // import { ref } from "vue"
const props = defineProps(['tabs', 'id']) const props = defineProps(['tabs'])
const emit = defineEmits(["update:id"]) const id = defineModel<number>('id')
function changeType({ id, disabled }: { id: number, disabled: boolean }) { function changeType({ id:id_, disabled }: { id: number, disabled: boolean }) {
if (disabled) return; if (disabled) return;
emit("update:id", id) console.log("id_", id_)
id.value = id_
} }
</script> </script>
@ -40,7 +41,6 @@ function changeType({ id, disabled }: { id: number, disabled: boolean }) {
z-index: -1; z-index: -1;
display: flex; display: flex;
height: 57px; height: 57px;
background-color: antiquewhite;
width: 100%; width: 100%;
img { img {

View File

@ -37,7 +37,6 @@
width: 249px; width: 249px;
object-fit: cover; object-fit: cover;
display: block; display: block;
background-color: antiquewhite;
} }
.content { .content {

View File

@ -51,6 +51,7 @@ const options = ref([
value: 4 value: 4
}, },
]) ])
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -10,9 +10,7 @@
</div> </div>
<div class="container_center"> <div class="container_center">
<div class="map_wrap"> <div class="map_wrap">
<!-- 悬浮窗 --> <mainMap></mainMap>
<img src="@/assets/img/image@1x.png" alt="">
<!-- <mainMap></mainMap> -->
</div> </div>
<div class="event_list_wrap"> <div class="event_list_wrap">
<eventList></eventList> <eventList></eventList>
@ -25,11 +23,30 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, provide, onMounted } from "vue"
import taskList from "@/components/zfControlPlatform/taskList/index.vue" import taskList from "@/components/zfControlPlatform/taskList/index.vue"
import positionView from "@/components/zfControlPlatform/positionView/index.vue" import positionView from "@/components/zfControlPlatform/positionView/index.vue"
import eventList from "@/components/zfControlPlatform/eventList/index.vue" import eventList from "@/components/zfControlPlatform/eventList/index.vue"
import taskController from "@/components/zfControlPlatform/taskController/index.vue" import taskController from "@/components/zfControlPlatform/taskController/index.vue"
import mainMap from "@/components/zfControlPlatform/mainMap/index.vue" import mainMap from "@/components/zfControlPlatform/mainMap/index.vue"
import { queryPointRoutePreviewParam, queryRegionalPlanningPreviewParam } from "@/api/task"
//
const pointRoutePreviewParam = ref<any[]>([])
provide("pointRoutePreviewParam", pointRoutePreviewParam)
//
async function getPointRoutePreviewParam() {
const [error, res] = await queryPointRoutePreviewParam(1)
if (error) return console.log("获取航点数据失败==>>", error);
pointRoutePreviewParam.value = res.data.waypointList
}
onMounted(() => {
getPointRoutePreviewParam()
})
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -38,30 +38,46 @@
</div> </div>
</div> </div>
</div> </div>
<div width="600px" height="500px" class="cesiumContainer" id="cesiumContainer"></div> <div class="cesiumContainer" id="cesiumContainer" ref="cesiumContainerRef"></div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { inject, watch, ref as vueRef, nextTick } from "vue"
import * as Cesium from "cesium" import * as Cesium from "cesium"
import { onMounted, ref } from "vue" import type { Viewer, Property } from "cesium"
import { onMounted, onUnmounted, ref } from "vue"
import type { Ref } from "vue"
import "cesium/Build/Cesium/Widgets/widgets.css"; import "cesium/Build/Cesium/Widgets/widgets.css";
import type { PointRoute } from "@/types/map"
let viewer; let viewer: Viewer | null = null;
let handler; const afterRender = new Set<() => void>()
let polyline; const cesiumContainerRef = vueRef<HTMLElement | null>(null);
let positions = []; const pointRoutePreviewParam = inject<Ref<PointRoute[]>>("pointRoutePreviewParam", ref([]))
let isDrawing = ref(false);
async function init() { async function init() {
// CesiumURL访
(window as any).CESIUM_BASE_URL = "/assets/Cesium/" (window as any).CESIUM_BASE_URL = "/assets/Cesium/"
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkNWM3YTc2OS1iMjQwLTRiYTUtOGFhNC1lNmFhNDRlN2QyODIiLCJpZCI6MjgxNzc2LCJpYXQiOjE3NDEyNTI2Mjh9.6H_pbpxLPYMBGveKGq9pek2rYlrFvQeWcnHGzVkmb00' const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkNWM3YTc2OS1iMjQwLTRiYTUtOGFhNC1lNmFhNDRlN2QyODIiLCJpZCI6MjgxNzc2LCJpYXQiOjE3NDEyNTI2Mjh9.6H_pbpxLPYMBGveKGq9pek2rYlrFvQeWcnHGzVkmb00'
Cesium.Ion.defaultAccessToken = token Cesium.Ion.defaultAccessToken = token
viewer = new Cesium.Viewer('cesiumContainer', await nextTick();
{
// terrain: Terrain.fromWorldTerrain(), //
// terrainProvider: undefined, if (!cesiumContainerRef.value) {
console.error('cesiumContainer元素不存在');
return;
}
try {
// Cesium
if (viewer) {
return;
}
// 使refDOM
viewer = new Cesium.Viewer(cesiumContainerRef.value, {
selectionIndicator: false, // selectionIndicator: false, //
infoBox: false, // infoBox: false, //
navigationHelpButton: false, // navigationHelpButton: false, //
@ -72,46 +88,120 @@ async function init() {
animation: false, // animation: false, //
geocoder: false, // geocoder: false, //
fullscreenButton: false // fullscreenButton: false //
} });
);
// //
handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); viewer.camera.setView({
viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(106.64219088, 38.75395285, 2000),
destination: Cesium.Cartesian3.fromDegrees(113.35, 23.11, 10000), orientation: {
orientation: { heading: Cesium.Math.toRadians(0.0),
heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-15.0),
pitch: Cesium.Math.toRadians(-15.0), roll: Cesium.Math.toRadians(0.0)
roll: Cesium.Math.toRadians(0.0) }
});
viewer.scene.mode = Cesium.SceneMode.SCENE2D; // 2D
viewer.scene.screenSpaceCameraController.maximumZoomDistance = 20000;
console.log('Cesium初始化成功');
} catch (error) {
console.error('初始化Cesium Viewer时出错:', error);
}
}
/**画出预览航线 */
function drawPointRoutePreview(pointRoute: PointRoute[]) {
if (!viewer) return
//
viewer.entities.removeAll();
// 线
const polyline = viewer.entities.add({
polyline: {
positions: pointRoute.map(item => {
return Cesium.Cartesian3.fromDegrees(item.wayPointLongitude, item.wayPointLatitude, item.wayPointAltitude)
}),
width: 4,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.fromCssColorString("#3a76ff"),
dashLength: 20
}),
clampToGround: true
} }
}); });
// viewer.fullscreenButton() flyToPointRoute(viewer, pointRoute)
// setTimeout(() => {
// viewer.camera.flyTo({
// destination: Cartesian3.fromDegrees(113.35, 23.11, 10000),
// orientation: {
// heading: CesiumMath.toRadians(0.0),
// pitch: CesiumMath.toRadians(-15.0),
// roll: CesiumMath.toRadians(0.0)
// }
// });
// }, 5000)
//3d
// const buildingTileset = await createOsmBuildingsAsync();
// viewer.scene.primitives.add(buildingTileset);
viewer.scene.mode = Cesium.SceneMode.SCENE2D; // 2D
// viewer.scene.mode = SceneMode.SCENE3D; // 3D
// viewer.scene.screenSpaceCameraController.minimumZoomDistance = 100;
// viewer.scene.screenSpaceCameraController.maximumZoomDistance = 20000;
// viewer.zoomTo(300)
} }
onMounted(() => { /**
init() * 将地图视角飞行到航线点的范围内
* @param viewer Cesium Viewer实例
* @param pointRoute 航线点数组
*/
function flyToPointRoute(viewer: Viewer, pointRoute: PointRoute[]) {
// 线
const positions = pointRoute.map(item => ({
longitude: item.wayPointLongitude,
latitude: item.wayPointLatitude
}));
//
const rectangle = Cesium.Rectangle.fromCartographicArray(
positions.map(pos => Cesium.Cartographic.fromDegrees(pos.longitude, pos.latitude))
);
// ,20%
const width = rectangle.east - rectangle.west;
const height = rectangle.north - rectangle.south;
const padding = {
x: width * 0.2, // 20%
y: height * 0.2 // 20%
};
//
const paddedRectangle = new Cesium.Rectangle(
rectangle.west - padding.x, // 西
rectangle.south - padding.y, //
rectangle.east + padding.x, //
rectangle.north + padding.y //
);
// 使
viewer.camera.flyTo({
destination: paddedRectangle, //
duration: 2 // 2
});
}
watch(() => pointRoutePreviewParam.value, () => {
if (pointRoutePreviewParam.value.length > 0) {
if (viewer) {
drawPointRoutePreview(pointRoutePreviewParam.value as PointRoute[])
} else {
afterRender.add(() => {
drawPointRoutePreview(pointRoutePreviewParam.value as PointRoute[])
})
}
}
}) })
onMounted(async () => {
// 使nextTickDOM
await nextTick();
// Cesium
await init();
//
afterRender.forEach(fn => fn());
});
// Cesium
onUnmounted(() => {
if (viewer) {
viewer.destroy();
viewer = null;
}
});
</script> </script>

View File

@ -30,7 +30,6 @@ import titleView from "@/components/zfControlPlatform/titleView/index.vue"
.content { .content {
flex: 1; flex: 1;
height: 0; height: 0;
background-color: antiquewhite;
img{ img{
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -1,49 +1,52 @@
<template> <template>
<div class="step_view"> <div class="step_view">
<div class="step"> <div class="step" v-for="item in props.taskProgress">
<div class="content"> <div class="content">
<div class="left"> <div class="left">
<div class="time">11:49</div> <div class="time">{{ item.time }}</div>
<div class="circle"></div> <div class="circle"></div>
<div class="label">任务前检测</div> <div class="label">{{ item.label }}</div>
</div> </div>
<div class="status_icon"> <div :class="['status_icon', { loading: item.status === 'loading' }]">
<img src="@/assets/img/circle_check.svg" alt=""> <img v-if="item.status === 'success'" src="@/assets/img/circle_check.svg" alt="">
</div> <img v-else src="@/assets/img/zf_task_loading_icon.svg" alt="">
</div>
</div>
<div class="step">
<div class="content">
<div class="left">
<div class="time">11:49</div>
<div class="circle"></div>
<div class="label">任务前检测</div>
</div>
<div class="status_icon">
<img src="@/assets/img/circle_check.svg" alt="">
</div>
</div>
</div>
<div class="step">
<div class="content">
<div class="left">
<div class="time">11:49</div>
<div class="circle"></div>
<div class="label">任务前检测</div>
</div>
<div class="status_icon">
<img src="@/assets/img/zf_task_loading_icon.svg" alt="">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
type TaskProgress = {
time: string,
label: string,
status: string
}
type Props = {
taskProgress: TaskProgress[]
}
const props = withDefaults(defineProps<Props>(), {
taskProgress: () => []
})
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@keyframes loading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.step_view { .step_view {
// background-color: antiquewhite; // background-color: antiquewhite;
width: 100%; width: 100%;
@ -100,6 +103,25 @@
} }
} }
} }
.status_icon {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
&.loading {
img {
animation: loading 1s linear infinite;
}
}
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }

View File

@ -18,7 +18,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// import taskItem from './taskItem.vue'; import taskItem from './taskItem.vue';
import ongoingTask from './ongoingTask.vue'; import ongoingTask from './ongoingTask.vue';
import titleView from "@/components/zfControlPlatform/titleView/index.vue" import titleView from "@/components/zfControlPlatform/titleView/index.vue"
</script> </script>

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="task_main"> <div class="task_main">
<div class="base_info"> <div class="base_info">
<div class="info_item"> <div class="info_item">
<span class="label">任务名称</span> <span class="label">任务名称</span>
@ -21,9 +20,8 @@
</div> </div>
</div> </div>
<div class="step"> <div class="step">
<stepView></stepView> <stepView :taskProgress="taskProgress"></stepView>
</div> </div>
</div> </div>
</template> </template>
@ -31,6 +29,29 @@
import { ref } from "vue" import { ref } from "vue"
import stepView from "@/components/zfControlPlatform/stepView/index.vue" import stepView from "@/components/zfControlPlatform/stepView/index.vue"
const progress = ref(50) const progress = ref(50)
type TaskProgress = {
time: string
label: string
status: "success" | "loading"
}
const taskProgress = ref<TaskProgress[]>([
{
time: "11:49",
label: "任务前检测",
status: "success"
},
{
time: "11:49",
label: "任务前检测",
status: "success"
},
{
time: "11:49",
label: "任务前检测",
status: "loading"
}
])
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -22,7 +22,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background-color: antiquewhite;
border-bottom: 1px #3c7bff solid; border-bottom: 1px #3c7bff solid;
padding: 0 20px; padding: 0 20px;
background: linear-gradient(90deg, #0A5CC8 3%, #053B7B 95%); background: linear-gradient(90deg, #0A5CC8 3%, #053B7B 95%);

View File

@ -1,17 +1,37 @@
import axios from "axios"; import axios from "axios";
import type { AxiosResponse, AxiosError } from "axios";
import useUserInfoStore from "@/stores/user"
import { ElMessage } from "element-plus"
const userInfoStore = useUserInfoStore()
const request = axios.create({ const request = axios.create({
baseURL: import.meta.env.VITE_BASE_URL baseURL: import.meta.env.VITE_BASE_URL
}) })
request.interceptors.request.use((config) => { request.interceptors.request.use((config) => {
config.headers.Authorization = userInfoStore.token
return config return config
}) })
request.interceptors.response.use((res) => { request.interceptors.response.use(
(res: AxiosResponse) => {
return res if (res.data.code === 200) {
}) return res.data
} else {
ElMessage({
type: "error",
message: res.data.msg || "请求失败"
})
return Promise.reject(res?.data?.msg || "请求失败")
}
},
(err: AxiosError) => {
ElMessage({
type: "error",
message: (err.response?.data as any)?.msg || "请求失败"
})
return Promise.reject((err.response?.data as any)?.msg || "请求失败")
}
)
export default request export default request

View File

@ -2,6 +2,7 @@ import './assets/style/base.less'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
@ -10,7 +11,10 @@ import 'element-plus/dist/index.css'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.use(router) app.use(router)
app.use(ElementPlus) app.use(ElementPlus)
app.mount('#app') app.mount('#app')

View File

@ -1,5 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/index/index.vue' import HomeView from '../views/index/index.vue'
import useUserInfoStore from "@/stores/user"
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -22,5 +24,19 @@ const router = createRouter({
], ],
}) })
router.beforeEach((to, from, next) => {
const userInfoStore = useUserInfoStore()
console.log("token==>>", userInfoStore.token)
if (to.path === "/login") {
if (userInfoStore.token) {
next({ path: "/" })
} else {
next()
}
}
if (!userInfoStore.token) return next({ path: "/login" })
next()
})
export default router export default router

View File

@ -2,14 +2,22 @@ import { defineStore } from 'pinia'
const useUserInfoStore = defineStore('userInfo', { const useUserInfoStore = defineStore('userInfo', {
state: () => ({ state: () => ({
token: "" token: "",
user_info: {
last_login_at: "",
last_login_ip: "",
login_count: 0,
username: ""
}
}), }),
getters: { getters: {
}, },
actions: { actions: {
} },
persist: true
}) })
export default useUserInfoStore export default useUserInfoStore

5
src/types/map.ts Normal file
View File

@ -0,0 +1,5 @@
export type PointRoute = {
wayPointLatitude: number,
wayPointLongitude: number,
wayPointAltitude: number
}

12
src/utils/crypto.ts Normal file
View File

@ -0,0 +1,12 @@
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'zx_web_secret_key_2024'
export const encrypt = (text: string): string => {
return CryptoJS.AES.encrypt(text, SECRET_KEY).toString()
}
export const decrypt = (ciphertext: string): string => {
const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY)
return bytes.toString(CryptoJS.enc.Utf8)
}

View File

@ -42,11 +42,11 @@ export default [
label: "智能助手", label: "智能助手",
disabled: true, disabled: true,
}, },
{ // {
id: 7, // id: 7,
type: "IFRAME", // type: "IFRAME",
label: "测试", // label: "测试",
// disabled: true, // disabled: true,
src:"http://127.0.0.1:5500/iframe%E9%80%9A%E8%AE%AF.html" // src:"http://127.0.0.1:5500/iframe%E9%80%9A%E8%AE%AF.html"
}, // },
] ]

View File

@ -7,17 +7,19 @@
</div> </div>
<h2 class="title">交通运输综合执法检查系统</h2> <h2 class="title">交通运输综合执法检查系统</h2>
<form class="form_wrap" @submit="submit"> <form class="form_wrap" @submit="submit">
<div class="form_item" :class="{ error: errors.userName }"> <div class="form_item" :class="{ error: errors.username }">
<div class="icon"> <div class="icon">
<img src="@/assets/img/login_user_icon.png" alt=""> <img src="@/assets/img/login_user_icon.png" alt="">
</div> </div>
<input class="input" v-model="loginFormData.userName" type="text" placeholder="请输入账号" @input="clearError('userName')"> <input class="input" v-model="loginFormData.username" type="text" placeholder="请输入账号"
@input="clearError('username')">
</div> </div>
<div class="form_item" :class="{ error: errors.password }"> <div class="form_item" :class="{ error: errors.password }">
<div class="icon"> <div class="icon">
<img src="@/assets/img/login_password_icon.png" alt=""> <img src="@/assets/img/login_password_icon.png" alt="">
</div> </div>
<input class="input" v-model="loginFormData.password" type="password" placeholder="请输入密码" @input="clearError('password')"> <input class="input" v-model="loginFormData.password" type="password" placeholder="请输入密码"
@input="clearError('password')">
</div> </div>
<div class="options"> <div class="options">
<label class="save_password"> <label class="save_password">
@ -38,44 +40,53 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue" import { ref } from "vue"
import { login } from "@/api/user.ts" import { login } from "@/api/user"
import { ElMessage } from "element-plus" import { ElMessage } from "element-plus"
import {useRouter} from "vue-router" import { useRouter } from "vue-router"
import useUserInfoStore from "@/stores/user"
import { encrypt, decrypt } from "@/utils/crypto"
const router = useRouter() const router = useRouter()
const savePasswordFlag = ref(!!(localStorage.getItem('user'))) const savePasswordFlag = ref(!!(localStorage.getItem('username') && localStorage.getItem('_k')))
const loginFormData = ref(JSON.parse(localStorage.getItem('user') || '{"userName":"","password":""}')) const loginFormData = ref({
username: localStorage.getItem('username') || '',
password: localStorage.getItem('_k') ? decrypt(localStorage.getItem('_k')!) : ''
})
// //
const errors = ref({ const errors = ref({
userName: false, username: false,
password: false password: false
}) })
const userInfoStore = useUserInfoStore()
// //
function clearError(field: 'userName' | 'password') { function clearError(field: 'username' | 'password') {
errors.value[field] = false errors.value[field] = false
} }
function changSavePassword() { function changSavePassword() {
if (savePasswordFlag.value) { if (savePasswordFlag.value) {
localStorage.setItem("user", JSON.stringify(loginFormData.value)) localStorage.setItem("username", loginFormData.value.username)
localStorage.setItem("_k", encrypt(loginFormData.value.password))
} else { } else {
localStorage.removeItem("user") localStorage.removeItem("username")
localStorage.removeItem("_k")
} }
} }
// //
function submit(event: Event) { function submit(event: Event) {
event.preventDefault() event.preventDefault()
console.log(loginFormData.value)
changSavePassword() changSavePassword()
if (!validate()) return; if (!validate()) return;
router.push("/") toLogin()
} }
/**表单校验 */ /**表单校验 */
function validate() { function validate() {
errors.value.userName = !loginFormData.value.userName errors.value.username = !loginFormData.value.username
errors.value.password = !loginFormData.value.password errors.value.password = !loginFormData.value.password
if (errors.value.username) {
if (errors.value.userName) {
ElMessage({ ElMessage({
type: "error", type: "error",
message: "请输入用户名" message: "请输入用户名"
@ -95,7 +106,16 @@ function validate() {
async function toLogin() { async function toLogin() {
const [err, res] = await login({}) const [err, res] = await login({
username: loginFormData.value.username,
password: loginFormData.value.password
})
if (err) {
return
}
userInfoStore.token = res.data.access_token
userInfoStore.user_info = res.data.user_info
router.push("/")
} }
</script> </script>

View File

@ -25,5 +25,12 @@ export default defineConfig({
key: fs.readFileSync(path.resolve(__dirname, 'cert/privkey.pem')), key: fs.readFileSync(path.resolve(__dirname, 'cert/privkey.pem')),
cert: fs.readFileSync(path.resolve(__dirname, 'cert/fullchain.pem')), cert: fs.readFileSync(path.resolve(__dirname, 'cert/fullchain.pem')),
}, },
proxy:{
'/api': {
changeOrigin: true,
target: 'http://192.168.10.176:6001',
rewrite: (path) => path.replace(/^\/api/, '/api')
}
}
}, },
}) })