Files
qiji/frontend/src/components/EditLogPanel.vue
T
v6ole bf67e0575f feat: 信息架构重组 + 变更追踪 + 图片预览增强
- 信息架构重组: 周报精简为拜访+纪要两个Tab,工作计划/商机/要客独立为侧边栏「工作」分组下的独立页面
- 侧边栏分组: 汇总/工作/管理三层分组,仪表盘四卡可点击跳转
- 变更追踪(edit_log): 5张表新增JSONB edit_log列,POST创建/PUT diff自动记录,编辑弹窗变更时间轴,表格🕐编辑标记
- 图片预览增强: ImagePreview统一组件,支持适应页面/缩放/拖拽平移/滚轮缩放/键盘快捷键
- 修复客户导入500错误(errors变量未初始化)
- 移除工作计划/商机/要客页面冗余编辑按钮

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 08:30:12 +08:00

197 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
editLog: Array<{ editor: string; time: string; changes: Record<string, [string, string]>; reason?: string }>
}>()
const fieldLabels: Record<string, string> = {
customer_id: '客户单位',
customer_name: '客户名称',
visit_date: '拜访日期',
note_date: '日期',
visit_method: '拜访方式',
time_range: '时间范围',
visitor_name: '拜访人',
visitor_phone: '电话',
communication_content: '沟通内容',
customer_demand: '客户需求',
category: '分类',
content: '工作内容',
plan_content: '计划内容',
plan_date: '计划时间',
status: '状态',
product_type: '产品类型',
amount: '金额',
follow_up_detail: '跟进内容',
expected_revenue_date: '预计列收',
urgency_level: '紧急度',
description: '描述',
progress_status: '进展',
planned_date: '计划日期',
planned_visitor: '拜访人',
visit_target: '拜访对象',
}
const hasHistory = computed(() => (props.editLog || []).length > 0)
function formatTime(t: string) {
try {
const d = new Date(t)
return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
} catch { return t }
}
function labelFor(field: string) {
return fieldLabels[field] || field
}
</script>
<template>
<el-collapse v-if="hasHistory" style="margin-top:16px">
<el-collapse-item title="变更记录" name="1">
<div class="log-timeline">
<div v-for="(entry, idx) in editLog" :key="idx" class="log-entry"
:class="{ 'log-entry--create': !entry.changes || Object.keys(entry.changes).length === 0 }">
<div class="log-dot"></div>
<div class="log-body">
<div class="log-meta">
<span class="log-editor">{{ entry.editor }}</span>
<span class="log-time">{{ formatTime(entry.time) }}</span>
<el-tag v-if="!entry.changes || Object.keys(entry.changes).length === 0" size="small" type="info" effect="plain">创建</el-tag>
</div>
<div v-if="entry.changes && Object.keys(entry.changes).length > 0" class="log-changes">
<div v-for="(vals, field) in entry.changes" :key="field" class="log-change">
<span class="change-label">{{ labelFor(field) }}</span>
<span class="change-old">{{ vals[0] || '(空)' }}</span>
<span class="change-arrow"></span>
<span class="change-new">{{ vals[1] || '(空)' }}</span>
</div>
</div>
<div v-if="entry.reason" class="log-reason">
<span class="reason-label">原因</span>{{ entry.reason }}
</div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</template>
<style scoped>
.log-timeline {
padding: 4px 0;
}
.log-entry {
display: flex;
gap: 10px;
padding: 10px 0;
border-left: 2px solid var(--warm-border);
margin-left: 6px;
padding-left: 16px;
}
.log-entry:first-child {
border-left-color: var(--gold);
}
.log-entry--create {
opacity: 0.6;
}
.log-dot {
width: 8px; height: 8px;
background: var(--warm-border);
margin-left: -21px;
margin-top: 5px;
flex-shrink: 0;
}
.log-entry:first-child .log-dot {
background: var(--gold);
}
.log-body {
flex: 1;
min-width: 0;
}
.log-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.log-editor {
font-family: 'ZCOOL XiaoWei', STSong, serif;
font-size: 13px;
color: var(--ink);
font-weight: 600;
}
.log-time {
font-family: 'JetBrains Mono', 'SF Mono', monospace;
font-size: 11px;
color: var(--c-text-muted);
}
.log-changes {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 4px;
}
.log-change {
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.change-label {
font-weight: 600;
color: var(--ink);
min-width: 56px;
}
.change-old {
color: var(--vermilion);
text-decoration: line-through;
text-decoration-color: var(--vermilion);
text-decoration-thickness: 1px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.change-arrow {
color: var(--gold);
font-weight: 600;
}
.change-new {
color: var(--sage);
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-reason {
margin-top: 4px;
font-size: 12px;
color: var(--c-text-secondary);
font-style: italic;
}
.reason-label {
color: var(--warm-gray);
font-weight: 500;
}
</style>