index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <a-card title="设备统计" >
  3. <a-row justify="center">
  4. <a-col
  5. v-for="item in state.deviceCount"
  6. :key="item.key"
  7. :xs="12" :sm="12" :md="6" :lg="8" :xl="4"
  8. >
  9. <div class="statistic" >
  10. <span class="count" :style="{color: item.color}" >{{item.value}}</span>
  11. <span>台</span>
  12. </div>
  13. <div class="label" >
  14. {{item.label}}
  15. </div>
  16. </a-col>
  17. </a-row>
  18. </a-card>
  19. <a-card style="margin-top: 20px;">
  20. <a-row justify="space-between" >
  21. <a-col :span="18" >
  22. <a-form layout="inline" >
  23. <a-form-item >
  24. <a-select allowClear style="width: 176px;" v-model:value="searchState.deviceStatus" placeholder="请输入设备状态">
  25. <a-select-option
  26. v-for="item in DeviceContriller.deviceStatus"
  27. :key="item.key"
  28. :value="item.key"
  29. >{{item.name}}</a-select-option>
  30. </a-select>
  31. </a-form-item>
  32. <a-form-item style="width: 400px;">
  33. <a-input-group compact >
  34. <a-select allowClear v-model:value="searchState.searchKey" style="width: 150px;" >
  35. <a-select-option v-for="item in searchList" :key="item.key" :value="item.key">{{item.name}}</a-select-option>
  36. </a-select>
  37. <a-input allowClear v-model:value="searchState.searchValue" style="width: 176px" placeholder="请输入查询值"/>
  38. </a-input-group>
  39. </a-form-item>
  40. <a-form-item><a-button @click="resetFields([])">重置</a-button></a-form-item>
  41. <a-form-item><a-button type="primary" @click="getDevicePage">搜索</a-button> </a-form-item>
  42. </a-form>
  43. </a-col>
  44. <a-col :span="4" >
  45. <a-space>
  46. <a-button type="primary" @click="openModal('add', {})"> + 新增设备</a-button>
  47. <a-button @click="exportExcel"> 导出</a-button>
  48. </a-space>
  49. </a-col>
  50. </a-row>
  51. <a-table
  52. style="margin-top: 20px;width: 100%;"
  53. :columns="columns"
  54. :data-source="state.dataSource"
  55. :loading="state.loading"
  56. :pagination="searchState"
  57. @change="changePage"
  58. >
  59. <template #bodyCell="{column, record}">
  60. <template v-if="column.key === 'id'" >
  61. <a @click="goDetailPage(record.id)">{{record.id}}</a>
  62. </template>
  63. <template v-if="column.key === 'deviceStatus'" >
  64. <a-tag :color="record.deviceStatus.color" >{{record.deviceStatus.name}}</a-tag>
  65. </template>
  66. <template v-if="column.key === 'deviceNodeType'" >
  67. <a-tag>{{record.deviceNodeType == 'GATEWAY'?'直连类型':'非直连类型'}}</a-tag>
  68. </template>
  69. <!-- <template v-if="column.key === 'rtsUrl'" >
  70. {{record.rtsUrl ? '支持': '否'}}
  71. </template> -->
  72. <template v-if="column.key === 'action'">
  73. <a-space>
  74. <a @click="goDetailPage(record.id)">查看</a>
  75. <a @click="goDebugPage(record.id)" >调试</a>
  76. <a @click="goAnalysisPage(record.id)">分析</a>
  77. <a-popconfirm
  78. title="确实要删除吗?"
  79. ok-text="确定"
  80. cancel-text="取消"
  81. @confirm="delDevice(record.id)"
  82. >
  83. <a href="#">删除</a>
  84. </a-popconfirm>
  85. </a-space>
  86. </template>
  87. </template>
  88. </a-table>
  89. </a-card>
  90. <a-modal
  91. title="单设备注册"
  92. :open="state.visible"
  93. @cancel="state.visible = false"
  94. @ok="ok"
  95. >
  96. <a-form :labelCol="{span: 6}" :wrapperCol="{span: 14}" >
  97. <a-form-item label="所属产品" v-bind="validateInfosDeviceState.modelId" >
  98. <a-select allowClear v-model:value="deviceState.modelId" >
  99. <a-select-option
  100. v-for="item in state.modelList"
  101. :key="item.id"
  102. :value="item.id"
  103. >
  104. {{item.modelLabel}}
  105. </a-select-option>
  106. </a-select>
  107. </a-form-item>
  108. <a-form-item label="设备标识码" v-bind="validateInfosDeviceState.deviceCode" >
  109. <a-input allowClear v-model:value="deviceState.deviceCode" />
  110. </a-form-item>
  111. <a-form-item label="设备名称" >
  112. <a-input allowClear v-model:value="deviceState.deviceLabel" />
  113. </a-form-item>
  114. <a-form-item label="设备地址" >
  115. <a-input allowClear v-model:value="deviceState.deviceAddress" />
  116. </a-form-item>
  117. <a-form-item label="设备认证类型" >
  118. <a-radio-group v-model:value="deviceState.deviceAuthType">
  119. <a-radio value="SECRET">密钥认证</a-radio>
  120. <a-radio value="X509CERT">x509证书</a-radio>
  121. </a-radio-group>
  122. </a-form-item>
  123. <a-form-item label="密匙" v-if="deviceState.deviceAuthType === DeviceAuthTypeEnum.SECRET " >
  124. <a-input-password allowClear v-model:value="deviceState.deviceSecret" />
  125. </a-form-item>
  126. <a-form-item label="确认密匙" v-if="deviceState.deviceAuthType === DeviceAuthTypeEnum.SECRET ">
  127. <a-input-password allowClear v-model:value="deviceState.confirmSecret" />
  128. </a-form-item>
  129. </a-form>
  130. </a-modal>
  131. </template>
  132. <script lang="ts" setup >
  133. import { DeviceContriller, ModelController } from '@/controller/index'
  134. import { onMounted, reactive, ref, toRefs, nextTick, computed, watch } from 'vue'
  135. import { Form, message } from 'ant-design-vue'
  136. import { DeviceAuthTypeEnum } from '@/enum/common'
  137. import { useRoute, useRouter } from 'vue-router'
  138. import { DownOutlined } from '@ant-design/icons-vue'
  139. import { useExportExcel } from '@/hooks'
  140. const useForm = Form.useForm
  141. const router = useRouter()
  142. const modelId = (useRoute().query.modelId || '') as string
  143. console.log('modelId:', modelId)
  144. const columns = [
  145. {
  146. title: '设备ID',
  147. dataIndex: 'id',
  148. key: 'id',
  149. ellipsis: true
  150. },
  151. {
  152. title: '设备名称',
  153. dataIndex: 'deviceLabel',
  154. ellipsis: true
  155. },
  156. {
  157. title: '设备标识码',
  158. dataIndex: 'deviceCode',
  159. ellipsis: true
  160. },
  161. {
  162. title: '设备描述',
  163. dataIndex: 'deviceDescription',
  164. ellipsis: true
  165. },
  166. {
  167. title: '节点类型',
  168. dataIndex: 'deviceNodeType',
  169. key: 'deviceNodeType',
  170. width: 100
  171. },
  172. {
  173. title: '所属产品',
  174. dataIndex: 'modelLabel',
  175. ellipsis: true
  176. },
  177. // {
  178. // title: '支持live',
  179. // dataIndex: 'rtsUrl',
  180. // key: 'rtsUrl',
  181. // width: 100
  182. // },
  183. {
  184. title: '状态',
  185. dataIndex: 'deviceStatus',
  186. key: 'deviceStatus',
  187. ellipsis: true,
  188. width: 80
  189. },
  190. {
  191. title: '操作',
  192. key: 'action'
  193. }
  194. ]
  195. const searchList = [
  196. { name: '设备id', key: 'deviceId' },
  197. { name: '设备名称', key: 'deviceLabel' },
  198. { name: '设备标识码', key: 'deviceCode' }
  199. ]
  200. const analysisList = [
  201. { label: '上下线分析', key: 'session' },
  202. { label: '属性分析', key: 'attr' }
  203. ]
  204. const state = reactive<{
  205. modelId: string,
  206. deviceCount: any,
  207. dataSource: IOT.API.DEVICE.Device[],
  208. loading: boolean,
  209. visible: boolean,
  210. opraState: 'add' | 'update',
  211. modelList: IOT.API.MODEL.ModelDot[]
  212. analysisVisible: boolean
  213. analysisType: 'session' | 'attr' | ''
  214. }>({
  215. modelId: modelId,
  216. deviceCount: [],
  217. dataSource: [],
  218. modelList: [],
  219. loading: false,
  220. visible: false,
  221. analysisVisible: false,
  222. analysisType: '',
  223. opraState: 'add'
  224. })
  225. const searchState = reactive({
  226. page: 1,
  227. pageSize: 10,
  228. total: 0,
  229. deviceStatus: null,
  230. modelId: modelId,
  231. searchKey: 'deviceId',
  232. searchValue: '',
  233. showSizeChanger: false
  234. })
  235. const deviceState = reactive({
  236. id: '',
  237. modelId: '',
  238. deviceLabel: '',
  239. deviceCode: '',
  240. deviceDescription: '',
  241. deviceAuthType: DeviceAuthTypeEnum.SECRET,
  242. modelLabel: '',
  243. deviceSecret: '',
  244. confirmSecret: '',
  245. deviceAddress: ''
  246. })
  247. const { resetFields, validate, validateInfos } = useForm(searchState, {})
  248. const { resetFields: resetFieldsDevice, validate: validateDevice, validateInfos: validateInfosDeviceState } = useForm(deviceState, reactive({
  249. modelId: [{ required: true, message: '请选择所属产品' }],
  250. deviceCode: [{ required: true, message: '请填写设备码' }]
  251. }))
  252. const exportExcel = async () => {
  253. const { data } = await DeviceContriller.exportDeviceExcel(searchState)
  254. useExportExcel(data, '设备列表')
  255. }
  256. const changePage = ({ current }) => {
  257. searchState.page = current
  258. getDevicePage()
  259. }
  260. const goDebugPage = (id: string) => {
  261. router.push({ path: '/devOps/onlineTest', query: { id } })
  262. }
  263. const goDetailPage = (id: string) => {
  264. router.push({ path: '/device/detail', query: { id } })
  265. }
  266. const delDevice = async (id: string) => {
  267. await DeviceContriller.del(id)
  268. getDevicePage()
  269. }
  270. const goAnalysisPage = (id: string) => {
  271. router.push({ path: '/device/analysis', query: { id } })
  272. }
  273. const ok = () => {
  274. if (deviceState.deviceAuthType === DeviceAuthTypeEnum.SECRET && deviceState.deviceSecret !== deviceState.confirmSecret) {
  275. message.warn('两次密匙输入不同')
  276. return
  277. }
  278. validateDevice().then(async (r) => {
  279. await DeviceContriller.post(deviceState)
  280. state.visible = false
  281. getDevicePage()
  282. })
  283. }
  284. const getModelList = async () => {
  285. const { data } = await ModelController.list()
  286. state.modelList = data
  287. }
  288. const openModal = (opraState: 'add' | 'update', record:Partial<IOT.API.DEVICE.Device> = {}) => {
  289. state.visible = true
  290. state.opraState = opraState
  291. getModelList()
  292. resetFieldsDevice({})
  293. }
  294. const getDevicePage = async () => {
  295. state.loading = true
  296. const { data, sum } = await DeviceContriller.page(searchState)
  297. state.loading = false
  298. state.dataSource = data
  299. searchState.total = sum
  300. }
  301. const getDeviceStatistics = async () => {
  302. const data = await DeviceContriller.statistics({ modelId: state.modelId })
  303. state.deviceCount = data as any
  304. }
  305. onMounted(() => {
  306. getDeviceStatistics()
  307. getDevicePage()
  308. })
  309. </script>
  310. <style lang="less" scoped >
  311. @import '~@/styles/theme.less';
  312. .statistic {
  313. color: @sublabel-color;
  314. .count {
  315. font-size: 48px;
  316. margin-right: 10px;
  317. }
  318. }
  319. .label {
  320. font-size: 18px;
  321. color: @sublabel-color;
  322. }
  323. </style>