20260808172946
This commit is contained in:
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>
|
||||
Reference in new issue
Block a user