|
@@ -0,0 +1,372 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="deviceList">
|
|
|
|
|
+ <Search :selectedRowKeys="state.selectedRowKeys" :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch"
|
|
|
|
|
+ :onClickReset="onClickReset" />
|
|
|
|
|
+ <div class="deviceList-table">
|
|
|
|
|
+ <a-table :scroll="{ x: '100%', y: 500 }" childrenColumnName="null" rowKey="device_sn"
|
|
|
|
|
+ :loading="state.listLoading" :columns="columns" :dataSource="state.list" @change="refreshData"
|
|
|
|
|
+ :rowClassName="rowClassName" :pagination="paginationConfig" :rowSelection="rowSelection">
|
|
|
|
|
+ <!-- 设备型号 -->
|
|
|
|
|
+ <template #device_name="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="device_name" :showIcon="true" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 设备SN -->
|
|
|
|
|
+ <template #device_sn="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="device_sn" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 设备名称 -->
|
|
|
|
|
+ <template #nickname="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="nickname" :isEdit="!!state.editableData[record.device_sn]" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 固件版本 -->
|
|
|
|
|
+ <template #firmware_version="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="firmware_version" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 固件升级 -->
|
|
|
|
|
+ <template #firmware_status="{ record }">
|
|
|
|
|
+ <DeviceFirmwareUpgrade :device="record" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 当前状态 -->
|
|
|
|
|
+ <template #status="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="status_text" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 加入项目时间 -->
|
|
|
|
|
+ <template #bound_time="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="bound_time" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 最后在线时间 -->
|
|
|
|
|
+ <template #login_time="{ record }">
|
|
|
|
|
+ <CustomCell :record="record" fieldName="login_time" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 操作 -->
|
|
|
|
|
+ <template #action="{ record }">
|
|
|
|
|
+ <!-- 编辑态操作 -->
|
|
|
|
|
+ <div v-if="state.editableData[record.device_sn]">
|
|
|
|
|
+ <a-tooltip title="确定">
|
|
|
|
|
+ <CheckOutlined style="color: #28d445;margin-right: 10px;" @click="onClickSave(record)" />
|
|
|
|
|
+ </a-tooltip>
|
|
|
|
|
+ <a-tooltip title="取消">
|
|
|
|
|
+ <CloseOutlined style="color: #e70102;" @click="() => delete state.editableData[record.device_sn]" />
|
|
|
|
|
+ </a-tooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 非编辑态操作 -->
|
|
|
|
|
+ <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
|
|
|
|
|
+ <a-tooltip title="编辑">
|
|
|
|
|
+ <EditOutlined style="margin-right: 10px;" @click="onClickEdit(record)" />
|
|
|
|
|
+ </a-tooltip>
|
|
|
|
|
+ <a-tooltip title="删除">
|
|
|
|
|
+ <DeleteOutlined @click="onClickDelete(record)" />
|
|
|
|
|
+ </a-tooltip>
|
|
|
|
|
+ <a-dropdown v-if="record.domain === 3">
|
|
|
|
|
+ <EllipsisOutlined style="font-size: 20px;margin-left: 10px;" />
|
|
|
|
|
+ <template #overlay>
|
|
|
|
|
+ <a-menu>
|
|
|
|
|
+ <a-menu-item @click="onClickFeedback(record)">
|
|
|
|
|
+ 异常反馈
|
|
|
|
|
+ </a-menu-item>
|
|
|
|
|
+ <a-menu-item @click="onClickDeviceHms(record)">
|
|
|
|
|
+ 告警信息
|
|
|
|
|
+ </a-menu-item>
|
|
|
|
|
+ <!-- <a-menu-item>
|
|
|
|
|
+ 设备运维
|
|
|
|
|
+ </a-menu-item> -->
|
|
|
|
|
+ </a-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-dropdown>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 异常反馈 -->
|
|
|
|
|
+ <FeedbackDrawer :visible="state.feedbackDrawerVisible" :device="state.currentDevice"
|
|
|
|
|
+ :onClose="() => state.feedbackDrawerVisible = false" v-if="state.feedbackDrawerVisible" />
|
|
|
|
|
+ <!-- 告警信息 -->
|
|
|
|
|
+ <DeviceHmsDrawer v-model:visible="state.deviceHmsDrawerVisible" :device="state.currentDevice" />
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import { reactive, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
+import { Modal } from 'ant-design-vue';
|
|
|
|
|
+import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
+import Search from './components/Search.vue';
|
|
|
|
|
+import CustomCell from './components/CustomCell.vue';
|
|
|
|
|
+import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue';
|
|
|
|
|
+import FeedbackDrawer from './components/FeedbackDrawer.vue';
|
|
|
|
|
+import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue';
|
|
|
|
|
+import { getBindingDevices, updateDevice, unbindDevice } from '/@/api/manage';
|
|
|
|
|
+import { apis } from '/@/api/custom';
|
|
|
|
|
+import { getWorkspaceId } from '/@/utils/index';
|
|
|
|
|
+
|
|
|
|
|
+interface State {
|
|
|
|
|
+ workspaceId: string,
|
|
|
|
|
+ interval: number | null,
|
|
|
|
|
+ query: any,
|
|
|
|
|
+ listLoading: boolean,
|
|
|
|
|
+ list: any[],
|
|
|
|
|
+ selectedRowKeys: string[],
|
|
|
|
|
+ currentDevice: any,
|
|
|
|
|
+ editableData: {
|
|
|
|
|
+ [key: string]: any,
|
|
|
|
|
+ },
|
|
|
|
|
+ feedbackDrawerVisible: boolean,
|
|
|
|
|
+ deviceHmsDrawerVisible: boolean,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const state: State = reactive({
|
|
|
|
|
+ workspaceId: getWorkspaceId(),
|
|
|
|
|
+ interval: null,
|
|
|
|
|
+ query: undefined,
|
|
|
|
|
+ listLoading: false,
|
|
|
|
|
+ list: [],
|
|
|
|
|
+ selectedRowKeys: [],
|
|
|
|
|
+ currentDevice: {},
|
|
|
|
|
+ editableData: {},
|
|
|
|
|
+ feedbackDrawerVisible: false,
|
|
|
|
|
+ deviceHmsDrawerVisible: false,
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const paginationConfig = reactive({
|
|
|
|
|
+ pageSizeOptions: ['20', '50', '100'],
|
|
|
|
|
+ showQuickJumper: true,
|
|
|
|
|
+ showSizeChanger: true,
|
|
|
|
|
+ pageSize: 20,
|
|
|
|
|
+ current: 1,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const fetchList = async () => {
|
|
|
|
|
+ state.listLoading = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getBindingDevices(state.workspaceId, {
|
|
|
|
|
+ ...state.query,
|
|
|
|
|
+ page: paginationConfig.current,
|
|
|
|
|
+ page_size: paginationConfig.pageSize,
|
|
|
|
|
+ });
|
|
|
|
|
+ state.list = res.data.list;
|
|
|
|
|
+ paginationConfig.total = res.data.pagination.total;
|
|
|
|
|
+ paginationConfig.current = res.data.pagination.page;
|
|
|
|
|
+ paginationConfig.pageSize = res.data.pagination.page_size;
|
|
|
|
|
+ await autoUpdateDeviceStatus()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ state.listLoading = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 自动更新设备状态
|
|
|
|
|
+const autoUpdateDeviceStatus = async () => {
|
|
|
|
|
+ if (state.list.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const snList: string[] = [];
|
|
|
|
|
+ state.list.forEach(item => {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ snList.push(item.children.device_sn);
|
|
|
|
|
+ }
|
|
|
|
|
+ snList.push(item.device_sn);
|
|
|
|
|
+ })
|
|
|
|
|
+ const data = {
|
|
|
|
|
+ snList: snList.join(','),
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await apis.fetchDeviceStatus(data);
|
|
|
|
|
+ const deviceStatusMap = new Map();
|
|
|
|
|
+ res.data.forEach((item: any) => {
|
|
|
|
|
+ deviceStatusMap.set(item.device_sn, item.status_text);
|
|
|
|
|
+ });
|
|
|
|
|
+ state.list.forEach(item => {
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ item.children.status_text = deviceStatusMap.get(item.children.device_sn);
|
|
|
|
|
+ }
|
|
|
|
|
+ item.status_text = deviceStatusMap.get(item.device_sn);
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 开始定时器
|
|
|
|
|
+const startAutoUpdate = () => {
|
|
|
|
|
+ state.interval = window.setInterval(autoUpdateDeviceStatus, 10000);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 清除定时器
|
|
|
|
|
+const clearAutoUpdate = () => {
|
|
|
|
|
+ if (state.interval !== null) {
|
|
|
|
|
+ clearInterval(state.interval);
|
|
|
|
|
+ state.interval = null;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ await fetchList();
|
|
|
|
|
+ startAutoUpdate(); // 页面加载完成后启动定时器
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ clearAutoUpdate(); // 页面卸载前清除定时器
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const columns = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '设备型号',
|
|
|
|
|
+ dataIndex: 'device_name',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ sorter: (a: any, b: any) => a.device_name.localeCompare(b.device_name),
|
|
|
|
|
+ slots: { customRender: 'device_name' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '设备SN',
|
|
|
|
|
+ dataIndex: 'device_sn',
|
|
|
|
|
+ width: 250,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ slots: { customRender: 'device_sn' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '设备名称',
|
|
|
|
|
+ dataIndex: 'nickname',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ sorter: (a: any, b: any) => a.nickname.localeCompare(b.nickname),
|
|
|
|
|
+ slots: { customRender: 'nickname' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '固件版本',
|
|
|
|
|
+ dataIndex: 'firmware_version',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ slots: { customRender: 'firmware_version' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '固件升级',
|
|
|
|
|
+ dataIndex: 'firmware_status',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ slots: { customRender: 'firmware_status' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '当前状态',
|
|
|
|
|
+ dataIndex: 'status',
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ slots: { customRender: 'status' }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '加入项目时间',
|
|
|
|
|
+ dataIndex: 'bound_time',
|
|
|
|
|
+ width: 200,
|
|
|
|
|
+ sorter: (a: any, b: any) => a.bound_time.localeCompare(b.bound_time),
|
|
|
|
|
+ slots: { customRender: 'bound_time' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '最后在线时间',
|
|
|
|
|
+ dataIndex: 'login_time',
|
|
|
|
|
+ width: 200,
|
|
|
|
|
+ sorter: (a: any, b: any) => a.login_time.localeCompare(b.login_time),
|
|
|
|
|
+ slots: { customRender: 'login_time' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '操作',
|
|
|
|
|
+ dataIndex: 'actions',
|
|
|
|
|
+ fixed: 'right',
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ slots: { customRender: 'action' },
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const rowClassName = (record: any, index: number) => {
|
|
|
|
|
+ const className = []
|
|
|
|
|
+ if ((index & 1) === 0) {
|
|
|
|
|
+ className.push('table-striped')
|
|
|
|
|
+ }
|
|
|
|
|
+ return className.toString().replaceAll(',', ' ')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const refreshData = async (page: any) => {
|
|
|
|
|
+ paginationConfig.current = page?.current!
|
|
|
|
|
+ paginationConfig.pageSize = page?.pageSize!
|
|
|
|
|
+ await fetchList();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const rowSelection = {
|
|
|
|
|
+ onChange: (selectedRowKeys: string[]) => {
|
|
|
|
|
+ state.selectedRowKeys = selectedRowKeys;
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击批量删除
|
|
|
|
|
+const onClickBatchDelete = async () => {
|
|
|
|
|
+ console.log(state.selectedRowKeys, '点击批量删除');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击搜索
|
|
|
|
|
+const onClickSearch = async (query: any) => {
|
|
|
|
|
+ state.query = query;
|
|
|
|
|
+ await fetchList();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击重置
|
|
|
|
|
+const onClickReset = async (query: any) => {
|
|
|
|
|
+ state.query = query;
|
|
|
|
|
+ await fetchList();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击异常反馈
|
|
|
|
|
+const onClickFeedback = (record: any) => {
|
|
|
|
|
+ state.feedbackDrawerVisible = true;
|
|
|
|
|
+ state.currentDevice = record;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击告警信息
|
|
|
|
|
+const onClickDeviceHms = (record: any) => {
|
|
|
|
|
+ state.deviceHmsDrawerVisible = true;
|
|
|
|
|
+ state.currentDevice = record;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击编辑
|
|
|
|
|
+const onClickEdit = (record: any) => {
|
|
|
|
|
+ state.editableData[record.device_sn] = record;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击保存
|
|
|
|
|
+const onClickSave = async (record: any) => {
|
|
|
|
|
+ delete state.editableData[record.device_sn];
|
|
|
|
|
+ await updateDevice({ nickname: record.nickname }, state.workspaceId, record.device_sn)
|
|
|
|
|
+ if (record.children) {
|
|
|
|
|
+ await updateDevice({ nickname: record.children.nickname }, state.workspaceId, record.children.device_sn)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 点击删除
|
|
|
|
|
+const onClickDelete = (record: any) => {
|
|
|
|
|
+ Modal.confirm({
|
|
|
|
|
+ title: '提示',
|
|
|
|
|
+ content: `确定删除${record.device_name}吗?`,
|
|
|
|
|
+ onOk: async () => {
|
|
|
|
|
+ const res = await unbindDevice(record.device_sn);
|
|
|
|
|
+ if (res.code !== 0) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ await fetchList();
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+.deviceList {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.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;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-striped {
|
|
|
|
|
+ background-color: #f7f9fa;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|