GatewayList.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <script setup lang="ts">
  2. import { onMounted, ref, useTemplateRef } from 'vue';
  3. import ConfirmModal from '@/components/ConfirmModal.vue';
  4. import SvgIcon from '@/components/SvgIcon.vue';
  5. import { useRequest } from '@/hooks/request';
  6. import { t } from '@/i18n';
  7. import {
  8. addGatewayLinkBatchUpdate,
  9. gatewayList,
  10. getGatewayLinks,
  11. obtainListInterfaces,
  12. obtainListPhysicalInterfaces,
  13. orgGatewayUnregister,
  14. } from '@/api';
  15. import type { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select';
  16. import type {
  17. BatchUpdate,
  18. GatewayListItem,
  19. GatewayQuery,
  20. InterfaceData,
  21. InterfaceLsit,
  22. ListInterfaces,
  23. ListPhysicalInterfaces,
  24. } from '@/types';
  25. const gatewayColumns = [
  26. {
  27. title: t('common.serialNumber'),
  28. dataIndex: 'index',
  29. key: 'index',
  30. },
  31. {
  32. title: t('common.sequenceNumber'),
  33. dataIndex: 'snCode',
  34. key: 'snCode',
  35. ellipsis: true,
  36. },
  37. {
  38. title: t('createDevice.modelNumber'),
  39. dataIndex: 'modelName',
  40. key: 'modelName',
  41. ellipsis: true,
  42. },
  43. {
  44. title: t('registerGateway.onlineStatus'),
  45. dataIndex: 'state',
  46. key: 'state',
  47. ellipsis: true,
  48. },
  49. {
  50. title: t('common.operation'),
  51. dataIndex: 'action',
  52. key: 'action',
  53. width: 80,
  54. },
  55. ];
  56. const agreementColumns = [
  57. {
  58. title: t('registerGateway.stationNumber'),
  59. dataIndex: 'station',
  60. key: 'station',
  61. ellipsis: true,
  62. },
  63. {
  64. title: t('registerGateway.agreement'),
  65. dataIndex: 'protocolName',
  66. key: 'protocolName',
  67. ellipsis: true,
  68. },
  69. {
  70. title: t('registerGateway.associatedEquipment'),
  71. dataIndex: 'deviceName',
  72. key: 'deviceName',
  73. ellipsis: true,
  74. },
  75. ];
  76. const gatewayData = ref<GatewayListItem[]>([]);
  77. const interfaceList = ref<InterfaceLsit[]>([]);
  78. const interfaceOriginalData = ref<InterfaceLsit[]>([]);
  79. const gatewayId = ref<number>();
  80. const gatewayEditor = ref<boolean>(false);
  81. const interfaceSelectList = ref<ListInterfaces[]>([]);
  82. const listPhysicalInterfaces = ref<ListPhysicalInterfaces[]>([]);
  83. const gatewayLinks = ref<BatchUpdate[]>([]);
  84. // 添加选中状态
  85. const selectedRowId = ref<string | null>(null);
  86. const activeKey = ref();
  87. const gatewayQuery = ref<GatewayQuery>({
  88. pageIndex: 1,
  89. pageSize: 10,
  90. searchContent: '',
  91. state: -1,
  92. total: 0,
  93. });
  94. const modalComponentRef = useTemplateRef('modalComponent');
  95. const { handleRequest } = useRequest();
  96. const getGatewayList = () => {
  97. handleRequest(async () => {
  98. const { records, total } = await gatewayList(gatewayQuery.value);
  99. gatewayData.value = records;
  100. gatewayQuery.value.total = total;
  101. if (records.length) {
  102. getObtainListInterfaces(gatewayData.value[0].modelId);
  103. postLinkGetList(gatewayData.value[0].id);
  104. selectedRowId.value = String(gatewayData.value[0].id);
  105. } else {
  106. interfaceList.value = [];
  107. }
  108. });
  109. };
  110. const addGatewayList = () => {
  111. gatewayQuery.value.pageIndex = 1;
  112. getGatewayList();
  113. };
  114. const addReset = () => {
  115. gatewayQuery.value.searchContent = '';
  116. gatewayQuery.value.state = -1;
  117. addGatewayList();
  118. };
  119. const rowClick = (record: GatewayListItem) => {
  120. return {
  121. onClick: () => {
  122. // 更新选中行ID
  123. selectedRowId.value = String(record.id);
  124. gatewayId.value = record.id;
  125. getObtainListInterfaces(record.modelId);
  126. postLinkGetList(record.id);
  127. },
  128. };
  129. };
  130. // 动态行类名
  131. const rowClassName = (record: GatewayListItem) => {
  132. return String(record.id) === selectedRowId.value ? 'selected-row' : '';
  133. };
  134. const getObtainListInterfaces = (id: number) => {
  135. handleRequest(async () => {
  136. interfaceSelectList.value = await obtainListInterfaces(id);
  137. });
  138. };
  139. const getObtainListPhysicalInterfaces = (id: number, value?: InterfaceData) => {
  140. handleRequest(async () => {
  141. listPhysicalInterfaces.value = await obtainListPhysicalInterfaces(id);
  142. if (value) {
  143. if (listPhysicalInterfaces.value.length) {
  144. value.protocolType = listPhysicalInterfaces.value[0].protocolName;
  145. } else {
  146. value.protocolType = '';
  147. }
  148. }
  149. });
  150. };
  151. const choosePhysicalInterface = (
  152. selectValue: SelectValue,
  153. option: DefaultOptionType,
  154. id: number,
  155. value: InterfaceData,
  156. index: number,
  157. ) => {
  158. interfaceList.value[index].interfaceType = option.interfaceType;
  159. interfaceList.value[index].linkName = option.name;
  160. getObtainListPhysicalInterfaces(id, value);
  161. };
  162. const postLinkGetList = (id: number) => {
  163. handleRequest(async () => {
  164. interfaceOriginalData.value = await getGatewayLinks(id);
  165. if (interfaceOriginalData.value.length) {
  166. interfaceList.value = interfaceOriginalData.value;
  167. activeKey.value = interfaceList.value[0].id;
  168. getObtainListPhysicalInterfaces(interfaceList.value[0].interfaceId);
  169. } else {
  170. interfaceList.value = [];
  171. }
  172. });
  173. };
  174. const switchPages = () => {
  175. getGatewayList();
  176. };
  177. const refreshData = () => {
  178. gatewayQuery.value.pageIndex = 1;
  179. getGatewayList();
  180. };
  181. const confirm = () => {
  182. handleRequest(async () => {
  183. if (gatewayId.value) {
  184. await orgGatewayUnregister(gatewayId.value);
  185. modalComponentRef.value?.hideView();
  186. getGatewayList();
  187. }
  188. });
  189. };
  190. const addGatewayDelete = (id: number) => {
  191. gatewayId.value = id;
  192. modalComponentRef.value?.showView();
  193. };
  194. const cancelSave = () => {
  195. gatewayEditor.value = false;
  196. if (gatewayId.value) {
  197. postLinkGetList(gatewayId.value);
  198. }
  199. };
  200. const addSave = () => {
  201. handleRequest(async () => {
  202. interfaceList.value.forEach((item) => {
  203. const {
  204. id,
  205. linkName,
  206. gatewayId,
  207. interfaceId,
  208. protocolType,
  209. bindState,
  210. dataBit,
  211. parityBit,
  212. stopBit,
  213. baudRate,
  214. readTimeout,
  215. nextDataReadDelay,
  216. nextRoundDataReadDelay,
  217. } = item;
  218. gatewayLinks.value.push({
  219. link: {
  220. id,
  221. linkName,
  222. gatewayId,
  223. interfaceId,
  224. protocolType,
  225. bindState,
  226. dataBit,
  227. parityBit,
  228. stopBit,
  229. baudRate,
  230. readTimeout,
  231. nextDataReadDelay,
  232. nextRoundDataReadDelay,
  233. },
  234. protocols: item.protocols,
  235. });
  236. });
  237. await addGatewayLinkBatchUpdate(gatewayLinks.value);
  238. gatewayEditor.value = false;
  239. });
  240. };
  241. onMounted(() => {
  242. addGatewayList();
  243. });
  244. </script>
  245. <template>
  246. <div>
  247. <AFlex justify="space-between">
  248. <div class="text-top">{{ $t('navigation.deviceManage') }}</div>
  249. <div>
  250. <AButton type="primary" class="icon-button">
  251. <AFlex align="center">
  252. <SvgIcon name="plus" />
  253. <span> {{ $t('common.add') }} </span>
  254. </AFlex>
  255. </AButton>
  256. </div>
  257. </AFlex>
  258. <div class="gateway-content">
  259. <ARow>
  260. <ACol :span="12" class="gateway-left">
  261. <AFlex justify="space-between" align="center" class="gateway-left-top">
  262. <div class="gateway-left-top-text">{{ $t('navigation.gatewayList') }}</div>
  263. <AButton type="text" @click="refreshData" class="icon-button gateway-left-top-icon">
  264. <AFlex align="center">
  265. <SvgIcon name="refresh-o" />
  266. <span> {{ $t('registerGateway.refreshData') }} </span>
  267. </AFlex>
  268. </AButton>
  269. </AFlex>
  270. <AFlex justify="space-between" align="center" class="input-bottom">
  271. <div>
  272. <span class="gateway-left-text">{{ $t('common.search') }}</span>
  273. <AInput v-model:value="gatewayQuery.searchContent" placeholder="请输入序列号、型号" class="input-width" />
  274. </div>
  275. <div>
  276. <span class="gateway-left-text">{{ $t('common.status') }}</span>
  277. <ASelect class="select-width" v-model:value="gatewayQuery.state" placeholder="请选择">
  278. <ASelectOption :value="-1">{{ $t('common.all') }}</ASelectOption>
  279. <ASelectOption :value="1">{{ $t('common.online') }}</ASelectOption>
  280. <ASelectOption :value="0">{{ $t('common.offline') }}</ASelectOption>
  281. </ASelect>
  282. </div>
  283. </AFlex>
  284. <AFlex justify="flex-end">
  285. <AButton type="primary" class="query-button" @click="addGatewayList">{{ $t('common.query') }}</AButton>
  286. <AButton @click="addReset" class="default-button">{{ $t('common.reset') }}</AButton>
  287. </AFlex>
  288. <ATable
  289. :columns="gatewayColumns"
  290. :data-source="gatewayData"
  291. :row-key="(record) => record.id"
  292. :custom-row="rowClick"
  293. :pagination="false"
  294. :row-class-name="rowClassName"
  295. >
  296. <template #bodyCell="{ column, record, index }">
  297. <template v-if="column.key === 'index'">
  298. {{ index + 1 }}
  299. </template>
  300. <template v-else-if="column.key === 'state'">
  301. {{ record.state === 0 ? $t('common.offline') : $t('common.online') }}
  302. </template>
  303. <template v-else-if="column.key === 'action'">
  304. <SvgIcon @click="addGatewayDelete(record.id)" class="icon-delete" name="delete" />
  305. </template>
  306. </template>
  307. </ATable>
  308. <AFlex justify="flex-end" class="gateway-left-footer">
  309. <APagination
  310. v-model:current="gatewayQuery.pageIndex"
  311. v-model:page-size="gatewayQuery.pageSize"
  312. :total="gatewayQuery.total"
  313. :show-size-changer="true"
  314. @change="switchPages"
  315. show-quick-jumper
  316. :show-total="(total) => $t('common.pageTotal', { total })"
  317. />
  318. </AFlex>
  319. </ACol>
  320. <ACol :span="12" class="gateway-right">
  321. <AFlex justify="space-between" align="center" class="gateway-right-top">
  322. <div class="gateway-left-top-text">{{ $t('registerGateway.gatewayConfiguration') }}</div>
  323. <div v-if="gatewayEditor">
  324. <AButton v-if="gatewayEditor" type="text" class="icon-button gateway-left-top-icon" @click="cancelSave">
  325. <AFlex align="center">
  326. <SvgIcon name="close" />
  327. <span> {{ $t('common.cancel') }} </span>
  328. </AFlex>
  329. </AButton>
  330. <AButton v-if="gatewayEditor" type="text" class="icon-button gateway-left-top-icon" @click="addSave">
  331. <AFlex align="center">
  332. <SvgIcon name="edit-o" />
  333. <span> {{ $t('common.save') }} </span>
  334. </AFlex>
  335. </AButton>
  336. </div>
  337. <div v-else>
  338. <AButton
  339. v-if="!gatewayEditor"
  340. type="text"
  341. class="icon-button gateway-left-top-icon"
  342. @click="gatewayEditor = true"
  343. :disabled="!interfaceList.length"
  344. >
  345. <AFlex align="center">
  346. <SvgIcon name="edit-o" />
  347. <span> {{ $t('common.editor') }} </span>
  348. </AFlex>
  349. </AButton>
  350. </div>
  351. </AFlex>
  352. <ACollapse v-model:active-key="activeKey" accordion v-if="interfaceList.length" collapsible="icon">
  353. <ACollapsePanel v-for="(item, index) in interfaceList" :key="item.id">
  354. <template #header>
  355. <span class="interface-text-right">{{ $t('createDevice.physicalInterface') }}: </span>
  356. <span v-if="!gatewayEditor">{{ item.linkName }}</span>
  357. <ASelect
  358. v-else
  359. v-model:value="item.interfaceId"
  360. class="interface-select"
  361. :options="interfaceSelectList"
  362. :field-names="{ label: 'name', value: 'id' }"
  363. @change="(value, option) => choosePhysicalInterface(value, option, item.interfaceId, item, index)"
  364. />
  365. </template>
  366. <AFlex align="center" class="gateway-type-bottom">
  367. <div class="gateway-right-text">{{ $t('setupProtocol.protocolType') }}:</div>
  368. <div>
  369. <span v-if="!gatewayEditor">{{ item.protocolType }}</span>
  370. <ASelect
  371. v-else
  372. v-model:value="item.protocolType"
  373. class="interface-select"
  374. :options="listPhysicalInterfaces"
  375. :field-names="{ label: 'protocolName', value: 'protocolName' }"
  376. />
  377. </div>
  378. </AFlex>
  379. <ATable :columns="agreementColumns" :data-source="item.protocols" :pagination="false">
  380. <template #bodyCell="{ column, record }">
  381. <template v-if="column.key === 'station'">
  382. <AInputNumber v-if="gatewayEditor" v-model:value="record.station" :min="0" />
  383. </template>
  384. </template>
  385. </ATable>
  386. </ACollapsePanel>
  387. </ACollapse>
  388. </ACol>
  389. </ARow>
  390. </div>
  391. <ConfirmModal
  392. ref="modalComponent"
  393. :title="$t('common.deleteConfirmation')"
  394. :description-text="$t('common.confirmDeletion')"
  395. :icon="{ name: 'delete' }"
  396. :icon-bg-color="'#F56C6C'"
  397. @confirm="confirm"
  398. />
  399. </div>
  400. </template>
  401. <style lang="scss" scoped>
  402. /* 添加选中行样式 */
  403. :deep(.selected-row) td {
  404. background-color: #e0f5f5 !important;
  405. }
  406. .gateway-type-bottom {
  407. margin-bottom: 12px;
  408. }
  409. .interface-text-right {
  410. margin-right: 10px;
  411. }
  412. .interface-select {
  413. width: 192px;
  414. }
  415. .icon-delete {
  416. font-size: 21px;
  417. color: var(--antd-color-primary-hover);
  418. cursor: pointer;
  419. }
  420. .gateway-left-footer {
  421. margin-top: 24px;
  422. }
  423. .gateway-right-text {
  424. margin-right: 10px;
  425. font-size: 14px;
  426. font-style: normal;
  427. font-weight: 500;
  428. line-height: 24px;
  429. color: rgb(0 0 0 / 85%);
  430. text-align: left;
  431. }
  432. .gateway-right-top {
  433. margin-bottom: 16px;
  434. }
  435. .query-button {
  436. margin-right: 12px;
  437. margin-bottom: 16px;
  438. }
  439. .input-bottom {
  440. margin-bottom: 16px;
  441. }
  442. .select-width {
  443. width: 192px;
  444. }
  445. .input-width {
  446. width: 192px;
  447. }
  448. .gateway-left-text {
  449. margin-right: 12px;
  450. font-size: 14px;
  451. font-style: normal;
  452. font-weight: 400;
  453. line-height: 22px;
  454. color: rgb(0 0 0 / 85%);
  455. text-align: right;
  456. }
  457. .gateway-left-top-icon {
  458. font-size: 14px;
  459. font-style: normal;
  460. font-weight: 400;
  461. line-height: 24px;
  462. color: #666;
  463. text-align: left;
  464. }
  465. .gateway-left-top-text {
  466. font-size: 16px;
  467. font-style: normal;
  468. font-weight: 600;
  469. line-height: 24px;
  470. color: #333;
  471. text-align: left;
  472. }
  473. .gateway-left-top {
  474. margin-bottom: 24px;
  475. }
  476. .gateway-left {
  477. padding: 24px;
  478. }
  479. .gateway-right {
  480. padding: 24px;
  481. border-left: 1px solid #e4e7ed;
  482. }
  483. .gateway-content {
  484. margin-top: 24px;
  485. background: #fff;
  486. border-radius: 16px;
  487. }
  488. .text-top {
  489. font-size: 20px;
  490. font-style: normal;
  491. font-weight: 500;
  492. line-height: 28px;
  493. color: rgb(0 0 0 / 85%);
  494. text-align: left;
  495. }
  496. .deletion-button {
  497. width: 84px;
  498. height: 32px;
  499. margin-right: 16px;
  500. }
  501. </style>