index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <div class="deviceList">
  3. <Search :onClickSearch="async () => { }" :onClickReset="async () => { }" />
  4. <div class="deviceList-table">
  5. <a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData"
  6. row-key="device_sn" :expandedRowKeys="expandRows" :row-selection="rowSelection" :rowClassName="rowClassName"
  7. :scroll="{ x: '100%', y: 500 }" :expandIcon="expandIcon" :loading="loading">
  8. <template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col">
  9. <div>
  10. <a-input v-if="editableData[record.device_sn]" v-model:value="editableData[record.device_sn][col]" />
  11. <template v-else>
  12. <a-tooltip :title="text">
  13. {{ text }}
  14. </a-tooltip>
  15. </template>
  16. </div>
  17. </template>
  18. <template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col">
  19. <a-tooltip :title="text">
  20. <span>{{ text }}</span>
  21. </a-tooltip>
  22. </template>
  23. <!-- 固件版本 -->
  24. <template #firmware_version="{ record }">
  25. <span v-if="record.domin === 3">
  26. <DeviceFirmwareUpgrade :device="record" class="table-flex-col" @device-upgrade="onDeviceUpgrade" />
  27. </span>
  28. <span v-else>
  29. {{ record.firmware_version }}
  30. </span>
  31. </template>
  32. <!-- 固件升级 -->
  33. <template #firmware_status="{ text }">
  34. <div v-if="text === -1">
  35. 不支持
  36. </div>
  37. <div v-else>
  38. {{ DeviceFirmwareStatus[text] }}
  39. </div>
  40. </template>
  41. <!-- 状态 -->
  42. <template #status="{ text }">
  43. <span v-if="text" class="flex-row flex-align-center">
  44. <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
  45. <span>在线</span>
  46. </span>
  47. <span class="flex-row flex-align-center" v-else>
  48. <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
  49. <span>离线</span>
  50. </span>
  51. </template>
  52. <!-- 操作 -->
  53. <template #action="{ record }">
  54. <!-- 编辑态操作 -->
  55. <div v-if="editableData[record.device_sn]">
  56. <a-tooltip title="确定">
  57. <CheckOutlined style="color: #28d445;margin-right: 10px;" @click="save(record)" />
  58. </a-tooltip>
  59. <a-tooltip title="取消">
  60. <CloseOutlined style="color: #e70102;" @click="() => delete editableData[record.device_sn]" />
  61. </a-tooltip>
  62. </div>
  63. <!-- 非编辑态操作 -->
  64. <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
  65. <a-tooltip title="设备日志">
  66. <CloudServerOutlined style="margin-right: 10px;" @click="showDeviceLogUploadRecord(record)" />
  67. </a-tooltip>
  68. <a-tooltip title="告警信息">
  69. <FileSearchOutlined style="margin-right: 10px;" @click="showHms(record)" />
  70. </a-tooltip>
  71. <a-tooltip title="编辑">
  72. <EditOutlined style="margin-right: 10px;" @click="edit(record)" />
  73. </a-tooltip>
  74. <a-tooltip title="删除">
  75. <DeleteOutlined @click="onClickDelete(record)" />
  76. </a-tooltip>
  77. </div>
  78. </template>
  79. </a-table>
  80. <!-- 设备升级 -->
  81. <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible"
  82. :device="selectedDevice" @ok="onUpgradeDeviceOk" />
  83. <!-- 设备日志上传记录 -->
  84. <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice" />
  85. <!-- hms 信息 -->
  86. <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice" />
  87. </div>
  88. </div>
  89. </template>
  90. <script lang="ts" setup>
  91. import { h, onMounted, reactive, ref, UnwrapRef } from 'vue'
  92. import { Modal, notification } from 'ant-design-vue'
  93. import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue'
  94. import Search from './components/Search.vue'
  95. import { getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'
  96. import { Device, DeviceFirmwareStatus, DeviceFirmwareStatusEnum } from '/@/types/device'
  97. import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue'
  98. import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue'
  99. import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade'
  100. import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event'
  101. import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
  102. import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
  103. import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
  104. import { IPage } from '/@/api/http/type'
  105. import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
  106. const loading = ref(true)
  107. const columns = [
  108. {
  109. title: '设备型号',
  110. dataIndex: 'device_name',
  111. width: 150,
  112. ellipsis: true,
  113. sorter: (a: any, b: any) => a.device_name.localeCompare(b.device_name),
  114. },
  115. {
  116. title: '设备SN',
  117. dataIndex: 'device_sn',
  118. width: 200,
  119. ellipsis: true,
  120. slots: { customRender: 'sn' }
  121. },
  122. {
  123. title: '设备名称',
  124. dataIndex: 'nickname',
  125. width: 150,
  126. ellipsis: true,
  127. sorter: (a: any, b: any) => a.nickname.localeCompare(b.nickname),
  128. slots: { customRender: 'nickname' }
  129. },
  130. {
  131. title: '固件版本',
  132. dataIndex: 'firmware_version',
  133. width: 150,
  134. ellipsis: true,
  135. slots: { customRender: 'firmware_version' },
  136. },
  137. {
  138. title: '固件升级',
  139. dataIndex: 'firmware_status',
  140. width: 150,
  141. ellipsis: true,
  142. slots: { customRender: 'firmware_status' },
  143. },
  144. {
  145. title: '当前状态',
  146. dataIndex: 'status',
  147. width: 100,
  148. ellipsis: true,
  149. slots: { customRender: 'status' }
  150. },
  151. {
  152. title: '加入项目时间',
  153. dataIndex: 'bound_time',
  154. width: 200,
  155. sorter: (a: any, b: any) => a.bound_time.localeCompare(b.bound_time),
  156. },
  157. {
  158. title: '最后在线时间',
  159. dataIndex: 'login_time',
  160. width: 200,
  161. sorter: (a: any, b: any) => a.login_time.localeCompare(b.login_time),
  162. },
  163. {
  164. title: '操作',
  165. dataIndex: 'actions',
  166. fixed: 'right',
  167. width: 150,
  168. slots: { customRender: 'action' },
  169. },
  170. ]
  171. const expandIcon = (props: any) => {
  172. if (!props.expanded) {
  173. return h('div',
  174. {
  175. style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;',
  176. class: 'mt-5 ml0',
  177. })
  178. }
  179. }
  180. const rowClassName = (record: any, index: number) => {
  181. const className = []
  182. if ((index & 1) === 0) {
  183. className.push('table-striped')
  184. }
  185. return className.toString().replaceAll(',', ' ')
  186. }
  187. const expandRows = ref<string[]>([])
  188. const data: {
  189. device: Device[]
  190. } = reactive({
  191. device: []
  192. })
  193. const paginationProp = reactive({
  194. pageSizeOptions: ['20', '50', '100'],
  195. showQuickJumper: true,
  196. showSizeChanger: true,
  197. pageSize: 20,
  198. current: 1,
  199. total: 0
  200. })
  201. // 获取分页信息
  202. function getPaginationBody() {
  203. return {
  204. page: paginationProp.current,
  205. page_size: paginationProp.pageSize
  206. } as IPage
  207. }
  208. const rowSelection = {
  209. onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {
  210. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
  211. },
  212. onSelect: (record: any, selected: boolean, selectedRows: []) => {
  213. console.log(record, selected, selectedRows)
  214. },
  215. onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {
  216. console.log(selected, selectedRows, changeRows)
  217. },
  218. getCheckboxProps: (record: any) => ({
  219. disabled: record.domain !== EDeviceTypeName.Dock,
  220. }),
  221. }
  222. const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
  223. const editableData: UnwrapRef<Record<string, Device>> = reactive({})
  224. // 设备升级
  225. const {
  226. deviceFirmwareUpgradeModalVisible,
  227. selectedDevice,
  228. onDeviceUpgrade,
  229. onUpgradeDeviceOk
  230. } = useDeviceFirmwareUpgrade(workspaceId)
  231. function onDeviceUpgradeWs(payload: DeviceCmdExecuteInfo) {
  232. updateDevicesByWs(data.device, payload)
  233. }
  234. function updateDevicesByWs(devices: Device[], payload: DeviceCmdExecuteInfo) {
  235. if (!devices || devices.length <= 0) {
  236. return
  237. }
  238. for (let i = 0; i < devices.length; i++) {
  239. if (devices[i].device_sn === payload.sn) {
  240. if (!payload.output) return
  241. const { status, progress, ext } = payload.output
  242. if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中
  243. const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''
  244. devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade
  245. devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate
  246. } else { // 终态:成功,失败,超时
  247. if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {
  248. notification.error({
  249. message: `(${payload.sn}) Upgrade failed`,
  250. description: `Error Code: ${payload.result}`,
  251. duration: null
  252. })
  253. }
  254. // 拉取列表
  255. getDevices(true)
  256. }
  257. return
  258. }
  259. if (devices[i].children) {
  260. updateDevicesByWs(devices[i].children || [], payload)
  261. }
  262. }
  263. }
  264. useDeviceUpgradeEvent(onDeviceUpgradeWs)
  265. // 获取设备列表信息
  266. function getDevices(closeLoading?: boolean) {
  267. if (!closeLoading) {
  268. loading.value = true
  269. }
  270. getBindingDevices(workspaceId, getPaginationBody()).then(res => {
  271. if (res.code !== 0) {
  272. return
  273. }
  274. const resData: Device[] = res.data.list
  275. expandRows.value = []
  276. resData.forEach((val: any) => {
  277. if (val.children) {
  278. val.children = [val.children]
  279. expandRows.value.push(val.device_sn);
  280. }
  281. })
  282. data.device = resData;
  283. paginationProp.total = res.data.pagination.total
  284. paginationProp.current = res.data.pagination.page
  285. paginationProp.pageSize = res.data.pagination.page_size
  286. loading.value = false
  287. })
  288. }
  289. function refreshData(page: any) {
  290. paginationProp.current = page?.current!
  291. paginationProp.pageSize = page?.pageSize!
  292. getDevices()
  293. }
  294. // 编辑
  295. function edit(record: Device) {
  296. editableData[record.device_sn] = record
  297. }
  298. // 保存
  299. function save(record: Device) {
  300. delete editableData[record.device_sn]
  301. updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)
  302. }
  303. // 点击删除
  304. const onClickDelete = (record: Device) => {
  305. Modal.confirm({
  306. title: '提示',
  307. content: `确定删除${record.device_name}吗?`,
  308. onOk: async () => {
  309. unbindDevice(record.device_sn).then(res => {
  310. if (res.code !== 0) {
  311. return
  312. }
  313. getDevices()
  314. })
  315. },
  316. });
  317. }
  318. const currentDevice = ref({} as Device)
  319. // 设备日志
  320. const deviceLogUploadRecordVisible = ref(false)
  321. function showDeviceLogUploadRecord(dock: Device) {
  322. deviceLogUploadRecordVisible.value = true
  323. currentDevice.value = dock
  324. }
  325. // 健康状态
  326. const hmsVisible = ref<boolean>(false)
  327. function showHms(dock: Device) {
  328. hmsVisible.value = true
  329. currentDevice.value = dock
  330. }
  331. onMounted(() => {
  332. getDevices()
  333. })
  334. </script>
  335. <style lang="scss">
  336. .deviceList {
  337. padding: 20px;
  338. }
  339. .ant-table {
  340. border-top: 1px solid rgb(0, 0, 0, 0.06);
  341. border-bottom: 1px solid rgb(0, 0, 0, 0.06);
  342. }
  343. .ant-table-tbody tr td {
  344. border: 0;
  345. }
  346. .table-striped {
  347. background-color: #f7f9fa;
  348. }
  349. </style>