20260808172946

This commit is contained in:
oneao committed 2026-08-08 17:29:51 +08:00
1 parent 4fbfea82f2
commit d72ca4193d
293 files changed
+37523 -73467

No files matched your search

@@ -0,0 +1,66 @@
<template>
<!-- UPDATE -->
<el-table
v-if="data.action === 'UPDATE'"
:data="data.diffs"
size="small"
border
show-header
class="diff-table"
>
<el-table-column prop="field" label="字段" width="200" />
<el-table-column label="旧值">
<template #default="{ row }">
<span class="old-value">
{{ row.old == null ? "" : row.old }}
</span>
</template>
</el-table-column>
<el-table-column label="新值">
<template #default="{ row }">
<span class="new-value">
{{ row.new == null ? "" : row.new }}
</span>
</template>
</el-table-column>
</el-table>
<!-- INSERT / DELETE -->
<el-descriptions v-else :column="2" border size="small" class="snapshot">
<el-descriptions-item
v-for="(val, key) in data.values"
:key="key"
:label="key"
>
{{ val == null ? "" : val }}
</el-descriptions-item>
</el-descriptions>
</template>
<script setup>
const props = defineProps({
data: {
type: Object,
required: true,
},
});
</script>
<style scoped>
.diff-table {
margin-top: 6px;
}
.old-value {
color: #f56c6c;
text-decoration: line-through;
}
.new-value {
color: #67c23a;
}
.snapshot {
margin-top: 6px;
}
</style>
@@ -0,0 +1,256 @@
<template>
<el-dialog
v-model="visible"
title="变更详情"
width="920px"
@close="onClose"
>
<div class="timeline" v-loading="loading">
<div
v-for="(item, index) in logList"
:key="index"
class="timeline-item"
>
<!-- 左侧圆点 + 竖线 -->
<div class="timeline-left">
<div class="dot" :class="dotClass(item.action)"></div>
<div
class="line"
v-if="index < logList.length - 1"
></div>
</div>
<!-- 右侧内容 -->
<div class="timeline-right">
<!-- 摘要行 -->
<div class="summary">
<span class="time">{{ formatTime(item.operateTime) }}</span>
<el-tag :type="tagType(item.action)" size="small">
{{ item.action }}
</el-tag>
<span class="operator">{{ item.operator }}</span>
<span class="table">{{ item.table }}</span>
<span class="row-id">ID: {{ item.rowId }}</span>
</div>
<!-- 展开/折叠 diff -->
<div class="diff-wrapper">
<AuditLogDetail :data="item" />
</div>
</div>
</div>
</div>
<el-empty
v-if="!loading && logList.length === 0"
description="暂无变更记录"
/>
</el-dialog>
</template>
<script setup>
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";
import axios from "@/utils/axios";
import AuditLogDetail from "./AuditLogDetail.vue";
const props = defineProps({
modelValue: Boolean,
bizNo: String, // ✅ 业务单据号(loggroupid)
});
const emit = defineEmits(["update:modelValue"]);
const visible = ref(false);
const loading = ref(false);
const logList = ref([]);
watch(
() => props.modelValue,
(val) => {
visible.value = val;
if (val) {
loadLog();
}
}
);
watch(visible, (val) => {
emit("update:modelValue", val);
});
const loadLog = async () => {
if (!props.bizNo) {
ElMessage.warning("bizNo 不能为空");
return;
}
loading.value = true;
try {
const response = await axios.get("/logdetail", {
params: {
bizNo: props.bizNo,
},
});
const result = response.data;
if (!result?.data || result.data.length === 0) {
logList.value = [];
return;
}
const parsedList = [];
for (const raw of result.data) {
try {
const changeData =
typeof raw.changeData === "string"
? JSON.parse(raw.changeData)
: raw.changeData;
changeData.table = changeData.table || raw.tableName;
changeData.operator = changeData.operator || raw.operator;
changeData.operateTime =
changeData.operateTime || raw.operateTime;
parsedList.push(changeData);
} catch (e) {
console.error("changeData 解析失败", e);
}
}
logList.value = parsedList;
} catch (e) {
console.error(e);
ElMessage.error("加载变更日志失败");
} finally {
loading.value = false;
}
};
const onClose = () => {
logList.value = [];
};
const tagType = (action) => {
switch (action) {
case "INSERT":
return "success";
case "UPDATE":
return "primary";
case "DELETE":
return "danger";
default:
return "info";
}
};
const dotClass = (action) => {
switch (action) {
case "INSERT":
return "dot-green";
case "UPDATE":
return "dot-blue";
case "DELETE":
return "dot-red";
default:
return "dot-gray";
}
};
const formatTime = (time) => {
if (!time) return "";
if (typeof time === "string" && time.includes("T")) {
return time.replace("T", " ").split(".")[0];
}
return time;
};
</script>
<style scoped>
.timeline {
padding: 8px 0;
}
.timeline-item {
display: flex;
align-items: flex-start;
}
.timeline-left {
width: 24px;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 12px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
}
.dot-green {
background-color: #67c23a;
}
.dot-blue {
background-color: #409eff;
}
.dot-red {
background-color: #f56c6c;
}
.dot-gray {
background-color: #909399;
}
.line {
width: 2px;
flex: 1;
background-color: #e4e7ed;
margin-top: 4px;
min-height: 24px;
}
.timeline-right {
flex: 1;
padding-bottom: 16px;
}
.summary {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: #606266;
}
.summary .time {
font-weight: bold;
color: #303133;
}
.summary .operator {
color: #409eff;
}
.summary .table {
font-weight: bold;
}
.summary .row-id {
color: #909399;
}
.diff-wrapper {
background-color: #fafafa;
border-radius: 4px;
padding: 8px 12px;
margin-top: 6px;
}
</style>