dev #8

Merged
hy merged 45 commits from dev into master 2025-08-26 19:03:29 +08:00
13 changed files with 488 additions and 276 deletions
Showing only changes of commit 9f8da9c38a - Show all commits

View File

@ -37,3 +37,13 @@ export function Edit(info: MenuItem) {
export function Del(ids: number[]) { export function Del(ids: number[]) {
return http.request<Res<MenuItem[]>>("post", `Menu/Del`, { data: ids }); return http.request<Res<MenuItem[]>>("post", `Menu/Del`, { data: ids });
} }
/**获取角色的菜单 */
export function RoleMenu(roleId: number) {
return http.request<Res<number[]>>("get", `MenuRelation/RoleMenu?roleId=${roleId}`);
}
/**修改角色菜单 */
export function SetMenu(data: { roleId: number; menuId: number[] }) {
return http.request<Res<MenuItem[]>>("post", `MenuRelation/SetMenu`, {
data: data
});
}

View File

@ -56,3 +56,24 @@ export function getSchoolBusinessPeopleListApi(data: object) {
data data
}); });
} }
/**
* @description excel
* @return {object}
*/
export function importExcel(file: File) {
let formData = new FormData();
formData.append("file", file);
return http.request<any>(
"post",
`SchoolBusiness/Import`,
{
data: formData
},
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
responseType: "blob"
}
);
}

View File

@ -19,7 +19,7 @@ const {
userName, userName,
userAvatar, userAvatar,
avatarsStyle, avatarsStyle,
toggleSideBar toggleSideBar,
} = useNav(); } = useNav();
</script> </script>
@ -45,7 +45,7 @@ const {
<!-- 全屏 --> <!-- 全屏 -->
<LaySidebarFullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
<LayNotice id="header-notice" /> <LayNotice id="header-notice" v-show="false" />
<!-- 退出登录 --> <!-- 退出登录 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none"> <span class="el-dropdown-link navbar-bg-hover select-none">
@ -55,20 +55,13 @@ const {
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="logout"> <el-dropdown-menu class="logout">
<el-dropdown-item @click="logout"> <el-dropdown-item @click="logout">
<IconifyIconOffline <IconifyIconOffline :icon="LogoutCircleRLine" style="margin: 5px" />
:icon="LogoutCircleRLine"
style="margin: 5px"
/>
退出系统 退出系统
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<span <span class="set-icon navbar-bg-hover" title="打开系统配置" @click="onPanel">
class="set-icon navbar-bg-hover"
title="打开系统配置"
@click="onPanel"
>
<IconifyIconOffline :icon="Setting" /> <IconifyIconOffline :icon="Setting" />
</span> </span>
</div> </div>

View File

@ -8,10 +8,9 @@ const noticesNum = ref(0);
const notices = ref(noticesData); const notices = ref(noticesData);
const activeKey = ref(noticesData[0]?.key); const activeKey = ref(noticesData[0]?.key);
notices.value.map(v => (noticesNum.value += v.list.length)); notices.value.map((v) => (noticesNum.value += v.list.length));
const getLabel = computed( const getLabel = computed(() => (item) =>
() => item =>
item.name + (item.list.length > 0 ? `(${item.list.length})` : "") item.name + (item.list.length > 0 ? `(${item.list.length})` : "")
); );
</script> </script>
@ -23,7 +22,7 @@ const getLabel = computed(
'dropdown-badge', 'dropdown-badge',
'navbar-bg-hover', 'navbar-bg-hover',
'select-none', 'select-none',
Number(noticesNum) !== 0 && 'mr-[10px]' Number(noticesNum) !== 0 && 'mr-[10px]',
]" ]"
> >
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99"> <el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
@ -40,11 +39,7 @@ const getLabel = computed(
class="dropdown-tabs" class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }" :style="{ width: notices.length === 0 ? '200px' : '330px' }"
> >
<el-empty <el-empty v-if="notices.length === 0" description="暂无消息" :image-size="60" />
v-if="notices.length === 0"
description="暂无消息"
:image-size="60"
/>
<span v-else> <span v-else>
<template v-for="item in notices" :key="item.key"> <template v-for="item in notices" :key="item.key">
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`"> <el-tab-pane :label="getLabel(item)" :name="`${item.key}`">

View File

@ -4,16 +4,11 @@ import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref, defineOptions } from "vue"; import { onMounted, ref, defineOptions } from "vue";
import { fa } from "element-plus/es/locales.mjs"; import { fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable"; import { hTableAPI } from "@/api/hTable";
import { import { ruleAccount, rulePassword, rulePhone, ruleRequired } from "@/utils/rules";
ruleAccount,
rulePassword,
rulePhone,
ruleRequired
} from "@/utils/rules";
const ControllerName = "Admin"; const ControllerName = "Admin";
defineOptions({ defineOptions({
name: ControllerName name: ControllerName,
}); });
function searchCallback(data) { function searchCallback(data) {
@ -40,7 +35,7 @@ const tableData: TableConfig = {
PageSize: 20, PageSize: 20,
OrderBy: "CreateTime", // OrderBy: "CreateTime", //
defaultConditions: [], // defaultConditions: [], //
Conditions: [] Conditions: [],
}, },
operationColumn: true, // operationColumn: true, //
operationColumnData: [ operationColumnData: [
@ -48,22 +43,22 @@ const tableData: TableConfig = {
// //
topBtn: false, // topBtn: false, //
label: "修改", label: "修改",
btnType: "edit" // add edit del custom btnType: "edit", // add edit del custom
}, },
{ {
// //
topBtn: true, // topBtn: true, //
label: "添加", label: "添加",
btnStyle: "success", btnStyle: "success",
btnType: "add" // add edit del custom btnType: "add", // add edit del custom
}, },
{ {
topBtn: false, // topBtn: false, //
show: true, show: true,
label: "删除", label: "删除",
btnType: "del", // add edit del btnType: "del", // add edit del
btnStyle: "danger" // topBtn: true success danger btnStyle: "danger", // topBtn: true success danger
} },
], ],
column: { column: {
// //
@ -72,7 +67,7 @@ const tableData: TableConfig = {
search: true, search: true,
add: false, // add: false, //
edit: false, // edit: false, //
width: "150px" width: "150px",
}, },
name: { name: {
label: "名称", label: "名称",
@ -81,22 +76,22 @@ const tableData: TableConfig = {
search: true, search: true,
searchType: ConditionalType.Like, searchType: ConditionalType.Like,
add: true, // add: true, //
edit: true // edit: true, //
}, },
Phone: { phone: {
label: "手机号", label: "手机号",
rules: rulePhone, rules: rulePhone,
width: "200px", width: "200px",
search: true, search: true,
add: true, // add: true, //
edit: true // edit: true, //
}, },
account: { account: {
label: "账号", label: "账号",
rules: ruleAccount, rules: ruleAccount,
search: true, search: true,
add: true, // add: true, //
edit: false // edit: false, //
}, },
password: { password: {
label: "密码", label: "密码",
@ -105,7 +100,7 @@ const tableData: TableConfig = {
rules: rulePassword, rules: rulePassword,
search: false, search: false,
add: true, // add: true, //
edit: false // edit: false, //
}, },
enable: { enable: {
label: "启用", label: "启用",
@ -114,7 +109,7 @@ const tableData: TableConfig = {
add: true, // add: true, //
edit: true, // edit: true, //
valueE: true // valueE: true, //
}, },
roleId: { roleId: {
label: "角色", label: "角色",
@ -124,15 +119,15 @@ const tableData: TableConfig = {
add: true, // add: true, //
edit: false, // edit: false, //
setting: { setting: {
datasource: [] datasource: [],
} },
} },
}, },
data: [], data: [],
pageData: { pageData: {
total: 0 total: 0,
}, },
selectRows: [] selectRows: [],
}; };
const showTable = ref(false); const showTable = ref(false);

View File

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import ahTable from "@/components/hTable/index.vue"; import ahTable from "@/components/hTable/index.vue";
import { ConditionalType, TableConfig } from "@/components/hTable/hTable"; import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref,defineOptions } from "vue"; import { onMounted, ref, defineOptions } from "vue";
import { fa } from "element-plus/es/locales.mjs"; import { fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable"; import { hTableAPI } from "@/api/hTable";
const ControllerName = "AdminRole"; const ControllerName = "AdminRole";
defineOptions({ defineOptions({
name: ControllerName name: ControllerName,
}); });
function searchCallback(data) {} function searchCallback(data) {}
@ -24,7 +24,7 @@ const tableData: TableConfig = {
PageSize: 20, PageSize: 20,
OrderBy: "CreateTime", // OrderBy: "CreateTime", //
defaultConditions: [], // defaultConditions: [], //
Conditions: [] Conditions: [],
}, },
operationColumn: true, // operationColumn: true, //
operationColumnData: [ operationColumnData: [
@ -34,28 +34,35 @@ const tableData: TableConfig = {
label: "角色授权", label: "角色授权",
perms: "角色授权", // perms: "角色授权", //
btnType: "custom", // add edit del btnType: "custom", // add edit del
btnStyle: "success" // topBtn: true success danger btnStyle: "success", // topBtn: true success danger
custom: {
// custom
title: "分配权限", // title
src: "menu/index", //
width: "1200px", //
height: "800px", //
},
}, },
{ {
// //
topBtn: false, // topBtn: false, //
label: "修改", label: "修改",
btnType: "edit" // add edit del custom btnType: "edit", // add edit del custom
}, },
{ {
// //
topBtn: true, // topBtn: true, //
label: "添加", label: "添加",
btnStyle: "success", btnStyle: "success",
btnType: "add" // add edit del custom btnType: "add", // add edit del custom
}, },
{ {
topBtn: false, // topBtn: false, //
show: true, show: true,
label: "删除", label: "删除",
btnType: "del", // add edit del btnType: "del", // add edit del
btnStyle: "danger" // topBtn: true success danger btnStyle: "danger", // topBtn: true success danger
} },
], ],
column: { column: {
// //
@ -64,7 +71,7 @@ const tableData: TableConfig = {
search: true, search: true,
add: false, // add: false, //
edit: false, // edit: false, //
width: "150px" width: "150px",
}, },
name: { name: {
label: "角色名称", label: "角色名称",
@ -72,7 +79,7 @@ const tableData: TableConfig = {
search: true, search: true,
searchType: ConditionalType.Like, searchType: ConditionalType.Like,
add: true, // add: true, //
edit: true // edit: true, //
}, },
enable: { enable: {
label: "启用", label: "启用",
@ -80,14 +87,14 @@ const tableData: TableConfig = {
search: false, search: false,
add: true, // add: true, //
edit: true, // edit: true, //
valueE: true // valueE: true, //
}, },
createTime: { createTime: {
label: "创建时间", label: "创建时间",
type: "datetime", type: "datetime",
search: true, search: true,
add: false, // add: false, //
edit: false // edit: false, //
}, },
remark: { remark: {
label: "备注", label: "备注",
@ -95,14 +102,14 @@ const tableData: TableConfig = {
editRows: 3, editRows: 3,
search: false, search: false,
add: true, // add: true, //
edit: true // edit: true, //
} },
}, },
data: [], data: [],
pageData: { pageData: {
total: 0 total: 0,
}, },
selectRows: [] selectRows: [],
}; };
const showTable = ref(false); const showTable = ref(false);

View File

@ -43,7 +43,7 @@ const tableData: TableConfig = {
btnStyle: "primary", btnStyle: "primary",
custom: { custom: {
title: "考试学生班级详情", // title title: "考试学生班级详情", // title
src: "exam/userDetails", // src: "exam/classExamRecord", //
width: "1600px", // width: "1600px", //
height: "800px", // height: "800px", //
}, },

View File

@ -0,0 +1,164 @@
<script setup lang="ts">
import ahTable from "@/components/hTable/index.vue";
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref, defineOptions } from "vue";
import { fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable";
import { getenum } from "@/api/enum";
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
import { DeleteExamInfo, ImportExamInfo } from "@/api/exam";
import { entryExamInfo } from "./examFun";
import { ElMessage, ElMessageBox } from "element-plus";
import { average } from "@pureadmin/utils";
const ControllerName = "ClassExamRecord";
defineOptions({
name: ControllerName,
});
const props = defineProps<{
data: any;
}>();
function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = {
apiUrl: `ExamClassInfo`,
selectColumn: false, //
border: false, //
searchCallback: searchCallback,
search: {
//
show: true,
PageIndex: 0,
PageSize: 20,
OrderByType: 1, //
OrderBy: "Id", //
defaultConditions: [
{
FieldName: "ClassId",
FieldValue: props.data[0].classId + "",
ConditionalType: ConditionalType.Equal,
},
], //
Conditions: [],
},
operationColumn: true, //
operationColumnData: [
{
topBtn: false, //
show: true,
label: "学生成绩详情",
btnType: "custom",
btnStyle: "primary",
custom: {
title: "考试学生班级详情", // title
src: "exam/userDetails", //
width: "1600px", //
height: "880px", //
},
},
],
column: {
//
examName: {
label: "考试名称",
search: true,
width: "150px",
},
type: {
label: "考试类型",
search: true,
type: "dropdown",
setting: {},
width: "80px",
},
testPaperType: {
label: "试卷类型",
search: true,
type: "dropdown",
setting: {},
width: "80px",
},
grade: {
label: "年级",
search: true,
type: "dropdown",
setting: {},
width: "60px",
},
onLineCount: {
label: "重本人数",
search: false,
width: "80px",
},
onLineRate: {
label: "重本率",
search: false,
custom: (row) => `${Math.round(row.onLineRate * 100)}%`,
width: "80px",
},
onLineRanking: {
label: "重本率排名",
search: false,
width: "100px",
},
maxScore: {
label: "最高分[赋分]",
search: false,
width: "140px",
},
minScore: {
label: "最低分[赋分]",
search: false,
width: "140px",
},
average: {
label: "总平均分[赋分]",
search: false,
custom: (row) => `${Math.round(row.average)}`,
width: "140px",
},
average1: {
label: "资源校平均分[赋分]",
search: false,
width: "160px",
},
averageRank: {
label: "总平均分排名",
search: false,
width: "110px",
},
rank: {
label: "远端平均/资源校平均",
search: false,
width: "95px",
custom: (row) =>
`${row.average1 == 0 ? "--" : Math.round((row.average / row.average1) * 100)}%`,
},
},
data: [],
pageData: {
total: 0,
},
selectRows: [],
};
const showTable = ref(false);
onMounted(async () => {
//
tableData.column.grade.setting.datasource = (await getenum("GradeEnum")).data;
tableData.column.testPaperType.setting.datasource = (
await getenum("TestPaperTypeEnum")
).data;
tableData.column.type.setting.datasource = (await getenum("ExamTypeEnum")).data;
showTable.value = true;
});
</script>
<template>
<div><ahTable v-if="showTable" ref="table" :tableConfig="tableData" /></div>
</template>

View File

@ -1,32 +1,34 @@
<template> <template>
<div> <div>
<div style="padding-bottom: 5px"> <div style="padding-bottom: 5px">
<el-button type="success" @click="() => showDialog(null)"> <el-button type="success" v-show="!isAuthorized" @click="() => showDialog(null)">
新增根菜单 新增根菜单
</el-button> </el-button>
<el-button type="primary"> 分配权限 </el-button> <el-button type="primary" v-show="isAuthorized" @click="callBack">
分配权限
</el-button>
</div> </div>
<el-tree <el-tree
:data="treeData" :data="treeData"
:props="treeProps" :props="treeProps"
:default-checked-keys="defaultCheckedKeys"
node-key="id" node-key="id"
:default-expand-all="true" :default-expand-all="true"
:highlight-current="true" :highlight-current="true"
:expand-on-click-node="false" :expand-on-click-node="false"
show-checkbox show-checkbox
class="menu-tree" ref="treeRef"
:class="isAuthorized ? `menu-tree menu-tree1` : `menu-tree`"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div v-if="!data.isButton" class="menu-node"> <div v-if="!data.isButton" class="menu-node">
<div class="menu-node"> <div class="menu-node">
<i v-if="data.icon" :class="data.icon" class="menu-icon"></i> <i v-if="data.icon" :class="data.icon" class="menu-icon"></i>
<span class="menu-title">{{ data.title }}</span> <span class="menu-title">{{ data.title }}</span>
<span class="menu-path-Rank" v-if="data.rank" <span class="menu-path-Rank" v-if="data.rank">排序[{{ data.rank }}]</span>
>排序[{{ data.rank }}]</span
>
<span class="menu-path" v-if="data.path">{{ data.path }}</span> <span class="menu-path" v-if="data.path">{{ data.path }}</span>
</div> </div>
<div style="display: flex; gap: 6px"> <div style="display: flex; gap: 6px" v-show="!isAuthorized">
<el-button <el-button
type="success" type="success"
link link
@ -50,7 +52,7 @@
{{ data.title }} {{ data.title }}
</el-button> </el-button>
</div> </div>
<div style="display: flex; gap: 6px"> <div style="display: flex; gap: 6px" v-show="!isAuthorized">
<el-button type="primary" link @click="() => showDialog(data)"> <el-button type="primary" link @click="() => showDialog(data)">
编辑 编辑
</el-button> </el-button>
@ -78,19 +80,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { MenuAll, MenuItem, Del } from "@/api/menu"; import { MenuAll, MenuItem, Del, RoleMenu, SetMenu } from "@/api/menu";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import MenuEdit from "./edit.vue"; import MenuEdit from "./edit.vue";
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
defineOptions({ defineOptions({
name: "Menu" name: "Menu",
}); });
const props = defineProps<{
data: any;
}>();
const isAuthorized = props.data != null && props.data.length > 0;
const treeRef = ref();
// ID
const defaultCheckedKeys = ref([0]);
/** 显示弹窗 */ /** 显示弹窗 */
const dialogData = ref({ const dialogData = ref({
visible: false, visible: false,
info: null info: null,
}); });
function showDialog(info: MenuItem) { function showDialog(info: MenuItem) {
dialogData.value.visible = true; dialogData.value.visible = true;
@ -110,13 +120,36 @@ const handleClose = (done: () => void) => {
// // catch error // // catch error
// }); // });
}; };
async function callBack() {
//
const checkedNodes = treeRef.value.getCheckedNodes();
//
const halfCheckedNodes = treeRef.value.getHalfCheckedNodes();
//
SetMenu({
roleId: props.data[0].id,
menuId: checkedNodes
.map((node: MenuItem) => node.id)
.concat(halfCheckedNodes.map((node: MenuItem) => node.id)),
})
.then((res) => {
if (res.code === 200) {
ElMessage.success("分配权限成功");
} else {
ElMessage.error(res.message);
}
})
.catch((error) => {
ElMessage.error("分配权限失败");
});
}
async function delMenu(menuId: number) { async function delMenu(menuId: number) {
try { try {
await ElMessageBox.confirm("确定要删除此菜单?", "提示", { await ElMessageBox.confirm("确定要删除此菜单?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning" type: "warning",
}); });
const res = await Del([menuId]); const res = await Del([menuId]);
if (res.code === 200) { if (res.code === 200) {
@ -134,12 +167,12 @@ const convertToTree = (menus: MenuItem[]): MenuItem[] => {
const tree: MenuItem[] = []; const tree: MenuItem[] = [];
// children // children
menus.forEach(menu => { menus.forEach((menu) => {
menuMap.set(menu.id, { ...menu, children: [] }); menuMap.set(menu.id, { ...menu, children: [] });
}); });
// //
menuMap.forEach(menu => { menuMap.forEach((menu) => {
if (menu.parentId === 0) { if (menu.parentId === 0) {
tree.push(menu); tree.push(menu);
} else { } else {
@ -153,7 +186,7 @@ const convertToTree = (menus: MenuItem[]): MenuItem[] => {
// //
const sortChildren = (nodes: MenuItem[]) => { const sortChildren = (nodes: MenuItem[]) => {
nodes.sort((a, b) => a.rank - b.rank); nodes.sort((a, b) => a.rank - b.rank);
nodes.forEach(node => { nodes.forEach((node) => {
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
sortChildren(node.children); sortChildren(node.children);
} }
@ -170,7 +203,7 @@ const treeData = ref<MenuItem[]>([]);
// //
const treeProps = { const treeProps = {
label: "title", label: "title",
children: "children" children: "children",
}; };
async function fetchInitData() { async function fetchInitData() {
const flatData = await MenuAll(); const flatData = await MenuAll();
@ -179,6 +212,7 @@ async function fetchInitData() {
} else { } else {
ElMessage.error(flatData.message); ElMessage.error(flatData.message);
} }
if (isAuthorized) defaultCheckedKeys.value = (await RoleMenu(props.data[0].id)).data;
} }
onMounted(async () => { onMounted(async () => {
await fetchInitData(); await fetchInitData();
@ -190,6 +224,11 @@ onMounted(async () => {
padding: 15px; padding: 15px;
background: #fff; background: #fff;
border-radius: 4px; border-radius: 4px;
max-height: calc(88vh - 48px - 42px);
overflow-y: auto;
}
.menu-tree1 {
max-height: calc(88vh - 120px);
} }
.menu-node { .menu-node {

View File

@ -25,12 +25,7 @@
</el-form-item> --> </el-form-item> -->
<el-form-item v-show="search.userType === 1" style="width: 100px"> <el-form-item v-show="search.userType === 1" style="width: 100px">
<el-select <el-select v-model="search.level" placeholder="学生层次" clearable filterable>
v-model="search.level"
placeholder="学生层次"
clearable
filterable
>
<el-option <el-option
v-for="item in userLevelList" v-for="item in userLevelList"
:key="item.value" :key="item.value"
@ -78,12 +73,7 @@
</el-form-item> </el-form-item>
<el-form-item style="width: 100px"> <el-form-item style="width: 100px">
<el-select <el-select v-model="search.classId" placeholder="班级" clearable filterable>
v-model="search.classId"
placeholder="班级"
clearable
filterable
>
<el-option <el-option
v-for="item in classList" v-for="item in classList"
:key="item.value" :key="item.value"
@ -95,12 +85,7 @@
</el-form-item> </el-form-item>
<el-form-item style="width: 100px"> <el-form-item style="width: 100px">
<el-select <el-select v-model="search.subjectId" placeholder="科目" clearable filterable>
v-model="search.subjectId"
placeholder="科目"
clearable
filterable
>
<el-option <el-option
v-for="item in subjectList" v-for="item in subjectList"
:key="item.value" :key="item.value"
@ -117,10 +102,7 @@
> >
</el-form-item> </el-form-item>
<el-form-item v-show="selectUser"> <el-form-item v-show="selectUser">
<el-button <el-button type="success" @click="selectUserCallBack()" icon="el-icon-check"
type="success"
@click="selectUserCallBack()"
icon="el-icon-check"
>选择用户</el-button >选择用户</el-button
> >
</el-form-item> </el-form-item>
@ -161,13 +143,9 @@
<el-table-column prop="id" label="用户Id" width="100" /> <el-table-column prop="id" label="用户Id" width="100" />
<el-table-column label="用户信息" width="200"> <el-table-column label="用户信息" width="200">
<template #default="scope"> <template #default="scope">
<el-tag <el-tag :type="getUserTypeTag(scope.row.userType)" style="margin-right: 5px">{{
:type="getUserTypeTag(scope.row.userType)" userTypeList.find((s) => s.value == scope.row.userType)?.text
style="margin-right: 5px" }}</el-tag>
>{{
userTypeList.find(s => s.value == scope.row.userType)?.text
}}</el-tag
>
<span>{{ scope.row.realName }} </span> <span>{{ scope.row.realName }} </span>
</template> </template>
</el-table-column> </el-table-column>
@ -183,9 +161,7 @@
<div <div
v-for="(position, index) in scope.row.positions" v-for="(position, index) in scope.row.positions"
:key="'Position' + index" :key="'Position' + index"
v-show=" v-show="index < 3 || (index >= 3 && showAllPosition.includes(scope.row))"
index < 3 || (index >= 3 && showAllPosition.includes(scope.row))
"
> >
<div v-if="position.enable === false"> <div v-if="position.enable === false">
<el-tag type="info">{{ position.schoolName || "-" }}</el-tag> <el-tag type="info">{{ position.schoolName || "-" }}</el-tag>
@ -209,9 +185,7 @@
</div> </div>
</div> </div>
<div <div
v-if=" v-if="scope.row.positions != undefined && scope.row.positions.length > 3"
scope.row.positions != undefined && scope.row.positions.length > 3
"
@click="showPosition(scope.row)" @click="showPosition(scope.row)"
class="userTagRow" class="userTagRow"
> >
@ -227,6 +201,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination
style="display: flex; justify-content: center"
@size-change="pageSizeChange" @size-change="pageSizeChange"
@current-change="pageIndexChange" @current-change="pageIndexChange"
:current-page="pagination.total + 1" :current-page="pagination.total + 1"
@ -264,7 +239,7 @@ import {
getSubjectData, getSubjectData,
getPageUserList, getPageUserList,
UserDetail, UserDetail,
Position Position,
} from "@/api/userCenter"; } from "@/api/userCenter";
import { getenum } from "@/api/enum"; import { getenum } from "@/api/enum";
import { hTableAPI } from "@/api/hTable"; import { hTableAPI } from "@/api/hTable";
@ -276,7 +251,7 @@ import {
Message, Message,
ArrowDownBold, ArrowDownBold,
Search, Search,
Star Star,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel } from "@/components/hTable/hTable";
@ -344,20 +319,20 @@ interface DialogData {
const props = defineProps({ const props = defineProps({
selectUser: { selectUser: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
selectCallBack: { selectCallBack: {
type: Function, type: Function,
default: () => {} default: () => {},
}, },
maxTableHeight: { maxTableHeight: {
type: Number, type: Number,
default: 580 default: 580,
}, },
searchData: { searchData: {
type: Object as () => SearchParams, type: Object as () => SearchParams,
default: undefined default: undefined,
} },
}); });
const baseUrl = import.meta.env.VITE_APP_BASE_API; const baseUrl = import.meta.env.VITE_APP_BASE_API;
@ -378,13 +353,13 @@ const search = reactive<SearchParams>({
grade: "", grade: "",
classId: "", classId: "",
subjectId: "", subjectId: "",
positionId: "" positionId: "",
}); });
const userTypeList = ref<ComboModel[]>([ const userTypeList = ref<ComboModel[]>([
{ value: 1, text: "学生" }, { value: 1, text: "学生" },
{ value: 2, text: "教师" }, { value: 2, text: "教师" },
{ value: 3, text: "管理员" } { value: 3, text: "管理员" },
]); ]);
const userLevelList = ref<ComboModel[]>([]); const userLevelList = ref<ComboModel[]>([]);
@ -395,7 +370,7 @@ const gradeList = ref<ComboModel[]>([
{ value: "初三", text: "初三" }, { value: "初三", text: "初三" },
{ value: "高一", text: "高一" }, { value: "高一", text: "高一" },
{ value: "高二", text: "高二" }, { value: "高二", text: "高二" },
{ value: "高三", text: "高三" } { value: "高三", text: "高三" },
]); ]);
const classList = ref<ComboModel[]>([]); const classList = ref<ComboModel[]>([]);
const subjectList = ref<ComboModel[]>([]); const subjectList = ref<ComboModel[]>([]);
@ -405,13 +380,13 @@ const table = reactive<TableData>({
data: [], data: [],
selectRows: [], selectRows: [],
sort: "", sort: "",
border: true border: true,
}); });
const pagination = reactive<PaginationData>({ const pagination = reactive<PaginationData>({
index: 1, index: 1,
size: 10, size: 10,
total: 0 total: 0,
}); });
const dialog = reactive<DialogData>({ const dialog = reactive<DialogData>({
@ -419,32 +394,32 @@ const dialog = reactive<DialogData>({
update: { update: {
title: "", title: "",
visible: false, visible: false,
width: "800px" width: "800px",
}, },
editLevel: { editLevel: {
userIds: [], userIds: [],
title: "", title: "",
visible: false, visible: false,
width: "400px" width: "400px",
}, },
editSubjectLevel: { editSubjectLevel: {
userIds: [], userIds: [],
title: "", title: "",
visible: false, visible: false,
width: "450px" width: "450px",
}, },
bindUser: { bindUser: {
title: "分配权限码", title: "分配权限码",
visible: false, visible: false,
width: "1150px", width: "1150px",
height: "" height: "",
}, },
userBindInfo: { userBindInfo: {
title: "用户权限码", title: "用户权限码",
visible: false, visible: false,
width: "1150px", width: "1150px",
height: "" height: "",
} },
}); });
const checkUserBindInfo = () => { const checkUserBindInfo = () => {
@ -507,7 +482,7 @@ const exportUser = async () => {
SubjectId: search.subjectId || 0, SubjectId: search.subjectId || 0,
PositionId: search.positionId || 0, PositionId: search.positionId || 0,
PageIndex: pagination.index, PageIndex: pagination.index,
PageSize: pagination.size PageSize: pagination.size,
}; };
// const res = await exportUserApi(data); // const res = await exportUserApi(data);
@ -559,9 +534,9 @@ const getClass = () => {
const data = { const data = {
schoolId: search.schoolId || 0, schoolId: search.schoolId || 0,
graduationYear: search.graduationYear || 0, graduationYear: search.graduationYear || 0,
grade: search.grade grade: search.grade,
}; };
getClassCombo(data).then(res => { getClassCombo(data).then((res) => {
if (res.code === 200) { if (res.code === 200) {
classList.value = res.data; classList.value = res.data;
} }
@ -581,12 +556,12 @@ const fetchPagedData = (searchUnUse = false) => {
PositionId: search.positionId || 0, PositionId: search.positionId || 0,
PageIndex: pagination.index, PageIndex: pagination.index,
PageSize: pagination.size, PageSize: pagination.size,
UnUsed: searchUnUse UnUsed: searchUnUse,
}; };
getPageUserList(data).then(res => { getPageUserList(data).then((res) => {
if (res.code === 200) { if (res.code === 200) {
pagination.total = res.data.total; pagination.total = res.data.total;
res.data.data.forEach(item => { res.data.data.forEach((item) => {
if (item.positions) { if (item.positions) {
item.positions = PositionsSort(item.positions); item.positions = PositionsSort(item.positions);
} }
@ -666,7 +641,7 @@ const getUserLevelTag = (level: number) => {
}; };
const getUserLevelText = (level: number) => { const getUserLevelText = (level: number) => {
const r = userLevelList.value.filter(w => w.value === level); const r = userLevelList.value.filter((w) => w.value === level);
if (r.length > 0) { if (r.length > 0) {
return r[0].text; return r[0].text;
} }
@ -728,7 +703,7 @@ const handleEditLevel = () => {
return; return;
} }
dialog.editLevel.title = "修改学生层次"; dialog.editLevel.title = "修改学生层次";
dialog.editLevel.userIds = table.selectRows.map(w => w.id); dialog.editLevel.userIds = table.selectRows.map((w) => w.id);
dialog.editLevel.visible = true; dialog.editLevel.visible = true;
}; };
@ -743,7 +718,7 @@ const handleEditSubjectLevel = () => {
return; return;
} }
dialog.editSubjectLevel.title = "修改学生科目层次"; dialog.editSubjectLevel.title = "修改学生科目层次";
dialog.editSubjectLevel.userIds = table.selectRows.map(w => w.id); dialog.editSubjectLevel.userIds = table.selectRows.map((w) => w.id);
dialog.editSubjectLevel.visible = true; dialog.editSubjectLevel.visible = true;
}; };
@ -787,7 +762,7 @@ const importData = () => {
}; };
const readerBlob = (data: Blob): Promise<any> => { const readerBlob = (data: Blob): Promise<any> => {
return new Promise(resolve => { return new Promise((resolve) => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(data, "utf-8"); reader.readAsText(data, "utf-8");
reader.onload = function () { reader.onload = function () {

View File

@ -205,6 +205,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination
style="display: flex; justify-content: center"
@size-change="pageSizeChange" @size-change="pageSizeChange"
@current-change="pageIndexChange" @current-change="pageIndexChange"
:current-page="pagination.total + 1" :current-page="pagination.total + 1"

View File

@ -41,7 +41,7 @@
}} }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="赴校时间"> <el-descriptions-item label="赴校时间">
{{ safeDetail.startTime?.split("T")[0] }} {{ safeDetail.startTimeStr || safeDetail.startTime }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
@ -77,7 +77,7 @@
<el-tab-pane <el-tab-pane
v-for="(i, idx) in sortData(safeDetail.feedbackQuestions || [])" v-for="(i, idx) in sortData(safeDetail.feedbackQuestions || [])"
:key="idx" :key="idx"
:label="'问题' + (idx + 1) + (i.solution ? '(已解决)' : '(未解决)')" :label="'问题' + (idx + 1) + (i.solution ? '(已解决)' : '(未解决)')"
:name="idx" :name="idx"
> >
<div style="font-size: 12px; margin-bottom: 4px"> <div style="font-size: 12px; margin-bottom: 4px">
@ -87,7 +87,7 @@
{{ i.question }} {{ i.question }}
</div> </div>
<div v-if="i.solution" style="font-size: 12px; margin-top: 10px"> <div v-if="i.solution" style="font-size: 12px; margin-top: 10px">
<span> 解决时间{{ i.endTime.split("T")[0] }} </span> <span> 解决时间{{ i.endTimeStr || i.endTime }} </span>
<div style="padding: 10px; background-color: #f3f3f3"> <div style="padding: 10px; background-color: #f3f3f3">
{{ i.solution }} {{ i.solution }}
</div> </div>
@ -117,15 +117,9 @@
/> />
<el-divider /> <el-divider />
<el-descriptions title="解决方案执行跟踪记录" :column="1" border> <el-descriptions title="解决方案执行跟踪记录" :column="1" border> </el-descriptions>
</el-descriptions>
<span>需求+解决方案</span> <span>需求+解决方案</span>
<el-input <el-input v-model="solutionText" :rows="4" type="textarea" :disabled="isDetail" />
v-model="solutionText"
:rows="4"
type="textarea"
:disabled="isDetail"
/>
<!-- 添加按钮区域 --> <!-- 添加按钮区域 -->
<div style="margin-top: 5px; display: flex; gap: 20px"> <div style="margin-top: 5px; display: flex; gap: 20px">
@ -163,7 +157,7 @@
" "
> >
<div style="font-weight: bold; color: #409eff"> <div style="font-weight: bold; color: #409eff">
执行记录{{ index + 1 }}{{ record.time.split("T")[0] }} 执行记录{{ index + 1 }}{{ record.time }}
</div> </div>
<div style="margin-top: 5px; white-space: pre-wrap"> <div style="margin-top: 5px; white-space: pre-wrap">
{{ record.content }} {{ record.content }}
@ -183,7 +177,7 @@
" "
> >
<div style="font-weight: bold; color: #a69400"> <div style="font-weight: bold; color: #a69400">
完结情况{{ finishRecord.time.split("T")[0] }} 完结情况{{ finishRecord.time }}
</div> </div>
<div style="margin-top: 5px; white-space: pre-wrap"> <div style="margin-top: 5px; white-space: pre-wrap">
{{ finishRecord.content }} {{ finishRecord.content }}
@ -194,12 +188,7 @@
</el-dialog> </el-dialog>
<!-- 操作弹窗 --> <!-- 操作弹窗 -->
<el-dialog <el-dialog v-model="operationDialogVisible" title="操作" width="500px" align-center>
v-model="operationDialogVisible"
title="操作"
width="500px"
align-center
>
<el-form <el-form
ref="operationFormRef" ref="operationFormRef"
:model="operationForm" :model="operationForm"
@ -239,10 +228,7 @@ import { ref, reactive, computed, defineProps, defineEmits, watch } from "vue";
import type { FormInstance, FormRules, TabsPaneContext } from "element-plus"; import type { FormInstance, FormRules, TabsPaneContext } from "element-plus";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getSchoolData } from "@/api/userCenter"; import { getSchoolData } from "@/api/userCenter";
import { import { getSchoolBusinessPeopleListApi, addOrEditApi } from "@/api/toschoolinfomanage";
getSchoolBusinessPeopleListApi,
addOrEditApi
} from "@/api/toschoolinfomanage";
import { setFips } from "crypto"; import { setFips } from "crypto";
const activeName = ref<any>(0); const activeName = ref<any>(0);
@ -267,7 +253,7 @@ const operationDialogVisible = ref(false);
const operationType = ref(""); // markSolved, addRecord, addFinish const operationType = ref(""); // markSolved, addRecord, addFinish
const operationForm = reactive({ const operationForm = reactive({
operationTime: "", operationTime: "",
operationContent: "" operationContent: "",
}); });
const operationFormRef = ref<FormInstance>(); const operationFormRef = ref<FormInstance>();
@ -278,19 +264,19 @@ const finishRecord = ref<{ time: string; content: string } | null>(null);
// detailData.solutionRecord // detailData.solutionRecord
watch( watch(
() => props.detailData, () => props.detailData,
val => { (val) => {
const sr = (val as any)?.solutionRecord || {}; const sr = (val as any)?.solutionRecord || {};
// //
const recs = Array.isArray(sr?.record) ? sr.record : []; const recs = Array.isArray(sr?.record) ? sr.record : [];
executionRecords.value = recs.map((r: any) => ({ executionRecords.value = recs.map((r: any) => ({
time: r?.executionTime || "", time: r?.executionTimeStr || r?.executionTime || "",
content: r?.executionRecords || "" content: r?.executionRecords || "",
})); }));
// //
if (sr?.endRecordTime || sr?.endRecord) { if (sr?.endRecordTime || sr?.endRecord) {
finishRecord.value = { finishRecord.value = {
time: sr?.endRecordTime || "", time: sr?.endRecordTimeStr || "",
content: sr?.endRecord || "" content: sr?.endRecord || "",
}; };
} else { } else {
finishRecord.value = null; finishRecord.value = null;
@ -318,21 +304,19 @@ const operationContentLabel = computed(() => {
// //
const operationRules: FormRules = { const operationRules: FormRules = {
operationTime: [ operationTime: [{ required: true, message: "请选择操作时间", trigger: "change" }],
{ required: true, message: "请选择操作时间", trigger: "change" }
],
operationContent: [ operationContent: [
{ {
required: true, required: true,
message: `请输入${operationContentLabel.value}`, message: `请输入${operationContentLabel.value}`,
trigger: "blur" trigger: "blur",
} },
] ],
}; };
const dialogVisible = computed({ const dialogVisible = computed({
get: () => props.visible, get: () => props.visible,
set: v => emit("update:visible", v) set: (v) => emit("update:visible", v),
}); });
const closeModal = () => { const closeModal = () => {
@ -344,39 +328,37 @@ const queType = {
10: "双师课堂", 10: "双师课堂",
15: "设备", 15: "设备",
20: "学生", 20: "学生",
999: "其他" 999: "其他",
}; };
/** /**
* 获取未解决问题数量 * 获取未解决问题数量
* @param data * @param data
*/ */
const handleUnHandleQust = (data: Array<any>) => { const handleUnHandleQust = (data: Array<any>) => {
return (data || []).filter(i => !i?.solution).length; return (data || []).filter((i) => !i?.solution).length;
}; };
const sortData = (data: Array<any>) => { const sortData = (data: Array<any>) => {
const categorizedData = [ const categorizedData = [
...data ...data
.filter(item => item.questionType === 1) .filter((item) => item.questionType === 1)
.sort((a, b) => a.sort.localeCompare(b.sort)), .sort((a, b) => a.sort.localeCompare(b.sort)),
...data ...data
.filter(item => item.questionType === 10) .filter((item) => item.questionType === 10)
.sort((a, b) => a.sort.localeCompare(b.sort)), .sort((a, b) => a.sort.localeCompare(b.sort)),
...data ...data
.filter(item => item.questionType === 15) .filter((item) => item.questionType === 15)
.sort((a, b) => a.sort.localeCompare(b.sort)), .sort((a, b) => a.sort.localeCompare(b.sort)),
...data ...data
.filter(item => item.questionType === 20) .filter((item) => item.questionType === 20)
.sort((a, b) => a.sort.localeCompare(b.sort)), .sort((a, b) => a.sort.localeCompare(b.sort)),
...data ...data
.filter(item => item.questionType === 999) .filter((item) => item.questionType === 999)
.sort((a, b) => a.sort.localeCompare(b.sort)) .sort((a, b) => a.sort.localeCompare(b.sort)),
]; ];
return categorizedData; return categorizedData;
}; };
const safeDetail = computed(() => props.detailData || {}); const safeDetail = computed(() => props.detailData || {});
const statusText = computed(() => const statusText = computed(() => (safeDetail.value?.solutionEnd ? "已完结" : "跟进中"));
safeDetail.value?.solutionEnd ? "已完结" : "跟进中"
);
const statusType = computed(() => const statusType = computed(() =>
safeDetail.value?.solutionEnd ? "success" : "warning" safeDetail.value?.solutionEnd ? "success" : "warning"
); );
@ -387,13 +369,13 @@ const solutionText = computed({
safeDetail.value.solutionRecord = {}; safeDetail.value.solutionRecord = {};
} }
safeDetail.value.solutionRecord.solution = value; safeDetail.value.solutionRecord.solution = value;
} },
}); });
// solution // solution
const unresolvedCount = computed(() => { const unresolvedCount = computed(() => {
const list = (safeDetail.value?.feedbackQuestions as any[]) || []; const list = (safeDetail.value?.feedbackQuestions as any[]) || [];
return list.filter(item => !item?.solution).length; return list.filter((item) => !item?.solution).length;
}); });
const markTitle = (data: any) => { const markTitle = (data: any) => {
console.log("标记已解决", data); console.log("标记已解决", data);
@ -416,7 +398,7 @@ const addFinish = () => {
const sr = (props.detailData as any)?.solutionRecord; const sr = (props.detailData as any)?.solutionRecord;
// 使 finishRecord // 使 finishRecord
if (sr && (sr.endRecordTime || sr.endRecord)) { if (sr && (sr.endRecordTime || sr.endRecord)) {
operationForm.operationTime = sr.endRecordTime || ""; operationForm.operationTime = sr.endRecordTimeStr || sr.endRecordTime || "";
operationForm.operationContent = sr.endRecord || ""; operationForm.operationContent = sr.endRecord || "";
} else if (finishRecord.value) { } else if (finishRecord.value) {
operationForm.operationTime = finishRecord.value.time; operationForm.operationTime = finishRecord.value.time;
@ -455,27 +437,25 @@ const confirmOperation = async () => {
// //
executionRecords.value.push({ executionRecords.value.push({
time: operationTime, time: operationTime,
content: operationContent content: operationContent,
}); });
// solutionRecord.record // solutionRecord.record
if (!props.detailData.solutionRecord) if (!props.detailData.solutionRecord) props.detailData.solutionRecord = {} as any;
props.detailData.solutionRecord = {} as any;
if (!Array.isArray(props.detailData.solutionRecord.record)) if (!Array.isArray(props.detailData.solutionRecord.record))
props.detailData.solutionRecord.record = []; props.detailData.solutionRecord.record = [];
props.detailData.solutionRecord.record.push({ props.detailData.solutionRecord.record.push({
executionTime: operationTime, executionTime: operationTime,
executionRecords: operationContent executionRecords: operationContent,
}); });
break; break;
case "addFinish": case "addFinish":
// //
finishRecord.value = { finishRecord.value = {
time: operationTime, time: operationTime,
content: operationContent content: operationContent,
}; };
// solutionRecord.endRecordTime / endRecord // solutionRecord.endRecordTime / endRecord
if (!props.detailData.solutionRecord) if (!props.detailData.solutionRecord) props.detailData.solutionRecord = {} as any;
props.detailData.solutionRecord = {} as any;
props.detailData.solutionRecord.endRecordTime = operationTime; props.detailData.solutionRecord.endRecordTime = operationTime;
props.detailData.solutionRecord.endRecord = operationContent; props.detailData.solutionRecord.endRecord = operationContent;
break; break;
@ -491,7 +471,7 @@ const confirmOperation = async () => {
console.log("确认操作", { console.log("确认操作", {
type: operationType.value, type: operationType.value,
time: operationTime, time: operationTime,
content: operationContent content: operationContent,
}); });
closeOperationDialog(); closeOperationDialog();
} catch (error) { } catch (error) {
@ -514,7 +494,7 @@ function onClickSave() {
console.log("保存", props.detailData); console.log("保存", props.detailData);
let copyParams = JSON.parse(JSON.stringify(props.detailData)); let copyParams = JSON.parse(JSON.stringify(props.detailData));
delete copyParams.solutionEnd; delete copyParams.solutionEnd;
addOrEditApi(copyParams).then(res => { addOrEditApi(copyParams).then((res) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success("提交成功"); ElMessage.success("提交成功");
// //

View File

@ -1,7 +1,7 @@
<template> <template>
<div style="padding: 20px"> <div>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<el-form :model="query" inline label-width="80px" class="search-form"> <el-form :model="query" inline class="search-form">
<el-form-item label="学校"> <el-form-item label="学校">
<el-select <el-select
v-model="query.school" v-model="query.school"
@ -72,11 +72,14 @@
style="width: 300px" style="width: 300px"
/> />
</el-form-item> </el-form-item>
<el-form-item> <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> </el-form>
<!-- 操作按钮区域 -->
<div style="margin-bottom: 15px">
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
<!-- 操作按钮区域 --> <!-- 操作按钮区域 -->
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">
<el-button type="primary" @click="handleAdd">新建</el-button> <el-button type="primary" @click="handleAdd">新建</el-button>
@ -85,30 +88,8 @@
<el-button type="info" @click="downLoadTpl">下载模版</el-button> <el-button type="info" @click="downLoadTpl">下载模版</el-button>
</div> </div>
<!-- 表格区域 --> <!-- 表格区域 -->
<el-table :data="listData" border style="width: 100%"> <el-table :data="listData" style="width: 100%">
<el-table-column prop="school" label="学校" min-width="140" /> <el-table-column label="操作" width="200">
<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 }"> <template #default="{ row }">
<!-- <el-button size="small" type="danger" plain @click="onDelete(row)" <!-- <el-button size="small" type="danger" plain @click="onDelete(row)"
>删除</el-button >删除</el-button
@ -121,27 +102,38 @@
@confirm="onDelete(row)" @confirm="onDelete(row)"
> >
<template #reference> <template #reference>
<el-button type="danger" size="small">删除</el-button> <el-button type="danger" text size="small">删除</el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
<el-button <el-button size="small" type="primary" text @click="onDetailOrFollow(row, true)"
size="small"
type="primary"
plain
@click="onDetailOrFollow(row, true)"
>详情</el-button >详情</el-button
> >
<el-button <el-button
v-if="!row.solutionEnd" v-if="row.canOperate && !row.solutionEnd"
size="small" size="small"
type="success" type="success"
plain text
@click="onDetailOrFollow(row, false)" @click="onDetailOrFollow(row, false)"
>跟进</el-button >跟进</el-button
> >
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="school" label="学校" min-width="140" />
<el-table-column label="状态" min-width="80">
<template #default="{ row }">
<el-tag :type="row.solutionEnd ? 'success' : 'warning'">
{{ row.solutionEnd ? "已完结" : "跟进中" }}
</el-tag>
</template>
</el-table-column>
<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 prop="lastTime" label="最后跟进时间" min-width="160" /> -->
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
@ -157,7 +149,7 @@
@size-change="handleSizeChange" @size-change="handleSizeChange"
/> />
</div> </div>
</div>
<!-- 新建 --> <!-- 新建 -->
<AddModal v-model:visible="isShowAddModal" @handleReset="handleReset" /> <AddModal v-model:visible="isShowAddModal" @handleReset="handleReset" />
<!-- 跟进 --> <!-- 跟进 -->
@ -168,6 +160,7 @@
:isDetail="isDetail" :isDetail="isDetail"
@handleReset="handleReset" @handleReset="handleReset"
/> />
</div>
</template> </template>
<!-- 赴校信息管理菜单 --> <!-- 赴校信息管理菜单 -->
<script setup lang="ts" name="Toschoolinfomanage"> <script setup lang="ts" name="Toschoolinfomanage">
@ -176,13 +169,15 @@ import {
getPageListApi, getPageListApi,
getSchoolBusinessDetailApi, getSchoolBusinessDetailApi,
deleteSchoolBusinessApi, deleteSchoolBusinessApi,
getSchoolBusinessPeopleListApi getSchoolBusinessPeopleListApi,
importExcel,
} from "@/api/toschoolinfomanage"; } from "@/api/toschoolinfomanage";
import { getSchoolData } from "@/api/userCenter"; import { getSchoolData } from "@/api/userCenter";
import { ref, reactive, computed, onMounted } from "vue"; import { ref, reactive, computed, onMounted } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import AddModal from "./addModal.vue"; import AddModal from "./addModal.vue";
import { Check, Search } from "@element-plus/icons-vue";
import EditModal from "./editModal.vue"; import EditModal from "./editModal.vue";
import { message } from "@/utils/message"; import { message } from "@/utils/message";
interface TableItem { interface TableItem {
@ -190,6 +185,7 @@ interface TableItem {
school: string; school: string;
grade: string; grade: string;
people: string; people: string;
canOperate: boolean; //
times: string; // YYYY-MM-DD times: string; // YYYY-MM-DD
feedbackTotals: number; feedbackTotals: number;
solveTotals: number; solveTotals: number;
@ -204,11 +200,11 @@ const isDetail = ref(false);
* 获取学校下拉数据 * 获取学校下拉数据
*/ */
const getSchoolDataFn = () => { const getSchoolDataFn = () => {
getSchoolData().then(res => { getSchoolData().then((res) => {
if (res.code == 200) { if (res.code == 200) {
schoolOptions.value = res.data.map((i: any) => ({ schoolOptions.value = res.data.map((i: any) => ({
label: i.text, label: i.text,
value: i.value value: i.value,
})); }));
} }
}); });
@ -219,10 +215,10 @@ const getSchoolDataFn = () => {
const getSchoolBusinessPeopleList = () => { const getSchoolBusinessPeopleList = () => {
getSchoolBusinessPeopleListApi({}).then((res: any) => { getSchoolBusinessPeopleListApi({}).then((res: any) => {
if (res.code == 200) { if (res.code == 200) {
peopleOptions.value = (res.data || []).map(i => { peopleOptions.value = (res.data || []).map((i) => {
return { return {
value: i.text, value: i.text,
text: i.text text: i.text,
}; };
}); });
} }
@ -245,7 +241,7 @@ const gradeOptions = [
{ label: "初三", value: "初三" }, { label: "初三", value: "初三" },
{ label: "高一", value: "高一" }, { label: "高一", value: "高一" },
{ label: "高二", value: "高二" }, { label: "高二", value: "高二" },
{ label: "高三", value: "高三" } { label: "高三", value: "高三" },
]; ];
/** /**
* 新建赴校信息提交 * 新建赴校信息提交
@ -264,8 +260,8 @@ const addOrEdit = () => {
{ {
question: "xb测试反馈问题1双师课堂", question: "xb测试反馈问题1双师课堂",
questionType: 10, questionType: 10,
sort: "1111111111" sort: "1111111111",
} },
// { // {
// question: "xb2", // question: "xb2",
// questionType: 15, // questionType: 15,
@ -280,7 +276,7 @@ const addOrEdit = () => {
isDiscussion: true, isDiscussion: true,
discussion: "开展座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈", discussion: "开展座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈座谈",
isClassMeeting: true, isClassMeeting: true,
classMeeting: "班会情况班会情况班会情况班会情况班会情况班会情况班会情况" classMeeting: "班会情况班会情况班会情况班会情况班会情况班会情况班会情况",
}); });
}; };
@ -289,7 +285,7 @@ const query = reactive({
grade: "" as string | undefined, grade: "" as string | undefined,
people: "" as string | undefined, people: "" as string | undefined,
solutionEnd: undefined, solutionEnd: undefined,
times: [] as string[] times: [] as string[],
}); });
const page = ref(1); const page = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
@ -297,12 +293,8 @@ const total = ref(0);
const listData = ref<TableItem[]>([]); const listData = ref<TableItem[]>([]);
function mapApiItemToRow(item: any): TableItem { function mapApiItemToRow(item: any): TableItem {
const peopleArr = Array.isArray(item.schoolBusinessUser) const peopleArr = Array.isArray(item.schoolBusinessUser) ? item.schoolBusinessUser : [];
? item.schoolBusinessUser const start = item.startTime ? dayjs(item.startTime).format("YYYY-MM-DD") : "";
: [];
const start = item.startTime
? dayjs(item.startTime).format("YYYY-MM-DD")
: "";
let last = start; let last = start;
const rec = item.solutionRecord?.record || []; const rec = item.solutionRecord?.record || [];
if (Array.isArray(rec) && rec.length > 0) { if (Array.isArray(rec) && rec.length > 0) {
@ -319,10 +311,11 @@ function mapApiItemToRow(item: any): TableItem {
grade: item.grade || "", grade: item.grade || "",
people: peopleArr.join(""), people: peopleArr.join(""),
times: start, times: start,
canOperate: item.canOperate || false, //
feedbackTotals: Number(item.feedbackCount) || 0, feedbackTotals: Number(item.feedbackCount) || 0,
solveTotals: Number(item.solveFeedbackCount) || 0, solveTotals: Number(item.solveFeedbackCount) || 0,
solutionEnd: item.solutionEnd, solutionEnd: item.solutionEnd,
lastTime: last lastTime: last,
}; };
} }
@ -330,15 +323,14 @@ async function loadList() {
const payload: any = { const payload: any = {
pageIndex: page.value, pageIndex: page.value,
pageSize: pageSize.value, pageSize: pageSize.value,
orderBy: "startTime" orderBy: "startTime",
}; };
if (query.school) payload.schoolId = query.school; if (query.school) payload.schoolId = query.school;
if (query.grade) payload.grade = query.grade; if (query.grade) payload.grade = query.grade;
if (query.people) { if (query.people) {
payload.UserName = query.people; payload.UserName = query.people;
} }
if (typeof query.solutionEnd !== "undefined") if (typeof query.solutionEnd !== "undefined") payload.solutionEnd = query.solutionEnd;
payload.solutionEnd = query.solutionEnd;
if (Array.isArray(query.times) && query.times.length === 2) { if (Array.isArray(query.times) && query.times.length === 2) {
payload.startTime = query.times[0]; payload.startTime = query.times[0];
payload.endTime = query.times[1]; payload.endTime = query.times[1];
@ -396,7 +388,7 @@ function handleSizeChange(s: number) {
function onDelete(row: TableItem) { function onDelete(row: TableItem) {
console.log(`删除`, row); console.log(`删除`, row);
deleteSchoolBusinessApi([row.id]).then(res => { deleteSchoolBusinessApi([row.id]).then((res) => {
if (res.code === 200) { if (res.code === 200) {
message("删除成功", { type: "success" }); message("删除成功", { type: "success" });
loadList(); loadList();
@ -412,7 +404,7 @@ function onDetailOrFollow(row: TableItem, disabled = false) {
isShowEditModal.value = true; isShowEditModal.value = true;
editModalLoading.value = true; editModalLoading.value = true;
getSchoolBusinessDetailApi(row.id) getSchoolBusinessDetailApi(row.id)
.then(res => { .then((res) => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
detailData.value = res.data; detailData.value = res.data;
} }
@ -446,23 +438,63 @@ function handleAdd() {
} }
function handleImport() { function handleImport() {
console.log("批量导入"); console.log("批量导入");
let fileE = document.createElement("input");
fileE.type = "file";
var formData = new window.FormData();
fileE.onchange = async function () {
formData.append("file", fileE.files[0]);
let res = await importExcel(fileE.files[0]);
if (res.code != undefined) {
if (res.code !== 200) return ElMessage.error(res.message);
else return ElMessage.success("所有数据录入成功");
} else if (res.type === "application/json") {
let json = await res.text();
if (json !== undefined && json.Code !== 200) {
return ElMessage.error(json.Message);
} else {
return ElMessage.success("操所有数据录入成功作成功");
}
} else if (res === undefined || res.size === 0)
return ElMessage.success("所有数据录入成功");
const url = res && window.URL.createObjectURL(res);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "未成功导入的考试信息数据" + ".xlsx");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success("导入失败,已导出错误数据");
};
try {
fileE.click();
} catch (error) {}
} }
function handleExport() { function handleExport() {
console.log("导出"); console.log("导出");
} }
function downLoadTpl() { function downLoadTpl() {
console.log("下载模版"); console.log("下载模版");
const baseUrl = import.meta.env.VITE_API_BASEURL;
const excelImportUsersUrl = `${baseUrl}/SchoolBusiness/DwImportTemplate`;
const link = document.createElement("a");
link.href = excelImportUsersUrl;
link.setAttribute("download", "导入赴校信息模板" + ".xlsx");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success("下载成功!");
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-form { .search-form {
margin-bottom: 12px; margin-bottom: 0px;
} }
.pager { .pager {
display: flex; display: flex;
justify-content: flex-end; justify-content: center;
margin-top: 12px; margin-top: 12px;
} }
</style> </style>