index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <template>
  2. <div class="deviceList">
  3. <Search :selectedRowKeys="state.selectedRowKeys" :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch"
  4. :onClickReset="onClickReset" />
  5. <div class="deviceList-table">
  6. <a-table :scroll="{ x: '100%', y: 500 }" rowKey="device_sn"
  7. :loading="state.listLoading" :columns="columns" :dataSource="state.list" @change="refreshData"
  8. :rowClassName="rowClassName" :pagination="paginationConfig" :rowSelection="rowSelection">
  9. <!-- 设备型号 -->
  10. <template #device_name="{ record }">
  11. <CustomCell :record="record" fieldName="device_name" :showIcon="true" />
  12. </template>
  13. <!-- 设备SN -->
  14. <template #device_sn="{ record }">
  15. <CustomCell :record="record" fieldName="device_sn" />
  16. </template>
  17. <!-- 设备名称 -->
  18. <template #nickname="{ record }">
  19. <CustomCell :record="record" fieldName="nickname" :isEdit="!!state.editableData[record.device_sn]" />
  20. </template>
  21. <!-- 固件版本 -->
  22. <template #firmware_version="{ record }">
  23. <CustomCell :record="record" fieldName="firmware_version" />
  24. </template>
  25. <!-- 固件升级 -->
  26. <template #firmware_status="{ record }">
  27. <DeviceFirmwareUpgrade :device="record" />
  28. </template>
  29. <!-- 当前状态 -->
  30. <template #status="{ record }">
  31. <CustomCell :record="record" fieldName="status_text" />
  32. </template>
  33. <!-- 加入项目时间 -->
  34. <template #bound_time="{ record }">
  35. <CustomCell :record="record" fieldName="bound_time" />
  36. </template>
  37. <!-- 最后在线时间 -->
  38. <template #login_time="{ record }">
  39. <CustomCell :record="record" fieldName="login_time" />
  40. </template>
  41. <!-- 操作 -->
  42. <template #action="{ record }">
  43. <!-- 编辑态操作 -->
  44. <div v-if="state.editableData[record.device_sn]">
  45. <a-tooltip title="确定">
  46. <CheckOutlined style="color: #28d445;margin-right: 10px;" @click="onClickSave(record)" />
  47. </a-tooltip>
  48. <a-tooltip title="取消">
  49. <CloseOutlined style="color: #e70102;" @click="() => delete state.editableData[record.device_sn]" />
  50. </a-tooltip>
  51. </div>
  52. <!-- 非编辑态操作 -->
  53. <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
  54. <a-tooltip title="编辑">
  55. <EditOutlined style="margin-right: 10px;" @click="onClickEdit(record)" />
  56. </a-tooltip>
  57. <a-tooltip title="删除">
  58. <DeleteOutlined @click="onClickDelete(record)" />
  59. </a-tooltip>
  60. <a-dropdown v-if="record.domain === 3">
  61. <EllipsisOutlined style="font-size: 20px;margin-left: 10px;" />
  62. <template #overlay>
  63. <a-menu>
  64. <a-menu-item @click="onClickFeedback(record)">
  65. 异常反馈
  66. </a-menu-item>
  67. <a-menu-item @click="onClickDeviceHms(record)">
  68. 告警信息
  69. </a-menu-item>
  70. <!-- <a-menu-item>
  71. 设备运维
  72. </a-menu-item> -->
  73. </a-menu>
  74. </template>
  75. </a-dropdown>
  76. </div>
  77. </template>
  78. </a-table>
  79. </div>
  80. </div>
  81. <!-- 异常反馈 -->
  82. <FeedbackDrawer :visible="state.feedbackDrawerVisible" :device="state.currentDevice"
  83. :onClose="() => state.feedbackDrawerVisible = false" v-if="state.feedbackDrawerVisible" />
  84. <!-- 告警信息 -->
  85. <DeviceHmsDrawer v-model:visible="state.deviceHmsDrawerVisible" :device="state.currentDevice" />
  86. </template>
  87. <script lang="ts" setup>
  88. import { reactive, onMounted, onUnmounted } from 'vue';
  89. import { Modal } from 'ant-design-vue';
  90. import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue';
  91. import Search from './components/Search.vue';
  92. import CustomCell from './components/CustomCell.vue';
  93. import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue';
  94. import FeedbackDrawer from './components/FeedbackDrawer.vue';
  95. import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue';
  96. import { getBindingDevices, updateDevice, unbindDevice } from '/@/api/manage';
  97. import { apis } from '/@/api/custom';
  98. import { getWorkspaceId } from '/@/utils/index';
  99. interface State {
  100. workspaceId: string,
  101. interval: number | null,
  102. query: any,
  103. listLoading: boolean,
  104. list: any[],
  105. selectedRowKeys: string[],
  106. currentDevice: any,
  107. editableData: {
  108. [key: string]: any,
  109. },
  110. feedbackDrawerVisible: boolean,
  111. deviceHmsDrawerVisible: boolean,
  112. };
  113. const state: State = reactive({
  114. workspaceId: getWorkspaceId(),
  115. interval: null,
  116. query: undefined,
  117. listLoading: false,
  118. list: [],
  119. selectedRowKeys: [],
  120. currentDevice: {},
  121. editableData: {},
  122. feedbackDrawerVisible: false,
  123. deviceHmsDrawerVisible: false,
  124. })
  125. const paginationConfig = reactive({
  126. pageSizeOptions: ['20', '50', '100'],
  127. showQuickJumper: true,
  128. showSizeChanger: true,
  129. pageSize: 20,
  130. current: 1,
  131. total: 0
  132. })
  133. const fetchList = async () => {
  134. state.listLoading = true;
  135. try {
  136. const res = await getBindingDevices(state.workspaceId, {
  137. ...state.query,
  138. page: paginationConfig.current,
  139. page_size: paginationConfig.pageSize,
  140. });
  141. state.list = res.data.list;
  142. paginationConfig.total = res.data.pagination.total;
  143. paginationConfig.current = res.data.pagination.page;
  144. paginationConfig.pageSize = res.data.pagination.page_size;
  145. await autoUpdateDeviceStatus()
  146. } catch (e) {
  147. console.error(e);
  148. } finally {
  149. state.listLoading = false;
  150. }
  151. }
  152. // 自动更新设备状态
  153. const autoUpdateDeviceStatus = async () => {
  154. if (state.list.length === 0) {
  155. return;
  156. }
  157. const snList: string[] = [];
  158. state.list.forEach(item => {
  159. if (item.children) {
  160. snList.push(item.children.device_sn);
  161. }
  162. snList.push(item.device_sn);
  163. })
  164. const data = {
  165. snList: snList.join(','),
  166. }
  167. const res = await apis.fetchDeviceStatus(data);
  168. const deviceStatusMap = new Map();
  169. res.data.forEach((item: any) => {
  170. deviceStatusMap.set(item.device_sn, item.status_text);
  171. });
  172. state.list.forEach(item => {
  173. if (item.children) {
  174. item.children.status_text = deviceStatusMap.get(item.children.device_sn);
  175. }
  176. item.status_text = deviceStatusMap.get(item.device_sn);
  177. })
  178. }
  179. // 开始定时器
  180. const startAutoUpdate = () => {
  181. state.interval = window.setInterval(autoUpdateDeviceStatus, 10000);
  182. };
  183. // 清除定时器
  184. const clearAutoUpdate = () => {
  185. if (state.interval !== null) {
  186. clearInterval(state.interval);
  187. state.interval = null;
  188. }
  189. };
  190. onMounted(async () => {
  191. await fetchList();
  192. startAutoUpdate(); // 页面加载完成后启动定时器
  193. });
  194. onUnmounted(() => {
  195. clearAutoUpdate(); // 页面卸载前清除定时器
  196. });
  197. const columns = [
  198. {
  199. title: '设备型号',
  200. dataIndex: 'device_name',
  201. width: 150,
  202. ellipsis: true,
  203. sorter: (a: any, b: any) => a.device_name.localeCompare(b.device_name),
  204. slots: { customRender: 'device_name' }
  205. },
  206. {
  207. title: '设备SN',
  208. dataIndex: 'device_sn',
  209. width: 250,
  210. ellipsis: true,
  211. slots: { customRender: 'device_sn' }
  212. },
  213. {
  214. title: '设备名称',
  215. dataIndex: 'nickname',
  216. width: 150,
  217. ellipsis: true,
  218. sorter: (a: any, b: any) => a.nickname.localeCompare(b.nickname),
  219. slots: { customRender: 'nickname' }
  220. },
  221. {
  222. title: '固件版本',
  223. dataIndex: 'firmware_version',
  224. width: 150,
  225. ellipsis: true,
  226. slots: { customRender: 'firmware_version' },
  227. },
  228. {
  229. title: '固件升级',
  230. dataIndex: 'firmware_status',
  231. width: 150,
  232. ellipsis: true,
  233. slots: { customRender: 'firmware_status' },
  234. },
  235. {
  236. title: '当前状态',
  237. dataIndex: 'status',
  238. width: 100,
  239. ellipsis: true,
  240. slots: { customRender: 'status' }
  241. },
  242. {
  243. title: '加入项目时间',
  244. dataIndex: 'bound_time',
  245. width: 200,
  246. sorter: (a: any, b: any) => a.bound_time.localeCompare(b.bound_time),
  247. slots: { customRender: 'bound_time' },
  248. },
  249. {
  250. title: '最后在线时间',
  251. dataIndex: 'login_time',
  252. width: 200,
  253. sorter: (a: any, b: any) => a.login_time.localeCompare(b.login_time),
  254. slots: { customRender: 'login_time' },
  255. },
  256. {
  257. title: '操作',
  258. dataIndex: 'actions',
  259. fixed: 'right',
  260. width: 100,
  261. slots: { customRender: 'action' },
  262. },
  263. ]
  264. const rowClassName = (record: any, index: number) => {
  265. const className = []
  266. if ((index & 1) === 0) {
  267. className.push('table-striped')
  268. }
  269. return className.toString().replaceAll(',', ' ')
  270. }
  271. const refreshData = async (page: any) => {
  272. paginationConfig.current = page?.current!
  273. paginationConfig.pageSize = page?.pageSize!
  274. await fetchList();
  275. }
  276. const rowSelection = {
  277. onChange: (selectedRowKeys: string[]) => {
  278. state.selectedRowKeys = selectedRowKeys;
  279. },
  280. }
  281. // 点击批量删除
  282. const onClickBatchDelete = async () => {
  283. console.log(state.selectedRowKeys, '点击批量删除');
  284. }
  285. // 点击搜索
  286. const onClickSearch = async (query: any) => {
  287. state.query = query;
  288. await fetchList();
  289. }
  290. // 点击重置
  291. const onClickReset = async (query: any) => {
  292. state.query = query;
  293. await fetchList();
  294. }
  295. // 点击异常反馈
  296. const onClickFeedback = (record: any) => {
  297. state.feedbackDrawerVisible = true;
  298. state.currentDevice = record;
  299. }
  300. // 点击告警信息
  301. const onClickDeviceHms = (record: any) => {
  302. state.deviceHmsDrawerVisible = true;
  303. state.currentDevice = record;
  304. }
  305. // 点击编辑
  306. const onClickEdit = (record: any) => {
  307. state.editableData[record.device_sn] = record;
  308. }
  309. // 点击保存
  310. const onClickSave = async (record: any) => {
  311. delete state.editableData[record.device_sn];
  312. await updateDevice({ nickname: record.nickname }, state.workspaceId, record.device_sn)
  313. if (record.children) {
  314. await updateDevice({ nickname: record.children.nickname }, state.workspaceId, record.children.device_sn)
  315. }
  316. }
  317. // 点击删除
  318. const onClickDelete = (record: any) => {
  319. Modal.confirm({
  320. title: '提示',
  321. content: `确定删除${record.device_name}吗?`,
  322. onOk: async () => {
  323. const res = await unbindDevice(record.device_sn);
  324. if (res.code !== 0) {
  325. return
  326. }
  327. await fetchList();
  328. },
  329. });
  330. }
  331. </script>
  332. <style lang="scss">
  333. .deviceList {
  334. padding: 20px;
  335. }
  336. .ant-table {
  337. border-top: 1px solid rgb(0, 0, 0, 0.06);
  338. border-bottom: 1px solid rgb(0, 0, 0, 0.06);
  339. }
  340. .ant-table-tbody tr td {
  341. border: 0;
  342. }
  343. .table-striped {
  344. background-color: #f7f9fa;
  345. }
  346. </style>