|
|
@@ -210,26 +210,39 @@
|
|
|
|
|
|
<!-- Tab5:审批记录 -->
|
|
|
<el-tab-pane label="审批记录" name="history">
|
|
|
- <el-timeline>
|
|
|
+ <el-timeline class="approval-timeline">
|
|
|
<el-timeline-item
|
|
|
v-for="record in selectedProject.approvalRecords || []"
|
|
|
:key="record.id"
|
|
|
:timestamp="formatDateTime(record.approveTime)"
|
|
|
+ placement="top"
|
|
|
:type="getRecordTagType(record.result)"
|
|
|
+ :hollow="false"
|
|
|
>
|
|
|
- <strong>{{ record.nodeName }}</strong>
|
|
|
- <span style="margin-left: 8px">{{ record.approverName }}</span>
|
|
|
- <el-tag
|
|
|
- size="small"
|
|
|
- :type="getRecordTagType(record.result)"
|
|
|
- style="margin-left: 8px"
|
|
|
- >
|
|
|
- {{ getResultLabel(record.result) }}
|
|
|
- </el-tag>
|
|
|
- <p v-if="record.opinion" style="margin: 4px 0 0; color: #909399">
|
|
|
- {{ record.opinion }}
|
|
|
- </p>
|
|
|
+ <!-- 自定义节点图标 -->
|
|
|
+ <template #dot>
|
|
|
+ <div class="node-dot" :class="getNodeClass(record.result)">
|
|
|
+ <Icon :icon="getNodeIcon(record)" class="node-icon" />
|
|
|
+ <span class="node-badge" :class="getBadgeClass(record.result)">
|
|
|
+ <Icon :icon="getBadgeIcon(record.result)" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div class="record-content">
|
|
|
+ <strong>{{ record.nodeName }}</strong>
|
|
|
+ <span class="approver">{{ record.approverName }}</span>
|
|
|
+ <el-tag
|
|
|
+ size="small"
|
|
|
+ :type="getRecordTagType(record.result)"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ >
|
|
|
+ {{ getResultLabel(record.result) }}
|
|
|
+ </el-tag>
|
|
|
+ <p v-if="record.opinion" class="opinion">{{ record.opinion }}</p>
|
|
|
+ </div>
|
|
|
</el-timeline-item>
|
|
|
+
|
|
|
<el-empty
|
|
|
v-if="!selectedProject.approvalRecords?.length"
|
|
|
description="暂无审批记录"
|
|
|
@@ -518,6 +531,39 @@ const getStageNodeLabel = (p: any) => {
|
|
|
return ''
|
|
|
}
|
|
|
|
|
|
+// 节点主图标:提交用"人",审批用"审批/印章"图标
|
|
|
+const getNodeIcon = (record: any) => {
|
|
|
+ // result === 4 是"提交",其它是审批
|
|
|
+ return record.result === 4 ? 'ep:user' : 'ep:stamp'
|
|
|
+}
|
|
|
+
|
|
|
+// 节点主色:都统一蓝色,也可以按状态给不同色
|
|
|
+const getNodeClass = (result: number) => {
|
|
|
+ // 目前截图里节点都是蓝色,就统一返回 blue
|
|
|
+ return 'node-dot--blue'
|
|
|
+}
|
|
|
+
|
|
|
+// 右下角状态角标
|
|
|
+const getBadgeClass = (result: number) => {
|
|
|
+ const map: Record<number, string> = {
|
|
|
+ 1: 'badge--success', // 通过 绿勾
|
|
|
+ 2: 'badge--danger', // 驳回 红叉
|
|
|
+ 3: 'badge--info', // 撤回 灰
|
|
|
+ 4: 'badge--primary' // 提交 蓝
|
|
|
+ }
|
|
|
+ return map[result] || 'badge--info'
|
|
|
+}
|
|
|
+
|
|
|
+const getBadgeIcon = (result: number) => {
|
|
|
+ const map: Record<number, string> = {
|
|
|
+ 1: 'ep:check',
|
|
|
+ 2: 'ep:close',
|
|
|
+ 3: 'ep:refresh-left',
|
|
|
+ 4: 'ep:upload'
|
|
|
+ }
|
|
|
+ return map[result] || 'ep:info-filled'
|
|
|
+}
|
|
|
+
|
|
|
const formatDateTime = (dt: any) => {
|
|
|
if (!dt) return ''
|
|
|
// 1. 如果是数字(时间戳),转成 Date
|
|
|
@@ -821,4 +867,96 @@ onMounted(async () => {
|
|
|
grid-template-columns: 1fr;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.approval-timeline {
|
|
|
+ padding: 8px 0;
|
|
|
+
|
|
|
+ :deep(.el-timeline-item__node) {
|
|
|
+ background: transparent;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-timeline-item__tail) {
|
|
|
+ border-left: 2px solid var(--el-border-color-lighter);
|
|
|
+ left: 18px; /* 对齐圆点中心 */
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-timeline-item__wrapper) {
|
|
|
+ padding-left: 56px; /* 给左侧大圆点让位 */
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-timeline-item__timestamp) {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 节点大圆 */
|
|
|
+.node-dot {
|
|
|
+ position: relative;
|
|
|
+ width: 38px;
|
|
|
+ height: 38px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: grid;
|
|
|
+ place-items: center;
|
|
|
+ color: #fff;
|
|
|
+ box-shadow: 0 2px 6px rgba(64, 158, 255, 0.35);
|
|
|
+
|
|
|
+ &--blue {
|
|
|
+ background: linear-gradient(135deg, #5b8ff9, #3f6fe0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .node-icon {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 右下角状态角标 */
|
|
|
+ .node-badge {
|
|
|
+ position: absolute;
|
|
|
+ right: -4px;
|
|
|
+ bottom: -4px;
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: grid;
|
|
|
+ place-items: center;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 11px;
|
|
|
+ border: 2px solid #fff;
|
|
|
+ box-sizing: content-box;
|
|
|
+
|
|
|
+ &.badge--success {
|
|
|
+ background: #52c41a;
|
|
|
+ }
|
|
|
+ &.badge--danger {
|
|
|
+ background: #f56c6c;
|
|
|
+ }
|
|
|
+ &.badge--info {
|
|
|
+ background: #909399;
|
|
|
+ }
|
|
|
+ &.badge--primary {
|
|
|
+ background: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧内容 */
|
|
|
+.record-content {
|
|
|
+ strong {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ .approver {
|
|
|
+ margin-left: 8px;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .opinion {
|
|
|
+ margin: 6px 0 0;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|