cloudview.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <a-card
  3. style="width: 100%"
  4. :tab-list="tabListNoTitle"
  5. :active-tab-key="state.tasActive"
  6. @tabChange="onTabChange"
  7. >
  8. <template v-if="state.tasActive == 'msg'" >
  9. <a-alert :message="tipMessage" type="info" show-icon />
  10. <div class="subtitle" >注意:平台为每个设备默认最多保存20条消息,超过20条后,后续的消息会替换下发最早的消息。</div>
  11. <a-row justify="space-between" >
  12. <a-col></a-col>
  13. <a-col> <a-button type="primary" @click="state.visible = true">下发消息</a-button> </a-col>
  14. </a-row>
  15. </template>
  16. <template v-else >
  17. <a-alert :message="tipMessage" type="info" show-icon style="margin-bottom: 20px;" />
  18. <a-row justify="space-between" >
  19. <a-col class="msg-title" >
  20. <div class="title" >同步命令下发</div>
  21. <div class="subtitle" >同步命令暂不支持历史记录查看。</div>
  22. </a-col>
  23. <a-col> <a-button type="primary" @click="state.visible = true">命令下发</a-button> </a-col>
  24. </a-row>
  25. </template>
  26. <a-table
  27. style="margin-top: 20px;"
  28. :columns="state.tasActive === 'msg' ? columns :cmdColumns"
  29. :dataSource="state.msgDataSource"
  30. :loading="state.loading"
  31. :pagination="false"
  32. >
  33. <template #bodyCell="{ column, record }">
  34. <template v-if="column.key === 'cmdPayload'" >
  35. <a-tooltip color="white" :overlayStyle="{width: '800px'}" >
  36. <template #title>
  37. <a-textarea
  38. :bordered="false"
  39. style="width: 600px;"
  40. :auto-size="{ minRows: 5, maxRows: 15 }"
  41. :value="JSON.stringify(JSON.parse(record.cmdPayload), null, '\t')"
  42. >
  43. </a-textarea>
  44. </template>
  45. {{record.cmdPayload}}
  46. </a-tooltip>
  47. </template>
  48. <template v-if="column.key === 'cmdRet'" >
  49. <a-tooltip color="white" :overlayStyle="{width: '200px'}" >
  50. <template #title>
  51. <a-textarea
  52. :bordered="false"
  53. style="width: 600px;"
  54. :auto-size="{ minRows: 5, maxRows: 15 }"
  55. :value="record.cmdRet"
  56. >
  57. </a-textarea>
  58. </template>
  59. {{record.cmdRet}}
  60. </a-tooltip>
  61. </template>
  62. <template v-if="column.key === 'createAt'">
  63. {{dayjs(record.createAt).format('YYYY-MM-DD HH:mm:ss')}}
  64. </template>
  65. <template v-if="column.key === 'action'">
  66. <a @click="openDetailModal(record)" >详情</a>
  67. </template>
  68. </template>
  69. </a-table>
  70. </a-card>
  71. <modal-pro
  72. :title="modalTitle"
  73. style="width: 700px;"
  74. :open="state.visible"
  75. @cancel="state.visible = false"
  76. @ok="ok"
  77. >
  78. <a-form
  79. v-if="state.tasActive === 'msg'"
  80. :labelCol="{span: 4}"
  81. :wrapperCol="{span: 14}"
  82. >
  83. <a-form-item
  84. label="消息名称"
  85. v-bind="validateInfos.msgLabel"
  86. >
  87. <a-input allowClear v-model:value="msgState.msgLabel" > </a-input>
  88. </a-form-item>
  89. <a-form-item
  90. label="Topic"
  91. >
  92. <a-input allowClear v-model:value="msgState.topic" > </a-input>
  93. </a-form-item>
  94. <a-form-item
  95. label="消息内容"
  96. v-bind="validateInfos.msgPayload"
  97. >
  98. <a-textarea allowClear v-model:value="msgState.msgPayload" > </a-textarea>
  99. </a-form-item>
  100. </a-form>
  101. <template v-else >
  102. <a-alert message="同步命令成功下发后,设备需要在20秒内向平台回复响应,否则会认为命令请求超时。" type="info" show-icon style="margin-bottom: 20px;" />
  103. <a-form
  104. :labelCol="{span: 4}"
  105. :wrapperCol="{span: 14}"
  106. >
  107. <a-form-item
  108. label="命令名称"
  109. v-bind="validateInfosCmd.cmdId"
  110. >
  111. <a-select allowClear v-model:value="cmdState.cmdId" >
  112. <a-select-option
  113. v-for="item in state.cmdList"
  114. :key="item.id"
  115. :value="item.id"
  116. >
  117. {{item.cmdLabel}}
  118. </a-select-option>
  119. </a-select>
  120. </a-form-item>
  121. <a-form-item
  122. label="设置命令参数"
  123. v-bind="validateInfos.validateInfosCmd"
  124. v-if="cmdDetail?.cmdParams"
  125. >
  126. <a-row>
  127. <a-col v-for="(item, index) in cmdDetail?.cmdParams" :key="index" style="margin: 10px 0px;" >
  128. <a-input-group size="large" >
  129. <a-row :gutter="8" align="middle" >
  130. <a-col :span="8">
  131. <a-input allowClear placeholder="key" v-model:value="item.paramLabel" disabled />
  132. </a-col>
  133. <a-col :span="8">
  134. <a-input allowClear placeholder="value" v-model:value="item.dataUnit" />
  135. </a-col>
  136. </a-row>
  137. </a-input-group>
  138. </a-col>
  139. </a-row>
  140. </a-form-item>
  141. <a-form-item label="设置命令参数" >
  142. 产品尚未配置命令,请先去 <a @click="pushProductDetail" >产品详情</a> 定义命令。
  143. </a-form-item>
  144. </a-form>
  145. </template>
  146. </modal-pro>
  147. <modal-pro
  148. title="下发消息"
  149. layout="vertical"
  150. :open="state.detailVisible"
  151. @cancel="state.detailVisible = false"
  152. >
  153. <a-descriptions
  154. :column="1"
  155. :labelStyle="{width: '100px', textAlign: 'left', display: 'block'}"
  156. v-if="state.tasActive === 'msg'"
  157. >
  158. <a-descriptions-item :span="24" label="状态">{{ DeviceContriller.deviceMag.get(state.msgDetail!.status! )?.name }}</a-descriptions-item>
  159. <a-descriptions-item :span="24" label="消息 ID">{{state.msgDetail.msgId}}</a-descriptions-item>
  160. <a-descriptions-item :span="24" label="消息名称">{{state.msgDetail.msgLabel}}</a-descriptions-item>
  161. <a-descriptions-item :span="24" label="Topic">{{state.msgDetail.topic}}</a-descriptions-item>
  162. <a-descriptions-item :span="24" label="消息创建时间">
  163. {{dayjs(state.msgDetail.createAt).format('YYYY-MM-DD HH:mm:ss')}}
  164. </a-descriptions-item>
  165. </a-descriptions>
  166. <a-descriptions
  167. :column="1"
  168. :labelStyle="{width: '100px', textAlign: 'left', display: 'block'}"
  169. v-else
  170. >
  171. <a-descriptions-item :span="24" label="状态">{{ DeviceContriller.deviceMag.get(state.cmdDetail!.status! )?.name }}</a-descriptions-item>
  172. <a-descriptions-item :span="24" label="命令名称">{{state.cmdDetail.cmdLabel}}</a-descriptions-item>
  173. <a-descriptions-item :span="24" label="参数">{{state.cmdDetail.cmdPayload}}</a-descriptions-item>
  174. <!-- <a-descriptions-item :span="24" label="下发参数">{{state.cmdDetail.msgLabel}}</a-descriptions-item> -->
  175. <a-descriptions-item :span="24" label="消息创建时间">
  176. {{dayjs(state.cmdDetail.createAt).format('YYYY-MM-DD HH:mm:ss')}}
  177. </a-descriptions-item>
  178. </a-descriptions>
  179. </modal-pro>
  180. </template>
  181. <script lang="ts" setup >
  182. import { DeviceContriller, ModelCmdController } from '@/controller'
  183. import { computed, onMounted, reactive, ref } from 'vue'
  184. import { useRoute, useRouter } from 'vue-router'
  185. import { Form, message } from 'ant-design-vue'
  186. import dayjs from 'dayjs'
  187. import { CodeMirrorTsx } from '@/components/CodeMirror/index'
  188. const msg = '消息下发不依赖产品模型,平台会以异步方式(消息下发后无需等待设备侧回复响应)下发消息给设备。当前仅MQTT设备支持消息下发。'
  189. const cmdMsg = '如果设备所属产品定义了命令功能,则您可以通过应用调用平台接口或者操作下面的“下发命令”按钮下发命令。当前MQTT设备仅支持同步命令下发,设备仅支持异步命令下发 。'
  190. const modalTitle = '新增下发消息'
  191. const tabListNoTitle = [
  192. {
  193. key: 'msg',
  194. tab: '消息下发'
  195. },
  196. {
  197. key: 'cmd',
  198. tab: '命令下发'
  199. }
  200. ]
  201. const columns = [
  202. {
  203. title: '状态',
  204. dataIndex: 'status',
  205. key: 'status'
  206. },
  207. {
  208. title: '消息名称',
  209. dataIndex: 'msgLabel'
  210. },
  211. {
  212. title: '消息id',
  213. dataIndex: 'msgId'
  214. },
  215. {
  216. title: '消息内容',
  217. dataIndex: 'msgPayload'
  218. },
  219. {
  220. title: '消息创建时间',
  221. dataIndex: 'createAt',
  222. key: 'createAt'
  223. },
  224. {
  225. title: '操作',
  226. key: 'action'
  227. }
  228. ]
  229. const cmdColumns = [
  230. {
  231. title: '状态',
  232. dataIndex: 'status',
  233. key: 'status'
  234. },
  235. {
  236. title: '命令名称',
  237. dataIndex: 'cmdLabel'
  238. },
  239. {
  240. title: '命令id',
  241. dataIndex: 'msgId'
  242. },
  243. {
  244. title: '命令内容',
  245. dataIndex: 'cmdPayload',
  246. key: 'cmdPayload'
  247. },
  248. {
  249. title: '命令结果',
  250. dataIndex: 'cmdRet',
  251. key: 'cmdRet'
  252. },
  253. {
  254. title: '命令创建时间',
  255. dataIndex: 'createAt',
  256. key: 'createAt'
  257. },
  258. {
  259. title: '操作',
  260. key: 'action'
  261. }
  262. ]
  263. const useForm = Form.useForm
  264. const route = useRoute()
  265. const router = useRouter()
  266. const deviceId = route.query.id as string
  267. const tipMessage = computed(() => state.tasActive === 'msg' ? msg : cmdMsg)
  268. const cmdDetail = computed(() => state.cmdList.find(item => item.id === cmdState.cmdId))
  269. const cmdParametersTrans = computed(() => {
  270. return Object.keys(cmdState.cmdParameters).map(key => {
  271. return { key: key, value: cmdState.cmdParameters[key] }
  272. })
  273. })
  274. const state = reactive<{
  275. tasActive: 'msg' | 'cmd',
  276. msgDataSource: (IOT.API.DEVICE.Msg | IOT.API.DEVICE.Cmd)[],
  277. visible: boolean,
  278. loading: boolean,
  279. detailVisible: boolean,
  280. msgDetail: Partial<IOT.API.DEVICE.Msg >
  281. cmdDetail: Partial<IOT.API.DEVICE.Cmd>
  282. cmdList: IOT.API.CMD.Cmd[],
  283. deviceDetail: IOT.API.DEVICE.Device | null
  284. }>({
  285. tasActive: 'msg',
  286. msgDataSource: [],
  287. visible: false,
  288. loading: false,
  289. detailVisible: false,
  290. msgDetail: {},
  291. cmdDetail: {},
  292. cmdList: [],
  293. deviceDetail: null
  294. })
  295. const msgState = reactive({
  296. deviceId: deviceId,
  297. msgPayload: '',
  298. msgLabel: '',
  299. topic: ''
  300. })
  301. const cmdState = reactive<{
  302. cmdId: string
  303. deviceId: string,
  304. cmdLabel: string,
  305. cmdParameters: {key: string, value: string}[]
  306. }>({
  307. cmdId: '',
  308. deviceId: deviceId,
  309. cmdLabel: '',
  310. cmdParameters: []
  311. })
  312. const { resetFields, validate, validateInfos } = useForm(msgState, reactive({
  313. msgPayload: [{ required: true, message: '请填写消息内容' }],
  314. msgLabel: [{ required: true, message: '请填写消息标题' }]
  315. }))
  316. const { resetFields: resetFieldsCmd, validate: validateCmd, validateInfos: validateInfosCmd } = useForm(cmdState, reactive({
  317. cmdId: [{ required: true, message: '请填写命令名称' }]
  318. }))
  319. const getModelCmdList = async () => {
  320. const { data } = await ModelCmdController.list({ modelId: state.deviceDetail!.modelId })
  321. state.cmdList = data
  322. }
  323. const pushProductDetail = () => {
  324. router.push({ path: '/product/detail', query: { id: state.deviceDetail!.modelId } })
  325. }
  326. const ok = () => {
  327. if (state.tasActive === 'msg') {
  328. validate().then(async () => {
  329. await DeviceContriller.addDeviceMsg(msgState)
  330. state.visible = false
  331. getDeviceMsgList()
  332. })
  333. } else {
  334. validateCmd().then(async () => {
  335. const _cmdParameters: Record<string, string> = {}
  336. cmdDetail.value?.cmdParams.forEach(item => {
  337. _cmdParameters[item.paramLabel] = item.dataUnit
  338. })
  339. const $params = {
  340. ...cmdState,
  341. cmdLabel: cmdDetail.value!.cmdLabel,
  342. cmdParameters: _cmdParameters
  343. }
  344. await DeviceContriller.addDeviceCmd($params)
  345. state.visible = false
  346. getDeviceMsgList()
  347. })
  348. }
  349. }
  350. const openDetailModal = (record: IOT.API.DEVICE.Msg) => {
  351. const key = state.tasActive === 'cmd' ? 'cmdDetail' : 'msgDetail'
  352. state.detailVisible = true
  353. state[key] = record
  354. }
  355. const getDeviceMsgList = async () => {
  356. state.loading = true
  357. state.msgDataSource = state.tasActive === 'msg' ? await DeviceContriller.listDeviceMsg({ deviceId }) : await DeviceContriller.listDeviceCmd({ deviceId })
  358. state.loading = false
  359. }
  360. const getDeviceById = async () => {
  361. state.deviceDetail = await DeviceContriller.byId(deviceId)
  362. getModelCmdList()
  363. }
  364. const onTabChange = (value: 'msg' | 'cmd') => {
  365. state.tasActive = value
  366. getDeviceMsgList()
  367. }
  368. onMounted(() => {
  369. getDeviceMsgList()
  370. getDeviceById()
  371. })
  372. </script>
  373. <style lang="less" scoped >
  374. @import '~@/styles/theme.less';
  375. .subtitle {
  376. color: @sublabel-color;
  377. margin: 20px 0px;
  378. }
  379. .msg-title {
  380. .title {
  381. font-size: 20px;
  382. }
  383. .subtitle {
  384. color: @sublabel-color;
  385. font-size: 14px;
  386. }
  387. }
  388. </style>