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.

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