Learn.Archives.Web/src/views/toschoolinfomanage/editModal.vue

562 lines
18 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>
<el-dialog
v-model="dialogVisible"
:title="isDetail ? '详情' : '跟进'"
width="800px"
@close="closeModal"
align-center
>
<!-- {{ isDetail }} -->
<div class="modal-header">
<div class="status-box">
<span>当前状态:</span>
<el-tag :type="statusType">{{ statusText }}</el-tag>
</div>
<div class="action-box">
<el-button @click="onClickCancel">取消</el-button>
<el-button v-show="!isDetail" type="primary" @click="onClickSave">保存</el-button>
</div>
</div>
<div
style="height: 80vh; overflow-y: auto; padding-right: 22px"
v-loading="editModalLoading"
>
<!-- <el-divider /> -->
<el-descriptions title="基础信息" :column="2" border>
<el-descriptions-item label="学校">
{{ safeDetail.schoolName || safeDetail.school || "-" }}
</el-descriptions-item>
<el-descriptions-item label="年级">
{{ safeDetail.grade || safeDetail.gradeLevel || "-" }}
</el-descriptions-item>
<el-descriptions-item label="赴校人员">
{{
Array.isArray(safeDetail.schoolBusinessUser)
? safeDetail.schoolBusinessUser.join("")
: safeDetail.schoolBusinessUser || "-"
}}
</el-descriptions-item>
<el-descriptions-item label="赴校时间">
{{ safeDetail.startTimeStr || safeDetail.startTime }}
</el-descriptions-item>
</el-descriptions>
<el-divider />
<el-descriptions title="基础工作" :column="1" border>
<el-descriptions-item label="座谈">
<el-tag
:type="safeDetail.isDiscussion ? 'success' : 'info'"
style="margin-right: 8px"
>
{{ safeDetail.isDiscussion ? "已开展" : "未开展" }}
</el-tag>
<span class="inline-block max-w-[500px]!">{{
safeDetail.discussion || "-"
}}</span>
</el-descriptions-item>
<el-descriptions-item label="班会">
<el-tag
:type="safeDetail.isClassMeeting ? 'success' : 'info'"
style="margin-right: 8px"
>
{{ safeDetail.isClassMeeting ? "已开展" : "未开展" }}
</el-tag>
<span class="inline-block max-w-[500px]!">{{
safeDetail.classMeeting || "-"
}}</span>
</el-descriptions-item>
</el-descriptions>
<el-divider />
<el-descriptions title="反馈问题" :column="1" border> </el-descriptions>
<div style="display: flex; gap: 40px; margin-bottom: 5px">
<span> 问题总数:{{ safeDetail.feedbackQuestions?.length }} </span>
<span> 未解决问题:{{ unresolvedCount }} </span>
</div>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane
v-for="(i, idx) in sortData(safeDetail.feedbackQuestions || [])"
:key="idx"
:label="'问题' + (idx + 1) + (i.solution ? '(✅已解决)' : '(未解决)')"
:name="idx"
>
<div style="font-size: 12px; margin-bottom: 4px">
<span>问题类型:</span> <span>{{ queType[i.questionType] }}</span>
</div>
<div style="padding: 10px; background-color: #f3f3f3">
{{ i.question }}
</div>
<div v-if="i.solution" style="font-size: 12px; margin-top: 10px">
<span> 解决时间:{{ i.endTimeStr || i.endTime }} </span>
<div style="padding: 10px; background-color: #f3f3f3">
{{ i.solution }}
</div>
</div>
<el-button
v-show="!isDetail"
type="text"
v-else
style="margin-top: 5px; font-size: 12px"
class="markTitle"
@click="markTitle(i)"
>
标记已解决
</el-button>
</el-tab-pane>
</el-tabs>
<el-divider />
<!-- {{ safeDetail.feedbackQuestions }} -->
<!-- .............................................................. -->
<el-descriptions title="备注" :column="1" border> </el-descriptions>
<span v-show="!safeDetail.remark">未录入备注</span>
<el-input
v-model="safeDetail.remark"
:rows="4"
type="textarea"
:disabled="isDetail"
/>
<el-divider />
<el-descriptions title="解决方案执行跟踪记录" :column="1" border> </el-descriptions>
<span>需求+解决方案</span>
<el-input v-model="solutionText" :rows="4" type="textarea" :disabled="isDetail" />
<!-- 添加按钮区域 -->
<div style="margin-top: 5px; display: flex; gap: 20px">
<el-button
type="text"
style="margin-top: 5px; font-size: 12px"
class="markTitle"
v-show="!isDetail"
@click="addRecord"
>
添加执行记录
</el-button>
<el-button
style="margin-top: 5px; font-size: 12px"
class="markTitle"
type="text"
v-show="!isDetail"
@click="addFinish"
>
{{ finishRecord ? "修改完结情况" : "添加完结情况" }}
</el-button>
</div>
<!-- 执行记录列表 -->
<div v-if="executionRecords.length > 0" style="margin-top: 15px">
<!-- <div style="font-weight: bold; margin-bottom: 8px">执行记录:</div> -->
<div
v-for="(record, index) in executionRecords"
:key="index"
style="
margin-bottom: 10px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
"
>
<div style="font-weight: bold; color: #409eff">
执行记录{{ index + 1 }}{{ record.operator }} {{ record.time }}
</div>
<div style="margin-top: 5px; white-space: pre-wrap">
{{ record.content }}
</div>
</div>
</div>
<!-- 完结情况 -->
<div v-if="finishRecord" style="margin-top: 15px">
<!-- <div style="font-weight: bold; margin-bottom: 8px">完结情况:</div> -->
<div
style="
padding: 10px;
background-color: #f0f9ff;
border-radius: 4px;
border: 1px solid #b3d8ff;
"
>
<div style="font-weight: bold; color: #a69400">
完结情况:{{ finishRecord.time }}
</div>
<div style="margin-top: 5px; white-space: pre-wrap">
{{ finishRecord.content }}
</div>
</div>
</div>
</div>
</el-dialog>
<!-- 操作弹窗 -->
<el-dialog v-model="operationDialogVisible" title="操作" width="500px" align-center>
<el-form
ref="operationFormRef"
:model="operationForm"
:rules="operationRules"
label-width="80px"
>
<el-form-item label="操作时间" prop="operationTime">
<el-date-picker
v-model="operationForm.operationTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择操作时间"
style="width: 100%"
/>
</el-form-item>
<el-form-item :label="operationContentLabel" prop="operationContent">
<el-input
v-model="operationForm.operationContent"
type="textarea"
:rows="4"
maxlength="300"
show-word-limit
:placeholder="`请输入${operationContentLabel}`"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeOperationDialog">取消</el-button>
<el-button type="primary" @click="confirmOperation">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="EditModal">
import { ref, reactive, computed, defineProps, defineEmits, watch } from "vue";
import type { FormInstance, FormRules, TabsPaneContext } from "element-plus";
import { ElMessage } from "element-plus";
import { getSchoolData } from "@/api/userCenter";
import { getSchoolBusinessPeopleListApi, addOrEditApi } from "@/api/toschoolinfomanage";
import { setFips } from "crypto";
import { useUserStoreHook } from "@/store/modules/user";
import { isAllEmpty } from "@pureadmin/utils";
const activeName = ref<any>(0);
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab.props.name, event);
activeName.value = tab.props.name;
};
const props = defineProps<{
visible: boolean;
detailData: any;
editModalLoading: boolean;
isDetail: boolean;
}>();
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "handleReset"): void;
}>();
// 操作弹窗相关数据
const operationDialogVisible = ref(false);
const operationType = ref(""); // 操作类型markSolved, addRecord, addFinish
const operationForm = reactive({
operationTime: "",
operationContent: "",
});
const operationFormRef = ref<FormInstance>();
// 执行记录和完结情况数据
const executionRecords = ref<Array<{ time: string; content: string; operator?: string }>>(
[]
);
const finishRecord = ref<{ time: string; content: string } | null>(null);
// 从父级 detailData.solutionRecord 回显本地显示数据
watch(
() => props.detailData,
(val) => {
const sr = (val as any)?.solutionRecord || {};
// 执行记录回显
const recs = Array.isArray(sr?.record) ? sr.record : [];
executionRecords.value = recs.map((r: any) => ({
time: r?.executionTimeStr || r?.executionTime || "",
content: r?.executionRecords || "",
operator: r?.operator || "",
}));
// 完结情况回显
if (sr?.endRecordTime || sr?.endRecord) {
finishRecord.value = {
time: sr?.endRecordTimeStr || "",
content: sr?.endRecord || "",
};
} else {
finishRecord.value = null;
}
},
{ immediate: true, deep: true }
);
// 当前标记“已解决”的问题项
const currentMarkedQuestion = ref<any | null>(null);
// 根据操作类型动态显示文案
const operationContentLabel = computed(() => {
switch (operationType.value) {
case "markSolved":
return "解决情况";
case "addRecord":
return "执行记录";
case "addFinish":
return "完结情况";
default:
return "操作内容";
}
});
// 表单验证规则
const operationRules: FormRules = {
operationTime: [{ required: true, message: "请选择操作时间", trigger: "change" }],
operationContent: [
{
required: true,
message: `请输入${operationContentLabel.value}`,
trigger: "blur",
},
],
};
const dialogVisible = computed({
get: () => props.visible,
set: (v) => emit("update:visible", v),
});
const closeModal = () => {
emit("update:visible", false);
activeName.value = 0;
};
const queType = {
1: "学校领导班子",
10: "双师课堂",
15: "设备",
20: "学生",
999: "其他",
};
/**
* 获取未解决问题数量
* @param data
*/
const handleUnHandleQust = (data: Array<any>) => {
return (data || []).filter((i) => !i?.solution).length;
};
const sortData = (data: Array<any>) => {
const categorizedData = [
...data
.filter((item) => item.questionType === 1)
.sort((a, b) => a.sort.localeCompare(b.sort)),
...data
.filter((item) => item.questionType === 10)
.sort((a, b) => a.sort.localeCompare(b.sort)),
...data
.filter((item) => item.questionType === 15)
.sort((a, b) => a.sort.localeCompare(b.sort)),
...data
.filter((item) => item.questionType === 20)
.sort((a, b) => a.sort.localeCompare(b.sort)),
...data
.filter((item) => item.questionType === 999)
.sort((a, b) => a.sort.localeCompare(b.sort)),
];
return categorizedData;
};
const safeDetail = computed(() => props.detailData || {});
const statusText = computed(() => (safeDetail.value?.solutionEnd ? "已完结" : "跟进中"));
const statusType = computed(() =>
safeDetail.value?.solutionEnd ? "success" : "warning"
);
const solutionText = computed({
get: () => safeDetail.value?.solutionRecord?.solution || "",
set: (value: string) => {
if (!safeDetail.value.solutionRecord) {
safeDetail.value.solutionRecord = {};
}
safeDetail.value.solutionRecord.solution = value;
},
});
// 未解决问题数量(依赖每个问题项的 solution 字段,确保标记已解决后自动更新)
const unresolvedCount = computed(() => {
const list = (safeDetail.value?.feedbackQuestions as any[]) || [];
return list.filter((item) => !item?.solution).length;
});
const markTitle = (data: any) => {
console.log("标记已解决", data);
operationType.value = "markSolved";
currentMarkedQuestion.value = data;
// 预填已有值
operationForm.operationTime = data?.endTime || "";
operationForm.operationContent = data?.solution || "";
operationFormRef.value?.clearValidate();
operationDialogVisible.value = true;
};
const addRecord = () => {
console.log("添加执行记录");
operationType.value = "addRecord";
operationDialogVisible.value = true;
};
const addFinish = () => {
console.log("添加完结情况");
operationType.value = "addFinish";
const sr = (props.detailData as any)?.solutionRecord;
// 优先从父级已有完结情况预填,其次使用本地 finishRecord
if (sr && (sr.endRecordTime || sr.endRecord)) {
operationForm.operationTime = sr.endRecordTimeStr || sr.endRecordTime || "";
operationForm.operationContent = sr.endRecord || "";
} else if (finishRecord.value) {
operationForm.operationTime = finishRecord.value.time;
operationForm.operationContent = finishRecord.value.content;
} else {
// 重置表单
operationForm.operationTime = "";
operationForm.operationContent = "";
}
operationDialogVisible.value = true;
};
// 操作弹窗相关方法
const closeOperationDialog = () => {
operationDialogVisible.value = false;
// 重置表单
operationForm.operationTime = "";
operationForm.operationContent = "";
// 清除表单验证状态
operationFormRef.value?.clearValidate();
// 重置当前标记项
currentMarkedQuestion.value = null;
};
const confirmOperation = async () => {
if (!operationFormRef.value) return;
try {
await operationFormRef.value.validate();
const { operationTime, operationContent } = operationForm;
// 根据操作类型处理数据
switch (operationType.value) {
case "addRecord":
// 添加执行记录
executionRecords.value.push({
time: operationTime,
content: operationContent,
});
// 同步到父数据 solutionRecord.record
if (!props.detailData.solutionRecord) props.detailData.solutionRecord = {} as any;
if (!Array.isArray(props.detailData.solutionRecord.record))
props.detailData.solutionRecord.record = [];
const userName = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickName)
? useUserStoreHook()?.userName
: useUserStoreHook()?.nickName;
});
props.detailData.solutionRecord.record.push({
executionTime: operationTime,
executionRecords: operationContent,
operator: userName.value,
});
break;
case "addFinish":
// 添加或修改完结情况
finishRecord.value = {
time: operationTime,
content: operationContent,
};
// 同步到父数据 solutionRecord.endRecordTime / endRecord
if (!props.detailData.solutionRecord) props.detailData.solutionRecord = {} as any;
props.detailData.solutionRecord.endRecordTime = operationTime;
props.detailData.solutionRecord.endRecord = operationContent;
break;
case "markSolved":
// 将提交的数据写回当前问题项
if (currentMarkedQuestion.value) {
currentMarkedQuestion.value.endTime = operationTime;
currentMarkedQuestion.value.solution = operationContent;
}
break;
}
console.log("确认操作", {
type: operationType.value,
time: operationTime,
content: operationContent,
});
closeOperationDialog();
} catch (error) {
console.log("表单验证失败", error);
}
};
function onClickCancel() {
console.log("取消");
emit("update:visible", false);
// 清空本地临时数据
executionRecords.value = [];
finishRecord.value = null;
currentMarkedQuestion.value = null;
operationForm.operationTime = "";
operationForm.operationContent = "";
activeName.value = 0;
operationFormRef.value?.clearValidate();
}
function onClickSave() {
console.log("保存", props.detailData);
let copyParams = JSON.parse(JSON.stringify(props.detailData));
delete copyParams.solutionEnd;
addOrEditApi(copyParams).then((res) => {
if (res.code === 200) {
ElMessage.success("提交成功");
// 关闭弹窗
emit("update:visible", false);
// 清空本地临时数据
executionRecords.value = [];
finishRecord.value = null;
currentMarkedQuestion.value = null;
operationForm.operationTime = "";
operationForm.operationContent = "";
activeName.value = 0;
operationFormRef.value?.clearValidate();
// 通知父级刷新列表复用父级的handleReset实现刷新
emit("handleReset");
}
});
}
</script>
<style scoped lang="scss">
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
.status-box {
display: flex;
align-items: center;
gap: 8px;
}
.action-box {
display: flex;
align-items: center;
gap: 8px;
}
.markTitle {
color: #409eff;
}
.markTitle:hover {
text-decoration: underline;
cursor: pointer;
user-select: none;
}
</style>