ReportParserImport.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <template>
  2. <div class="report-import admin-page">
  3. <el-card>
  4. <div slot="header">
  5. <span>解析器导入管理</span>
  6. <div style="float: right; display: flex; gap: 10px;">
  7. <el-button type="primary" size="small" icon="el-icon-upload2" @click="uploadDialogVisible = true">上传导入包</el-button>
  8. </div>
  9. </div>
  10. <el-table :data="imports" v-loading="loading" border stripe>
  11. <el-table-column prop="id" label="ID" width="60" />
  12. <el-table-column prop="packageName" label="导入包名称" min-width="200" />
  13. <el-table-column prop="packageFileUrl" label="文件" width="150" />
  14. <el-table-column label="状态" width="100">
  15. <template slot-scope="{ row }">
  16. <el-tag :type="statusType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
  17. </template>
  18. </el-table-column>
  19. <el-table-column prop="createdAt" label="导入时间" width="180" />
  20. <el-table-column label="操作" width="160" fixed="right">
  21. <template slot-scope="{ row }">
  22. <template v-if="row.status === 'pending'">
  23. <el-button size="mini" type="success" @click="handleApprove(row)">批准</el-button>
  24. <el-button size="mini" type="danger" @click="handleReject(row)">驳回</el-button>
  25. </template>
  26. <el-button size="mini" type="primary" @click="handlePreview(row)">预览</el-button>
  27. </template>
  28. </el-table-column>
  29. </el-table>
  30. </el-card>
  31. <!-- 上传导入包对话框 -->
  32. <el-dialog :visible.sync="uploadDialogVisible" title="上传导入包" width="600px" :close-on-click-modal="false">
  33. <el-upload
  34. drag
  35. :auto-upload="false"
  36. :limit="1"
  37. accept=".json"
  38. :on-change="handleFileChange">
  39. <i class="el-icon-upload"></i>
  40. <div class="el-upload__text">将 json 文件拖到此处,或<em>点击上传</em></div>
  41. <div class="el-upload__tip" slot="tip">导入包 JSON 格式:{ packageName, typeId, displayName, family, description, parserScript, fingerprints: [{pattern, weight, isExclusion}] }</div>
  42. </el-upload>
  43. <div slot="footer">
  44. <el-button @click="uploadDialogVisible = false">取消</el-button>
  45. <el-button type="primary" @click="handleImport" :loading="submitting">确认导入</el-button>
  46. </div>
  47. </el-dialog>
  48. <!-- 预览导入包对话框 -->
  49. <el-dialog :visible.sync="previewDialogVisible" title="导入包预览" width="700px">
  50. <div v-if="previewData">
  51. <el-descriptions :column="2" border size="small">
  52. <el-descriptions-item label="名称">{{ previewData.packageName }}</el-descriptions-item>
  53. <el-descriptions-item label="类型ID">{{ previewData.typeId }}</el-descriptions-item>
  54. <el-descriptions-item label="显示名称">{{ previewData.displayName }}</el-descriptions-item>
  55. <el-descriptions-item label="家族">{{ previewData.family }}</el-descriptions-item>
  56. <el-descriptions-item label="描述" :span="2">{{ previewData.description }}</el-descriptions-item>
  57. </el-descriptions>
  58. <h4>指纹规则</h4>
  59. <el-table :data="previewData.fingerprints || []" border size="small">
  60. <el-table-column prop="pattern" label="文本特征" min-width="200" />
  61. <el-table-column prop="weight" label="权重" width="70" />
  62. <el-table-column label="类型" width="80">
  63. <template slot-scope="{ row }">
  64. <el-tag :type="row.isExclusion ? 'danger' : 'success'" size="mini">{{ row.isExclusion ? '排除' : '正向' }}</el-tag>
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <h4>解析脚本</h4>
  69. <pre class="script-preview">{{ previewData.parserScript }}</pre>
  70. </div>
  71. </el-dialog>
  72. </div>
  73. </template>
  74. <script>
  75. import { getParserImports, approveParserImport, rejectParserImport, importParserPackage } from '@/api/reportParser'
  76. export default {
  77. name: 'ReportParserImport',
  78. data() {
  79. return {
  80. loading: false, submitting: false,
  81. imports: [],
  82. uploadDialogVisible: false,
  83. previewDialogVisible: false,
  84. selectedFile: null,
  85. previewData: null,
  86. }
  87. },
  88. mounted() { this.fetchImports() },
  89. methods: {
  90. async fetchImports() {
  91. this.loading = true
  92. try {
  93. const res = await getParserImports({ page: 1, size: 50 })
  94. this.imports = res.data?.records || res.data || []
  95. } catch (e) { this.$message.error('加载失败') }
  96. this.loading = false
  97. },
  98. statusType(status) {
  99. return { pending: 'warning', approved: 'success', rejected: 'danger' }[status] || 'info'
  100. },
  101. statusText(status) {
  102. return { pending: '待审核', approved: '已批准', rejected: '已驳回' }[status] || status
  103. },
  104. handleFileChange(file) {
  105. this.selectedFile = file.raw
  106. },
  107. async handleImport() {
  108. if (!this.selectedFile) {
  109. this.$message.warning('请先选择文件')
  110. return
  111. }
  112. this.submitting = true
  113. const reader = new FileReader()
  114. reader.onload = async (e) => {
  115. try {
  116. const jsonContent = e.target.result
  117. const res = await importParserPackage({ jsonContent, fileName: this.selectedFile.name })
  118. this.$message.success('导入成功,待审核')
  119. this.uploadDialogVisible = false
  120. this.selectedFile = null
  121. this.fetchImports()
  122. } catch (err) { this.$message.error('导入失败') }
  123. this.submitting = false
  124. }
  125. reader.readAsText(this.selectedFile)
  126. },
  127. handlePreview(row) {
  128. try {
  129. const jsonContent = row.fingerprintJson
  130. this.previewData = JSON.parse(jsonContent)
  131. this.previewDialogVisible = true
  132. } catch (e) { this.$message.error('预览失败:JSON 解析错误') }
  133. },
  134. handleApprove(row) {
  135. this.$confirm('确认批准此导入?将注册报告类型和指纹规则。', '提示', { type: 'warning' })
  136. .then(async () => {
  137. try {
  138. await approveParserImport({ id: row.id })
  139. this.$message.success('已批准')
  140. this.fetchImports()
  141. } catch (e) { this.$message.error('操作失败') }
  142. }).catch(() => {})
  143. },
  144. handleReject(row) {
  145. this.$prompt('请输入驳回原因', '驳回导入', { inputPlaceholder: '驳回原因', inputValidator: (v) => !!v || '请输入原因' })
  146. .then(async ({ value }) => {
  147. try {
  148. await rejectParserImport({ id: row.id, note: value })
  149. this.$message.success('已驳回')
  150. this.fetchImports()
  151. } catch (e) { this.$message.error('操作失败') }
  152. }).catch(() => {})
  153. },
  154. }
  155. }
  156. </script>
  157. <style scoped>
  158. .script-preview {
  159. background: #f5f7fa;
  160. border: 1px solid #e4e7ed;
  161. border-radius: 4px;
  162. padding: 10px;
  163. max-height: 300px;
  164. overflow: auto;
  165. font-size: 12px;
  166. white-space: pre-wrap;
  167. word-break: break-all;
  168. }
  169. </style>