dev #33

Merged
hy merged 2 commits from dev into staging 2025-11-27 17:07:46 +08:00
4 changed files with 151 additions and 77 deletions

View File

@ -82,3 +82,24 @@ export function PosititonIds(data:any[]) {
data data
}); });
} }
/**
* @description
* @return {object}
*/
export function ImportUpdateStudent(file: File) {
let formData = new FormData();
formData.append("file", file);
return http.request<any>(
"post",
`Student/ImportUpdateStudent`,
{
data: formData
},
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
responseType: "blob"
}
);
}

View File

@ -117,7 +117,7 @@ const tableData: TableConfig = intTableData({
selectRows: [], selectRows: [],
}); });
async function deleteInfo(o, row, c) { async function deleteInfo(o, row, baseRefresh) {
try { try {
await ElMessageBox.confirm("是否删除考试信息?", "提示", { await ElMessageBox.confirm("是否删除考试信息?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
@ -125,11 +125,12 @@ async function deleteInfo(o, row, c) {
type: "warning", type: "warning",
}); });
await DeleteExamInfo({ examId: row[0].examId, classId: row[0].classId }); await DeleteExamInfo({ examId: row[0].examId, classId: row[0].classId });
baseRefresh();
} catch (error) { } catch (error) {
ElMessage.info("取消删除"); ElMessage.info("取消删除");
} }
} }
async function reloadImportInfo(o, row, c) { async function reloadImportInfo(o, row, baseRefresh) {
try { try {
await ElMessageBox.confirm("是否重新录入考试信息?", "提示", { await ElMessageBox.confirm("是否重新录入考试信息?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
@ -138,6 +139,7 @@ async function reloadImportInfo(o, row, c) {
}); });
await DeleteExamInfo({ examId: row[0].examId, classId: row[0].classId }); await DeleteExamInfo({ examId: row[0].examId, classId: row[0].classId });
entryExamInfo(row[0].examId); entryExamInfo(row[0].examId);
baseRefresh();
} catch (error) { } catch (error) {
ElMessage.info("取消重新录入"); ElMessage.info("取消重新录入");
} }

View File

@ -42,12 +42,7 @@
</el-form-item> </el-form-item>
<el-form-item v-show="search.schoolId" style="width: 100px"> <el-form-item v-show="search.schoolId" 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"
@ -75,6 +70,9 @@
<!-- <el-button title="根据当前筛选条件导出" type="primary" @click="exportUser" <!-- <el-button title="根据当前筛选条件导出" type="primary" @click="exportUser"
>导出用户</el-button >导出用户</el-button
> --> > -->
<el-button type="success" @click="importUpdateStudent"
>使用Excel模板更新用户</el-button
>
</div> </div>
<div v-show="!selectUser" class="toolbar-container"> <div v-show="!selectUser" class="toolbar-container">
<!-- 按钮组 --> <!-- 按钮组 -->
@ -99,26 +97,17 @@
</el-table-column> </el-table-column>
<el-table-column label="学生信息" width="450"> <el-table-column label="学生信息" width="450">
<template #default="scope"> <template #default="scope">
<el-tooltip <el-tooltip :content="`id ` + scope.row.id" placement="top" effect="light">
:content="`id ` + scope.row.id"
placement="top"
effect="light"
>
<div> <div>
<span :tips="scope.row.id">{{ scope.row.realName }}</span> <span :tips="scope.row.id">{{ scope.row.realName }}</span>
<div <div
v-for="(position, index) in scope.row.positions" v-for="(position, index) in scope.row.positions"
v-show=" v-show="index < 3 || (index >= 3 && showAllPosition.includes(scope.row))"
index < 3 ||
(index >= 3 && showAllPosition.includes(scope.row))
"
:key="'Position' + index" :key="'Position' + index"
class="inline-block" class="inline-block"
> >
<div class="subjectTagEnableDiv"> <div class="subjectTagEnableDiv">
<el-tag v-if="position.enable === false" type="info" <el-tag v-if="position.enable === false" type="info">已禁用</el-tag>
>已禁用</el-tag
>
<el-tag>{{ position.schoolName || "-" }}</el-tag> <el-tag>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="success">{{ <el-tag type="success">{{
position.graduationYear position.graduationYear
@ -146,8 +135,9 @@
<template #default="scope"> <template #default="scope">
<span v-if="!scope.row.reliefApplication">未申请</span> <span v-if="!scope.row.reliefApplication">未申请</span>
<span v-else <span v-else
>{{ scope.row.reliefSubTime }}申请{{ scope.row.amountRelief }} >{{ scope.row.reliefSubTime }}申请{{ scope.row.amountRelief }} <br />{{
<br />{{ scope.row.reliefType }} scope.row.reliefType
}}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
@ -199,7 +189,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";
@ -211,10 +201,10 @@ import {
Message, Message,
ArrowDownBold, ArrowDownBold,
Search, Search,
Star Star,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ComboModel, gradeComboModel } from "@/components/hTable/hTable"; import { ComboModel, gradeComboModel } from "@/components/hTable/hTable";
import { ImportStudent, PageList } from "@/api/student"; import { ImportStudent, ImportUpdateStudent, PageList } from "@/api/student";
const classAPI = new hTableAPI("usercenter/back/classes"); const classAPI = new hTableAPI("usercenter/back/classes");
const schoolsAPI = new hTableAPI("usercenter/back/schools"); const schoolsAPI = new hTableAPI("usercenter/back/schools");
@ -281,20 +271,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;
@ -315,13 +305,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[]>([]);
@ -335,13 +325,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>({
@ -349,32 +339,32 @@ const dialog = reactive<DialogData>({
update: { update: {
title: "", title: "",
visible: false, visible: false,
width: "1000px" width: "1000px",
}, },
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 = () => {
@ -437,7 +427,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);
@ -498,9 +488,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;
} }
@ -520,12 +510,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,
}; };
PageList(data).then(res => { PageList(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);
} }
@ -603,14 +593,14 @@ const getUserLevelTag = (level: number) => {
return level === 0 return level === 0
? "info" ? "info"
: level === 1 : level === 1
? "success" ? "success"
: level === 2 : level === 2
? "warning" ? "warning"
: "error"; : "error";
}; };
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;
} }
@ -627,7 +617,7 @@ const PositionsSort = (arr: Position[]) => {
return 1; return 1;
} }
}); });
return arr.filter(s => s.enable); return arr.filter((s) => s.enable);
}; };
const handleReloadPaged = (event?: any, searchUnUse?: boolean) => { const handleReloadPaged = (event?: any, searchUnUse?: boolean) => {
@ -673,7 +663,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;
}; };
@ -688,7 +678,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;
}; };
@ -697,15 +687,18 @@ const handleEditSubjectLevelCallback = () => {
handleReloadPaged(); handleReloadPaged();
}; };
const importData = () => { const importUpdateStudent = () => {
importData(ImportUpdateStudent);
};
const importData = (api = null) => {
console.log("批量导入"); console.log("批量导入");
api = api ?? ImportStudent;
let fileE = document.createElement("input"); let fileE = document.createElement("input");
fileE.type = "file"; fileE.type = "file";
var formData = new window.FormData(); var formData = new window.FormData();
fileE.onchange = async function () { fileE.onchange = async function () {
formData.append("file", fileE.files[0]); formData.append("file", fileE.files[0]);
let res = await ImportStudent(fileE.files[0]); let res = await api(fileE.files[0]);
if (res.code != undefined) { if (res.code != undefined) {
if (res.code !== 200) return ElMessage.error(res.message); if (res.code !== 200) return ElMessage.error(res.message);
else return ElMessage.success("所有数据录入成功"); else return ElMessage.success("所有数据录入成功");
@ -734,7 +727,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

@ -29,7 +29,31 @@
{{ safeDetail.schoolName || safeDetail.school || "-" }} {{ safeDetail.schoolName || safeDetail.school || "-" }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="年级"> <el-descriptions-item label="年级">
{{ safeDetail.gradeLevel + safeDetail.gradeYear }} <span v-show="!isDetail">
<el-select
v-model="safeDetail.gradeLevel"
placeholder="请年级"
clearable
style="width: 120px"
>
<el-option
v-for="g in gradeOptions"
:key="g.value"
:label="g.label"
:value="g.value"
/>
</el-select>
<el-input-number
style="width: 120px"
v-model="safeDetail.gradeYear"
:min="2020"
:max="2100"
/>
</span>
<span v-show="isDetail">
{{ safeDetail.gradeLevel || "-" }}{{ safeDetail.gradeYear || "-" }}
</span>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="赴校人员"> <el-descriptions-item label="赴校人员">
{{ {{
@ -51,26 +75,50 @@
<el-descriptions title="基础工作" :column="1" border> <el-descriptions title="基础工作" :column="1" border>
<el-descriptions-item label="座谈"> <el-descriptions-item label="座谈">
<el-tag <div v-if="!isDetail" class="flexCenterWrap">
:type="safeDetail.isDiscussion ? 'success' : 'info'" <el-tag :type="safeDetail.isDiscussion ? 'success' : 'info'">
style="margin-right: 8px" {{ safeDetail.isDiscussion ? "已开展" : "未开展" }}
> </el-tag>
{{ safeDetail.isDiscussion ? "已开展" : "未开展" }} <el-radio-group :disabled="isDetail" v-model="safeDetail.isDiscussion">
</el-tag> <el-radio :label="true"></el-radio>
<span class="inline-block max-w-[500px]!">{{ <el-radio :label="false"></el-radio>
safeDetail.discussion || "-" </el-radio-group>
}}</span>
<el-input
:maxlength="500"
type="textarea"
:rows="3"
v-model="safeDetail.discussion"
:disabled="!safeDetail.isDiscussion"
placeholder="请输入座谈情况"
/>
</div>
<div v-else>
{{ safeDetail.isDiscussion ? safeDetail.discussion : "未开展" }}
</div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="班会"> <el-descriptions-item label="班会">
<el-tag <div v-if="!isDetail" class="flexCenterWrap">
:type="safeDetail.isClassMeeting ? 'success' : 'info'" <el-tag :type="safeDetail.isClassMeeting ? 'success' : 'info'">
style="margin-right: 8px" {{ safeDetail.isClassMeeting ? "已开展" : "未开展" }}
> </el-tag>
{{ safeDetail.isClassMeeting ? "已开展" : "未开展" }} <el-radio-group :disabled="isDetail" v-model="safeDetail.isClassMeeting">
</el-tag> <el-radio :label="true"></el-radio>
<span class="inline-block max-w-[500px]!">{{ <el-radio :label="false"></el-radio>
safeDetail.classMeeting || "-" </el-radio-group>
}}</span>
<el-input
:maxlength="500"
type="textarea"
:rows="3"
v-model="safeDetail.classMeeting"
:disabled="isDetail || !safeDetail.isClassMeeting"
placeholder="请输入班会情况"
/>
</div>
<div v-else>
{{ safeDetail.isDiscussion ? safeDetail.discussion : "未开展" }}
</div>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-divider /> <el-divider />
@ -578,6 +626,7 @@ function onClickCancel() {
function onClickSave() { 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));
copyParams.grade = `${copyParams.gradeLevel}${copyParams.gradeYear}`;
delete copyParams.solutionEnd; delete copyParams.solutionEnd;
addOrEditApi(copyParams).then((res) => { addOrEditApi(copyParams).then((res) => {
if (res.code === 200) { if (res.code === 200) {
@ -633,4 +682,13 @@ function onClickSave() {
text-decoration: underline; text-decoration: underline;
user-select: none; user-select: none;
} }
.flexCenterWrap {
gap: 4px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
align-items: center;
justify-content: flex-start;
}
</style> </style>