| src/components/MediaPanel.vue | ●●●●● patch | view | raw | blame | history | |
| src/components/task/TaskPanel.vue | ●●●●● patch | view | raw | blame | history | |
| src/pages/page-web/projects/devices.vue | ●●●●● patch | view | raw | blame | history |
src/components/MediaPanel.vue
@@ -1,63 +1,73 @@ <template> <!-- <div class="header">媒体文件</div>--> <!-- <div class="header">媒体文件</div>--> <!--搜索栏--> <div class="search-panel-wrapper"> <div class="search-part"> <a-range-picker :size="searchPanelOptions.size" :format="searchPanelOptions.dateFormat" :valueFormat="searchPanelOptions.valueFormat" v-model:value="timeRangeArr.data" @change="dateChange" style="width: 300px" /> <a-config-provider :locale="zhCN"> <div class="search-panel-wrapper"> <div class="search-part"> <a-range-picker :size="searchPanelOptions.size" :format="searchPanelOptions.dateFormat" :valueFormat="searchPanelOptions.valueFormat" v-model:value="timeRangeArr.data" @change="dateChange" style="width: 300px"/> </div> <div class="search-part"> <a-select v-model:value="subFileTypeArr" allowClear @change="subFileTypeChange" :options="subFileTypeOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="所有类型" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-select v-model:value="payloadArr" allowClear @change="payloadChange" :options="payloadOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="所有负载" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-input-search :size="searchPanelOptions.size" v-model:value="searchQuery.name" @change="inputChange" placeholder="按文件名称搜索" style="width: 300px"/> </div> </div> <div class="search-part"> <a-select v-model:value="subFileTypeArr" allowClear @change="subFileTypeChange" :options="subFileTypeOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="所有类型" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-select v-model:value="payloadArr" allowClear @change="payloadChange" :options="payloadOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="所有负载" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-input-search :size="searchPanelOptions.size" v-model:value="searchQuery.name" @change="inputChange" placeholder="按文件名称搜索" style="width: 300px"/> </div> </div> <a-spin :spinning="loading" :delay="1000" tip="downloading" size="large"> <div class="media-panel-wrapper"> <a-table class="media-table" :columns="columns" :data-source="mediaData.data" row-key="fingerprint" :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData"> <template v-for="col in ['name', 'path']" #[col]="{ text }" :key="col"> <a-tooltip :title="text"> <a-spin :spinning="loading" :delay="1000" tip="downloading" size="large"> <div class="media-panel-wrapper"> <a-table class="media-table" :columns="columns" :data-source="mediaData.data" row-key="fingerprint" :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData"> <template v-for="col in ['name', 'path']" #[col]="{ text }" :key="col"> <a-tooltip :title="text"> <a v-if="col === 'name'">{{ text }}</a> <span v-else>{{ text }}</span> </a-tooltip> </template> <template #original="{ text }"> {{ text }} </template> <template #action="{ record }"> <a-tooltip title="download"> <a class="fz18" @click="downloadMedia(record)"><DownloadOutlined /></a> </a-tooltip> </template> </a-table> </div> </a-spin> </a-tooltip> </template> <template #original="{ text }"> {{ text }} </template> <template #action="{ record }"> <a-tooltip title="download"> <a class="fz18" @click="downloadMedia(record)"> <DownloadOutlined/> </a> </a-tooltip> </template> </a-table> </div> </a-spin> </a-config-provider> </template> <script setup lang="ts"> import { ref } from '@vue/reactivity' import { TableState } from 'ant-design-vue/lib/table/interface' import { onMounted, reactive } from 'vue' import { IPage } from '../api/http/type' import { ELocalStorageKey } from '../types/enums' import { downloadFile } from '../utils/common' import { downloadMediaFile, getMediaFiles, MediaQueryParam } from '/@/api/media' import { DownloadOutlined } from '@ant-design/icons-vue' import { message, Pagination } from 'ant-design-vue' import { TaskStatus, TaskStatusMap } from '/@/types/task' import zhCN from 'ant-design-vue/es/locale/zh_CN' import {ref} from '@vue/reactivity' import {TableState} from 'ant-design-vue/lib/table/interface' import {onMounted, reactive} from 'vue' import {IPage} from '../api/http/type' import {ELocalStorageKey} from '../types/enums' import {downloadFile} from '../utils/common' import {downloadMediaFile, getMediaFiles, MediaQueryParam} from '/@/api/media' import {DownloadOutlined} from '@ant-design/icons-vue' import {message, Pagination} from 'ant-design-vue' import {TaskStatus, TaskStatusMap} from '/@/types/task' const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)! const loading = ref(false) @@ -71,18 +81,16 @@ }) const subFileTypeOptions = [ { value: 0, label: '普通图片' }, { value: 1, label: '全景图' }, {value: 0, label: '普通图片'}, {value: 1, label: '全景图'}, ] const payloadOptions = [ ] const payloadOptions = [] const timeRangeArr = reactive({ data: [] as string[] }) const searchQuery = reactive<MediaQueryParam>({ }) const searchQuery = reactive<MediaQueryParam>({}) const subFileTypeArr = reactive([]) const payloadArr = reactive([]) @@ -91,7 +99,7 @@ title: '文件名称', dataIndex: 'file_name', ellipsis: true, slots: { customRender: 'name' } slots: {customRender: 'name'} }, // { // title: '文件路径', @@ -122,7 +130,7 @@ }, { title: '操作', slots: { customRender: 'action' } slots: {customRender: 'action'} } ] const body: IPage = { @@ -160,27 +168,27 @@ getFiles() }) function dateChange (value:any) { function dateChange(value: any) { searchQuery.startTime = value[0] searchQuery.endTime = value[1] getFiles() } function subFileTypeChange (value:any) { function subFileTypeChange(value: any) { searchQuery.subFileType = value.join(',') getFiles() } function payloadChange (value:any) { function payloadChange(value: any) { searchQuery.payload = value.join(',') getFiles() } function inputChange (searchValue: string) { function inputChange(searchValue: string) { getFiles() } function getFiles () { function getFiles() { getMediaFiles(workspaceId, body, searchQuery).then(res => { mediaData.data = res.data.list paginationProp.total = res.data.pagination.total @@ -189,13 +197,13 @@ }) } function refreshData (page: Pagination) { function refreshData(page: Pagination) { body.page = page?.current! body.page_size = page?.pageSize! getFiles() } function downloadMedia (media: MediaFile) { function downloadMedia(media: MediaFile) { loading.value = true downloadMediaFile(workspaceId, media.file_id).then(res => { if (!res) { @@ -214,15 +222,18 @@ .media-panel-wrapper { width: 100%; padding: 16px; .media-table { background: #fff; margin-top: 10px; } .action-area { color: $primary; cursor: pointer; } } .header { width: 100%; height: 60px; @@ -241,7 +252,7 @@ display: flex; align-items: center; .search-part{ .search-part { margin-right: 10px; } src/components/task/TaskPanel.vue
@@ -1,136 +1,147 @@ <template> <!-- <div class="header">计划库</div>--> <!--搜索栏--> <div class="search-panel-wrapper"> <div class="search-part"> <a-range-picker :size="searchPanelOptions.size" :format="searchPanelOptions.dateFormat" :valueFormat="searchPanelOptions.valueFormat" v-model:value="timeRangeArr.data" @change="dateChange" style="width: 300px" /> <a-config-provider :locale="zhCN"> <div class="search-panel-wrapper"> <div class="search-part"> <a-range-picker :size="searchPanelOptions.size" :format="searchPanelOptions.dateFormat" :valueFormat="searchPanelOptions.valueFormat" v-model:value="timeRangeArr.data" @change="dateChange" style="width: 300px"/> </div> <div class="search-part"> <a-select :size="searchPanelOptions.size" @change="selectChange" ref="select" v-model:value="searchQuery.taskType" style="width: 300px"> <a-select-option value="">所有类型</a-select-option> <a-select-option :value="item.value" v-for="(item) in TaskTypeOptions" :key="item.value">{{ item.label }} </a-select-option> </a-select> </div> <div class="search-part"> <a-select v-model:value="statusArr" allowClear @change="selectMultipleChange" :options="TaskStatusOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="全部状态" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-input-search :size="searchPanelOptions.size" v-model:value="searchQuery.name" @change="inputChange" placeholder="按航线或计划名搜索" style="width: 300px"/> </div> </div> <div class="search-part"> <a-select :size="searchPanelOptions.size" @change="selectChange" ref="select" v-model:value="searchQuery.taskType" style="width: 300px"> <a-select-option value="">所有类型</a-select-option> <a-select-option :value="item.value" v-for="(item) in TaskTypeOptions" :key="item.value">{{ item.label }} </a-select-option> </a-select> </div> <div class="search-part"> <a-select v-model:value="statusArr" allowClear @change="selectMultipleChange" :options="TaskStatusOptions" :maxTagCount="searchPanelOptions.maxTagCount" mode="multiple" :size="searchPanelOptions.size" placeholder="全部状态" style="width: 300px"> </a-select> </div> <div class="search-part"> <a-input-search :size="searchPanelOptions.size" v-model:value="searchQuery.name" @change="inputChange" placeholder="按航线或计划名搜索" style="width: 300px"/> </div> </div> <!--表格--> <div class="plan-panel-wrapper"> <a-table :loading="tableLoading" class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id" :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData"> <!-- 执行时间 --> <template #duration="{ record }"> <div class="flex-row" style="white-space: pre-wrap"> <!--表格--> <div class="plan-panel-wrapper"> <a-table :loading="tableLoading" class="plan-table" :columns="columns" :data-source="plansData.data" row-key="job_id" :pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData"> <!-- 执行时间 --> <template #duration="{ record }"> <div class="flex-row" style="white-space: pre-wrap"> <div> <div>{{ formatTaskTime(record.begin_time) }}</div> <div>{{ formatTaskTime(record.end_time) }}</div> </div> <div class="ml10"> <div>{{ formatTaskTime(record.execute_time) }}</div> <div>{{ formatTaskTime(record.completed_time) }}</div> </div> </div> </template> <!-- 状态 --> <template #status="{ record }"> <div> <div>{{ formatTaskTime(record.begin_time) }}</div> <div>{{ formatTaskTime(record.end_time) }}</div> <div class="flex-display flex-align-center"> <span class="circle-icon" :style="{backgroundColor: formatTaskStatus(record).color}"></span> {{ formatTaskStatus(record).text }} <a-tooltip v-if="!!record.code" placement="bottom" arrow-point-at-center> <template #title> <div>{{ getCodeMessage(record.code) }}</div> </template> <exclamation-circle-outlined class="ml5" :style="{color: commonColor.WARN, fontSize: '16px' }"/> </a-tooltip> </div> <div v-if="record.status === TaskStatus.Carrying"> <a-progress :percent="record.progress || 0"/> </div> </div> <div class="ml10"> <div>{{ formatTaskTime(record.execute_time) }}</div> <div>{{ formatTaskTime(record.completed_time) }}</div> </template> <!-- 任务类型 --> <template #taskType="{ record }"> <div>{{ formatTaskType(record) }}</div> </template> <!-- 失控动作 --> <template #lostAction="{ record }"> <div>{{ formatLostAction(record) }}</div> </template> <!-- 媒体上传状态 --> <template #media_upload="{ record }"> <div> <div class="flex-display flex-align-center"> <span class="circle-icon" :style="{backgroundColor: formatMediaTaskStatus(record).color}"></span> {{ formatMediaTaskStatus(record).text }} </div> <div class="pl15"> {{ formatMediaTaskStatus(record).number }} <a-tooltip v-if="formatMediaTaskStatus(record).status === MediaStatus.ToUpload" placement="bottom" arrow-point-at-center> <template #title> <div>Upload now</div> </template> <UploadOutlined class="ml5" :style="{color: commonColor.BLUE, fontSize: '16px' }" @click="onUploadMediaFileNow(record.job_id)"/> </a-tooltip> </div> </div> </div> </template> <!-- 状态 --> <template #status="{ record }"> <div> <div class="flex-display flex-align-center"> <span class="circle-icon" :style="{backgroundColor: formatTaskStatus(record).color}"></span> {{ formatTaskStatus(record).text }} <a-tooltip v-if="!!record.code" placement="bottom" arrow-point-at-center> <template #title> <div>{{ getCodeMessage(record.code) }}</div> </template> <exclamation-circle-outlined class="ml5" :style="{color: commonColor.WARN, fontSize: '16px' }"/> </a-tooltip> </template> <!-- 操作 --> <template #action="{ record }"> <div class="action-area"> <a-popconfirm v-if="record.status === TaskStatus.Wait" title="你确定要删除该计划吗?" ok-text="确定" cancel-text="取消" @confirm="onDeleteTask(record.job_id)" > <a-button type="primary" size="small">删除</a-button> </a-popconfirm> <a-popconfirm v-if="record.status === TaskStatus.Carrying" title="你确定要暂停该任务吗?" ok-text="确定" cancel-text="取消" @confirm="onSuspendTask(record.job_id)" > <a-button type="primary" size="small">暂停</a-button> </a-popconfirm> <a-popconfirm v-if="record.status === TaskStatus.Paused" title="你确定要重新开始吗?" ok-text="确定" cancel-text="取消" @confirm="onResumeTask(record.job_id)" > <a-button type="primary" size="small">重新开始</a-button> </a-popconfirm> </div> <div v-if="record.status === TaskStatus.Carrying"> <a-progress :percent="record.progress || 0"/> </div> </div> </template> <!-- 任务类型 --> <template #taskType="{ record }"> <div>{{ formatTaskType(record) }}</div> </template> <!-- 失控动作 --> <template #lostAction="{ record }"> <div>{{ formatLostAction(record) }}</div> </template> <!-- 媒体上传状态 --> <template #media_upload="{ record }"> <div> <div class="flex-display flex-align-center"> <span class="circle-icon" :style="{backgroundColor: formatMediaTaskStatus(record).color}"></span> {{ formatMediaTaskStatus(record).text }} </div> <div class="pl15"> {{ formatMediaTaskStatus(record).number }} <a-tooltip v-if="formatMediaTaskStatus(record).status === MediaStatus.ToUpload" placement="bottom" arrow-point-at-center> <template #title> <div>Upload now</div> </template> <UploadOutlined class="ml5" :style="{color: commonColor.BLUE, fontSize: '16px' }" @click="onUploadMediaFileNow(record.job_id)"/> </a-tooltip> </div> </div> </template> <!-- 操作 --> <template #action="{ record }"> <div class="action-area"> <a-popconfirm v-if="record.status === TaskStatus.Wait" title="你确定要删除该计划吗?" ok-text="确定" cancel-text="取消" @confirm="onDeleteTask(record.job_id)" > <a-button type="primary" size="small">删除</a-button> </a-popconfirm> <a-popconfirm v-if="record.status === TaskStatus.Carrying" title="你确定要暂停该任务吗?" ok-text="确定" cancel-text="取消" @confirm="onSuspendTask(record.job_id)" > <a-button type="primary" size="small">暂停</a-button> </a-popconfirm> <a-popconfirm v-if="record.status === TaskStatus.Paused" title="你确定要重新开始吗?" ok-text="确定" cancel-text="取消" @confirm="onResumeTask(record.job_id)" > <a-button type="primary" size="small">重新开始</a-button> </a-popconfirm> </div> </template> </a-table> </div> </template> </a-table> </div> </a-config-provider> </template> <script setup lang="ts"> import { reactive, ref } from '@vue/reactivity' import { message } from 'ant-design-vue' import { TableState } from 'ant-design-vue/lib/table/interface' import { computed, onMounted } from 'vue' import { IPage } from '/@/api/http/type' import {reactive, ref} from '@vue/reactivity' import {message} from 'ant-design-vue' import {TableState} from 'ant-design-vue/lib/table/interface' import {computed, onMounted} from 'vue' import {IPage} from '/@/api/http/type' import zhCN from 'ant-design-vue/es/locale/zh_CN' import 'moment/dist/locale/zh-cn' import { deleteTask, updateTaskStatus, @@ -140,9 +151,9 @@ uploadMediaFileNow, TaskQueryParam } from '/@/api/wayline' import { useMyStore } from '/@/store' import { ELocalStorageKey } from '/@/types/enums' import { useFormatTask } from './use-format-task' import {useMyStore} from '/@/store' import {ELocalStorageKey} from '/@/types/enums' import {useFormatTask} from './use-format-task' import { TaskTypeOptions, TaskStatus, @@ -154,23 +165,22 @@ MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task' import { useTaskWsEvent } from './use-task-ws-event' import { getErrorMessage } from '/@/utils/error-code/index' import { commonColor } from '/@/utils/color' import { ExclamationCircleOutlined, UploadOutlined } from '@ant-design/icons-vue' import {useTaskWsEvent} from './use-task-ws-event' import {getErrorMessage} from '/@/utils/error-code/index' import {commonColor} from '/@/utils/color' import {ExclamationCircleOutlined, UploadOutlined} from '@ant-design/icons-vue' const store = useMyStore() const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)! const dockSns = computed(() => store.state.common.dockSns) // 监听设备选择 watch( () => dockSns.value, (newVal) => { searchQuery.dockSn = newVal getPlans() } () => dockSns.value, (newVal) => { searchQuery.dockSn = newVal getPlans() } ) // 搜索栏配置项 @@ -210,13 +220,13 @@ title: '计划|实际时间', dataIndex: 'duration', width: 200, slots: { customRender: 'duration' }, slots: {customRender: 'duration'}, }, { title: '执行状态', key: 'status', width: 150, slots: { customRender: 'status' } slots: {customRender: 'status'} }, { title: '计划名称', @@ -238,12 +248,12 @@ title: '航线飞行中失联', dataIndex: 'out_of_control_action', width: 140, slots: { customRender: 'lostAction' }, slots: {customRender: 'lostAction'}, }, { title: '操作', width: 120, slots: { customRender: 'action' } slots: {customRender: 'action'} } ] type Pagination = TableState['pagination'] @@ -252,13 +262,13 @@ data: [] as Task[] }) const { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus } = useFormatTask() const {formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus} = useFormatTask() // 设备任务执行进度更新 function onTaskProgressWs (data: TaskProgressInfo) { const { bid, output } = data function onTaskProgressWs(data: TaskProgressInfo) { const {bid, output} = data if (output) { const { status, progress } = output || {} const {status, progress} = output || {} const taskItem = plansData.data.find(task => task.job_id === bid) if (!taskItem) return if (status) { @@ -274,8 +284,8 @@ } // 媒体上传进度更新 function onTaskMediaProgressWs (data: MediaStatusProgressInfo) { const { media_count: mediaCount, uploaded_count: uploadedCount, job_id: jobId } = data function onTaskMediaProgressWs(data: MediaStatusProgressInfo) { const {media_count: mediaCount, uploaded_count: uploadedCount, job_id: jobId} = data if (isNaN(mediaCount) || isNaN(uploadedCount) || !jobId) { return } @@ -290,8 +300,8 @@ taskItem.uploaded_count = uploadedCount } function onoTaskMediaHighestPriorityWS (data: TaskMediaHighestPriorityProgressInfo) { const { pre_job_id: preJobId, job_id: jobId } = data function onoTaskMediaHighestPriorityWS(data: TaskMediaHighestPriorityProgressInfo) { const {pre_job_id: preJobId, job_id: jobId} = data const preTaskItem = plansData.data.find(task => task.job_id === preJobId) const taskItem = plansData.data.find(task => task.job_id === jobId) if (preTaskItem) { @@ -302,7 +312,7 @@ } } function getCodeMessage (code: number) { function getCodeMessage(code: number) { return getErrorMessage(code) + `(code: ${code})` } @@ -316,26 +326,26 @@ getPlans() }) function dateChange (value:any) { function dateChange(value: any) { searchQuery.startTime = value[0] searchQuery.endTime = value[1] getPlans() } function inputChange (searchValue: string) { function inputChange(searchValue: string) { getPlans() } function selectChange (value:any) { function selectChange(value: any) { getPlans() } function selectMultipleChange (value:any) { function selectMultipleChange(value: any) { searchQuery.status = value.join(',') getPlans() } function getPlans () { function getPlans() { console.log('计划查询请求参数', searchQuery) tableLoading.value = true getWaylineJobs(workspaceId, body, searchQuery).then(res => { @@ -353,15 +363,15 @@ }) } function refreshData (page: Pagination) { function refreshData(page: Pagination) { body.page = page?.current! body.page_size = page?.pageSize! getPlans() } // 删除任务 async function onDeleteTask (jobId: string) { const { code } = await deleteTask(workspaceId, { async function onDeleteTask(jobId: string) { const {code} = await deleteTask(workspaceId, { job_id: jobId }) if (code === 0) { @@ -371,8 +381,8 @@ } // 挂起任务 async function onSuspendTask (jobId: string) { const { code } = await updateTaskStatus(workspaceId, { async function onSuspendTask(jobId: string) { const {code} = await updateTaskStatus(workspaceId, { job_id: jobId, status: UpdateTaskStatus.Suspend }) @@ -383,8 +393,8 @@ } // 解除挂起任务 async function onResumeTask (jobId: string) { const { code } = await updateTaskStatus(workspaceId, { async function onResumeTask(jobId: string) { const {code} = await updateTaskStatus(workspaceId, { job_id: jobId, status: UpdateTaskStatus.Resume }) @@ -395,8 +405,8 @@ } // 立即上传媒体 async function onUploadMediaFileNow (jobId: string) { const { code } = await uploadMediaFileNow(workspaceId, jobId) async function onUploadMediaFileNow(jobId: string) { const {code} = await uploadMediaFileNow(workspaceId, jobId) if (code === 0) { message.success('Upload Media File successfully') getPlans() @@ -453,7 +463,7 @@ display: flex; align-items: center; .search-part{ .search-part { margin-right: 10px; } src/pages/page-web/projects/devices.vue
@@ -1,4 +1,3 @@ <template> <a-menu v-model:selectedKeys="current" mode="horizontal" @select="select"> <a-menu-item :key="EDeviceTypeName.Aircraft" class="ml20"> @@ -8,119 +7,132 @@ 机场 </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> <a-tooltip :title="text"> {{ text }} </a-tooltip> </template> </div> </template> <template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col"> <a-tooltip :title="text"> <a-config-provider :locale="zhCN"> <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> <a-tooltip :title="text"> {{ text }} </a-tooltip> </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 }"> </a-tooltip> </template> <!-- 固件版本 --> <template #firmware_version="{ record }"> <span v-if="judgeCurrentType(EDeviceTypeName.Dock)"> <DeviceFirmwareUpgrade :device="record" class="table-flex-col" @device-upgrade="onDeviceUpgrade" /> class="table-flex-col" @device-upgrade="onDeviceUpgrade" /> </span> <span v-else> <span v-else> {{ record.firmware_version }} </span> </template> <!-- 状态 --> <template #status="{ text }"> </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 class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;"/> <span>在线</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 class="flex-row flex-align-center" v-else> <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;"/> <span>离线</span> </span> </template> <!-- 操作 --> <template #action="{ record }"> <div class="editable-row-operations"> <!-- 编辑态操作 --> <div v-if="editableData[record.device_sn]"> <a-tooltip title="保存"> <span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span> </a-tooltip> <a-tooltip title="取消"> <span @click="() => delete editableData[record.device_sn]" style="color: #e70102;"><CloseOutlined /></span> </a-tooltip> </template> <!-- 操作 --> <template #action="{ record }"> <div class="editable-row-operations"> <!-- 编辑态操作 --> <div v-if="editableData[record.device_sn]"> <a-tooltip title="保存"> <span @click="save(record)" style="color: #28d445;"><CheckOutlined/></span> </a-tooltip> <a-tooltip title="取消"> <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信息"> <FileSearchOutlined @click="showHms(record)"/> </a-tooltip> <a-tooltip title="编辑"> <EditOutlined @click="edit(record)"/> </a-tooltip> <a-tooltip title="删除"> <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }"/> </a-tooltip> </div> </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信息"> <FileSearchOutlined @click="showHms(record)"/> </a-tooltip> <a-tooltip title="编辑"> <EditOutlined @click="edit(record)"/> </a-tooltip> <a-tooltip title="删除"> <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }"/> </a-tooltip> </div> </div> </template> </template> </a-table> <a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }" @ok="unbind"> </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;">确定从项目中删除该设备吗?</p> <template #title> <div class="flex-row flex-justify-center"> <span>确定</span> </div> <div class="flex-row flex-justify-center"> <span>确定</span> </div> </template> </a-modal> </a-modal> <!-- 设备升级 --> <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible" :device="selectedDevice" @ok="onUpgradeDeviceOk" ></DeviceFirmwareUpgradeModal> <!-- 设备升级 --> <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible" :device="selectedDevice" @ok="onUpgradeDeviceOk" ></DeviceFirmwareUpgradeModal> <!-- 设备日志上传记录 --> <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice" ></DeviceLogUploadRecordDrawer> <!-- 设备日志上传记录 --> <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice" ></DeviceLogUploadRecordDrawer> <!-- hms 信息 --> <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice"> </DeviceHmsDrawer> </div> <!-- hms 信息 --> <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice"> </DeviceHmsDrawer> </div> </a-config-provider> </template> <script lang="ts" setup> import zhCN from 'ant-design-vue/es/locale/zh_CN' 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 { 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' @@ -131,16 +143,25 @@ import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue' import { message, notification } from 'ant-design-vue' import { useMyStore } from '/@/store' interface DeviceData { device: Device[] } const store = useMyStore() const loading = ref(false) const deleteTip = ref<boolean>(false) const deleteSn = ref<string>() const columns: ColumnProps[] = [ { title: '设备型号', dataIndex: 'device_name', width: 100, className: 'titleStyle' }, { title: '设备SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } }, { title: '设备SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } }, { title: '设备组织名称', dataIndex: 'nickname', @@ -150,7 +171,13 @@ ellipsis: true, slots: { customRender: 'nickname' } }, { title: '固件版本', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } }, { title: '固件版本', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } }, { title: '在线状态', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } }, { title: '所属项目', @@ -172,8 +199,20 @@ return obj } }, { title: '加入组织时间', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' }, { title: '在线时间', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' }, { title: '加入组织时间', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' }, { title: '在线时间', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' }, { title: '操作', dataIndex: 'actions', @@ -367,6 +406,7 @@ const currentDevice = ref({} as Device) // 设备日志 const deviceLogUploadRecordVisible = ref(false) function showDeviceLogUploadRecord (dock: Device) { deviceLogUploadRecordVisible.value = true currentDevice.value = dock @@ -386,8 +426,8 @@ </script> <style lang="scss" scoped> .device-table-wrap{ .editable-row-operations{ .device-table-wrap { .editable-row-operations { div > span { margin-right: 10px; } @@ -402,42 +442,53 @@ 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); 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;