Browse Source

feat: 功能添加

main
betaqi 4 months ago
parent
commit
9abd809062
  1. 2
      .env
  2. 1
      .env.local
  3. 1
      .env.prod
  4. 3
      global.types/components.d.ts
  5. 4
      package.json
  6. 11
      src/api/module/firmware/index.ts
  7. 1
      src/api/module/transfer/index.ts
  8. 16
      src/composables/useZMQJsonWorker.ts
  9. 62
      src/stores/transferData.ts
  10. 4
      src/utils/zmq.ts
  11. 14
      src/utils/zmqJsonWorker.ts
  12. 40
      src/views/firmwareUpload/index.vue
  13. 138
      src/views/stationData/components/PointGroupTree.vue
  14. 169
      src/views/stationData/components/deviceDrawer.vue
  15. 19
      src/views/stationData/components/newDataChart.vue
  16. 35
      src/views/stationData/components/offTransferDlg.vue
  17. 39
      src/views/stationData/components/transferMask.vue
  18. 179
      src/views/stationData/index.vue
  19. 309
      src/views/stationData/transferData.vue
  20. 8
      src/views/stationData/type.ts
  21. 4
      vite.config.ts

2
.env

@ -1 +1,3 @@
VITE_BASE_API = '/remoteServer' VITE_BASE_API = '/remoteServer'
VITE_SHOW_ONLINE_DEVICE = true
VITE_APP_ENV = local

1
.env.local

@ -0,0 +1 @@
VITE_APP_ENV = local

1
.env.prod

@ -0,0 +1 @@
VITE_APP_ENV = prod

3
global.types/components.d.ts vendored

@ -16,13 +16,13 @@ declare module 'vue' {
EdfsWrap: typeof import('./../src/components/Edfs-wrap.vue')['default'] EdfsWrap: typeof import('./../src/components/Edfs-wrap.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside'] ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
@ -34,6 +34,7 @@ declare module 'vue' {
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

4
package.json

@ -8,6 +8,8 @@
"build": "run-p type-check \"build-only {@}\" --", "build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"build:local": "vite build --mode local",
"build:prod": "vite build --mode prod",
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
"dependencies": { "dependencies": {
@ -48,4 +50,4 @@
"vite-plugin-vue-devtools": "^7.7.2", "vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.2" "vue-tsc": "^2.2.2"
} }
} }

11
src/api/module/firmware/index.ts

@ -3,19 +3,20 @@ import { globalServer } from '../index'
export const uploadFirmwareFile = (params: File, abort: AbortController) => export const uploadFirmwareFile = (params: FormData, abort: AbortController) =>
globalServer({ globalServer({
url: 'api/upload', url: 'api/upload',
method: 'POST', method: 'POST',
data: params, data: params,
signal: abort.signal, signal: abort.signal,
headers: { headers: { 'Content-Type': 'multipart/form-data' },
'Content-Type': 'multipart/form-data', timeout: 0,
},
}) })
export const getFirmwarePath = () => globalServer<string>({ export const getFirmwarePath = () => globalServer<{
path: string
}>({
url: 'api/package-path', url: 'api/package-path',
method: 'GET', method: 'GET',
}) })

1
src/api/module/transfer/index.ts

@ -6,6 +6,7 @@ interface IGetDeviceDataParams {
limit?: number limit?: number
offset?: number offset?: number
host?: string host?: string
name: string
} }

16
src/composables/useZMQJsonWorker.ts

@ -2,7 +2,7 @@ import { WorkerCMD, ZmqCMD, } from '@/utils/zmq'
import type { ManualAction, PublishMsg, PubMsgData, SubMsgData, TimeoutMsg, ZmqMessage } from '@/utils/zmq' import type { ManualAction, PublishMsg, PubMsgData, SubMsgData, TimeoutMsg, ZmqMessage } from '@/utils/zmq'
import webWorker from '@/utils/zmqJsonWorker?worker' import webWorker from '@/utils/zmqJsonWorker?worker'
const defaultHost = import.meta.env.PROD ? window.location.hostname : '192.168.1.115' const defaultHost = import.meta.env.PROD ? window.location.hostname : '192.168.1.199'
class ZMQJsonWorker { class ZMQJsonWorker {
private static instance: ZMQJsonWorker | null = null; // ➤ 单例实例 private static instance: ZMQJsonWorker | null = null; // ➤ 单例实例
@ -11,6 +11,7 @@ class ZMQJsonWorker {
private pubTimeoutHandlers: Map<string, (msg: TimeoutMsg) => void> = new Map(); private pubTimeoutHandlers: Map<string, (msg: TimeoutMsg) => void> = new Map();
private host: string; private host: string;
private statusCallback: ((status: string) => void) | null = null; private statusCallback: ((status: string) => void) | null = null;
private isAlwaysListenMsgMap: Map<string, PublishMsg<any>> = new Map();
private constructor(host: string = defaultHost) { private constructor(host: string = defaultHost) {
this.host = host; this.host = host;
@ -51,7 +52,7 @@ class ZMQJsonWorker {
this.worker.postMessage({ cmd: WorkerCMD.UNSUBSCRIBE, topic }); this.worker.postMessage({ cmd: WorkerCMD.UNSUBSCRIBE, topic });
} }
publish<T extends ManualAction>(topic: string, msg: PublishMsg<T>, isTimeout: boolean = false, handler?: (msg: TimeoutMsg) => void) { publish<T extends ManualAction>(topic: string, msg: PublishMsg<T>, isTimeout: boolean = false, handler?: (msg: TimeoutMsg) => void, isAlwaysListen: boolean = false) {
if (isTimeout) { if (isTimeout) {
const timeoutId = msg.id const timeoutId = msg.id
if (typeof handler !== 'function') { if (typeof handler !== 'function') {
@ -60,7 +61,10 @@ class ZMQJsonWorker {
} }
this.pubTimeoutHandlers.set(timeoutId, handler) this.pubTimeoutHandlers.set(timeoutId, handler)
} }
this.worker.postMessage({ cmd: WorkerCMD.PUBLISH, topic, msg: JSON.stringify(msg), isTimeout }); if (isAlwaysListen) {
this.isAlwaysListenMsgMap.set(`${msg.id}`, msg)
}
this.worker.postMessage({ cmd: WorkerCMD.PUBLISH, topic, msg: JSON.stringify(msg), isTimeout, isAlwaysListen });
} }
setStatusCallback(callback: (status: string) => void) { setStatusCallback(callback: (status: string) => void) {
@ -117,8 +121,10 @@ class ZMQJsonWorker {
this.handleSubscribeMessage(`${topic}-${json.id}`, json); this.handleSubscribeMessage(`${topic}-${json.id}`, json);
// 删除发布消息的回调 // 删除发布消息的回调
if (Object.keys(json).includes('result') && json.result !== 'progress') { if (Object.keys(json).includes('result') && json.result !== 'progress') {
this.GC_pubReleaseSub(`${topic}-${json.id}`) if (!this.isAlwaysListenMsgMap.has(`${json.id}`)) {
this.GC_pubReleaseTimeout(`${json.id}`) this.GC_pubReleaseSub(`${topic}-${json.id}`)
this.GC_pubReleaseTimeout(`${json.id}`)
}
} }
} }

62
src/stores/transferData.ts

@ -35,22 +35,27 @@ export const useTransferDataStore = defineStore('transfer', () => {
function getSubDevicesCb(msg: SubMsgData) { function getSubDevicesCb(msg: SubMsgData) {
const { feedback } = msg const { feedback } = msg
const sn = feedback[1] const sn = feedback[1]
const device: IOnlineDevice = {
clientIp: feedback[0],
sn: sn,
stationName: feedback[2],
footprint: feedback[3] ?? '--',
lastUpdated: Date.now(),
status: '在线', // 初始状态为在线
isChecked: false,
}
const hasDevice = devicesMap.get(sn)
if (hasDevice) {
hasDevice.lastUpdated = Date.now()
hasDevice.status = '在线'
} else {
const device: IOnlineDevice = {
clientIp: feedback[0],
sn: sn,
stationName: feedback[2],
footprint: feedback[3] ?? '--',
lastUpdated: Date.now(),
status: '在线', // 初始状态为在线
isChecked: false,
}
devicesMap.set(sn, device)
}
devicesMap.set(sn, device)
isConnected.value = devicesMap.size > 0 isConnected.value = devicesMap.size > 0
} }
const onlineCount = computed(() => { const onlineCount = computed(() => {
return Array.from(devicesMap.values()).filter(item => item.status === '在线') return Array.from(devicesMap.values()).filter(item => item.status === '在线')
.length .length
@ -61,9 +66,6 @@ export const useTransferDataStore = defineStore('transfer', () => {
.length .length
}) })
onMounted(() => { onMounted(() => {
worker.subscribe(getDeviceTopic, getSubDevicesCb) worker.subscribe(getDeviceTopic, getSubDevicesCb)
checkDeviceStatus() checkDeviceStatus()
@ -84,14 +86,30 @@ export const useTransferDataStore = defineStore('transfer', () => {
const device = devicesMap.get(sn) const device = devicesMap.get(sn)
if (device) { if (device) {
device.upFirmware = 'updating' device.upFirmware = 'updating'
const step = feedback[0] const step = feedback[1]
const progress = feedback[2] || undefined
const errMsg = feedback[3] || undefined
if (step < (device.upFirmwareStatus?.step ?? -100)) return
device.upFirmwareStatus = {
step,
progress: progress === -1 ? 100 : progress,
errMsg,
}
}
}
function upFirmwareStatusReject(sn: string, feedback: any[]) {
const device = devicesMap.get(sn)
if (device) {
device.upFirmware = 'rejected'
const step = feedback[1]
const progress = feedback[2] || undefined const progress = feedback[2] || undefined
const errMsg = feedback[3] || undefined const errMsg = feedback[3] || undefined
if (step < (device.upFirmwareStatus?.step ?? -100)) return if (step < (device.upFirmwareStatus?.step ?? -100)) return
device.upFirmwareStatus = { device.upFirmwareStatus = {
step, step,
progress, progress: progress === -1 ? 100 : progress,
errMsg errMsg,
} }
} }
} }
@ -127,6 +145,14 @@ export const useTransferDataStore = defineStore('transfer', () => {
} }
} }
function upFirmwareTimeout(sn: string) {
const device = devicesMap.get(sn)
if (device) {
device.upFirmware = 'timeout'
device.upFirmwareStatus = undefined
}
}
return { return {
isConnected, isConnected,
@ -141,5 +167,7 @@ export const useTransferDataStore = defineStore('transfer', () => {
upFirmwareReset, upFirmwareReset,
upFirmwareStatus, upFirmwareStatus,
upFirmwareSucceed, upFirmwareSucceed,
upFirmwareStatusReject,
upFirmwareTimeout
} }
}) })

4
src/utils/zmq.ts

@ -69,11 +69,11 @@ export interface SubMsgData {
*/ */
export function getPubTopic(type: TopicType, module: string,) { export function getPubTopic(type: TopicType, module: string,) {
return `web/${type}/${module}` return `web/${type}/${module}/`
} }
export function getSubTopic(server: string, type: TopicType, module: string,) { export function getSubTopic(server: string, type: TopicType, module: string,) {
return `${server}/${type}/${module}` return `${server}/${type}/${module}/`
} }
// 获取随机id // 获取随机id

14
src/utils/zmqJsonWorker.ts

@ -5,7 +5,7 @@ import { WorkerCMD, ZmqCMD, type PublishMsg, type PubMsgData, } from './zmq'
const HEARTBEAT_TOPIC = 'HEARTBEAT' const HEARTBEAT_TOPIC = 'HEARTBEAT'
const HEARTBEAT_INTERVAL = 3000 const HEARTBEAT_INTERVAL = 3000
const STATUS_CHECK_INTERVAL = 1000 const STATUS_CHECK_INTERVAL = 1000
let messageTimeout = 10000 let messageTimeout = 5000
let heartClient: ZmqClient | null, subClient: ZmqClient | null, pubClient: ZmqClient | null let heartClient: ZmqClient | null, subClient: ZmqClient | null, pubClient: ZmqClient | null
let subHost = '', pubHost = '' let subHost = '', pubHost = ''
@ -78,8 +78,10 @@ function handleZmqMessage(topic: Uint8Array, msg: Uint8Array) {
msg: jsonMessage msg: jsonMessage
}) })
const parsedMessage = JSON.parse(jsonMessage) as PubMsgData const parsedMessage = JSON.parse(jsonMessage) as PubMsgData
if (parsedMessage.id && traceMessages.has(parsedMessage.id)) { // traceMessages.get(parsedMessage.id)
if (parsedMessage.result === 'progress') { const curTraceMessages = traceMessages.get(parsedMessage.id)
if (parsedMessage.id && !!curTraceMessages) {
if (curTraceMessages.isAlwaysListen || parsedMessage.result === 'progress') {
// 重置消息超时时间 // 重置消息超时时间
const val = traceMessages.get(parsedMessage.id) const val = traceMessages.get(parsedMessage.id)
if (val) { if (val) {
@ -106,7 +108,6 @@ function connect(host: string) {
heartClient = new ZmqClient('sub') heartClient = new ZmqClient('sub')
heartClient.zmqSub(subHost, updateHeartbeat) heartClient.zmqSub(subHost, updateHeartbeat)
heartClient.subscribe(HEARTBEAT_TOPIC) heartClient.subscribe(HEARTBEAT_TOPIC)
monitorConnection() monitorConnection()
pubHost = `ws://${host}:15556` pubHost = `ws://${host}:15556`
@ -131,7 +132,7 @@ function connect(host: string) {
const traceMessages = new Map<string, any>() const traceMessages = new Map<string, any>()
self.onmessage = function (event) { self.onmessage = function (event) {
const { cmd, topic, msg, isTimeout = false } = event.data const { cmd, topic, msg, isTimeout = false, isAlwaysListen } = event.data
switch (cmd) { switch (cmd) {
case WorkerCMD.START: case WorkerCMD.START:
@ -152,7 +153,8 @@ self.onmessage = function (event) {
const parseMsg = JSON.parse(msg) as PublishMsg<string> const parseMsg = JSON.parse(msg) as PublishMsg<string>
traceMessages.set(parseMsg.id, { traceMessages.set(parseMsg.id, {
timestamp: Date.now(), timestamp: Date.now(),
topic: topic topic: topic,
isAlwaysListen,
}) })
} }
pubClient?.publishStr(topic, msg) pubClient?.publishStr(topic, msg)

40
src/views/firmwareUpload/index.vue

@ -1,19 +1,9 @@
<template> <template>
<div class="flex justify-center items-center size-full"> <div class="flex justify-center items-center size-full">
<EdfsWrap title="固件上传" style="width: 50%; height: 50%"> <EdfsWrap title="固件上传" style="width: 50%; height: 50%">
<el-upload <el-upload v-model:fileList="fileList" v-loading="loading" element-loading-text="上传中..." drag action=""
v-model:fileList="fileList" accept=".tar.gz" :limit="1" :on-exceed="handleExceed" :auto-upload="false" ref="uploadRef"
v-loading="loading" class="h-[calc(100%-30px)] w-full">
element-loading-text="上传中..."
drag
action=""
accept=".tar.gz"
:limit="1"
:on-exceed="handleExceed"
:auto-upload="false"
ref="upload"
class="h-[calc(100%-30px)] w-full"
>
<div class="i-line-md:cloud-alt-upload-loop text-20px mx-auto"></div> <div class="i-line-md:cloud-alt-upload-loop text-20px mx-auto"></div>
<div class="text">拖拽文件或者 <em>点击上传</em></div> <div class="text">拖拽文件或者 <em>点击上传</em></div>
<template #tip v-if="!fileList.length"> <template #tip v-if="!fileList.length">
@ -21,9 +11,7 @@
</template> </template>
</el-upload> </el-upload>
<div class="flex justify-center"> <div class="flex justify-center">
<el-button type="primary" @click="onSave" v-show="fileList.length && !loading" <el-button type="primary" @click="onSave" v-show="fileList.length && !loading">确定上传</el-button>
>确定上传</el-button
>
<el-button type="info" v-show="loading" @click="onClone">取消上传</el-button> <el-button type="info" v-show="loading" @click="onClone">取消上传</el-button>
</div> </div>
</EdfsWrap> </EdfsWrap>
@ -52,11 +40,15 @@ const handleExceed: UploadProps['onExceed'] = files => {
upload.value!.handleStart(file) upload.value!.handleStart(file)
} }
const uploadRef = ref<UploadInstance>()
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => { const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
const accept = ['.zip', '.tar', '.tar.gz'] const accept = ['.zip', '.tar', '.tar.gz']
const fileTypes = rawFile.name.substring(rawFile.name.lastIndexOf('.')) const fileTypes = rawFile.name.substring(rawFile.name.indexOf('.'))
if (!accept.includes(fileTypes)) { if (!accept.includes(fileTypes)) {
message.error('请上传 tar.gz 文件') message.error('请上传 tar.gz 文件')
uploadRef.value?.clearFiles?.()
return false return false
} }
return true return true
@ -66,11 +58,8 @@ function validate() {
message.error('请上传文件') message.error('请上传文件')
return true return true
} }
if (beforeAvatarUpload(fileList.value[0].raw!)) {
message.error('请上传 tar.gz 文件') return beforeAvatarUpload(fileList.value[0].raw!)
return true
}
return false
} }
const loading = ref(false) const loading = ref(false)
const abortController = ref<AbortController>() const abortController = ref<AbortController>()
@ -83,9 +72,11 @@ async function onSave() {
if (!validate()) return if (!validate()) return
loading.value = true loading.value = true
abortController.value = new AbortController() abortController.value = new AbortController()
const data = new FormData()
data.append('file', fileList.value[0].raw as File)
const userFile = fileList.value[0] as UploadUserFile const userFile = fileList.value[0] as UploadUserFile
const res = await uploadFirmwareFile(userFile.raw as File, abortController.value) const res = await uploadFirmwareFile(data, abortController.value)
if (res.code === 200) { if (res.code === 0) {
message.success('上传成功') message.success('上传成功')
} else { } else {
message.error('上传失败') message.error('上传失败')
@ -175,6 +166,7 @@ function onClone() {
height: calc(100% - 50px); height: calc(100% - 50px);
width: 100%; width: 100%;
} }
:deep(.el-upload-dragger) { :deep(.el-upload-dragger) {
height: 100%; height: 100%;
width: 100%; width: 100%;

138
src/views/stationData/components/PointGroupTree.vue

@ -0,0 +1,138 @@
<template>
<div class=" h-full flex">
<div class="w-530">
<el-scrollbar class="fault-device-tree">
<el-tree :data="data" node-key="name" ref="treeRef" :expand-on-click-node="false" :props="defaultProps"
highlight-current @node-click="handleNodeClick" :default-expanded-keys="checkDefault">
<template #default="scope">
<div class="item">
<span class="label">{{ scope.data.name }}</span>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
<el-divider direction="vertical" class="h-full" />
<div class="w-440">
<el-scrollbar class="scroll">
<el-checkbox-group v-model="checkPointList" class="point-checks" @change="changePoints">
<template v-for="(item, index) in devicePoints" :key="index">
<el-checkbox :label="item.label" :value="item" />
</template>
</el-checkbox-group>
</el-scrollbar>
</div>
</div>
</template>
<script lang="ts" setup>
import type { ElTree } from 'element-plus'
import type { IPointGroupOV } from '@/api/module/transfer'
import type { IMyPoint } from '../type'
interface Props {
data: IPointGroupOV[]
devicePoints: Array<{
label: string
addr: string
}>
groupChangeLoading: boolean
}
const props = withDefaults(defineProps<Props>(), {
data: () => [],
groupChangeLoading: false,
})
const emit = defineEmits<{
'device-select': [IPointGroupOV]
'onchange-points': [IMyPoint[]]
}>()
const treeRef = ref<InstanceType<typeof ElTree>>()
const checkDefault = ref<string[]>([])
const handleNodeClick = (data: IPointGroupOV) => {
checkPointList.value = []
emit('device-select', data)
nextTick(() => {
changePoints()
})
}
const defaultProps = {
children: 'children',
label: 'name',
}
const checkPointList = ref<any>([])
function changePoints() {
emit('onchange-points', checkPointList.value)
}
onMounted(() => {
if (props.data.length) {
let defaultSelectDevice = props.data[0]
checkDefault.value = [defaultSelectDevice.type]
nextTick(() => {
treeRef.value?.setCurrentKey(defaultSelectDevice.name)
})
}
})
</script>
<style lang="scss">
.point-checks {
display: flex;
flex-direction: column;
align-items: start;
:deep(.el-checkbox__inner) {
width: 20px;
height: 20px;
}
:deep(.el-checkbox__inner::after) {
width: 4px;
height: 8px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(45deg);
border-width: 3px;
// border: 1px solid #ffffff;
}
}
</style>
<style lang="scss" scoped>
.fault-device-tree {
width: 100%;
height: 100%;
overflow: auto;
:deep(.el-tree-node__content) {
height: 40px;
font-size: 16px;
color: var(--label-text);
border-left: 2px solid transparent;
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background-color: rgba(174, 230, 114, 0.12);
border-left: 2px solid #619925;
}
}
:deep(.el-divider--vertical) {
height: 100%;
margin: 0;
}
.scroll {
height: 100%;
padding: 10px;
}
</style>

169
src/views/stationData/components/deviceDrawer.vue

@ -1,27 +1,15 @@
<template> <template>
<div class="fault-rule-drawer"> <div class="fault-rule-drawer">
<el-drawer <el-drawer v-model="isShowDrawer" :title="title" direction="rtl" size="90%" modal-class="model-dev-opn"
v-model="isShowDrawer" :before-close="handleBeforeClose" @opened="onDrawerOpened">
:title="title" <main class="wh-full flex">
direction="rtl" <EdfsWrap title="点位组" class="p-r-4 h-full border-r-1 border-solid border-r-#e4e7ed">
size="60%" <PointGroupTree v-if="isShowDrawer" :data="pointGroup" @device-select="onGroupChange"
modal-class="model-dev-opn" :groupChangeLoading="groupChangeLoading" @onchangePoints="onchangePoints" :device-points="pointList"
:before-close="handleBeforeClose" :isTransfer="props.isTransfer" :siteInfo="props.siteInfo" ref="pointGroupTreeRef" />
> </EdfsWrap>
<main class="drawer-box"> <NewDataChart v-if="isShowChart" :chart-datas="chartDatas" :legends="checkPointList"
<el-button-group class="ml-4"> :axis-data="Array.from(axisData)" ref="chartRef" />
<template v-for="item in pointGroup" :key="item.module">
<el-button class="ml-4" @click="onGroupChange(item)">
{{ item.type }}
</el-button>
</template>
</el-button-group>
<NewDataChart
:chart-datas="chartDatas"
:legends="tableCol"
:axis-data="Array.from(axisData)"
v-if="isShowDrawer"
/>
</main> </main>
</el-drawer> </el-drawer>
</div> </div>
@ -29,6 +17,7 @@
<script setup lang="ts"> <script setup lang="ts">
import NewDataChart from './newDataChart.vue' import NewDataChart from './newDataChart.vue'
import PointGroupTree from './PointGroupTree.vue'
import { import {
getPubInitData, getPubInitData,
type ManualAction, type ManualAction,
@ -36,7 +25,7 @@ import {
type TimeoutMsg, type TimeoutMsg,
} from '@/utils/zmq' } from '@/utils/zmq'
import ZMQWorker from '@/composables/useZMQJsonWorker' import ZMQWorker from '@/composables/useZMQJsonWorker'
import type { IOfflineDevice, IOnlineDevice } from '../type' import type { IMyPoint, IOfflineDevice, IOnlineDevice } from '../type'
import { import {
getDeviceDetails, getDeviceDetails,
getPointGroup, getPointGroup,
@ -48,12 +37,14 @@ import {
} from '@/api/module/transfer' } from '@/api/module/transfer'
import { useMessage } from '@/composables/useMessage' import { useMessage } from '@/composables/useMessage'
import { getTransferTopic, postTransferTopic } from '../utils' import { getTransferTopic, postTransferTopic } from '../utils'
import dayjs from 'dayjs'
const worker = ZMQWorker.getInstance() const worker = ZMQWorker.getInstance()
const isShowDrawer = defineModel<boolean>() const isShowDrawer = defineModel<boolean>()
const title = computed(() => (props.isTransfer ? '数据详情' : `已迁移数据详情`)) const title = computed(() => (props.isTransfer ? '数据详情' : `已迁移数据详情`))
const message = useMessage() const message = useMessage()
const pointGroupTreeRef = ref<InstanceType<typeof PointGroupTree>>()
const props = defineProps<{ const props = defineProps<{
siteInfo: ISite | null siteInfo: ISite | null
isTransfer: boolean isTransfer: boolean
@ -65,17 +56,22 @@ const pubIdWithDevice = new Map<
{ device: IOfflineDevice | IOnlineDevice; action: ManualAction } { device: IOfflineDevice | IOnlineDevice; action: ManualAction }
>() >()
const tableCol = ref<any[]>([]) const pointList = ref<IMyPoint[]>([])
const curDevice = ref<IOfflineDevice | IOnlineDevice>() const curDevice = ref<IOfflineDevice | IOnlineDevice>()
function open(device: IOfflineDevice | IOnlineDevice) { async function open(device: IOfflineDevice | IOnlineDevice) {
curDevice.value = device curDevice.value = device
props.isTransfer ? loadDeviceDetails() : zmqImport(device as IOfflineDevice) await loadPointGroup()
.then(async () => {
props.isTransfer ? await loadDeviceDetails() : zmqImport(device as IOfflineDevice)
})
.catch(() => {
message.error('获取点位组数据失败')
fullscreenLoading.value?.close()
})
} }
const pointsData = ref<{ addr: string; label: string }[]>([]) const columsParams = computed(() => ['ts', ...pointList.value.map(i => i.addr)])
const columsParams = computed(() => ['ts', ...tableCol.value.map(i => i.addr)])
async function loadPoints() { async function loadPoints() {
if (!curGroup.value) { if (!curGroup.value) {
message.error('请先选择点位组') message.error('请先选择点位组')
@ -96,58 +92,48 @@ async function loadPoints() {
} }
const res = await getPoints(params) const res = await getPoints(params)
if (res.code === 0) { if (res.code === 0) {
pointsData.value = res.data pointList.value = res.data.map((i: any) => ({
tableCol.value = res.data.map((i: any) => ({ label: i.cnName,
label: i.point_name, addr: i.addr,
addr: i.point_id,
})) }))
tableCol.value.push({ pointList.value.push({
label: '时间', label: '时间',
addr: 'ts', addr: 'ts',
}) })
} }
return res return res
} }
const chartDatas = new Map<string, any[]>()
const axisData = new Set<string>()
const pointData = ref<any[]>([])
async function loadDeviceDetails() { async function loadDeviceDetails() {
if (!fullscreenLoading.value) { if (!fullscreenLoading.value) {
openFullScreen() openFullScreen()
} }
const poinsRes = await loadPoints() const poinsRes = await loadPoints()
if (!poinsRes || poinsRes.code !== 0) { if (!poinsRes || poinsRes.code !== 0) {
message.error('获取点位数据失败') message.error('获取点位数据失败')
fullscreenLoading.value?.close() fullscreenLoading.value?.close()
return return
} }
const res = await getDeviceDetails({ const res = await getDeviceDetails({
columns: columsParams.value, columns: columsParams.value,
isLocal: props.isTransfer ? false : true, isLocal: props.isTransfer ? false : true,
host: props.isTransfer ? (curDevice.value as IOnlineDevice).clientIp : '', host: props.isTransfer ? (curDevice.value as IOnlineDevice).clientIp : '',
name: curGroupName.value as string
}) })
if (res.code === 0) { if (res.code === 0) {
res.data.results.forEach((data: any[]) => { pointData.value = Array.isArray(res.data.results) ? res.data.results : []
columsParams.value.forEach((col, idx) => {
if (idx === 0) {
axisData.add(data[idx])
}
const ts = data[0]
if (col !== 'ts') {
const colData = chartDatas.get(col)
if (colData) {
colData.push([ts, data[idx]])
} else {
chartDatas.set(col, [[ts, data[idx]]])
}
}
})
})
isShowDrawer.value = true isShowDrawer.value = true
} else {
message.error('获取设备数据失败')
} }
groupChangeLoading.value = false
fullscreenLoading.value?.close() fullscreenLoading.value?.close()
} }
@ -191,12 +177,21 @@ function zmqTimeoutCb(msg: TimeoutMsg) {
function handleBeforeClose(done: () => void) { function handleBeforeClose(done: () => void) {
isShowDrawer.value = false isShowDrawer.value = false
tableCol.value = [] pointList.value = []
chartDatas.clear() chartDatas.clear()
axisData.clear() axisData.clear()
fullscreenLoading.value = null fullscreenLoading.value = null
done() done()
} }
const chartRef = ref<InstanceType<typeof NewDataChart>>()
const isShowChart = ref(false)
function onDrawerOpened() {
nextTick(() => {
isShowChart.value = true
})
}
const fullscreenLoading = ref<any>(null) const fullscreenLoading = ref<any>(null)
const openFullScreen = () => { const openFullScreen = () => {
fullscreenLoading.value = ElLoading.service({ fullscreenLoading.value = ElLoading.service({
@ -208,9 +203,9 @@ const openFullScreen = () => {
const pointGroup = ref<IPointGroupOV[]>([]) const pointGroup = ref<IPointGroupOV[]>([])
const curGroup = ref<string>() const curGroup = ref<string>()
const curGroupName = ref<string>()
async function loadPointGroup() { async function loadPointGroup() {
if (!props.siteInfo) return
const params: IPointGroupParams = {} const params: IPointGroupParams = {}
if (props.isTransfer) { if (props.isTransfer) {
const onlineDevice = curDevice.value as IOnlineDevice const onlineDevice = curDevice.value as IOnlineDevice
@ -225,23 +220,54 @@ async function loadPointGroup() {
const res = await getPointGroup(params) const res = await getPointGroup(params)
if (res.code === 0) { if (res.code === 0) {
curGroup.value = res.data[0]?.type
curGroupName.value = res.data[0]?.name
pointGroup.value = Array.isArray(res?.data) ? res.data : [] pointGroup.value = Array.isArray(res?.data) ? res.data : []
if (res.data.length > 0) { return Promise.resolve()
curGroup.value = res.data[0].type
}
} else { } else {
message.error('获取点位组数据失败') return Promise.reject()
} }
} }
function onGroupChange(item: IPointGroupOV) {} const groupChangeLoading = ref<boolean>(false)
function onGroupChange(item: IPointGroupOV) {
onMounted(async () => { if (!item?.type) {
await loadPointGroup() return
if (props.isTransfer) {
await loadDeviceDetails()
} }
}) groupChangeLoading.value = true
curGroup.value = item.type
curGroupName.value = item.name
loadDeviceDetails()
}
const chartDatas = reactive(new Map<string, any[]>())
const axisData = new Set<string>()
const checkPointList = ref<IMyPoint[]>([])
function onchangePoints(checkPoints: IMyPoint[]) {
checkPointList.value = checkPoints
chartDatas.clear()
axisData.clear()
pointData.value.forEach((data: any[]) => {
const [ts, val, addr] = data
if (checkPoints.some(i => i.addr === addr)) {
const time = dayjs(Number(ts)).format('YYYY-MM-DD HH:mm:ss')
if (addr) {
const colData = chartDatas.get(addr)
if (colData) {
colData.push([time, val])
} else {
chartDatas.set(addr, [[time, val]])
}
}
if (ts) {
axisData.add(time)
}
}
})
}
defineExpose({ defineExpose({
open, open,
@ -253,13 +279,12 @@ defineExpose({
.fault-rule-drawer { .fault-rule-drawer {
font-size: 16px; font-size: 16px;
:deep(.el-drawer__header) { :deep(.edfs-wrap) {
color: var(--text-color); width: auto;
} }
.drawer-box { :deep(.el-drawer__header) {
width: 100%; color: var(--text-color);
height: 100%;
} }
} }
</style> </style>

19
src/views/stationData/components/newDataChart.vue

@ -1,14 +1,7 @@
<template> <template>
<div class="device-data-chart"> <div class="device-data-chart">
<v-chart <v-chart class="chart" :option="chartOption" :autoresize="autoresize" :loading-options="loadingOpt"
class="chart" :loading="loading" ref="chartRef" @legendselectchanged="changeLegend" />
:option="chartOption"
:autoresize="autoresize"
:loading-options="loadingOpt"
:loading="loading"
ref="chartRef"
@legendselectchanged="changeLegend"
/>
</div> </div>
</template> </template>
@ -65,7 +58,7 @@ const props = defineProps({
const loading = ref(true) const loading = ref(true)
const loadingOpt = { const loadingOpt = {
type: 'default', type: 'default',
text: '暂无数据', text: '请选择点位',
color: '#c23531', color: '#c23531',
textColor: '#666', textColor: '#666',
maskColor: 'rgba(0, 0, 0, 0)', maskColor: 'rgba(0, 0, 0, 0)',
@ -79,7 +72,6 @@ const chartOption = computed<EChartsOption>(() => {
return {} return {}
} }
loading.value = false loading.value = false
const tmpSeries: SeriesOption[] = [] const tmpSeries: SeriesOption[] = []
for (const legend of props.legends.filter(item => item.addr !== 'ts')) { for (const legend of props.legends.filter(item => item.addr !== 'ts')) {
const entry = props.chartDatas.get(legend.addr) const entry = props.chartDatas.get(legend.addr)
@ -97,6 +89,8 @@ const chartOption = computed<EChartsOption>(() => {
} }
tmpSeries.push(lineData) tmpSeries.push(lineData)
} }
const option: EChartsOption = { const option: EChartsOption = {
grid: { grid: {
left: 60, left: 60,
@ -211,10 +205,7 @@ function changeLegend(data: { name: string; selected: Record<string, boolean> })
} else { } else {
unCheckArr.value = unCheckArr.value.filter(item => item !== name) unCheckArr.value = unCheckArr.value.filter(item => item !== name)
} }
console.log(unCheckArr.value)
} }
// useWindowResize(handleResize)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

35
src/views/stationData/components/siteTransferDlg.vue → src/views/stationData/components/offTransferDlg.vue

@ -13,17 +13,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs'
import { getPubInitData, type PublishMsg } from '@/utils/zmq' import { getPubInitData, type PublishMsg } from '@/utils/zmq'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { useMessage } from '@/composables/useMessage' import { useMessage } from '@/composables/useMessage'
import type { IOfflineDevice } from '../type'
import type { ISite } from '@/api/module/transfer' import type { ISite } from '@/api/module/transfer'
const message = useMessage() const message = useMessage()
const emit = defineEmits<{ const emit = defineEmits<{
'on-save': [msg: PublishMsg<'import'>, site: ISite] 'on-save': [msg: PublishMsg<'import'>, site: IOfflineDevice]
}>()
const props = defineProps<{
siteInfo: ISite | null
}>() }>()
@ -32,20 +36,20 @@ const fromData = {
clientIp: '', clientIp: '',
} }
const curSite = ref<ISite>() const curOffDeive = ref<IOfflineDevice>()
const form = ref(cloneDeep(fromData)) const form = ref(cloneDeep(fromData))
function open(item: ISite) { function open(item: IOfflineDevice) {
curSite.value = item curOffDeive.value = item
visible.value = true visible.value = true
} }
function onSave() { function onSave() {
if (!verifyData()) return if (verifyData()) return
if (!curSite.value) { if (!curOffDeive.value) {
message.error('请选择设备') message.error('请选择设备')
return return
} }
@ -56,26 +60,33 @@ function onSave() {
'', '',
'', '',
'', '',
`${curSite.value.export_root_path}`, `${props.siteInfo!.name}/${curOffDeive.value.sn}`,
] ]
const msg = getPubInitData<'import'>('import', params) const msg = getPubInitData<'import'>('import', params)
emit('on-save', msg, curSite.value) emit('on-save', msg, curOffDeive.value)
close() close()
} }
function close() { function close() {
form.value = cloneDeep(fromData) form.value = cloneDeep(fromData)
curSite.value = undefined curOffDeive.value = undefined
visible.value = false visible.value = false
} }
const ipPattern =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
function verifyData() { function verifyData() {
if (!form.value.clientIp) { if (!form.value.clientIp) {
message.error('请输入客户端IP') message.error('请输入客户端IP')
return false return true
}
if (!ipPattern.test(form.value.clientIp)) {
message.error('请输入正确的IP地址')
return true
} }
return true return false
} }
defineExpose({ defineExpose({

39
src/views/stationData/components/transferMask.vue

@ -0,0 +1,39 @@
<template>
<div class="transfer-mask absolute left-0 top-0 wh-full z-10 bg-#FFF-90 h-full w-full" v-if="isShowMask">
<el-button class="absolute r-4 t-4" type="info" @click="onclose" v-show="!transferLoading">关闭</el-button>
<div class="wh-full flex-col justify-center items-center">
<span class="absolute top-0 left-1/2 -translate-x-1/2 mt-2 text-red">数据传输中请不要刷新或关闭页面!!!</span>
<div class="w-300px h-300px flex-col justify-center items-center" v-if="transferLoading">
<div class="el-loading-spinner">
<svg class="circular" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<div class="mt-28">
<div class="text-14px text-#999999">指令下发中请稍后... </div>
</div>
</div>
<template v-else>
<slot></slot>
</template>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits<{
'close': []
}>()
defineProps<{
transferLoading: boolean
}>()
const isShowMask = defineModel<boolean>()
function onclose() {
emit('close')
}
</script>
<style lang="scss" scoped></style>

179
src/views/stationData/index.vue

@ -1,48 +1,47 @@
<template> <template>
<div class="flex-col gap-16 wh-full"> <div class="flex-col gap-16 wh-full">
<EdfsWrap title="当前连接站点" v-if="connectSite?.title" shape="circle" shapeColor="#4B9E5F" class="h-auto"> <template v-if="env.VITE_APP_ENV == 'local'">
<div class="station-list-flow"> <EdfsWrap title="当前连接站点" v-if="connectSite?.title" shape="circle" shapeColor="#4B9E5F" class="h-auto">
<div class="station-item"> <div class="station-list-flow">
<div class="title bg-[#4B9E5F]"> <div class="station-item">
<div>{{ connectSite.title }}</div> <div class="title bg-[#4B9E5F]">
</div> <div>{{ connectSite.title }}</div>
<div class="body"> </div>
<div class="info"> <div class="body">
<div class="info-item"> <div class="info">
<div class="info-item-label">在线设备</div> <div class="info-item">
<div class="info-item-value"> <div class="info-item-label">在线设备</div>
<div class="i-octicon:cloud-16 color-[#4B9E5F] text-16px"></div> <div class="info-item-value">
{{ onlineCount }} <div class="i-octicon:cloud-16 color-[#4B9E5F] text-16px"></div>
{{ onlineCount }}
</div>
</div> </div>
</div> <div class="info-item">
<div class="info-item"> <div class="info-item-label">离线设备</div>
<div class="info-item-label">离线设备</div> <div class="info-item-value">
<div class="info-item-value"> <div class="i-octicon:cloud-offline-16 color-[#F44336] text-16px"></div>
<div class="i-octicon:cloud-offline-16 color-[#F44336] text-16px"></div> {{ offlineCount }}
{{ offlineCount }} </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="footer row-end-0">
<div class="footer row-end-0"> <div class="m-l-auto p-b-8">
<div class="m-l-auto p-b-8"> <el-button type="primary" style="height: 28px; padding: 0 12px" color="#4B9E5F"
<el-button type="primary" style="height: 28px; padding: 0 12px" color="#4B9E5F" @click="onTransferData">迁移数据</el-button>
@click="onTransferData">迁移数据</el-button> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </EdfsWrap>
</EdfsWrap> </template>
<EdfsWrap title="迁移历史" shape="circle" shapeColor="#F1BF63" class="flex-1" useScrollBar> <EdfsWrap title="迁移历史" shape="circle" shapeColor="#F1BF63" class="flex-1" useScrollBar>
<div class="station-list-flow"> <div class="station-list-flow">
<div class="station-item" v-for="item in siteList" :key="item.id"> <div class="station-item" v-for="item in siteList" :key="item.id">
<div class="title bg-[#F1BF63]"> <div class="title bg-[#F1BF63]">
{{ item.name }} {{ item.name }}
<div class="flex items-center gap-col-2"> <div class="flex items-center gap-col-2">
<el-tooltip content="数据迁移">
<i class="i-mdi:database-arrow-right-outline :hover:color-[#ddd] color-[#FFFFFF] cursor-pointer text-20px"
@click="openTransferDlg(item)"></i>
</el-tooltip>
<el-tooltip content="详情"> <el-tooltip content="详情">
<div <div
class="i-material-symbols:info-outline :hover:color-[#ddd] color-[#FFFFFF] cursor-pointer text-20px" class="i-material-symbols:info-outline :hover:color-[#ddd] color-[#FFFFFF] cursor-pointer text-20px"
@ -70,38 +69,7 @@
</div> </div>
</EdfsWrap> </EdfsWrap>
</div> </div>
<div class="transfer-mask absolute left-0 top-0 wh-full z-10 bg-#FFF-90 h-full w-full"
v-if="isSiteTransfer || transferLoading">
<div class="wh-full flex-col justify-center items-center">
<span class="mt-2 text-red">导出中请不要刷新或关闭页面!!!</span>
<div class="w-300px h-300px flex-col justify-center items-center" v-if="transferLoading">
<div class="el-loading-spinner"><svg class="circular" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none"></circle>
</svg></div>
<div class="mt-28">
<div class="text-14px text-#999999">指令下发中请稍后...</div>
</div>
</div>
<div class="w-56% h-full flex justify-center items-center" v-if="isSiteTransfer && !transferLoading">
<div class="flex-col gap-row-4 w-full">
<div class="flex items-center gap-col-1">
<div class="flex-1 items-center">
<el-progress :percentage="100" class="flex-1" :stroke-width="12" striped striped-flow :duration="20">
<div class="text-16px font-500">数据迁移中...</div>
</el-progress>
</div>
</div>
<div class="h-420 border-radius-8px bg-[#F9FAFB] p-10">
<div class="text-16px font-500">迁移日志</div>
<el-scrollbar class="h-full">
<div v-for="i in siteTransferLogList"> {{ i }} </div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</div>
<SiteTransferDlg ref="siteTransferDlgRef" :isBatchTransfer="false" @on-save="onSiteTransfer"></SiteTransferDlg>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -109,15 +77,8 @@ import dayjs from 'dayjs'
import { useTransferDataStore } from '@/stores/transferData' import { useTransferDataStore } from '@/stores/transferData'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { getSiteList, type ISite } from '@/api/module/transfer' import { getSiteList, type ISite } from '@/api/module/transfer'
import SiteTransferDlg from './components/siteTransferDlg.vue' const env = import.meta.env
import type { PublishMsg, PubMsgData, TimeoutMsg } from '@/utils/zmq'
import { getTransferTopic, postTransferTopic } from './utils'
import ZMQWorker from '@/composables/useZMQJsonWorker'
import { useMessage } from '@/composables/useMessage'
const message = useMessage()
const worker = ZMQWorker.getInstance()
const router = useRouter() const router = useRouter()
const transferDataStore = useTransferDataStore() const transferDataStore = useTransferDataStore()
@ -152,23 +113,7 @@ function onSiteDetails(site: ISite) {
const isSiteTransfer = ref(false) const isSiteTransfer = ref(false)
const transferLoading = ref(false) const transferLoading = ref(false)
const siteList = ref<ISite[]>([])
const siteList = ref<ISite[]>([
{
id: '1',
name: '站点1',
export_root_path: '/data/transfer/site112312312312',
export_time: '2023-10-01T12:00:00Z',
last_modify_time: '2023-10-01T12:00:00Z',
},
{
id: '2',
name: '站点2',
export_root_path: '/data/transfer/site2',
export_time: '2023-10-02T12:00:00Z',
last_modify_time: '2023-10-02T12:00:00Z',
}
])
async function loadSiteList() { async function loadSiteList() {
const res = await getSiteList() const res = await getSiteList()
if (res.code === 200 || res.code === 0) { if (res.code === 200 || res.code === 0) {
@ -176,63 +121,6 @@ async function loadSiteList() {
} }
} }
const siteTransferDlgRef = ref<typeof SiteTransferDlg>()
function openTransferDlg(item: ISite) {
siteTransferDlgRef.value?.open(item)
}
const pubIdWithSite = new Map<
string,
{ site: ISite; action: 'import' }
>()
function onSiteTransfer(msg: PublishMsg<'import'>, site: ISite) {
pubIdWithSite.set(msg.id, { site, action: 'import' })
worker.publish(postTransferTopic, msg, true, zmqTimeoutCb)
worker.subscribe(getTransferTopic, zmqImportCb, msg.id)
transferLoading.value = true
isSiteTransfer.value = true
}
const siteTransferLogList = ref<string[]>([])
function zmqImportCb(msg: PubMsgData) {
const { id, result, feedback } = msg
const { site, action } = pubIdWithSite.get(id)!
if (action !== 'import' || !site) return
transferLoading.value = false
if (result === 'progress') {
const log: string = Array.isArray(feedback) ? feedback[0] || '' : ''
siteTransferLogList.value.push(log)
}
if (result !== 'progress') {
if (result === 'success') {
isSiteTransfer.value = false
message.error(`导出数据成功`)
} else {
isSiteTransfer.value = false
message.error(`导出数据失败`)
}
}
}
function zmqTimeoutCb(msg: TimeoutMsg) {
const { site, action } = pubIdWithSite.get(msg.timeoutId)!
if (site && action === 'import') {
message.error(`站点:${site.name}数据导出超时,请稍后重试`)
pubIdWithSite.delete(msg.timeoutId)
isSiteTransfer.value = false
transferLoading.value = false
}
}
onMounted(() => { onMounted(() => {
initConnectSite() initConnectSite()
loadSiteList() loadSiteList()
@ -254,7 +142,6 @@ onMounted(() => {
column-gap: 20px; column-gap: 20px;
row-gap: 24px; row-gap: 24px;
.station-item { .station-item {
width: 280px; width: 280px;
height: 180px; height: 180px;
@ -285,8 +172,6 @@ onMounted(() => {
} }
} }
.body { .body {
background-color: var(--station-card-bg); background-color: var(--station-card-bg);
display: flex; display: flex;

309
src/views/stationData/transferData.vue

@ -19,36 +19,26 @@
<div class="device-item" v-for="item in devices"> <div class="device-item" v-for="item in devices">
<div class="device-item-header"> <div class="device-item-header">
<div class="flex items-center"> <div class="flex items-center">
<el-checkbox :value="item.sn" v-if="isBatchTransfer || isBatchUpgrade"> <el-checkbox :value="item.sn" v-if="(isBatchTransfer || isBatchUpgrade) && item.status !== '离线'">
<div>设备ID: {{ item.sn }}</div> <div>设备ID: {{ item.sn }}</div>
</el-checkbox> </el-checkbox>
<div v-else> <div v-else class="h-32 leading-32px">
<div>设备ID: {{ item.sn }}</div> <div>设备ID: {{ item.sn }}</div>
</div> </div>
</div> </div>
<div class="flex items-center gap-col-2" v-if="!(isBatchTransfer || isBatchUpgrade)">
<div class="flex items-center gap-col-2"> <el-tooltip content="数据迁移" v-if="isTransfer ? item.status === '在线' : true">
<el-tooltip <i class="i-mdi:database-arrow-right-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
content="数据迁移" @click="onTransfer(item)"></i>
v-if="isTransfer && item.status === '在线'"
>
<i
class="i-mdi:database-arrow-right-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
@click="onTransfer(item)"
></i>
</el-tooltip> </el-tooltip>
<!-- v-if="isTransfer && item.status === '在线'" --> <el-tooltip content="固件升级" v-if="isTransfer && item.status === '在线'">
<el-tooltip content="固件升级"> <i class="i-codicon:chip :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
<i @click="onFirmwareUpload([item])"></i>
class="i-codicon:chip :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
@click="onFirmwareUpload([item])"
></i>
</el-tooltip> </el-tooltip>
<el-tooltip content="详情"> <el-tooltip content="详情">
<div <div
class="i-material-symbols:info-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px" class="i-material-symbols:info-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
@click="onDeviceDetails(item)" @click="onDeviceDetails(item)"></div>
></div>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
@ -84,15 +74,10 @@
</div> </div>
</template> </template>
</template> </template>
<div <div class="absolute l-0 t-0 w-full h-full z-10 bg-#FFF-90"
class="absolute l-0 t-0 w-full h-full z-10 bg-#FFF-90" v-if="['updating', 'pending', 'rejected', 'timeout'].includes(item.upFirmware)">
v-if="['updating', 'pending', 'rejected'].includes(item.upFirmware)" <div class="i-material-symbols-light:close absolute-rt text-base text-gray-950 cursor-pointer"
> v-if="['timeout', 'rejected'].includes(item.upFirmware)" @click="upFirmwareSucceed(item.sn)"></div>
<div
class="i-material-symbols-light:close absolute-rt text-base text-gray-950 cursor-pointer"
v-if="item.upFirmware === 'rejected'"
@click="upFirmwareSucceed(item.sn)"
></div>
<template v-if="item.upFirmware === 'updating'"> <template v-if="item.upFirmware === 'updating'">
<div class="device-item-body"> <div class="device-item-body">
<div class="info-item"> <div class="info-item">
@ -101,105 +86,118 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<div>当前进度:</div> <div>当前进度:</div>
<el-progress <el-progress class="flex-1" :stroke-width="12" :show-text="true"
class="flex-1" :percentage="item.upFirmwareStatus?.progress || 0" />
:stroke-width="12"
:show-text="true"
:percentage="item.upFirmwareStatus?.progress || 0"
/>
</div> </div>
</div> </div>
</template> </template>
<template v-else-if="item.upFirmware === 'pending'"> <template v-else-if="item.upFirmware === 'pending'">
<div class="w-full h-full flex items-center justify-center"> <div class="w-full h-full flex items-center justify-center">
<div class="font-400 text-base">等待升级中...</div> <div class="font-400 text-base text-[#4B9E5F]">等待升级中...</div>
</div> </div>
</template> </template>
<template v-if="item.upFirmware === 'rejected'"> <template v-if="item.upFirmware === 'rejected'">
<div class="device-item-body"> <div class="device-item-body">
<div class="info-item"> <div class="info-item">
<div>当前步骤:</div> <div>错误发生步骤:</div>
<div> <div class="text-red">
{{ {{
upgradeProgressStatusMap.find( upgradeProgressStatusMap.find(
r => r.status === item.upFirmwareStatus?.step r => r.status == item.upFirmwareStatus?.step
)?.text ?? '--' )?.text ?? '--'
}} }}失败
</div> </div>
</div> </div>
<div class="info-item"> <div class="info-item">
<div>升级错误信息:</div> <div>错误信息:</div>
<div>{{ item.upFirmwareStatus?.errMsg ?? '--' }}</div> <div>{{ item.upFirmwareStatus?.errMsg ?? '--' }}</div>
</div> </div>
</div> </div>
</template> </template>
<template v-if="item.upFirmware === 'timeout'">
<div class="w-full h-full flex items-center justify-center">
<div class="font-400 text-base text-[#E6A23C]">升级超时</div>
</div>
</template>
</div> </div>
</div> </div>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
</EdfsWrap> </EdfsWrap>
<EdfsWrap title="迁移进度" class="transfer-wrap h-[42%]" v-if="isShowTransfer"> <TransferMask v-model="isShowTransferMask" :transferLoading="transferLoading" @close="closeTransferMask">
<div class="flex-col gap-col-10 wh-full"> <template v-if="curTransfer === 'export'">
<div class="flex items-center gap-col-1"> <div class="flex-col gap-col-10 h-full w-56% justify-center">
<div class="flex-1 flex items-center"> <div class="flex items-center gap-col-1">
<el-progress <div class="flex-1 flex items-center">
:percentage="100" <el-progress :percentage="100" class="flex-1" :stroke-width="18" :text-inside="true"
class="flex-1" :striped="transferStatus === 'progress'" :striped-flow="transferStatus === 'progress'" :duration="20"
:stroke-width="18" :status="['progress', 'success', undefined].includes(transferStatus)
:text-inside="true"
:status="
['progress', 'success', undefined].includes(transferStatus)
? 'success' ? 'success'
: 'exception' : 'exception'
" ">
> {{
{{ transferStatusMap[transferStatus as keyof typeof transferStatusMap] ?? ''
transferStatusMap[transferStatus as keyof typeof transferStatusMap] ?? '' }}
}}
</el-progress> </el-progress>
</div>
<el-button v-if="transferStatus === 'progress'" type="primary" @click="onStopTransfer">停止迁移</el-button>
</div>
<div class="transfer-log-wrap h-490 flex-col">
<div class="text-16px font-500">迁移日志</div>
<el-scrollbar class="flex-1">
<div v-for="i in curTransferLog" :class="i.status === 'failed' ? 'text-red-500' : ''"
class="text-gray-600">
{{ i.msg }}
</div>
</el-scrollbar>
</div> </div>
<el-button
v-if="transferStatus === 'progress'"
type="primary"
@click="onStopTransfer"
>停止迁移</el-button
>
</div> </div>
<div class="transfer-log-wrap"> </template>
<div class="text-16px font-500">迁移日志</div> <template v-else-if="curTransfer === 'import'">
<el-scrollbar class="h-full"> <div class="flex-col gap-col-10 h-full w-56% justify-center">
<div <div class="flex items-center gap-col-1">
v-for="i in curTransferLog" <div class="flex-1 items-center">
:class="i.status === 'failed' ? 'text-red-500' : ''" <el-progress :percentage="100" :status="['progress', 'success', undefined].includes(onOffDeviceTransferStatus)
class="text-gray-600" ? 'success'
> : 'exception'
{{ i.msg }} " class="flex-1" :stroke-width="18" :text-inside="true"
:striped="onOffDeviceTransferStatus === 'progress'"
:striped-flow="onOffDeviceTransferStatus === 'progress'" :duration="20">
<div class="text-16px font-500">{{ transferStatusMap[onOffDeviceTransferStatus as keyof typeof
transferStatusMap]
??
'' }}</div>
</el-progress>
</div> </div>
</el-scrollbar> </div>
<div class="h-490 border-radius-8px bg-[#F9FAFB] p-10 flex-col">
<div class="text-16px font-500">迁移日志</div>
<el-scrollbar class="flex-1">
<div v-for="i in siteTransferLogList"> {{ i }} </div>
</el-scrollbar>
</div>
</div> </div>
</div> </template>
</EdfsWrap>
</TransferMask>
</div> </div>
<TransferDlg <TransferDlg ref="transferDlgRef" @on-save="onLineDeviceTransfer" :is-batch-transfer="isBatchTransfer" />
ref="transferDlgRef" <OffTransferDlg ref="offTransferDlg" :isBatchTransfer="false" :siteInfo="siteInfo" @on-save="onOffDeviceTransfer" />
@on-save="onSave" <DeviceDrawer v-model="isShowDetails" ref="deviceDrawerRef" :siteInfo="siteInfo" :is-transfer="isTransfer" />
:is-batch-transfer="isBatchTransfer"
/>
<DeviceDrawer
v-model="isShowDetails"
ref="deviceDrawerRef"
:siteInfo="siteInfo"
:is-transfer="isTransfer"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import TransferMask from './components/transferMask.vue'
import TransferDlg from './components/transferDlg.vue' import TransferDlg from './components/transferDlg.vue'
import ZMQWorker from '@/composables/useZMQJsonWorker' import ZMQWorker from '@/composables/useZMQJsonWorker'
import OffTransferDlg from './components/offTransferDlg.vue'
import { import {
getPubInitData, getPubInitData,
type PublishMsg, type PublishMsg,
@ -217,6 +215,7 @@ import type {
import { useMessage } from '@/composables/useMessage' import { useMessage } from '@/composables/useMessage'
import { getDeviceList, type ISite } from '@/api/module/transfer' import { getDeviceList, type ISite } from '@/api/module/transfer'
import DeviceDrawer from './components/deviceDrawer.vue' import DeviceDrawer from './components/deviceDrawer.vue'
import { import {
getFirmwareUpTopic, getFirmwareUpTopic,
getTransferTopic, getTransferTopic,
@ -235,14 +234,14 @@ const type = ref<'export' | 'details'>(route.query.type as 'export' | 'details')
const isTransfer = computed(() => type.value === 'export') const isTransfer = computed(() => type.value === 'export')
const isShowTransfer = computed(() => isTransfer.value && !!curTransferLog.value.length) const isShowTransferMask = ref(false)
const message = useMessage() const message = useMessage()
const worker = ZMQWorker.getInstance() const worker = ZMQWorker.getInstance()
const transferDataStore = useTransferDataStore() const transferDataStore = useTransferDataStore()
const { upFirmwarePending, upFirmwareReset, upFirmwareStatus, upFirmwareSucceed } = const { upFirmwarePending, upFirmwareReset, upFirmwareStatus, upFirmwareSucceed, upFirmwareStatusReject, upFirmwareTimeout } =
transferDataStore transferDataStore
const { devicesMap } = storeToRefs(transferDataStore) const { devicesMap } = storeToRefs(transferDataStore)
@ -265,11 +264,33 @@ const devices = computed(() => {
return isTransfer.value ? Array.from(devicesMap.value.values()) : deviceList.value return isTransfer.value ? Array.from(devicesMap.value.values()) : deviceList.value
}) as Ref<any[]> }) as Ref<any[]>
function onSave(msg: PublishMsg<'export'>, device: IOnlineDevice) { const transferLoading = ref(false)
const curTransfer = ref<'import' | 'export'>()
function openTransferMask(status: 'import' | 'export') {
isShowTransferMask.value = true
curTransfer.value = status
transferLoading.value = true
}
function closeTransferMask() {
curTransfer.value = undefined
isShowTransferMask.value = false
transferLoading.value = false
curTransferLog.value = [] curTransferLog.value = []
transferStatus.value = undefined
siteTransferLogList.value = []
pubIdWithOffDevice.clear()
exportPubDeviceMap.clear()
}
function onLineDeviceTransfer(msg: PublishMsg<'export'>, device: IOnlineDevice) {
curTransferLog.value = []
openTransferMask('export')
worker.publish(postTransferTopic, msg, true, zmqTimeoutCb) worker.publish(postTransferTopic, msg, true, zmqTimeoutCb)
exportPubDeviceMap.set(msg.id, { device, action: 'export' }) exportPubDeviceMap.set(msg.id, { device, action: 'export' })
worker.subscribe(getTransferTopic, zmqExportCb, msg.id) worker.subscribe(getTransferTopic, zmqExportCb, msg.id)
if (isBatchTransfer.value) { if (isBatchTransfer.value) {
onBatchCancel() onBatchCancel()
} }
@ -281,15 +302,17 @@ const statusMap = {
1003: 'failed', 1003: 'failed',
} }
function zmqExportCb(msg: PubMsgData) { function zmqExportCb(msg: PubMsgData) {
if (!isTransfer.value) return if (!isTransfer.value) return
const { feedback, result, id } = msg const { feedback, result, id } = msg
transferLoading.value = false
if (feedback && feedback[0]) { if (feedback && feedback[0]) {
const status = feedback[1] const status = feedback[1]
? (statusMap[feedback[1] as keyof typeof statusMap] as ? (statusMap[feedback[1] as keyof typeof statusMap] as
| 'success' | 'success'
| 'padding' | 'padding'
| 'failed') | 'failed')
: 'failed' : 'failed'
curTransferLog.value.push({ curTransferLog.value.push({
msg: `主机【${feedback[0]}】: ${feedback[2]}`, msg: `主机【${feedback[0]}】: ${feedback[2]}`,
@ -297,9 +320,9 @@ function zmqExportCb(msg: PubMsgData) {
status, status,
}) })
} }
// status failed
transferStatus.value = 'progress' transferStatus.value = 'progress'
if (result !== 'progress') { if (result !== 'progress') {
const curMsgInfo = exportPubDeviceMap.get(id)! const curMsgInfo = exportPubDeviceMap.get(id)!
if (!curMsgInfo) return if (!curMsgInfo) return
const { device, action } = curMsgInfo const { device, action } = curMsgInfo
@ -314,6 +337,7 @@ function zmqExportCb(msg: PubMsgData) {
transferStatus.value = 'failed' transferStatus.value = 'failed'
} }
} else if (['failed', 'failure'].includes(result)) { } else if (['failed', 'failure'].includes(result)) {
message.error(`迁移失败`) message.error(`迁移失败`)
transferStatus.value = 'failed' transferStatus.value = 'failed'
} }
@ -339,21 +363,17 @@ function onStopTransfer() {
const msg = getPubInitData<'cancel'>('cancel', [], 'no') const msg = getPubInitData<'cancel'>('cancel', [], 'no')
worker.publish(postTransferTopic, msg) worker.publish(postTransferTopic, msg)
message.success('迁移已取消') message.success('迁移已取消')
clearTransferData() exportPubDeviceMap.clear()
}) })
} }
function clearTransferData() {
curTransferLog.value = []
transferStatus.value = undefined
exportPubDeviceMap.clear()
}
function zmqTimeoutCb(msg: TimeoutMsg) { function zmqTimeoutCb(msg: TimeoutMsg) {
const { device, action } = exportPubDeviceMap.get(msg.timeoutId)! const { device, action } = exportPubDeviceMap.get(msg.timeoutId)!
if (device && action === 'export') { if (device && action === 'export') {
message.error(`迁移超时,请重新稍后尝试`) message.error(`迁移超时,请重新稍后尝试`)
exportPubDeviceMap.delete(msg.timeoutId) exportPubDeviceMap.delete(msg.timeoutId)
closeTransferMask()
} }
} }
@ -430,7 +450,11 @@ function onBatchCancel() {
onlineDeviceCheckList.value = [] onlineDeviceCheckList.value = []
} }
function onTransfer(item: IOnlineDevice) { function onTransfer(item: IOnlineDevice) {
transferDlgRef.value?.open(item) if (isTransfer.value) {
transferDlgRef.value?.open(item)
} else {
offTransferDlg.value?.open(item)
}
} }
function onBack() { function onBack() {
@ -458,7 +482,7 @@ onBeforeRouteLeave(async (to, from, next) => {
const deviceList = ref<IOfflineDevice[]>([]) const deviceList = ref<IOfflineDevice[]>([])
async function loadDeviceList() { async function loadDeviceList() {
const res = await getDeviceList(siteInfo.value.id) const res = await getDeviceList(siteInfo.value.name)
if (res.code === 200 || res.code === 0) { if (res.code === 200 || res.code === 0) {
deviceList.value = res.data deviceList.value = res.data
} }
@ -472,7 +496,7 @@ onMounted(async () => {
} else { } else {
const res = await getFirmwarePath() const res = await getFirmwarePath()
if (res.code === 200 || res.code === 0) { if (res.code === 200 || res.code === 0) {
firmwarePath.value = res.data firmwarePath.value = res.data.path
} }
} }
}) })
@ -507,7 +531,7 @@ function onFirmwareUpload(devices: IOnlineDevice[]) {
action: 'upgrade', action: 'upgrade',
}) })
} }
worker.publish(postFirmwareUpTopic, msg, true, firmwareUpTimeoutCb) worker.publish(postFirmwareUpTopic, msg, true, firmwareUpTimeoutCb, true)
worker.subscribe(getFirmwareUpTopic, zmqUpgradeCb, msg.id) worker.subscribe(getFirmwareUpTopic, zmqUpgradeCb, msg.id)
upgradeSnList.value = deviceSn.split(',') upgradeSnList.value = deviceSn.split(',')
@ -517,9 +541,18 @@ function onFirmwareUpload(devices: IOnlineDevice[]) {
function firmwareUpTimeoutCb(msg: TimeoutMsg) { function firmwareUpTimeoutCb(msg: TimeoutMsg) {
const { device, action } = upgradePubDeviceMap.get(msg.timeoutId)! const { device, action } = upgradePubDeviceMap.get(msg.timeoutId)!
if (device && action === 'upgrade') { if (device && action === 'upgrade') {
message.error(`固件升级超时,请重新稍后尝试`) const timeoutUpgradeSnList = upgradeSnList.value
if (timeoutUpgradeSnList.length === 0) {
upFirmwareReset(upgradeSnList.value)
upgradePubDeviceMap.delete(msg.timeoutId)
return
}
for (const deviec of timeoutUpgradeSnList) {
upFirmwareTimeout(deviec)
}
upgradePubDeviceMap.delete(msg.timeoutId) upgradePubDeviceMap.delete(msg.timeoutId)
upFirmwareReset(upgradeSnList.value) message.warning(`固件升级超时,请稍后重试`)
} }
} }
@ -529,8 +562,7 @@ function zmqUpgradeCb(msg: PubMsgData) {
const deviceSn = msg.feedback[0] const deviceSn = msg.feedback[0]
const progressStatus = msg.feedback[1] as number const progressStatus = msg.feedback[1] as number
const progress = msg.feedback[2] || undefined const progress = msg.feedback[2] || undefined
const errMsg = msg.feedback[3] || undefined const curentDevice = upgradePubDeviceMap.get(msg.id)
const curentDevice = upgradePubDeviceMap.get(deviceSn)
if (curentDevice && curentDevice.action === 'upgrade') { if (curentDevice && curentDevice.action === 'upgrade') {
const { device } = curentDevice const { device } = curentDevice
if (device) { if (device) {
@ -542,17 +574,70 @@ function zmqUpgradeCb(msg: PubMsgData) {
} }
} }
if (status === 'success' || status === 'error') { if (status === 'success' || status === 'error') {
message.success(`固件升级${status === 'success' ? '完成' : '失败'}`) upFirmwareStatus(deviceSn, msg.feedback)
upgradePubDeviceMap.delete(deviceSn) if (status === 'error') {
upFirmwareStatusReject(deviceSn, msg.feedback)
}
upgradeSnList.value = upgradeSnList.value.filter(item => item !== deviceSn)
// upgradePubDeviceMap.delete(deviceSn)
}
}
}
// ================线=========
const offTransferDlg = ref<typeof OffTransferDlg>()
const pubIdWithOffDevice = new Map<string, { offDevice: IOfflineDevice; action: 'import' }>()
function onOffDeviceTransfer(msg: PublishMsg<'import'>, offDevice: IOfflineDevice) {
pubIdWithOffDevice.set(msg.id, { offDevice, action: 'import' })
worker.publish(postTransferTopic, msg, true, zmqImportTimeoutCb)
worker.subscribe(getTransferTopic, zmqImportCb, msg.id)
openTransferMask('import')
}
const siteTransferLogList = ref<string[]>([])
const onOffDeviceTransferStatus = ref<'progress' | 'success' | 'failed' | 'timeout' | undefined>()
function zmqImportCb(msg: PubMsgData) {
const { id, result, feedback } = msg
const { offDevice, action } = pubIdWithOffDevice.get(id)!
if (action !== 'import' || !offDevice) return
transferLoading.value = false
if (result === 'progress') {
onOffDeviceTransferStatus.value = 'progress'
const log: string = Array.isArray(feedback) ? feedback[0] || '' : ''
siteTransferLogList.value.push(log)
}
if (result !== 'progress') {
if (result === 'success') {
onOffDeviceTransferStatus.value = 'success'
message.success(`导出数据成功`)
} else {
onOffDeviceTransferStatus.value = 'failed'
message.error(`导出数据失败`)
} }
} }
} }
function zmqImportTimeoutCb(msg: TimeoutMsg) {
const { offDevice, action } = pubIdWithOffDevice.get(msg.timeoutId)!
if (offDevice && action === 'import') {
message.error(`站点:${offDevice.sn}数据导出超时,请稍后重试`)
pubIdWithOffDevice.delete(msg.timeoutId)
onOffDeviceTransferStatus.value = 'timeout'
closeTransferMask()
}
}
// ================ 线 end =========
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.transfer-log-wrap { .transfer-log-wrap {
margin-top: 10px; margin-top: 10px;
height: calc(100% - 30px);
@apply border-radius-8px bg-[#F9FAFB] p-10; @apply border-radius-8px bg-[#F9FAFB] p-10;
:deep(.el-scrollbar) { :deep(.el-scrollbar) {

8
src/views/stationData/type.ts

@ -6,7 +6,7 @@ export interface IOnlineDevice {
lastUpdated: number // 新增字段,记录最后更新时间 lastUpdated: number // 新增字段,记录最后更新时间
status?: string status?: string
isChecked?: boolean isChecked?: boolean
upFirmware?: 'updating' | 'pending' | 'fulfilled' | 'rejected' upFirmware?: 'updating' | 'pending' | 'fulfilled' | 'rejected' | 'timeout'
upFirmwareStatus?: IUpFirmwareStatus upFirmwareStatus?: IUpFirmwareStatus
} }
@ -35,4 +35,10 @@ export interface IUpFirmwareStatus {
step: number step: number
progress: number | undefined progress: number | undefined
errMsg: string | undefined errMsg: string | undefined
}
export interface IMyPoint {
addr: string
label: string
} }

4
vite.config.ts

@ -65,14 +65,12 @@ export default defineConfig({
host: '0.0.0.0', host: '0.0.0.0',
proxy: { proxy: {
'/remoteServer': { '/remoteServer': {
target: 'http://192.168.1.115:8080/', target: 'http://192.168.1.199:8080/',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: true, ws: true,
rewrite: path => path.replace(/^\/remoteServer/, ''), rewrite: path => path.replace(/^\/remoteServer/, ''),
}, },
} }
}, },
}) })
Loading…
Cancel
Save