Files
fad_oa/ruoyi-ui/src/views/oa/finance/progress/components/ProgressLine.vue
2026-04-13 17:04:38 +08:00

327 lines
8.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.

<template>
<div>
<el-timeline v-if="localActivities && localActivities.length > 0">
<el-timeline-item
v-for="(activity, index) in localActivities"
:key="activity.id || index"
:timestamp="activity.planPayDate"
placement="top"
>
<el-card class="editable-card">
<!-- 编辑状态 -->
<div v-if="activity.isEditing" class="edit-mode">
<div class="form-item">
<el-input
v-model="activity.detailName"
placeholder="进度名称"
size="small"
></el-input>
</div>
<div class="time-pickers">
<el-date-picker
v-model="activity.actualStartDate"
type="date"
:default-value="
activity.actualStartDate
? activity.actualStartDate
: activity.planPayDate
"
placeholder="实际开始时间"
value-format="yyyy-MM-dd"
size="small"
></el-date-picker>
<el-date-picker
v-model="activity.actualEndDate"
type="date"
:default-value="
activity.actualEndDate
? activity.actualEndDate
: activity.planPayDate
"
placeholder="实际结束时间"
value-format="yyyy-MM-dd"
size="small"
:disabled-date="
(endDate) =>
activity.actualStartDate &&
new Date(endDate) < new Date(activity.actualStartDate)
"
></el-date-picker>
</div>
<!-- <div class="form-item">
<el-select>
</el-select>
</div> -->
<div class="actions">
<el-button type="danger" size="mini" @click="cancelEdit(index)"
>取消</el-button
>
<el-button type="success" size="mini" @click="confirmEdit(index)"
>确认</el-button
>
</div>
</div>
<!-- 展示状态 -->
<div v-else class="view-mode">
<div class="header">
<span style="gap: 8px; display: flex">
<span class="title">{{
activity.detailName || "未命名进度"
}}</span>
<el-tag
v-if="activity.detailStatus == 1"
type="success"
size="mini"
effect="dark"
>
已完成
</el-tag>
</span>
<span style="gap: 8px; display: flex">
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click.stop="toggleEdit(index)"
></el-button>
<el-button
v-if="activity.detailStatus != 1"
type="success"
icon="el-icon-check"
size="mini"
@click.stop="markAsComplete(index)"
></el-button>
</span>
</div>
<div class="time-info">
<!-- 计划开始时间 -->
<div v-if="activity.planStartDate" class="time-item">
<i class="el-icon-circle-check"></i>
计划开始:{{ formatDate(activity.planStartDate) }}
</div>
<!-- 剩余时间 -->
<div
v-if="
activity.remainTime !== undefined &&
activity.detailStatus != 1
"
class="time-item"
>
<i
class="el-icon-time"
:style="{ color: remainTimeColor(activity) }"
></i>
<span :style="{ color: remainTimeColor(activity) }">
剩余时间:{{ activity.remainTime }}天
</span>
</div>
<div v-if="activity.actualStartDate" class="time-item">
<i class="el-icon-time"></i>
{{
formatDate(
activity.actualStartDate
? activity.actualStartDate
: activity.planStartDate
)
}}
</div>
<div v-if="activity.actualEndDate" class="time-item">
<i class="el-icon-circle-check"></i>
{{
formatDate(
activity.actualEndDate
? activity.actualEndDate
: activity.planEndDate
)
}}
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无进度"></el-empty>
</div>
</template>
<script>
import { formatDate } from "@/utils";
export default {
name: "EditableTimeline",
props: {
activities: {
type: Array,
required: true,
},
},
data() {
return {
localActivities: this.activities.map((a) => ({
...a,
isEditing: false,
})),
};
},
methods: {
// 修改toggleEdit方法保存原始数据
toggleEdit(index) {
this.localActivities = this.localActivities.map((a, i) => {
if (i === index) {
// 进入编辑模式时保存快照
const newState = { ...a, isEditing: !a.isEditing };
if (!a.isEditing) {
newState._originalData = JSON.parse(JSON.stringify(a));
}
return newState;
}
return a;
});
},
// 新增取消编辑方法
cancelEdit(index) {
const original = this.localActivities[index]._originalData;
if (original) {
// 恢复原始数据
this.localActivities.splice(index, 1, {
...original,
isEditing: false,
});
this.$forceUpdate(); // 强制更新视图
this.$message.info("已取消编辑");
}
},
// 修改确认保存方法清理临时数据
async confirmEdit(index) {
try {
await this.$confirm("确定要保存修改吗", "操作确认", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
const activity = this.localActivities[index];
// 清理临时数据
if (activity._originalData) {
delete activity._originalData;
}
this.toggleEdit(index);
this.$emit("change", {
list: this.localActivities,
activity: activity,
});
} catch (error) {
// 取消操作时自动捕获错误
}
},
// 剩余时间颜色判断
remainTimeColor(activity) {
if (activity.detailStatus == 1) return "#67C23A"; // 已完成绿色
return activity.remainTime < 3 ? "#F56C6C" : "#909399"; // 小于3天红色
},
async markAsComplete(index) {
try {
await this.$confirm("确定要完成该进度吗", "操作确认", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
const activity = {
...this.localActivities[index],
detailStatus: 1,
actualEndDate: formatDate(new Date()),
};
this.$emit("change", {
list: this.localActivities.splice(index, 1, activity),
activity,
});
} catch {}
},
formatDate(dateStr) {
return dateStr;
},
},
watch: {
activities(newVal) {
this.localActivities = newVal.map((a) => ({
...a,
isEditing: false,
}));
},
},
};
</script>
<style scoped lang="scss">
.editable-card {
margin: 10px 0;
transition: all 0.3s;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.title {
font-weight: 500;
font-size: 15px;
}
.time-info {
padding: 8px 0;
}
.time-item {
display: flex;
align-items: center;
margin: 6px 0;
color: #666;
i {
margin-right: 8px;
font-size: 14px;
}
}
.edit-mode {
.form-item {
margin-bottom: 12px;
}
.time-pickers {
display: grid;
grid-gap: 10px;
margin-bottom: 12px;
}
.actions {
text-align: right;
}
}
/* 增加按钮间距 */
.actions .el-button {
margin-left: 8px;
}
</style>