dev #8
|
|
@ -6,7 +6,9 @@ VITE_PUBLIC_PATH = /
|
|||
|
||||
# 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||
VITE_ROUTER_HISTORY = "hash"
|
||||
|
||||
VITE_API_BASEURL = "http://192.168.2.33:5199/api"
|
||||
# 接口地址
|
||||
VITE_API_BASEURL = "http://localhost:5199/api"
|
||||
# VITE_API_BASEURL = "http://localhost:5199/api"
|
||||
#数据中心后台地址
|
||||
VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# shellcheck source=./_/husky.sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
PATH="/usr/local/bin:$PATH"
|
||||
|
||||
npx --no-install commitlint --edit "$1"
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
command_exists () {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Workaround for Windows 10, Git Bash and Pnpm
|
||||
if command_exists winpty && test -t 1; then
|
||||
exec < /dev/tty
|
||||
fi
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
|
||||
[ -n "$CI" ] && exit 0
|
||||
|
||||
PATH="/usr/local/bin:$PATH"
|
||||
|
||||
# Perform lint check on files in the staging area through .lintstagedrc configuration
|
||||
pnpm exec lint-staged
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { http } from "@/utils/http";
|
||||
import type { Res } from "@/utils/http/types";
|
||||
// import type { Res } from "@/utils/http/types";
|
||||
|
||||
/**
|
||||
* @description 导入表单
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { http } from "@/utils/http";
|
||||
import type { Res } from "@/utils/http/types";
|
||||
|
||||
/**
|
||||
* @description 获取学校枚举下拉
|
||||
* @param {string} type 枚举类型 type=StatusEnum
|
||||
* @return {object}
|
||||
*/
|
||||
export function getenumApi(data) {
|
||||
return http.request<Res<any>>("post", `/SchoolBusiness/QueryCombo`, {
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description 获取学校枚举下拉
|
||||
* @param {string} type 枚举类型 type=StatusEnum
|
||||
* @return {object}
|
||||
*/
|
||||
export function getPageListApi(data) {
|
||||
return http.request<Res<any>>("post", `/SchoolBusiness/QueryPageList`, {
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description 新增赴校信息或者编辑 id:0(新增),其他(编辑)
|
||||
* @return {object}
|
||||
*/
|
||||
export function addOrEditApi(data: any) {
|
||||
return http.request<Res<any>>("post", `/SchoolBusiness/Edit`, {
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description 获取赴校信息详情
|
||||
* @param {string} type 枚举类型 type=StatusEnum
|
||||
* @return {object}
|
||||
*/
|
||||
export function getSchoolBusinessDetailApi(id: string | number) {
|
||||
return http.request<Res<any>>("get", `/SchoolBusiness/${id}`);
|
||||
}
|
||||
/**
|
||||
* @description 删除赴校信息
|
||||
* @return {object}
|
||||
*/
|
||||
export function deleteSchoolBusinessApi(data: Array<string | number>) {
|
||||
return http.request<Res<any>>("post", `/SchoolBusiness/Del`, {
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description 获取赴校人员下拉数据
|
||||
* @return {object}
|
||||
*/
|
||||
export function getSchoolBusinessPeopleListApi(data: object) {
|
||||
return http.request<Res<any>>("post", `/Admin/QueryCombo`, {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
|
@ -588,26 +588,28 @@ function fetchPagedData() {
|
|||
.columnTemplate .btn {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.columnTemplate {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-flow: row wrap;
|
||||
gap: 5px;
|
||||
place-content: flex-start flex-start;
|
||||
align-items: center;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.sty .tab_tip {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.maxWidth600px {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { stringify } from "qs";
|
|||
import NProgress from "../progress";
|
||||
import { getToken, formatToken } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { string } from "vue-types";
|
||||
// import { string } from "vue-types";
|
||||
import router from "@/router";
|
||||
|
||||
/**请求后端的地址 未配置则访问BaseURL */
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ahTable from "@/components/hTable/index.vue";
|
||||
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, defineOptions } from "vue";
|
||||
import { fa } from "element-plus/es/locales.mjs";
|
||||
import { hTableAPI } from "@/api/hTable";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ahTable from "@/components/hTable/index.vue";
|
||||
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref,defineOptions } from "vue";
|
||||
import { fa } from "element-plus/es/locales.mjs";
|
||||
import { hTableAPI } from "@/api/hTable";
|
||||
const ControllerName = "AdminRole";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ahTable from "@/components/hTable/index.vue";
|
||||
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, defineOptions } from "vue";
|
||||
import { fa } from "element-plus/es/locales.mjs";
|
||||
import { hTableAPI } from "@/api/hTable";
|
||||
import { getenum } from "@/api/enum";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ahTable from "@/components/hTable/index.vue";
|
||||
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, defineOptions } from "vue";
|
||||
import { fa } from "element-plus/es/locales.mjs";
|
||||
import { hTableAPI } from "@/api/hTable";
|
||||
import { getenum } from "@/api/enum";
|
||||
|
|
|
|||
|
|
@ -96,5 +96,7 @@ const tableData: TableConfig = {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div><ahTable ref="table" :tableConfig="tableData" /></div>
|
||||
<div>
|
||||
<ahTable ref="table" :tableConfig="tableData" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts" name="Testxb">
|
||||
import { ref } from "vue";
|
||||
// defineOptions({
|
||||
// name: "Testxb"
|
||||
// });
|
||||
let name = ref("nihao");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>测试菜单</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,526 @@
|
|||
<!-- 新建赴校信息 -->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="新建"
|
||||
width="800px"
|
||||
@close="onCancel"
|
||||
align-center
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-divider>基础信息</el-divider>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学校" prop="baseInfo.school">
|
||||
<el-select
|
||||
v-model="form.baseInfo.school"
|
||||
placeholder="请选择学校"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="s in schoolOptions"
|
||||
:key="s.value"
|
||||
:label="s.label"
|
||||
:value="s.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年级" prop="baseInfo.grade">
|
||||
<el-select
|
||||
v-model="form.baseInfo.grade"
|
||||
placeholder="请选择年级"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="g in gradeOptions"
|
||||
:key="g.value"
|
||||
:label="g.label"
|
||||
:value="g.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="赴校时间" prop="baseInfo.date">
|
||||
<el-date-picker
|
||||
v-model="form.baseInfo.date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="赴校人员" prop="baseInfo.people">
|
||||
<el-select
|
||||
v-model="form.baseInfo.people"
|
||||
placeholder="请选择赴校人员"
|
||||
clearable
|
||||
multiple
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in peopleOptions"
|
||||
:key="p.value"
|
||||
:label="p.text"
|
||||
:value="p.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider>基础工作</el-divider>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开展座谈" prop="work.talk">
|
||||
<el-radio-group v-model="form.work.talk">
|
||||
<el-radio :label="true">是</el-radio>
|
||||
<el-radio :label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="座谈情况">
|
||||
<el-input
|
||||
v-model="form.work.talkDetail"
|
||||
:disabled="!form.work.talk"
|
||||
placeholder="请输入座谈情况"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开展班会" prop="work.classMeeting">
|
||||
<el-radio-group v-model="form.work.classMeeting">
|
||||
<el-radio :label="true">是</el-radio>
|
||||
<el-radio :label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班会情况">
|
||||
<el-input
|
||||
v-model="form.work.classMeetingDetail"
|
||||
:disabled="!form.work.classMeeting"
|
||||
placeholder="请输入班会情况"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider>反馈问题</el-divider>
|
||||
<div style="height: 370px; overflow-y: auto">
|
||||
<div
|
||||
v-for="group in feedbackGroups"
|
||||
:key="group.key"
|
||||
class="feedback-group"
|
||||
>
|
||||
<div class="feedback-header">
|
||||
<span class="group-title">{{ group.name }}</span>
|
||||
<el-button type="primary" link @click="addProblem(group.key)"
|
||||
>添加问题</el-button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.feedback[group.key].length === 0"
|
||||
class="feedback-empty"
|
||||
>
|
||||
暂无问题
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, idx) in form.feedback[group.key]"
|
||||
:key="item.id"
|
||||
class="feedback-item"
|
||||
>
|
||||
<el-input
|
||||
v-model="item.text"
|
||||
:placeholder="`请输入${group.name}问题描述`"
|
||||
/>
|
||||
<el-button type="danger" text @click="removeProblem(group.key, idx)"
|
||||
>删除</el-button
|
||||
>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onCancel">取 消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit"
|
||||
>确 定</el-button
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="AddModal">
|
||||
import { ref, reactive, computed, defineProps, defineEmits, watch } from "vue";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { getSchoolData } from "@/api/userCenter";
|
||||
import {
|
||||
getSchoolBusinessPeopleListApi,
|
||||
addOrEditApi
|
||||
} from "@/api/toschoolinfomanage";
|
||||
const props = defineProps<{ visible: boolean }>();
|
||||
// const emit = defineEmits<{ (e: "update:visible", value: boolean): void }>();
|
||||
const emit = defineEmits<{
|
||||
(e: "update:visible", value: boolean): void;
|
||||
(e: "handleReset"): void;
|
||||
}>();
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: v => emit("update:visible", v)
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
function uid() {
|
||||
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
const schoolOptions = ref([]);
|
||||
const peopleOptions = ref([]);
|
||||
const getSchoolDataFn = () => {
|
||||
getSchoolData().then(res => {
|
||||
if (res.code == 200) {
|
||||
schoolOptions.value = res.data.map((i: any) => ({
|
||||
label: i.text,
|
||||
value: i.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取赴校人员下拉数据
|
||||
*/
|
||||
const getSchoolBusinessPeopleList = () => {
|
||||
getSchoolBusinessPeopleListApi({}).then((res: any) => {
|
||||
if (res.code == 200) {
|
||||
peopleOptions.value = (res.data || []).map(i => ({
|
||||
label: i.text,
|
||||
value: i.text
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
getSchoolDataFn();
|
||||
getSchoolBusinessPeopleList();
|
||||
const gradeOptions = [
|
||||
{ label: "初一", value: "初一" },
|
||||
{ label: "初二", value: "初二" },
|
||||
{ label: "初三", value: "初三" },
|
||||
{ label: "高一", value: "高一" },
|
||||
{ label: "高二", value: "高二" },
|
||||
{ label: "高三", value: "高三" }
|
||||
];
|
||||
|
||||
type FeedbackKey =
|
||||
| "leaders"
|
||||
| "classroom"
|
||||
| "equipment"
|
||||
| "students"
|
||||
| "others";
|
||||
interface FeedbackItem {
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
interface FormModel {
|
||||
baseInfo: {
|
||||
school?: string;
|
||||
grade?: string;
|
||||
date?: string;
|
||||
people: string[];
|
||||
};
|
||||
work: {
|
||||
talk: boolean;
|
||||
talkDetail: string;
|
||||
classMeeting: boolean;
|
||||
classMeetingDetail: string;
|
||||
};
|
||||
feedback: Record<FeedbackKey, FeedbackItem[]>;
|
||||
}
|
||||
|
||||
const form = reactive<FormModel>({
|
||||
baseInfo: {
|
||||
school: undefined,
|
||||
grade: undefined,
|
||||
date: undefined,
|
||||
people: []
|
||||
},
|
||||
work: {
|
||||
talk: false,
|
||||
talkDetail: "",
|
||||
classMeeting: false,
|
||||
classMeetingDetail: ""
|
||||
},
|
||||
feedback: {
|
||||
leaders: [],
|
||||
classroom: [],
|
||||
equipment: [],
|
||||
students: [],
|
||||
others: []
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => form.work.talk,
|
||||
val => {
|
||||
!val ? (form.work.talkDetail = "") : "";
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => form.work.classMeeting,
|
||||
val => {
|
||||
!val ? (form.work.classMeetingDetail = "") : "";
|
||||
}
|
||||
);
|
||||
const rules: FormRules = {
|
||||
"baseInfo.school": [
|
||||
{ required: true, message: "请选择学校", trigger: "change" }
|
||||
],
|
||||
"baseInfo.grade": [
|
||||
{ required: true, message: "请选择年级", trigger: "change" }
|
||||
],
|
||||
"baseInfo.date": [
|
||||
{ required: true, message: "请选择赴校时间", trigger: "change" }
|
||||
],
|
||||
"baseInfo.people": [
|
||||
{ required: true, message: "请选择赴校人员", trigger: "change" },
|
||||
{
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请至少选择一名赴校人员",
|
||||
trigger: "change"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const feedbackGroups = [
|
||||
{ key: "leaders", name: "学校领导班子" },
|
||||
{ key: "classroom", name: "双师课堂" },
|
||||
{ key: "equipment", name: "设备" },
|
||||
{ key: "students", name: "学生" },
|
||||
{ key: "others", name: "其他" }
|
||||
] as { key: FeedbackKey; name: string }[];
|
||||
|
||||
function addProblem(key: FeedbackKey) {
|
||||
form.feedback[key].push({ id: uid(), text: "" });
|
||||
}
|
||||
|
||||
function removeProblem(key: FeedbackKey, index: number) {
|
||||
form.feedback[key].splice(index, 1);
|
||||
}
|
||||
|
||||
// 新增:反馈问题非空校验
|
||||
const feedbackGroupNameMap: Record<FeedbackKey, string> = {
|
||||
leaders: "学校领导班子",
|
||||
classroom: "双师课堂",
|
||||
equipment: "设备",
|
||||
students: "学生",
|
||||
others: "其他"
|
||||
};
|
||||
function validateFeedbackNotEmpty() {
|
||||
for (const g of feedbackGroups) {
|
||||
const items = form.feedback[g.key] || [];
|
||||
if (items.length === 0) continue; // 允许为 0
|
||||
for (const it of items) {
|
||||
if (!it.text || !it.text.trim()) {
|
||||
ElMessage.error(`${feedbackGroupNameMap[g.key]} 的问题内容不能为空`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const submitting = ref(false);
|
||||
|
||||
function onCancel() {
|
||||
dialogVisible.value = false;
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
/**
|
||||
* 把表单数据处理成接口需要的结构
|
||||
* @param data
|
||||
*/
|
||||
const handleFeedback = (data: any) => {
|
||||
const processData = (items: any[], questionType: number) => {
|
||||
return items.map((item, idx) => ({
|
||||
question: item.text,
|
||||
questionType,
|
||||
sort: (idx + 1).toString()
|
||||
}));
|
||||
};
|
||||
|
||||
let handledData = [];
|
||||
|
||||
if (data.leaders.length > 0)
|
||||
handledData.push(...processData(data.leaders, 1));
|
||||
if (data.classroom.length > 0)
|
||||
handledData.push(...processData(data.classroom, 10));
|
||||
if (data.equipment.length > 0)
|
||||
handledData.push(...processData(data.equipment, 15));
|
||||
if (data.students.length > 0)
|
||||
handledData.push(...processData(data.students, 20));
|
||||
if (data.others.length > 0)
|
||||
handledData.push(...processData(data.others, 999));
|
||||
|
||||
return handledData;
|
||||
};
|
||||
let editParams = {
|
||||
id: 0,
|
||||
schoolId: 0,
|
||||
schoolName: "string",
|
||||
grade: "string",
|
||||
gradeYear: 0,
|
||||
gradeLevel: "string",
|
||||
// 赴校人员
|
||||
schoolBusinessUser: ["string"],
|
||||
// 赴校时间
|
||||
startTime: "2025-08-19T07:20:29.292Z",
|
||||
// 备注
|
||||
remark: "string",
|
||||
// 反馈问题
|
||||
feedbackQuestions: [
|
||||
{
|
||||
// 问题类型
|
||||
questionType: 1,
|
||||
// 排序
|
||||
sort: "string",
|
||||
// 问题描述
|
||||
question: "string",
|
||||
// 解决情况
|
||||
solution: "string",
|
||||
// 解决时间
|
||||
endTime: "2025-08-19T07:20:29.292Z"
|
||||
}
|
||||
],
|
||||
// 解决方案记录
|
||||
solutionRecord: {
|
||||
// 解决方案
|
||||
solution: "string",
|
||||
// 完结情况
|
||||
endRecord: "string",
|
||||
// 完结时间
|
||||
endRecordTime: "string",
|
||||
record: [
|
||||
{
|
||||
// 执行记录
|
||||
executionRecords: "string",
|
||||
// 执行时间
|
||||
executionTime: "2025-08-19T07:20:29.292Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
// 座谈情况
|
||||
discussion: "string",
|
||||
// 班会情况
|
||||
classMeeting: "string"
|
||||
};
|
||||
async function onSubmit() {
|
||||
if (!formRef.value) return;
|
||||
await formRef.value.validate(valid => {
|
||||
if (!valid) return;
|
||||
// 新增:校验反馈问题不为空
|
||||
if (!validateFeedbackNotEmpty()) return;
|
||||
submitting.value = true;
|
||||
console.log("Submit payload:", form);
|
||||
// enum FeedbackQuestionTypeEnum {
|
||||
// 学校领导班子 = 1,
|
||||
// 双师课堂 = 10,
|
||||
// 设备 = 15,
|
||||
// 学生 = 20,
|
||||
// 其他 = 999
|
||||
// }
|
||||
let reqParams = {
|
||||
id: 0, //id传0代表新增
|
||||
schoolId: form.baseInfo.school,
|
||||
schoolName: schoolOptions.value.find(i => i.value == form.baseInfo.school)
|
||||
.label,
|
||||
grade: form.baseInfo.grade,
|
||||
// 固定传空字符串
|
||||
gradeLevel: "",
|
||||
schoolBusinessUser: form.baseInfo.people,
|
||||
startTime: form.baseInfo.date,
|
||||
isDiscussion: form.work.talk,
|
||||
discussion: form.work.talkDetail,
|
||||
isClassMeeting: form.work.classMeeting,
|
||||
classMeeting: form.work.classMeetingDetail,
|
||||
feedbackQuestions: handleFeedback(form.feedback)
|
||||
};
|
||||
// return;
|
||||
console.log("提交数据", reqParams);
|
||||
addOrEditApi(reqParams)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("提交成功");
|
||||
dialogVisible.value = false;
|
||||
resetForm();
|
||||
formRef.value.resetFields();
|
||||
emit("handleReset");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
submitting.value = false;
|
||||
});
|
||||
|
||||
// }, 600);
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.baseInfo.school = undefined;
|
||||
form.baseInfo.grade = undefined;
|
||||
form.baseInfo.date = undefined;
|
||||
form.baseInfo.people = [];
|
||||
form.work.talk = false;
|
||||
form.work.talkDetail = "";
|
||||
form.work.classMeeting = false;
|
||||
form.work.classMeetingDetail = "";
|
||||
(Object.keys(form.feedback) as FeedbackKey[]).forEach(k => {
|
||||
form.feedback[k] = [];
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.feedback-group {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.feedback-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feedback-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.feedback-empty {
|
||||
margin: 4px 0 8px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,564 @@
|
|||
<!-- 跟进 -->
|
||||
<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 :disabled="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.startTime?.split("T")[0] }}
|
||||
</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>{{ 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>{{ 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.endTime.split("T")[0] }} </span>
|
||||
<div style="padding: 10px; background-color: #f3f3f3">
|
||||
{{ i.solution }}
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="text"
|
||||
v-else
|
||||
style="margin-top: 5px; font-size: 12px"
|
||||
class="markTitle"
|
||||
:disabled="isDetail"
|
||||
@click="markTitle(i)"
|
||||
>
|
||||
标记已解决
|
||||
</el-button>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-divider />
|
||||
|
||||
<!-- {{ safeDetail.feedbackQuestions }} -->
|
||||
<!-- .............................................................. -->
|
||||
<el-descriptions title="备注" :column="1" border> </el-descriptions>
|
||||
<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"
|
||||
:disabled="isDetail"
|
||||
@click="addRecord"
|
||||
>
|
||||
添加执行记录
|
||||
</el-button>
|
||||
<el-button
|
||||
style="margin-top: 5px; font-size: 12px"
|
||||
class="markTitle"
|
||||
type="text"
|
||||
:disabled="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.time.split("T")[0] }}
|
||||
</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.split("T")[0] }}
|
||||
</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"
|
||||
: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";
|
||||
|
||||
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 }>>([]);
|
||||
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?.executionTime || "",
|
||||
content: r?.executionRecords || ""
|
||||
}));
|
||||
// 完结情况回显
|
||||
if (sr?.endRecordTime || sr?.endRecord) {
|
||||
finishRecord.value = {
|
||||
time: sr?.endRecordTime || "",
|
||||
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.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 = [];
|
||||
props.detailData.solutionRecord.record.push({
|
||||
executionTime: operationTime,
|
||||
executionRecords: operationContent
|
||||
});
|
||||
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>
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
<template>
|
||||
<div style="padding: 20px">
|
||||
<!-- 搜索区域 -->
|
||||
<el-form :model="query" inline label-width="80px" class="search-form">
|
||||
<el-form-item label="学校">
|
||||
<el-select
|
||||
v-model="query.school"
|
||||
placeholder="请选择学校"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 180px"
|
||||
>
|
||||
<el-option
|
||||
v-for="s in schoolOptions"
|
||||
:key="s.value"
|
||||
:label="s.label"
|
||||
:value="s.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="年级">
|
||||
<el-select
|
||||
v-model="query.grade"
|
||||
placeholder="请选择年级"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option
|
||||
v-for="g in gradeOptions"
|
||||
:key="g.value"
|
||||
:label="g.label"
|
||||
:value="g.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="赴校人员">
|
||||
<el-select
|
||||
v-model="query.people"
|
||||
placeholder="请选择赴校人员"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 300px"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in peopleOptions"
|
||||
:key="p.value"
|
||||
:label="p.text"
|
||||
:value="p.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="query.solutionEnd"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="已完结" :value="true" />
|
||||
<el-option label="跟进中" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="赴校时间">
|
||||
<el-date-picker
|
||||
v-model="query.times"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
unlink-panels
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮区域 -->
|
||||
<div style="margin-bottom: 10px">
|
||||
<el-button type="primary" @click="handleAdd">新建</el-button>
|
||||
<el-button type="success" @click="handleImport">批量导入</el-button>
|
||||
<el-button type="info" @click="handleExport">导出</el-button>
|
||||
<el-button type="info" @click="downLoadTpl">下载模版</el-button>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<el-table :data="listData" border style="width: 100%">
|
||||
<el-table-column prop="school" label="学校" min-width="140" />
|
||||
<el-table-column prop="grade" label="年级" min-width="100" />
|
||||
<el-table-column prop="people" label="赴校人员" min-width="120" />
|
||||
<el-table-column prop="times" label="赴校时间" min-width="140" />
|
||||
<el-table-column
|
||||
prop="feedbackTotals"
|
||||
label="反馈问题数量"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="solveTotals"
|
||||
label="解决问题数量"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column label="状态" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.solutionEnd ? 'success' : 'warning'">
|
||||
{{ row.solutionEnd ? "已完结" : "跟进中" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="lastTime" label="最后跟进时间" min-width="160" /> -->
|
||||
<el-table-column label="操作" fixed="right" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<!-- <el-button size="small" type="danger" plain @click="onDelete(row)"
|
||||
>删除</el-button
|
||||
> -->
|
||||
<el-popconfirm
|
||||
confirm-button-text="确定"
|
||||
cancel-button-text="取消"
|
||||
icon-color="#626AEF"
|
||||
title="确定删除吗?"
|
||||
@confirm="onDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
@click="onDetailOrFollow(row, true)"
|
||||
>详情</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="!row.solutionEnd"
|
||||
size="small"
|
||||
type="success"
|
||||
plain
|
||||
@click="onDetailOrFollow(row, false)"
|
||||
>跟进</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 新建 -->
|
||||
<AddModal v-model:visible="isShowAddModal" @handleReset="handleReset" />
|
||||
<!-- 跟进 -->
|
||||
<EditModal
|
||||
v-model:visible="isShowEditModal"
|
||||
:editModalLoading="editModalLoading"
|
||||
:detailData="detailData"
|
||||
:isDetail="isDetail"
|
||||
@handleReset="handleReset"
|
||||
/>
|
||||
</template>
|
||||
<!-- 赴校信息管理菜单 -->
|
||||
<script setup lang="ts" name="Toschoolinfomanage">
|
||||
import {
|
||||
addOrEditApi,
|
||||
getPageListApi,
|
||||
getSchoolBusinessDetailApi,
|
||||
deleteSchoolBusinessApi,
|
||||
getSchoolBusinessPeopleListApi
|
||||
} from "@/api/toschoolinfomanage";
|
||||
import { getSchoolData } from "@/api/userCenter";
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { ElMessage } from "element-plus";
|
||||
import AddModal from "./addModal.vue";
|
||||
import EditModal from "./editModal.vue";
|
||||
import { message } from "@/utils/message";
|
||||
interface TableItem {
|
||||
id: number;
|
||||
school: string;
|
||||
grade: string;
|
||||
people: string;
|
||||
times: string; // YYYY-MM-DD
|
||||
feedbackTotals: number;
|
||||
solveTotals: number;
|
||||
solutionEnd: boolean; // true: 已完结, false: 跟进中
|
||||
lastTime: string; // YYYY-MM-DD
|
||||
}
|
||||
|
||||
const schoolOptions = ref([]);
|
||||
const peopleOptions = ref([]);
|
||||
const isDetail = ref(false);
|
||||
/**
|
||||
* 获取学校下拉数据
|
||||
*/
|
||||
const getSchoolDataFn = () => {
|
||||
getSchoolData().then(res => {
|
||||
if (res.code == 200) {
|
||||
schoolOptions.value = res.data.map((i: any) => ({
|
||||
label: i.text,
|
||||
value: i.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取赴校人员下拉数据
|
||||
*/
|
||||
const getSchoolBusinessPeopleList = () => {
|
||||
getSchoolBusinessPeopleListApi({}).then((res: any) => {
|
||||
if (res.code == 200) {
|
||||
peopleOptions.value = (res.data || []).map(i => {
|
||||
return {
|
||||
value: i.text,
|
||||
text: i.text
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const isShowAddModal = ref(false);
|
||||
const isShowEditModal = ref(false);
|
||||
const editModalLoading = ref(false);
|
||||
const detailData = ref({});
|
||||
onMounted(() => {
|
||||
// addOrEdit();
|
||||
loadList();
|
||||
getSchoolDataFn();
|
||||
getSchoolBusinessPeopleList();
|
||||
});
|
||||
|
||||
const gradeOptions = [
|
||||
{ label: "初一", value: "初一" },
|
||||
{ label: "初二", value: "初二" },
|
||||
{ label: "初三", value: "初三" },
|
||||
{ label: "高一", value: "高一" },
|
||||
{ label: "高二", value: "高二" },
|
||||
{ label: "高三", value: "高三" }
|
||||
];
|
||||
/**
|
||||
* 新建赴校信息提交
|
||||
*/
|
||||
const addOrEdit = () => {
|
||||
addOrEditApi({
|
||||
id: 0, //id传0代表新增
|
||||
schoolId: 10079,
|
||||
schoolName: "系统测试学校",
|
||||
grade: "初二",
|
||||
gradeLevel: "",
|
||||
schoolBusinessUser: ["刘德华123"],
|
||||
startTime: "2025-07-24T07:28:38",
|
||||
// remark: "string",
|
||||
feedbackQuestions: [
|
||||
{
|
||||
question: "xb测试反馈问题1(双师课堂)",
|
||||
questionType: 10,
|
||||
sort: "1111111111"
|
||||
}
|
||||
// {
|
||||
// question: "xb测试反馈问题2(设备)",
|
||||
// questionType: 15,
|
||||
// sort: "2"
|
||||
// },
|
||||
// {
|
||||
// question: "xb测试反馈问题2(学生)",
|
||||
// questionType: 20,
|
||||
// sort: "3"
|
||||
// }
|
||||
],
|
||||
isDiscussion: true,
|
||||
discussion: "开展座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈",
|
||||
isClassMeeting: true,
|
||||
classMeeting: "班会情况班会情况班会情况班会情况班会情况班会情况班会情况"
|
||||
});
|
||||
};
|
||||
|
||||
const query = reactive({
|
||||
school: "" as string | undefined,
|
||||
grade: "" as string | undefined,
|
||||
people: "" as string | undefined,
|
||||
solutionEnd: undefined,
|
||||
times: [] as string[]
|
||||
});
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
const listData = ref<TableItem[]>([]);
|
||||
|
||||
function mapApiItemToRow(item: any): TableItem {
|
||||
const peopleArr = Array.isArray(item.schoolBusinessUser)
|
||||
? item.schoolBusinessUser
|
||||
: [];
|
||||
const start = item.startTime
|
||||
? dayjs(item.startTime).format("YYYY-MM-DD")
|
||||
: "";
|
||||
let last = start;
|
||||
const rec = item.solutionRecord?.record || [];
|
||||
if (Array.isArray(rec) && rec.length > 0) {
|
||||
const maxTs = rec
|
||||
.map((r: any) => r.executionTime)
|
||||
.filter(Boolean)
|
||||
.map((t: string) => dayjs(t).valueOf())
|
||||
.reduce((a: number, b: number) => Math.max(a, b), 0);
|
||||
if (maxTs) last = dayjs(maxTs).format("YYYY-MM-DD");
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
school: item.schoolName || "",
|
||||
grade: item.grade || "",
|
||||
people: peopleArr.join(","),
|
||||
times: start,
|
||||
feedbackTotals: Number(item.feedbackCount) || 0,
|
||||
solveTotals: Number(item.solveFeedbackCount) || 0,
|
||||
solutionEnd: item.solutionEnd,
|
||||
lastTime: last
|
||||
};
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
const payload: any = {
|
||||
pageIndex: page.value,
|
||||
pageSize: pageSize.value,
|
||||
orderBy: "startTime"
|
||||
};
|
||||
if (query.school) payload.schoolId = query.school;
|
||||
if (query.grade) payload.grade = query.grade;
|
||||
if (query.people) {
|
||||
payload.UserName = query.people;
|
||||
}
|
||||
if (typeof query.solutionEnd !== "undefined")
|
||||
payload.solutionEnd = query.solutionEnd;
|
||||
if (Array.isArray(query.times) && query.times.length === 2) {
|
||||
payload.startTime = query.times[0];
|
||||
payload.endTime = query.times[1];
|
||||
}
|
||||
try {
|
||||
const res = await getPageListApi(payload);
|
||||
if (res.code === 200) {
|
||||
const rows = Array.isArray(res.data?.data) ? res.data.data : [];
|
||||
listData.value = rows.map(mapApiItemToRow);
|
||||
total.value = Number(res.data?.total) || rows.length;
|
||||
} else {
|
||||
ElMessage.error(res.message || "加载失败");
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error("列表加载失败");
|
||||
}
|
||||
}
|
||||
|
||||
function rand(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function randomDate(start: string, end: string) {
|
||||
const startTs = dayjs(start).valueOf();
|
||||
const endTs = dayjs(end).valueOf();
|
||||
const v = rand(startTs, endTs);
|
||||
return dayjs(v).format("YYYY-MM-DD");
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
page.value = 1;
|
||||
loadList();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
query.school = "";
|
||||
query.grade = "";
|
||||
query.people = "";
|
||||
query.solutionEnd = undefined;
|
||||
query.times = [];
|
||||
page.value = 1;
|
||||
loadList();
|
||||
}
|
||||
|
||||
function handlePageChange(p: number) {
|
||||
page.value = p;
|
||||
loadList();
|
||||
}
|
||||
|
||||
function handleSizeChange(s: number) {
|
||||
pageSize.value = s;
|
||||
page.value = 1;
|
||||
loadList();
|
||||
}
|
||||
|
||||
function onDelete(row: TableItem) {
|
||||
console.log(`删除`, row);
|
||||
deleteSchoolBusinessApi([row.id]).then(res => {
|
||||
if (res.code === 200) {
|
||||
message("删除成功", { type: "success" });
|
||||
loadList();
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 详情或者跟进
|
||||
* @param row
|
||||
*/
|
||||
function onDetailOrFollow(row: TableItem, disabled = false) {
|
||||
isDetail.value = disabled;
|
||||
isShowEditModal.value = true;
|
||||
editModalLoading.value = true;
|
||||
getSchoolBusinessDetailApi(row.id)
|
||||
.then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
detailData.value = res.data;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
editModalLoading.value = false;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 跟进
|
||||
* @param row
|
||||
*/
|
||||
// function onFollow(row: TableItem) {
|
||||
// isShowEditModal.value = true;
|
||||
// editModalLoading.value = true;
|
||||
// console.log(`跟进`);
|
||||
// getSchoolBusinessDetailApi(row.id)
|
||||
// .then(res => {
|
||||
// if (res.code === 200 && res.data) {
|
||||
// detailData.value = res.data;
|
||||
// }
|
||||
// })
|
||||
// .finally(() => {
|
||||
// editModalLoading.value = false;
|
||||
// });
|
||||
// }
|
||||
|
||||
function handleAdd() {
|
||||
console.log("新建");
|
||||
isShowAddModal.value = true;
|
||||
}
|
||||
function handleImport() {
|
||||
console.log("批量导入");
|
||||
}
|
||||
function handleExport() {
|
||||
console.log("导出");
|
||||
}
|
||||
function downLoadTpl() {
|
||||
console.log("下载模版");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-form {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue