Jelajahi Sumber

数据审核

Xiao_123 5 hari lalu
induk
melakukan
6a0d5191a0

+ 13 - 0
src/utils/date.js

@@ -457,3 +457,16 @@ export function formatGMT (time) {
   // const dd = '-' + (d < 10 ? '0' + d : d)
   return date.getFullYear() + '-' + m.toString().padStart(2, '0') + '-' + d.toString().padStart(2, '0')
 }
+
+export function formatDate (date) {
+  if (!date) return '-'
+  const d = new Date(date)
+  if (isNaN(d.getTime())) return date
+  const year = d.getFullYear()
+  const month = String(d.getMonth() + 1).padStart(2, '0')
+  const day = String(d.getDate()).padStart(2, '0')
+  const hours = String(d.getHours()).padStart(2, '0')
+  const minutes = String(d.getMinutes()).padStart(2, '0')
+  const seconds = String(d.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}

+ 51 - 97
src/views/dataDeduplication/components/change.vue

@@ -1,25 +1,32 @@
 <template>
-  <div>
-    <m-card title="元数据新旧差异" class="pb-3">
-      <m-table
-        class="candidate-table"
-        :headers="headers"
-        :items="items"
-        :loading="false"
-        :is-tools="false"
-        :disable-sort="true"
-        :show-select="false"
-        :can-delete="false"
-        :show-page="false"
-        :items-per-page="-1"
-        :no-radius="true"
-      >
-      </m-table>
-    </m-card>
-
-    <m-card title="影响关系图谱" bodyStyle="flex: 1" style="height: 800px;" class="d-flex flex-column mt-3">
-      <Graph :graphData="info.impact_graph"></Graph>
-    </m-card>
+  <div style="height: calc(100% - 48px);">
+    <v-tabs v-model="activeIndex" background-color="#f7f8fa">
+      <v-tab>元数据新旧差异</v-tab>
+      <v-tab>影响关系图谱</v-tab>
+    </v-tabs>
+    <v-tabs-items v-model="activeIndex" style="flex: 1">
+      <v-tab-item>
+        <common-component :info="info" class="mt-3" @notesChange="handleNotesChange" />
+        <m-table
+          :headers="headers"
+          :items="items"
+          :loading="false"
+          :is-tools="false"
+          :disable-sort="true"
+          :show-select="false"
+          :can-delete="false"
+          :show-page="false"
+          :items-per-page="-1"
+          :no-radius="true"
+        >
+        </m-table>
+      </v-tab-item>
+      <v-tab-item>
+        <m-card style="height: calc(100% - 12px);" no-title class="d-flex flex-column mt-3" bodyStyle="flex: 1">
+          <Graph :graphData="info.impact_graph"></Graph>
+        </m-card>
+      </v-tab-item>
+    </v-tabs-items>
   </div>
 </template>
 
@@ -27,13 +34,15 @@
 import MCard from '@/components/MCard'
 import MTable from '@/components/List/table.vue'
 import Graph from './graph.vue'
+import CommonComponent from './common.vue'
 
 export default {
   name: 'changeComponent',
   components: {
     MCard,
     MTable,
-    Graph
+    Graph,
+    CommonComponent
   },
   props: {
     info: {
@@ -43,16 +52,10 @@ export default {
   },
   data () {
     return {
-      selectedCandidateMetaId: null
+      activeIndex: 0
     }
   },
   computed: {
-    newMetaSnapshot () {
-      if (!this.info || !this.info.new_meta) {
-        return {}
-      }
-      return this.info.new_meta
-    },
     headers () {
       return [
         { text: '', align: 'start', value: 'title' },
@@ -64,97 +67,48 @@ export default {
       if (!this.info || !this.info.new_meta || !this.info.old_meta) {
         return []
       }
+      const newMeta = this.info.new_meta || {}
+      const oldMeta = this.info.old_meta.snapshot || {}
+
+      const newTagIds = newMeta.tag_ids && newMeta.tag_ids.length ? newMeta.tag_ids.map(tag => tag.name_zh).join(', ') : '-'
+      const oldTagIds = oldMeta.tag_ids && oldMeta.tag_ids.length ? oldMeta.tag_ids.map(tag => tag.name_zh).join(', ') : '-'
       return [
         {
           title: '中文名',
-          new: this.info.new_meta.name_zh || '-',
-          old: this.info.old_meta.snapshot.name_zh || '-'
+          new: newMeta?.name_zh || '-',
+          old: oldMeta?.name_zh || '-'
         },
         {
           title: '英文名',
-          new: this.info.new_meta.name_en || '-',
-          old: this.info.old_meta.snapshot.name_en || '-'
+          new: newMeta?.name_en || '-',
+          old: oldMeta?.name_en || '-'
         },
         {
           title: '数据类型',
-          new: this.info.new_meta.data_type || '-',
-          old: this.info.old_meta.snapshot.data_type || '-'
+          new: newMeta?.data_type || '-',
+          old: oldMeta?.data_type || '-'
         },
         {
           title: '数据标签',
-          new: this.info.new_meta.tag_ids?.join(', ') || '-',
-          old: this.info.old_meta.snapshot.tag_ids?.join(', ') || '-'
+          new: newTagIds,
+          old: oldTagIds
         }
       ]
     }
   },
   methods: {
-    handleSelect (candidateMetaId) {
-      this.selectedCandidateMetaId = candidateMetaId
-      this.$emit('selectCandidateMeta', candidateMetaId)
+    handleNotesChange (notes) {
+      this.$emit('notesChange', notes)
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-.snapshot-card {
-  background: #f5f3f3;
-  border-radius: 6px;
-  padding: 16px 20px;
-  box-shadow: 0 0 0 1px #eee;
-}
-
-.snapshot-title {
-  font-weight: 700;
-  margin-bottom: 16px;
-  // font-size: 14px;
-  color: #333;
+::v-deep .v-window {
+  height: 100%;
 }
-
-.snapshot-wrapper {
-  display: flex;
-  align-items: flex-start;
-}
-
-.snapshot-labels,
-.snapshot-values {
-  list-style: none;
-  padding: 0;
-  margin: 0;
-  font-size: 15px;
-  line-height: 1.6;
-}
-
-.snapshot-labels {
-  // flex: 0 0 90px;
-  text-align: right;
-  margin-right: 12px;
-  color: #403b3b;
-  // font-weight: 700;
-}
-
-.snapshot-values {
-  flex: 1;
-  word-break: break-all;
-}
-
-.snapshot-labels li,
-.snapshot-values li {
-  margin: 6px 0;
-}
-
-.candidate-table thead th {
-  background-color: #f5f5f5;
-  font-weight: 600;
-  font-size: 13px;
-}
-
-.candidate-table tbody tr:hover {
-  background-color: #f9f9f9;
-}
-
-.candidate-table td {
-  font-size: 13px;
+::v-deep .v-window__container, .v-window-item {
+  height: 100%;
 }
 </style>

+ 108 - 0
src/views/dataDeduplication/components/common.vue

@@ -0,0 +1,108 @@
+<template>
+  <div>
+    <v-row>
+      <v-col cols="12">
+        <m-card title="基本信息" style="min-height: 260px;">
+          <div class="snapshot-wrapper mt-3">
+            <ul class="snapshot-labels">
+              <li>状态:</li>
+              <li>记录类型:</li>
+              <li>创建时间:</li>
+              <li>更新时间:</li>
+              <li>备注:</li>
+            </ul>
+            <ul class="snapshot-values">
+              <li>
+                <v-chip small :color="info.status === 'pending' ? 'warning' : info.status === 'resolved' ? 'success' : ''">
+                  {{ info.status === 'pending' ? '待处理' : info.status === 'resolved' ? '已处理' : '已忽略' }}
+                </v-chip>
+              </li>
+              <li>{{ info.record_type === 'redundancy' ? '疑似冗余' : '疑似变动' || '-' }}</li>
+              <li>{{ formatDate(info.created_at) || '-' }}</li>
+              <li>{{ formatDate(info.updated_at) || '-' }}</li>
+              <li>
+                <v-text-field
+                  v-if="info?.status === 'pending'"
+                  v-model="notes"
+                  label=""
+                  outlined
+                  clearable
+                  dense
+                  hide-details
+                  placeholder="请输入备注信息"
+                  @input="handleNotesChange"
+                />
+                <span v-else>{{ info.notes || '-' }}</span>
+              </li>
+            </ul>
+          </div>
+        </m-card>
+      </v-col>
+    </v-row>
+  </div>
+</template>
+
+<script>
+import MCard from '@/components/MCard'
+import { formatDate } from '@/utils/date'
+
+export default {
+  name: 'commonComponent',
+  components: {
+    MCard
+  },
+  props: {
+    info: {
+      type: Object,
+      default: () => ({ notes: null })
+    }
+  },
+  data () {
+    return {
+      notes: this.info.notes || null
+    }
+  },
+  methods: {
+    formatDate (date) {
+      return formatDate(date)
+    },
+    handleNotesChange () {
+      this.$emit('notesChange', this.notes)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.snapshot-wrapper {
+  display: flex;
+  align-items: flex-start;
+}
+
+.snapshot-labels,
+.snapshot-values {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+  font-size: 15px;
+  line-height: 1.6;
+}
+
+.snapshot-labels {
+  // flex: 0 0 90px;
+  text-align: right;
+  margin-right: 12px;
+  color: #403b3b;
+  // font-weight: 700;
+}
+
+.snapshot-values {
+  flex: 1;
+  word-break: break-all;
+}
+
+.snapshot-labels li,
+.snapshot-values li {
+  margin: 6px 0;
+}
+</style>

+ 11 - 4
src/views/dataDeduplication/components/redundancy.vue

@@ -1,5 +1,6 @@
 <template>
-  <!-- <m-card class="mb-3 pa-3" no-title> -->
+  <div>
+    <common-component :info="info" @notesChange="handleNotesChange" />
     <v-row>
       <!-- 左侧:新解析元数据快照 new_meta -->
       <v-col cols="5">
@@ -17,7 +18,7 @@
               <li>{{ newMetaSnapshot.data_type || '-' }}</li>
               <li>
                 <span v-if="Array.isArray(newMetaSnapshot.tag_ids) && newMetaSnapshot.tag_ids.length">
-                  {{ newMetaSnapshot.tag_ids.join(', ') }}
+                  {{ newMetaSnapshot.tag_ids.map(tag => tag.name_zh).join(', ') }}
                 </span>
                 <span v-else>-</span>
               </li>
@@ -59,6 +60,7 @@
             </template>
             <template #actions="{ item }">
               <v-btn
+                v-if="info?.status === 'pending'"
                 class="mr-2"
                 :color="selectedCandidateMetaId === item.candidate_meta_id ? '' : 'success'"
                 text
@@ -72,18 +74,20 @@
         </m-card>
       </v-col>
     </v-row>
-  <!-- </m-card> -->
+  </div>
 </template>
 
 <script>
 import MCard from '@/components/MCard'
 import MTable from '@/components/List/table.vue'
+import CommonComponent from './common.vue'
 
 export default {
   name: 'redundancyComponent',
   components: {
     MCard,
-    MTable
+    MTable,
+    CommonComponent
   },
   props: {
     info: {
@@ -123,6 +127,9 @@ export default {
     handleSelect (candidateMetaId) {
       this.selectedCandidateMetaId = candidateMetaId
       this.$emit('selectCandidateMeta', candidateMetaId)
+    },
+    handleNotesChange (notes) {
+      this.$emit('notesChange', notes)
     }
   }
 }

+ 8 - 11
src/views/dataDeduplication/index.vue

@@ -37,11 +37,13 @@
         v-if="info?.record_type === 'redundancy'"
         :info="info"
         @selectCandidateMeta="val => candidateMetaId = val"
+        @notesChange="val => notes = val"
       />
       <!-- 疑似变动 -->
       <change-component
         v-if="info?.record_type === 'change'"
         :info="info"
+        @notesChange="val => notes = val"
       />
       <template v-if="info?.status === 'pending'" #footer>
         <v-divider></v-divider>
@@ -79,6 +81,7 @@ import RedundancyComponent from './components/redundancy.vue'
 import ChangeComponent from './components/change.vue'
 import { dataReviewList, dataReviewDetail, dataReviewResolve } from '@/api/dataDeduplication'
 import { api } from '@/api/dataGovernance'
+import { formatDate } from '@/utils/date'
 export default {
   name: 'dataProcess',
   components: {
@@ -147,6 +150,7 @@ export default {
       candidateMetaId: null,
       total: 0,
       info: {},
+      notes: '',
       createNew: {
         show: false,
         name_zh: null,
@@ -202,6 +206,7 @@ export default {
       try {
         const { data } = await dataReviewDetail(item.id)
         this.info = data || {}
+        this.notes = data?.notes || ''
         this.show = true
       } catch (error) {
         this.$snackbar.error(error)
@@ -238,7 +243,7 @@ export default {
         id: item.id,
         action: action,
         resolved_by: this.$store.getters.userInfo.username,
-        notes: ''
+        notes: this.notes || ''
       }
       // 作为新元数据创建
       if (action === 'create_new') {
@@ -267,22 +272,14 @@ export default {
         await dataReviewResolve(params)
         this.$snackbar.success('操作成功')
         this.show = false
+        this.notes = ''
         this.init()
       } catch (error) {
         this.$snackbar.error(error)
       }
     },
     formatDate (date) {
-      if (!date) return '-'
-      const d = new Date(date)
-      if (isNaN(d.getTime())) return date
-      const year = d.getFullYear()
-      const month = String(d.getMonth() + 1).padStart(2, '0')
-      const day = String(d.getDate()).padStart(2, '0')
-      const hours = String(d.getHours()).padStart(2, '0')
-      const minutes = String(d.getMinutes()).padStart(2, '0')
-      const seconds = String(d.getSeconds()).padStart(2, '0')
-      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+      return formatDate(date)
     }
   }
 }