sean.zhou
2 years ago
69 changed files with 7059 additions and 4216 deletions
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request' |
||||
import { DeviceCmd } from '/@/types/device-cmd' |
||||
|
||||
const CMD_API_PREFIX = '/control/api/v1' |
||||
|
||||
export interface SendCmdParams { |
||||
dock_sn: string, // 机场cn
|
||||
device_cmd: DeviceCmd // 指令
|
||||
} |
||||
|
||||
/** |
||||
* 发送机场控制指令 |
||||
* @param params |
||||
* @returns |
||||
*/ |
||||
// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}
|
||||
export async function postSendCmd (params: SendCmdParams): Promise<IWorkspaceResponse<{}>> { |
||||
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`) |
||||
return resp.data |
||||
} |
@ -0,0 +1,172 @@
@@ -0,0 +1,172 @@
|
||||
import request, { IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request' |
||||
import { DeviceValue, DOMAIN } from '/@/types/device' |
||||
import { DeviceLogUploadStatusEnum } from '/@/types/device-log' |
||||
import { ELocalStorageKey } from '/@/types' |
||||
import { CURRENT_CONFIG } from '/@/api/http/config' |
||||
|
||||
const MNG_API_PREFIX = '/manage/api/v1' |
||||
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
||||
|
||||
export interface GetDeviceUploadLogListParams { |
||||
device_sn: string, |
||||
page: number, |
||||
page_size: number, |
||||
begin_time?: number, // 开始时间
|
||||
end_time?: number, // 结束时间
|
||||
status?: DeviceLogUploadStatusEnum, // 日志上传状态
|
||||
logs_information?: string // 搜索内容
|
||||
} |
||||
|
||||
export interface BriefDeviceInfo { |
||||
sn: string, |
||||
device_model: DeviceValue, |
||||
device_callsign: string |
||||
} |
||||
|
||||
export interface DeviceLogProgressInfo{ |
||||
device_sn: string, |
||||
device_model_domain: DOMAIN, |
||||
progress: number, // 进度
|
||||
result: number, // 上传结果
|
||||
upload_rate: number, // 上传速率
|
||||
status: DeviceLogUploadStatusEnum // 上传状态
|
||||
} |
||||
|
||||
export interface DeviceLogItem { |
||||
boot_index: number, // 日志id
|
||||
start_time: number, // 日志开始时间
|
||||
end_time: number, // 日志结束时间
|
||||
size: number // 日志大小
|
||||
} |
||||
|
||||
export interface DeviceLogFileInfo { |
||||
device_sn: string, |
||||
module: DOMAIN, |
||||
result: number, |
||||
object_key: string, |
||||
file_id: string, |
||||
list: DeviceLogItem[] |
||||
} |
||||
|
||||
export interface DeviceLogFileListInfo { |
||||
files: DeviceLogFileInfo[] |
||||
} |
||||
|
||||
export interface GetDeviceUploadLogListRsp { |
||||
logs_id: string, // 记录id
|
||||
happen_time: string, // 发生时间
|
||||
user_name: string, // 用户
|
||||
logs_information: string, // 异常描述
|
||||
create_time: string, // 上传时间
|
||||
status:DeviceLogUploadStatusEnum, // 日志上传状态
|
||||
device_topo:{ // 设备topo
|
||||
hosts: BriefDeviceInfo[], |
||||
parents: BriefDeviceInfo[] |
||||
}, |
||||
logs_progress: DeviceLogProgressInfo[], // 日志上传进度
|
||||
device_logs: DeviceLogFileListInfo // 设备日志
|
||||
} |
||||
|
||||
/** |
||||
* 获取设备上传日志列表信息 |
||||
* @param params |
||||
* @returns |
||||
*/ |
||||
export async function getDeviceUploadLogList (params: GetDeviceUploadLogListParams): Promise<IListWorkspaceResponse<GetDeviceUploadLogListRsp>> { |
||||
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs-uploaded`, { |
||||
params: params |
||||
}) |
||||
return resp.data |
||||
} |
||||
|
||||
export interface GetDeviceLogListParams{ |
||||
device_sn: string, |
||||
domain: DOMAIN[] |
||||
} |
||||
|
||||
/** |
||||
* 获取设备日志列表信息 |
||||
* @param params |
||||
* @returns |
||||
*/ |
||||
export async function getDeviceLogList (params: GetDeviceLogListParams): Promise<IWorkspaceResponse<DeviceLogFileListInfo>> { |
||||
const domain = params.domain ? params.domain : [] |
||||
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs`, { |
||||
params: { |
||||
domain_list: domain.join(',') |
||||
} |
||||
}) |
||||
return resp.data |
||||
} |
||||
|
||||
export interface UploadDeviceLogBody { |
||||
device_sn: string |
||||
happen_time: string // 发生时间
|
||||
logs_information: string // 异常描述
|
||||
files:{ |
||||
list: DeviceLogItem[], |
||||
device_sn: string, |
||||
module: DOMAIN |
||||
}[] |
||||
} |
||||
|
||||
/** |
||||
* 上传设备日志 |
||||
* @param body |
||||
* @returns |
||||
*/ |
||||
export async function postDeviceUpgrade (body: UploadDeviceLogBody): Promise<IWorkspaceResponse<{}>> { |
||||
const resp = await request.post(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`, body) |
||||
return resp.data |
||||
} |
||||
|
||||
export type DeviceLogUploadAction = 'cancel' |
||||
|
||||
export interface CancelDeviceLogUploadBody { |
||||
device_sn: string |
||||
status: DeviceLogUploadAction |
||||
module_list: DOMAIN[] |
||||
} |
||||
|
||||
// 取消上传
|
||||
export async function cancelDeviceLogUpload (body: CancelDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> { |
||||
const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs` |
||||
const result = await request.delete(url, { |
||||
data: body |
||||
}) |
||||
return result.data |
||||
} |
||||
|
||||
export interface DeleteDeviceLogUploadBody { |
||||
device_sn: string |
||||
logs_id: string |
||||
} |
||||
|
||||
// 取消上传
|
||||
export async function deleteDeviceLogUpload (body: DeleteDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> { |
||||
const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs/${body.logs_id}` |
||||
const result = await request.delete(url, { |
||||
data: body |
||||
}) |
||||
return result.data |
||||
} |
||||
|
||||
export interface GetUploadDeviceLogUrlParams{ |
||||
logs_id: string, |
||||
file_id: string, |
||||
} |
||||
|
||||
// export interface GetUploadDeviceLogRsp{
|
||||
// url: string
|
||||
// }
|
||||
|
||||
/** |
||||
* 获取设备上传日志url |
||||
* @param params |
||||
* @returns |
||||
*/ |
||||
export async function getUploadDeviceLogUrl (params: GetUploadDeviceLogUrlParams): Promise<IWorkspaceResponse<string>> { |
||||
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/logs/${params.logs_id}/url/${params.file_id}`) |
||||
return resp.data |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
import request, { IWorkspaceResponse } from '/@/api/http/request' |
||||
import { DeviceFirmwareTypeEnum } from '/@/types/device' |
||||
|
||||
const MNG_API_PREFIX = '/manage/api/v1' |
||||
|
||||
export interface GetDeviceUpgradeInfoParams { |
||||
device_name: string |
||||
} |
||||
|
||||
export interface GetDeviceUpgradeInfoRsp { |
||||
device_name: string |
||||
product_version: string |
||||
release_note: string |
||||
released_time: string |
||||
} |
||||
|
||||
/** |
||||
* 获取设备升级信息 |
||||
* @param params |
||||
* @returns |
||||
*/ |
||||
export async function getDeviceUpgradeInfo (params: GetDeviceUpgradeInfoParams): Promise<IWorkspaceResponse<GetDeviceUpgradeInfoRsp[]>> { |
||||
const resp = await request.get(`${MNG_API_PREFIX}/workspaces/firmware-release-notes/latest`, { |
||||
params: params |
||||
}) |
||||
return resp.data |
||||
} |
||||
|
||||
export interface UpgradeDeviceInfo { |
||||
device_name: string, |
||||
sn: string, |
||||
product_version: string, |
||||
firmware_upgrade_type: DeviceFirmwareTypeEnum // 1-普通升级,2-一致性升级
|
||||
} |
||||
|
||||
export type DeviceUpgradeBody = UpgradeDeviceInfo[] |
||||
|
||||
/** |
||||
* 设备升级 |
||||
* @param workspace_id |
||||
* @param body |
||||
* @returns |
||||
*/ |
||||
export async function postDeviceUpgrade (workspace_id: string, body: DeviceUpgradeBody): Promise<IWorkspaceResponse<{}>> { |
||||
const resp = await request.post(`${MNG_API_PREFIX}/devices/${workspace_id}/devices/ota`, body) |
||||
return resp.data |
||||
} |
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket' |
||||
import { ELocalStorageKey } from '../types/enums' |
||||
import { CURRENT_CONFIG as config } from '/@/api/http/config' |
||||
|
||||
let socket: ReconnectingWebSocket |
||||
|
||||
export default { |
||||
init (getMsgFunc: any) { |
||||
const token: string = localStorage.getItem(ELocalStorageKey.Token)! |
||||
const wspath = config.websocketURL + '?x-auth-token=' + encodeURI(token) |
||||
socket = new ReconnectingWebSocket(wspath, '', { maxRetries: 5 }) |
||||
socket.onopen = this.onOpen |
||||
socket.onerror = this.onError |
||||
socket.onmessage = getMsgFunc |
||||
socket.onclose = this.onClose |
||||
return socket |
||||
}, |
||||
onOpen () { |
||||
console.log('ws opened') |
||||
}, |
||||
onError (err: any) { |
||||
console.error(err) |
||||
}, |
||||
onClose () { |
||||
console.log('ws closed') |
||||
}, |
||||
sendMsg (data: any) { |
||||
socket.send(data) |
||||
}, |
||||
close () { |
||||
socket.close() |
||||
} |
||||
} |
@ -0,0 +1,268 @@
@@ -0,0 +1,268 @@
|
||||
<template> |
||||
<a-drawer |
||||
title="Hms Info" |
||||
placement="right" |
||||
v-model:visible="sVisible" |
||||
@update:visible="onVisibleChange" |
||||
:destroyOnClose="true" |
||||
:width="800"> |
||||
<div class="flex-row flex-align-center"> |
||||
<div style="width: 240px;"> |
||||
<a-range-picker |
||||
v-model:value="time" |
||||
format="YYYY-MM-DD" |
||||
:placeholder="['Start Time', 'End Time']" |
||||
@change="onTimeChange"/> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-select |
||||
style="width: 150px" |
||||
v-model:value="param.level" |
||||
@select="onLevelSelect"> |
||||
<a-select-option |
||||
v-for="item in levels" |
||||
:key="item.label" |
||||
:value="item.value" |
||||
> |
||||
{{ item.label }} |
||||
</a-select-option> |
||||
</a-select> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-select |
||||
v-model:value="param.domain" |
||||
:disabled="!param.children_sn || !param.device_sn" |
||||
style="width: 150px" |
||||
@select="onDeviceTypeSelect"> |
||||
<a-select-option |
||||
v-for="item in deviceTypes" |
||||
:key="item.label" |
||||
:value="item.value" |
||||
> |
||||
{{ item.label }} |
||||
</a-select-option> |
||||
</a-select> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-input-search |
||||
v-model:value="param.message" |
||||
placeholder="input search message" |
||||
style="width: 200px" |
||||
@search="getHms"/> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<a-table :columns="hmsColumns" :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data" :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id" |
||||
:rowClassName="rowClassName" :loading="loading"> |
||||
<template #time="{ record }"> |
||||
<div>{{ record.create_time }}</div> |
||||
<div :style="record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' : |
||||
record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">{{ record.update_time ?? 'It is happening...' }}</div> |
||||
</template> |
||||
<template #level="{ text }"> |
||||
<div class="flex-row flex-align-center"> |
||||
<div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'" style="width: 10px; height: 10px; border-radius: 50%;"></div> |
||||
<div style="margin-left: 3px;">{{ EHmsLevel[text] }}</div> |
||||
</div> |
||||
</template> |
||||
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col"> |
||||
<a-tooltip :title="text"> |
||||
<span>{{ text }}</span> |
||||
</a-tooltip> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
</a-drawer> |
||||
</template> |
||||
|
||||
<!-- 暂时只抽取该组件 --> |
||||
<script lang="ts" setup> |
||||
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
||||
import { getDeviceHms, HmsQueryBody } from '/@/api/manage' |
||||
import moment, { Moment } from 'moment' |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { Device, DeviceHms } from '/@/types/device' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types' |
||||
|
||||
const props = defineProps<{ |
||||
visible: boolean, |
||||
device: null | Device, |
||||
}>() |
||||
const emit = defineEmits(['update:visible', 'ok', 'cancel']) |
||||
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
||||
// 健康状态 |
||||
const sVisible = ref(false) |
||||
|
||||
watchEffect(() => { |
||||
sVisible.value = props.visible |
||||
// 显示弹框时,获取设备hms信息 |
||||
if (props.visible) { |
||||
showHms() |
||||
} |
||||
}) |
||||
|
||||
function onVisibleChange (sVisible: boolean) { |
||||
setVisible(sVisible) |
||||
} |
||||
|
||||
function setVisible (v: boolean, e?: Event) { |
||||
sVisible.value = v |
||||
emit('update:visible', v, e) |
||||
} |
||||
|
||||
const loading = ref(false) |
||||
|
||||
const hmsColumns: ColumnProps[] = [ |
||||
{ title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } }, |
||||
{ title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } }, |
||||
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle' }, |
||||
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } }, |
||||
{ title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } }, |
||||
] |
||||
|
||||
interface DeviceHmsData { |
||||
data: DeviceHms[] |
||||
} |
||||
|
||||
const hmsData = reactive<DeviceHmsData>({ |
||||
data: [] |
||||
}) |
||||
|
||||
type Pagination = TableState['pagination'] |
||||
|
||||
const hmsPaginationProp = reactive({ |
||||
pageSizeOptions: ['20', '50', '100'], |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
pageSize: 50, |
||||
current: 1, |
||||
total: 0 |
||||
}) |
||||
|
||||
// 获取分页信息 |
||||
function getPaginationBody () { |
||||
return { |
||||
page: hmsPaginationProp.current, |
||||
page_size: hmsPaginationProp.pageSize |
||||
} as IPage |
||||
} |
||||
|
||||
function showHms () { |
||||
const dock = props.device |
||||
if (!dock) return |
||||
if (dock.domain === EDeviceTypeName.Dock) { |
||||
getDeviceHmsBySn(dock.device_sn, dock.children?.[0].device_sn ?? '') |
||||
} |
||||
if (dock.domain === EDeviceTypeName.Aircraft) { |
||||
param.domain = EDeviceTypeName.Aircraft |
||||
getDeviceHmsBySn('', dock.device_sn) |
||||
} |
||||
} |
||||
|
||||
function refreshHmsData (page: Pagination) { |
||||
hmsPaginationProp.current = page?.current! |
||||
hmsPaginationProp.pageSize = page?.pageSize! |
||||
getHms() |
||||
} |
||||
|
||||
const param = reactive<HmsQueryBody>({ |
||||
sns: [], |
||||
device_sn: '', |
||||
children_sn: '', |
||||
language: 'en', |
||||
begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0), |
||||
end_time: new Date().setHours(23, 59, 59, 999), |
||||
domain: '', |
||||
level: '', |
||||
message: '' |
||||
}) |
||||
|
||||
const levels = [ |
||||
{ |
||||
label: 'All', |
||||
value: '' |
||||
}, { |
||||
label: EHmsLevel[0], |
||||
value: EHmsLevel.NOTICE |
||||
}, { |
||||
label: EHmsLevel[1], |
||||
value: EHmsLevel.CAUTION |
||||
}, { |
||||
label: EHmsLevel[2], |
||||
value: EHmsLevel.WARN |
||||
} |
||||
] |
||||
|
||||
const deviceTypes = [ |
||||
{ |
||||
label: 'All', |
||||
value: '' |
||||
}, { |
||||
label: EDeviceTypeName.Aircraft, |
||||
value: EDeviceTypeName.Aircraft |
||||
}, { |
||||
label: EDeviceTypeName.Dock, |
||||
value: EDeviceTypeName.Dock |
||||
} |
||||
] |
||||
|
||||
const rowClassName = (record: any, index: number) => { |
||||
const className = [] |
||||
if ((index & 1) === 0) { |
||||
className.push('table-striped') |
||||
} |
||||
if (record.domain !== EDeviceTypeName.Dock) { |
||||
className.push('child-row') |
||||
} |
||||
return className.toString().replaceAll(',', ' ') |
||||
} |
||||
|
||||
const time = ref([moment(param.begin_time), moment(param.end_time)]) |
||||
|
||||
function getHms () { |
||||
loading.value = true |
||||
getDeviceHms(param, workspaceId, getPaginationBody()) |
||||
.then(res => { |
||||
hmsPaginationProp.total = res.data.pagination.total |
||||
hmsPaginationProp.current = res.data.pagination.page |
||||
hmsData.data = res.data.list |
||||
hmsData.data.forEach(hms => { |
||||
hms.domain = hms.sn === param.children_sn ? EDeviceTypeName.Aircraft : EDeviceTypeName.Dock |
||||
}) |
||||
loading.value = false |
||||
}).catch(_err => { |
||||
loading.value = false |
||||
}) |
||||
} |
||||
|
||||
function getDeviceHmsBySn (sn: string, childSn: string) { |
||||
param.device_sn = sn |
||||
param.children_sn = childSn |
||||
param.sns = [param.device_sn, param.children_sn] |
||||
getHms() |
||||
} |
||||
|
||||
function onTimeChange (newTime: [Moment, Moment]) { |
||||
param.begin_time = newTime[0].valueOf() |
||||
param.end_time = newTime[1].valueOf() |
||||
getHms() |
||||
} |
||||
|
||||
function onDeviceTypeSelect (val: string) { |
||||
param.sns = [param.device_sn, param.children_sn] |
||||
if (val === EDeviceTypeName.Dock) { |
||||
param.sns = [param.device_sn, ''] |
||||
} |
||||
if (val === EDeviceTypeName.Aircraft) { |
||||
param.sns = ['', param.children_sn] |
||||
} |
||||
getHms() |
||||
} |
||||
|
||||
function onLevelSelect (val: number) { |
||||
param.level = val |
||||
getHms() |
||||
} |
||||
</script> |
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
<template> |
||||
<a-modal |
||||
title="日志上传详情" |
||||
v-model:visible="sVisible" |
||||
width="900px" |
||||
:footer="null" |
||||
@update:visible="onVisibleChange"> |
||||
<div class="device-log-detail-wrap"> |
||||
<div class="device-log-list"> |
||||
<div class="log-list-item"> |
||||
<a-button type="primary" class="download-btn" :disabled="!airportTableLogState.logList?.file_id" size="small" @click="onDownloadLog(airportTableLogState.logList.file_id)"> |
||||
下载机场日志 |
||||
</a-button> |
||||
<a-table :columns="airportLogColumns" |
||||
:scroll="{ x: '100%', y: 600 }" |
||||
:data-source="airportTableLogState.logList?.list" |
||||
rowKey="boot_index" |
||||
:pagination = "false" |
||||
> |
||||
<template #log_time="{record}"> |
||||
<div>{{getLogTime(record)}}</div> |
||||
</template> |
||||
<template #size="{record}"> |
||||
<div>{{getLogSize(record.size)}}</div> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
<div class="log-list-item"> |
||||
<a-button type="primary" class="download-btn" :disabled="!droneTableLogState.logList?.file_id" size="small" @click="onDownloadLog(droneTableLogState.logList.file_id)"> |
||||
下载飞行器日志 |
||||
</a-button> |
||||
<a-table :columns="droneLogColumns" |
||||
:scroll="{ x: '100%', y: 600 }" |
||||
:data-source="droneTableLogState.logList?.list" |
||||
rowKey="boot_index" |
||||
:pagination = "false" |
||||
> |
||||
<template #log_time="{record}"> |
||||
<div>{{getLogTime(record)}}</div> |
||||
</template> |
||||
<template #size="{record}"> |
||||
<div>{{getLogSize(record.size)}}</div> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</a-modal> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { DOMAIN } from '/@/types/device' |
||||
import { DeviceLogFileInfo, GetDeviceUploadLogListRsp, getUploadDeviceLogUrl } from '/@/api/device-log' |
||||
import { useDeviceLogUploadDetail } from './use-device-log-upload-detail' |
||||
import { download } from '/@/utils/download' |
||||
|
||||
const props = defineProps<{ |
||||
visible: boolean, |
||||
deviceLog: null | GetDeviceUploadLogListRsp, |
||||
}>() |
||||
const emit = defineEmits(['update:visible']) |
||||
|
||||
const sVisible = ref(false) |
||||
|
||||
watchEffect(() => { |
||||
sVisible.value = props.visible |
||||
if (props.visible) { |
||||
classifyDeviceLog() |
||||
} |
||||
}) |
||||
|
||||
function onVisibleChange (sVisible: boolean) { |
||||
setVisible(sVisible) |
||||
} |
||||
|
||||
function setVisible (v: boolean, e?: Event) { |
||||
sVisible.value = v |
||||
emit('update:visible', v, e) |
||||
} |
||||
|
||||
// 表格 |
||||
const airportLogColumns: ColumnProps[] = [ |
||||
{ title: '机场日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } }, |
||||
{ title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } }, |
||||
] |
||||
|
||||
const droneLogColumns: ColumnProps[] = [ |
||||
{ title: '飞行器日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } }, |
||||
{ title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } }, |
||||
] |
||||
|
||||
const airportTableLogState = reactive({ |
||||
logList: {} as DeviceLogFileInfo, |
||||
}) |
||||
|
||||
const droneTableLogState = reactive({ |
||||
logList: {} as DeviceLogFileInfo, |
||||
}) |
||||
|
||||
function classifyDeviceLog () { |
||||
if (!props.deviceLog) return |
||||
const { device_logs } = props.deviceLog |
||||
const { files } = device_logs || {} |
||||
if (files && files.length > 0) { |
||||
files.forEach(file => { |
||||
if (file.module === DOMAIN.DOCK) { |
||||
airportTableLogState.logList = file |
||||
} else if (file.module === DOMAIN.DRONE) { |
||||
droneTableLogState.logList = file |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const { getLogTime, getLogSize } = useDeviceLogUploadDetail() |
||||
|
||||
async function onDownloadLog (fileId: string) { |
||||
const { data } = await getUploadDeviceLogUrl({ |
||||
file_id: fileId, |
||||
logs_id: props.deviceLog?.logs_id || '' |
||||
}) |
||||
if (data) { |
||||
download(data) |
||||
// download('https:/github.com/dji-sdk/Mobile-SDK-Android-V5/archive/refs/heads/dev-sdk-main.zip') |
||||
} |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.device-log-detail-wrap{ |
||||
|
||||
.device-log-list{ |
||||
display: flex; |
||||
justify-content: space-between; |
||||
padding: 8px 0; |
||||
.log-list-item{ |
||||
width: 420px; |
||||
|
||||
.download-btn{ |
||||
margin-bottom: 10px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
> |
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
<template> |
||||
<a-modal |
||||
title="设备日志上传" |
||||
v-model:visible="sVisible" |
||||
width="900px" |
||||
:footer="null" |
||||
@update:visible="onVisibleChange"> |
||||
<div class="device-log-upload-wrap"> |
||||
<div class="page-action-row"> |
||||
<a-button type="primary" :disabled="deviceLogUploadBtnDisabled" @click="uploadDeviceLog">上传日志</a-button> |
||||
</div> |
||||
<div class="device-log-list"> |
||||
<div class="log-list-item"> |
||||
<a-table :columns="airportLogColumns" |
||||
:scroll="{ x: '100%', y: 600 }" |
||||
:data-source="airportTableLogState.logList?.list" |
||||
:loading="airportTableLogState.tableLoading" |
||||
:row-selection="airportTableLogState.rowSelection" |
||||
rowKey="boot_index" |
||||
:pagination = "false"> |
||||
<template #log_time="{record}"> |
||||
<div>{{getLogTime(record)}}</div> |
||||
</template> |
||||
<template #size="{record}"> |
||||
<div>{{getLogSize(record.size)}}</div> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
<div class="log-list-item"> |
||||
<a-table :columns="droneLogColumns" |
||||
:scroll="{ x: '100%', y: 600 }" |
||||
:data-source="droneTableLogState.logList?.list" |
||||
:loading="droneTableLogState.tableLoading" |
||||
:row-selection="droneTableLogState.rowSelection" |
||||
rowKey="boot_index" |
||||
:pagination = "false"> |
||||
<template #log_time="{record}"> |
||||
<div>{{getLogTime(record)}}</div> |
||||
</template> |
||||
<template #size="{record}"> |
||||
<div>{{getLogSize(record.size)}}</div> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</a-modal> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { watchEffect, reactive, ref, computed, defineProps, defineEmits } from 'vue' |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { Device, DOMAIN } from '/@/types/device' |
||||
import { getDeviceLogList, postDeviceUpgrade, DeviceLogFileInfo, UploadDeviceLogBody, DeviceLogItem } from '/@/api/device-log' |
||||
import { message } from 'ant-design-vue' |
||||
import { useDeviceLogUploadDetail } from './use-device-log-upload-detail' |
||||
|
||||
const props = defineProps<{ |
||||
visible: boolean, |
||||
device: null | Device, |
||||
}>() |
||||
const emit = defineEmits(['update:visible', 'upload-log-ok']) |
||||
|
||||
const sVisible = ref(false) |
||||
|
||||
watchEffect(() => { |
||||
sVisible.value = props.visible |
||||
// 显示弹框时,获取设备日志信息 |
||||
if (props.visible) { |
||||
getDeviceLogInfo() |
||||
} |
||||
}) |
||||
|
||||
function onVisibleChange (sVisible: boolean) { |
||||
setVisible(sVisible) |
||||
if (!sVisible) { |
||||
resetTableLogState() |
||||
} |
||||
} |
||||
|
||||
function setVisible (v: boolean, e?: Event) { |
||||
sVisible.value = v |
||||
emit('update:visible', v, e) |
||||
} |
||||
|
||||
// 表格 |
||||
const airportLogColumns: ColumnProps[] = [ |
||||
{ title: '机场日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } }, |
||||
{ title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } }, |
||||
] |
||||
|
||||
const droneLogColumns: ColumnProps[] = [ |
||||
{ title: '飞行器日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } }, |
||||
{ title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } }, |
||||
] |
||||
|
||||
const airportTableLogState = reactive({ |
||||
logList: {} as DeviceLogFileInfo, |
||||
tableLoading: false, |
||||
selectRow: [], |
||||
rowSelection: { |
||||
columnWidth: 15, |
||||
selectedRowKeys: [] as number[], |
||||
onChange: (selectedRowKeys:number[], selectedRows: []) => { |
||||
airportTableLogState.rowSelection.selectedRowKeys = selectedRowKeys |
||||
airportTableLogState.selectRow = selectedRows |
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
||||
}, |
||||
} |
||||
}) |
||||
|
||||
function resetTableLogState () { |
||||
airportTableLogState.logList = {} as DeviceLogFileInfo |
||||
airportTableLogState.selectRow = [] |
||||
airportTableLogState.tableLoading = false |
||||
} |
||||
|
||||
const droneTableLogState = reactive({ |
||||
logList: {} as DeviceLogFileInfo, |
||||
tableLoading: false, |
||||
selectRow: [], |
||||
rowSelection: { |
||||
columnWidth: 15, |
||||
selectedRowKeys: [] as number[], |
||||
onChange: (selectedRowKeys: number[], selectedRows: []) => { |
||||
droneTableLogState.rowSelection.selectedRowKeys = selectedRowKeys |
||||
droneTableLogState.selectRow = selectedRows |
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
||||
}, |
||||
} |
||||
}) |
||||
|
||||
const deviceLogUploadBtnDisabled = computed(() => { |
||||
return (airportTableLogState.rowSelection.selectedRowKeys && airportTableLogState.rowSelection.selectedRowKeys.length <= 0) && |
||||
(droneTableLogState.rowSelection.selectedRowKeys && droneTableLogState.rowSelection.selectedRowKeys.length <= 0) |
||||
}) |
||||
|
||||
// 获取设备内日志 |
||||
async function getDeviceLogInfo () { |
||||
airportTableLogState.tableLoading = true |
||||
droneTableLogState.tableLoading = true |
||||
try { |
||||
const { code, data } = await getDeviceLogList({ |
||||
device_sn: props.device?.device_sn || '', |
||||
domain: [DOMAIN.DOCK, DOMAIN.DRONE] |
||||
}) |
||||
if (code === 0) { |
||||
const { files } = data |
||||
if (files && files.length > 0) { |
||||
files.forEach(file => { |
||||
if (file.module === DOMAIN.DOCK) { |
||||
airportTableLogState.logList = file |
||||
} else if (file.module === DOMAIN.DRONE) { |
||||
droneTableLogState.logList = file |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
} catch (err) { |
||||
} |
||||
airportTableLogState.tableLoading = false |
||||
droneTableLogState.tableLoading = false |
||||
} |
||||
|
||||
// 日志上传 |
||||
async function uploadDeviceLog () { |
||||
const body = { |
||||
device_sn: props.device?.device_sn || '', |
||||
files: [] as any |
||||
} as UploadDeviceLogBody |
||||
if (airportTableLogState.selectRow && airportTableLogState.selectRow.length > 0) { |
||||
body.files.push({ |
||||
list: airportTableLogState.selectRow, |
||||
device_sn: airportTableLogState.logList.device_sn, |
||||
module: airportTableLogState.logList.module |
||||
}) |
||||
} |
||||
if (droneTableLogState.selectRow && droneTableLogState.selectRow.length > 0) { |
||||
body.files.push({ |
||||
list: droneTableLogState.selectRow, |
||||
device_sn: droneTableLogState.logList.device_sn, |
||||
module: droneTableLogState.logList.module |
||||
}) |
||||
} |
||||
const { code } = await postDeviceUpgrade(body) |
||||
if (code === 0) { |
||||
message.success('日志上传任务执行成功') |
||||
emit('upload-log-ok') |
||||
setVisible(false) |
||||
} |
||||
} |
||||
|
||||
const { getLogTime, getLogSize } = useDeviceLogUploadDetail() |
||||
|
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.device-log-upload-wrap{ |
||||
|
||||
.device-log-list{ |
||||
display: flex; |
||||
justify-content: space-between; |
||||
padding: 8px 0; |
||||
.log-list-item{ |
||||
width: 420px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,326 @@
@@ -0,0 +1,326 @@
|
||||
<template> |
||||
<a-drawer |
||||
title="设备日志上传记录" |
||||
placement="right" |
||||
v-model:visible="sVisible" |
||||
@update:visible="onVisibleChange" |
||||
:width="800"> |
||||
<!-- 设备日志上传记录 --> |
||||
<div class="device-log-upload-record-wrap"> |
||||
<div class="page-action-row"> |
||||
<a-button type="primary" @click="onUploadDeviceLog">上传日志</a-button> |
||||
</div> |
||||
<div class="device-log-upload-list"> |
||||
<a-table :columns="deviceLogUploadListColumns" |
||||
:scroll="{ x: '100%', y: 600 }" |
||||
:data-source="deviceUploadLogState.uploadLogList" |
||||
:loading="deviceUploadLogState.loading" |
||||
:pagination="deviceUploadLogState.paginationProp" |
||||
@change="onDeviceUploadLogTableChange" |
||||
rowKey="logs_id"> |
||||
<!-- 设备类型 --> |
||||
<template #device_type="{ record }"> |
||||
<div> |
||||
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.key]}}</div> |
||||
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.key]}}</div> |
||||
</div> |
||||
</template> |
||||
<!-- 设备sn --> |
||||
<template #device_sn="{ record }"> |
||||
<div> |
||||
<div v-if="getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0">{{ getDeviceInfo(record).parents[0].sn }}</div> |
||||
<div v-if="getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0">{{ getDeviceInfo(record).hosts[0].sn }}</div> |
||||
</div> |
||||
</template> |
||||
<!-- 上传状态 --> |
||||
<template #status="{ record }"> |
||||
<div> |
||||
<div> |
||||
<span class="circle-icon" :style="{backgroundColor: getDeviceLogUploadStatus(record).color}"></span> |
||||
{{ getDeviceLogUploadStatus(record).text }} |
||||
</div> |
||||
<div v-if="record.status === DeviceLogUploadStatusEnum.Uploading"> |
||||
<a-progress :percent="getLogProgress(record)" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<!-- 操作 --> |
||||
<template #action="{ record }"> |
||||
<div class="row-action"> |
||||
<a-tooltip title="查看详情"> |
||||
<FileTextOutlined @click="showDeviceLogDetail(record)"/> |
||||
</a-tooltip> |
||||
<span v-if="record.status === DeviceLogUploadStatusEnum.Uploading"> |
||||
<a-tooltip title="取消"> |
||||
<StopOutlined @click="onCancelUploadDeviceLog(record)"/> |
||||
</a-tooltip> |
||||
</span> |
||||
<span v-else> |
||||
<a-tooltip title="删除"> |
||||
<DeleteOutlined @click="onDeleteUploadDeviceLog(record)"/> |
||||
</a-tooltip> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
</div> |
||||
</a-drawer> |
||||
<!-- 设备日志上传弹框 --> |
||||
<DeviceLogUploadModal |
||||
v-model:visible="deviceLogUploadModalVisible" |
||||
:device="props.device" |
||||
@upload-log-ok="onUploadLogOk" |
||||
></DeviceLogUploadModal> |
||||
|
||||
<!-- 设备日志上传详情弹框 --> |
||||
<DeviceLogDetailModal |
||||
v-model:visible="deviceLogDetailModalVisible" |
||||
:deviceLog="currentDeviceLog" |
||||
></DeviceLogDetailModal> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue' |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { Device, DOMAIN, DEVICE_NAME } from '/@/types/device' |
||||
import DeviceLogUploadModal from './DeviceLogUploadModal.vue' |
||||
import DeviceLogDetailModal from './DeviceLogDetailModal.vue' |
||||
import { getDeviceUploadLogList, GetDeviceUploadLogListRsp, cancelDeviceLogUpload, deleteDeviceLogUpload } from '/@/api/device-log' |
||||
import { StopOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons-vue' |
||||
import { DeviceLogUploadStatusEnum, DeviceLogUploadStatusMap, DeviceLogUploadStatusColor, DeviceLogUploadInfo, DeviceLogUploadWsStatusMap, DeviceLogProgressInfo } from '/@/types/device-log' |
||||
import { useDeviceLogUploadProgressEvent } from './use-device-log-upload-progress-event' |
||||
import { Modal } from 'ant-design-vue' |
||||
|
||||
const props = defineProps<{ |
||||
visible: boolean, |
||||
device: null | Device, |
||||
}>() |
||||
const emit = defineEmits(['update:visible']) |
||||
|
||||
const sVisible = ref(false) |
||||
|
||||
watchEffect(() => { |
||||
sVisible.value = props.visible |
||||
// 显示弹框时,获取设备日志上传记录信息 |
||||
if (props.visible) { |
||||
getDeviceUploadLogInfo() |
||||
} |
||||
}) |
||||
|
||||
function onVisibleChange (sVisible: boolean) { |
||||
setVisible(sVisible) |
||||
} |
||||
|
||||
function setVisible (v: boolean, e?: Event) { |
||||
sVisible.value = v |
||||
emit('update:visible', v, e) |
||||
} |
||||
|
||||
// 日志列表 |
||||
const deviceLogUploadListColumns: ColumnProps[] = [ |
||||
{ title: '上传时间', dataIndex: 'create_time', width: 100 }, |
||||
{ title: '设备型号', dataIndex: 'device_type', width: 80, slots: { customRender: 'device_type' } }, |
||||
{ title: '设备SN', dataIndex: 'device_sn', width: 120, slots: { customRender: 'device_sn' } }, |
||||
{ title: '上传状态', dataIndex: 'status', width: 120, slots: { customRender: 'status' } }, |
||||
{ title: '操作', dataIndex: 'actions', width: 80, slots: { customRender: 'action' } }, |
||||
] |
||||
|
||||
const deviceUploadLogState = reactive({ |
||||
uploadLogList: [] as GetDeviceUploadLogListRsp[], |
||||
loading: false, |
||||
paginationProp: { |
||||
pageSizeOptions: ['20', '50', '100'], |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
pageSize: 50, |
||||
current: 1, |
||||
total: 0 |
||||
} |
||||
}) |
||||
|
||||
// 获取上传的设备日志 |
||||
async function getDeviceUploadLogInfo () { |
||||
deviceUploadLogState.loading = true |
||||
try { |
||||
const { code, data } = await getDeviceUploadLogList({ |
||||
device_sn: props.device?.device_sn || '', |
||||
page: deviceUploadLogState.paginationProp.current, |
||||
page_size: deviceUploadLogState.paginationProp.pageSize |
||||
}) |
||||
if (code === 0) { |
||||
deviceUploadLogState.uploadLogList = data.list |
||||
deviceUploadLogState.paginationProp.total = data.pagination.total |
||||
deviceUploadLogState.paginationProp.current = data.pagination.page |
||||
deviceUploadLogState.paginationProp.pageSize = data.pagination.page_size |
||||
} |
||||
deviceUploadLogState.loading = false |
||||
} catch (error) { |
||||
deviceUploadLogState.loading = false |
||||
} |
||||
} |
||||
type Pagination = TableState['pagination'] |
||||
|
||||
// 获取设备信息 |
||||
function getDeviceInfo (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
const { device_topo: deviceTopo } = deviceLogItem |
||||
return deviceTopo |
||||
} |
||||
|
||||
// 获取上传状态 |
||||
function getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
const statusObj = { |
||||
color: '', |
||||
text: '' |
||||
} |
||||
const { status } = deviceLogItem |
||||
statusObj.color = DeviceLogUploadStatusColor[status] |
||||
statusObj.text = DeviceLogUploadStatusMap[status] |
||||
return statusObj |
||||
} |
||||
|
||||
// 获取上传进度 |
||||
function getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
let percent = 0 |
||||
const { logs_progress } = deviceLogItem |
||||
if (logs_progress && logs_progress.length > 0) { |
||||
logs_progress.forEach(log => { |
||||
percent += (log.progress || 0) |
||||
}) |
||||
percent = percent / logs_progress.length |
||||
} |
||||
return Math.floor(percent) |
||||
} |
||||
|
||||
// 设备日志上传进度更新 |
||||
function onDeviceLogUploadWs (data: DeviceLogUploadInfo) { |
||||
const { sn, output } = data |
||||
if (output) { |
||||
const { files, status, logs_id: logId } = output || {} |
||||
const deviceLogItem = deviceUploadLogState.uploadLogList.find(log => log.logs_id === logId) |
||||
if (!deviceLogItem) return |
||||
if (status) { |
||||
deviceLogItem.status = DeviceLogUploadWsStatusMap[status] |
||||
} |
||||
if (files && files.length > 0) { |
||||
const logsProgress = [] as DeviceLogProgressInfo[] |
||||
files.forEach(file => { |
||||
logsProgress.push({ |
||||
...file, |
||||
status: DeviceLogUploadWsStatusMap[file.status] |
||||
}) |
||||
}) |
||||
deviceLogItem.logs_progress = logsProgress |
||||
} |
||||
} |
||||
} |
||||
|
||||
useDeviceLogUploadProgressEvent(onDeviceLogUploadWs) |
||||
|
||||
// 搜索 |
||||
async function onDeviceUploadLogTableChange (page: Pagination) { |
||||
deviceUploadLogState.paginationProp.current = page?.current || 1 |
||||
deviceUploadLogState.paginationProp.pageSize = page?.pageSize || 20 |
||||
await getDeviceUploadLogInfo() |
||||
} |
||||
|
||||
// 查看上传设备日志详情 |
||||
const deviceLogDetailModalVisible = ref(false) |
||||
const currentDeviceLog = ref({} as GetDeviceUploadLogListRsp) |
||||
|
||||
function showDeviceLogDetail (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
if (!deviceLogItem) return |
||||
currentDeviceLog.value = deviceLogItem |
||||
deviceLogDetailModalVisible.value = true |
||||
} |
||||
|
||||
// 取消上传设备日志 |
||||
async function onCancelUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
Modal.confirm({ |
||||
title: '取消日志上传', |
||||
content: '您确认取消设备日志上传吗?', |
||||
okType: 'danger', |
||||
onOk () { |
||||
cancelDeviceLogUploadOk() |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
async function cancelDeviceLogUploadOk () { |
||||
const { code } = await cancelDeviceLogUpload({ |
||||
device_sn: props.device?.device_sn || '', |
||||
module_list: [DOMAIN.DOCK, DOMAIN.DRONE], |
||||
status: 'cancel' |
||||
}) |
||||
if (code === 0) { |
||||
await getDeviceUploadLogInfo() |
||||
} |
||||
} |
||||
|
||||
// 删除上传的设备日志 |
||||
function onDeleteUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
Modal.confirm({ |
||||
title: '删除上传日志', |
||||
content: '您确认删除该条已上传设备日志吗?', |
||||
okType: 'danger', |
||||
onOk () { |
||||
deleteUploadDeviceLogOk(deviceLogItem) |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
async function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp) { |
||||
const { code } = await deleteDeviceLogUpload({ |
||||
device_sn: props.device?.device_sn || '', |
||||
logs_id: deviceLogItem.logs_id |
||||
}) |
||||
if (code === 0) { |
||||
await getDeviceUploadLogInfo() |
||||
} |
||||
} |
||||
|
||||
// 上传日志 |
||||
const deviceLogUploadModalVisible = ref(false) |
||||
|
||||
function onUploadDeviceLog () { |
||||
deviceLogUploadModalVisible.value = true |
||||
} |
||||
|
||||
function onUploadLogOk () { |
||||
// 刷新列表 |
||||
getDeviceUploadLogInfo() |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.device-log-upload-record-wrap{ |
||||
.page-action-row{ |
||||
display: flex; |
||||
justify-content: space-between; |
||||
width: 100%; |
||||
} |
||||
|
||||
.device-log-upload-list{ |
||||
padding: 20px 0 10px; |
||||
} |
||||
|
||||
.circle-icon { |
||||
display: inline-block; |
||||
width: 12px; |
||||
height: 12px; |
||||
margin-right: 3px; |
||||
border-radius: 50%; |
||||
vertical-align: middle; |
||||
flex-shrink: 0; |
||||
} |
||||
|
||||
.row-action{ |
||||
color: #2d8cf0; |
||||
|
||||
& > span{ |
||||
margin-right: 10px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
import { DeviceLogItem } from '/@/api/device-log' |
||||
import { bytesToSize } from '/@/utils/bytes' |
||||
import { formatUnixTime } from '/@/utils/time' |
||||
import { |
||||
DATE_FORMAT_MINUTE |
||||
} from '/@/utils/constants' |
||||
|
||||
export function useDeviceLogUploadDetail () { |
||||
function getLogTime (deviceLog: DeviceLogItem): string { |
||||
const startTime = formatUnixTime(deviceLog.start_time, DATE_FORMAT_MINUTE) |
||||
const endTime = formatUnixTime(deviceLog.end_time, DATE_FORMAT_MINUTE) |
||||
return `${startTime} — ${endTime}` |
||||
} |
||||
|
||||
function getLogSize (size: number) { |
||||
return bytesToSize(size) |
||||
} |
||||
|
||||
return { |
||||
getLogTime, |
||||
getLogSize |
||||
} |
||||
} |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
import EventBus from '/@/event-bus/' |
||||
import { onMounted, onBeforeUnmount } from 'vue' |
||||
import { DeviceLogUploadInfo } from '/@/types/device-log' |
||||
|
||||
export function useDeviceLogUploadProgressEvent (onDeviceLogUploadWs: (data: DeviceLogUploadInfo) => void): void { |
||||
function handleDeviceLogUploadProgress (payload: any) { |
||||
onDeviceLogUploadWs(payload.data) |
||||
// eslint-disable-next-line no-unused-expressions
|
||||
// console.log('payload', payload.data)
|
||||
} |
||||
|
||||
onMounted(() => { |
||||
EventBus.on('deviceLogUploadProgress', handleDeviceLogUploadProgress) |
||||
}) |
||||
|
||||
onBeforeUnmount(() => { |
||||
EventBus.off('deviceLogUploadProgress', handleDeviceLogUploadProgress) |
||||
}) |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
<template> |
||||
<div class="firmware_upgrade_wrap"> |
||||
<!-- 版本 --> |
||||
<span class="version"> {{ device.firmware_version }}</span> |
||||
<!-- tag --> |
||||
<span v-if="getTagStatus(device)" |
||||
class="status-tag pointer"> |
||||
<a-tag class="pointer" |
||||
:color="getFirmwareTag(device.firmware_status).color" |
||||
@click="deviceUpgrade(device)"> |
||||
{{ getFirmwareTag(device.firmware_status).text }} |
||||
</a-tag> |
||||
</span> |
||||
<!-- 进度 --> |
||||
<span v-if="device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade"> |
||||
{{ `${device.firmware_progress}%`}} |
||||
</span> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { defineProps, defineEmits, ref, watch, computed } from 'vue' |
||||
import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareStatusColor } from '/@/types/device' |
||||
|
||||
const props = defineProps<{ |
||||
device: Device, |
||||
}>() |
||||
|
||||
const emit = defineEmits(['device-upgrade']) |
||||
const needUpgrade = computed(() => { |
||||
return props.device.firmware_status === DeviceFirmwareStatusEnum.ConsistencyUpgrade || |
||||
props.device.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded |
||||
}) |
||||
|
||||
function getTagStatus (record: Device) { |
||||
return record.firmware_status && record.firmware_status !== DeviceFirmwareStatusEnum.None |
||||
} |
||||
|
||||
function getFirmwareTag (status: DeviceFirmwareStatusEnum) { |
||||
return { |
||||
text: DeviceFirmwareStatus[status] || '', |
||||
color: DeviceFirmwareStatusColor[status] || '' |
||||
} |
||||
} |
||||
|
||||
function deviceUpgrade (record: Device) { |
||||
if (!needUpgrade.value) return |
||||
emit('device-upgrade', record) |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.firmware_upgrade_wrap{ |
||||
|
||||
.status-tag{ |
||||
margin-left: 10px; |
||||
} |
||||
|
||||
.pointer { |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
<template> |
||||
<a-modal :visible="sVisible" |
||||
:title="title" |
||||
:closable="false" |
||||
centered |
||||
@update:visible="onVisibleChange" |
||||
@cancel="onCancel" |
||||
@ok="onConfirm"> |
||||
<div> |
||||
升级固件版本: {{ deviceUpgradeInfo?.product_version }} |
||||
</div> |
||||
</a-modal> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { defineProps, defineEmits, ref, Ref, watchEffect } from 'vue' |
||||
import { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareTypeEnum } from '/@/types/device' |
||||
import { getDeviceUpgradeInfo, GetDeviceUpgradeInfoRsp, DeviceUpgradeBody } from '/@/api/device-upgrade' |
||||
|
||||
const props = defineProps<{ |
||||
visible: boolean, |
||||
title: string, |
||||
device: null | Device, |
||||
}>() |
||||
|
||||
const emit = defineEmits(['update:visible', 'ok', 'cancel']) |
||||
|
||||
const deviceUpgradeInfo:Ref<GetDeviceUpgradeInfoRsp> = ref({} as GetDeviceUpgradeInfoRsp) |
||||
const sVisible = ref(false) |
||||
|
||||
watchEffect(() => { |
||||
sVisible.value = props.visible |
||||
// 显示弹框时,获取设备升级信息 |
||||
if (props.visible) { |
||||
initDeviceUpgradeInfo() |
||||
} |
||||
}) |
||||
|
||||
function onVisibleChange (sVisible: boolean) { |
||||
setVisible(sVisible) |
||||
} |
||||
|
||||
function setVisible (v: boolean, e?: Event) { |
||||
sVisible.value = v |
||||
emit('update:visible', v, e) |
||||
} |
||||
|
||||
// 获取设备升级信息 |
||||
async function initDeviceUpgradeInfo () { |
||||
if (!props.device?.device_name) { |
||||
return |
||||
} |
||||
const { code, data } = await getDeviceUpgradeInfo({ device_name: props.device?.device_name }) |
||||
if (code === 0) { |
||||
deviceUpgradeInfo.value = data && data[0] |
||||
} |
||||
} |
||||
|
||||
// 提交 |
||||
function checkConfirm () { |
||||
if (!deviceUpgradeInfo.value.product_version) { |
||||
return false |
||||
} |
||||
if (!props.device) { |
||||
return false |
||||
} |
||||
if (props.device.firmware_status !== DeviceFirmwareStatusEnum.ToUpgraded && props.device.firmware_status !== DeviceFirmwareStatusEnum.ConsistencyUpgrade) { |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
function onConfirm (e: Event) { |
||||
if (!checkConfirm()) { |
||||
return |
||||
} |
||||
setVisible(false, e) |
||||
emit('ok', [{ |
||||
device_name: props.device?.device_name, |
||||
sn: props.device?.device_sn, |
||||
product_version: deviceUpgradeInfo.value.product_version, |
||||
firmware_upgrade_type: props.device?.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded ? DeviceFirmwareTypeEnum.ToUpgraded : DeviceFirmwareTypeEnum.ConsistencyUpgrade // 1-普通升级,2-一致性升级 |
||||
}] as DeviceUpgradeBody, e) |
||||
} |
||||
|
||||
function onCancel (e: Event) { |
||||
setVisible(false, e) |
||||
emit('cancel', e) |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
</style> |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
import EventBus from '/@/event-bus/' |
||||
import { onMounted, onBeforeUnmount } from 'vue' |
||||
import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
||||
|
||||
export function useDeviceUpgradeEvent (onDeviceUpgradeWs: (payload: DeviceCmdExecuteInfo) => void): void { |
||||
function handleDeviceUpgrade (payload: any) { |
||||
onDeviceUpgradeWs(payload.data) |
||||
// eslint-disable-next-line no-unused-expressions
|
||||
// console.log('payload', payload.data)
|
||||
} |
||||
|
||||
onMounted(() => { |
||||
EventBus.on('deviceUpgrade', handleDeviceUpgrade) |
||||
}) |
||||
|
||||
onBeforeUnmount(() => { |
||||
EventBus.off('deviceUpgrade', handleDeviceUpgrade) |
||||
}) |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
import { Ref, ref } from 'vue' |
||||
import { Device } from '/@/types/device' |
||||
import { postDeviceUpgrade, DeviceUpgradeBody } from '/@/api/device-upgrade' |
||||
|
||||
export function useDeviceFirmwareUpgrade (workspaceId: string) { |
||||
const deviceFirmwareUpgradeModalVisible = ref(false) |
||||
const selectedDevice: Ref<null | Device> = ref(null) |
||||
|
||||
function setDeviceFirmwareUpgradeModalVisible (visible: boolean) { |
||||
deviceFirmwareUpgradeModalVisible.value = visible |
||||
} |
||||
|
||||
function setSelectedDevice (device: null | Device) { |
||||
selectedDevice.value = device |
||||
} |
||||
|
||||
// 点击设备升级
|
||||
function onDeviceUpgrade (record: Device) { |
||||
if (!record) { |
||||
return |
||||
} |
||||
setSelectedDevice(record) |
||||
setDeviceFirmwareUpgradeModalVisible(true) |
||||
} |
||||
|
||||
// 确认设备升级
|
||||
async function onUpgradeDeviceOk (deviceUpgradeBody: DeviceUpgradeBody) { |
||||
const { code } = await postDeviceUpgrade(workspaceId, deviceUpgradeBody) |
||||
if (code === 0) { |
||||
// setDeviceFirmwareUpgradeModalVisible(false)
|
||||
} |
||||
} |
||||
|
||||
return { |
||||
deviceFirmwareUpgradeModalVisible, |
||||
setDeviceFirmwareUpgradeModalVisible, |
||||
selectedDevice, |
||||
setSelectedDevice, |
||||
onDeviceUpgrade, |
||||
onUpgradeDeviceOk, |
||||
} |
||||
} |
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
<template> |
||||
<div class="dock-control-panel"> |
||||
<!-- title --> |
||||
<div class="dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between"> |
||||
<span>远程调试 {{ props.sn}}</span> |
||||
<span @click="closeControlPanel"> |
||||
<CloseOutlined /> |
||||
</span> |
||||
</div> |
||||
<!-- cmd --> |
||||
<div class="control-cmd-wrapper"> |
||||
<div v-for="(cmdItem, index) in cmdList" :key="cmdItem.cmdKey" class="control-cmd-item"> |
||||
<div class="control-cmd-item-left"> |
||||
<div class="item-label">{{ cmdItem.label }}</div> |
||||
<div class="item-status">{{ cmdItem.status }}</div> |
||||
</div> |
||||
<div class="control-cmd-item-right"> |
||||
<a-button :loading="cmdItem.loading" size="small" type="primary" @click="sendControlCmd(cmdItem, index)"> |
||||
{{ cmdItem.operateText }} |
||||
</a-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { defineProps, defineEmits, ref, watch } from 'vue' |
||||
import { |
||||
CloseOutlined |
||||
} from '@ant-design/icons-vue' |
||||
import { useDockControl } from './useDockControl' |
||||
import { DeviceInfoType } from '/@/types/device' |
||||
import { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd' |
||||
import { useMyStore } from '/@/store' |
||||
import { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd' |
||||
|
||||
const props = defineProps<{ |
||||
sn: string, |
||||
deviceInfo: DeviceInfoType, |
||||
}>() |
||||
|
||||
const store = useMyStore() |
||||
const initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem)) |
||||
const cmdList = ref(initCmdList) |
||||
|
||||
// 根据机场指令执行状态更新信息 |
||||
watch(() => store.state.devicesCmdExecuteInfo, (devicesCmdExecuteInfo) => { |
||||
if (props.sn && devicesCmdExecuteInfo[props.sn]) { |
||||
updateDeviceCmdInfoByExecuteInfo(cmdList.value, devicesCmdExecuteInfo[props.sn]) |
||||
} |
||||
}, { |
||||
immediate: true, |
||||
deep: true, |
||||
}) |
||||
|
||||
// 根据设备osd信息更新信息 |
||||
watch(() => props.deviceInfo, (value) => { |
||||
updateDeviceCmdInfoByOsd(cmdList.value, value) |
||||
// console.log('deviceInfo', value) |
||||
}, { |
||||
immediate: true, |
||||
deep: true |
||||
}) |
||||
|
||||
const emit = defineEmits(['close-control-panel']) |
||||
|
||||
function closeControlPanel () { |
||||
emit('close-control-panel', props.sn, false) |
||||
} |
||||
|
||||
// dock 控制指令 |
||||
const { |
||||
sendDockControlCmd, |
||||
} = useDockControl() |
||||
|
||||
async function sendControlCmd (cmdItem: DeviceCmdItem, index: number) { |
||||
const success = await sendDockControlCmd({ |
||||
sn: props.sn, |
||||
cmd: cmdItem.cmdKey |
||||
}, true) |
||||
if (success) { |
||||
// updateDeviceSingleCmdInfo(cmdList.value[index]) |
||||
} |
||||
} |
||||
|
||||
</script> |
||||
|
||||
<style lang='scss' scoped> |
||||
.dock-control-panel{ |
||||
position: absolute; |
||||
left: calc(100% + 10px); |
||||
top: 0px; |
||||
width: 480px; |
||||
padding: 0 !important; |
||||
background: #000; |
||||
color: #fff; |
||||
border-radius: 2px; |
||||
|
||||
.dock-control-panel-header{ |
||||
border-bottom: 1px solid #515151; |
||||
} |
||||
|
||||
.control-cmd-wrapper{ |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
justify-content: space-between; |
||||
padding: 4px 10px; |
||||
.control-cmd-item{ |
||||
width: 220px; |
||||
height: 58px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
border: 1px solid #666; |
||||
margin: 4px 0; |
||||
padding: 0 8px; |
||||
|
||||
.control-cmd-item-left{ |
||||
display: flex; |
||||
flex-direction: column; |
||||
|
||||
.item-label{ |
||||
font-weight: 700; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
import { message } from 'ant-design-vue' |
||||
import { ref } from 'vue' |
||||
import { postSendCmd } from '/@/api/device-cmd' |
||||
import { DeviceCmd } from '/@/types/device-cmd' |
||||
|
||||
export function useDockControl () { |
||||
const controlPanelVisible = ref(false) |
||||
|
||||
function setControlPanelVisible (visible: boolean) { |
||||
controlPanelVisible.value = visible |
||||
} |
||||
|
||||
// 远程调试开关
|
||||
async function dockDebugOnOff (sn: string, on: boolean) { |
||||
const success = await sendDockControlCmd({ |
||||
sn: sn, |
||||
cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose |
||||
}, false) |
||||
if (success) { |
||||
setControlPanelVisible(on) |
||||
} |
||||
} |
||||
|
||||
// 发送指令
|
||||
async function sendDockControlCmd (params: { |
||||
sn: string, |
||||
cmd: DeviceCmd |
||||
}, tip = true) { |
||||
try { |
||||
const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }) |
||||
if (code === 0) { |
||||
tip && message.success('指令发送成功') |
||||
return true |
||||
} |
||||
throw (msg) |
||||
} catch (e) { |
||||
tip && message.error('指令发送失败') |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return { |
||||
controlPanelVisible, |
||||
setControlPanelVisible, |
||||
sendDockControlCmd, |
||||
dockDebugOnOff, |
||||
} |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
import mitt, { Emitter } from 'mitt' |
||||
|
||||
type Events = { |
||||
deviceUpgrade: any; |
||||
deviceLogUploadProgress: any |
||||
}; |
||||
|
||||
const emitter: Emitter<Events> = mitt<Events>() |
||||
|
||||
export default emitter |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
import { onMounted, onUnmounted } from 'vue' |
||||
import ReconnectingWebSocket from 'reconnecting-websocket' |
||||
import ConnectWebSocket, { MessageHandler } from '/@/websocket' |
||||
import { getWebsocketUrl } from '/@/websocket/util/config' |
||||
|
||||
/** |
||||
* 接收一个message函数 |
||||
* @param messageHandler |
||||
*/ |
||||
export function useConnectWebSocket (messageHandler: MessageHandler) { |
||||
const webSocket = new ConnectWebSocket(getWebsocketUrl()) |
||||
|
||||
onMounted(() => { |
||||
webSocket?.registerMessageHandler(messageHandler) |
||||
webSocket?.initSocket() |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
webSocket?.close() |
||||
}) |
||||
} |
@ -1,21 +0,0 @@
@@ -1,21 +0,0 @@
|
||||
<template> |
||||
<div class="element-map-wrapper"> |
||||
<GMap/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { defineComponent, onMounted } from 'vue' |
||||
import GMap from '/@/components/GMap.vue' |
||||
export default defineComponent({ |
||||
name: 'Elements', |
||||
components: { GMap }, |
||||
setup () { |
||||
return { |
||||
} |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
</style> |
@ -0,0 +1,436 @@
@@ -0,0 +1,436 @@
|
||||
|
||||
<template> |
||||
<a-menu v-model:selectedKeys="current" mode="horizontal" @select="select"> |
||||
<a-menu-item :key="EDeviceTypeName.Aircraft" class="ml20"> |
||||
Aircraft |
||||
</a-menu-item> |
||||
<a-menu-item :key="EDeviceTypeName.Dock"> |
||||
Dock |
||||
</a-menu-item> |
||||
</a-menu> |
||||
<div class="device-table-wrap table flex-display flex-column"> |
||||
<a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData" row-key="device_sn" :expandedRowKeys="expandRows" |
||||
:row-selection="rowSelection" :rowClassName="rowClassName" :scroll="{ x: '100%', y: 600 }" |
||||
:expandIcon="expandIcon" :loading="loading"> |
||||
<template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col"> |
||||
<div> |
||||
<a-input |
||||
v-if="editableData[record.device_sn]" |
||||
v-model:value="editableData[record.device_sn][col]" |
||||
style="margin: -5px 0" |
||||
/> |
||||
<template v-else> |
||||
{{ text }} |
||||
</template> |
||||
</div> |
||||
</template> |
||||
<template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col"> |
||||
<a-tooltip :title="text"> |
||||
<span>{{ text }}</span> |
||||
</a-tooltip> |
||||
</template> |
||||
<!-- 固件版本 --> |
||||
<template #firmware_version="{ record }"> |
||||
<span v-if="judgeCurrentType(EDeviceTypeName.Dock)"> |
||||
<DeviceFirmwareUpgrade :device="record" |
||||
class="table-flex-col" |
||||
@device-upgrade="onDeviceUpgrade" |
||||
/> |
||||
</span> |
||||
<span v-else> |
||||
{{ record.firmware_version }} |
||||
</span> |
||||
</template> |
||||
<!-- 状态 --> |
||||
<template #status="{ text }"> |
||||
<span v-if="text" class="flex-row flex-align-center"> |
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" /> |
||||
<span>Online</span> |
||||
</span> |
||||
<span class="flex-row flex-align-center" v-else> |
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" /> |
||||
<span>Offline</span> |
||||
</span> |
||||
</template> |
||||
<!-- 操作 --> |
||||
<template #action="{ record }"> |
||||
<div class="editable-row-operations"> |
||||
<!-- 编辑态操作 --> |
||||
<div v-if="editableData[record.device_sn]"> |
||||
<a-tooltip title="Confirm changes"> |
||||
<span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span> |
||||
</a-tooltip> |
||||
<a-tooltip title="Modification canceled"> |
||||
<span @click="() => delete editableData[record.device_sn]" style="color: #e70102;"><CloseOutlined /></span> |
||||
</a-tooltip> |
||||
</div> |
||||
<!-- 非编辑态操作 --> |
||||
<div v-else class="flex-align-center flex-row" style="color: #2d8cf0"> |
||||
<a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="设备日志"> |
||||
<CloudServerOutlined @click="showDeviceLogUploadRecord(record)"/> |
||||
</a-tooltip> |
||||
<a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="Hms Info"> |
||||
<FileSearchOutlined @click="showHms(record)"/> |
||||
</a-tooltip> |
||||
<a-tooltip title="Edit"> |
||||
<EditOutlined @click="edit(record)"/> |
||||
</a-tooltip> |
||||
<a-tooltip title="Delete"> |
||||
<DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }"/> |
||||
</a-tooltip> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
</a-table> |
||||
<a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }" @ok="unbind"> |
||||
<p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p> |
||||
<template #title> |
||||
<div class="flex-row flex-justify-center"> |
||||
<span>Delete devices</span> |
||||
</div> |
||||
</template> |
||||
</a-modal> |
||||
|
||||
<!-- 设备升级 --> |
||||
<DeviceFirmwareUpgradeModal title="设备升级" |
||||
v-model:visible="deviceFirmwareUpgradeModalVisible" |
||||
:device="selectedDevice" |
||||
@ok="onUpgradeDeviceOk" |
||||
></DeviceFirmwareUpgradeModal> |
||||
|
||||
<!-- 设备日志上传记录 --> |
||||
<DeviceLogUploadRecordDrawer |
||||
v-model:visible="deviceLogUploadRecordVisible" |
||||
:device="currentDevice" |
||||
></DeviceLogUploadRecordDrawer> |
||||
|
||||
<!-- hms 信息 --> |
||||
<DeviceHmsDrawer |
||||
v-model:visible="hmsVisible" |
||||
:device="currentDevice"> |
||||
</DeviceHmsDrawer> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { h, onMounted, reactive, ref, UnwrapRef } from 'vue' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { BindBody, bindDevice, getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage' |
||||
import { EDeviceTypeName, ELocalStorageKey } from '/@/types' |
||||
import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue' |
||||
import { Device, DeviceFirmwareStatusEnum } from '/@/types/device' |
||||
import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue' |
||||
import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue' |
||||
import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade' |
||||
import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event' |
||||
import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
||||
import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue' |
||||
import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue' |
||||
import { message } from 'ant-design-vue' |
||||
|
||||
interface DeviceData { |
||||
device: Device[] |
||||
} |
||||
|
||||
const loading = ref(true) |
||||
const deleteTip = ref<boolean>(false) |
||||
const deleteSn = ref<string>() |
||||
const columns: ColumnProps[] = [ |
||||
{ title: 'Model', dataIndex: 'device_name', width: 100, className: 'titleStyle' }, |
||||
{ title: 'SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } }, |
||||
{ |
||||
title: 'Name', |
||||
dataIndex: 'nickname', |
||||
width: 100, |
||||
sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname), |
||||
className: 'titleStyle', |
||||
ellipsis: true, |
||||
slots: { customRender: 'nickname' } |
||||
}, |
||||
{ title: 'Firmware Version', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } }, |
||||
{ title: 'Status', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } }, |
||||
{ |
||||
title: 'Workspace', |
||||
dataIndex: 'workspace_name', |
||||
width: 100, |
||||
className: 'titleStyle', |
||||
ellipsis: true, |
||||
slots: { customRender: 'workspace' }, |
||||
customRender: ({ text, record, index }) => { |
||||
const obj = { |
||||
children: text, |
||||
props: {} as any, |
||||
} |
||||
if (current.value.indexOf(EDeviceTypeName.Dock) !== -1) { |
||||
if (record.domain === EDeviceTypeName.Aircraft) { |
||||
obj.children = '' |
||||
} |
||||
} |
||||
return obj |
||||
} |
||||
}, |
||||
{ title: 'Joined', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' }, |
||||
{ title: 'Last Online', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' }, |
||||
{ |
||||
title: 'Actions', |
||||
dataIndex: 'actions', |
||||
width: 100, |
||||
className: 'titleStyle', |
||||
slots: { customRender: 'action' } |
||||
}, |
||||
] |
||||
|
||||
const expandIcon = (props: any) => { |
||||
if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) { |
||||
return h('div', |
||||
{ |
||||
style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;', |
||||
class: 'mt-5 ml0', |
||||
}) |
||||
} |
||||
} |
||||
|
||||
const rowClassName = (record: any, index: number) => { |
||||
const className = [] |
||||
if ((index & 1) === 0) { |
||||
className.push('table-striped') |
||||
} |
||||
if (record.domain !== EDeviceTypeName.Dock) { |
||||
className.push('child-row') |
||||
} |
||||
return className.toString().replaceAll(',', ' ') |
||||
} |
||||
|
||||
const expandRows = ref<string[]>([]) |
||||
const data = reactive<DeviceData>({ |
||||
device: [] |
||||
}) |
||||
|
||||
const paginationProp = reactive({ |
||||
pageSizeOptions: ['20', '50', '100'], |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
pageSize: 50, |
||||
current: 1, |
||||
total: 0 |
||||
}) |
||||
|
||||
// 获取分页信息 |
||||
function getPaginationBody () { |
||||
return { |
||||
page: paginationProp.current, |
||||
page_size: paginationProp.pageSize |
||||
} as IPage |
||||
} |
||||
|
||||
const rowSelection = { |
||||
onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => { |
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
||||
}, |
||||
onSelect: (record: any, selected: boolean, selectedRows: []) => { |
||||
console.log(record, selected, selectedRows) |
||||
}, |
||||
onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => { |
||||
console.log(selected, selectedRows, changeRows) |
||||
}, |
||||
getCheckboxProps: (record: any) => ({ |
||||
disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock, |
||||
style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : '' |
||||
}), |
||||
} |
||||
type Pagination = TableState['pagination'] |
||||
|
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '' |
||||
const editableData: UnwrapRef<Record<string, Device>> = reactive({}) |
||||
const current = ref([EDeviceTypeName.Aircraft]) |
||||
|
||||
function judgeCurrentType (type: EDeviceTypeName): boolean { |
||||
return current.value.indexOf(type) !== -1 |
||||
} |
||||
|
||||
// 设备升级 |
||||
const { |
||||
deviceFirmwareUpgradeModalVisible, |
||||
selectedDevice, |
||||
onDeviceUpgrade, |
||||
onUpgradeDeviceOk |
||||
} = useDeviceFirmwareUpgrade(workspaceId) |
||||
|
||||
function onDeviceUpgradeWs (payload: DeviceCmdExecuteInfo) { |
||||
updateDevicesByWs(data.device, payload) |
||||
} |
||||
|
||||
function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) { |
||||
if (!devices || devices.length <= 0) { |
||||
return |
||||
} |
||||
for (let i = 0; i < devices.length; i++) { |
||||
if (devices[i].device_sn === payload.sn) { |
||||
if (!payload.output) return |
||||
const { status, progress } = payload.output |
||||
if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中 |
||||
devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade |
||||
devices[i].firmware_progress = progress?.percent || 0 |
||||
} else { // 终态:成功,失败,超时 |
||||
if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) { |
||||
message.error(`设备(${payload.sn}) 升级失败`) |
||||
} |
||||
// 拉取列表 |
||||
getDevices(current.value[0], true) |
||||
} |
||||
return |
||||
} |
||||
if (devices[i].children) { |
||||
updateDevicesByWs(devices[i].children || [], payload) |
||||
} |
||||
} |
||||
} |
||||
|
||||
useDeviceUpgradeEvent(onDeviceUpgradeWs) |
||||
|
||||
// 获取设备列表信息 |
||||
function getDevices (domain: string, closeLoading?: boolean) { |
||||
if (!closeLoading) { |
||||
loading.value = true |
||||
} |
||||
getBindingDevices(workspaceId, getPaginationBody(), domain).then(res => { |
||||
if (res.code !== 0) { |
||||
return |
||||
} |
||||
const resData: Device[] = res.data.list |
||||
expandRows.value = [] |
||||
resData.forEach((val: any) => { |
||||
if (val.children) { |
||||
val.children = [val.children] |
||||
} |
||||
if (judgeCurrentType(EDeviceTypeName.Dock)) { |
||||
expandRows.value.push(val.device_sn) |
||||
} |
||||
}) |
||||
data.device = resData |
||||
paginationProp.total = res.data.pagination.total |
||||
paginationProp.current = res.data.pagination.page |
||||
paginationProp.pageSize = res.data.pagination.page_size |
||||
loading.value = false |
||||
}) |
||||
} |
||||
|
||||
function refreshData (page: Pagination) { |
||||
paginationProp.current = page?.current! |
||||
paginationProp.pageSize = page?.pageSize! |
||||
getDevices(current.value[0]) |
||||
} |
||||
|
||||
// 编辑 |
||||
function edit (record: Device) { |
||||
editableData[record.device_sn] = record |
||||
} |
||||
|
||||
// 保存 |
||||
function save (record: Device) { |
||||
delete editableData[record.device_sn] |
||||
updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn) |
||||
} |
||||
|
||||
// 删除 |
||||
function showDeleteTip (sn: any) { |
||||
deleteTip.value = true |
||||
} |
||||
|
||||
// 解绑 |
||||
function unbind () { |
||||
deleteTip.value = false |
||||
unbindDevice(deleteSn.value?.toString()!).then(res => { |
||||
if (res.code !== 0) { |
||||
return |
||||
} |
||||
getDevices(current.value[0]) |
||||
}) |
||||
} |
||||
|
||||
// 选择设备 |
||||
function select (item: any) { |
||||
getDevices(item.key) |
||||
} |
||||
|
||||
const currentDevice = ref({} as Device) |
||||
// 设备日志 |
||||
const deviceLogUploadRecordVisible = ref(false) |
||||
function showDeviceLogUploadRecord (dock: Device) { |
||||
deviceLogUploadRecordVisible.value = true |
||||
currentDevice.value = dock |
||||
} |
||||
|
||||
// 健康状态 |
||||
const hmsVisible = ref<boolean>(false) |
||||
|
||||
function showHms (dock: Device) { |
||||
hmsVisible.value = true |
||||
currentDevice.value = dock |
||||
} |
||||
|
||||
onMounted(() => { |
||||
getDevices(current.value[0]) |
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.device-table-wrap{ |
||||
.editable-row-operations{ |
||||
div > span { |
||||
margin-right: 10px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<style lang="scss"> |
||||
.table { |
||||
background-color: white; |
||||
margin: 20px; |
||||
padding: 20px; |
||||
height: 88vh; |
||||
} |
||||
.table-striped { |
||||
background-color: #f7f9fa; |
||||
} |
||||
.ant-table { |
||||
border-top: 1px solid rgb(0,0,0,0.06); |
||||
border-bottom: 1px solid rgb(0,0,0,0.06); |
||||
} |
||||
.ant-table-tbody tr td { |
||||
border: 0; |
||||
} |
||||
.ant-table td { |
||||
white-space: nowrap; |
||||
} |
||||
.ant-table-thead tr th { |
||||
background: white !important; |
||||
border: 0; |
||||
} |
||||
th.ant-table-selection-column { |
||||
background-color: white !important; |
||||
} |
||||
.ant-table-header { |
||||
background-color: white !important; |
||||
} |
||||
.child-row { |
||||
height: 70px; |
||||
} |
||||
.notice { |
||||
background: $success; |
||||
overflow: hidden; |
||||
cursor: pointer; |
||||
} |
||||
.caution { |
||||
background: orange; |
||||
cursor: pointer; |
||||
overflow: hidden; |
||||
} |
||||
.warn { |
||||
background: red; |
||||
cursor: pointer; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
@ -1,518 +0,0 @@
@@ -1,518 +0,0 @@
|
||||
|
||||
<template> |
||||
<a-menu v-model:selectedKeys="current" mode="horizontal" @select="select"> |
||||
<a-menu-item :key="EDeviceTypeName.Aircraft" class="ml20"> |
||||
Aircraft |
||||
</a-menu-item> |
||||
<a-menu-item :key="EDeviceTypeName.Dock"> |
||||
Dock |
||||
</a-menu-item> |
||||
</a-menu> |
||||
<div class="table flex-display flex-column"> |
||||
<a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData" row-key="device_sn" :expandedRowKeys="expandRows" |
||||
:row-selection="rowSelection" :rowClassName="rowClassName" :scroll="{ x: '100%', y: 600 }" |
||||
:expandIcon="expandIcon" :loading="loading"> |
||||
<template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col"> |
||||
<div> |
||||
<a-input |
||||
v-if="editableData[record.device_sn]" |
||||
v-model:value="editableData[record.device_sn][col]" |
||||
style="margin: -5px 0" |
||||
/> |
||||
<template v-else> |
||||
{{ text }} |
||||
</template> |
||||
</div> |
||||
</template> |
||||
<template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col"> |
||||
<a-tooltip :title="text"> |
||||
<span>{{ text }}</span> |
||||
</a-tooltip> |
||||
</template> |
||||
<template #status="{ text }"> |
||||
<span v-if="text" class="flex-row flex-align-center"> |
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" /> |
||||
<span>Online</span> |
||||
</span> |
||||
<span class="flex-row flex-align-center" v-else> |
||||
<span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" /> |
||||
<span>Offline</span> |
||||
</span> |
||||
</template> |
||||
<template #action="{ record }"> |
||||
<div class="editable-row-operations"> |
||||
<span v-if="editableData[record.device_sn]"> |
||||
<a-tooltip title="Confirm changes"> |
||||
<span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span> |
||||
</a-tooltip> |
||||
<a-tooltip title="Modification canceled"> |
||||
<span @click="() => delete editableData[record.device_sn]" class="ml15" style="color: #e70102;"><CloseOutlined /></span> |
||||
</a-tooltip> |
||||
</span> |
||||
<span v-else class="flex-align-center flex-row" style="color: #2d8cf0"> |
||||
<a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="Hms Info"> |
||||
<FileSearchOutlined @click="showHms(record)"/> |
||||
</a-tooltip> |
||||
<a-tooltip title="Edit"> |
||||
<EditOutlined @click="edit(record)" class="ml10" /> |
||||
</a-tooltip> |
||||
<a-tooltip title="Delete"> |
||||
<DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }" class="ml15" /> |
||||
</a-tooltip> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
|
||||
</a-table> |
||||
<a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }" @ok="unbind"> |
||||
<p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p> |
||||
<template #title> |
||||
<div class="flex-row flex-justify-center"> |
||||
<span>Delete devices</span> |
||||
</div> |
||||
</template> |
||||
</a-modal> |
||||
|
||||
<a-drawer |
||||
title="Hms Info" |
||||
placement="right" |
||||
v-model:visible="hmsVisible" |
||||
:width="800"> |
||||
<div class="flex-row flex-align-center"> |
||||
<div style="width: 240px;"> |
||||
<a-range-picker |
||||
v-model:value="time" |
||||
format="YYYY-MM-DD" |
||||
:placeholder="['Start Time', 'End Time']" |
||||
@change="onTimeChange"/> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-select |
||||
style="width: 150px" |
||||
v-model:value="param.level" |
||||
@select="onLevelSelect"> |
||||
<a-select-option |
||||
v-for="item in levels" |
||||
:key="item.label" |
||||
:value="item.value" |
||||
> |
||||
{{ item.label }} |
||||
</a-select-option> |
||||
</a-select> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-select |
||||
v-model:value="param.domain" |
||||
:disabled="!param.children_sn || !param.device_sn" |
||||
style="width: 150px" |
||||
@select="onDeviceTypeSelect"> |
||||
<a-select-option |
||||
v-for="item in deviceTypes" |
||||
:key="item.label" |
||||
:value="item.value" |
||||
> |
||||
{{ item.label }} |
||||
</a-select-option> |
||||
</a-select> |
||||
</div> |
||||
<div class="ml5"> |
||||
<a-input-search |
||||
v-model:value="param.message" |
||||
placeholder="input search message" |
||||
style="width: 200px" |
||||
@search="getHms"/> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<a-table :columns="hmsColumns" :scroll="{ x: '100%', y: 600 }" :data-source="hmsData.data" :pagination="hmsPaginationProp" @change="refreshHmsData" row-key="hms_id" |
||||
:rowClassName="rowClassName" :loading="loading"> |
||||
<template #time="{ record }"> |
||||
<div>{{ record.create_time }}</div> |
||||
<div :style="record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' : |
||||
record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'">{{ record.update_time ?? 'It is happening...' }}</div> |
||||
</template> |
||||
<template #level="{ text }"> |
||||
<div class="flex-row flex-align-center"> |
||||
<div :class="text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'" style="width: 10px; height: 10px; border-radius: 50%;"></div> |
||||
<div style="margin-left: 3px;">{{ EHmsLevel[text] }}</div> |
||||
</div> |
||||
</template> |
||||
<template v-for="col in ['code', 'message']" #[col]="{ text }" :key="col"> |
||||
<a-tooltip :title="text"> |
||||
<span>{{ text }}</span> |
||||
</a-tooltip> |
||||
</template> |
||||
</a-table> |
||||
</div> |
||||
</a-drawer> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { message } from 'ant-design-vue' |
||||
import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface' |
||||
import { h, onMounted, reactive, ref, UnwrapRef } from 'vue' |
||||
import { IPage } from '/@/api/http/type' |
||||
import { BindBody, bindDevice, getBindingDevices, getDeviceHms, HmsQueryBody, unbindDevice, updateDevice } from '/@/api/manage' |
||||
import { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types' |
||||
import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined } from '@ant-design/icons-vue' |
||||
import { Device, DeviceHms } from '/@/types/device' |
||||
import moment, { Moment } from 'moment' |
||||
|
||||
interface DeviceData { |
||||
device: Device[] |
||||
} |
||||
const loading = ref(true) |
||||
const deleteTip = ref<boolean>(false) |
||||
const deleteSn = ref<string>() |
||||
const hmsVisible = ref<boolean>(false) |
||||
const columns: ColumnProps[] = [ |
||||
{ title: 'Model', dataIndex: 'device_name', width: '10%', className: 'titleStyle' }, |
||||
{ title: 'SN', dataIndex: 'device_sn', width: '10%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } }, |
||||
{ |
||||
title: 'Name', |
||||
dataIndex: 'nickname', |
||||
width: '15%', |
||||
sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname), |
||||
className: 'titleStyle', |
||||
ellipsis: true, |
||||
slots: { customRender: 'nickname' } |
||||
}, |
||||
{ title: 'Firmware Version', dataIndex: 'firmware_version', width: '10%', className: 'titleStyle' }, |
||||
{ title: 'Status', dataIndex: 'status', width: '100px', className: 'titleStyle', slots: { customRender: 'status' } }, |
||||
{ |
||||
title: 'Workspace', |
||||
dataIndex: 'workspace_name', |
||||
width: '10%', |
||||
className: 'titleStyle', |
||||
ellipsis: true, |
||||
slots: { customRender: 'workspace' }, |
||||
customRender: ({ text, record, index }) => { |
||||
const obj = { |
||||
children: text, |
||||
props: {} as any, |
||||
} |
||||
if (current.value.indexOf(EDeviceTypeName.Aircraft) !== -1 || (!record.child_device_sn && record.domain === EDeviceTypeName.Dock)) { |
||||
return obj |
||||
} |
||||
|
||||
obj.props.rowSpan = record.domain === EDeviceTypeName.Dock ? 2 : 0 |
||||
return obj |
||||
} |
||||
}, |
||||
{ title: 'Joined', dataIndex: 'bound_time', width: '15%', sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' }, |
||||
{ title: 'Last Online', dataIndex: 'login_time', width: '15%', sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' }, |
||||
{ |
||||
title: 'Actions', |
||||
dataIndex: 'actions', |
||||
className: 'titleStyle', |
||||
slots: { customRender: 'action' } |
||||
}, |
||||
] |
||||
const expandIcon = (props: any) => { |
||||
if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) { |
||||
return h('div', |
||||
{ |
||||
style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;', |
||||
class: 'mt-5 ml0', |
||||
}) |
||||
} |
||||
} |
||||
const rowClassName = (record: any, index: number) => { |
||||
const className = [] |
||||
if ((index & 1) === 0) { |
||||
className.push('table-striped') |
||||
} |
||||
if (record.domain !== EDeviceTypeName.Dock) { |
||||
className.push('child-row') |
||||
} |
||||
return className.toString().replaceAll(',', ' ') |
||||
} |
||||
|
||||
const expandRows = ref<string[]>([]) |
||||
const data = reactive<DeviceData>({ |
||||
device: [] |
||||
}) |
||||
|
||||
const paginationProp = reactive({ |
||||
pageSizeOptions: ['20', '50', '100'], |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
pageSize: 50, |
||||
current: 1, |
||||
total: 0 |
||||
}) |
||||
|
||||
const rowSelection = { |
||||
onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => { |
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows) |
||||
}, |
||||
onSelect: (record: any, selected: boolean, selectedRows: []) => { |
||||
console.log(record, selected, selectedRows) |
||||
}, |
||||
onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => { |
||||
console.log(selected, selectedRows, changeRows) |
||||
}, |
||||
getCheckboxProps: (record: any) => ({ |
||||
disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock, |
||||
style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : '' |
||||
}), |
||||
} |
||||
type Pagination = TableState['pagination'] |
||||
|
||||
const body: IPage = { |
||||
page: 1, |
||||
total: 0, |
||||
page_size: 50 |
||||
} |
||||
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)! |
||||
|
||||
const editableData: UnwrapRef<Record<string, Device>> = reactive({}) |
||||
|
||||
const current = ref([EDeviceTypeName.Aircraft]) |
||||
|
||||
onMounted(() => { |
||||
getDevices(workspaceId, body, current.value[0]) |
||||
}) |
||||
|
||||
function judgeCurrentType (type: EDeviceTypeName): boolean { |
||||
return current.value.indexOf(type) !== -1 |
||||
} |
||||
|
||||
function getDevices (workspaceId: string, body: IPage, domain: string) { |
||||
loading.value = true |
||||
getBindingDevices(workspaceId, body, domain).then(res => { |
||||
if (res.code !== 0) { |
||||
return |
||||
} |
||||
const resData: Device[] = res.data.list |
||||
expandRows.value = [] |
||||
resData.forEach((val: any) => { |
||||
if (val.children) { |
||||
val.children = [val.children] |
||||
} |
||||
if (judgeCurrentType(EDeviceTypeName.Dock)) { |
||||
expandRows.value.push(val.device_sn) |
||||
} |
||||
}) |
||||
data.device = resData |
||||
paginationProp.total = res.data.pagination.total |
||||
paginationProp.current = res.data.pagination.page |
||||
loading.value = false |
||||
}) |
||||
} |
||||
|
||||
function refreshData (page: Pagination) { |
||||
body.page = page?.current! |
||||
body.page_size = page?.pageSize! |
||||
getDevices(workspaceId, body, current.value[0]) |
||||
} |
||||
|
||||
function edit (record: Device) { |
||||
editableData[record.device_sn] = record |
||||
} |
||||
|
||||
function save (record: Device) { |
||||
delete editableData[record.device_sn] |
||||
updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn) |
||||
} |
||||
|
||||
function showDeleteTip (sn: any) { |
||||
deleteTip.value = true |
||||
} |
||||
|
||||
function unbind () { |
||||
deleteTip.value = false |
||||
unbindDevice(deleteSn.value?.toString()!).then(res => { |
||||
if (res.code !== 0) { |
||||
return |
||||
} |
||||
getDevices(workspaceId, body, current.value[0]) |
||||
}) |
||||
} |
||||
|
||||
function select (item: any) { |
||||
getDevices(workspaceId, body, item.key) |
||||
} |
||||
|
||||
const hmsColumns: ColumnProps[] = [ |
||||
{ title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } }, |
||||
{ title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } }, |
||||
{ title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle' }, |
||||
{ title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', slots: { customRender: 'code' } }, |
||||
{ title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } }, |
||||
] |
||||
|
||||
interface DeviceHmsData { |
||||
data: DeviceHms[] |
||||
} |
||||
const hmsData = reactive<DeviceHmsData>({ |
||||
data: [] |
||||
}) |
||||
|
||||
const hmsPaginationProp = reactive({ |
||||
pageSizeOptions: ['20', '50', '100'], |
||||
showQuickJumper: true, |
||||
showSizeChanger: true, |
||||
pageSize: 50, |
||||
current: 1, |
||||
total: 0 |
||||
}) |
||||
|
||||
const hmsPage: IPage = { |
||||
page: 1, |
||||
total: 0, |
||||
page_size: 50 |
||||
} |
||||
|
||||
function showHms (dock: Device) { |
||||
hmsVisible.value = true |
||||
if (dock.domain === EDeviceTypeName.Dock) { |
||||
param.domain = '' |
||||
getDeviceHmsBySn(dock.device_sn, dock.children?.[0].device_sn ?? '') |
||||
} |
||||
if (dock.domain === EDeviceTypeName.Aircraft) { |
||||
param.domain = EDeviceTypeName.Aircraft |
||||
getDeviceHmsBySn('', dock.device_sn) |
||||
} |
||||
} |
||||
|
||||
function refreshHmsData (page: Pagination) { |
||||
hmsPage.page = page?.current! |
||||
hmsPage.page_size = page?.pageSize! |
||||
getHms() |
||||
} |
||||
|
||||
const param = reactive<HmsQueryBody>({ |
||||
sns: [], |
||||
device_sn: '', |
||||
children_sn: '', |
||||
language: 'en', |
||||
begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0), |
||||
end_time: new Date().setHours(23, 59, 59, 999), |
||||
domain: '', |
||||
level: '', |
||||
message: '' |
||||
}) |
||||
|
||||
const levels = [ |
||||
{ |
||||
label: 'All', |
||||
value: '' |
||||
}, { |
||||
label: EHmsLevel[0], |
||||
value: EHmsLevel.NOTICE |
||||
}, { |
||||
label: EHmsLevel[1], |
||||
value: EHmsLevel.CAUTION |
||||
}, { |
||||
label: EHmsLevel[2], |
||||
value: EHmsLevel.WARN |
||||
} |
||||
] |
||||
|
||||
const deviceTypes = [ |
||||
{ |
||||
label: 'All', |
||||
value: '' |
||||
}, { |
||||
label: EDeviceTypeName.Aircraft, |
||||
value: EDeviceTypeName.Aircraft |
||||
}, { |
||||
label: EDeviceTypeName.Dock, |
||||
value: EDeviceTypeName.Dock |
||||
} |
||||
] |
||||
|
||||
const time = ref([moment(param.begin_time), moment(param.end_time)]) |
||||
|
||||
function getHms () { |
||||
getDeviceHms(param, workspaceId, hmsPage) |
||||
.then(res => { |
||||
hmsPaginationProp.total = res.data.pagination.total |
||||
hmsPaginationProp.current = res.data.pagination.page |
||||
hmsData.data = res.data.list |
||||
hmsData.data.forEach(hms => { |
||||
hms.domain = hms.sn === param.children_sn ? EDeviceTypeName.Aircraft : EDeviceTypeName.Dock |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
function getDeviceHmsBySn (sn: string, childSn: string) { |
||||
param.device_sn = sn |
||||
param.children_sn = childSn |
||||
param.sns = [param.device_sn, param.children_sn] |
||||
getHms() |
||||
} |
||||
|
||||
function onTimeChange (newTime: [Moment, Moment]) { |
||||
param.begin_time = newTime[0].valueOf() |
||||
param.end_time = newTime[1].valueOf() |
||||
getHms() |
||||
} |
||||
|
||||
function onDeviceTypeSelect (val: string) { |
||||
param.sns = [param.device_sn, param.children_sn] |
||||
if (val === EDeviceTypeName.Dock) { |
||||
param.sns = [param.device_sn, ''] |
||||
} |
||||
if (val === EDeviceTypeName.Aircraft) { |
||||
param.sns = ['', param.children_sn] |
||||
} |
||||
getHms() |
||||
} |
||||
|
||||
function onLevelSelect (val: number) { |
||||
param.level = val |
||||
getHms() |
||||
} |
||||
</script> |
||||
<style lang="scss"> |
||||
|
||||
.table { |
||||
background-color: white; |
||||
margin: 20px; |
||||
padding: 20px; |
||||
height: 88vh; |
||||
} |
||||
.table-striped { |
||||
background-color: #f7f9fa; |
||||
} |
||||
.ant-table { |
||||
border-top: 1px solid rgb(0,0,0,0.06); |
||||
border-bottom: 1px solid rgb(0,0,0,0.06); |
||||
} |
||||
.ant-table-tbody tr td { |
||||
border: 0; |
||||
} |
||||
.ant-table td { |
||||
white-space: nowrap; |
||||
} |
||||
.ant-table-thead tr th { |
||||
background: white !important; |
||||
border: 0; |
||||
} |
||||
th.ant-table-selection-column { |
||||
background-color: white !important; |
||||
} |
||||
.ant-table-header { |
||||
background-color: white !important; |
||||
} |
||||
.child-row { |
||||
height: 70px; |
||||
} |
||||
.notice { |
||||
background: $success; |
||||
overflow: hidden; |
||||
cursor: pointer; |
||||
} |
||||
.caution { |
||||
background: orange; |
||||
cursor: pointer; |
||||
overflow: hidden; |
||||
} |
||||
.warn { |
||||
background: red; |
||||
cursor: pointer; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
// 机场存储容量:总容量(单位:KB)、已使用(单位:KB)
|
||||
export interface AirportStorage { |
||||
total: number, // 单位:KB
|
||||
used: number |
||||
} |
||||
|
||||
// 舱盖状态
|
||||
export enum CoverStateEnum { |
||||
Close = 0, // 关闭
|
||||
Open = 1, // 打开
|
||||
HalfOpen = 2, // 半打开
|
||||
Failed = 3 // 失败
|
||||
} |
||||
|
||||
// 推杆状态
|
||||
export enum PutterStateEnum { |
||||
Close = 0, // 关闭
|
||||
Open = 1, // 打开
|
||||
HalfOpen = 2, // 半打开
|
||||
Failed = 3 // 失败
|
||||
} |
||||
|
||||
// 充电状态
|
||||
export enum ChargeStateEnum { |
||||
NotCharge = 0, // 空闲
|
||||
Charge = 1, // 正在充电
|
||||
} |
||||
|
||||
export interface DroneChargeState { |
||||
state: ChargeStateEnum, |
||||
capacity_percent: string, |
||||
} |
||||
|
||||
// 补光灯状态
|
||||
export enum SupplementLightStateEnum { |
||||
Close = 0, // 关闭
|
||||
Open = 1, // 打开
|
||||
} |
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
// 机场指令集
|
||||
export enum DeviceCmd { |
||||
// 简单指令
|
||||
DebugModeOpen = 'debug_mode_open', // 调试模式开启
|
||||
DebugModeClose = 'debug_mode_close', // 调试模式关闭
|
||||
SupplementLightOpen = 'supplement_light_open', // 打开补光灯
|
||||
SupplementLightClose = 'supplement_light_close', // 关闭补光灯
|
||||
ReturnHome = 'return_home', // 一键返航
|
||||
// 复杂指令
|
||||
DeviceReboot = 'device_reboot', // 机场重启
|
||||
DroneOpen = 'drone_open', // 飞行器开机
|
||||
DroneClose = 'drone_close', // 飞行器关机
|
||||
// DeviceCheck = 'device_check', // 一键排障(一键起飞自检)
|
||||
DeviceFormat = 'device_format', // 机场数据格式化
|
||||
DroneFormat = 'drone_format', // 飞行器数据格式化
|
||||
CoverOpen = 'cover_open', // 打开舱盖
|
||||
CoverClose = 'cover_close', // 关闭舱盖
|
||||
PutterOpen = 'putter_open', // 推杆展开
|
||||
PutterClose = 'putter_close', // 推杆闭合
|
||||
ChargeOpen = 'charge_open', // 打开充电
|
||||
ChargeClose = 'charge_close', // 关闭充电
|
||||
} |
||||
|
||||
export interface DeviceCmdItem{ |
||||
label: string, // 标题
|
||||
status: string, // 当前状态
|
||||
operateText: string, // 按钮文字
|
||||
cmdKey: DeviceCmd, // 请求指令
|
||||
oppositeCmdKey?: DeviceCmd, // 相反状态指令
|
||||
func: string, // 处理函数
|
||||
loading: boolean // 按钮loading
|
||||
} |
||||
|
||||
// 机场指令
|
||||
export const cmdList: DeviceCmdItem[] = [ |
||||
{ |
||||
// iconName: ,
|
||||
label: '机场系统', |
||||
status: '工作中', |
||||
operateText: '重启', |
||||
cmdKey: DeviceCmd.DeviceReboot, |
||||
func: 'deviceReboot', |
||||
loading: false, |
||||
// btnAnimationIconName: '',
|
||||
// operateTips: '',
|
||||
// statusColor: '',
|
||||
}, |
||||
{ |
||||
label: '飞行器', |
||||
status: '关机', |
||||
operateText: '开机', |
||||
cmdKey: DeviceCmd.DroneOpen, |
||||
oppositeCmdKey: DeviceCmd.DroneClose, |
||||
func: 'droneStatus', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '舱盖', |
||||
status: '关', |
||||
operateText: '开启', |
||||
cmdKey: DeviceCmd.CoverOpen, |
||||
oppositeCmdKey: DeviceCmd.CoverClose, |
||||
func: 'coverStatus', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '推杆', |
||||
status: '闭合', |
||||
operateText: '展开', |
||||
cmdKey: DeviceCmd.PutterOpen, |
||||
oppositeCmdKey: DeviceCmd.PutterClose, |
||||
func: 'putterStatus', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '充电状态', |
||||
status: '未充电', |
||||
operateText: '充电', |
||||
cmdKey: DeviceCmd.ChargeOpen, |
||||
oppositeCmdKey: DeviceCmd.ChargeClose, |
||||
func: 'chargeStatus', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '一键返航', |
||||
status: '--', |
||||
operateText: '返航', |
||||
cmdKey: DeviceCmd.ReturnHome, |
||||
func: 'returnHome', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '机场存储', |
||||
status: '--', |
||||
operateText: '格式化', |
||||
cmdKey: DeviceCmd.DeviceFormat, |
||||
func: 'deviceFormat', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '飞行器存储', |
||||
status: '--', |
||||
operateText: '格式化', |
||||
cmdKey: DeviceCmd.DroneFormat, |
||||
func: 'droneFormat', |
||||
loading: false, |
||||
}, |
||||
{ |
||||
label: '补光灯', |
||||
status: '关', |
||||
operateText: '打开', |
||||
cmdKey: DeviceCmd.SupplementLightOpen, |
||||
oppositeCmdKey: DeviceCmd.SupplementLightClose, |
||||
func: 'supplementLightStatus', |
||||
loading: false, |
||||
}, |
||||
] |
||||
|
||||
export enum DeviceCmdStatusText { |
||||
DeviceRebootNormalText = '工作中', |
||||
DeviceRebootInProgressText = '重启中...', |
||||
DeviceRebootFailedText = '重启失败', |
||||
|
||||
DroneStatusOpenNormalText = '开', |
||||
DroneStatusOpenInProgressText = '开机中...', |
||||
DroneStatusOpenFailedText = '关', |
||||
DroneStatusOpenBtnText = '关机', |
||||
|
||||
DroneStatusCloseNormalText = '关', |
||||
DroneStatusCloseInProgressText = '关机中...', |
||||
DroneStatusCloseFailedText = '开', |
||||
DroneStatusCloseBtnText = '开机', |
||||
|
||||
DeviceCoverOpenNormalText = '开', |
||||
DeviceCoverOpenInProgressText = '开启中...', |
||||
DeviceCoverOpenFailedText = '关', |
||||
DeviceCoverOpenBtnText = '关闭', |
||||
|
||||
DeviceCoverCloseNormalText = '关', |
||||
DeviceCoverCloseInProgressText = '关闭中...', |
||||
DeviceCoverCloseFailedText = '开', |
||||
DeviceCoverCloseBtnText = '开启', |
||||
|
||||
DevicePutterOpenNormalText = '展开', |
||||
DevicePutterOpenBtnText = '闭合', |
||||
DevicePutterOpenInProgressText = '推杆展开中', |
||||
DevicePutterOpenFailedText = '闭合', |
||||
|
||||
DevicePutterCloseNormalText = '闭合', |
||||
DevicePutterCloseInProgressText = '推杆闭合中', |
||||
DevicePutterCloseFailedText = '展开', |
||||
DevicePutterCloseBtnText = '展开', |
||||
|
||||
DeviceChargeOpenNormalText = '充电', |
||||
DeviceChargeOpenInProgressText = '充电中...', |
||||
DeviceChargeOpenFailedText = '未充电', |
||||
DeviceChargeOpenBtnText = '断电', |
||||
|
||||
DeviceChargeCloseNormalText = '断电', |
||||
DeviceChargeCloseInProgressText = '断电中...', |
||||
DeviceChargeCloseFailedText = '充电', |
||||
DeviceChargeCloseBtnText = '充电', |
||||
|
||||
DeviceFormatInProgressText = '格式化...', |
||||
DeviceFormatFailedText = '格式化失败', |
||||
|
||||
DroneFormatInProgressText = '格式化...', |
||||
DroneFormatFailedText = '格式化失败', |
||||
|
||||
DeviceSupplementLightOpenNormalText = '开', |
||||
DeviceSupplementLightOpenInProgressText = '开启中...', |
||||
DeviceSupplementLightOpenFailedText = '关', |
||||
DeviceSupplementLightOpenBtnText = '关闭', |
||||
|
||||
DeviceSupplementLightCloseNormalText = '关', |
||||
DeviceSupplementLightCloseText = '关闭中...', |
||||
DeviceSupplementLightCloseFailedText = '开', |
||||
DeviceSupplementLightCloseBtnText = '打开', |
||||
} |
||||
|
||||
// cmd ws 消息状态
|
||||
export enum DeviceCmdExecuteStatus { |
||||
Sent = 'sent', // 已下发
|
||||
InProgress = 'in_progress', // 执行中
|
||||
OK = 'ok', // 执行成功
|
||||
Failed = 'failed', // 失败
|
||||
Canceled = 'canceled', // 取消
|
||||
Timeout = 'timeout' // 超时
|
||||
} |
||||
|
||||
export interface DeviceCmdExecuteInfo { |
||||
biz_code: string, |
||||
timestamp: number, |
||||
sn: string, |
||||
bid: string, |
||||
output:{ |
||||
status: DeviceCmdExecuteStatus, |
||||
progress?: { |
||||
percent: number, |
||||
step_key: string, |
||||
step_result: number |
||||
} |
||||
} |
||||
result: number, |
||||
} |
||||
|
||||
// 所有机场的指令执行状态
|
||||
export interface DevicesCmdExecuteInfo { |
||||
[key: string]: DeviceCmdExecuteInfo[], // sn --- DeviceCmdExecuteInfo
|
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
import { DOMAIN } from '/@/types/device' |
||||
import { commonColor } from '/@/utils/color' |
||||
|
||||
// 日志上传状态
|
||||
export enum DeviceLogUploadStatusEnum { |
||||
Uploading = 1, // 上传中
|
||||
Done = 2, // 完成
|
||||
Canceled = 3, // 取消
|
||||
Failed = 4, // 失败
|
||||
} |
||||
|
||||
export const DeviceLogUploadStatusMap = { |
||||
[DeviceLogUploadStatusEnum.Uploading]: '上传中', |
||||
[DeviceLogUploadStatusEnum.Done]: '上传成功', |
||||
[DeviceLogUploadStatusEnum.Canceled]: '取消上传', |
||||
[DeviceLogUploadStatusEnum.Failed]: '上传失败', |
||||
} |
||||
|
||||
export const DeviceLogUploadStatusColor = { |
||||
[DeviceLogUploadStatusEnum.Uploading]: commonColor.BLUE, |
||||
[DeviceLogUploadStatusEnum.Done]: commonColor.NORMAL, |
||||
[DeviceLogUploadStatusEnum.Canceled]: commonColor.WARN, |
||||
[DeviceLogUploadStatusEnum.Failed]: commonColor.FAIL, |
||||
} |
||||
|
||||
// 设备日志上传 ws 消息状态
|
||||
export enum DeviceLogUploadStatus { |
||||
FilePull = 'file_pull', // 拉取日志 可以作为 正在处理中
|
||||
FileZip = 'file_zip', // 拉取日志,日志压缩可以作为 正在处理中
|
||||
FileUploading = 'file_uploading', // 正在上传
|
||||
Canceled = 'canceled', // 取消
|
||||
Timeout = 'timeout', // 超时
|
||||
Failed = 'failed', // 失败
|
||||
OK = 'ok', // 上传成功
|
||||
// Paused = 'paused' // 暂停
|
||||
} |
||||
|
||||
export interface DeviceLogUploadInfo { |
||||
sn: string, |
||||
bid: string, |
||||
output:{ |
||||
logs_id: string |
||||
status: DeviceLogUploadStatus, |
||||
files: { |
||||
device_sn: string, |
||||
device_model_domain: DOMAIN, |
||||
progress: number, |
||||
result: number, |
||||
upload_rate: number, |
||||
status: DeviceLogUploadStatus |
||||
}[] |
||||
} |
||||
result: number, |
||||
} |
||||
|
||||
// ws status => log status
|
||||
export const DeviceLogUploadWsStatusMap = { |
||||
[DeviceLogUploadStatus.FilePull]: DeviceLogUploadStatusEnum.Uploading, |
||||
[DeviceLogUploadStatus.FileZip]: DeviceLogUploadStatusEnum.Uploading, |
||||
[DeviceLogUploadStatus.FileUploading]: DeviceLogUploadStatusEnum.Uploading, |
||||
[DeviceLogUploadStatus.OK]: DeviceLogUploadStatusEnum.Done, |
||||
[DeviceLogUploadStatus.Failed]: DeviceLogUploadStatusEnum.Failed, |
||||
[DeviceLogUploadStatus.Canceled]: DeviceLogUploadStatusEnum.Canceled, |
||||
[DeviceLogUploadStatus.Timeout]: DeviceLogUploadStatusEnum.Failed, |
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants' |
||||
|
||||
/** |
||||
* 转换字节数为单位B,KB,GB... |
||||
* 保留一位小数 |
||||
* @param bytes 字节数 |
||||
* @param holder 0字节占位符,默认 -- |
||||
* @returns |
||||
*/ |
||||
export function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string { |
||||
if (isNaN(bytes) || bytes === 0) { |
||||
return holder |
||||
} |
||||
// 兼容负数
|
||||
let prefix = '' |
||||
if (bytes < 0) { |
||||
bytes = 0 - bytes |
||||
prefix = '-' |
||||
} |
||||
const k = 1024 |
||||
const sizes = unit ? BYTE_SIZES : byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)) |
||||
return prefix + (bytes / Math.pow(k, i)).toFixed(fix) + '' + sizes[i] |
||||
} |
||||
|
||||
// 获取转化后数据及单位
|
||||
export function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): { |
||||
value: string, |
||||
size: string |
||||
index: number |
||||
} { |
||||
if (isNaN(bytes) || bytes === 0) { |
||||
return { |
||||
value: holder, |
||||
size: '', |
||||
index: -1, |
||||
} |
||||
} |
||||
// 兼容负数
|
||||
let prefix = '' |
||||
if (bytes < 0) { |
||||
bytes = 0 - bytes |
||||
prefix = '-' |
||||
} |
||||
const k = 1024 |
||||
const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)) |
||||
|
||||
return { |
||||
value: prefix + (bytes / Math.pow(k, i)).toFixed(fix), |
||||
size: sizes[i], |
||||
index: i, |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 根据最小单位返回文件大小 |
||||
* @param bytes |
||||
* @param minUnit |
||||
* @param fix |
||||
* @returns |
||||
*/ |
||||
export function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1): string { |
||||
const holder = `0${minUnit}` |
||||
const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
||||
const k = 1024 |
||||
const findIndex = sizes.findIndex(item => item === minUnit) |
||||
|
||||
const { value, size, index } = getBytesObject(bytes, holder, fix) |
||||
// 0
|
||||
if (index === -1) { |
||||
return holder |
||||
} |
||||
// 转换后单位小于传入的最小单位
|
||||
if (index < findIndex) { |
||||
const sizeToMinUint = parseFloat(value) / (Math.pow(k, findIndex - index)) |
||||
return sizeToMinUint.toFixed(fix) + minUnit |
||||
} |
||||
// 其他
|
||||
return value + size |
||||
} |
||||
// console.log('size', bytesToSizeWithMinUnit(0))
|
||||
// console.log('size', bytesToSizeWithMinUnit(1023))
|
||||
// console.log('size', bytesToSizeWithMinUnit(1024))
|
||||
// console.log('size', bytesToSizeWithMinUnit(1000 * 1024, 'MB', 2))
|
||||
// console.log('size', bytesToSizeWithMinUnit(1024 * 1024, 'MB', 2))
|
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
export const commonColor = { |
||||
WARN: '#FF9900', // 黄色
|
||||
FAIL: '#E02020', // 红色
|
||||
WHITE: '#FFFFFF', // 白色
|
||||
NORMAL: '#19BE6B', // 绿色
|
||||
BLUE: '#2B85E4', // 蓝色
|
||||
PINK: '#F7C0BA', // 粉
|
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
|
||||
export const DEFAULT_PLACEHOLDER = '--' // 默认占位符
|
||||
|
||||
// 全局日期格式
|
||||
export const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss' |
||||
export const DATE_FORMAT_MINUTE = 'YYYY-MM-DD HH:mm' |
||||
export const DATE_FORMAT_DAY = 'YYYY-MM-DD' |
||||
export const TIME_FORMAT = 'HH:mm:ss' |
||||
export const TIME_FORMAT_MINUTE = 'HH:mm' |
||||
export const DATE_FORMAT_MM = 'MM-DD HH:mm' |
||||
|
||||
export const SIZES = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] |
||||
export const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] |
||||
export const PAGE_SIZE_OPTIONS = ['20', '50', '100'] |
||||
export const PAGE_SIZE = 50 |
@ -0,0 +1,350 @@
@@ -0,0 +1,350 @@
|
||||
import { DeviceInfoType } from '/@/types/device' |
||||
import { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd' |
||||
import { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum } from '/@/types/airport-tsa' |
||||
import { getBytesObject } from './bytes' |
||||
import { DEFAULT_PLACEHOLDER } from './constants' |
||||
|
||||
/** |
||||
* 根据osd 更新信息 |
||||
* @param cmdList |
||||
* @param deviceInfo |
||||
* @returns |
||||
*/ |
||||
export function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: DeviceInfoType) { |
||||
const { device, dock, gateway } = deviceInfo || {} |
||||
if (!cmdList || cmdList.length < 1) { |
||||
return |
||||
} |
||||
cmdList.forEach(cmdItem => { |
||||
if (cmdItem.loading) { |
||||
return |
||||
} |
||||
if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启
|
||||
// console.log('DeviceReboot')
|
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneOpen || cmdItem.cmdKey === DeviceCmd.DroneClose) { // 飞行器开关机
|
||||
getDroneState(cmdItem, device) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.CoverOpen || cmdItem.cmdKey === DeviceCmd.CoverClose) { // 舱盖开关
|
||||
getCoverState(cmdItem, dock) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.PutterOpen || cmdItem.cmdKey === DeviceCmd.PutterClose) { // 推杆闭合展开
|
||||
getPutterState(cmdItem, dock) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen || cmdItem.cmdKey === DeviceCmd.ChargeClose) { // 充电状态
|
||||
getChargeState(cmdItem, dock) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储
|
||||
deviceFormat(cmdItem, dock) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储
|
||||
droneFormat(cmdItem, device) |
||||
} else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关
|
||||
getSupplementLightState(cmdItem, dock) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// 飞行器开关机
|
||||
function getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) { |
||||
if (!droneProperties) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.DroneOpen) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} else { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.DroneClose) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 舱盖开关
|
||||
function getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) { |
||||
const coverState = airportProperties?.cover_state as CoverStateEnum |
||||
|
||||
if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.CoverOpen) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} else if (coverState === CoverStateEnum.Open || coverState === CoverStateEnum.HalfOpen) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.CoverClose) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 推杆状态
|
||||
function getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) { |
||||
const putterState = airportProperties?.putter_state as PutterStateEnum |
||||
if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.PutterOpen) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} else if (putterState === PutterStateEnum.Open || putterState === PutterStateEnum.HalfOpen) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.PutterClose) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 充电状态
|
||||
function getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) { |
||||
const chargeState = airportProperties?.drone_charge_state |
||||
const state = chargeState?.state as ChargeStateEnum |
||||
if (!state) return |
||||
if (state === ChargeStateEnum.Charge) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.ChargeClose) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} else if (state === ChargeStateEnum.NotCharge) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText |
||||
if (cmdItem.cmdKey !== DeviceCmd.ChargeOpen) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 机场存储格式化
|
||||
function deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) { |
||||
const airportStorage = airportProperties?.storage |
||||
const value = getAirportStorage(airportStorage) |
||||
cmdItem.status = value |
||||
} |
||||
|
||||
// 机场存储格式化
|
||||
function droneFormat (cmdItem: DeviceCmdItem, droneProperties: any) { |
||||
const droneStorage = droneProperties?.storage |
||||
const value = getAirportStorage(droneStorage) |
||||
cmdItem.status = value |
||||
} |
||||
|
||||
// 获取机场存储容量
|
||||
// {
|
||||
// "total": 10000, // 单位:KB
|
||||
// "used": 500
|
||||
// }
|
||||
export function getAirportStorage (storage: AirportStorage) { |
||||
if (!storage) { |
||||
return DEFAULT_PLACEHOLDER |
||||
} |
||||
const total = storage.total |
||||
const used = storage.used |
||||
const byteObj = getBytesObject(total * 1024) |
||||
const _total = byteObj.value |
||||
const _used = getBytes(used * 1024, byteObj.index) |
||||
return `${_used}/${_total} ${byteObj.size}` |
||||
} |
||||
|
||||
function getBytes (bytes: number, index: number, fixed = 1) { |
||||
return (bytes / Math.pow(1024, index)).toFixed(fixed) |
||||
} |
||||
|
||||
// 补光灯状态
|
||||
function getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) { |
||||
const supplementLightState = airportProperties?.supplement_light_state |
||||
if (supplementLightState === SupplementLightStateEnum.Close) { |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText |
||||
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText |
||||
if (cmdItem.cmdKey !== DeviceCmd.SupplementLightOpen) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} else if (supplementLightState === SupplementLightStateEnum.Open) { |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText |
||||
cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText |
||||
if (cmdItem.cmdKey !== DeviceCmd.SupplementLightClose) { |
||||
exchangeDeviceCmd(cmdItem) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 交换指令 |
||||
* @param cmd |
||||
*/ |
||||
function exchangeDeviceCmd (cmdItem: DeviceCmdItem) { |
||||
if (cmdItem.oppositeCmdKey) { |
||||
const oppositeCmdKey = cmdItem.oppositeCmdKey |
||||
cmdItem.oppositeCmdKey = cmdItem.cmdKey |
||||
cmdItem.cmdKey = oppositeCmdKey |
||||
} |
||||
} |
||||
|
||||
// /**
|
||||
// * 更新简单指令发送情况更新信息
|
||||
// * @param cmd
|
||||
// */
|
||||
// export function updateDeviceSingleCmdInfo (cmdItem: DeviceCmdItem) {
|
||||
// // 补光灯
|
||||
// if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen) {
|
||||
// cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText
|
||||
// cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText
|
||||
// exchangeDeviceCmd(cmdItem)
|
||||
// } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightClose) {
|
||||
// cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText
|
||||
// cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText
|
||||
// exchangeDeviceCmd(cmdItem)
|
||||
// }
|
||||
// }
|
||||
|
||||
/** |
||||
* 根据指令执行消息更新信息 |
||||
* @param cmd |
||||
* @param deviceCmdExecuteInfo |
||||
* @returns |
||||
*/ |
||||
export function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], deviceCmdExecuteInfos?: DeviceCmdExecuteInfo[]) { |
||||
if (!deviceCmdExecuteInfos || !cmdList) { |
||||
return |
||||
} |
||||
cmdList.forEach(cmdItem => { |
||||
// 获取当前设备相应指令信息
|
||||
const deviceCmdExecuteInfo = deviceCmdExecuteInfos.find(cmdExecuteInfo => cmdExecuteInfo.biz_code === cmdItem.cmdKey) |
||||
if (deviceCmdExecuteInfo) { |
||||
if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceRebootNormalText |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneOpen) { // 飞行器开关机
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneClose) { |
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.CoverOpen) { // 舱盖开关
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.CoverClose) { |
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.PutterOpen) { // 推杆闭合展开
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.PutterClose) { |
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen) { // 充电状态
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.ChargeClose) { |
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText |
||||
cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText |
||||
exchangeDeviceCmd(cmdItem) |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.loading = false |
||||
} |
||||
} else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储
|
||||
if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText |
||||
cmdItem.loading = true |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.Failed) { |
||||
cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText |
||||
cmdItem.loading = false |
||||
} else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) { |
||||
cmdItem.loading = false |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
/** |
||||
* 加载图片 |
||||
* @param url |
||||
* @returns |
||||
*/ |
||||
export function urlToImage (url: string) { |
||||
return new Promise<HTMLImageElement>((resolve, reject) => { |
||||
const image = new Image() |
||||
image.src = url |
||||
image.onload = () => { resolve(image) } |
||||
image.onerror = () => { reject(new Error('image load error')) } |
||||
}) |
||||
} |
||||
|
||||
export interface CompressImageData { |
||||
blob: Blob | null; |
||||
imageData: ImageData; |
||||
} |
||||
export function compressImage (imgToCompress: HTMLImageElement, targetWidth: number, targetHeight: number): Promise<CompressImageData> | undefined { |
||||
// resizing the image
|
||||
const canvas = document.createElement('canvas') |
||||
const context = canvas.getContext('2d') |
||||
if (context) { |
||||
const iWidth = imgToCompress.width |
||||
const iHeight = imgToCompress.height |
||||
const iRatio = iWidth / iHeight // 图像宽高比
|
||||
const tRatio = targetWidth / targetHeight // 目标宽高比
|
||||
let dw = targetWidth |
||||
let dh = targetHeight |
||||
let dx = 0 |
||||
let dy = 0 |
||||
if (iRatio > tRatio) { |
||||
// 如果图像宽高比比目标宽高比要大,说明图像比目标尺寸更宽,这时候我们应该按照高度缩放比来进行缩放宽度
|
||||
dw = (targetHeight / iHeight) * iWidth |
||||
// 宽度溢出,应该放在中间
|
||||
dx = -(dw - targetWidth) / 2 |
||||
} else { |
||||
// 否则说明图像比目标尺寸更高,按照宽度缩放比来缩放高度
|
||||
dh = (targetWidth / iWidth) * iHeight |
||||
// 高度溢出,应该放在中间
|
||||
dy = -(dh - targetHeight) / 2 |
||||
} |
||||
|
||||
canvas.width = targetWidth |
||||
canvas.height = targetHeight |
||||
|
||||
context.drawImage( |
||||
imgToCompress, |
||||
dx, |
||||
dy, |
||||
dw, |
||||
dh, |
||||
) |
||||
|
||||
return new Promise<CompressImageData>((resolve) => { |
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height) |
||||
|
||||
canvas.toBlob(blob => resolve({ |
||||
blob, |
||||
imageData, |
||||
})) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 根据资源url下载文件 |
||||
* @param url |
||||
* @param fileName |
||||
*/ |
||||
export function download (url: string, fileName = ''): void { |
||||
const aLink = document.createElement('a') |
||||
aLink.style.display = 'none' |
||||
aLink.download = fileName |
||||
aLink.href = url |
||||
document.body.appendChild(aLink) |
||||
// 避免新开页面,闪烁
|
||||
// aLink.target = '_blank'
|
||||
aLink.click() |
||||
document.body.removeChild(aLink) |
||||
// aLink.remove()
|
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
import { |
||||
DATE_FORMAT, |
||||
DEFAULT_PLACEHOLDER |
||||
} from '/@/utils/constants' |
||||
import moment, { Moment } from 'moment' |
||||
|
||||
// 时间字符串 或者 Unix 时间戳(毫秒数)
|
||||
export function formatDateTime (time: string | number, format = DATE_FORMAT) { |
||||
return time ? moment(time, format) : DEFAULT_PLACEHOLDER |
||||
} |
||||
|
||||
// Unix 时间戳 (秒)
|
||||
export function formatUnixTime (time: number, format = DATE_FORMAT): string { |
||||
return time ? moment.unix(time).format(format) : DEFAULT_PLACEHOLDER |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
import { message } from 'ant-design-vue' |
||||
import ReconnectingWebSocket from 'reconnecting-websocket' |
||||
|
||||
interface WebSocketOptions { |
||||
data: any |
||||
cache?: boolean | string |
||||
destroyCache?: string |
||||
} |
||||
|
||||
export interface MessageHandler { |
||||
(data : {[key: string]: any}): void |
||||
} |
||||
|
||||
/** |
||||
* ConnectWebSocket 类 |
||||
* TODO: 优化messageHandler: EventEmitter。暂时传入回调函数 |
||||
*/ |
||||
class ConnectWebSocket { |
||||
_url: string |
||||
_socket: ReconnectingWebSocket | null |
||||
_hasInit: boolean |
||||
_messageHandler: MessageHandler | null |
||||
|
||||
constructor (url: string) { |
||||
this._url = url |
||||
this._socket = null |
||||
this._hasInit = false |
||||
this._messageHandler = null |
||||
} |
||||
|
||||
initSocket () { |
||||
if (this._hasInit) { |
||||
return |
||||
} |
||||
if (!this._url) { |
||||
return |
||||
} |
||||
|
||||
// 会自动重连,无需处理重连逻辑
|
||||
this._socket = new ReconnectingWebSocket(this._url, [], { |
||||
maxReconnectionDelay: 20000, // 断开后最大的重连时间: 20s,每多一次重连,会增加 1.3 倍,5 * 1.3 * 1.3 * 1.3...
|
||||
minReconnectionDelay: 5000, // 断开后最短的重连时间: 5s
|
||||
maxRetries: 5 |
||||
}) |
||||
|
||||
this._hasInit = true |
||||
|
||||
this._socket.addEventListener('open', this._onOpen.bind(this)) |
||||
this._socket.addEventListener('close', this._onClose.bind(this)) |
||||
this._socket.addEventListener('error', this._onError.bind(this)) |
||||
this._socket.addEventListener('message', this._onMessage.bind(this)) |
||||
} |
||||
|
||||
_onOpen () { |
||||
console.log('连接成功') |
||||
} |
||||
|
||||
_onClose () { |
||||
console.log('连接已断开') |
||||
} |
||||
|
||||
_onError () { |
||||
console.log('连接 error') |
||||
} |
||||
|
||||
registerMessageHandler (messageHandler: MessageHandler) { |
||||
this._messageHandler = messageHandler |
||||
} |
||||
|
||||
_onMessage (msg: MessageEvent) { |
||||
const data = JSON.parse(msg.data) |
||||
this._messageHandler && this._messageHandler(data) |
||||
// console.log('接受消息', message)
|
||||
} |
||||
|
||||
sendMessage = (message: WebSocketOptions): void => { |
||||
this._socket?.send(JSON.stringify(message.data)) |
||||
} |
||||
|
||||
close () { |
||||
this._socket?.close() |
||||
} |
||||
} |
||||
|
||||
export default ConnectWebSocket |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
import { ELocalStorageKey } from '/@/types/enums' |
||||
import { CURRENT_CONFIG } from '/@/api/http/config' |
||||
|
||||
export function getWebsocketUrl () { |
||||
const token: string = localStorage.getItem(ELocalStorageKey.Token) || '' as string |
||||
const url = CURRENT_CONFIG.websocketURL + '?x-auth-token=' + encodeURI(token) |
||||
return url |
||||
} |
Loading…
Reference in new issue