index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. <template>
  2. <div>
  3. <div class="gva-search-box">
  4. <el-form
  5. ref="elSearchFormRef"
  6. :inline="true"
  7. :model="searchInfo"
  8. @keyup.enter="onSubmit"
  9. >
  10. <el-form-item label="密码设施名称">
  11. <el-input
  12. v-model="searchInfo.name"
  13. placeholder="请输入密码设施名称"
  14. clearable
  15. />
  16. </el-form-item>
  17. <el-form-item>
  18. <el-button
  19. type="primary"
  20. icon="search"
  21. @click="onSubmit"
  22. >查询</el-button>
  23. <el-button
  24. icon="refresh"
  25. @click="onReset"
  26. >重置</el-button>
  27. </el-form-item>
  28. </el-form>
  29. </div>
  30. <div class="gva-table-box">
  31. <div class="gva-btn-list">
  32. <el-button
  33. type="primary"
  34. icon="plus"
  35. @click="addServiceDialog"
  36. >添加服务</el-button>
  37. </div>
  38. <el-table
  39. :data="tableData"
  40. style="width: 100%"
  41. >
  42. <el-table-column
  43. align="center"
  44. label="服务标识"
  45. prop="iden"
  46. width="300"
  47. />
  48. <el-table-column
  49. align="center"
  50. label="密码设施名称"
  51. prop="name"
  52. width="250"
  53. />
  54. <el-table-column
  55. align="center"
  56. label="密码设施ip"
  57. prop="ip"
  58. width="120"
  59. />
  60. <el-table-column
  61. align="center"
  62. label="密码设施端口"
  63. prop="port"
  64. width="120"
  65. />
  66. <el-table-column
  67. align="center"
  68. label="密码设施类型"
  69. prop="type"
  70. width="150"
  71. >
  72. <template #default="scope">
  73. {{ serviceTypeOptions.find(item => item.value === scope.row.type)?.label || scope.row.type }}
  74. </template>
  75. </el-table-column>
  76. <el-table-column
  77. label="创建时间"
  78. prop="createAt"
  79. width="200"
  80. >
  81. <template #default="scope">
  82. {{ formatDateTime(scope.row.createAt) }}
  83. </template>
  84. </el-table-column>
  85. <el-table-column
  86. fixed="right"
  87. label="操作"
  88. >
  89. <template #default="{ row }">
  90. <el-button
  91. link
  92. type="primary"
  93. @click="openDetail(row)"
  94. >采集接口配置</el-button>
  95. <el-button
  96. link
  97. type="primary"
  98. @click="openAppApiDialog(row)"
  99. >应用接口管理</el-button>
  100. <el-button
  101. link
  102. type="primary"
  103. @click="handleVerify(row)"
  104. >态势感知</el-button>
  105. <el-button
  106. link
  107. type="primary"
  108. @click="handleUpdate(row)"
  109. >编辑</el-button>
  110. <el-button
  111. link
  112. type="primary"
  113. @click="handleDelete(row)"
  114. >删除</el-button>
  115. </template>
  116. </el-table-column>
  117. </el-table>
  118. </div>
  119. <div class="gva-pagination">
  120. <el-pagination
  121. :current-page="page"
  122. :page-size="pageSize"
  123. :page-sizes="[10, 30, 50, 100]"
  124. :total="total"
  125. layout="total, sizes, prev, pager, next, jumper"
  126. @current-change="handleCurrentChange"
  127. @size-change="handleSizeChange"
  128. />
  129. </div>
  130. <!-- 添加/编辑服务 -->
  131. <el-dialog
  132. v-model="addServiceDialogVisible"
  133. title="添加服务"
  134. width="50%"
  135. >
  136. <el-form
  137. ref="addServiceFormRef"
  138. :model="addServiceForm"
  139. :rules="createRules"
  140. label-width="120px"
  141. >
  142. <el-form-item
  143. label="服务名称"
  144. prop="name"
  145. >
  146. <el-input
  147. v-model="addServiceForm.name"
  148. placeholder="请输入服务名称"
  149. />
  150. </el-form-item>
  151. <el-form-item
  152. label="服务IP"
  153. prop="ip"
  154. >
  155. <el-input
  156. v-model="addServiceForm.ip"
  157. placeholder="请输入服务IP"
  158. />
  159. </el-form-item>
  160. <el-form-item
  161. label="服务端口"
  162. prop="port"
  163. >
  164. <el-input
  165. v-model="addServiceForm.port"
  166. placeholder="请输入服务端口"
  167. />
  168. </el-form-item>
  169. <el-form-item
  170. label="服务类型"
  171. prop="type"
  172. >
  173. <el-select
  174. v-model="addServiceForm.type"
  175. placeholder="请选择服务类型"
  176. >
  177. <el-option
  178. v-for="item in serviceTypeOptions"
  179. :key="item.value"
  180. :label="item.label"
  181. :value="item.value"
  182. />
  183. </el-select>
  184. </el-form-item>
  185. </el-form>
  186. <template #footer>
  187. <el-button @click="addServiceDialogVisible = false">取消</el-button>
  188. <el-button
  189. type="primary"
  190. @click="submitAddServiceForm"
  191. >确定</el-button>
  192. </template>
  193. </el-dialog>
  194. <!-- 采集接口配置 -->
  195. <el-dialog
  196. v-model="apiDialogVisible"
  197. title="采集接口配置"
  198. width="75%"
  199. :close-on-click-modal="false"
  200. @closed="onApiDialogClosed"
  201. >
  202. <div class="api-dialog-tip">
  203. 服务名称:<span class="api-dialog-tip__name">{{ currentService.name || '-' }}</span>
  204. 服务标识:<span class="api-dialog-tip__name">{{ currentService.iden || '-' }}</span>
  205. </div>
  206. <div class="api-dialog-actions">
  207. <el-button
  208. type="primary"
  209. icon="plus"
  210. :disabled="hasEditingApiRow"
  211. @click="handleAddApiRow"
  212. >新增</el-button>
  213. </div>
  214. <el-table
  215. v-loading="apiDialogLoading"
  216. :data="apiList"
  217. style="width: 100%"
  218. >
  219. <el-table-column
  220. align="center"
  221. label="序号"
  222. prop="index"
  223. width="60"
  224. >
  225. <template #default="scope">
  226. {{ scope.$index + 1 }}
  227. </template>
  228. </el-table-column>
  229. <el-table-column
  230. align="center"
  231. label="接口类型"
  232. prop="apiType"
  233. width="200"
  234. >
  235. <template #default="{ row }">
  236. <el-select
  237. v-if="row._isNew"
  238. v-model="row.apiType"
  239. placeholder="请选择接口类型"
  240. >
  241. <el-option
  242. v-for="item in apiTypeOptions"
  243. :key="item.value"
  244. :label="item.label"
  245. :value="item.value"
  246. />
  247. </el-select>
  248. <span v-else>{{ formatApiType(row.apiType) }}</span>
  249. </template>
  250. </el-table-column>
  251. <el-table-column
  252. align="center"
  253. label="接口地址"
  254. min-width="320"
  255. >
  256. <template #default="{ row }">
  257. <el-input
  258. v-model="row.apiEndpoint"
  259. placeholder="请输入接口地址"
  260. :maxlength="255"
  261. show-word-limit
  262. clearable
  263. />
  264. </template>
  265. </el-table-column>
  266. <el-table-column
  267. align="center"
  268. label="请求方式"
  269. prop="method"
  270. width="120"
  271. >
  272. <template #default="{ row }">
  273. <el-select
  274. v-model="row.method"
  275. placeholder="请选择请求方式"
  276. >
  277. <el-option
  278. label="GET"
  279. value="GET"
  280. />
  281. <el-option
  282. label="POST"
  283. value="POST"
  284. />
  285. </el-select>
  286. </template>
  287. </el-table-column>
  288. <el-table-column
  289. align="center"
  290. label="参数类型"
  291. prop="paramType"
  292. width="120"
  293. >
  294. <template #default="{ row }">
  295. <el-select
  296. v-model="row.paramType"
  297. placeholder="请选择参数类型"
  298. >
  299. <el-option
  300. label="JSON"
  301. value="json"
  302. />
  303. <el-option
  304. label="FORM"
  305. value="form"
  306. />
  307. <el-option
  308. label="QUERY"
  309. value="query"
  310. />
  311. </el-select>
  312. </template>
  313. </el-table-column>
  314. <el-table-column
  315. align="center"
  316. label="请求参数"
  317. prop="params"
  318. min-width="240"
  319. >
  320. <template #default="{ row }">
  321. <el-input
  322. v-model="row.params"
  323. type="textarea"
  324. :autosize="{ minRows: 1, maxRows: 4 }"
  325. placeholder='JSON 对象字符串,如 {"page":1}'
  326. :maxlength="API_PARAMS_MAX_LENGTH"
  327. show-word-limit
  328. clearable
  329. />
  330. </template>
  331. </el-table-column>
  332. <el-table-column
  333. align="center"
  334. label="状态"
  335. prop="status"
  336. width="80"
  337. >
  338. <template #default="{ row }">
  339. <el-switch
  340. v-model="row.status"
  341. :active-value="1"
  342. :inactive-value="2"
  343. :disabled="row._isNew || !row.id"
  344. :loading="row._statusLoading"
  345. @change="handleApiStatusChange(row)"
  346. />
  347. </template>
  348. </el-table-column>
  349. <el-table-column
  350. align="center"
  351. label="操作"
  352. width="180"
  353. fixed="right"
  354. >
  355. <template #default="{ row }">
  356. <el-button
  357. link
  358. type="primary"
  359. :loading="row._saving"
  360. @click="handleSaveApi(row)"
  361. >保存</el-button>
  362. <el-button
  363. link
  364. type="primary"
  365. :loading="row._testing"
  366. :disabled="row._isNew || !row.id"
  367. @click="handleTestApi(row)"
  368. >测试连通</el-button>
  369. </template>
  370. </el-table-column>
  371. <template #empty>
  372. <div style="padding: 20px 0; color: #909399;">暂无接口配置数据</div>
  373. </template>
  374. </el-table>
  375. <template #footer>
  376. <el-button @click="apiDialogVisible = false">关闭</el-button>
  377. </template>
  378. </el-dialog>
  379. <!-- 应用接口管理 -->
  380. <el-dialog
  381. v-model="appApiDialogVisible"
  382. title="应用接口管理"
  383. width="70%"
  384. :close-on-click-modal="false"
  385. @closed="onAppApiDialogClosed"
  386. >
  387. <div class="api-dialog-tip">
  388. 服务名称:<span class="api-dialog-tip__name">{{ currentService.name || '-' }}</span>
  389. 服务标识:<span class="api-dialog-tip__name">{{ currentService.iden || '-' }}</span>
  390. </div>
  391. <el-table
  392. v-loading="appApiLoading"
  393. :data="appApiList"
  394. style="width: 100%"
  395. >
  396. <el-table-column
  397. align="center"
  398. type="index"
  399. label="序号"
  400. width="60"
  401. />
  402. <el-table-column
  403. align="center"
  404. label="地址"
  405. prop="url"
  406. min-width="320"
  407. >
  408. <template #default="{ row }">
  409. <span>{{ row.url || '-' }}</span>
  410. </template>
  411. </el-table-column>
  412. <el-table-column
  413. align="center"
  414. label="备注"
  415. prop="remark"
  416. min-width="160"
  417. >
  418. <template #default="{ row }">
  419. <span>{{ row.remark || '-' }}</span>
  420. </template>
  421. </el-table-column>
  422. <el-table-column
  423. align="center"
  424. label="状态"
  425. prop="status"
  426. width="80"
  427. >
  428. <template #default="{ row }">
  429. <el-switch
  430. v-model="row.status"
  431. :active-value="1"
  432. :inactive-value="0"
  433. :loading="row._statusLoading"
  434. @change="handleAppApiStatusChange(row)"
  435. />
  436. </template>
  437. </el-table-column>
  438. <template #empty>
  439. <div style="padding: 20px 0; color: #909399;">暂无应用接口数据</div>
  440. </template>
  441. </el-table>
  442. <template #footer>
  443. <el-button @click="appApiDialogVisible = false">关闭</el-button>
  444. </template>
  445. </el-dialog>
  446. </div>
  447. </template>
  448. <script setup>
  449. import { ref, computed } from 'vue'
  450. import { ElMessage, ElMessageBox } from 'element-plus'
  451. import {
  452. serviceAdd,
  453. serviceDelete,
  454. serviceList,
  455. serviceUpdate,
  456. apiListByRegulatedIden,
  457. apiAdd,
  458. apiChangeStatus,
  459. apiUpdate,
  460. appApiList as appApiListApi,
  461. appApiToggle,
  462. appApiTest
  463. } from '@/api/sys/service'
  464. import { useRouter } from 'vue-router'
  465. const searchInfo = ref({})
  466. const elSearchFormRef = ref(null)
  467. const page = ref(1)
  468. const pageSize = ref(10)
  469. const total = ref(0)
  470. const tableData = ref([])
  471. /* ------------------------- 采集接口配置 ------------------------- */
  472. const apiDialogVisible = ref(false)
  473. const apiDialogLoading = ref(false)
  474. const apiList = ref([])
  475. const currentService = ref({})
  476. const API_ENDPOINT_MAX_LENGTH = 255
  477. // 后端 params 为 text(utf8mb4),最大 65535 字节;按 4 字节/字符取安全上限 16000
  478. const API_PARAMS_MAX_LENGTH = 2000
  479. // 是否存在未保存的新增行(同时用于禁用新增按钮,避免同时编辑多行)
  480. const hasEditingApiRow = computed(() => apiList.value.some(row => row._isNew))
  481. // 接口类型映射(如后端取值不同,按需调整,未匹配时直接展示原值)
  482. const apiTypeOptions = [
  483. { label: '服务器动态信息采集接口', value: 1 },
  484. { label: '调用次数采集接口', value: 2 },
  485. { label: '服务器静态信息采集接口', value: 3 },
  486. { label: '最近一周的接口请求数量', value: 4 },
  487. { label: '应用接口列表采集', value: 5 },
  488. { label: '应用接口启停', value: 6 }
  489. ]
  490. const formatApiType = val => {
  491. const hit = apiTypeOptions.find(item => String(item.value) === String(val))
  492. return hit ? hit.label : (val ?? '-')
  493. }
  494. const loadApiList = async () => {
  495. if (!currentService.value?.iden) {
  496. ElMessage.warning('当前服务缺少服务标识(iden),无法获取接口配置')
  497. return
  498. }
  499. apiDialogLoading.value = true
  500. try {
  501. const res = await apiListByRegulatedIden(currentService.value.iden)
  502. if (res.code === 100000) {
  503. apiList.value = (res.data || []).map(item => ({
  504. ...item,
  505. apiEndpoint: item.apiEndpoint ?? '',
  506. method: item.method || 'GET',
  507. paramType: item.paramType || 'json',
  508. params: item.params ?? '',
  509. status: item.status ?? 1,
  510. _saving: false,
  511. _testing: false,
  512. _statusLoading: false
  513. }))
  514. } else {
  515. apiList.value = []
  516. }
  517. } catch (err) {
  518. apiList.value = []
  519. ElMessage.error(err.message || '获取采集接口配置失败')
  520. } finally {
  521. apiDialogLoading.value = false
  522. }
  523. }
  524. const openDetail = row => {
  525. currentService.value = { ...row }
  526. apiList.value = []
  527. apiDialogVisible.value = true
  528. loadApiList()
  529. }
  530. const onApiDialogClosed = () => {
  531. apiList.value = []
  532. currentService.value = {}
  533. apiDialogLoading.value = false
  534. }
  535. // 列表末尾追加一行待保存的接口配置
  536. const handleAddApiRow = () => {
  537. apiList.value.push({
  538. apiType: '',
  539. apiEndpoint: '',
  540. method: 'GET',
  541. paramType: 'json',
  542. params: '',
  543. status: 1,
  544. _saving: false,
  545. _testing: false,
  546. _statusLoading: false,
  547. _isNew: true
  548. })
  549. }
  550. const handleApiStatusChange = async row => {
  551. // el-switch 变更后 v-model 已更新,失败时回滚到相反值即可
  552. const nextStatus = row.status
  553. const prevStatus = nextStatus === 1 ? 2 : 1
  554. row._statusLoading = true
  555. try {
  556. const res = await apiChangeStatus(row.id, nextStatus)
  557. if (res.code === 100000) {
  558. ElMessage.success(nextStatus === 1 ? '已启用' : '已停用')
  559. } else {
  560. row.status = prevStatus
  561. }
  562. } catch (err) {
  563. row.status = prevStatus
  564. ElMessage.error(err.message || '修改启停状态失败')
  565. } finally {
  566. row._statusLoading = false
  567. }
  568. }
  569. const validateApiEndpoint = endpoint => {
  570. if (!endpoint) {
  571. ElMessage.warning('接口地址不能为空')
  572. return false
  573. }
  574. if (endpoint.length > API_ENDPOINT_MAX_LENGTH) {
  575. ElMessage.warning(`接口地址不能超过${API_ENDPOINT_MAX_LENGTH}个字符`)
  576. return false
  577. }
  578. return true
  579. }
  580. // 请求参数需为 JSON 对象字符串(可为空)
  581. const validateApiParams = params => {
  582. if (!params) {
  583. return true
  584. }
  585. if (params.length > API_PARAMS_MAX_LENGTH) {
  586. ElMessage.warning(`请求参数不能超过${API_PARAMS_MAX_LENGTH}个字符`)
  587. return false
  588. }
  589. try {
  590. const parsed = JSON.parse(params)
  591. if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
  592. ElMessage.warning('请求参数必须是合法的 JSON 对象字符串')
  593. return false
  594. }
  595. return true
  596. } catch {
  597. ElMessage.warning('请求参数必须是合法的 JSON 对象字符串')
  598. return false
  599. }
  600. }
  601. const handleSaveApi = async row => {
  602. if (row._isNew && !row.apiType) {
  603. ElMessage.warning('请选择接口类型')
  604. return
  605. }
  606. const endpoint = (row.apiEndpoint || '').trim()
  607. if (!validateApiEndpoint(endpoint)) {
  608. return
  609. }
  610. const params = (row.params || '').trim()
  611. if (!validateApiParams(params)) {
  612. return
  613. }
  614. row._saving = true
  615. try {
  616. const reqData = row._isNew
  617. ? {
  618. regulatedIden: currentService.value.iden,
  619. apiType: row.apiType,
  620. apiEndpoint: endpoint,
  621. method: row.method || 'GET',
  622. paramType: row.paramType || 'json',
  623. params
  624. }
  625. : { ...row, apiEndpoint: endpoint, method: row.method || 'GET', paramType: row.paramType || 'json', params, _saving: undefined }
  626. const res = await (row._isNew ? apiAdd(reqData) : apiUpdate(reqData))
  627. if (res.code === 100000) {
  628. if (row._isNew) {
  629. ElMessage.success('保存成功')
  630. // 保存成功后重新加载列表,新增行会变成带主键的普通行
  631. await loadApiList()
  632. } else {
  633. row.apiEndpoint = endpoint
  634. ElMessage.success('保存成功')
  635. }
  636. }
  637. } catch (err) {
  638. ElMessage.error(err.message || '保存失败')
  639. } finally {
  640. row._saving = false
  641. }
  642. }
  643. // 测试采集接口连通性(需先保存,后端按 id 读取配置)
  644. const escapeHtml = value => String(value ?? '-')
  645. .replace(/&/g, '&amp;')
  646. .replace(/</g, '&lt;')
  647. .replace(/>/g, '&gt;')
  648. .replace(/"/g, '&quot;')
  649. const handleTestApi = async row => {
  650. if (!row.id) {
  651. ElMessage.warning('请先保存接口配置后再测试连通')
  652. return
  653. }
  654. row._testing = true
  655. try {
  656. const res = await appApiTest({
  657. regulatedIden: currentService.value.iden,
  658. id: row.id
  659. })
  660. if (res.code === 100000 && res.data) {
  661. const result = res.data
  662. const lines = [
  663. `请求方式:${escapeHtml(result.method)}`,
  664. `参数类型:${escapeHtml(result.paramType)}`,
  665. `接口地址:${escapeHtml(result.endpoint)}`,
  666. `HTTP 状态:${escapeHtml(result.httpStatus)}`,
  667. `耗时:${escapeHtml(result.costMs)} ms`,
  668. `结果:${result.success ? '成功' : '失败'}`
  669. ]
  670. if (result.message) {
  671. lines.push(`信息:${escapeHtml(result.message)}`)
  672. }
  673. ElMessageBox.alert(lines.join('<br/>'), '连通性测试结果', {
  674. dangerouslyUseHTMLString: true,
  675. confirmButtonText: '确定',
  676. type: result.success ? 'success' : 'error'
  677. })
  678. } else if (res.code === 100000) {
  679. ElMessage.success(res.msg || res.message || '接口连接正常')
  680. }
  681. } catch (err) {
  682. ElMessage.error(err.message || '接口连接失败')
  683. } finally {
  684. row._testing = false
  685. }
  686. }
  687. /* ----------------------- 采集接口配置 end ----------------------- */
  688. /* ------------------------- 应用接口管理 ------------------------- */
  689. const appApiDialogVisible = ref(false)
  690. const appApiLoading = ref(false)
  691. const appApiList = ref([])
  692. const loadAppApiList = async () => {
  693. if (!currentService.value?.iden) {
  694. ElMessage.warning('当前服务缺少服务标识(iden),无法获取应用接口列表')
  695. return
  696. }
  697. appApiLoading.value = true
  698. try {
  699. const res = await appApiListApi({ regulatedIden: currentService.value.iden })
  700. if (res.code === 100000) {
  701. appApiList.value = (res.data?.list || []).map(item => ({
  702. ...item,
  703. _statusLoading: false
  704. }))
  705. }
  706. } catch (err) {
  707. appApiList.value = []
  708. ElMessage.error(err.message || '获取应用接口列表失败')
  709. } finally {
  710. appApiLoading.value = false
  711. }
  712. }
  713. const openAppApiDialog = row => {
  714. currentService.value = { ...row }
  715. appApiList.value = []
  716. appApiDialogVisible.value = true
  717. loadAppApiList()
  718. }
  719. const onAppApiDialogClosed = () => {
  720. appApiList.value = []
  721. appApiLoading.value = false
  722. }
  723. const handleAppApiStatusChange = async row => {
  724. // el-switch 变更后 v-model 已更新,失败时回滚到相反值即可
  725. const nextStatus = row.status
  726. const prevStatus = nextStatus === 1 ? 0 : 1
  727. row._statusLoading = true
  728. try {
  729. const res = await appApiToggle({
  730. id: row.id,
  731. regulatedIden: currentService.value.iden
  732. })
  733. if (res.code === 100000) {
  734. ElMessage.success(nextStatus === 1 ? '已启用' : '已停用')
  735. } else {
  736. row.status = prevStatus
  737. }
  738. } catch (err) {
  739. row.status = prevStatus
  740. ElMessage.error(err.message || '修改启停状态失败')
  741. } finally {
  742. row._statusLoading = false
  743. }
  744. }
  745. /* ----------------------- 应用接口管理 end ----------------------- */
  746. const addServiceDialog = () => {
  747. addServiceDialogVisible.value = true
  748. isEditMode.value = false
  749. addServiceForm.value = {
  750. name: '',
  751. ip: '',
  752. port: '',
  753. type: ''
  754. }
  755. // 重置表单验证状态
  756. addServiceFormRef.value?.clearValidate()
  757. }
  758. const submitAddServiceForm = () => {
  759. addServiceFormRef.value.validate(async valid => {
  760. if (valid) {
  761. if (isEditMode.value) {
  762. const res = await serviceUpdate(addServiceForm.value)
  763. if (res.code === 100000) {
  764. ElMessage.success('编辑服务成功')
  765. addServiceDialogVisible.value = false
  766. isEditMode.value = false
  767. await getTableData()
  768. }
  769. } else {
  770. // 调用添加服务的接口
  771. const res = await serviceAdd(addServiceForm.value)
  772. if (res.code === 100000) {
  773. ElMessage.success('添加服务成功')
  774. addServiceDialogVisible.value = false
  775. await getTableData()
  776. }
  777. }
  778. }
  779. })
  780. }
  781. const isEditMode = ref(false)
  782. const handleUpdate = row => {
  783. isEditMode.value = true
  784. addServiceDialogVisible.value = true
  785. addServiceForm.value = { ...row }
  786. // 重置表单验证状态
  787. addServiceFormRef.value?.clearValidate()
  788. }
  789. const handleDelete = row => {
  790. ElMessageBox.confirm(
  791. `确定要删除服务【${row.name}】吗?`,
  792. '提示',
  793. {
  794. confirmButtonText: '确定',
  795. cancelButtonText: '取消',
  796. type: 'warning'
  797. }
  798. ).then(async () => {
  799. // 调用删除服务的接口
  800. const res = await serviceDelete(row.iden)
  801. if (res.code === 100000) {
  802. ElMessage.success('删除服务成功')
  803. await getTableData()
  804. }
  805. }).catch(() => {
  806. ElMessage.info('已取消删除')
  807. })
  808. }
  809. const router = useRouter()
  810. // 态势感知(占位,按需实现)
  811. const handleVerify = row => {
  812. router.push({ name: 'SA', query: { regulatedIden: row.iden } })
  813. }
  814. const addServiceDialogVisible = ref(false)
  815. const addServiceForm = ref({
  816. name: '',
  817. ip: '',
  818. port: '',
  819. type: ''
  820. })
  821. const addServiceFormRef = ref(null)
  822. const createRules = {
  823. name: [{ required: true, message: '请输入服务名称', trigger: 'blur' }, { max: 20, message: '服务名称不能超过20个字符', trigger: 'blur' }],
  824. ip: [{ required: true, message: '请输入服务IP', trigger: 'blur' }, { pattern: /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/, message: '请输入正确的IP地址', trigger: 'blur' }],
  825. port: [{ required: true, message: '请输入服务端口', trigger: 'blur' }, { pattern: /^(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/, message: '服务端口必须为0-65535之间', trigger: 'blur' }],
  826. type: [{ required: true, message: '请选择服务类型', trigger: 'blur' }]
  827. }
  828. const serviceTypeOptions = [
  829. { label: '密码服务中台', value: 1 },
  830. { label: '数据库透明加密', value: 2 },
  831. // { label: '密码机', value: 3 },
  832. { label: '密码服务平台', value: 4 },
  833. { label: '密钥管理系统', value: 5 },
  834. { label: '数字证书管理系统', value: 6 }
  835. ]
  836. const formatDateTime = value => value ? String(value).replace('T', ' ') : ''
  837. const getTableData = async () => {
  838. try {
  839. const res = await serviceList({ pageNum: page.value, pageSize: pageSize.value, searchCount: true, ...searchInfo.value })
  840. tableData.value = res.data?.list || []
  841. total.value = res.data?.total || 0
  842. } catch (err) {
  843. ElMessage.error(err.message || '获取印章列表失败')
  844. }
  845. }
  846. const onSubmit = () => {
  847. page.value = 1
  848. getTableData()
  849. }
  850. const onReset = () => {
  851. searchInfo.value = {}
  852. onSubmit()
  853. }
  854. const handleSizeChange = val => {
  855. pageSize.value = val
  856. getTableData()
  857. }
  858. const handleCurrentChange = val => {
  859. page.value = val
  860. getTableData()
  861. }
  862. getTableData()
  863. </script>
  864. <style scoped lang="scss">
  865. .el-select,
  866. .el-date-editor {
  867. width: 100%;
  868. }
  869. .api-dialog-tip {
  870. margin-bottom: 12px;
  871. font-size: 13px;
  872. color: #606266;
  873. &__name {
  874. font-weight: 600;
  875. color: #303133;
  876. }
  877. }
  878. .api-dialog-actions {
  879. margin-bottom: 12px;
  880. }
  881. </style>