You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
21 KiB

  1. <template>
  2. <div class="mod-config">
  3. <!-- 条件查询 -->
  4. <el-card :class="['search-card', { 'collapsed': !searchExpanded }]" shadow="hover">
  5. <div slot="header" class="search-header">
  6. <div class="header-left">
  7. <i class="el-icon-search"></i>
  8. <span class="header-title">接口日志查询</span>
  9. </div>
  10. <div class="header-right">
  11. <el-button
  12. type="text"
  13. size="small"
  14. @click="toggleSearchExpand"
  15. class="collapse-btn">
  16. <i :class="searchExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
  17. {{ searchExpanded ? '收起' : '展开' }}
  18. </el-button>
  19. </div>
  20. </div>
  21. <el-form
  22. :model="searchForm"
  23. label-width="110px"
  24. class="search-form"
  25. @keyup.enter.native="getDataList">
  26. <!-- 所有查询条件 - 可展开/收起 -->
  27. <template v-if="searchExpanded">
  28. <!-- 第一行 -->
  29. <el-row :gutter="20">
  30. <el-col :span="6">
  31. <el-form-item label="接口名称">
  32. <el-input
  33. v-model="searchForm.interfaceName"
  34. placeholder="请输入接口名称"
  35. clearable
  36. prefix-icon="el-icon-document">
  37. </el-input>
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="6">
  41. <el-form-item label="请求ID">
  42. <el-input
  43. v-model="searchForm.requestId"
  44. placeholder="请输入请求ID"
  45. clearable
  46. prefix-icon="el-icon-key">
  47. </el-input>
  48. </el-form-item>
  49. </el-col>
  50. <el-col :span="6">
  51. <el-form-item label="关联单据号">
  52. <el-input
  53. v-model="searchForm.orderNo"
  54. placeholder="请输入关联单据号"
  55. clearable>
  56. </el-input>
  57. </el-form-item>
  58. </el-col>
  59. <el-col :span="6">
  60. <el-form-item label="关联单据类型">
  61. <el-input
  62. v-model="searchForm.interfaceType"
  63. placeholder="请输入关联单据类型"
  64. clearable>
  65. </el-input>
  66. </el-form-item>
  67. </el-col>
  68. </el-row>
  69. <!-- 第二行 -->
  70. <el-row :gutter="20">
  71. <el-col :span="6">
  72. <el-form-item label="状态码">
  73. <el-input
  74. v-model="searchForm.statusCode"
  75. placeholder="请输入状态码"
  76. clearable>
  77. </el-input>
  78. </el-form-item>
  79. </el-col>
  80. <el-col :span="6">
  81. <el-form-item label="来源系统">
  82. <el-input
  83. v-model="searchForm.sourceSystem"
  84. placeholder="请输入来源系统"
  85. clearable>
  86. </el-input>
  87. </el-form-item>
  88. </el-col>
  89. <el-col :span="6">
  90. <el-form-item label="目标系统">
  91. <el-input
  92. v-model="searchForm.targetSystem"
  93. placeholder="请输入目标系统"
  94. clearable>
  95. </el-input>
  96. </el-form-item>
  97. </el-col>
  98. <el-col :span="6">
  99. <el-form-item label="是否需要重试">
  100. <el-select v-model="searchForm.needRetryFlag" placeholder="请选择" clearable>
  101. <el-option label="是" :value="1"></el-option>
  102. <el-option label="否" :value="0"></el-option>
  103. </el-select>
  104. </el-form-item>
  105. </el-col>
  106. </el-row>
  107. <!-- 第三行 -->
  108. <el-row :gutter="20">
  109. <el-col :span="6">
  110. <el-form-item label="仓库ID">
  111. <el-input
  112. v-model="searchForm.warehouseId"
  113. placeholder="请输入仓库ID"
  114. clearable>
  115. </el-input>
  116. </el-form-item>
  117. </el-col>
  118. <el-col :span="12">
  119. <el-form-item label="创建时间">
  120. <el-date-picker
  121. v-model="searchForm.dateRange"
  122. type="daterange"
  123. range-separator="至"
  124. start-placeholder="开始日期"
  125. end-placeholder="结束日期"
  126. value-format="yyyy-MM-dd"
  127. style="width: 100%">
  128. </el-date-picker>
  129. </el-form-item>
  130. </el-col>
  131. </el-row>
  132. </template>
  133. <!-- 操作按钮区域 -->
  134. <div class="search-actions">
  135. <div class="action-left">
  136. <el-button type="primary" @click="getDataList" icon="el-icon-search">查询</el-button>
  137. <el-button @click="resetSearch" icon="el-icon-refresh-left">重置</el-button>
  138. </div>
  139. </div>
  140. </el-form>
  141. </el-card>
  142. <!-- 表格操作按钮 -->
  143. <div class="table-actions">
  144. <el-button
  145. type="danger"
  146. @click="deleteHandle()"
  147. icon="el-icon-delete"
  148. size="small">
  149. 批量删除
  150. </el-button>
  151. <el-button
  152. type="warning"
  153. @click="batchRetryInterfaceHandle()"
  154. icon="el-icon-refresh-right"
  155. size="small">
  156. 手动重试
  157. </el-button>
  158. </div>
  159. <!-- 数据表格 -->
  160. <el-table
  161. :height="tableHeight"
  162. :data="dataList"
  163. border
  164. v-loading="dataListLoading"
  165. @selection-change="selectionChangeHandle"
  166. style="width: 100%;">
  167. <el-table-column
  168. type="selection"
  169. header-align="center"
  170. align="center"
  171. width="50">
  172. </el-table-column>
  173. <el-table-column
  174. prop="id"
  175. header-align="center"
  176. align="center"
  177. label="ID"
  178. width="60">
  179. </el-table-column>
  180. <el-table-column
  181. prop="requestId"
  182. header-align="center"
  183. align="center"
  184. label="请求ID"
  185. width="100">
  186. </el-table-column>
  187. <el-table-column
  188. prop="interfaceName"
  189. header-align="center"
  190. align="left"
  191. label="接口名称"
  192. min-width="250">
  193. </el-table-column>
  194. <el-table-column
  195. prop="orderNo"
  196. header-align="center"
  197. align="left"
  198. label="关联单据号"
  199. min-width="150">
  200. </el-table-column>
  201. <el-table-column
  202. prop="interfaceType"
  203. header-align="center"
  204. align="center"
  205. label="关联单据类型"
  206. min-width="170">
  207. </el-table-column>
  208. <el-table-column
  209. prop="statusCode"
  210. header-align="center"
  211. align="center"
  212. label="状态码"
  213. width="100">
  214. <template slot-scope="scope">
  215. <el-tag :type="scope.row.statusCode === '200' ? 'success' : 'danger'">
  216. {{ scope.row.statusCode }}
  217. </el-tag>
  218. </template>
  219. </el-table-column>
  220. <el-table-column
  221. prop="message"
  222. header-align="center"
  223. align="left"
  224. label="消息"
  225. min-width="250"
  226. show-overflow-tooltip>
  227. </el-table-column>
  228. <el-table-column
  229. prop="sourceSystem"
  230. header-align="center"
  231. align="center"
  232. label="来源系统"
  233. width="100">
  234. </el-table-column>
  235. <el-table-column
  236. prop="targetSystem"
  237. header-align="center"
  238. align="center"
  239. label="目标系统"
  240. width="100">
  241. </el-table-column>
  242. <el-table-column
  243. prop="retryCount"
  244. header-align="center"
  245. align="center"
  246. label="重试次数"
  247. width="90">
  248. </el-table-column>
  249. <el-table-column
  250. prop="warehouseId"
  251. header-align="center"
  252. align="center"
  253. label="仓库ID"
  254. width="120">
  255. </el-table-column>
  256. <el-table-column
  257. prop="createdDate"
  258. header-align="center"
  259. align="center"
  260. label="创建时间"
  261. width="155">
  262. </el-table-column>
  263. <el-table-column
  264. fixed="right"
  265. header-align="center"
  266. align="center"
  267. width="100"
  268. label="操作">
  269. <template slot-scope="scope">
  270. <el-button type="text" size="small" @click="viewParamsHandle(scope.row)">
  271. 接口参数
  272. </el-button>
  273. </template>
  274. </el-table-column>
  275. </el-table>
  276. <!-- 分页 -->
  277. <el-pagination
  278. @size-change="sizeChangeHandle"
  279. @current-change="currentChangeHandle"
  280. :current-page="pageIndex"
  281. :page-sizes="[10, 20, 50, 100, 200]"
  282. :page-size="pageSize"
  283. :total="totalPage"
  284. layout="total, sizes, prev, pager, next, jumper"
  285. style="margin-top: 0px;">
  286. </el-pagination>
  287. <!-- 接口参数对话框 -->
  288. <el-dialog
  289. title="接口参数详情"
  290. :visible.sync="paramsDialogVisible"
  291. width="60%"
  292. :close-on-click-modal="false">
  293. <div class="json-viewer">
  294. <pre>{{ formattedParams }}</pre>
  295. </div>
  296. <span slot="footer" class="dialog-footer">
  297. <el-button @click="paramsDialogVisible = false"> </el-button>
  298. <el-button type="primary" @click="copyParams">复制参数</el-button>
  299. </span>
  300. </el-dialog>
  301. </div>
  302. </template>
  303. <script>
  304. import { searchApiLogs, getApiLogParams, deleteApiLogs, retryApiLogs, retryInterface } from '@/api/sys/interface-log'
  305. export default {
  306. data() {
  307. return {
  308. searchExpanded: false,
  309. searchForm: {
  310. interfaceName: '',
  311. requestId: '',
  312. orderNo: '',
  313. interfaceType: '',
  314. statusCode: '',
  315. sourceSystem: '',
  316. targetSystem: '',
  317. needRetryFlag: null,
  318. warehouseId: '',
  319. dateRange: [],
  320. userName: this.$store.state.user.name
  321. },
  322. dataList: [],
  323. pageIndex: 1,
  324. pageSize: 20,
  325. totalPage: 0,
  326. dataListLoading: false,
  327. dataListSelections: [],
  328. paramsDialogVisible: false,
  329. currentParams: null,
  330. tableHeight: 200
  331. }
  332. },
  333. computed: {
  334. formattedParams() {
  335. if (!this.currentParams) return ''
  336. return JSON.stringify(this.currentParams, null, 2)
  337. }
  338. },
  339. mounted() {
  340. this.$nextTick(() => {
  341. this.tableHeight = window.innerHeight - 280
  342. })
  343. this.getDataList()
  344. },
  345. methods: {
  346. // 切换搜索框展开/收起
  347. toggleSearchExpand() {
  348. this.searchExpanded = !this.searchExpanded
  349. },
  350. // 获取数据列表
  351. getDataList() {
  352. this.dataListLoading = true
  353. const params = {
  354. page: this.pageIndex,
  355. limit: this.pageSize,
  356. ...this.searchForm
  357. }
  358. // 处理日期范围
  359. if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
  360. params.startDate = this.searchForm.dateRange[0]
  361. params.endDate = this.searchForm.dateRange[1]
  362. }
  363. delete params.dateRange
  364. searchApiLogs(params).then(({ data }) => {
  365. if (data && data.code === 0) {
  366. this.dataList = data.page.list
  367. this.totalPage = data.page.totalCount
  368. } else {
  369. this.dataList = []
  370. this.totalPage = 0
  371. }
  372. this.dataListLoading = false
  373. }).catch(() => {
  374. this.dataListLoading = false
  375. })
  376. },
  377. // 重置查询
  378. resetSearch() {
  379. this.searchForm = {
  380. interfaceName: '',
  381. requestId: '',
  382. reDocumentNo: '',
  383. reDocumentType: '',
  384. statusCode: '',
  385. sourceSystem: '',
  386. targetSystem: '',
  387. needRetryFlag: null,
  388. warehouseId: '',
  389. dateRange: [],
  390. userName: this.$store.state.user.name
  391. }
  392. this.pageIndex = 1
  393. this.getDataList()
  394. },
  395. // 每页数
  396. sizeChangeHandle(val) {
  397. this.pageSize = val
  398. this.pageIndex = 1
  399. this.getDataList()
  400. },
  401. // 当前页
  402. currentChangeHandle(val) {
  403. this.pageIndex = val
  404. this.getDataList()
  405. },
  406. // 多选
  407. selectionChangeHandle(val) {
  408. this.dataListSelections = val
  409. },
  410. // 查看接口参数
  411. viewParamsHandle(row) {
  412. const params = {
  413. site: row.site,
  414. buNo: row.buNo,
  415. requestId: row.requestId,
  416. requestGroupId: row.requestGroupId
  417. }
  418. getApiLogParams(params).then(({ data }) => {
  419. if (data && data.code === 0) {
  420. this.currentParams = data.params
  421. this.paramsDialogVisible = true
  422. } else {
  423. this.$message.error(data.msg || '获取接口参数失败')
  424. }
  425. })
  426. },
  427. // 复制参数
  428. copyParams() {
  429. const textarea = document.createElement('textarea')
  430. textarea.value = this.formattedParams
  431. document.body.appendChild(textarea)
  432. textarea.select()
  433. document.execCommand('copy')
  434. document.body.removeChild(textarea)
  435. this.$message.success('复制成功')
  436. },
  437. // 删除
  438. deleteHandle() {
  439. const ids = this.dataListSelections.map(item => item.id)
  440. this.$confirm('确定要删除选中的记录吗?', '提示', {
  441. confirmButtonText: '确定',
  442. cancelButtonText: '取消',
  443. type: 'warning'
  444. }).then(() => {
  445. deleteApiLogs({ ids }).then(({ data }) => {
  446. if (data && data.code === 0) {
  447. this.$message.success('删除成功')
  448. this.getDataList()
  449. } else {
  450. this.$message.error(data.msg || '删除失败')
  451. }
  452. })
  453. })
  454. },
  455. // 批量手动重试接口
  456. batchRetryInterfaceHandle() {
  457. if (this.dataListSelections.length === 0) {
  458. this.$message.warning('请选择要重试的记录!')
  459. return
  460. }
  461. const count = this.dataListSelections.length
  462. this.$confirm(`确定要手动重试选中的 ${count} 个接口吗?`, '提示', {
  463. confirmButtonText: '确定',
  464. cancelButtonText: '取消',
  465. type: 'warning'
  466. }).then(() => {
  467. const loading = this.$loading({
  468. lock: true,
  469. text: `正在重试 ${count} 个接口,请稍候...`,
  470. spinner: 'el-icon-loading',
  471. background: 'rgba(0, 0, 0, 0.7)'
  472. })
  473. // 构造批量重试参数
  474. const retryList = this.dataListSelections.map(item => ({
  475. site: item.site,
  476. buNo: item.buNo,
  477. requestId: item.requestId,
  478. requestGroupId: item.requestGroupId,
  479. interfaceName: item.interfaceName
  480. }))
  481. // 调用批量重试接口
  482. retryInterface({ retryList }).then(({ data }) => {
  483. loading.close()
  484. if (data && data.code === 0) {
  485. const result = data.result
  486. const successCount = result.successCount || 0
  487. const failureCount = result.failureCount || 0
  488. const errorCount = result.errorCount || 0
  489. // 显示详细结果
  490. let message = `重试完成!成功:${successCount},失败:${failureCount},异常:${errorCount}`
  491. if (successCount === count) {
  492. this.$message.success(message)
  493. } else if (successCount > 0) {
  494. this.$message.warning(message)
  495. } else {
  496. this.$message.error(message)
  497. }
  498. // 显示详细结果对话框
  499. if (result.details && result.details.length > 0) {
  500. this.showRetryResultDialog(result.details)
  501. }
  502. this.getDataList()
  503. } else {
  504. this.$message.error(data.msg || '批量重试失败')
  505. }
  506. }).catch(err => {
  507. loading.close()
  508. this.$message.error('批量重试异常:' + (err.message || '请求失败'))
  509. })
  510. })
  511. },
  512. // 显示重试结果详情对话框
  513. showRetryResultDialog(details) {
  514. const successList = details.filter(d => d.status === 'success')
  515. const failureList = details.filter(d => d.status === 'failure')
  516. const errorList = details.filter(d => d.status === 'error')
  517. let messageHtml = '<div style="max-height: 400px; overflow-y: auto;">'
  518. if (successList.length > 0) {
  519. messageHtml += '<div style="margin-bottom: 15px;"><strong style="color: #67C23A;">成功 (' + successList.length + '):</strong><ul style="margin: 5px 0; padding-left: 20px;">'
  520. successList.forEach(item => {
  521. messageHtml += '<li>' + item.interfaceName + (item.u8CCode ? ' - U8单号:' + item.u8CCode : '') + '</li>'
  522. })
  523. messageHtml += '</ul></div>'
  524. }
  525. if (failureList.length > 0) {
  526. messageHtml += '<div style="margin-bottom: 15px;"><strong style="color: #E6A23C;">失败 (' + failureList.length + '):</strong><ul style="margin: 5px 0; padding-left: 20px;">'
  527. failureList.forEach(item => {
  528. messageHtml += '<li>' + item.interfaceName + ':' + (item.message || '未知错误') + '</li>'
  529. })
  530. messageHtml += '</ul></div>'
  531. }
  532. if (errorList.length > 0) {
  533. messageHtml += '<div><strong style="color: #F56C6C;">异常 (' + errorList.length + '):</strong><ul style="margin: 5px 0; padding-left: 20px;">'
  534. errorList.forEach(item => {
  535. messageHtml += '<li>' + item.interfaceName + ':' + (item.message || '未知异常') + '</li>'
  536. })
  537. messageHtml += '</ul></div>'
  538. }
  539. messageHtml += '</div>'
  540. this.$alert(messageHtml, '批量重试结果详情', {
  541. confirmButtonText: '确定',
  542. dangerouslyUseHTMLString: true
  543. })
  544. }
  545. }
  546. }
  547. </script>
  548. <style scoped>
  549. /* 搜索卡片样式 */
  550. .search-card {
  551. margin-bottom: 16px;
  552. border-radius: 8px;
  553. overflow: hidden;
  554. transition: all 0.3s ease;
  555. }
  556. .search-card:hover {
  557. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  558. }
  559. .search-card /deep/ .el-card__header {
  560. padding: 5px 20px;
  561. background: linear-gradient(135deg, #9ac3d0 20%, #b6c7dd 80%);
  562. border-bottom: none;
  563. }
  564. .search-header {
  565. display: flex;
  566. justify-content: space-between;
  567. align-items: center;
  568. }
  569. .header-left {
  570. display: flex;
  571. align-items: center;
  572. color: #fff;
  573. }
  574. .header-left i {
  575. font-size: 16px;
  576. margin-right: 8px;
  577. }
  578. .header-title {
  579. font-size: 14px;
  580. font-weight: 600;
  581. letter-spacing: 0.5px;
  582. }
  583. .header-right {
  584. color: #fff;
  585. }
  586. .collapse-btn {
  587. color: #fff;
  588. font-weight: 500;
  589. transition: all 0.3s ease;
  590. }
  591. .collapse-btn:hover {
  592. color: #f0f0f0;
  593. transform: translateY(-1px);
  594. }
  595. .collapse-btn i {
  596. transition: transform 0.3s ease;
  597. }
  598. /* 搜索表单样式 */
  599. .search-form {
  600. padding: 10px 0;
  601. min-height: 0;
  602. }
  603. /* 卡片主体样式 */
  604. .search-card /deep/ .el-card__body {
  605. padding: 10px;
  606. transition: all 0.3s ease;
  607. }
  608. /* 收起时的样式 */
  609. .search-card.collapsed /deep/ .el-card__body {
  610. padding: 10px 20px;
  611. }
  612. .search-form /deep/ .el-form-item {
  613. margin-bottom: 18px;
  614. }
  615. .search-form /deep/ .el-form-item__label {
  616. font-weight: 500;
  617. color: #606266;
  618. padding-bottom: 6px;
  619. }
  620. .search-form /deep/ .el-input__inner,
  621. .search-form /deep/ .el-textarea__inner {
  622. border-radius: 6px;
  623. border: 1px solid #DCDFE6;
  624. transition: all 0.3s ease;
  625. }
  626. .search-form /deep/ .el-input__inner:focus,
  627. .search-form /deep/ .el-textarea__inner:focus {
  628. border-color: #667eea;
  629. box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
  630. }
  631. .search-form /deep/ .el-select {
  632. width: 100%;
  633. }
  634. .search-form /deep/ .el-date-editor.el-input {
  635. width: 100%;
  636. }
  637. /* 操作按钮区域 */
  638. .search-actions {
  639. display: flex;
  640. justify-content: space-between;
  641. align-items: center;
  642. padding: 8px 0 2px 0;
  643. }
  644. /* 展开时显示上边框 */
  645. .search-card:not(.collapsed) .search-actions {
  646. border-top: 1px solid #f0f0f0;
  647. margin-top: 8px;
  648. }
  649. /* 收起时不显示上边框和上边距 */
  650. .search-card.collapsed .search-actions {
  651. border-top: none;
  652. margin-top: 0;
  653. padding-top: 0;
  654. }
  655. .action-left,
  656. .action-right {
  657. display: flex;
  658. gap: 8px;
  659. }
  660. .search-actions .el-button {
  661. border-radius: 4px;
  662. padding: 5px 10px;
  663. font-size: 12px;
  664. font-weight: 500;
  665. transition: all 0.3s ease;
  666. }
  667. .search-actions .el-button:hover {
  668. transform: translateY(-2px);
  669. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  670. }
  671. .search-actions .el-button--primary {
  672. background: #60aeff;
  673. border-color: #60aeff;
  674. }
  675. .search-actions .el-button--primary:hover {
  676. background: #7dbdff;
  677. border-color: #7dbdff;
  678. }
  679. /* 表格操作按钮区域 */
  680. .table-actions {
  681. margin-bottom: 8px;
  682. margin-top: 8px;
  683. }
  684. .table-actions .el-button {
  685. border-radius: 4px;
  686. font-weight: 500;
  687. transition: all 0.3s ease;
  688. }
  689. .table-actions .el-button:hover {
  690. transform: translateY(-2px);
  691. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  692. }
  693. /* 响应式设计 */
  694. @media (max-width: 1200px) {
  695. .search-actions {
  696. flex-direction: column;
  697. gap: 10px;
  698. }
  699. .action-left,
  700. .action-right {
  701. width: 100%;
  702. justify-content: center;
  703. }
  704. }
  705. /* 表格样式 */
  706. .el-table /deep/ .cell {
  707. height: auto;
  708. line-height: 1.5;
  709. }
  710. /* JSON查看器样式 */
  711. .json-viewer {
  712. max-height: 500px;
  713. overflow: auto;
  714. background-color: #f5f5f5;
  715. border: 1px solid #e0e0e0;
  716. border-radius: 4px;
  717. padding: 15px;
  718. }
  719. .json-viewer pre {
  720. margin: 0;
  721. font-family: 'Courier New', Courier, monospace;
  722. font-size: 13px;
  723. line-height: 1.6;
  724. color: #333;
  725. white-space: pre-wrap;
  726. word-wrap: break-word;
  727. }
  728. </style>