index.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. <template>
  2. <div
  3. class="device-page grid grid-cols-[auto_1fr] grid-rows-[auto_auto_minmax(0,1fr)] gap-3 gap-x-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
  4. <!-- 左侧部门树 -->
  5. <DeptTreeSelect
  6. class="row-span-3"
  7. :top-id="rootDeptId"
  8. :deptId="deptId"
  9. v-model="queryParams.deptId"
  10. :init-select="false"
  11. :show-title="false"
  12. request-api="getSimpleDeptList"
  13. @node-click="handleDeptNodeClick" />
  14. <!-- 搜索工作栏 -->
  15. <el-form
  16. ref="queryFormRef"
  17. :model="queryParams"
  18. size="default"
  19. label-width="68px"
  20. class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-2 flex items-center flex-wrap min-w-0">
  21. <div class="flex items-center gap-4 flex-wrap">
  22. <el-form-item :label="t('iotDevice.yfCode')" prop="yfDeviceCode">
  23. <el-input
  24. v-model="queryParams.yfDeviceCode"
  25. :placeholder="t('iotDevice.yfCodeHolder')"
  26. clearable
  27. @keyup.enter="handleQuery"
  28. class="!w-200px" />
  29. </el-form-item>
  30. <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
  31. <el-input
  32. v-model="queryParams.deviceCode"
  33. :placeholder="t('iotDevice.codeHolder')"
  34. clearable
  35. @keyup.enter="handleQuery"
  36. class="!w-200px" />
  37. </el-form-item>
  38. <el-form-item :label="t('iotDevice.name')" prop="deviceName">
  39. <el-input
  40. v-model="queryParams.deviceName"
  41. :placeholder="t('iotDevice.nameHolder')"
  42. clearable
  43. @keyup.enter="handleQuery"
  44. class="!w-200px" />
  45. </el-form-item>
  46. <el-form-item label="车牌号" prop="carNo">
  47. <el-input
  48. v-model="queryParams.carNo"
  49. placeholder="请输入车牌号"
  50. clearable
  51. @keyup.enter="handleQuery"
  52. class="!w-200px" />
  53. </el-form-item>
  54. <el-form-item label="设备号" prop="deviceNo">
  55. <el-input
  56. v-model="queryParams.deviceNo"
  57. placeholder="请输入设备号"
  58. clearable
  59. @keyup.enter="handleQuery"
  60. class="!w-200px" />
  61. </el-form-item>
  62. <el-form-item :label="t('iotDevice.brand')" prop="brandName">
  63. <el-input
  64. v-model="queryParams.brandName"
  65. :placeholder="t('iotDevice.brandHolder')"
  66. clearable
  67. @keyup.enter="handleQuery"
  68. class="!w-200px" />
  69. </el-form-item>
  70. <el-form-item v-show="ifShow" :label="t('iotDevice.status')" prop="deviceStatus">
  71. <el-select
  72. v-model="queryParams.deviceStatus"
  73. :placeholder="t('iotDevice.statusHolder')"
  74. clearable
  75. class="!w-200px">
  76. <el-option
  77. v-for="dict in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
  78. :key="dict.value"
  79. :label="dict.label"
  80. :value="dict.value" />
  81. </el-select>
  82. </el-form-item>
  83. <el-form-item v-show="ifShow" :label="t('iotDevice.assets')" prop="assetProperty">
  84. <el-select
  85. v-model="queryParams.assetProperty"
  86. :placeholder="t('iotDevice.assetsHolder')"
  87. clearable
  88. class="!w-200px">
  89. <el-option
  90. v-for="dict in getStrDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY)"
  91. :key="dict.value"
  92. :label="dict.label"
  93. :value="dict.value" />
  94. </el-select>
  95. </el-form-item>
  96. <el-form-item
  97. v-show="ifShow"
  98. :label="t('deviceForm.category')"
  99. prop="assetClass"
  100. style="width: 15vw">
  101. <el-tree-select
  102. v-model="queryParams.assetClass"
  103. :data="productClassifyList"
  104. :props="defaultProps"
  105. class="!w-200px"
  106. check-strictly
  107. node-key="id"
  108. :placeholder="t('deviceForm.categoryHolder')"
  109. filterable />
  110. </el-form-item>
  111. <el-form-item>
  112. <el-button v-if="!ifShow" @click="moreQuery(true)" type="warning">
  113. <Icon icon="ep:search" class="mr-5px" /> {{ t('iotDevice.moreSearch') }}
  114. </el-button>
  115. <el-button v-if="ifShow" @click="moreQuery(false)" type="danger">
  116. <Icon icon="ep:search" class="mr-5px" /> {{ t('iotDevice.closeSearch') }}
  117. </el-button>
  118. <el-button @click="handleQuery">
  119. <Icon icon="ep:search" class="mr-5px" />{{ t('operationFill.search') }}
  120. </el-button>
  121. <el-button @click="resetQuery">
  122. <Icon icon="ep:refresh" class="mr-5px" /> {{ t('operationFill.reset') }}
  123. </el-button>
  124. <el-button
  125. type="primary"
  126. plain
  127. @click="openForm('create', undefined, queryParams.deptId)"
  128. v-hasPermi="['rq:iot-device:create']">
  129. <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
  130. </el-button>
  131. <el-button
  132. type="success"
  133. plain
  134. @click="handleExport"
  135. :loading="exportLoading"
  136. v-hasPermi="['rq:iot-device:export']">
  137. <Icon icon="ep:download" class="mr-5px" /> 导出
  138. </el-button>
  139. </el-form-item>
  140. </div>
  141. </el-form>
  142. <div class="device-dashboard-grid min-w-0">
  143. <DeviceTotalCard :total="dashboardTotal" :loading="dashboardLoading" />
  144. <DeviceStatusCard :data="dashboardStatus" :loading="dashboardLoading" />
  145. <DeviceClassifyTopCard :data="dashboardClassify" :loading="dashboardLoading" />
  146. </div>
  147. <!-- 列表 -->
  148. <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-2 pt-3 min-w-0">
  149. <div class="flex-1 relative min-h-0">
  150. <el-auto-resizer class="absolute">
  151. <template #default="{ width, height }">
  152. <zm-table
  153. :loading="loading"
  154. :data="list"
  155. :width="width"
  156. :height="height"
  157. :max-height="height"
  158. show-border>
  159. <zm-table-column
  160. :label="t('iotDevice.serial')"
  161. width="70"
  162. align="center"
  163. fixed="left"
  164. hide-in-column-settings>
  165. <template #default="scope">
  166. {{ scope.$index + 1 }}
  167. </template>
  168. </zm-table-column>
  169. <zm-table-column
  170. :label="t('iotDevice.yfCode')"
  171. align="center"
  172. prop="yfDeviceCode"
  173. width="150"
  174. fixed="left"
  175. zm-sortable
  176. zm-filterable
  177. :zm-sort-method="handleZmSortChange"
  178. v-model:filter-model-value="queryParams.yfDeviceCode">
  179. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  180. <div class="p-2">
  181. <el-input
  182. :model-value="filterModelValue"
  183. placeholder="请输入油服编码"
  184. clearable
  185. @update:model-value="updateFilterModelValue"
  186. @keyup.enter="() => confirmColumnFilter(close)" />
  187. <div class="mt-2 flex justify-end gap-2">
  188. <el-button
  189. size="small"
  190. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  191. 清空
  192. </el-button>
  193. <el-button
  194. size="small"
  195. type="primary"
  196. :icon="Search"
  197. @click="() => confirmColumnFilter(close)">
  198. 搜索
  199. </el-button>
  200. </div>
  201. </div>
  202. </template>
  203. </zm-table-column>
  204. <zm-table-column
  205. :label="t('iotDevice.code')"
  206. align="center"
  207. prop="deviceCode"
  208. width="150"
  209. fixed="left"
  210. zm-sortable
  211. zm-filterable
  212. :zm-sort-method="handleZmSortChange"
  213. v-model:filter-model-value="queryParams.deviceCode">
  214. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  215. <div class="p-2">
  216. <el-input
  217. :model-value="filterModelValue"
  218. placeholder="请输入历史编码"
  219. clearable
  220. @update:model-value="updateFilterModelValue"
  221. @keyup.enter="() => confirmColumnFilter(close)" />
  222. <div class="mt-2 flex justify-end gap-2">
  223. <el-button
  224. size="small"
  225. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  226. 清空
  227. </el-button>
  228. <el-button
  229. size="small"
  230. type="primary"
  231. :icon="Search"
  232. @click="() => confirmColumnFilter(close)">
  233. 搜索
  234. </el-button>
  235. </div>
  236. </div>
  237. </template>
  238. </zm-table-column>
  239. <zm-table-column
  240. :label="t('iotDevice.name')"
  241. align="center"
  242. prop="deviceName"
  243. min-width="280"
  244. zm-sortable
  245. zm-filterable
  246. :zm-sort-method="handleZmSortChange"
  247. v-model:filter-model-value="queryParams.deviceName">
  248. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  249. <div class="p-2">
  250. <el-input
  251. :model-value="filterModelValue"
  252. placeholder="请输入设备名称"
  253. clearable
  254. @update:model-value="updateFilterModelValue"
  255. @keyup.enter="() => confirmColumnFilter(close)" />
  256. <div class="mt-2 flex justify-end gap-2">
  257. <el-button
  258. size="small"
  259. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  260. 清空
  261. </el-button>
  262. <el-button
  263. size="small"
  264. type="primary"
  265. :icon="Search"
  266. @click="() => confirmColumnFilter(close)">
  267. 搜索
  268. </el-button>
  269. </div>
  270. </div>
  271. </template>
  272. <template #default="scope">
  273. <el-link :underline="false" type="primary" @click="handleDetail(scope.row.id)">
  274. {{ scope.row.deviceName }}
  275. </el-link>
  276. </template>
  277. </zm-table-column>
  278. <zm-table-column
  279. label="设备号"
  280. align="center"
  281. prop="deviceNo"
  282. width="120"
  283. zm-sortable
  284. zm-filterable
  285. :zm-sort-method="handleZmSortChange"
  286. v-model:filter-model-value="queryParams.deviceNo">
  287. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  288. <div class="p-2">
  289. <el-input
  290. :model-value="filterModelValue"
  291. placeholder="请输入设备号"
  292. clearable
  293. @update:model-value="updateFilterModelValue"
  294. @keyup.enter="() => confirmColumnFilter(close)" />
  295. <div class="mt-2 flex justify-end gap-2">
  296. <el-button
  297. size="small"
  298. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  299. 清空
  300. </el-button>
  301. <el-button
  302. size="small"
  303. type="primary"
  304. :icon="Search"
  305. @click="() => confirmColumnFilter(close)">
  306. 搜索
  307. </el-button>
  308. </div>
  309. </div>
  310. </template>
  311. </zm-table-column>
  312. <zm-table-column
  313. :label="t('iotDevice.dept')"
  314. align="center"
  315. prop="deptName"
  316. min-width="150"
  317. zm-filterable
  318. v-model:filter-model-value="queryParams.deptId">
  319. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  320. <div class="p-2">
  321. <el-tree-select
  322. :model-value="filterModelValue"
  323. :data="deptList"
  324. :props="defaultProps"
  325. check-strictly
  326. node-key="id"
  327. filterable
  328. placeholder="请选择所在部门"
  329. class="w-full"
  330. @update:model-value="updateFilterModelValue" />
  331. <div class="mt-2 flex justify-end gap-2">
  332. <el-button
  333. size="small"
  334. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  335. 清空
  336. </el-button>
  337. <el-button
  338. size="small"
  339. type="primary"
  340. :icon="Search"
  341. @click="() => confirmColumnFilter(close)">
  342. 搜索
  343. </el-button>
  344. </div>
  345. </div>
  346. </template>
  347. </zm-table-column>
  348. <zm-table-column
  349. :label="t('iotDevice.status')"
  350. align="center"
  351. prop="deviceStatus"
  352. min-width="150"
  353. zm-filterable
  354. v-model:filter-model-value="queryParams.deviceStatus">
  355. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  356. <div class="p-2">
  357. <el-select
  358. :model-value="filterModelValue"
  359. placeholder="请选择设备状态"
  360. clearable
  361. class="w-full"
  362. @update:model-value="updateFilterModelValue">
  363. <el-option
  364. v-for="item in getStrDictOptions(DICT_TYPE.PMS_DEVICE_STATUS)"
  365. :key="item.value"
  366. :label="item.label"
  367. :value="item.value" />
  368. </el-select>
  369. <div class="mt-2 flex justify-end gap-2">
  370. <el-button
  371. size="small"
  372. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  373. 清空
  374. </el-button>
  375. <el-button
  376. size="small"
  377. type="primary"
  378. :icon="Search"
  379. @click="() => confirmColumnFilter(close)">
  380. 搜索
  381. </el-button>
  382. </div>
  383. </div>
  384. </template>
  385. <template #default="scope">
  386. <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
  387. </template>
  388. </zm-table-column>
  389. <zm-table-column
  390. :label="t('deviceForm.model')"
  391. align="center"
  392. prop="model"
  393. min-width="170"
  394. zm-filterable
  395. v-model:filter-model-value="queryParams.model">
  396. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  397. <div class="p-2">
  398. <el-input
  399. :model-value="filterModelValue"
  400. placeholder="请输入规格型号"
  401. clearable
  402. @update:model-value="updateFilterModelValue"
  403. @keyup.enter="() => confirmColumnFilter(close)" />
  404. <div class="mt-2 flex justify-end gap-2">
  405. <el-button
  406. size="small"
  407. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  408. 清空
  409. </el-button>
  410. <el-button
  411. size="small"
  412. type="primary"
  413. :icon="Search"
  414. @click="() => confirmColumnFilter(close)">
  415. 搜索
  416. </el-button>
  417. </div>
  418. </div>
  419. </template>
  420. </zm-table-column>
  421. <zm-table-column
  422. align="center"
  423. :label="t('deviceForm.mfg')"
  424. prop="manufacturer"
  425. zm-filterable
  426. v-model:filter-model-value="queryParams.manufacturerId">
  427. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  428. <div class="p-2">
  429. <el-select
  430. :model-value="filterModelValue"
  431. filterable
  432. :teleported="false"
  433. :loading="brandList.length === 0"
  434. loading-text="数据加载中..."
  435. placeholder="请选择制造厂家"
  436. class="w-full"
  437. @update:model-value="updateFilterModelValue"
  438. @visible-change="handleBrandChange">
  439. <el-option
  440. v-for="item in brandList"
  441. :key="item.id"
  442. :label="item.name"
  443. :value="item.id" />
  444. </el-select>
  445. <div class="mt-2 flex justify-end gap-2">
  446. <el-button
  447. size="small"
  448. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  449. 清空
  450. </el-button>
  451. <el-button
  452. size="small"
  453. type="primary"
  454. :icon="Search"
  455. @click="() => confirmColumnFilter(close)">
  456. 搜索
  457. </el-button>
  458. </div>
  459. </div>
  460. </template>
  461. </zm-table-column>
  462. <zm-table-column
  463. label="车牌号"
  464. align="center"
  465. prop="carNo"
  466. min-width="170"
  467. zm-filterable
  468. v-model:filter-model-value="queryParams.carNo">
  469. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  470. <div class="p-2">
  471. <el-input
  472. :model-value="filterModelValue"
  473. placeholder="请输入车牌号"
  474. clearable
  475. @update:model-value="updateFilterModelValue"
  476. @keyup.enter="() => confirmColumnFilter(close)" />
  477. <div class="mt-2 flex justify-end gap-2">
  478. <el-button
  479. size="small"
  480. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  481. 清空
  482. </el-button>
  483. <el-button
  484. size="small"
  485. type="primary"
  486. :icon="Search"
  487. @click="() => confirmColumnFilter(close)">
  488. 搜索
  489. </el-button>
  490. </div>
  491. </div>
  492. </template>
  493. </zm-table-column>
  494. <zm-table-column
  495. :label="t('deviceForm.brand')"
  496. align="center"
  497. prop="brandName"
  498. min-width="150"
  499. zm-filterable
  500. v-model:filter-model-value="queryParams.brandName">
  501. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  502. <div class="p-2">
  503. <el-input
  504. :model-value="filterModelValue"
  505. placeholder="请输入品牌"
  506. clearable
  507. @update:model-value="updateFilterModelValue"
  508. @keyup.enter="() => confirmColumnFilter(close)" />
  509. <div class="mt-2 flex justify-end gap-2">
  510. <el-button
  511. size="small"
  512. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  513. 清空
  514. </el-button>
  515. <el-button
  516. size="small"
  517. type="primary"
  518. :icon="Search"
  519. @click="() => confirmColumnFilter(close)">
  520. 搜索
  521. </el-button>
  522. </div>
  523. </div>
  524. </template>
  525. </zm-table-column>
  526. <zm-table-column
  527. :label="t('iotDevice.assetClass')"
  528. align="center"
  529. prop="assetClassName"
  530. min-width="170"
  531. zm-filterable
  532. v-model:filter-model-value="queryParams.assetClass">
  533. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  534. <div class="p-2">
  535. <el-tree-select
  536. :model-value="filterModelValue"
  537. :data="productClassifyList"
  538. :props="defaultProps"
  539. check-strictly
  540. node-key="id"
  541. :placeholder="t('deviceForm.categoryHolder')"
  542. filterable
  543. class="w-full"
  544. @update:model-value="updateFilterModelValue" />
  545. <div class="mt-2 flex justify-end gap-2">
  546. <el-button
  547. size="small"
  548. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  549. 清空
  550. </el-button>
  551. <el-button
  552. size="small"
  553. type="primary"
  554. :icon="Search"
  555. @click="() => confirmColumnFilter(close)">
  556. 搜索
  557. </el-button>
  558. </div>
  559. </div>
  560. </template>
  561. </zm-table-column>
  562. <zm-table-column
  563. :label="t('iotDevice.assets')"
  564. align="center"
  565. prop="assetProperty"
  566. min-width="110"
  567. zm-filterable
  568. v-model:filter-model-value="queryParams.assetProperty">
  569. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  570. <div class="p-2">
  571. <el-select
  572. :model-value="filterModelValue"
  573. :placeholder="t('iotDevice.assetsHolder')"
  574. clearable
  575. class="w-full"
  576. @update:model-value="updateFilterModelValue">
  577. <el-option
  578. v-for="item in getStrDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY)"
  579. :key="item.value"
  580. :label="item.label"
  581. :value="item.value" />
  582. </el-select>
  583. <div class="mt-2 flex justify-end gap-2">
  584. <el-button
  585. size="small"
  586. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  587. 清空
  588. </el-button>
  589. <el-button
  590. size="small"
  591. type="primary"
  592. :icon="Search"
  593. @click="() => confirmColumnFilter(close)">
  594. 搜索
  595. </el-button>
  596. </div>
  597. </div>
  598. </template>
  599. <template #default="scope">
  600. <dict-tag :type="DICT_TYPE.PMS_ASSET_PROPERTY" :value="scope.row.assetProperty" />
  601. </template>
  602. </zm-table-column>
  603. <zm-table-column
  604. :label="t('devicePerson.rp')"
  605. align="center"
  606. prop="chargeName"
  607. min-width="170"
  608. zm-filterable
  609. v-model:filter-model-value="queryParams.chargeName">
  610. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  611. <div class="p-2">
  612. <el-input
  613. :model-value="filterModelValue"
  614. placeholder="请输入责任人"
  615. clearable
  616. @update:model-value="updateFilterModelValue"
  617. @keyup.enter="() => confirmColumnFilter(close)" />
  618. <div class="mt-2 flex justify-end gap-2">
  619. <el-button
  620. size="small"
  621. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  622. 清空
  623. </el-button>
  624. <el-button
  625. size="small"
  626. type="primary"
  627. :icon="Search"
  628. @click="() => confirmColumnFilter(close)">
  629. 搜索
  630. </el-button>
  631. </div>
  632. </div>
  633. </template>
  634. </zm-table-column>
  635. <zm-table-column
  636. :label="t('deviceForm.useProject')"
  637. align="center"
  638. prop="useProject"
  639. min-width="170"
  640. zm-filterable
  641. v-model:filter-model-value="queryParams.useProject">
  642. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  643. <div class="p-2">
  644. <el-input
  645. :model-value="filterModelValue"
  646. placeholder="请输入使用项目"
  647. clearable
  648. @update:model-value="updateFilterModelValue"
  649. @keyup.enter="() => confirmColumnFilter(close)" />
  650. <div class="mt-2 flex justify-end gap-2">
  651. <el-button
  652. size="small"
  653. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  654. 清空
  655. </el-button>
  656. <el-button
  657. size="small"
  658. type="primary"
  659. :icon="Search"
  660. @click="() => confirmColumnFilter(close)">
  661. 搜索
  662. </el-button>
  663. </div>
  664. </div>
  665. </template>
  666. </zm-table-column>
  667. <zm-table-column
  668. :label="t('deviceForm.assetOwner')"
  669. align="center"
  670. prop="assetOwnership"
  671. min-width="170"
  672. zm-filterable
  673. v-model:filter-model-value="queryParams.assetOwnership">
  674. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  675. <div class="p-2">
  676. <el-input
  677. :model-value="filterModelValue"
  678. placeholder="请输入资产归属"
  679. clearable
  680. @update:model-value="updateFilterModelValue"
  681. @keyup.enter="() => confirmColumnFilter(close)" />
  682. <div class="mt-2 flex justify-end gap-2">
  683. <el-button
  684. size="small"
  685. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  686. 清空
  687. </el-button>
  688. <el-button
  689. size="small"
  690. type="primary"
  691. :icon="Search"
  692. @click="() => confirmColumnFilter(close)">
  693. 搜索
  694. </el-button>
  695. </div>
  696. </div>
  697. </template>
  698. </zm-table-column>
  699. <zm-table-column
  700. label="所在地点"
  701. min-width="120"
  702. align="center"
  703. prop="address"
  704. zm-filterable
  705. v-model:filter-model-value="queryParams.address">
  706. <template #filter="{ filterModelValue, updateFilterModelValue, close }">
  707. <div class="p-2">
  708. <el-input
  709. :model-value="filterModelValue"
  710. placeholder="请输入所在地点"
  711. clearable
  712. @update:model-value="updateFilterModelValue"
  713. @keyup.enter="() => confirmColumnFilter(close)" />
  714. <div class="mt-2 flex justify-end gap-2">
  715. <el-button
  716. size="small"
  717. @click="() => clearColumnFilter(updateFilterModelValue, close)">
  718. 清空
  719. </el-button>
  720. <el-button
  721. size="small"
  722. type="primary"
  723. :icon="Search"
  724. @click="() => confirmColumnFilter(close)">
  725. 搜索
  726. </el-button>
  727. </div>
  728. </div>
  729. </template>
  730. </zm-table-column>
  731. <zm-table-column
  732. :label="t('operationFill.operation')"
  733. align="center"
  734. min-width="150px"
  735. fixed="right"
  736. action>
  737. <template #default="scope">
  738. <el-button
  739. link
  740. type="primary"
  741. @click="openForm('update', scope.row.id)"
  742. v-hasPermi="['rq:iot-device:update']">
  743. {{ t('iotDevice.update') }}
  744. </el-button>
  745. <el-button
  746. link
  747. type="danger"
  748. @click="handleDelete(scope.row.id)"
  749. v-hasPermi="['rq:iot-device:delete']">
  750. {{ t('iotDevice.delete') }}
  751. </el-button>
  752. <!-- <el-button link type="warning" @click="handleUpload(scope.row.id)">-->
  753. <!-- {{t('iotDevice.upload')}}-->
  754. <!-- </el-button>-->
  755. </template>
  756. </zm-table-column>
  757. </zm-table>
  758. </template>
  759. </el-auto-resizer>
  760. </div>
  761. <!-- 分页 -->
  762. <div class="h-8 mt-2 flex items-center justify-end">
  763. <Pagination
  764. :total="total"
  765. v-model:page="queryParams.pageNo"
  766. v-model:limit="queryParams.pageSize"
  767. @pagination="getList" />
  768. </div>
  769. </div>
  770. </div>
  771. </template>
  772. <script setup lang="ts">
  773. import download from '@/utils/download'
  774. import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
  775. import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
  776. import { useUserStore } from '@/store/modules/user'
  777. import { defaultProps, handleTree } from '@/utils/tree'
  778. import * as ProductClassifyApi from '@/api/pms/productclassify'
  779. import { useRefreshStore } from '@/store/modules/pms/refreshStore'
  780. import { Search } from '@element-plus/icons-vue'
  781. import * as DeptApi from '@/api/system/dept'
  782. import { useTableComponents } from '@/components/ZmTable/useTableComponents'
  783. import DeviceTotalCard from './components/dashboard/DeviceTotalCard.vue'
  784. import DeviceStatusCard from './components/dashboard/DeviceStatusCard.vue'
  785. import DeviceClassifyTopCard from './components/dashboard/DeviceClassifyTopCard.vue'
  786. const { ZmTable, ZmTableColumn } = useTableComponents()
  787. /** 设备台账 列表 */
  788. defineOptions({ name: 'IotDevicePms' })
  789. const message = useMessage() // 消息弹窗
  790. const { t } = useI18n() // 国际化
  791. const { push } = useRouter() // 路由跳转
  792. const deptList = ref<Tree[]>([]) // 树形结构
  793. const rootDeptId = 156
  794. const deptId = useUserStore().getUser.deptId || rootDeptId
  795. const refreshStore = useRefreshStore()
  796. const loading = ref(true) // 列表的加载中
  797. const ifShow = ref(false)
  798. const list = ref<IotDeviceVO[]>([]) // 列表的数据
  799. const productClassifyList = ref<Tree[]>([]) // 树形结构
  800. const total = ref(0) // 列表的总页数
  801. const queryParams = reactive<any>({
  802. pageNo: 1,
  803. pageSize: 10,
  804. useProject: undefined,
  805. assetOwnership: undefined,
  806. address: undefined,
  807. deviceCode: undefined,
  808. deviceName: undefined,
  809. brand: undefined,
  810. brandName: undefined,
  811. model: undefined,
  812. deptId: undefined,
  813. deviceStatus: undefined,
  814. assetProperty: undefined,
  815. picUrl: undefined,
  816. remark: undefined,
  817. manufacturerId: undefined,
  818. supplierId: undefined,
  819. manDate: [],
  820. nameplate: undefined,
  821. expires: undefined,
  822. plPrice: undefined,
  823. plDate: [],
  824. plYear: undefined,
  825. plStartDate: [],
  826. plMonthed: undefined,
  827. plAmounted: undefined,
  828. remainAmount: undefined,
  829. infoId: undefined,
  830. infoType: undefined,
  831. infoName: undefined,
  832. infoRemark: undefined,
  833. infoUrl: undefined,
  834. templateJson: undefined,
  835. creator: undefined,
  836. sortingFields: [],
  837. assetClass: undefined,
  838. yfDeviceCode: undefined,
  839. carNo: undefined,
  840. deviceNo: undefined,
  841. manufacturer: undefined,
  842. chargeName: undefined
  843. })
  844. const queryFormRef = ref() // 搜索的表单
  845. const exportLoading = ref(false) // 导出的加载中
  846. const brandList = ref<any[]>([])
  847. const dashboardLoading = ref(false)
  848. const dashboardTotal = ref(0)
  849. const dashboardStatus = ref<any[]>([])
  850. const dashboardClassify = ref<any[]>([])
  851. const dashboardQueryParams = reactive({
  852. deptId: undefined as number | undefined,
  853. yfDeviceCode: undefined as string | undefined,
  854. deviceCode: undefined as string | undefined,
  855. deviceName: undefined as string | undefined,
  856. carNo: undefined as string | undefined,
  857. deviceNo: undefined as string | undefined,
  858. brandName: undefined as string | undefined,
  859. deviceStatus: undefined as string | undefined,
  860. assetProperty: undefined as string | undefined,
  861. assetClass: undefined as number | string | undefined
  862. })
  863. const handleZmSortChange = (prop: string, order: 'asc' | 'desc' | null) => {
  864. queryParams.sortingFields = order ? [{ field: prop, order }] : []
  865. getList()
  866. }
  867. const confirmColumnFilter = (close?: () => void) => {
  868. handleQuery()
  869. close?.()
  870. }
  871. const clearColumnFilter = (updateFilterModelValue: (value: any) => void, close?: () => void) => {
  872. updateFilterModelValue(undefined)
  873. confirmColumnFilter(close)
  874. }
  875. /** 查询列表 */
  876. const getList = async () => {
  877. loading.value = true
  878. try {
  879. const data = await IotDeviceApi.getIotDevicePage(queryParams)
  880. list.value = data.list
  881. total.value = data.total
  882. } finally {
  883. loading.value = false
  884. }
  885. }
  886. const syncDashboardQuery = () => {
  887. dashboardQueryParams.deptId = queryParams.deptId
  888. dashboardQueryParams.yfDeviceCode = queryParams.yfDeviceCode
  889. dashboardQueryParams.deviceCode = queryParams.deviceCode
  890. dashboardQueryParams.deviceName = queryParams.deviceName
  891. dashboardQueryParams.carNo = queryParams.carNo
  892. dashboardQueryParams.deviceNo = queryParams.deviceNo
  893. dashboardQueryParams.brandName = queryParams.brandName
  894. dashboardQueryParams.deviceStatus = queryParams.deviceStatus
  895. dashboardQueryParams.assetProperty = queryParams.assetProperty
  896. dashboardQueryParams.assetClass = queryParams.assetClass
  897. }
  898. const getDashboardData = async () => {
  899. dashboardLoading.value = true
  900. syncDashboardQuery()
  901. try {
  902. const [count, status, classify] = await Promise.all([
  903. IotDeviceApi.getIotDeviceCount(dashboardQueryParams),
  904. IotDeviceApi.getIotDeviceStatus(dashboardQueryParams),
  905. IotDeviceApi.getIotDeviceClassify(dashboardQueryParams)
  906. ])
  907. dashboardTotal.value = Number(count ?? 0)
  908. dashboardStatus.value = Array.isArray(status) ? status : []
  909. dashboardClassify.value = Array.isArray(classify) ? classify : []
  910. } finally {
  911. dashboardLoading.value = false
  912. }
  913. }
  914. /** 处理部门被点击 */
  915. const handleDeptNodeClick = async (row) => {
  916. queryParams.deptId = row.id
  917. await Promise.all([getList(), getDashboardData()])
  918. }
  919. /** 搜索按钮操作 */
  920. const handleQuery = () => {
  921. queryParams.pageNo = 1
  922. getList()
  923. }
  924. const moreQuery = (show) => {
  925. ifShow.value = show
  926. }
  927. /** 重置按钮操作 */
  928. const resetQuery = () => {
  929. queryParams.chargeName = undefined
  930. queryParams.address = undefined
  931. queryParams.manufacturer = undefined
  932. queryParams.useProject = undefined
  933. queryParams.model = undefined
  934. queryParams.assetOwnership = undefined
  935. queryParams.manufacturerId = undefined
  936. queryFormRef.value.resetFields()
  937. handleQuery()
  938. }
  939. /** 添加/修改操作 */
  940. const openForm = (type: string, id?: number, deptId?: number) => {
  941. //修改
  942. if (typeof id === 'number') {
  943. push({ name: 'DeviceDetailEdit', params: { type, id }, query: { source: 'devicerouter' } })
  944. return
  945. }
  946. // 新增
  947. if (deptId) {
  948. push({ name: 'DeviceDetailAdd', params: { type, deptId }, query: { source: 'devicerouter' } })
  949. } else {
  950. push({ name: 'DeviceDetailAddd', params: {}, query: { source: 'devicerouter' } })
  951. }
  952. }
  953. /** 删除按钮操作 */
  954. const handleDelete = async (id: number) => {
  955. try {
  956. // 删除的二次确认
  957. await message.delConfirm()
  958. // 发起删除
  959. await IotDeviceApi.deleteIotDevice(id)
  960. message.success(t('common.delSuccess'))
  961. // 刷新列表
  962. await getList()
  963. } catch {}
  964. }
  965. const handleDetail = (id: number) => {
  966. push({ name: 'DeviceDetailInfo', params: { id } })
  967. }
  968. const handleBrandChange = async (visible) => {
  969. if (visible && brandList.value.length === 0) {
  970. brandList.value = await IotDeviceApi.getDeviceBrand()
  971. }
  972. }
  973. /** 导出按钮操作 */
  974. const handleExport = async () => {
  975. try {
  976. // 导出的二次确认
  977. await message.exportConfirm()
  978. // 发起导出
  979. exportLoading.value = true
  980. const data = await IotDeviceApi.exportIotDevice(queryParams)
  981. download.excel(data, '设备台账.xls')
  982. } catch {
  983. } finally {
  984. exportLoading.value = false
  985. }
  986. }
  987. /** 初始化 **/
  988. onMounted(async () => {
  989. productClassifyList.value = handleTree(
  990. await ProductClassifyApi.IotProductClassifyApi.getSimpleProductClassifyList()
  991. )
  992. // brandList.value = await IotDeviceApi.getDeviceBrand()
  993. const sort = {
  994. field: 'sortColumn',
  995. order: 'asc'
  996. }
  997. queryParams.sortingFields.push(sort)
  998. await Promise.all([getList(), getDashboardData()])
  999. refreshStore.registerCallback('devicerouter', () => {
  1000. Promise.all([getList(), getDashboardData()])
  1001. })
  1002. deptList.value = handleTree(await DeptApi.getSimpleDeptList())
  1003. })
  1004. </script>
  1005. <style scoped lang="scss">
  1006. @media (width <= 1440px) {
  1007. .device-dashboard-grid {
  1008. grid-template-columns: minmax(180px, 0.7fr) minmax(390px, 1fr) minmax(360px, 1.35fr);
  1009. }
  1010. }
  1011. :deep(.el-form-item) {
  1012. margin-bottom: 0;
  1013. }
  1014. .device-dashboard-grid {
  1015. display: grid;
  1016. grid-template-columns: minmax(190px, 0.55fr) minmax(430px, 1.18fr) minmax(420px, 1.42fr);
  1017. gap: 12px;
  1018. min-height: 164px;
  1019. }
  1020. ::v-deep .el-table__header-wrapper {
  1021. position: sticky !important;
  1022. top: 0;
  1023. z-index: 2000;
  1024. width: 100%;
  1025. }
  1026. ::v-deep .el-tooltip__trigger {
  1027. border: none !important;
  1028. outline: none !important;
  1029. }
  1030. ::v-deep .el-table__header-wrapper {
  1031. position: sticky !important;
  1032. top: 0;
  1033. z-index: 2000;
  1034. width: 100%;
  1035. }
  1036. ::v-deep .el-tooltip__trigger {
  1037. border: none !important;
  1038. outline: none !important;
  1039. }
  1040. /* 表头对齐样式 */
  1041. .table-header-flex {
  1042. display: flex !important;
  1043. width: 100% !important;
  1044. height: 100% !important;
  1045. padding: 0 !important;
  1046. align-items: center !important;
  1047. justify-content: center !important;
  1048. }
  1049. .table-header-text {
  1050. font-size: 12px;
  1051. color: #606266;
  1052. text-align: center;
  1053. flex: 1;
  1054. }
  1055. .header-arrow-icon {
  1056. margin-left: 4px;
  1057. font-size: 12px;
  1058. color: #ad9399;
  1059. cursor: pointer;
  1060. }
  1061. .popover-content-flex {
  1062. display: flex;
  1063. align-items: center;
  1064. gap: 8px;
  1065. }
  1066. /* 修复单元格对齐 */
  1067. ::v-deep .el-table th,
  1068. ::v-deep .el-table td {
  1069. padding: 6px 0 !important;
  1070. }
  1071. ::v-deep .el-table th .cell,
  1072. ::v-deep .el-table td .cell {
  1073. display: flex;
  1074. align-items: center;
  1075. justify-content: center;
  1076. height: 100%;
  1077. padding: 0 8px;
  1078. }
  1079. </style>