整合前端

This commit is contained in:
砂糖
2026-04-13 17:04:38 +08:00
parent 69609a2cb1
commit 5d4794c9bd
915 changed files with 144259 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
<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>