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

695 lines
22 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="年级">
<span v-show="!isDetail">
<el-select
v-model="safeDetail.gradeLevel"
placeholder="请年级"
clearable
style="width: 120px"
>
<el-option
v-for="g in gradeOptions"
:key="g.value"
:label="g.label"
:value="g.value"
/>
</el-select>
<el-input-number
style="width: 120px"
v-model="safeDetail.gradeYear"
:min="2020"
:max="2100"
/>届
</span>
<span v-show="isDetail">
{{ safeDetail.gradeLevel || "-" }}{{ safeDetail.gradeYear || "-" }}届
</span>
</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-item label="预计完结时间">
{{ safeDetail.endTime != null ? safeDetail.endTime.split("T")[0] : "--" }}
</el-descriptions-item>
</el-descriptions>
<el-divider />
<el-descriptions title="基础工作" :column="1" border>
<el-descriptions-item label="座谈">
<div v-if="!isDetail" class="flexCenterWrap">
<el-tag :type="safeDetail.isDiscussion ? 'success' : 'info'">
{{ safeDetail.isDiscussion ? "已开展" : "未开展" }}
</el-tag>
<el-radio-group :disabled="isDetail" v-model="safeDetail.isDiscussion">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
<el-input
:maxlength="500"
type="textarea"
:rows="3"
v-model="safeDetail.discussion"
:disabled="!safeDetail.isDiscussion"
placeholder="请输入座谈情况"
/>
</div>
<div v-else>
{{ safeDetail.isDiscussion ? safeDetail.discussion : "未开展" }}
</div>
</el-descriptions-item>
<el-descriptions-item label="班会">
<div v-if="!isDetail" class="flexCenterWrap">
<el-tag :type="safeDetail.isClassMeeting ? 'success' : 'info'">
{{ safeDetail.isClassMeeting ? "已开展" : "未开展" }}
</el-tag>
<el-radio-group :disabled="isDetail" v-model="safeDetail.isClassMeeting">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
<el-input
:maxlength="500"
type="textarea"
:rows="3"
v-model="safeDetail.classMeeting"
:disabled="isDetail || !safeDetail.isClassMeeting"
placeholder="请输入班会情况"
/>
</div>
<div v-else>
{{ safeDetail.isDiscussion ? safeDetail.discussion : "未开展" }}
</div>
</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 questions" :key="idx" :name="idx">
<template #label>
<span class="custom-tabs-label" :class="i.solution ? `text-[#67c23a]` : ``">
<span>问题{{ idx + 1 }}</span>
<el-icon>
<Select v-if="i.solution" />
</el-icon>
</span>
</template>
<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-for="(rec, rIdx) in i.recordArr || []"
:key="rIdx"
style="margin-top: 10px"
>
<div>
执行记录{{ rIdx + 1 }}{{ rec.operator || "" }} {{ rec.executionTimeStr }}
</div>
<div style="padding: 5px; background-color: #f3f3f3">
{{ rec.executionRecords }}
</div>
</div>
<div v-if="i.solution" style="font-size: 12px; margin-top: 20px">
<span class="text-amber-400 font-bold">
解决时间:{{ i.endTimeStr || i.endTime }}
</span>
<div style="padding: 10px; background-color: #f3f3f3">
{{ i.solution }}
</div>
</div>
<div v-else class="mt-1.5">
<el-button
v-show="!isDetail"
link
:disabled="i.solution"
style="margin-top: 5px; font-size: 12px"
class="markTitle"
@click="addQRecord(i)"
>
添加执行记录
</el-button>
<el-button
v-show="!isDetail"
link
style="margin-top: 5px; font-size: 12px"
class="markTitle"
@click="markTitle(i)"
>
标记已解决
</el-button>
</div>
</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
link
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"
link
v-show="!isDetail"
:disabled="unresolvedCount != 0"
title="请先解决所有问题后再添加完结情况"
@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: #67c23a">
执行记录{{ 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 class="text-amber-400 font-bold">完结情况:{{ 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, onMounted } 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";
import { Select, CloseBold } from "@element-plus/icons-vue";
const activeName = ref<any>(0);
onMounted(() => {
console.log("onMounted ");
questions = ref(sortData(safeDetail.value.feedbackQuestions));
});
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab.props.name, event);
activeName.value = tab.props.name;
};
const gradeOptions = [
{ label: "高", value: "高" },
{ label: "初", value: "初" },
{ label: "小", value: "小" },
];
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":
case "addQRecord":
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>): number => {
return (data || []).filter((i) => !i?.solution).length;
};
const sortData = (data: Array<any>) => {
if (data == null || data.length == 0) return [];
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"
);
let questions = ref(sortData(safeDetail.value.feedbackQuestions));
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 addQRecord = (data: any) => {
console.log("添加问题执行记录", data);
operationType.value = "addQRecord";
currentMarkedQuestion.value = data;
// 预填已有值
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 userName = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickName)
? useUserStoreHook()?.userName
: useUserStoreHook()?.nickName;
});
const confirmOperation = async () => {
if (!operationFormRef.value) return;
try {
await operationFormRef.value.validate();
const { operationTime, operationContent } = operationForm;
// 根据操作类型处理数据
switch (operationType.value) {
case "addQRecord":
// 添加执行记录
if (currentMarkedQuestion.value) {
if (currentMarkedQuestion.value.recordArr == undefined) {
currentMarkedQuestion.value.recordArr = [];
}
currentMarkedQuestion.value.recordArr.push({
executionTime: operationTime,
executionRecords: operationContent,
operator: userName.value,
});
}
break;
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 = [];
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));
copyParams.grade = `${copyParams.gradeLevel}${copyParams.gradeYear}`;
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">
.custom-tabs-label .el-icon {
vertical-align: middle;
}
.custom-tabs-label span {
vertical-align: middle;
margin-left: 4px;
}
.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;
user-select: none;
}
.flexCenterWrap {
gap: 4px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
align-items: center;
justify-content: flex-start;
}
</style>