소스 검색

添加服务管理

zhaoke 2 년 전
부모
커밋
83a153befe

+ 1 - 0
src/getMenu.ts

@@ -45,6 +45,7 @@ router.beforeEach(async (to: any, from, next: any) => {
             const treeMenu = setTree(menusArray, 'up_auth_id', 'auth_id')
             const dataMenu = _.unionBy(treeMenu, 'auth_id')
             const menus = parseMenu(dataMenu)
+            console.log(menus)
             // const { roles }: any = await userStore.getInfo()
             // accessRoutes = await permissionStore.generateRoutes(roles)
             accessRoutes = menus

+ 135 - 0
src/views/systemSettings/components/AutoForm.vue

@@ -0,0 +1,135 @@
+<template>
+  <el-form :model="model" :label-width="labelWidth">
+    <el-row :gutter="20">
+      <el-col
+        v-for="(item, index) in filteredFormItems"
+        :key="index"
+        :span="24 / columnNumber"
+      >
+        <el-form-item :label="item.columnLabel" size="default">
+          <template v-if="typeof item.listqueryTemplateID === 'number'">
+            <el-select
+              v-model="model[correctKey(item)]"
+              size="small"
+              filterable
+              default-first-option
+              style="width: 100%"
+              placeholder="请选择"
+              clearable
+              @change="
+                val => {
+                  selectChangeHandler(val, model[correctKey(item)])
+                }
+              "
+            >
+              <el-option
+                v-for="option in selectOptionMap[item.listqueryTemplateID]"
+                :key="option[option.setvalue]"
+                :label="option[option.setlabel]"
+                :value="option[option.setvalue]"
+              />
+            </el-select>
+          </template>
+          <template v-else-if="item.dataType == 'longtext'">
+            <el-input
+              v-model="model[item.columnName]"
+              type="textarea"
+              size="small"
+              :rows="1"
+              @change="inputChangeHandler"
+            />
+          </template>
+          <template v-else-if="item.dataType == 'datetime'">
+            <el-date-picker
+              v-model="model[item.columnName]"
+              type="datetime"
+              format="yyyy-MM-dd HH:mm:ss"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              :rows="1"
+              placeholder="选择日期时间"
+              @change="inputChangeHandler"
+            />
+          </template>
+          <template v-else-if="item.dataType == 'number'">
+            <el-input
+              v-model.number="model[item.columnName]"
+              size="small"
+              onkeyup="value=value.replace(/[^1-9]/g,'')"
+              @change="inputChangeHandler"
+            />
+          </template>
+          <template v-else>
+            <el-input
+              v-model="model[item.columnName]"
+              size="small"
+              @change="inputChangeHandler"
+            />
+          </template>
+        </el-form-item>
+      </el-col>
+    </el-row>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { PropType } from 'vue'
+import { CommonTableColumn, SelectOption, CommonData } from '~/common'
+
+const props = defineProps({
+  model: {
+    type: Object as PropType<CommonData>,
+    required: true,
+  },
+  items: {
+    type: Array as PropType<CommonTableColumn[]>,
+    required: true,
+  },
+  columnNumber: {
+    type: Number as PropType<1 | 2>,
+    default: 1,
+  },
+  selectOptionMap: {
+    type: Object as PropType<{ [id: number]: SelectOption[] }>,
+    required: true,
+  },
+  labelWidth: {
+    type: [String, Number],
+    default: 80,
+  },
+})
+
+const filteredFormItems = computed(() =>
+  props.items.filter(item => item.needShow)
+)
+
+const correctKey = (item: CommonTableColumn) => {
+  if (typeof item.listqueryTemplateID === 'number') {
+    return props.selectOptionMap[item.listqueryTemplateID][0].setvalue
+  } else {
+    return item.columnName
+  }
+}
+
+watchEffect(() => {
+  filteredFormItems.value.forEach(item => {
+    if (!props.model[correctKey(item)]) {
+      props.model[correctKey(item)] = null
+    }
+  })
+})
+
+
+const selectChangeHandler = (val, key) => {
+  if (val === '') {
+    props.model[key] = null
+  }
+}
+
+const inputChangeHandler = (val, key) => {
+  if (val === '') {
+    props.model[key] = null
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 783 - 0
src/views/systemSettings/serviceManagement/index.vue

@@ -0,0 +1,783 @@
+<template>
+  <div class="serivce-home scroll-y">
+    <div class="wrap">
+      <MinHeader
+        :is-auth="true"
+        :is-statuser="true"
+        @addForm="addServiceDialogShow"
+      >
+        <template #header>
+          <div class="status flex-wrap">
+            <div class="manageTitle">服务管理</div>
+            <div class="status0"><span class="icon"></span>启动</div>
+            <div class="status2"><span class="icon"></span>异常</div>
+            <div class="status1"><span class="icon"></span>停止</div>
+          </div>
+        </template>
+      </MinHeader>
+      <div class="app-containers">
+        <DataTable
+          :is-Status="true"
+          :table-header="tableColumns"
+          :table-data="tableData"
+          :table-btn-group="tableBtnGroup"
+          btn-group-width="310px"
+          :table-property="{ rowKey: 'ID' }"
+          @btn-click="tableButtonClickHandler"
+        />
+      </div>
+      <Dialog
+        :width="serviceDialogWidth"
+        :flag="serviceDialogVisible"
+        :type="serviceDialogType"
+        :del-name="delName"
+        :msg-title="msgTitle"
+        @del-remove="delSubmitHandler"
+        @del-rest="delDialogHide"
+        @submit-form="serviceSubmitHandler"
+        @reset-form="addServiceDialogHide"
+      >
+        <el-form
+          ref="serviceFormRef"
+          class="service-form"
+          :model="serviceForm"
+          :rules="serviceFormRules"
+          label-position="right"
+          label-width="90px"
+          size="mini"
+        >
+          <el-row :gutter="36" type="flex">
+            <el-col :span="12">
+              <el-form-item label="服务名称" prop="serviceName">
+                <el-input
+                  v-model="serviceForm.serviceName"
+                  placeholder="请输入服务名称"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item
+                label="前序输出编号"
+                label-width="120px"
+                prop="serviceOutputID"
+              >
+                <el-input
+                  v-model="serviceForm.serviceOutputID"
+                  placeholder="请输入前序输出编号"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="类型" prop="serviceType">
+                <el-select v-model="serviceForm.serviceType" clearable>
+                  <el-option :value="1" label="管理前端" />
+                  <el-option :value="2" label="管理后端" />
+                  <el-option :value="3" label="业务前端" />
+                  <el-option :value="4" label="业务后端" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label-width="14px">
+                <el-radio-group v-model="serviceForm.isAsynchronous">
+                  <el-radio :label="1">同步</el-radio>
+                  <el-radio :label="0">异步</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <div class="flex">
+                <el-form-item label="数据来源" prop="dataSourceID">
+                  <el-select v-model="serviceForm.dataSourceID" clearable>
+                    <el-option
+                      v-for="dataSource in selectOptionMap[
+                        queryTemplateIDMap.dataSourceName
+                      ]"
+                      :key="dataSource.dataSourceID"
+                      :value="dataSource.dataSourceID"
+                      :label="dataSource.dataSourceName"
+                    />
+                  </el-select>
+                </el-form-item>
+                <el-button
+                  type="primary"
+                  size="small"
+                  style="height: 28px; line-height: 0px"
+                  @click="addDataSourceDialogShow"
+                  >新增</el-button
+                >
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item
+                label="数据来源对象"
+                label-width="120px"
+                prop="sourceObjectName"
+              >
+                <el-input
+                  v-model="serviceForm.sourceObjectName"
+                  placeholder="请输入数据来源对象名称"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="服务描述" prop="serviceDescribe">
+                <el-input
+                  v-model="serviceForm.serviceDescribe"
+                  placeholder="请输入描述"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item
+                label="生命周期ID键名"
+                prop="lifeCycleCol"
+                label-width="120px"
+              >
+                <el-input
+                  v-model="serviceForm.lifeCycleCol"
+                  placeholder="请输入生命周期ID键名"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item label="取值规则" prop="computingMethod">
+                <el-input
+                  v-model="serviceForm.computingMethod"
+                  placeholder="请输入取值规则"
+                  type="textarea"
+                  :autosize="{ minRows: 4, maxRows: 4 }"
+                  resize="none"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item label="检测规则" prop="validationExpression">
+                <el-input
+                  v-model="serviceForm.validationExpression"
+                  placeholder="请输入检测规则"
+                  type="textarea"
+                  :autosize="{ minRows: 4, maxRows: 4 }"
+                  resize="none"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="启动时间" prop="startTime">
+                <el-date-picker
+                  v-model="serviceForm.startTime"
+                  type="datetime"
+                  format="YYYY-MM-DD HH:mm"
+                  value-format="YYYY-MM-DD HH:mm"
+                  placeholder="请选择启动时间"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="停止时间" prop="stopTime">
+                <el-date-picker
+                  v-model="serviceForm.stopTime"
+                  type="datetime"
+                  format="YYYY-MM-DD HH:mm"
+                  value-format="YYYY-MM-DD HH:mm"
+                  placeholder="请选择停止时间"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item
+                label="失败重试次数"
+                prop="retryCount"
+                label-width="100px"
+              >
+                <el-input
+                  v-model="serviceForm.retryCount"
+                  type="number"
+                  placeholder="请输入重试次数"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="循环次数" prop="loopCount">
+                <el-input
+                  v-model="serviceForm.loopCount"
+                  type="number"
+                  placeholder="请输入循环次数"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="循环频率" prop="frequencyCount">
+                <el-input
+                  v-model="serviceForm.frequencyCount"
+                  type="number"
+                  placeholder="请输入循环频率"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item
+                label="循环频率单位"
+                prop="frequencyUnit"
+                label-width="100px"
+              >
+                <el-select v-model="serviceForm.frequencyUnit" clearable>
+                  <el-option label="天" :value="24 * 60 * 60 * 1000" />
+                  <el-option label="小时" :value="60 * 60 * 1000" />
+                  <el-option label="分钟" :value="60 * 1000" />
+                  <el-option label="秒" :value="1000" />
+                  <el-option label="毫秒" :value="1" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item
+                label="日志存储位置"
+                label-width="100px"
+                prop="logDataSourceID"
+              >
+                <el-select v-model="serviceForm.logDataSourceID">
+                  <el-option
+                    v-for="dataSource in selectOptionMap[
+                      queryTemplateIDMap.dataSourceName
+                    ]"
+                    :key="dataSource.dataSourceID"
+                    :value="dataSource.dataSourceID"
+                    :label="dataSource.dataSourceName"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item
+                label="日志过滤条件"
+                label-width="100px"
+                prop="logList"
+              >
+                <el-input
+                  v-model="serviceForm.logList"
+                  placeholder="请输入日志过滤条件"
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </Dialog>
+      <Dialog
+        type="add"
+        width="500px"
+        :flag="dataSourceDialogVisible"
+        msg-title="新增数据来源"
+        @submit-form="dataSourceSubmitHandler"
+        @reset-form="dataSourceDialogHide"
+      >
+        <el-form ref="sourceFormRef" label-width="100px" :model="sourceForm">
+          <el-form-item label="数据源名称">
+            <el-input v-model="sourceForm.dataSourceName" size="small" />
+          </el-form-item>
+          <el-form-item label="协议名称">
+            <el-select
+              size="small"
+              style="width: 100%"
+              v-model="sourceForm.protocolID"
+              placeholder="请选择活动区域"
+            >
+              <el-option
+                v-for="protocol in selectOptionMap[
+                  queryTemplateIDMap.protocolName
+                ]"
+                :key="protocol.protocolID"
+                :label="protocol.protocolName"
+                :value="protocol.protocolID"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="连接参数">
+            <el-input
+              size="small"
+              type="textarea"
+              rows="3"
+              v-model="sourceForm.connectConfig"
+            />
+          </el-form-item>
+        </el-form>
+      </Dialog>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import DataTable from '@/components/tableTemp/index.vue'
+import MinHeader from '@/components/minheader/index.vue'
+import Dialog from '@/components/dialog/index.vue'
+import { ElMessage, FormInstance } from 'element-plus'
+import {
+  CommonTableColumn,
+  CommonQueryResult,
+  SelectOption,
+  SelectOptionQueryResult,
+} from '~/common'
+import request from '@/utils/axiosReq'
+import { Query, GeneralDataReception } from '@/api/webApi'
+
+const router = useRouter()
+
+const queryTemplateIDMap = reactive<{
+  [x: string]: number
+}>({})
+const selectOptionMap = reactive<{
+  [id: number]: SelectOption[]
+}>({})
+const getSelectOptionOfColumn = (columns: CommonTableColumn[]) => {
+  columns.forEach(async ({ columnName, listqueryTemplateID }) => {
+    if (listqueryTemplateID !== null && !queryTemplateIDMap[columnName]) {
+      queryTemplateIDMap[columnName] = listqueryTemplateID
+      if (!selectOptionMap[listqueryTemplateID]) {
+        selectOptionMap[listqueryTemplateID] = []
+        selectOptionMap[listqueryTemplateID] = await getSelectOptions(
+          listqueryTemplateID
+        )
+      }
+    }
+  })
+}
+const getSelectOptions = async (id: number) => {
+  try {
+    const {
+      code,
+      returnData: { listValues },
+      message,
+    }: SelectOptionQueryResult = await Query({
+      id,
+      dataContent: [],
+    })
+    if (Number(code) !== 0) {
+      throw new Error(message ?? '失败')
+    }
+    const options = listValues.map(({ k, v, setlabel, setvalue }) => ({
+      k,
+      v,
+      setlabel,
+      setvalue,
+      [setlabel]: k,
+      [setvalue]: v,
+    }))
+    return options
+  } catch (error) {
+    console.error(error)
+    return []
+  }
+}
+onMounted(async () => {
+  queryTemplateIDMap.protocolName = DATACONTENT_ID.protocolOptions
+  selectOptionMap[queryTemplateIDMap.protocolName] = await getSelectOptions(
+    queryTemplateIDMap.protocolName
+  )
+})
+
+// 表格
+const tableColumns = ref<CommonTableColumn[]>([])
+const tableData = ref<any[]>([])
+const page = ref(0)
+const noMore = ref(false)
+const rowTitle = ref('')
+onMounted(() => {
+  getTableData()
+})
+const resetTable = () => {
+  page.value = 0
+  noMore.value = false
+  tableData.value = []
+  getTableData()
+}
+const getTableData = async () => {
+  try {
+    const {
+      code,
+      returnData: { columnSet, listValues },
+      message,
+    }: CommonQueryResult = await Query({
+      id: DATACONTENT_ID.sysServiceTable,
+      dataContent: [],
+      needPage: ++page.value,
+    })
+    if (Number(code) !== 0) {
+      throw new Error(message || '失败')
+    }
+    if (listValues.length === 0) {
+      page.value--
+      noMore.value = false
+    }
+    const titleColumn = columnSet.find(column => column.needShow === 1)
+    if (titleColumn) {
+      rowTitle.value = titleColumn.columnName
+    }
+    getSelectOptionOfColumn(columnSet)
+    tableColumns.value = columnSet
+    tableData.value.push(...listValues)
+  } catch (error) {
+    page.value--
+    console.error(error)
+  }
+}
+const tableBtnGroup = [
+  {
+    name: '查看',
+    className: 'editBtn',
+    param: 1,
+  },
+  {
+    name: '编辑',
+    className: 'editBtn',
+    param: 2,
+  },
+  {
+    name: '停止',
+    className: 'editBtn',
+    param: 3,
+  },
+  {
+    name: '删除',
+    className: 'delBtn',
+    param: 4,
+  },
+]
+const tableButtonClickHandler = (rowIndex: number, row: any, param: number) => {
+  switch (param) {
+    case 1:
+      router.push({
+        path: '/systemSettings/serviceTopology',
+        query: {
+          serviceID: row['serviceID'],
+          serviceName: row['serviceName'],
+        },
+      })
+      break
+    case 2:
+      router.push({
+        path: '/systemSettings/serviceEdit',
+        query: {
+          serviceID: row['serviceID'],
+          runState: row['runState'],
+        },
+      })
+      break
+    case 3:
+      changeServiceState(row['serviceID'], row['runState'])
+      break
+    case 4:
+      delID = row['serviceID']
+      delDialogShow(row)
+      break
+    default:
+      break
+  }
+}
+const changeServiceState = async (serviceID: number, runState: string) => {
+  const operate = runState === '运行' ? 'stop' : 'start'
+  try {
+    const {
+      code,
+      returnData: { listValues },
+      message,
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceNodeList,
+      dataContent: [serviceID],
+    })
+    if (Number(code) !== 0) {
+      throw new Error(message ?? '失败')
+    }
+    if (!listValues.length) {
+      throw new Error('无部署机器')
+    }
+    Promise.all(
+      listValues.map(node => {
+        request({
+          url: `${node.serviceURL}${operate}`,
+          method: 'post',
+          data: {
+            serviceId: serviceID,
+          },
+        })
+      })
+    )
+      .then((results: any) => {
+        if (results.every(result => Number(result.code) === 0)) {
+          ElMessage.success('操作成功')
+        } else {
+          ElMessage.error('操作失败')
+        }
+      })
+      .catch(error => {
+        throw new Error(error)
+      })
+  } catch (error) {
+    console.error(error)
+  }
+}
+
+// 新增/删除服务-弹窗
+const serviceDialogVisible = ref(false) // 弹窗开关
+const serviceDialogType = ref('') // 弹窗类型是否删除
+const serviceDialogWidth = computed(() =>
+  serviceDialogType.value === 'del' ? '600px' : '878px'
+)
+const delName = ref('') // 删除对象名
+let delID // 删除对象ID
+const msgTitle = ref('') // 服务弹窗-标题
+const delDialogShow = row => {
+  serviceDialogType.value = 'del'
+  msgTitle.value = '删除服务'
+  delName.value = row['serviceName']
+  serviceDialogVisible.value = true
+}
+const delDialogHide = () => {
+  serviceDialogVisible.value = false
+}
+const delSubmitHandler = async () => {
+  try {
+    await deleteService()
+    delDialogHide()
+    resetTable()
+  } catch (error) {
+    console.error(error)
+  }
+}
+const deleteService = async () => {
+  try {
+    const dataContent = {
+      serviceID: delID,
+      event: 3,
+    }
+    const { code, message } = await GeneralDataReception({
+      serviceID: SERVICE_ID.sysServiceEdit,
+      dataContent: JSON.stringify(dataContent),
+    })
+    if (Number(code) === 0) {
+      ElMessage.success(message ?? '成功')
+    } else {
+      throw new Error(message ?? '失败')
+    }
+  } catch (error) {
+    return Promise.reject(error)
+  }
+}
+const addServiceDialogShow = () => {
+  serviceDialogType.value = 'add'
+  msgTitle.value = '新增服务管理'
+  serviceDialogVisible.value = true
+  console.log(queryTemplateIDMap, selectOptionMap)
+}
+const addServiceDialogHide = () => {
+  serviceFormRef.value?.resetFields()
+  serviceDialogVisible.value = false
+}
+// 新增服务-表单
+const serviceFormRef = ref<FormInstance | null>(null)
+const serviceForm = reactive({
+  serviceName: '',
+  serviceOutputID: null,
+  serviceType: null,
+  dataSourceID: null,
+  sourceObjectName: '',
+  lifeCycleCol: '',
+  isAsynchronous: 1,
+  threads: null,
+  datatype: null,
+  computingMethod: '',
+  validationExpression: '',
+  startTime: null,
+  stopTime: null,
+  retryCount: null,
+  loopCount: null,
+  frequencyCount: null,
+  frequencyUnit: null,
+  serviceDescribe: '',
+  logDataSourceID: null,
+  logList: '',
+})
+const serviceFormRules = {
+  serviceName: [
+    {
+      required: true,
+      message: '请输入服务名称',
+      trigger: ['change', 'blur'],
+    },
+  ],
+}
+const serviceSubmitHandler = () => {
+  serviceFormRef.value!.validate(async valid => {
+    if (valid) {
+      try {
+        await createService()
+        addServiceDialogHide()
+        resetTable()
+      } catch (error) {
+        console.error(error)
+      }
+    }
+  })
+}
+const createService = async () => {
+  try {
+    Object.keys(serviceForm).forEach(key => {
+      if (
+        ![
+          'sourceObjectName',
+          'lifeCycleCol',
+          'computingMethod',
+          'validationExpression',
+          'logList',
+        ].includes(key) &&
+        serviceForm[key] === ''
+      ) {
+        serviceForm[key] = null
+      }
+    })
+    const dataContent = { ...serviceForm, event: 1 }
+    const { code, message } = await GeneralDataReception({
+      serviceID: SERVICE_ID.sysServiceEdit,
+      dataContent: JSON.stringify(dataContent),
+    })
+    if (Number(code) === 0) {
+      ElMessage.success(message ?? '成功')
+    } else {
+      throw new Error(message ?? '失败')
+    }
+  } catch (error) {
+    return Promise.reject(error)
+  }
+}
+
+// 新增数据来源-弹窗
+const dataSourceDialogVisible = ref(false)
+const addDataSourceDialogShow = () => {
+  dataSourceDialogVisible.value = true
+}
+const dataSourceDialogHide = () => {
+  sourceFormRef.value?.resetFields()
+  dataSourceDialogVisible.value = false
+}
+// 新增数据来源-表单
+const sourceFormRef = ref<FormInstance | null>(null)
+const sourceForm = reactive({
+  dataSourceName: null,
+  protocolID: null,
+  connectConfig: null,
+})
+const dataSourceSubmitHandler = () => {
+  dataSourceDialogHide()
+}
+</script>
+<style lang="scss" scoped>
+.app-containers {
+  height: calc(100vh - 180px);
+}
+
+.icon {
+  width: 14px;
+  height: 14px;
+  background: #2d67e3;
+  border-radius: 2px;
+  display: inline-block;
+  vertical-align: middle;
+  margin-right: 10px;
+  position: relative;
+  top: -2px;
+}
+.status0,
+.status2 {
+  margin-right: 28px;
+  position: relative;
+  top: 5px;
+}
+.status1 {
+  position: relative;
+  top: 5px;
+  .icon {
+    background-color: #afb4bf;
+  }
+}
+.status2 {
+  .icon {
+    background-color: #eb2f3b;
+  }
+}
+
+.service-form:deep {
+  display: flex;
+  flex-wrap: wrap;
+  padding-left: 20px;
+  > .el-row {
+    flex-wrap: wrap;
+    > .el-col .el-form-item {
+      margin-bottom: 20px;
+      .el-form-item__label {
+        padding-right: 16px;
+      }
+      .el-input,
+      .el-select {
+        width: 100%;
+      }
+      .el-input__inner,
+      .el-textarea__inner {
+        padding-left: 8px;
+      }
+      .el-date-editor {
+        .el-input__prefix,
+        .el-input__suffix {
+          right: 5px;
+          left: unset;
+          font-size: 16px;
+          color: #101116;
+          .el-input__suffix-inner {
+            font-size: 16px;
+          }
+        }
+        &.value-not-null:hover .el-input__prefix {
+          display: none;
+        }
+      }
+    }
+  }
+  label,
+  span,
+  button,
+  input,
+  optgroup,
+  select,
+  textarea {
+    font-family: Helvetica, Microsoft YaHei;
+    font-size: 14px;
+    font-weight: 400;
+  }
+  label {
+    color: #303133;
+  }
+  input {
+    color: #101116;
+    &::-webkit-outer-spin-button,
+    &::-webkit-inner-spin-button {
+      appearance: none;
+      -webkit-appearance: none !important;
+      &[type='number'] {
+        appearance: textfield;
+        -moz-appearance: textfield !important;
+      }
+    }
+    .el-radio {
+      font-weight: 400;
+    }
+  }
+}
+</style>

+ 863 - 0
src/views/systemSettings/serviceManagement/serviceEdit.vue

@@ -0,0 +1,863 @@
+<template>
+  <div class="service-edit">
+    <div class="service-basic-wrapper">
+      <Minheader is-journal is-statuser is-slot is-preser @add-journal-form="logDialogShow" @add-slot-form="slotDialogShow" @preser-form="serviceSubmitHandler">
+        <template #header>
+          <div class="status flex-wrap">
+            <div class="manageTitle">当前服务ID:{{ serviceID }}</div>
+            <div class="service-state">{{ serviceState }}</div>
+          </div>
+        </template>
+      </Minheader>
+      <div class="form-wrapper">
+        <el-form :model="serviceForm" class="w100 flex-wrap" label-width="100px">
+          <div class="w20 pd30">
+            <el-form-item label="名称" prop="serviceName" size="default">
+              <el-input v-model="serviceForm.serviceName" placeholder="请输入服务名称" clearable />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30 flex">
+            <el-form-item label="业务类型" prop="serviceType" size="default">
+              <el-select v-model="serviceForm.serviceType" style="width: 100px">
+                <el-option :value="1" label="管理前端" />
+                <el-option :value="2" label="管理后端" />
+                <el-option :value="3" label="业务前端" />
+                <el-option :value="4" label="业务后端" />
+              </el-select>
+            </el-form-item>
+            <el-form-item class="isAsynchronous" prop="isAsynchronous" size="default" label-width="0">
+              <el-select v-model="serviceForm.isAsynchronous" style="width: 80px">
+                <el-option :value="1" label="同步"></el-option>
+                <el-option :value="0" label="异步"></el-option>
+              </el-select>
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="启动时间" prop="hasStartTime" size="default">
+              <el-date-picker v-model="serviceForm.hasStartTime" type="datetime" format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm" placeholder="请选择启动时间" />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="停止时间" prop="hasEndTime" size="default">
+              <el-date-picker v-model="serviceForm.hasEndTime" type="datetime" format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm" placeholder="请选择停止时间" />
+            </el-form-item>
+          </div>
+          <div class="w20">
+            <el-form-item label="前序输出编号" prop="serviceOutputID" size="default">
+              <el-input v-model="serviceForm.serviceOutputID" placeholder="请输入前序输出编号" clearable />
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
+    </div>
+    <div class="service-config-wrapper">
+      <Minheader is-statuser>
+        <template #header>
+          <div class="status flex-wrap">
+            <div class="manageTitle">主动采集配置</div>
+          </div>
+        </template>
+      </Minheader>
+      <div class="form-wrapper">
+        <el-form :model="serviceForm" class="w100 flex-wrap" label-width="100px">
+          <div class="w20 pd30">
+            <el-form-item v-if="selectOptionMap[queryTemplateIDMap.dataSourceID]" label="数据源" prop="dataSourceID" size="default">
+              <el-select v-model="serviceForm.dataSourceID" clearable>
+                <el-option v-for="option in selectOptionMap[
+                    queryTemplateIDMap.dataSourceID
+                  ]" :key="option[option.setvalue]" :value="option[option.setvalue]" :label="option[option.setlabel]" />
+              </el-select>
+            </el-form-item>
+          </div>
+          <div class="w40">
+            <el-form-item label="详细位置" prop="sourceObjectName" size="default">
+              <el-input v-model="serviceForm.sourceObjectName" placeholder="请输入详细位置" clearable />
+            </el-form-item>
+          </div>
+          <div class="w40">
+            <el-form-item label="服务描述" prop="serviceDescribe" size="default">
+              <el-input v-model="serviceForm.serviceDescribe" placeholder="请输入服务描述" clearable />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="计划启动时间" prop="startTime" size="default">
+              <el-date-picker v-model="serviceForm.startTime" type="datetime" format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm" placeholder="请选择启动时间" />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="计划停止时间" prop="stopTime" size="default">
+              <el-date-picker v-model="serviceForm.stopTime" type="datetime" format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm" placeholder="请选择停止时间" />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="错误重试" prop="retryCount" size="default">
+              <el-input v-model="serviceForm.retryCount" placeholder="请输入重试次数" clearable />
+            </el-form-item>
+          </div>
+          <div class="w20 pd30">
+            <el-form-item label="循环次数" prop="loopCount" size="default">
+              <el-input v-model="serviceForm.loopCount" placeholder="请输入循环次数" clearable />
+            </el-form-item>
+          </div>
+          <div class="w20">
+            <el-form-item label="循环频率" prop="frequencyCount" size="default">
+              <el-input v-model="serviceForm.frequencyCount" placeholder="请输入循环频率" clearable />
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
+    </div>
+    <div class="service-receive-wrapper">
+      <Minheader is-statuser>
+        <template #header>
+          <div class="status flex-wrap">
+            <div class="manageTitle">统一接收</div>
+          </div>
+        </template>
+      </Minheader>
+      <div class="form-wrapper">
+        <el-form :model="serviceForm" class="w100 flex-wrap" label-width="100px">
+          <div class="w50 pd30">
+            <el-form-item label="生命周期编号" prop="lifeCycleCol" size="default">
+              <el-input v-model="serviceForm.lifeCycleCol" placeholder="请输入生命周期编号" clearable />
+            </el-form-item>
+          </div>
+          <div class="w50 pd30"></div>
+          <div class="w50 pd30">
+            <el-form-item label="取值表达式" prop="computingMethod" size="default">
+              <el-input v-model="serviceForm.computingMethod" placeholder="请输入取值表达式" type="textarea" :autosize="{
+                  minRows: 2,
+                  maxRows: 2,
+                }" resize="none" clearable />
+            </el-form-item>
+          </div>
+          <div class="w50">
+            <el-form-item label="检测表达式" prop="validationExpression" size="default">
+              <el-input v-model="serviceForm.validationExpression" placeholder="请输入检测表达式" type="textarea" :autosize="{
+                  minRows: 2,
+                  maxRows: 2,
+                }" resize="none" clearable />
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
+    </div>
+    <div class="service-output-wrapper">
+      <el-row :gutter="24">
+        <el-col :span="12">
+          <Minheader :is-statuser="true" :is-auth="true" @addForm="outputDialogShow">
+            <template #header>
+              <div class="status flex-wrap">
+                <div class="manageTitle">输出</div>
+              </div>
+            </template>
+          </Minheader>
+          <div class="app-containers">
+            <DataTable :table-header="outputTableColumns" :table-data="outputTableData" :table-btn-group="outputTableButtonGroup" @btnClick="outputTableButtonClickHandler" />
+          </div>
+        </el-col>
+        <el-col :span="12">
+          <Minheader :is-statuser="true">
+            <template #header>
+              <div class="status flex-wrap">
+                <div class="manageTitle">日志记录</div>
+              </div>
+            </template>
+          </Minheader>
+          <div class="form-wrapper">
+            <el-form :model="serviceForm" class="w100 flex-wrap" label-width="110px">
+              <div class="w50 pd30">
+                <el-form-item v-if="selectOptionMap[queryTemplateIDMap.dataSourceID]" label="日志存储数据源" prop="logDataSourceID" size="default">
+                  <el-select v-model="serviceForm.logDataSourceID">
+                    <el-option v-for="option in selectOptionMap[
+                        queryTemplateIDMap.dataSourceID
+                      ]" :key="option[option.setvalue]" :value="option[option.setvalue]" :label="option[option.setlabel]" />
+                  </el-select>
+                </el-form-item>
+              </div>
+              <div class="w50 pd30">
+                <el-form-item label="详细位置" prop="logDataSourceName" size="default">
+                  <el-input v-model="serviceForm.logDataSourceName" placeholder="请输入详细位置" clearable />
+                </el-form-item>
+              </div>
+              <div class="w100 pd30">
+                <el-form-item label="日志输出条件" prop="logList" size="default">
+                  <el-input v-model="serviceForm.logList" placeholder="请输入日志输出条件" type="textarea" :autosize="{
+                      minRows: 5,
+                      maxRows: 5,
+                    }" resize="none" clearable />
+                </el-form-item>
+              </div>
+            </el-form>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+    <Dialog width="852px" :flag="logDialogVisible" :with-footer="false" msg-title="查看日志" @resetForm="logDialogHide">
+      <div class="logDialog">
+        <div class="interfaceLog">
+          <div class="interfaceLog_head flex">
+            <div class="interfaceLog_head_time flex-wrap">
+              <div class="interfaceLog_head_time_start r12">
+                <el-date-picker v-model="logStartTime" size="default" type="datetime" placeholder="选择开始日期时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" :clearable="false" @change="logTimeChangeHandler" />
+              </div>
+              <div class="interfaceLog_head_time_end">
+                <el-date-picker v-model="logEndTime" size="default" type="datetime" placeholder="选择结束日期时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" :clearable="false" @change="logTimeChangeHandler" />
+              </div>
+            </div>
+            <div class="flex">
+              <el-input v-model="logSearchText" size="default" placeholder="请输入搜索关键词" clearable style="width: 180px" @clear="logSearchClearHandler" />
+              <el-button size="mini" type="primary" style="margin-left: 16px" @click="logSearchHandler">查询</el-button>
+            </div>
+          </div>
+          <div v-loading="logTableLoading" element-loading-text="拼命加载中" stripe element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)" class="interfaceLog_content flex-wrap">
+            <el-table :data="logTableData" class="table" height="500px" border style="width: 100%; margin-top: 20px">
+              <el-table-column prop="logTime" width="200" label="时间">
+              </el-table-column>
+              <el-table-column prop="logType" width="100" label="类型">
+              </el-table-column>
+              <el-table-column width="200" label="位置">
+                <template #default="scope">
+                  <el-tooltip class="item" effect="dark" :content="scope.row.logPositionID" placement="top">
+                    <div class="logPositionID">
+                      {{ scope.row.logPositionID }}
+                    </div>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+              <el-table-column width="100" prop="resultCode" label="成败">
+              </el-table-column>
+              <el-table-column label="详情">
+                <template #default="scope">
+                  <div class="flex-wrap">
+                    <el-tooltip class="item" effect="dark" :content="scope.row.resultDetails" placement="top">
+                      <span :id="'logId' + scope.$index" class="logDetails">{{
+                        scope.row.resultDetails
+                      }}</span>
+                    </el-tooltip>
+                    <el-button @click="logCopy(scope.$index)" style="margin-left: 10px" type="text">复制</el-button>
+                  </div>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </div>
+    </Dialog>
+    <Dialog width="852px" :flag="slotDialogVisible" msg-title="插槽编辑" @submit-form="slotSubmitHandler" @reset-form="slotDialogHide">
+      <div class="logcont">
+        <Minheader :is-auth="true" :is-statuser="true" @addForm="editSlotDialogShow">
+          <template #header>
+            <div class="status flex-wrap">
+              <div class="manageTitle">插槽列表</div>
+            </div>
+          </template>
+        </Minheader>
+      </div>
+      <div style="padding: 0 24px 24px 24px" class="dialog-content">
+        <DataTable :table-header="slotTableColumns" :table-data="slotTableData" :table-btn-group="slotTableButtonGroups" selection-enable :table-property="{ headerCellClassName: 'hide-header-check' }" class="slot-table" @select="slotSelectHandler" @btnClick="slotTableButtonClickHandler" />
+      </div>
+    </Dialog>
+    <Dialog :flag="editSlotDialogVisible" :type="editSlotDialogType" :msg-title="editSlotDialogTitle" :del-name="deployNodeName" @submit-form="editSlotSubmitHandler" @reset-form="editSlotDialogHide" @del-remove="editSlotSubmitHandler" @del-rest="editSlotDialogHide">
+      <div class="form-wrapper">
+        <AutoForm :model="slotForm" :items="slotTableColumns" :select-option-map="selectOptionMap" label-width="100px" />
+      </div>
+    </Dialog>
+    <Dialog :flag="outputDialogVisible" :type="outputDialogType" :msg-title="outputDialogTitle" :del-name="'服务输出' + serviceOutputID" @submit-form="outputSubmitHandler" @reset-form="outputDialogHide" @del-remove="outputSubmitHandler" @del-rest="outputDialogHide">
+      <div class="form-wrapper">
+        <AutoForm :model="outputForm" :items="outputTableColumns" :select-option-map="selectOptionMap" />
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import Minheader from "@/components/minheader/index.vue";
+import DataTable from "@/components/tableTemp/index.vue";
+import Dialog from "@/components/dialog/index.vue";
+import AutoForm from "../components/AutoForm.vue";
+import {
+  CommonTableColumn,
+  SelectOptionQueryResult,
+  SelectOptionObj,
+  CommonData,
+} from "~/common";
+import { ElMessage } from "element-plus";
+import { Query, GeneralDataReception } from "@/api/webApi";
+
+const queryTemplateIDMap = reactive<{
+  [x: string]: number;
+}>({});
+const selectOptionMap = reactive<{
+  [id: number]: SelectOptionObj[];
+}>({});
+const getSelectOptionOfColumn = (columns: CommonTableColumn[]) => {
+  columns.forEach(async ({ columnName, listqueryTemplateID }) => {
+    if (listqueryTemplateID !== null && !queryTemplateIDMap[columnName]) {
+      queryTemplateIDMap[columnName] = listqueryTemplateID;
+      if (!selectOptionMap[listqueryTemplateID]) {
+        selectOptionMap[listqueryTemplateID] = [];
+        selectOptionMap[listqueryTemplateID] = await getSelectOptions(
+          listqueryTemplateID
+        );
+      }
+    }
+  });
+};
+const getSelectOptions = async (id: number) => {
+  try {
+    const {
+      code,
+      returnData: { listValues },
+      message,
+    }: SelectOptionQueryResult = await Query({
+      id,
+      dataContent: [],
+    });
+    if (Number(code) !== 0) {
+      throw new Error(message ?? "失败");
+    }
+    const options = listValues.map(({ k, v, setlabel, setvalue }) => ({
+      k,
+      v,
+      setlabel,
+      setvalue,
+      [setlabel]: k,
+      [setvalue]: v,
+    }));
+    return options;
+  } catch (error) {
+    console.error(error);
+    return [];
+  }
+};
+
+const route = useRoute();
+const serviceID = Number(route.query.serviceID);
+const serviceState = computed(() =>
+  route.query.runState === "运行" ? "进行中" : "停止"
+); //状态
+const serviceForm = reactive({
+  serviceName: "",
+  serviceOutputID: null,
+  serviceType: null,
+  sourceObjectName: "",
+  lifeCycleCol: "",
+  serviceDescribe: "",
+  threads: null,
+  isAsynchronous: 1,
+  datatype: null,
+  dataSourceID: null,
+  computingMethod: "",
+  validationExpression: "",
+  startTime: null,
+  stopTime: null,
+  retryCount: null,
+  loopCount: null,
+  frequencyCount: null,
+  frequencyUnit: null,
+  logDataSourceID: null,
+  logList: "",
+  logDataSourceName: null,
+  dataSourceName: null,
+  hasStartTime: null,
+  hasEndTime: null,
+});
+const getServiceDetail = async () => {
+  try {
+    const {
+      code,
+      returnData: { listValues },
+      message,
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceDetail,
+      dataContent: [serviceID],
+    });
+    if (Number(code) !== 0) {
+      throw new Error(message ?? "失败");
+    }
+    if (!listValues.length) {
+      throw new Error("未查询到服务信息");
+    }
+    Object.entries(listValues[0]).forEach(([key, value]) => {
+      serviceForm[key] = value;
+    });
+  } catch (error) {
+    console.error(error);
+  }
+};
+onMounted(() => {
+  getServiceDetail();
+});
+const serviceSubmitHandler = async () => {
+  try {
+    const dataContent = {
+      serviceID,
+      ...serviceForm,
+      event: 2,
+    };
+    Object.keys(dataContent).forEach((key) => {
+      if (
+        ![
+          "serviceName",
+          "sourceObjectName",
+          "lifeCycleCol",
+          "serviceDescribe",
+          "computingMethod",
+          "validationExpression",
+          "logList",
+        ].includes(key) &&
+        dataContent[key] === ""
+      ) {
+        dataContent[key] = null;
+      }
+    });
+    const { code, message } = await GeneralDataReception({
+      serviceID: SERVICE_ID.sysServiceEdit,
+      dataContent: JSON.stringify(dataContent),
+    });
+    if (Number(code) === 0) {
+      ElMessage.success(message ?? "成功");
+    } else {
+      throw new Error(message ?? "失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+// 输出-表格
+const outputTableColumns = ref<CommonTableColumn[]>([]);
+const outputTableData = ref<CommonData[]>([]);
+const getOutputTable = async () => {
+  try {
+    const {
+      code,
+      returnData: { columnSet, listValues },
+      message,
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceOutTable,
+      dataContent: [serviceID],
+    });
+    if (Number(code) !== 0) {
+      throw new Error(message ?? "失败");
+    }
+    getSelectOptionOfColumn(columnSet);
+    outputTableColumns.value = columnSet;
+    outputTableData.value = listValues;
+  } catch (error) {
+    console.error(error);
+  }
+};
+onMounted(() => {
+  getOutputTable();
+});
+const outputTableButtonGroup = [
+  {
+    name: "编辑",
+    className: "editBtn",
+    param: 1,
+  },
+  {
+    name: "删除",
+    className: "delBtn",
+    param: 2,
+  },
+];
+const outputTableButtonClickHandler = (
+  rowIndex: number,
+  row: CommonData,
+  param: number
+) => {
+  switch (param) {
+    case 1:
+      outputDialogShow(2, row);
+      break;
+    case 2:
+      outputDialogShow(3, row);
+      break;
+    default:
+      break;
+  }
+};
+
+// 输出-新增/编辑
+const outputDialogVisible = ref(false);
+const outputDialogType = ref("");
+const outputDialogTitle = ref("");
+const serviceOutputID = ref<string | number>();
+const outputDialogShow = (operate: number = 1, row: CommonData) => {
+  switch (operate) {
+    case 1:
+      outputDialogType.value = "add";
+      outputDialogTitle.value = "新增输出";
+      break;
+    case 2:
+      outputDialogType.value = "edit";
+      outputDialogTitle.value = "编辑输出";
+      Object.entries(row).forEach(([key, value]) => {
+        outputForm[key] = value;
+        const column = outputTableColumns.value.find(
+          (column) => column.columnName === key
+        );
+        if (typeof column?.listqueryTemplateID === "number") {
+          const selectOptions = selectOptionMap[column.listqueryTemplateID];
+          const option = selectOptions.find((option) => option.k === value);
+          if (option) {
+            outputForm[option.setvalue] = option.v;
+          }
+        }
+      });
+      break;
+    case 3:
+      outputDialogType.value = "del";
+      outputDialogTitle.value = "删除输出";
+      break;
+    default:
+      break;
+  }
+  serviceOutputID.value = (row && row["serviceOutputID"]) ?? "";
+  outputDialogVisible.value = true;
+};
+const outputDialogHide = () => {
+  outputDialogVisible.value = false;
+  Object.keys(outputForm).forEach((key) => {
+    outputForm[key] = null;
+  });
+};
+const outputForm = reactive<CommonData>({});
+const outputSubmitHandler = async () => {
+  try {
+    const dataContent = {};
+    switch (outputDialogType.value) {
+      case "add":
+        Object.assign(dataContent, {
+          event: 1,
+          ...outputForm,
+        });
+        break;
+      case "edit":
+        Object.assign(dataContent, {
+          event: 2,
+          serviceOutputID: serviceOutputID.value,
+          ...outputForm,
+        });
+        break;
+      case "delete":
+        Object.assign(dataContent, {
+          event: 3,
+          serviceOutputID: serviceOutputID.value,
+        });
+        break;
+      default:
+        break;
+    }
+    const { code, message } = await GeneralDataReception({
+      serviceID: DATACONTENT_ID.sysServiceOutTable,
+      dataContent: JSON.stringify(dataContent),
+    });
+    if (Number(code) === 0) {
+      ElMessage.success(message ?? "成功");
+      outputDialogHide();
+    } else {
+      throw new Error(message ?? "失败");
+    }
+  } catch (error) {
+    console.error(error);
+  }
+};
+
+// 日志弹框
+const logDialogVisible = ref(false);
+const logDialogShow = () => {
+  logDialogVisible.value = true;
+  getToday();
+  getLogTableData();
+};
+const logDialogHide = () => {
+  logDialogVisible.value = false;
+};
+const logStartTime = ref("");
+const logEndTime = ref("");
+const getToday = () => {
+  function numberFormat(number) {
+    const string = "0" + number;
+    return string.slice(-2);
+  }
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = now.getMonth() + 1;
+  const date = now.getDate();
+  const today = `${year}-${numberFormat(month)}-${numberFormat(date)}`;
+  logStartTime.value = `${today} 00:00:00`;
+  logEndTime.value = `${today} 23:59:59`;
+};
+const logTimeChangeHandler = () => {
+  if (!logStartTime.value || !logEndTime.value) {
+    return;
+  }
+  const startTime = new Date(logStartTime.value).getTime();
+  const endTime = new Date(logEndTime.value).getTime();
+  if (startTime > endTime) {
+    ElMessage.error("开始时间不能大于结束时间");
+    logEndTime.value = "";
+    return;
+  }
+  if (startTime < endTime - (3 * 24 * 60 * 60 - 1) * 1000) {
+    ElMessage.error("间隔时间不能大于三天");
+    logEndTime.value = "";
+    return;
+  }
+  resetLogTable();
+  getLogTableData();
+};
+const logSearchText = ref("");
+const logSearchHandler = () => {
+  getLogTableData();
+};
+const logSearchClearHandler = () => {
+  getLogTableData();
+};
+
+const logTableData = ref<CommonData[]>([]);
+const logTableLoading = ref(false);
+const resetLogTable = () => {
+  logTableData.value = [];
+};
+const getLogTableData = async () => {
+  try {
+    logTableLoading.value = true;
+    const {
+      code,
+      returnData: { listValues },
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceTopologyTable,
+      dataContent: [serviceID, logStartTime.value, logEndTime.value],
+    });
+    if (Number(code) !== 0) {
+      throw new Error("获取数据失败");
+    }
+    logTableData.value = listValues;
+    logTableLoading.value = false;
+  } catch (error) {
+    console.error(error);
+    logTableLoading.value = false;
+  }
+};
+
+// 选择插槽
+const slotDialogVisible = ref(false);
+const slotDialogShow = () => {
+  getSlotTable();
+  slotDialogVisible.value = true;
+};
+const slotDialogHide = () => {
+  slotDialogVisible.value = false;
+};
+const slotTableColumns = ref<CommonTableColumn[]>([]);
+const slotTableData = ref<CommonData[]>([]);
+const getSlotTable = async () => {
+  try {
+    const {
+      code,
+      returnData: { columnSet, listValues },
+      message,
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceNodeList,
+      dataContent: [serviceID],
+    });
+    if (Number(code) !== 0) {
+      throw new Error(message ?? "失败");
+    }
+    getSelectOptionOfColumn(columnSet);
+    slotTableColumns.value = columnSet;
+    slotTableData.value = listValues;
+  } catch (error) {
+    console.error(error);
+  }
+};
+const slotTableButtonGroups = [
+  {
+    name: "编辑",
+    className: "editBtn",
+    param: 1,
+  },
+  {
+    name: "删除",
+    className: "delBtn",
+    param: 2,
+  },
+];
+const slotTableButtonClickHandler = (
+  rowIndex: number,
+  row: CommonData,
+  param: number
+) => {
+  switch (param) {
+    case 1:
+      editSlotDialogShow(2, row);
+      break;
+    case 2:
+      editSlotDialogShow(3, row);
+      break;
+    default:
+      break;
+  }
+};
+const slotSelectHandler = (selection, row) => {
+  console.log(selection, row);
+};
+const slotSubmitHandler = () => {
+  slotDialogHide();
+};
+
+// 插槽-新增/编辑
+const editSlotDialogType = ref("");
+const editSlotDialogTitle = ref("");
+const deployNodeName = ref("");
+const editSlotDialogVisible = ref(false);
+const editSlotDialogShow = (operate: number = 1, row: CommonData) => {
+  switch (operate) {
+    case 1:
+      editSlotDialogType.value = "add";
+      editSlotDialogTitle.value = "新增插槽";
+      break;
+    case 2:
+      editSlotDialogType.value = "edit";
+      editSlotDialogTitle.value = "编辑插槽";
+      Object.entries(row).forEach(([key, value]) => {
+        slotForm[key] = value;
+        const column = slotTableColumns.value.find(
+          (column) => column.columnName === key
+        );
+        if (typeof column?.listqueryTemplateID === "number") {
+          const selectOptions = selectOptionMap[column.listqueryTemplateID];
+          const option = selectOptions.find((option) => option.k === value);
+          if (option) {
+            slotForm[option.setvalue] = option.v;
+          }
+        }
+      });
+      break;
+    case 3:
+      editSlotDialogType.value = "del";
+      editSlotDialogTitle.value = "删除插槽";
+      break;
+    default:
+      break;
+  }
+  deployNodeName.value = (row && (row["deployNodeName"] as string)) ?? "";
+  editSlotDialogVisible.value = true;
+};
+const editSlotDialogHide = () => {
+  editSlotDialogVisible.value = false;
+  Object.keys(slotForm).forEach((key) => {
+    slotForm[key] = null;
+  });
+};
+const slotForm = reactive<CommonData>({});
+const editSlotSubmitHandler = () => {
+  editSlotDialogHide();
+};
+
+//复制
+const logCopy = (index) => {
+  const ele = document.getElementById("logId" + index);
+  const val = ele!.innerText;
+  try {
+    const input = document.createElement("input");
+    //将input的值设置为需要复制的内容
+    input.value = val;
+    //添加input标签
+    document.body.appendChild(input);
+    //选中input标签
+    input.select();
+    //执行复制
+    document.execCommand("copy");
+    //移除input标签
+    document.body.removeChild(input);
+    // this.$message.success("复制成功");
+  } catch (e) {
+    // this.$message.error("复制失败");
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.service-edit {
+  height: 100%;
+  .service-basic-wrapper {
+    height: 140px;
+    background: #ffffff;
+    box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.1);
+    border-radius: 4px;
+    margin-bottom: 16px;
+    padding: 24px;
+  }
+  .service-config-wrapper {
+    height: 196px;
+    background: #ffffff;
+    box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.1);
+    border-radius: 4px;
+    margin-bottom: 16px;
+    padding: 24px;
+  }
+  .service-receive-wrapper {
+    height: 192px;
+    background: #ffffff;
+    box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.1);
+    border-radius: 4px;
+    margin-bottom: 16px;
+    padding: 24px;
+  }
+  .service-output-wrapper {
+    height: 250px;
+    background: #ffffff;
+    box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.1);
+    border-radius: 4px;
+    padding: 24px;
+  }
+
+  .form-wrapper {
+    width: 100%;
+    .w100 {
+      width: 100%;
+    }
+    .w50 {
+      width: 50%;
+    }
+    .w40 {
+      width: 40%;
+    }
+    .w20 {
+      width: 20%;
+    }
+    .pd30 {
+      padding: 0 30px 0 0;
+    }
+  }
+}
+.service-state {
+  font-size: 20px;
+  font-family: Microsoft YaHei;
+  font-weight: bold;
+  color: #53b074;
+}
+.app-containers {
+  height: 170px;
+}
+
+.interfaceLog {
+  padding: 0 24px 24px 24px;
+}
+.logcont {
+  padding: 0 24px 0px 24px;
+}
+:deep
+  .slot-table
+  .el-table__header
+  .el-table__cell.hide-header-check:first-child
+  .el-checkbox {
+  display: none;
+}
+</style>

+ 744 - 0
src/views/systemSettings/serviceManagement/serviceTopology.vue

@@ -0,0 +1,744 @@
+<template>
+  <div class="integration__cont__map__cont">
+    <Minheader
+      :is-statuser="true"
+      :is-Journal="true"
+      @addJournalForm="logDialogShow"
+    >
+      <template #header>
+        <div class="status flex-wrap">
+          <div class="manageTitle">服务拓扑</div>
+          <div class="typestart flex-wrap">
+            <div class="name">
+              <div class="log"></div>
+              正常
+            </div>
+            <div class="name">
+              <div class="log1"></div>
+              异常
+            </div>
+          </div>
+        </div>
+      </template>
+    </Minheader>
+    <div class="integration__cont" ref="chartDom"></div>
+    <Dialog
+      width="1200px"
+      :flag="flag"
+      :type="type"
+      :msg-title="msgTitle"
+      @resetForm="logDialogHide"
+    >
+      <div class="diacont">
+        <div class="interfaceLog_head flex">
+          <div class="interfaceLog_head_time flex-act">
+            <div class="header_name">当前服务:Kafka原始报文</div>
+          </div>
+          <div class="interfaceLog_head_btn flex">
+            <div class="interfaceLog_head_time_start r12">
+              <el-date-picker
+                v-model="timeStart"
+                size="default"
+                type="datetime"
+                placeholder="选择开始日期时间"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                :clearable="false"
+                @change="timeSelectHandler"
+              />
+            </div>
+            <div class="interfaceLog_head_time_end r16">
+              <el-date-picker
+                v-model="timeEnd"
+                size="default"
+                type="datetime"
+                placeholder="选择结束日期时间"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                :clearable="false"
+                @change="timeSelectHandler"
+              />
+            </div>
+            <Search
+              :is-title="false"
+              @get-search-data="getSearchData"
+              @clear-search-data="clearSearchData"
+            />
+          </div>
+        </div>
+        <div class="interfaceLog_content">
+          <el-row :gutter="24">
+            <el-col :span="19">
+              <DataTable
+                :table-header="tableColumns"
+                :table-data="tableData"
+                :table-property="{ rowKey: 'ID' }"
+                @load="load"
+                @row-click="rowClickHandler"
+              />
+            </el-col>
+            <el-col :span="5">
+              <div class="interfaceLog_content_progress">
+                <el-timeline>
+                  <el-timeline-item
+                    v-for="(item, index) in progressList"
+                    :key="index"
+                  >
+                    <div class="list">
+                      <div class="list_status"></div>
+                      <div class="list_title">{{ item.logType }}</div>
+                      <div class="list_code">{{ item.resultCode }}</div>
+                      <div class="list_time">{{ item.logTime }}</div>
+                      <!-- <div class="list_detial">{{ item.resultDetails }}</div> -->
+                    </div>
+                  </el-timeline-item>
+                </el-timeline>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import Minheader from '@/components/minheader/index.vue'
+import DataTable from '@/components/tableTemp/index.vue'
+import Dialog from '@/components/dialog/index.vue'
+import { Ref } from 'vue'
+import * as echarts from 'echarts'
+import _ from 'lodash'
+import { ElMessage } from 'element-plus'
+import { Query } from '@/api/webApi'
+import { CommonTableColumn } from '~/common'
+import img1 from '@/assets/integr/jiekou_blue.png'
+import img2 from '@/assets/integr/jiekou_red.png'
+import img3 from '@/assets/integr/pull_blue.png'
+import img4 from '@/assets/integr/kafka_blue.png'
+import img5 from '@/assets/integr/push_blue.png'
+import img6 from '@/assets/integr/save_blue.png'
+import img7 from '@/assets/integr/mysql_blue.png'
+import img8 from '@/assets/integr/pull_red.png'
+
+const route = useRoute()
+const serviceID = Number(route.query.serviceID)
+const serviceName = String(route.query.serviceName)
+const getTopologyData = async () => {
+  try {
+    const {
+      code,
+      returnData: { listValues },
+      message,
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceTopology,
+      dataContent: Array(6).fill(serviceID),
+    })
+    if (Number(code) !== 0) {
+      throw new Error(message ?? '失败')
+    }
+    console.log(listValues)
+  } catch (error: any) {
+    console.error(error)
+  }
+}
+getTopologyData()
+
+const chartDom = ref<HTMLDivElement | null>(null)
+const myChart = ref<Ref<echarts.ECharts> | null>(null)
+const flag = ref(false) //弹窗开关
+const type = ref('') //判断是否删除
+const msgTitle = ref('查看日志') //弹窗标题
+const getAssetsFile = (url: string) => {
+  return new URL(`../assets/integr/${url}`, import.meta.url).href
+}
+const datas = ref<any[]>([])
+const resizeDelay = 300
+//默认节点
+// const defaultImg = getAssetsFile("jiekou_blue.png");
+const defaultImg = 'image://' + img1
+//默认节点-错误
+const defaultImgError = 'image://' + img2
+//拉取节点
+const pullImg = 'image://' + img3
+//拉取节点-错误
+const pullImgError = 'image://' + img8
+//kafka节点
+const kafkaImg = 'image://' + img4
+//kafka节点-错误
+const kafkaImgError = getAssetsFile('kafka_red.png')
+//推送节点
+const pushImg = 'image://' + img5
+//推送节点-错误
+const pushImgError = 'image://' + getAssetsFile('push_red.png')
+//存储节点
+const saveImg = 'image://' + img6
+//存储节点-错误
+const saveImgError = getAssetsFile('save_red.png')
+//存储节点
+const mysqlImg = 'image://' + img7
+//存储节点-错误
+const mysqlImgError = getAssetsFile('mysql_red.png')
+const nodeDataList = ref([
+  {
+    name: '星盟SBH',
+    linkTargetName: 'Kafka',
+    linkValue: ' ',
+    coordConfig: {
+      level: '0',
+    },
+    value: [10, 245],
+    draggable: false,
+    fixed: true,
+    symbol: defaultImg,
+    symbolSize: 60,
+  },
+  {
+    name: 'Kafka',
+    linkTargetName: 'BSMBPM报文解析',
+    linkValue: ' ',
+    coordConfig: { level: '3' },
+    symbolSize: 60,
+    symbol: kafkaImg,
+    draggable: false,
+    value: [40, 245],
+  },
+  {
+    name: 'BSMBPM报文解析',
+    linkTargetName: 'BSM',
+    linkValue: ' ',
+    coordConfig: { level: '3' },
+    symbolSize: 60,
+    symbol: pullImgError,
+    draggable: false,
+    value: [70, 245],
+  },
+  {
+    name: 'BSM',
+    linkTargetName: '解析服务',
+    linkValue: ' ',
+    coordConfig: { level: '4' },
+    symbolSize: 60,
+    symbol: defaultImg,
+    draggable: false,
+    value: [100, 245],
+  },
+  {
+    name: 'BPM',
+    linkTargetName: '推送节点',
+    linkValue: ' ',
+    coordConfig: { level: '4' },
+    symbolSize: 60,
+    symbol: defaultImg,
+    draggable: false,
+    value: [100, 200],
+  },
+  {
+    name: '航班',
+    linkTargetName: '解析服务',
+    linkValue: ' ',
+    coordConfig: { level: '4' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [160, 120],
+  },
+  {
+    name: '行李投诉',
+    linkTargetName: '解析服务',
+    linkValue: ' ',
+    coordConfig: { level: '4' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [160, 90],
+  },
+  {
+    name: '旅客',
+    linkTargetName: '解析服务',
+    linkValue: ' ',
+    coordConfig: { level: '4' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [160, 60],
+  },
+  {
+    name: '推送节点',
+    linkTargetName: '首都机场BHS ',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    value: [200, 160],
+  },
+  {
+    name: '推送节点 ',
+    linkTargetName: '星盟SBH ',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    label: {
+      show: false,
+    },
+    value: [200, 160],
+  },
+  {
+    name: '推送节点  ',
+    linkTargetName: '局方公共平台 ',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    label: {
+      show: false,
+    },
+    value: [200, 160],
+  },
+  {
+    name: '推送节点   ',
+    linkTargetName: '行李推送事件',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    label: {
+      show: false,
+    },
+    value: [200, 160],
+  },
+  {
+    name: '推送节点    ',
+    linkTargetName: 'ES',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    label: {
+      show: false,
+    },
+    value: [200, 160],
+  },
+  {
+    name: '解析服务',
+    linkTargetName: ' Kafka',
+    linkValue: ' ',
+    coordConfig: { level: '5' },
+    symbolSize: 60,
+    symbol: pushImg,
+    draggable: false,
+    value: [200, 100],
+  },
+  {
+    name: '首都机场BHS ',
+    linkTargetName: '首都机场BHS ',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 220],
+  },
+  {
+    name: '星盟SBH ',
+    linkTargetName: '星盟SBH ',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 190],
+  },
+  {
+    name: '局方公共平台 ',
+    linkTargetName: '局方公共平台 ',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 160],
+  },
+  {
+    name: '行李推送事件',
+    linkTargetName: '行李推送事件',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 130],
+  },
+  {
+    name: 'ES',
+    linkTargetName: 'ES',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 100],
+  },
+  {
+    name: ' Kafka',
+    linkTargetName: '存储服务',
+    linkValue: ' ',
+    coordConfig: { level: '6' },
+    symbolSize: 30,
+    symbol: defaultImg,
+    draggable: false,
+    value: [240, 70],
+  },
+  {
+    name: '存储服务',
+    linkTargetName: 'Mysql',
+    linkValue: ' ',
+    coordConfig: { level: '7' },
+    symbolSize: 60,
+    symbol: saveImg,
+    draggable: false,
+    value: [280, 160],
+  },
+  {
+    name: 'Mysql',
+    linkTargetName: 'Mysql',
+    linkValue: ' ',
+    coordConfig: { level: '8' },
+    symbolSize: 60,
+    symbol: mysqlImg,
+    draggable: false,
+    value: [340, 160],
+  },
+])
+const options = reactive({
+  title: {
+    text: `当前服务:${serviceName}`,
+  },
+  itemStyle: {
+    normal: {
+      color: '#67C23A',
+    },
+    shadowBlur: 0,
+  },
+  textStyle: {
+    color: '#444',
+    fontSize: 16,
+    fontWeight: 600,
+  },
+  grid: {
+    top: 60,
+    bottom: 10,
+    left: 0,
+    right: 0,
+  },
+  legend: [
+    {
+      formatter: function (name) {
+        return echarts.format.truncateText(name, 200, '12px', '…', {})
+      },
+      tooltip: {
+        show: true,
+      },
+      selectedMode: 'false',
+      bottom: 20,
+    },
+  ],
+  animationDuration: 500,
+  animationEasingUpdate: 'quinticInOut',
+  xAxis: {
+    show: false,
+    type: 'value',
+  },
+  yAxis: {
+    show: false,
+    type: 'value',
+  },
+  series: [
+    {
+      type: 'graph',
+      coordinateSystem: 'cartesian2d',
+      //圆形上面的文字
+      label: {
+        position: 'bottom',
+        show: true,
+        fontSize: 12,
+      },
+      itemStyle: {
+        color: '#409eff',
+
+        shadowBlur: 0,
+      },
+      lineStyle: {
+        width: 0,
+        shadowColor: 'none',
+        color: '#ff0000',
+      },
+      data: <any[]>[],
+      links: <any[]>[],
+    },
+  ],
+})
+const getCoordDataList = (): any[] => {
+  let coorDataDict = {}
+  let defaultConfig = {
+    type: 'lines', // 块1,2...n到节点A,B...N
+    coordinateSystem: 'cartesian2d',
+    // animationDelay: 10000,
+    z: 1,
+    effect: {
+      show: true,
+      smooth: true,
+      trailLength: 0,
+      symbol: 'arrow',
+      color: '#92A7D5',
+      symbolSize: 10,
+      period: 3,
+      delay: 1500,
+      loop: true,
+    },
+    lineStyle: {
+      normal: {
+        curveness: 0,
+        color: '#92A7D5',
+        width: 1,
+      },
+    },
+    data: [],
+  }
+  nodeDataList.value.map(item => {
+    if (item.coordConfig !== undefined) {
+      if (!(item.coordConfig.level in coorDataDict)) {
+        let coorConfig = JSON.parse(JSON.stringify(defaultConfig))
+        if (item.coordConfig.lineStyle !== undefined) {
+          coorConfig.lineStyle = item.coordConfig.lineStyle
+        }
+        if (item.coordConfig.effect !== undefined) {
+          coorConfig.effect = item.coordConfig.effect
+        }
+        coorDataDict[item.coordConfig.level] = coorConfig
+      }
+
+      let coordData = [
+        item.value,
+        nodeDataList.value.filter(i => i.name == item.linkTargetName)[0]
+          .value ||
+          nodeDataList.value.filter(i => i.name == item.linkTargetName)[1]
+            .value,
+      ]
+      coorDataDict[item.coordConfig.level].data.push({
+        coords: coordData,
+      })
+      if (item.coordConfig.bilateral) {
+        coorDataDict[item.coordConfig.level].data.push({
+          coords: coordData.reverse(),
+        })
+      }
+    }
+  })
+  return Object.values(coorDataDict)
+}
+nodeDataList.value.forEach(item => {
+  datas.value.push({
+    source: item.name,
+    value: item.linkValue,
+    target: item.linkTargetName,
+  })
+})
+const resizeHandler = () => {
+  myChart.value?.resize()
+}
+onMounted(() => {
+  options.series[0].data = nodeDataList.value
+  options.series[0].links = datas.value
+  options.series.push(...getCoordDataList())
+  myChart.value = echarts.init(chartDom.value as HTMLDivElement)
+  myChart.value.setOption(options as echarts.EChartOption, true)
+  window.addEventListener('resize', _.debounce(resizeHandler, resizeDelay))
+})
+// // 事件
+// myChart.on("click", (params) => {
+//   console.log(params);
+// });
+
+//新增
+const logDialogShow = () => {
+  msgTitle.value = '查看日志'
+  flag.value = true
+  getToday()
+  getTableData()
+}
+//取消
+const logDialogHide = () => {
+  flag.value = false
+}
+
+const getSearchData = (text: string) => {
+  console.log(text)
+}
+const clearSearchData = () => {}
+
+let page = 0
+const loading = ref(false)
+const noMore = ref(false)
+const loadDisabled = computed(() => loading.value || noMore.value)
+//表头
+const tableColumns = ref<CommonTableColumn[]>([])
+//列表
+const tableData = ref<any[]>([])
+const resetTable = () => {
+  page = 0
+  loading.value = false
+  noMore.value = false
+  tableData.value = []
+}
+const getTableData = async () => {
+  try {
+    loading.value = true
+    const {
+      code,
+      returnData: { columnSet, listValues },
+    } = await Query({
+      id: DATACONTENT_ID.sysServiceTopologyTable,
+      needPage: ++page,
+      dataContent: [serviceID, timeStart.value, timeEnd.value],
+    })
+    if (Number(code) === 0) {
+      tableColumns.value = columnSet.map(column => ({
+        label: column.columnLabel,
+        key: column.columnName,
+        ...column,
+      }))
+      if (listValues.length === 0) {
+        page--
+        noMore.value = true
+      }
+      tableData.value.push(...listValues)
+      loading.value = false
+    } else {
+      throw new Error('获取数据失败')
+    }
+  } catch (error: any) {
+    page--
+    loading.value = false
+    ElMessage.error(error.message)
+  }
+}
+const load = () => {
+  if (loadDisabled.value) {
+    return
+  }
+  getTableData()
+}
+
+const timeStart = ref('')
+const timeEnd = ref('')
+const getToday = () => {
+  function numberFormat(number) {
+    const string = '0' + number
+    return string.slice(-2)
+  }
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = now.getMonth() + 1
+  const date = now.getDate()
+  const today = `${year}-${numberFormat(month)}-${numberFormat(date)}`
+  timeStart.value = `${today} 00:00:00`
+  timeEnd.value = `${today} 23:59:59`
+}
+const timeSelectHandler = () => {
+  const startTime = new Date(timeStart.value).getTime()
+  const endTime = new Date(timeEnd.value).getTime()
+  if (startTime > endTime) {
+    ElMessage.error('开始时间不能大于结束时间')
+    timeEnd.value = ''
+    return
+  }
+  if (startTime < endTime - (3 * 24 * 60 * 60 - 1) * 1000) {
+    ElMessage.error('间隔时间不能大于三天')
+    timeEnd.value = ''
+    return
+  }
+  resetTable()
+  getTableData()
+}
+
+type progressItem = {
+  logType: string
+  resultCode: string
+  logTime: string
+  resultDetails?: string
+}
+const progressList = ref<progressItem[]>([])
+// progressList.value.push(
+//   ...Array.from({ length: 3 }).map(() => ({
+//     logType: '更新 Github 模板',
+//     resultCode: '989665554',
+//     logTime: '2022-4-26 15:48:55',
+//   }))
+// )
+const rowClickHandler = (row, column, event) => {
+  progressList.value = []
+  if (row.logObject) {
+    tableData.value.forEach(record => {
+      if (record.logObject == row.logObject) {
+        progressList.value.push(record)
+      }
+    })
+  } else {
+    progressList.value.push(row)
+  }
+}
+</script>
+<style lang="scss" scoped>
+.integration__cont__map__cont {
+  width: 100%;
+  height: 100%;
+  .header_name {
+    font-size: 16px;
+    font-family: Microsoft YaHei;
+    font-weight: bold;
+    color: #303133;
+  }
+  .integration__cont {
+    background: #ffffff;
+    border-radius: 4px;
+    padding: 35px;
+    width: 100%;
+    height: calc(100vh - 177px);
+  }
+  .typestart {
+    height: 100%;
+    .name {
+      display: flex;
+      align-items: center;
+    }
+    .name:nth-child(2) {
+      margin-left: 24px;
+    }
+    .log {
+      width: 14px;
+      height: 14px;
+      background: #041741;
+      border-radius: 2px;
+      margin-right: 12px;
+    }
+    .log1 {
+      width: 14px;
+      height: 14px;
+      background: #e32d2d;
+      border-radius: 2px;
+      margin-right: 12px;
+    }
+  }
+}
+.interfaceLog_content {
+  margin-top: 24px;
+  // height: 452px;
+  // overflow: hidden;
+}
+</style>

+ 5 - 0
typings/common.d.ts

@@ -80,6 +80,11 @@ interface SelectOptionObj {
   v: any
 }
 
+type CommonValue = string | number | null
+interface CommonData {
+  [x: string]: CommonValue
+}
+
 interface CommonQueryResult<T = any> {
   code?: string | number
   returnData: {