|
|
@@ -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;
|
|
|
+}
|