index.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <template>
  2. <view class="page-container">
  3. <!-- 顶部导航栏 -->
  4. <wd-navbar
  5. title="错误日志详情"
  6. left-arrow placeholder safe-area-inset-top fixed
  7. @click-left="handleBack"
  8. />
  9. <!-- 详情内容 -->
  10. <view class="p-24rpx pb-200rpx">
  11. <wd-cell-group custom-class="cell-group" border>
  12. <wd-cell title="日志编号" :value="String(formData?.id ?? '-')" />
  13. <wd-cell title="链路追踪" :value="formData?.traceId || '-'" />
  14. <wd-cell title="应用名" :value="formData?.applicationName || '-'" />
  15. <wd-cell title="用户编号" :value="String(formData?.userId ?? '-')" />
  16. <wd-cell title="用户类型" :value="getDictLabel(DICT_TYPE.USER_TYPE, formData?.userType) || '-'" />
  17. <wd-cell title="用户 IP" :value="formData?.userIp || '-'" />
  18. <wd-cell title="用户 UA" :value="formData?.userAgent || '-'" />
  19. <wd-cell title="请求信息" :value="getRequestInfo()" />
  20. <wd-cell title="请求参数" is-link @click="handleCopyText(formData?.requestParams, '请求参数')">
  21. <view class="max-w-400rpx truncate text-right">
  22. {{ formData?.requestParams || '-' }}
  23. </view>
  24. </wd-cell>
  25. <wd-cell title="异常时间" :value="formatDateTime(formData?.exceptionTime) || '-'" />
  26. <wd-cell title="异常名" :value="formData?.exceptionName || '-'" />
  27. <wd-cell title="处理状态">
  28. <dict-tag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="formData?.processStatus" />
  29. </wd-cell>
  30. <wd-cell v-if="formData?.processUserId" title="处理人" :value="String(formData.processUserId)" />
  31. <wd-cell v-if="formData?.processTime" title="处理时间" :value="formatDateTime(formData.processTime) || '-'" />
  32. </wd-cell-group>
  33. <!-- 异常堆栈 -->
  34. <view v-if="formData?.exceptionStackTrace" class="mt-24rpx">
  35. <view class="mb-16rpx text-28rpx text-[#333] font-semibold">
  36. 异常堆栈
  37. </view>
  38. <view class="rounded-12rpx bg-white p-24rpx shadow-sm">
  39. <scroll-view scroll-y class="max-h-600rpx">
  40. <text class="whitespace-pre-wrap break-all text-24rpx text-[#666] leading-relaxed">{{ formData.exceptionStackTrace }}</text>
  41. </scroll-view>
  42. </view>
  43. </view>
  44. </view>
  45. <!-- 底部操作按钮 -->
  46. <view v-if="formData?.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" class="safe-area-inset-bottom fixed bottom-0 left-0 right-0 bg-white p-24rpx">
  47. <view class="w-full flex gap-24rpx">
  48. <wd-button class="flex-1" type="success" :loading="processing" @click="handleProcess(InfraApiErrorLogProcessStatusEnum.DONE)">
  49. 已处理
  50. </wd-button>
  51. <wd-button class="flex-1" type="warning" :loading="processing" @click="handleProcess(InfraApiErrorLogProcessStatusEnum.IGNORE)">
  52. 已忽略
  53. </wd-button>
  54. </view>
  55. </view>
  56. </view>
  57. </template>
  58. <script lang="ts" setup>
  59. import type { ApiErrorLog } from '@/api/infra/apiErrorLog'
  60. import { onMounted, ref } from 'vue'
  61. import { useToast } from 'wot-design-uni'
  62. import { getApiErrorLog, updateApiErrorLogStatus } from '@/api/infra/apiErrorLog'
  63. import { getDictLabel } from '@/hooks/useDict'
  64. import { DICT_TYPE, InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
  65. import { formatDateTime } from '@/utils/date'
  66. const props = defineProps<{
  67. id: number
  68. }>()
  69. definePage({
  70. style: {
  71. navigationBarTitleText: '',
  72. navigationStyle: 'custom',
  73. },
  74. })
  75. const toast = useToast()
  76. const formData = ref<ApiErrorLog>() // 详情数据
  77. const processing = ref(false) // 处理中
  78. /** 返回上一页 */
  79. function handleBack() {
  80. uni.navigateBack()
  81. }
  82. /** 复制文本并提示 */
  83. function handleCopyText(text?: string, title?: string) {
  84. if (!text || text === '-') {
  85. return
  86. }
  87. uni.setClipboardData({
  88. data: text,
  89. success: () => {
  90. uni.showToast({
  91. title: `${title || '内容'}已复制`,
  92. icon: 'success',
  93. })
  94. },
  95. })
  96. }
  97. /** 加载详情 */
  98. async function getDetail() {
  99. if (!props.id) {
  100. return
  101. }
  102. formData.value = await getApiErrorLog(props.id)
  103. }
  104. /** 获取请求信息 */
  105. function getRequestInfo() {
  106. if (formData.value?.requestMethod && formData.value?.requestUrl) {
  107. return `${formData.value.requestMethod} ${formData.value.requestUrl}`
  108. }
  109. return '-'
  110. }
  111. /** 处理日志 */
  112. function handleProcess(processStatus: number) {
  113. if (!props.id) {
  114. return
  115. }
  116. const statusText = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'
  117. uni.showModal({
  118. title: '提示',
  119. content: `确定标记为${statusText}吗?`,
  120. success: async (res) => {
  121. if (!res.confirm) {
  122. return
  123. }
  124. processing.value = true
  125. try {
  126. await updateApiErrorLogStatus(props.id, processStatus)
  127. toast.success('操作成功')
  128. // 刷新详情
  129. await getDetail()
  130. } finally {
  131. processing.value = false
  132. }
  133. },
  134. })
  135. }
  136. /** 初始化 */
  137. onMounted(() => {
  138. getDetail()
  139. })
  140. </script>
  141. <style lang="scss" scoped>
  142. :deep(.cell-group) {
  143. border-radius: 12rpx;
  144. overflow: hidden;
  145. box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
  146. }
  147. .safe-area-inset-bottom {
  148. padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
  149. }
  150. </style>