|
|
@@ -0,0 +1,919 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <div class="gva-search-box">
|
|
|
+ <el-form
|
|
|
+ ref="elSearchFormRef"
|
|
|
+ :inline="true"
|
|
|
+ :model="searchInfo"
|
|
|
+ @keyup.enter="onSubmit"
|
|
|
+ >
|
|
|
+ <el-form-item label="密码设施名称">
|
|
|
+ <el-input
|
|
|
+ v-model="searchInfo.name"
|
|
|
+ placeholder="请输入密码设施名称"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ icon="search"
|
|
|
+ @click="onSubmit"
|
|
|
+ >查询</el-button>
|
|
|
+ <el-button
|
|
|
+ icon="refresh"
|
|
|
+ @click="onReset"
|
|
|
+ >重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="gva-table-box">
|
|
|
+ <div class="gva-btn-list">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ icon="plus"
|
|
|
+ @click="addServiceDialog"
|
|
|
+ >添加服务</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ :data="tableData"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="服务标识"
|
|
|
+ prop="iden"
|
|
|
+ width="300"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="密码设施名称"
|
|
|
+ prop="name"
|
|
|
+ width="250"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="密码设施ip"
|
|
|
+ prop="ip"
|
|
|
+ width="120"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="密码设施端口"
|
|
|
+ prop="port"
|
|
|
+ width="120"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="密码设施类型"
|
|
|
+ prop="type"
|
|
|
+ width="150"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ serviceTypeOptions.find(item => item.value === scope.row.type)?.label || scope.row.type }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ label="创建时间"
|
|
|
+ prop="createAt"
|
|
|
+ width="200"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatDateTime(scope.row.createAt) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ fixed="right"
|
|
|
+ label="操作"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="openDetail(row)"
|
|
|
+ >采集接口配置</el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="openAppApiDialog(row)"
|
|
|
+ >应用接口管理</el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="handleVerify(row)"
|
|
|
+ >态势感知</el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="handleUpdate(row)"
|
|
|
+ >编辑</el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="handleDelete(row)"
|
|
|
+ >删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="gva-pagination">
|
|
|
+ <el-pagination
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="pageSize"
|
|
|
+ :page-sizes="[10, 30, 50, 100]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加/编辑服务 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="addServiceDialogVisible"
|
|
|
+ title="添加服务"
|
|
|
+ width="50%"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ ref="addServiceFormRef"
|
|
|
+ :model="addServiceForm"
|
|
|
+ :rules="createRules"
|
|
|
+ label-width="120px"
|
|
|
+ >
|
|
|
+ <el-form-item
|
|
|
+ label="服务名称"
|
|
|
+ prop="name"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="addServiceForm.name"
|
|
|
+ placeholder="请输入服务名称"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item
|
|
|
+ label="服务IP"
|
|
|
+ prop="ip"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="addServiceForm.ip"
|
|
|
+ placeholder="请输入服务IP"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item
|
|
|
+ label="服务端口"
|
|
|
+ prop="port"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="addServiceForm.port"
|
|
|
+ placeholder="请输入服务端口"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item
|
|
|
+ label="服务类型"
|
|
|
+ prop="type"
|
|
|
+ >
|
|
|
+ <el-select
|
|
|
+ v-model="addServiceForm.type"
|
|
|
+ placeholder="请选择服务类型"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in serviceTypeOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="addServiceDialogVisible = false">取消</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="submitAddServiceForm"
|
|
|
+ >确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 采集接口配置 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="apiDialogVisible"
|
|
|
+ title="采集接口配置"
|
|
|
+ width="75%"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @closed="onApiDialogClosed"
|
|
|
+ >
|
|
|
+ <div class="api-dialog-tip">
|
|
|
+ 服务名称:<span class="api-dialog-tip__name">{{ currentService.name || '-' }}</span>
|
|
|
+ 服务标识:<span class="api-dialog-tip__name">{{ currentService.iden || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="api-dialog-actions">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ icon="plus"
|
|
|
+ :disabled="hasEditingApiRow"
|
|
|
+ @click="handleAddApiRow"
|
|
|
+ >新增</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ v-loading="apiDialogLoading"
|
|
|
+ :data="apiList"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="序号"
|
|
|
+ prop="index"
|
|
|
+ width="60"
|
|
|
+ >
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.$index + 1 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="接口类型"
|
|
|
+ prop="apiType"
|
|
|
+ width="200"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-if="row._isNew"
|
|
|
+ v-model="row.apiType"
|
|
|
+ placeholder="请选择接口类型"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in apiTypeOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <span v-else>{{ formatApiType(row.apiType) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="接口地址"
|
|
|
+ min-width="320"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input
|
|
|
+ v-model="row.apiEndpoint"
|
|
|
+ placeholder="请输入接口地址"
|
|
|
+ :maxlength="255"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="请求方式"
|
|
|
+ prop="method"
|
|
|
+ width="120"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-model="row.method"
|
|
|
+ placeholder="请选择请求方式"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ label="GET"
|
|
|
+ value="GET"
|
|
|
+ />
|
|
|
+ <el-option
|
|
|
+ label="POST"
|
|
|
+ value="POST"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="参数类型"
|
|
|
+ prop="paramType"
|
|
|
+ width="120"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-model="row.paramType"
|
|
|
+ placeholder="请选择参数类型"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ label="JSON"
|
|
|
+ value="json"
|
|
|
+ />
|
|
|
+ <el-option
|
|
|
+ label="FORM"
|
|
|
+ value="form"
|
|
|
+ />
|
|
|
+ <el-option
|
|
|
+ label="QUERY"
|
|
|
+ value="query"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="请求参数"
|
|
|
+ prop="params"
|
|
|
+ min-width="240"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input
|
|
|
+ v-model="row.params"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 1, maxRows: 4 }"
|
|
|
+ placeholder='JSON 对象字符串,如 {"page":1}'
|
|
|
+ :maxlength="API_PARAMS_MAX_LENGTH"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="状态"
|
|
|
+ prop="status"
|
|
|
+ width="80"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch
|
|
|
+ v-model="row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="2"
|
|
|
+ :disabled="row._isNew || !row.id"
|
|
|
+ :loading="row._statusLoading"
|
|
|
+ @change="handleApiStatusChange(row)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="操作"
|
|
|
+ width="180"
|
|
|
+ fixed="right"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ :loading="row._saving"
|
|
|
+ @click="handleSaveApi(row)"
|
|
|
+ >保存</el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ :loading="row._testing"
|
|
|
+ :disabled="row._isNew || !row.id"
|
|
|
+ @click="handleTestApi(row)"
|
|
|
+ >测试连通</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <template #empty>
|
|
|
+ <div style="padding: 20px 0; color: #909399;">暂无接口配置数据</div>
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="apiDialogVisible = false">关闭</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 应用接口管理 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="appApiDialogVisible"
|
|
|
+ title="应用接口管理"
|
|
|
+ width="70%"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @closed="onAppApiDialogClosed"
|
|
|
+ >
|
|
|
+ <div class="api-dialog-tip">
|
|
|
+ 服务名称:<span class="api-dialog-tip__name">{{ currentService.name || '-' }}</span>
|
|
|
+ 服务标识:<span class="api-dialog-tip__name">{{ currentService.iden || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ v-loading="appApiLoading"
|
|
|
+ :data="appApiList"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ type="index"
|
|
|
+ label="序号"
|
|
|
+ width="60"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="地址"
|
|
|
+ prop="url"
|
|
|
+ min-width="320"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.url || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="备注"
|
|
|
+ prop="remark"
|
|
|
+ min-width="160"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.remark || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="状态"
|
|
|
+ prop="status"
|
|
|
+ width="80"
|
|
|
+ >
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch
|
|
|
+ v-model="row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ :loading="row._statusLoading"
|
|
|
+ @change="handleAppApiStatusChange(row)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <template #empty>
|
|
|
+ <div style="padding: 20px 0; color: #909399;">暂无应用接口数据</div>
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="appApiDialogVisible = false">关闭</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import {
|
|
|
+ serviceAdd,
|
|
|
+ serviceDelete,
|
|
|
+ serviceList,
|
|
|
+ serviceUpdate,
|
|
|
+ apiListByRegulatedIden,
|
|
|
+ apiAdd,
|
|
|
+ apiChangeStatus,
|
|
|
+ apiUpdate,
|
|
|
+ appApiList as appApiListApi,
|
|
|
+ appApiToggle,
|
|
|
+ appApiTest
|
|
|
+} from '@/api/sys/service'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+
|
|
|
+const searchInfo = ref({})
|
|
|
+const elSearchFormRef = ref(null)
|
|
|
+const page = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(0)
|
|
|
+const tableData = ref([])
|
|
|
+
|
|
|
+/* ------------------------- 采集接口配置 ------------------------- */
|
|
|
+const apiDialogVisible = ref(false)
|
|
|
+const apiDialogLoading = ref(false)
|
|
|
+const apiList = ref([])
|
|
|
+const currentService = ref({})
|
|
|
+
|
|
|
+const API_ENDPOINT_MAX_LENGTH = 255
|
|
|
+// 后端 params 为 text(utf8mb4),最大 65535 字节;按 4 字节/字符取安全上限 16000
|
|
|
+const API_PARAMS_MAX_LENGTH = 2000
|
|
|
+
|
|
|
+// 是否存在未保存的新增行(同时用于禁用新增按钮,避免同时编辑多行)
|
|
|
+const hasEditingApiRow = computed(() => apiList.value.some(row => row._isNew))
|
|
|
+
|
|
|
+// 接口类型映射(如后端取值不同,按需调整,未匹配时直接展示原值)
|
|
|
+const apiTypeOptions = [
|
|
|
+ { label: '服务器动态信息采集接口', value: 1 },
|
|
|
+ { label: '调用次数采集接口', value: 2 },
|
|
|
+ { label: '服务器静态信息采集接口', value: 3 },
|
|
|
+ { label: '最近一周的接口请求数量', value: 4 },
|
|
|
+ { label: '应用接口列表采集', value: 5 },
|
|
|
+ { label: '应用接口启停', value: 6 }
|
|
|
+]
|
|
|
+
|
|
|
+const formatApiType = val => {
|
|
|
+ const hit = apiTypeOptions.find(item => String(item.value) === String(val))
|
|
|
+ return hit ? hit.label : (val ?? '-')
|
|
|
+}
|
|
|
+
|
|
|
+const loadApiList = async () => {
|
|
|
+ if (!currentService.value?.iden) {
|
|
|
+ ElMessage.warning('当前服务缺少服务标识(iden),无法获取接口配置')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ apiDialogLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await apiListByRegulatedIden(currentService.value.iden)
|
|
|
+ if (res.code === 100000) {
|
|
|
+ apiList.value = (res.data || []).map(item => ({
|
|
|
+ ...item,
|
|
|
+ apiEndpoint: item.apiEndpoint ?? '',
|
|
|
+ method: item.method || 'GET',
|
|
|
+ paramType: item.paramType || 'json',
|
|
|
+ params: item.params ?? '',
|
|
|
+ status: item.status ?? 1,
|
|
|
+ _saving: false,
|
|
|
+ _testing: false,
|
|
|
+ _statusLoading: false
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ apiList.value = []
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ apiList.value = []
|
|
|
+ ElMessage.error(err.message || '获取采集接口配置失败')
|
|
|
+ } finally {
|
|
|
+ apiDialogLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const openDetail = row => {
|
|
|
+ currentService.value = { ...row }
|
|
|
+ apiList.value = []
|
|
|
+ apiDialogVisible.value = true
|
|
|
+ loadApiList()
|
|
|
+}
|
|
|
+
|
|
|
+const onApiDialogClosed = () => {
|
|
|
+ apiList.value = []
|
|
|
+ currentService.value = {}
|
|
|
+ apiDialogLoading.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 列表末尾追加一行待保存的接口配置
|
|
|
+const handleAddApiRow = () => {
|
|
|
+ apiList.value.push({
|
|
|
+ apiType: '',
|
|
|
+ apiEndpoint: '',
|
|
|
+ method: 'GET',
|
|
|
+ paramType: 'json',
|
|
|
+ params: '',
|
|
|
+ status: 1,
|
|
|
+ _saving: false,
|
|
|
+ _testing: false,
|
|
|
+ _statusLoading: false,
|
|
|
+ _isNew: true
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleApiStatusChange = async row => {
|
|
|
+ // el-switch 变更后 v-model 已更新,失败时回滚到相反值即可
|
|
|
+ const nextStatus = row.status
|
|
|
+ const prevStatus = nextStatus === 1 ? 2 : 1
|
|
|
+ row._statusLoading = true
|
|
|
+ try {
|
|
|
+ const res = await apiChangeStatus(row.id, nextStatus)
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success(nextStatus === 1 ? '已启用' : '已停用')
|
|
|
+ } else {
|
|
|
+ row.status = prevStatus
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ row.status = prevStatus
|
|
|
+ ElMessage.error(err.message || '修改启停状态失败')
|
|
|
+ } finally {
|
|
|
+ row._statusLoading = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const validateApiEndpoint = endpoint => {
|
|
|
+ if (!endpoint) {
|
|
|
+ ElMessage.warning('接口地址不能为空')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (endpoint.length > API_ENDPOINT_MAX_LENGTH) {
|
|
|
+ ElMessage.warning(`接口地址不能超过${API_ENDPOINT_MAX_LENGTH}个字符`)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 请求参数需为 JSON 对象字符串(可为空)
|
|
|
+const validateApiParams = params => {
|
|
|
+ if (!params) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ if (params.length > API_PARAMS_MAX_LENGTH) {
|
|
|
+ ElMessage.warning(`请求参数不能超过${API_PARAMS_MAX_LENGTH}个字符`)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(params)
|
|
|
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
|
+ ElMessage.warning('请求参数必须是合法的 JSON 对象字符串')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ } catch {
|
|
|
+ ElMessage.warning('请求参数必须是合法的 JSON 对象字符串')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSaveApi = async row => {
|
|
|
+ if (row._isNew && !row.apiType) {
|
|
|
+ ElMessage.warning('请选择接口类型')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const endpoint = (row.apiEndpoint || '').trim()
|
|
|
+ if (!validateApiEndpoint(endpoint)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const params = (row.params || '').trim()
|
|
|
+ if (!validateApiParams(params)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ row._saving = true
|
|
|
+ try {
|
|
|
+ const reqData = row._isNew
|
|
|
+ ? {
|
|
|
+ regulatedIden: currentService.value.iden,
|
|
|
+ apiType: row.apiType,
|
|
|
+ apiEndpoint: endpoint,
|
|
|
+ method: row.method || 'GET',
|
|
|
+ paramType: row.paramType || 'json',
|
|
|
+ params
|
|
|
+ }
|
|
|
+ : { ...row, apiEndpoint: endpoint, method: row.method || 'GET', paramType: row.paramType || 'json', params, _saving: undefined }
|
|
|
+ const res = await (row._isNew ? apiAdd(reqData) : apiUpdate(reqData))
|
|
|
+ if (res.code === 100000) {
|
|
|
+ if (row._isNew) {
|
|
|
+ ElMessage.success('保存成功')
|
|
|
+ // 保存成功后重新加载列表,新增行会变成带主键的普通行
|
|
|
+ await loadApiList()
|
|
|
+ } else {
|
|
|
+ row.apiEndpoint = endpoint
|
|
|
+ ElMessage.success('保存成功')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ ElMessage.error(err.message || '保存失败')
|
|
|
+ } finally {
|
|
|
+ row._saving = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 测试采集接口连通性(需先保存,后端按 id 读取配置)
|
|
|
+const escapeHtml = value => String(value ?? '-')
|
|
|
+ .replace(/&/g, '&')
|
|
|
+ .replace(/</g, '<')
|
|
|
+ .replace(/>/g, '>')
|
|
|
+ .replace(/"/g, '"')
|
|
|
+
|
|
|
+const handleTestApi = async row => {
|
|
|
+ if (!row.id) {
|
|
|
+ ElMessage.warning('请先保存接口配置后再测试连通')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ row._testing = true
|
|
|
+ try {
|
|
|
+ const res = await appApiTest({
|
|
|
+ regulatedIden: currentService.value.iden,
|
|
|
+ id: row.id
|
|
|
+ })
|
|
|
+ if (res.code === 100000 && res.data) {
|
|
|
+ const result = res.data
|
|
|
+ const lines = [
|
|
|
+ `请求方式:${escapeHtml(result.method)}`,
|
|
|
+ `参数类型:${escapeHtml(result.paramType)}`,
|
|
|
+ `接口地址:${escapeHtml(result.endpoint)}`,
|
|
|
+ `HTTP 状态:${escapeHtml(result.httpStatus)}`,
|
|
|
+ `耗时:${escapeHtml(result.costMs)} ms`,
|
|
|
+ `结果:${result.success ? '成功' : '失败'}`
|
|
|
+ ]
|
|
|
+ if (result.message) {
|
|
|
+ lines.push(`信息:${escapeHtml(result.message)}`)
|
|
|
+ }
|
|
|
+ ElMessageBox.alert(lines.join('<br/>'), '连通性测试结果', {
|
|
|
+ dangerouslyUseHTMLString: true,
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ type: result.success ? 'success' : 'error'
|
|
|
+ })
|
|
|
+ } else if (res.code === 100000) {
|
|
|
+ ElMessage.success(res.msg || res.message || '接口连接正常')
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ ElMessage.error(err.message || '接口连接失败')
|
|
|
+ } finally {
|
|
|
+ row._testing = false
|
|
|
+ }
|
|
|
+}
|
|
|
+/* ----------------------- 采集接口配置 end ----------------------- */
|
|
|
+
|
|
|
+/* ------------------------- 应用接口管理 ------------------------- */
|
|
|
+const appApiDialogVisible = ref(false)
|
|
|
+const appApiLoading = ref(false)
|
|
|
+const appApiList = ref([])
|
|
|
+
|
|
|
+const loadAppApiList = async () => {
|
|
|
+ if (!currentService.value?.iden) {
|
|
|
+ ElMessage.warning('当前服务缺少服务标识(iden),无法获取应用接口列表')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ appApiLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await appApiListApi({ regulatedIden: currentService.value.iden })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ appApiList.value = (res.data?.list || []).map(item => ({
|
|
|
+ ...item,
|
|
|
+ _statusLoading: false
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ appApiList.value = []
|
|
|
+ ElMessage.error(err.message || '获取应用接口列表失败')
|
|
|
+ } finally {
|
|
|
+ appApiLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const openAppApiDialog = row => {
|
|
|
+ currentService.value = { ...row }
|
|
|
+ appApiList.value = []
|
|
|
+ appApiDialogVisible.value = true
|
|
|
+ loadAppApiList()
|
|
|
+}
|
|
|
+
|
|
|
+const onAppApiDialogClosed = () => {
|
|
|
+ appApiList.value = []
|
|
|
+ appApiLoading.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const handleAppApiStatusChange = async row => {
|
|
|
+ // el-switch 变更后 v-model 已更新,失败时回滚到相反值即可
|
|
|
+ const nextStatus = row.status
|
|
|
+ const prevStatus = nextStatus === 1 ? 0 : 1
|
|
|
+ row._statusLoading = true
|
|
|
+ try {
|
|
|
+ const res = await appApiToggle({
|
|
|
+ id: row.id,
|
|
|
+ regulatedIden: currentService.value.iden
|
|
|
+ })
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success(nextStatus === 1 ? '已启用' : '已停用')
|
|
|
+ } else {
|
|
|
+ row.status = prevStatus
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ row.status = prevStatus
|
|
|
+ ElMessage.error(err.message || '修改启停状态失败')
|
|
|
+ } finally {
|
|
|
+ row._statusLoading = false
|
|
|
+ }
|
|
|
+}
|
|
|
+/* ----------------------- 应用接口管理 end ----------------------- */
|
|
|
+
|
|
|
+const addServiceDialog = () => {
|
|
|
+ addServiceDialogVisible.value = true
|
|
|
+ isEditMode.value = false
|
|
|
+ addServiceForm.value = {
|
|
|
+ name: '',
|
|
|
+ ip: '',
|
|
|
+ port: '',
|
|
|
+ type: ''
|
|
|
+ }
|
|
|
+ // 重置表单验证状态
|
|
|
+ addServiceFormRef.value?.clearValidate()
|
|
|
+}
|
|
|
+
|
|
|
+const submitAddServiceForm = () => {
|
|
|
+ addServiceFormRef.value.validate(async valid => {
|
|
|
+ if (valid) {
|
|
|
+ if (isEditMode.value) {
|
|
|
+ const res = await serviceUpdate(addServiceForm.value)
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success('编辑服务成功')
|
|
|
+ addServiceDialogVisible.value = false
|
|
|
+ isEditMode.value = false
|
|
|
+ await getTableData()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 调用添加服务的接口
|
|
|
+ const res = await serviceAdd(addServiceForm.value)
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success('添加服务成功')
|
|
|
+ addServiceDialogVisible.value = false
|
|
|
+ await getTableData()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const isEditMode = ref(false)
|
|
|
+
|
|
|
+const handleUpdate = row => {
|
|
|
+ isEditMode.value = true
|
|
|
+ addServiceDialogVisible.value = true
|
|
|
+ addServiceForm.value = { ...row }
|
|
|
+ // 重置表单验证状态
|
|
|
+ addServiceFormRef.value?.clearValidate()
|
|
|
+}
|
|
|
+
|
|
|
+const handleDelete = row => {
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除服务【${row.name}】吗?`,
|
|
|
+ '提示',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(async () => {
|
|
|
+ // 调用删除服务的接口
|
|
|
+ const res = await serviceDelete(row.iden)
|
|
|
+ if (res.code === 100000) {
|
|
|
+ ElMessage.success('删除服务成功')
|
|
|
+ await getTableData()
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage.info('已取消删除')
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+// 态势感知(占位,按需实现)
|
|
|
+const handleVerify = row => {
|
|
|
+ router.push({ name: 'SA', query: { regulatedIden: row.iden } })
|
|
|
+}
|
|
|
+
|
|
|
+const addServiceDialogVisible = ref(false)
|
|
|
+const addServiceForm = ref({
|
|
|
+ name: '',
|
|
|
+ ip: '',
|
|
|
+ port: '',
|
|
|
+ type: ''
|
|
|
+})
|
|
|
+const addServiceFormRef = ref(null)
|
|
|
+const createRules = {
|
|
|
+ name: [{ required: true, message: '请输入服务名称', trigger: 'blur' }, { max: 20, message: '服务名称不能超过20个字符', trigger: 'blur' }],
|
|
|
+ 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' }],
|
|
|
+ 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' }],
|
|
|
+ type: [{ required: true, message: '请选择服务类型', trigger: 'blur' }]
|
|
|
+}
|
|
|
+
|
|
|
+const serviceTypeOptions = [
|
|
|
+ { label: '密码机', value: 3 },
|
|
|
+ { label: '签名验签服务器', value: 7 },
|
|
|
+ { label: '安全接入网关', value: 8 }
|
|
|
+]
|
|
|
+const formatDateTime = value => value ? String(value).replace('T', ' ') : ''
|
|
|
+
|
|
|
+const getTableData = async () => {
|
|
|
+ try {
|
|
|
+ const res = await serviceList({ pageNum: page.value, pageSize: pageSize.value, searchCount: true, ...searchInfo.value })
|
|
|
+ tableData.value = res.data?.list || []
|
|
|
+ total.value = res.data?.total || 0
|
|
|
+ } catch (err) {
|
|
|
+ ElMessage.error(err.message || '获取印章列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+const onSubmit = () => {
|
|
|
+ page.value = 1
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+const onReset = () => {
|
|
|
+ searchInfo.value = {}
|
|
|
+ onSubmit()
|
|
|
+}
|
|
|
+const handleSizeChange = val => {
|
|
|
+ pageSize.value = val
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+const handleCurrentChange = val => {
|
|
|
+ page.value = val
|
|
|
+ getTableData()
|
|
|
+}
|
|
|
+getTableData()
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.el-select,
|
|
|
+.el-date-editor {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.api-dialog-tip {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+
|
|
|
+ &__name {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.api-dialog-actions {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+</style>
|