Explorar o código

✨feat: Refactor item edit history display for better readability

Shinohara Haruna hai 6 meses
pai
achega
e1926bb791

+ 7 - 21
smsb-plus-ui/src/views/smsb/item/index.vue

@@ -106,26 +106,8 @@
         </el-table-column>
 
         <!-- 编辑历史弹窗 -->
-        <el-dialog title="编辑历史" v-model="editHistoryDialog.visible" width="600px" append-to-body>
-          <el-table :data="editHistoryDialog.list" size="small" style="width: 100%">
-            <el-table-column prop="operName" label="操作人" width="120">
-              <template #default="scope">
-                <span class="edit-history-user">{{ scope.row.operName }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作参数" min-width="260">
-              <template #default="scope">
-                <div class="edit-history-param"
-                  v-html="renderOperParam(scope.row, scope.$index, editHistoryDialog.list, editHistoryDialog.type)">
-                </div>
-              </template>
-            </el-table-column>
-            <el-table-column prop="operTime" label="操作时间" width="180">
-              <template #default="scope">
-                <span class="edit-history-time">{{ scope.row.operTime }}</span>
-              </template>
-            </el-table-column>
-          </el-table>
+        <el-dialog title="编辑历史" v-model="editHistoryDialog.visible" width="800px" append-to-body>
+          <div class="edit-history-container" v-html="editHistoryDialog.tableHtml"></div>
         </el-dialog>
       </el-table>
 
@@ -327,7 +309,8 @@ import type { EditHistoryVo, ItemVO, ItemForm } from '@/api/smsb/source/item_typ
 const editHistoryDialog = reactive({
   visible: false,
   list: [] as EditHistoryVo[],
-  type: 1 as 1 | 2
+  type: 1 as 1 | 2,
+  tableHtml: ref('')
 });
 
 /**
@@ -361,6 +344,8 @@ function onShowEditHistory(row: ItemVO) {
       });
     }
     editHistoryDialog.list = list;
+    // 使用新的渲染函数生成表格HTML
+    editHistoryDialog.tableHtml = renderOperHistoryTable(list, type);
     console.log('编辑历史数据处理后:', list);
   });
 }
@@ -451,6 +436,7 @@ function renderOperParam(row: EditHistoryVo, index: number, list: EditHistoryVo[
 import { listItem, getItem, delItem, addItem, updateItem, itemStatistics } from '@/api/smsb/source/item';
 import { MinioDataQuery, MinioDataVO } from '@/api/smsb/source/minioData_type';
 import { listMinioData } from '@/api/smsb/source/minioData';
+import { renderOperHistoryTable } from './renderOperHistoryTable';
 import { nextTick } from 'vue';
 import type { ElTable } from 'element-plus';
 

+ 271 - 0
smsb-plus-ui/src/views/smsb/item/renderOperHistoryTable.ts

@@ -0,0 +1,271 @@
+interface EditHistoryVo {
+  operName: string;
+  operTime: string;
+  operParam: string;
+  [key: string]: any;
+}
+
+interface FileChange {
+  screen: string;
+  file: string;
+  action: string;
+}
+
+interface ParsedHistoryItem {
+  operName: string;
+  operTime: string;
+  operationType: string;
+  fileChanges: FileChange[];
+}
+
+interface GroupedChange {
+  action: string;
+  screen: string;
+  files: string[];
+  rowCount?: number;
+}
+
+/**
+ * 渲染操作历史表格,以更美观的方式展示
+ * @param list 历史记录列表
+ * @param type 类型 1=轮播组, 2=分屏组
+ */
+export function renderOperHistoryTable(list: EditHistoryVo[], type: 1 | 2) {
+  if (!list || list.length === 0) return '<p>暂无操作历史</p>';
+  
+  // 解析操作历史数据,转换为更易读的格式
+  const parsedData = list.map((row, index) => {
+    let curr, prev;
+    let operationType = '';
+    let fileChanges = [];
+    
+    try {
+      curr = JSON.parse(row.operParam || '{}');
+    } catch {
+      curr = {};
+    }
+    
+    try {
+      prev = index > 0 ? JSON.parse(list[index - 1].operParam || '{}') : {};
+    } catch {
+      prev = {};
+    }
+    
+    // 轮播组处理
+    if (type === 1) {
+      if (index === 0) {
+        operationType = '新增';
+        const files = curr.selectedFiles || [];
+        fileChanges = files.map(f => ({ screen: '第一屏', file: f.name, action: '新增' }));
+      } else {
+        const currFiles = curr.selectedFiles || [];
+        const prevFiles = prev.selectedFiles || [];
+        
+        // 获取文件名集合
+        const currFileNames = new Set(currFiles.map(f => f.name));
+        const prevFileNames = new Set(prevFiles.map(f => f.name));
+        
+        // 新增的文件
+        const addedFiles = currFiles.filter(f => !prevFileNames.has(f.name));
+        // 减少的文件
+        const removedFiles = prevFiles.filter(f => !currFileNames.has(f.name));
+        
+        if (addedFiles.length > 0 && removedFiles.length === 0) {
+          operationType = '新增';
+        } else if (addedFiles.length === 0 && removedFiles.length > 0) {
+          operationType = '减少';
+        } else if (addedFiles.length > 0 && removedFiles.length > 0) {
+          operationType = '新增/减少';
+        }
+        
+        // 添加新增文件
+        addedFiles.forEach(f => {
+          fileChanges.push({ screen: '第一屏', file: f.name, action: '新增' });
+        });
+        
+        // 添加减少文件
+        removedFiles.forEach(f => {
+          fileChanges.push({ screen: '第一屏', file: f.name, action: '减少' });
+        });
+      }
+    }
+    // 分屏组处理
+    else {
+      const screenFields = ['selectedFiles1', 'selectedFiles2', 'selectedFiles3', 'selectedFiles4'];
+      const screenNames = ['第一屏', '第二屏', '第三屏', '第四屏'];
+      
+      if (index === 0) {
+        operationType = '新增';
+        // 处理所有屏幕的初始文件
+        screenFields.forEach((field, idx) => {
+          const files = curr[field] || [];
+          if (files.length > 0) {
+            files.forEach(f => {
+              fileChanges.push({ screen: screenNames[idx], file: f.name, action: '新增' });
+            });
+          }
+        });
+      } else {
+        let hasAdded = false;
+        let hasRemoved = false;
+        
+        // 处理每个屏幕的变化
+        screenFields.forEach((field, idx) => {
+          const currFiles = curr[field] || [];
+          const prevFiles = prev[field] || [];
+          
+          // 获取文件名集合
+          const currFileNames = new Set(currFiles.map(f => f.name));
+          const prevFileNames = new Set(prevFiles.map(f => f.name));
+          
+          // 新增的文件
+          const addedFiles = currFiles.filter(f => !prevFileNames.has(f.name));
+          // 减少的文件
+          const removedFiles = prevFiles.filter(f => !currFileNames.has(f.name));
+          
+          if (addedFiles.length > 0) hasAdded = true;
+          if (removedFiles.length > 0) hasRemoved = true;
+          
+          // 添加新增文件
+          addedFiles.forEach(f => {
+            fileChanges.push({ screen: screenNames[idx], file: f.name, action: '新增' });
+          });
+          
+          // 添加减少文件
+          removedFiles.forEach(f => {
+            fileChanges.push({ screen: screenNames[idx], file: f.name, action: '减少' });
+          });
+        });
+        
+        if (hasAdded && !hasRemoved) {
+          operationType = '新增';
+        } else if (!hasAdded && hasRemoved) {
+          operationType = '减少';
+        } else if (hasAdded && hasRemoved) {
+          operationType = '新增/减少';
+        }
+      }
+    }
+    
+    return {
+      operName: row.operName,
+      operTime: row.operTime,
+      operationType,
+      fileChanges
+    };
+  });
+  
+  // 生成表格 HTML
+  let tableHtml = `
+  <figure class='table-figure'>
+    <table style="width: 100%; border-collapse: collapse;">
+      <thead>
+        <tr style="background-color: #f5f7fa;">
+          <th style='text-align:left; padding: 12px; border-bottom: 2px solid #dcdfe6;'>操作人</th>
+          <th style='text-align:center; padding: 12px; border-bottom: 2px solid #dcdfe6;'>操作</th>
+          <th style='text-align:center; padding: 12px; border-bottom: 2px solid #dcdfe6;'>屏幕</th>
+          <th style='text-align:left; padding: 12px; border-bottom: 2px solid #dcdfe6;'>文件</th>
+          <th style='text-align:left; padding: 12px; border-bottom: 2px solid #dcdfe6;'>操作时间</th>
+        </tr>
+      </thead>
+      <tbody>
+  `;
+  
+  // 生成表格内容
+  parsedData.forEach((item, itemIndex) => {
+    // 按屏幕和操作类型分组
+    const groupedChanges: Record<string, GroupedChange> = {};
+    
+    item.fileChanges.forEach(change => {
+      const key = `${change.action}_${change.screen}`;
+      if (!groupedChanges[key]) {
+        groupedChanges[key] = {
+          action: change.action,
+          screen: change.screen,
+          files: []
+        };
+      }
+      groupedChanges[key].files.push(change.file);
+    });
+    
+    const groups = Object.values(groupedChanges) as GroupedChange[];
+    
+    // 如果没有变化,跳过
+    if (groups.length === 0) return;
+    
+    // 计算每组的行数和总行数
+    let totalRows = 0;
+    groups.forEach(group => {
+      group.rowCount = group.files.length;
+      totalRows += group.rowCount;
+    });
+    
+    // 为每个条目添加一个更明显的分隔
+    const isLastItem = itemIndex === parsedData.length - 1;
+    const rowStyle = !isLastItem ? ' style="border-bottom: 2px solid #dcdfe6;"' : '';
+    
+    // 生成表格行
+    // 先按操作类型排序,先新增后减少
+    groups.sort((a, b) => {
+      if (a.action === '新增' && b.action === '减少') return -1;
+      if (a.action === '减少' && b.action === '新增') return 1;
+      return 0;
+    });
+    
+    groups.forEach((group, groupIndex) => {
+      // 判断是否需要在操作类型之间添加分隔线
+      const prevGroup = groupIndex > 0 ? groups[groupIndex - 1] : null;
+      const needsActionDivider = prevGroup && prevGroup.action !== group.action;
+      
+      group.files.forEach((file, fileIndex) => {
+        const isFirstRow = groupIndex === 0 && fileIndex === 0;
+        const isFirstInGroup = fileIndex === 0;
+        const isLastRowInItem = groupIndex === groups.length - 1 && fileIndex === group.files.length - 1;
+        
+        // 如果是第一行并且需要在操作类型之间添加分隔线
+        const rowBorderTop = isFirstInGroup && needsActionDivider ? ' border-top: 1px dashed #dcdfe6;' : '';
+        const rowBorderBottom = isLastRowInItem ? ' border-bottom: 2px solid #dcdfe6;' : '';
+        const rowStyle = (rowBorderTop || rowBorderBottom) ? ` style="${rowBorderTop}${rowBorderBottom}"` : '';
+        
+        tableHtml += `
+          <tr${rowStyle}>
+        `;
+        
+        // 操作人单元格 - 只在第一行显示,其余行合并
+        if (isFirstRow) {
+          tableHtml += `<td style='text-align:left; padding: 10px; vertical-align: middle; font-size: 16px; font-weight: 500;' rowspan="${totalRows}">${item.operName}</td>`;
+        }
+        
+        // 操作类型单元格 - 每组的第一行显示,其余行合并
+        if (isFirstInGroup) {
+          const actionColor = group.action === '新增' ? '#67C23A' : '#F56C6C';
+          tableHtml += `<td style='text-align:center; padding: 10px; vertical-align: middle;' rowspan="${group.rowCount}"><strong style="color:${actionColor};">${group.action}</strong></td>`;
+          tableHtml += `<td style='text-align:center; padding: 10px; vertical-align: middle;' rowspan="${group.rowCount}"><strong>${group.screen}</strong></td>`;
+        }
+        
+        // 文件名单元格 - 每行都显示
+        // 如果不是组内的最后一个文件,则添加底部虚线分隔线
+        const isLastFileInGroup = fileIndex === group.files.length - 1;
+        const fileCellStyle = !isLastFileInGroup ? 'padding: 10px; border-bottom: 1px dotted #ebeef5;' : 'padding: 10px;';
+        tableHtml += `<td style='${fileCellStyle}'>${file}</td>`;
+        
+        // 操作时间单元格 - 只在第一行显示,其余行合并
+        if (isFirstRow) {
+          tableHtml += `<td style='text-align:left; padding: 10px; vertical-align: middle;' rowspan="${totalRows}">${item.operTime}</td>`;
+        }
+        
+        tableHtml += `
+          </tr>
+        `;
+      });
+    });
+  });
+  
+  tableHtml += `
+      </tbody>
+    </table>
+  </figure>
+  `;
+  
+  return tableHtml;
+}