index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <template>
  2. <el-row :gutter="20">
  3. <!-- 左侧部门树 -->
  4. <el-col :span="4" :xs="24">
  5. <ContentWrap class="h-1/1">
  6. <DeptTree @node-click="handleDeptNodeClick" />
  7. </ContentWrap>
  8. </el-col>
  9. <el-col :span="20" :xs="24">
  10. <ContentWrap v-loading="loading">
  11. <ContentWrap>
  12. <!-- 搜索工作栏 -->
  13. <el-form
  14. class="-mb-15px"
  15. :model="queryParams"
  16. ref="queryFormRef"
  17. :inline="true"
  18. label-width="68px"
  19. >
  20. <el-form-item :label="t('monitor.deviceName')" prop="deviceName" style="margin-left: 25px">
  21. <el-input
  22. v-model="queryParams.deviceName"
  23. :placeholder="t('monitor.nameHolder')"
  24. clearable
  25. @keyup.enter="handleQuery"
  26. class="!w-240px"
  27. />
  28. </el-form-item>
  29. <el-form-item :label="t('monitor.deviceCode')" prop="deviceCode">
  30. <el-input
  31. v-model="queryParams.deviceCode"
  32. :placeholder="t('monitor.codeHolder')"
  33. clearable
  34. @keyup.enter="handleQuery"
  35. class="!w-240px"
  36. />
  37. </el-form-item>
  38. <el-form-item class="float-right !mr-0 !mb-0">
  39. <el-button-group>
  40. <el-button :type="viewMode === 'card' ? 'primary' : 'default'" @click="viewMode = 'card'">
  41. <Icon icon="ep:grid" />
  42. </el-button>
  43. <el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
  44. <Icon icon="ep:list" />
  45. </el-button>
  46. </el-button-group>
  47. </el-form-item>
  48. <el-form-item>
  49. <el-button @click="handleQuery">
  50. <Icon icon="ep:search" class="mr-5px" />
  51. {{t('monitor.search')}}
  52. </el-button>
  53. <el-button @click="resetQuery">
  54. <Icon icon="ep:refresh" class="mr-5px" />
  55. {{t('monitor.reset')}}
  56. </el-button>
  57. <el-button
  58. type="success"
  59. plain
  60. @click="handleExport"
  61. :loading="exportLoading"
  62. v-hasPermi="['iot:device:export']"
  63. >
  64. <Icon icon="ep:download" class="mr-5px" /> 导出
  65. </el-button>
  66. </el-form-item>
  67. </el-form>
  68. </ContentWrap>
  69. <!-- 列表 -->
  70. <ContentWrap>
  71. <template v-if="viewMode === 'card'">
  72. <el-row :gutter="16">
  73. <el-col v-for="item in list" :key="item.id" :xs="24" :sm="12" :md="12" :lg="6" class="mb-4">
  74. <el-card
  75. class="h-full transition-colors relative overflow-hidden"
  76. :body-style="{ padding: '0' }"
  77. >
  78. <!-- 添加渐变背景层 -->
  79. <div
  80. class="absolute top-0 left-0 right-0 h-[50px] pointer-events-none"
  81. :class="[
  82. item.ifInline===3
  83. ? 'bg-gradient-to-b from-[#eefaff] to-transparent'
  84. : 'bg-gradient-to-b from-[#fff1f1] to-transparent'
  85. ]"
  86. >
  87. </div>
  88. <div class="p-4 relative">
  89. <!-- 标题区域 -->
  90. <div class="flex items-center mb-3">
  91. <div class="mr-2.5 flex items-center">
  92. <img src="@/assets/svgs/iot/card-fill.svg" class="w-[18px] h-[18px]" />
  93. </div>
  94. <div class="text-[16px] font-600 flex-1">{{ item.deviceCode+item.deviceName }}</div>
  95. <!-- 添加设备状态标签 -->
  96. <div class="inline-flex items-center">
  97. <div
  98. class="w-1 h-1 rounded-full mr-1.5"
  99. :class="item.ifInline===3? 'bg-[var(--el-color-success)]': 'bg-[var(--el-color-danger)]'"
  100. >
  101. </div>
  102. <el-text
  103. class="!text-xs font-bold"
  104. :type="item.ifInline===3 ? 'success' : 'danger'"
  105. >
  106. {{ getDictLabel(DICT_TYPE.IOT_DEVICE_STATUS, item.ifInline) }}
  107. </el-text>
  108. </div>
  109. </div>
  110. <!-- 信息区域 -->
  111. <div class="flex items-center text-[14px]">
  112. <div class="flex-1">
  113. <div class="mb-2.5 last:mb-0">
  114. <span class="text-[#717c8e] mr-2.5">{{ t('monitor.deviceCode') }}</span>
  115. <span class="text-[#0070ff]">
  116. {{ item.deviceCode }}
  117. </span>
  118. </div>
  119. <div class="mb-2.5 last:mb-0">
  120. <span class="text-[#717c8e] mr-2.5">{{t('monitor.category')}}</span>
  121. <span class="text-[#0070ff]">
  122. {{ item.assetClassName }}
  123. </span>
  124. </div>
  125. <div class="mb-2.5 last:mb-0">
  126. <span class="text-[#717c8e] mr-2.5">{{ t('monitor.latestDataTime') }}</span>
  127. <span class="text-[#0070ff]">
  128. {{ item.lastInlineTime }}
  129. </span>
  130. </div>
  131. </div>
  132. <div class="w-[100px] h-[100px]">
  133. <img src="@/assets/imgs/iot/device.png" class="w-full h-full" />
  134. </div>
  135. </div>
  136. <!-- 分隔线 -->
  137. <el-divider class="!my-3" />
  138. <!-- 按钮 -->
  139. <div class="flex items-center px-0">
  140. <el-button
  141. class="flex-1 !px-2 !h-[32px] text-[13px]"
  142. type="warning"
  143. plain
  144. @click="openDetail(item.id, item.ifInline, item.lastInlineTime,item.deviceName,item.deviceCode,item.deptName)"
  145. >
  146. <Icon icon="ep:view" class="mr-1" />
  147. {{ t('monitor.details') }}
  148. </el-button>
  149. <!-- <div class="mx-[10px] h-[20px] w-[1px] bg-[#dcdfe6]"></div>-->
  150. </div>
  151. </div>
  152. </el-card>
  153. </el-col>
  154. </el-row>
  155. </template>
  156. <!-- 列表视图 -->
  157. <el-table
  158. v-else
  159. v-loading="loading"
  160. :data="list"
  161. :stripe="true"
  162. :show-overflow-tooltip="true"
  163. >
  164. <el-table-column :label="t('monitor.serial')" width="60" align="center">
  165. <template #default="scope">
  166. {{ scope.$index + 1 }}
  167. </template>
  168. </el-table-column>
  169. <el-table-column :label="t('monitor.deviceName')" align="center" prop="deviceName">
  170. <template #default="scope">
  171. <el-link @click="openDetail(scope.row.id)">{{ scope.row.deviceName }}</el-link>
  172. </template>
  173. </el-table-column>
  174. <el-table-column :label="t('monitor.deviceCode')" align="center" prop="deviceCode" />
  175. <el-table-column :label="t('monitor.category')" align="center" prop="assetClassName" />
  176. <el-table-column :label="t('monitor.status')" align="center" prop="deviceStatus" >
  177. <template #default="scope">
  178. <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
  179. </template>
  180. </el-table-column>
  181. <el-table-column :label="t('monitor.online')" align="center" prop="ifInline" >
  182. <template #default="scope">
  183. <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="scope.row.ifInline" />
  184. </template>
  185. </el-table-column>
  186. <el-table-column
  187. :label="t('monitor.latestDataTime')"
  188. align="center"
  189. prop="lastInlineTime"
  190. :formatter="dateFormatter"
  191. width="180px"
  192. />
  193. <el-table-column :label="t('monitor.operation')" align="center" min-width="120px">
  194. <template #default="scope">
  195. <el-button
  196. link
  197. type="primary"
  198. @click="openDetail(scope.row.id, scope.row.ifInline, scope.row.lastInlineTime,scope.row.deviceName,scope.row.deviceCode)"
  199. >
  200. {{t('monitor.check')}}
  201. </el-button>
  202. </template>
  203. </el-table-column>
  204. </el-table>
  205. <!-- 分页 -->
  206. <Pagination
  207. :total="total"
  208. v-model:page="queryParams.pageNo"
  209. v-model:limit="queryParams.pageSize"
  210. @pagination="getList"
  211. />
  212. </ContentWrap>
  213. </ContentWrap>
  214. </el-col>
  215. </el-row>
  216. <!-- &lt;!&ndash; 表单弹窗:添加/修改 &ndash;&gt;-->
  217. <!-- <DeviceForm ref="formRef" @success="getList" />-->
  218. <!-- &lt;!&ndash; 分组表单组件 &ndash;&gt;-->
  219. <!-- <DeviceGroupForm ref="groupFormRef" @success="getList" />-->
  220. <!-- &lt;!&ndash; 导入表单组件 &ndash;&gt;-->
  221. <!-- <DeviceImportForm ref="importFormRef" @success="getList" />-->
  222. </template>
  223. <script setup lang="ts">
  224. import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  225. import { dateFormatter } from '@/utils/formatTime'
  226. import { DeviceApi, DeviceVO, DeviceStateEnum } from '@/api/iot/device/device'
  227. import download from '@/utils/download'
  228. import DeptTree from "@/views/system/user/DeptTree.vue";
  229. import {IotDeviceApi} from "@/api/pms/device";
  230. /** IoT 设备列表 */
  231. defineOptions({ name: 'IoTDevice' })
  232. const message = useMessage() // 消息弹窗
  233. const { t } = useI18n() // 国际化
  234. const loading = ref(true) // 列表加载中
  235. const list = ref<DeviceVO[]>([]) // 列表的数据
  236. const total = ref(0) // 列表的总页数
  237. const queryParams = reactive({
  238. pageNo: 1,
  239. pageSize: 12,
  240. deviceCode: undefined,
  241. deviceName: undefined,
  242. brand: undefined,
  243. model: undefined,
  244. deptId: undefined,
  245. deviceStatus: undefined,
  246. assetProperty: undefined,
  247. picUrl: undefined,
  248. remark: undefined,
  249. manufacturerId: undefined,
  250. supplierId: undefined,
  251. manDate: [],
  252. nameplate: undefined,
  253. expires: undefined,
  254. plPrice: undefined,
  255. plDate: [],
  256. plYear: undefined,
  257. plStartDate: [],
  258. plMonthed: undefined,
  259. plAmounted: undefined,
  260. remainAmount: undefined,
  261. infoId: undefined,
  262. infoType: undefined,
  263. infoName: undefined,
  264. infoRemark: undefined,
  265. infoUrl: undefined,
  266. templateJson: undefined,
  267. creator: undefined
  268. })
  269. const queryFormRef = ref() // 搜索的表单
  270. const exportLoading = ref(false) // 导出加载状态
  271. const products = ref<ProductVO[]>([]) // 产品列表
  272. const deviceGroups = ref<DeviceGroupVO[]>([]) // 设备分组列表
  273. const selectedIds = ref<number[]>([]) // 选中的设备编号数组
  274. const viewMode = ref<'card' | 'list'>('card') // 视图模式状态
  275. const defaultPicUrl = ref('@/src/assets/imgs/iot/device.png') // 默认设备图片
  276. const defaultIconUrl = ref('/src/assets/svgs/iot/card-fill.svg') // 默认设备图标
  277. /** 查询列表 */
  278. const getList = async () => {
  279. loading.value = true
  280. try {
  281. const data = await IotDeviceApi.getIotDeviceTdPage(queryParams)
  282. list.value = data.list
  283. total.value = data.total
  284. } finally {
  285. loading.value = false
  286. }
  287. }
  288. /** 搜索按钮操作 */
  289. const handleQuery = () => {
  290. queryParams.pageNo = 1
  291. getList()
  292. }
  293. /** 重置按钮操作 */
  294. const resetQuery = () => {
  295. queryFormRef.value.resetFields()
  296. selectedIds.value = [] // 清空选择
  297. handleQuery()
  298. }
  299. // /** 添加/修改操作 */
  300. // const formRef = ref()
  301. // const openForm = (type: string, id?: number) => {
  302. // formRef.value.open(type, id)
  303. // }
  304. /** 打开详情 */
  305. const { push } = useRouter()
  306. const openDetail = (id: number,ifInline: string, time:string, name:string,code:string, dept:string) => {
  307. if (time === null||time === undefined) {
  308. message.warning("没有数采数据")
  309. return
  310. }
  311. push({ name: 'TdDeviceDetail', params: { id,ifInline, time,name,code,dept } })
  312. }
  313. /** 导出方法 */
  314. const handleExport = async () => {
  315. try {
  316. // 导出的二次确认
  317. await message.exportConfirm()
  318. // 发起导出
  319. exportLoading.value = true
  320. const data = await DeviceApi.exportDeviceExcel(queryParams)
  321. download.excel(data, '物联网设备.xls')
  322. } catch {
  323. } finally {
  324. exportLoading.value = false
  325. }
  326. }
  327. /** 多选框选中数据 */
  328. const handleSelectionChange = (selection: DeviceVO[]) => {
  329. selectedIds.value = selection.map((item) => item.id)
  330. }
  331. //
  332. // /** 添加到分组操作 */
  333. // const groupFormRef = ref()
  334. // const openGroupForm = () => {
  335. // groupFormRef.value.open(selectedIds.value)
  336. // }
  337. //
  338. // /** 打开物模型数据 */
  339. // const openModel = (id: number) => {
  340. // push({ name: 'IoTDeviceDetail', params: { id }, query: { tab: 'model' } })
  341. // }
  342. //
  343. // /** 设备导入 */
  344. // const importFormRef = ref()
  345. // const handleImport = () => {
  346. // importFormRef.value.open()
  347. // }
  348. /** 初始化 **/
  349. onMounted(async () => {
  350. await getList()
  351. })
  352. /** 处理部门被点击 */
  353. const handleDeptNodeClick = async (row) => {
  354. queryParams.deptId = row.id
  355. await getList()
  356. }
  357. </script>