Pārlūkot izejas kodu

Merge branch 'master' of http://1.14.104.71:10880/txl/ylrz_saas_his_scrm_adminUI

xw 13 stundas atpakaļ
vecāks
revīzija
2b9898022a

+ 63 - 0
src/api/company/externalApi.js

@@ -0,0 +1,63 @@
+import request from '@/utils/request'
+
+// 分页查询外部接口配置
+export function pageExternalApi(query) {
+  return request({
+    url: '/companyWorkflow/externalApi/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 详情
+export function getExternalApi(id) {
+  return request({
+    url: '/companyWorkflow/externalApi/' + id,
+    method: 'get'
+  })
+}
+
+// 新增/编辑
+export function saveOrUpdateExternalApi(data) {
+  return request({
+    url: '/companyWorkflow/externalApi/saveOrUpdate',
+    method: 'post',
+    data
+  })
+}
+
+// 启停
+export function changeExternalApiStatus(id, status) {
+  return request({
+    url: '/companyWorkflow/externalApi/' + id + '/status',
+    method: 'post',
+    params: { status }
+  })
+}
+
+// 删除
+export function deleteExternalApi(id) {
+  return request({
+    url: '/companyWorkflow/externalApi/' + id,
+    method: 'delete'
+  })
+}
+
+// 测试接口
+export function testExternalApi(id, data) {
+  return request({
+    url: '/companyWorkflow/externalApi/' + id + '/test',
+    method: 'post',
+    data
+  })
+}
+
+// 调用日志分页
+export function pageExternalApiLogs(query) {
+  return request({
+    url: '/companyWorkflow/externalApi/logs/page',
+    method: 'get',
+    params: query
+  })
+}
+

+ 23 - 0
src/api/system/config.js

@@ -97,4 +97,27 @@ export function getGatewayList(query) {
     method: 'get',
     params: query
   })
+}
+
+export function createTencentWordFile(title) {
+  return request({
+    url: '/third/tencentWord/createFile',
+    method: 'post',
+    data: title,
+    headers: { 'Content-Type': 'text/plain;charset=UTF-8' }
+  })
+}
+
+export function getTencentWordFiles() {
+  return request({
+    url: '/third/tencentWord/getFiles',
+    method: 'get',
+  })
+}
+
+export function synchronization(fileId) {
+  return request({
+    url: '/third/tencentWord/synchronization/' + fileId,
+    method: 'get',
+  })
 }

+ 419 - 0
src/views/company/workflowExternalApi/index.vue

@@ -0,0 +1,419 @@
+<template>
+  <div class="app-container">
+    <el-card shadow="never" class="mb8">
+      <div slot="header">
+        <span>外部接口管理配置</span>
+      </div>
+
+      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="70px">
+        <el-form-item label="接口类型">
+          <el-input v-model="queryParams.apiType" placeholder="请输入" clearable @keyup.enter.native="handleQuery" />
+        </el-form-item>
+        <el-form-item label="接口名称">
+          <el-input v-model="queryParams.apiName" placeholder="请输入" clearable @keyup.enter.native="handleQuery" />
+        </el-form-item>
+        <el-form-item label="接口编码">
+          <el-input v-model="queryParams.apiCode" placeholder="请输入" clearable @keyup.enter.native="handleQuery" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 120px">
+            <el-option label="启用" :value="1" />
+            <el-option label="停用" :value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增接口</el-button>
+      </el-col>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" border>
+      <el-table-column type="index" label="序号" width="60" />
+      <el-table-column prop="apiName" label="接口名称" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="apiCode" label="接口编码" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="apiType" label="接口类型" width="120" />
+      <el-table-column prop="apiUrl" label="请求地址" min-width="260" show-overflow-tooltip />
+      <el-table-column prop="httpMethod" label="请求方式" width="110" />
+      <el-table-column label="限频规则" width="140">
+        <template slot-scope="scope">
+          <span v-if="scope.row.rateMaxCount && scope.row.rateWindowSeconds">
+            {{ scope.row.rateMaxCount }}次/{{ formatWindow(scope.row.rateWindowSeconds) }}
+          </span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="priority" label="优先级" width="90" />
+      <el-table-column label="状态" width="110">
+        <template slot-scope="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="val => handleStatusChange(scope.row, val)"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="260" fixed="right">
+        <template slot-scope="scope">
+          <el-button type="text" size="mini" @click="handleTest(scope.row)">测试</el-button>
+          <el-button type="text" size="mini" @click="handleEdit(scope.row)">编辑</el-button>
+          <el-button type="text" size="mini" @click="handleLogs(scope.row)">日志</el-button>
+          <el-button type="text" size="mini" style="color:#F56C6C" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 新增/编辑 -->
+    <el-dialog :title="form.id ? '编辑接口' : '新增接口'" :visible.sync="openForm" width="720px" append-to-body>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
+        <el-row :gutter="12">
+          <el-col :span="12">
+            <el-form-item label="接口类型" prop="apiType">
+              <el-input v-model="form.apiType" placeholder="例如 WEATHER" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="接口名称" prop="apiName">
+              <el-input v-model="form.apiName" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="接口编码" prop="apiCode">
+              <el-input v-model="form.apiCode" :disabled="!!form.id" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="请求方式" prop="httpMethod">
+              <el-select v-model="form.httpMethod" placeholder="请选择" style="width:100%">
+                <el-option label="GET" value="GET" />
+                <el-option label="POST" value="POST" />
+                <el-option label="PUT" value="PUT" />
+                <el-option label="DELETE" value="DELETE" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="请求地址" prop="apiUrl">
+              <el-input v-model="form.apiUrl" placeholder="https://..." />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="限频窗口(秒)">
+              <el-input-number v-model="form.rateWindowSeconds" :min="1" :step="1" style="width:100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="窗口最大次数">
+              <el-input-number v-model="form.rateMaxCount" :min="1" :step="1" style="width:100%" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="优先级">
+              <el-input-number v-model="form.priority" :min="0" :step="1" style="width:100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态">
+              <el-select v-model="form.status" style="width:100%">
+                <el-option label="启用" :value="1" />
+                <el-option label="停用" :value="0" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="超时(ms)">
+              <el-input-number v-model="form.timeoutMs" :min="1000" :step="500" style="width:100%" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="默认请求头(JSON)">
+              <el-input v-model="form.defaultHeadersJson" type="textarea" :rows="3" placeholder='{"Content-Type":"application/json"}' />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="默认请求体(JSON)">
+              <el-input v-model="form.defaultBodyJson" type="textarea" :rows="4" placeholder='{"k":"v"}' />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openForm = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 测试 -->
+    <el-dialog title="测试接口" :visible.sync="openTest" width="760px" append-to-body>
+      <el-form :model="testForm" label-width="130px">
+        <el-form-item label="覆盖请求头(JSON)">
+          <el-input v-model="testForm.headersJson" type="textarea" :rows="3" />
+        </el-form-item>
+        <el-form-item label="覆盖请求体(JSON)">
+          <el-input v-model="testForm.bodyJson" type="textarea" :rows="4" />
+        </el-form-item>
+        <el-form-item label="覆盖超时(ms)">
+          <el-input-number v-model="testForm.timeoutMs" :min="1000" :step="500" />
+        </el-form-item>
+      </el-form>
+
+      <el-divider content-position="left">响应</el-divider>
+      <el-descriptions :column="2" border size="small">
+        <el-descriptions-item label="状态码">{{ testResult.responseStatus || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="耗时(ms)">{{ testResult.durationMs || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="是否成功">{{ testResult.success === true ? '是' : (testResult.success === false ? '否' : '-') }}</el-descriptions-item>
+        <el-descriptions-item label="错误信息">{{ testResult.errorMessage || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <el-input v-model="testResult.responseBody" type="textarea" :rows="10" readonly style="margin-top: 10px" />
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openTest = false">关闭</el-button>
+        <el-button type="primary" :loading="testLoading" @click="doTest">开始测试</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 日志 -->
+    <el-dialog title="调用日志" :visible.sync="openLogs" width="980px" append-to-body>
+      <el-table v-loading="logsLoading" :data="logs" border height="520px">
+        <el-table-column type="index" label="序号" width="60" />
+        <el-table-column prop="createTime" label="时间" width="170" />
+        <el-table-column prop="httpMethod" label="方法" width="90" />
+        <el-table-column prop="requestUrl" label="URL" min-width="260" show-overflow-tooltip />
+        <el-table-column prop="responseStatus" label="状态码" width="90" />
+        <el-table-column prop="durationMs" label="耗时(ms)" width="100" />
+        <el-table-column prop="success" label="成功" width="80">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.success === 1" type="success">成功</el-tag>
+            <el-tag v-else type="danger">失败</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="errorMessage" label="错误信息" min-width="160" show-overflow-tooltip />
+      </el-table>
+
+      <pagination
+        v-show="logsTotal > 0"
+        :total="logsTotal"
+        :page.sync="logsQuery.pageNum"
+        :limit.sync="logsQuery.pageSize"
+        @pagination="getLogs"
+      />
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openLogs = false">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  pageExternalApi,
+  getExternalApi,
+  saveOrUpdateExternalApi,
+  changeExternalApiStatus,
+  deleteExternalApi,
+  testExternalApi,
+  pageExternalApiLogs
+} from '@/api/company/externalApi'
+
+export default {
+  name: 'CompanyWorkflowExternalApi',
+  data() {
+    return {
+      loading: false,
+      list: [],
+      total: 0,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        apiType: undefined,
+        apiName: undefined,
+        apiCode: undefined,
+        status: undefined
+      },
+
+      openForm: false,
+      form: this.emptyForm(),
+      rules: {
+        apiType: [{ required: true, message: '接口类型不能为空', trigger: 'blur' }],
+        apiName: [{ required: true, message: '接口名称不能为空', trigger: 'blur' }],
+        apiCode: [{ required: true, message: '接口编码不能为空', trigger: 'blur' }],
+        apiUrl: [{ required: true, message: '请求地址不能为空', trigger: 'blur' }],
+        httpMethod: [{ required: true, message: '请求方式不能为空', trigger: 'change' }]
+      },
+
+      openTest: false,
+      testLoading: false,
+      currentRow: null,
+      testForm: {
+        headersJson: '',
+        bodyJson: '',
+        timeoutMs: 10000
+      },
+      testResult: {
+        responseStatus: null,
+        responseBody: '',
+        durationMs: null,
+        success: null,
+        errorMessage: ''
+      },
+
+      openLogs: false,
+      logsLoading: false,
+      logs: [],
+      logsTotal: 0,
+      logsQuery: {
+        pageNum: 1,
+        pageSize: 10,
+        configId: null
+      }
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    emptyForm() {
+      return {
+        id: null,
+        apiType: '',
+        apiName: '',
+        apiCode: '',
+        apiUrl: '',
+        httpMethod: 'GET',
+        rateWindowSeconds: null,
+        rateMaxCount: null,
+        priority: 0,
+        status: 1,
+        timeoutMs: 10000,
+        defaultHeadersJson: '',
+        defaultBodyJson: ''
+      }
+    },
+    formatWindow(seconds) {
+      if (!seconds) return '-'
+      if (seconds % 86400 === 0) return (seconds / 86400) + '天'
+      if (seconds % 3600 === 0) return (seconds / 3600) + '小时'
+      if (seconds % 60 === 0) return (seconds / 60) + '分钟'
+      return seconds + '秒'
+    },
+    getList() {
+      this.loading = true
+      pageExternalApi(this.queryParams).then(res => {
+        const page = res.data || {}
+        this.list = page.records || []
+        this.total = page.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    resetQuery() {
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        apiType: undefined,
+        apiName: undefined,
+        apiCode: undefined,
+        status: undefined
+      }
+      this.getList()
+    },
+    handleAdd() {
+      this.form = this.emptyForm()
+      this.openForm = true
+      this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
+    },
+    handleEdit(row) {
+      getExternalApi(row.id).then(res => {
+        this.form = Object.assign(this.emptyForm(), res.data || {})
+        this.openForm = true
+        this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
+      })
+    },
+    submitForm() {
+      this.$refs.formRef.validate(valid => {
+        if (!valid) return
+        saveOrUpdateExternalApi(this.form).then(() => {
+          this.$message.success('保存成功')
+          this.openForm = false
+          this.getList()
+        })
+      })
+    },
+    handleStatusChange(row, val) {
+      changeExternalApiStatus(row.id, val).then(() => {
+        this.$message.success('状态已更新')
+      }).catch(() => {
+        row.status = val === 1 ? 0 : 1
+      })
+    },
+    handleDelete(row) {
+      this.$confirm(`确认删除接口【${row.apiName}】?`, '提示', { type: 'warning' })
+        .then(() => deleteExternalApi(row.id))
+        .then(() => {
+          this.$message.success('删除成功')
+          this.getList()
+        })
+        .catch(() => {})
+    },
+    handleTest(row) {
+      this.currentRow = row
+      this.testForm = { headersJson: '', bodyJson: '', timeoutMs: row.timeoutMs || 10000 }
+      this.testResult = { responseStatus: null, responseBody: '', durationMs: null, success: null, errorMessage: '' }
+      this.openTest = true
+    },
+    doTest() {
+      if (!this.currentRow) return
+      this.testLoading = true
+      testExternalApi(this.currentRow.id, this.testForm).then(res => {
+        this.testResult = res.data || this.testResult
+      }).finally(() => {
+        this.testLoading = false
+      })
+    },
+    handleLogs(row) {
+      this.logsQuery = { pageNum: 1, pageSize: 10, configId: row.id }
+      this.openLogs = true
+      this.getLogs()
+    },
+    getLogs() {
+      this.logsLoading = true
+      pageExternalApiLogs(this.logsQuery).then(res => {
+        const page = res.data || {}
+        this.logs = page.records || []
+        this.logsTotal = page.total || 0
+      }).finally(() => {
+        this.logsLoading = false
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.mb8 { margin-bottom: 8px; }
+</style>
+

+ 119 - 2
src/views/system/config/config.vue

@@ -2655,6 +2655,48 @@
         </div>
       </el-tab-pane>
 
+      <el-tab-pane label="TC在线文档配置" name="tencent.sheetOnlineConfig">
+        <el-form ref="form37" :model="form37" :rules="rules37" label-width="100px">
+          <el-form-item label="client_id">
+            <el-input style="width: 200px" v-model="form37.clientId" placeholder="请输入client_id"></el-input>
+          </el-form-item>
+          <el-form-item label="access_token">
+            <el-input style="width: 200px" v-model="form37.accessToken" placeholder="请输入access_token"></el-input>
+          </el-form-item>
+          <el-form-item label="open_id">
+            <el-input style="width: 200px" v-model="form37.openId" placeholder="请输入open_id"></el-input>
+          </el-form-item>
+          
+          <div style="width: 1000px;padding-left: 100px;">
+            <el-table :data="tencentWordList" border>
+              <el-table-column prop="title" label="标题" min-width="140px"></el-table-column>
+              <el-table-column prop="fileId" label="文件ID" min-width="200px"></el-table-column>
+              <el-table-column prop="type" label="类型" width="80px">
+                <template slot-scope="scope">
+                  <el-tag type="success">{{ scope.row.type || '表格' }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="url" label="URL" min-width="260px" show-overflow-tooltip></el-table-column>
+              <el-table-column label="操作" width="100px">
+                <template slot-scope="scope">
+                  <el-button type="warning" size="small" @click="handleSync(scope.row)">同步</el-button>
+                </template>
+              </el-table-column>
+              
+            </el-table>
+          
+          </div>
+          <div class="footer">
+            <el-button type="primary" @click="handleShowGuide">操作指南</el-button>
+            <el-button type="primary" @click="submitForm37">提  交</el-button>
+            <el-button type="success" @click="createWordDialog.open = true">创建表格</el-button>
+          </div>
+          
+          
+          
+        </el-form>
+      </el-tab-pane>
+
     </el-tabs>
 
 
@@ -2666,11 +2708,25 @@
     >
       <productDeliveryGiftValueSelect ref="DeliveryGiftDetails" @selectDeliveryGift="selectDeliveryGift"/>
     </el-dialog>
+    <el-dialog title="创建腾讯文档表格" :visible.sync="createWordDialog.open" width="450px" append-to-body>
+      <el-form :model="createWordDialog" label-width="80px">
+        <el-form-item label="标题">
+          <el-input v-model="createWordDialog.title" placeholder="请输入表格标题"></el-input>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="createWordDialog.open = false">取 消</el-button>
+        <el-button type="primary" @click="handleCreateFile">确 定</el-button>
+      </span>
+    </el-dialog>
+    <el-dialog title="操作指南" :visible.sync="guideDialogVisible" width="1860px" append-to-body>
+      <img :src="guideImage" style="width: 100%">
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn ,getGatewayList } from '@/api/system/config'
+import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn ,getGatewayList, createTencentWordFile, getTencentWordFiles, synchronization } from '@/api/system/config'
 import { listStore } from '@/api/his/storeProduct'
 import { js } from 'js-beautify'
 import Material from '@/components/Material'
@@ -2784,6 +2840,14 @@ export default {
       form34:{},
       form35:{},
       form36:{},
+      form37:{},
+      createWordDialog: {
+        open: false,
+        title: ''
+      },
+      tencentWordList: [],
+      guideDialogVisible: false,
+      guideImage: require('./操作指南.png'),
       
       form40: {
         enablePhoneConfig: false,
@@ -3104,7 +3168,9 @@ export default {
     },
     handleClick(tab, event) {
       this.getConfigByKey(tab.name)
-
+      if (tab.name === 'tencent.sheetOnlineConfig') {
+        this.loadTencentWordFiles()
+      }
     },
     handleAddProduct() {
       setTimeout(() => {
@@ -3342,6 +3408,9 @@ export default {
         if(key=="im.config"){
           this.form34 = this.safeParseConfig(configValue, { ...this.form34 });
         }
+        if(key=="tencent.sheetOnlineConfig"){
+          this.form37 = this.safeParseConfig(configValue, { ...this.form37 });
+        }
         if(key == 'vc.config'){
            if(!!response.data){
           this.configId = response.data.configId
@@ -3665,6 +3734,54 @@ export default {
         }
       });
     },
+    submitForm37(){
+      var param={configId:this.configId,configValue:JSON.stringify(this.form37),configKey:this.configKey,configName:"TC在线文档配置"}
+      updateConfigByKey(param).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+        }
+      });
+    },
+    handleCreateFile(){
+      const title = this.createWordDialog.title;
+      if (!title || !title.trim()) {
+        this.msgError("请输入表格标题");
+        return;
+      }
+      createTencentWordFile(title).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("创建成功");
+          this.createWordDialog.open = false;
+          this.createWordDialog.title = '';
+          this.loadTencentWordFiles();
+        } else {
+          this.msgError(response.msg || "创建失败");
+        }
+      });
+    },
+    loadTencentWordFiles(){
+      getTencentWordFiles().then(response => {
+        this.tencentWordList = response.data || [];
+      });
+    },
+    handleSync(row){
+      this.$confirm('确定要同步「' + row.title + '」的工作表数据吗?', '同步', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'info'
+      }).then(() => {
+        synchronization(row.fileId).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess("同步成功");
+          } else {
+            this.msgError(response.msg || "同步失败");
+          }
+        });
+      }).catch(() => {});
+    },
+    handleShowGuide(){
+      this.guideDialogVisible = true;
+    },
     submitForm35(){
       var param={configId:this.configId,configValue:JSON.stringify(this.form35)}
       updateConfigByKey(param).then(response => {

BIN
src/views/system/config/操作指南.png