dev #8

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

View File

@ -9,4 +9,4 @@ VITE_ROUTER_HISTORY = "hash"
# 接口地址
VITE_API_BASEURL = "http://localhost:5199/api"
#数据中心后台地址
VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api/back"
VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api/"

View File

@ -10,4 +10,10 @@ VITE_CDN = false
# 是否启用gzip压缩或brotli压缩分两种情况删除原始文件和不删除原始文件
# 压缩时不删除原始文件的配置gzip、brotli、both同时开启 gzip 与 brotli 压缩、none不开启压缩默认
# 压缩时删除原始文件的配置gzip-clear、brotli-clear、both-clear同时开启 gzip 与 brotli 压缩、none不开启压缩默认
VITE_COMPRESSION = "none"
VITE_COMPRESSION = "none"
# 接口地址
VITE_API_BASEURL = "http://localhost:5199/api"
#数据中心后台地址
VITE_API_USERCENTER_URL = "https://dcb.23544.com/api"

View File

@ -1,12 +0,0 @@
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
/**
* @description
* @return {object}
*/
export function addClasses(info: any) {
return http.request<Res<any>>("post", `classes/addclass`, {
data: info
});
}

View File

@ -28,10 +28,3 @@ export function getregion(r) {
return http.request<Res<any>>("get", `address/${r}/region`);
}
/**
* @description
* @return {void}
*/
export function EditSchool(data: any) {
return http.request<Res<any>>("post", `schools/add`, { data });
}

182
src/api/userCenter.ts Normal file
View File

@ -0,0 +1,182 @@
import { http } from "@/utils/http";
import type { Res, ResPage } from "@/utils/http/types";
/**
* @description
* @param {string} type type=StatusEnum
* @return {object}
*/
export function getPageUserList(data: any) {
return http.request<ResPage<UserDetail[]>>(
"post",
`userCenter/back/users/getpageuserlist`,
{
data
}
);
}
/**
* @description
* @return {void}
*/
export function EditSchool(data: any) {
return http.request<Res<any>>("post", `userCenter/back/schools/add`, {
data
});
}
/**
* @description
* @return {void}
*/
export function getSchoolData() {
return http.request<Res<any>>("get", `userCenter/public/getschooldata`);
}
/**
* @description
* @return {void}
*/
export function getPositionList(data: any) {
return http.request<Res<any>>(
"post",
`userCenter/back/positions/getpositionlist`,
{ data }
);
}
/**
* @description
* @return {object}
*/
export function cloudSchoolCombo() {
let data = {
ValueName: "Id",
TextName: "Name"
};
return http.request<Res<any>>(
"post",
`userCenter/back/cloudschool/querycombo`,
{
data
}
);
}
/**
* @description
* @return {object}
*/
export function getUserInfo(id) {
return http.request<Res<any>>(
"get",
`userCenter/back/users/getuserinfo?id=${id}`
);
}
/**
* @description
* @return {object}
*/
export function addClasses(info: any) {
return http.request<Res<any>>("post", `userCenter/back/classes/addclass`, {
data: info
});
}
/**
* @description
* @return {object}
*/
export function editUser(data: any) {
return http.request<Res<any>>("post", `userCenter/back/users/edituser`, {
data
});
}
/**
* @description
* @return {object}
*/
export function getClassCombo(info: any) {
return http.request<Res<any>>("post", `userCenter/public/getclasscombo`, {
data: info
});
}
/**
* @description
* @return {object}
*/
export function getSubjectData() {
return http.request<Res<any>>("get", `userCenter/public/getsubjectdata`);
}
/**
* @description
* @return {object}
*/
export function getPositions(data) {
return http.request<Res<any>>("post", `userCenter/back/positions/positions`, {
data
});
}
//--------------------------interface----------------------------
/**
*
*/
export interface Position {
userId: number;
id: number;
name: string;
schoolId: number;
enable: boolean;
endTime: string | null;
schoolName: string;
graduationYear: number;
grade: string;
classId: number;
className: string;
subjectId: number;
subjectName: string;
positionType: number;
positionLevel: number;
status: boolean;
}
/**
*
*/
export interface UserDetail {
id: number;
templateId: number;
phone: string;
edited: boolean;
userType: number;
cloudSchoolId: number;
account: string;
studentId: string;
realName: string;
sex: number;
subjectLevel: any;
birthDate: string;
residence: string;
national: string;
headImage: string;
idCard: string | null;
pid: number;
pname: string;
cid: number;
cname: string;
rid: number;
rname: string;
wx: string;
isPerfectInfo: number;
level: number;
state: number;
meetingAccount: string | null;
gkSubject: string | null;
glSubject: string | null;
gSubject1: string | null;
gSubject2: string | null;
thirdPartyId: string | null;
pointPenSN: string | null;
subjectLevels: null;
positions: Position[];
}

View File

@ -559,7 +559,14 @@ function fetchPagedData() {
</template>
</el-table-column>
</el-table>
<div style="display: flex; align-items: center; justify-content: center">
<div
style="
padding-top: 15px;
display: flex;
align-items: center;
justify-content: center;
"
>
<el-pagination
:current-page="table.search.PageIndex + 1"
:page-sizes="[20, 40, 80, 100]"

View File

@ -166,7 +166,11 @@ router.beforeEach((to: ToRouteType, _from, next) => {
getTopMenu(true);
// query、params模式路由传参数的标签页不在此处处理
if (route && route.meta?.title) {
if (isAllEmpty(route.parentId) && route.meta?.backstage) {
if (
isAllEmpty(route.parentId) &&
route.meta?.backstage &&
route.children
) {
// 此处为动态顶级路由(目录)
const { path, name, meta } = route.children[0];
useMultiTagsStoreHook().handleTags("push", {

View File

@ -20,15 +20,6 @@ export default {
title: "首页",
showLink: VITE_HIDE_HOME === "true" ? false : true
}
},
{
path: "/school",
name: "school",
component: () => import("@/views/school/index.vue"),
meta: {
title: "学校",
showLink: true
}
}
]
} satisfies RouteConfigsTable;

View File

@ -18,14 +18,17 @@ import router from "@/router";
/**请求后端的地址 未配置则访问BaseURL */
const apiServiceConfig = {
classes: import.meta.env.VITE_API_USERCENTER_URL,
schools: import.meta.env.VITE_API_USERCENTER_URL
userCenter: import.meta.env.VITE_API_USERCENTER_URL,
usercenter: import.meta.env.VITE_API_USERCENTER_URL
};
function getAPIUrl(url: string): string {
function setAPIUrl(c: PureHttpRequestConfig): void {
let url = c.url;
let token = url.startsWith("/") ? url.split("/")[1] : url.split("/")[0];
if (apiServiceConfig[token] != null) return apiServiceConfig[token];
else return import.meta.env.VITE_API_BASEURL;
if (apiServiceConfig[token] != null) {
c.url = url.replaceAll(token, "");
c.baseURL = apiServiceConfig[token];
} else c.baseURL = import.meta.env.VITE_API_BASEURL;
}
const snakeToCamel = (str: string): string => {
@ -118,7 +121,7 @@ class PureHttp {
// 开启进度条动画
NProgress.start();
if (config.url.indexOf("http") === -1) {
config.baseURL = getAPIUrl(config.url);
setAPIUrl(config);
}
// 优先判断post/get等方法是否传入回调否则执行初始化设置等回调
if (typeof config.beforeRequestCallback === "function") {

View File

@ -18,6 +18,14 @@ export interface PureHttpError extends AxiosError {
isCancelRequest?: boolean;
}
export type ResPage<T> = {
code: number;
data: {
data: T;
total: number;
};
message: string;
};
export type Res<T> = {
code: number;
data: T;

View File

@ -180,7 +180,7 @@ import type { FormInstance } from "element-plus";
import { getenum } from "@/api/enum";
import { hTableAPI } from "@/api/hTable";
import { ruleRequired } from "@/utils/rules";
import { addClasses } from "@/api/class";
import { addClasses } from "@/api/userCenter";
import { ComboModel } from "@/components/hTable/hTable";
interface Formdata {
@ -295,7 +295,7 @@ const handleResetForm = () => {
classesAddForm.value?.resetFields();
};
const SchoolApi = new hTableAPI("schools");
const SchoolApi = new hTableAPI("usercenter/back/schools");
const fetchInitdata = async () => {
//
ClassNameList.value = Array.from({ length: 500 }, (_, i) => `${i + 1}`);

View File

@ -12,12 +12,12 @@ defineOptions({
name: ControllerName
});
const SchoolApi = new hTableAPI("schools");
const SchoolApi = new hTableAPI("usercenter/back/schools");
function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = {
apiUrl: ControllerName,
apiUrl: "usercenter/back/classes",
selectColumn: false, //
border: false, //
searchCallback: searchCallback,

View File

@ -12,7 +12,7 @@ defineOptions({
name: ControllerName
});
const SchoolApi = new hTableAPI("School");
const SchoolApi = new hTableAPI("usercenter/back/schools");
function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>();

View File

@ -105,7 +105,8 @@
</div>
</template>
<script setup lang="ts">
import { getregion, getcity, getProvince, EditSchool } from "@/api/school";
import { getregion, getcity, getProvince } from "@/api/school";
import { EditSchool } from "@/api/userCenter";
import { ElMessage, FormInstance } from "element-plus";
import { onMounted, ref } from "vue";

View File

@ -11,7 +11,7 @@ onMounted(() => {});
function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(null);
const tableData: TableConfig = {
apiUrl: "schools",
apiUrl: "usercenter/back/schools",
selectColumn: false, //
border: false, //
searchCallback: searchCallback,

555
src/views/teacher/edit.vue Normal file
View File

@ -0,0 +1,555 @@
<template>
<div>
<el-form
ref="userEditForm"
:model="form"
:label-width="formLabelWidth"
clearable
>
<el-row>
<el-col :span="12">
<el-form-item label="账号:" prop="Account">
<el-input
type="text"
v-model="form.account"
autocomplete="off"
minlength="6"
maxlength="33"
:show-word-limit="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话号码" :rules="rulePhone" prop="phone">
<el-input type="text" v-model="form.phone" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="姓名:" prop="realName" :rules="ruleRequired">
<el-input
type="text"
v-model="form.realName"
autocomplete="off"
maxlength="20"
:show-word-limit="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属云校" prop="cloudSchoolId">
<el-select
v-model="form.cloudSchoolId"
filterable
style="width: 180px"
>
<el-option
v-for="(item, i) in CloudSchoolArr"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="form.userType === 1">
<el-col :span="24">
<div style="display: flex; gap: 10px">
<label for="Level" class="el-form-item__label" style="width: 120px"
>新高考</label
>
<el-select
v-model="form.gLSubject"
filterable
placeholder="历史/地理"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject1"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
<el-select
v-model="form.gSubject1"
filterable
placeholder="小学科"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject2"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
<el-select
v-model="form.gSubject2"
filterable
placeholder="小学科"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject2"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</div>
</el-col>
</el-row>
<el-row v-if="form.userType === 1">
<el-col :span="24">
<el-form-item label="学生层次:" prop="level">
<el-radio-group v-model="form.subjectLevel.Level">
<el-radio
v-for="(item, i) in userLevelList"
:key="i"
:label="item.value"
>{{ item.text }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.userType === 1">
<el-col :span="24">
<el-form-item label="科目层次:">
<ul class="userform_ul">
<li
v-for="(value, i) in getUserSubjectLevel(form.subjectLevel)"
:key="i"
>
{{ userLevel2subject(value[0]) }}:
<el-radio-group v-model="form.subjectLevel[value[0]]">
<el-radio
v-for="(level, j) in userLevelList"
:key="j"
:label="level.value"
>{{ level.text }}</el-radio
>
</el-radio-group>
</li>
</ul>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12"> </el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="职位:" prop="positionIds" :rules="ruleRequired">
<el-button type="success" @click="CheckPosition()"
>分配职位</el-button
>
</el-form-item>
</el-col>
</el-row>
<div
v-for="(position, index) in positionList"
:key="index"
style="padding-left: 120px; padding-bottom: 20px"
>
<div class="subjectTagEnableDiv" v-if="position.enable === false">
<el-tag type="info">{{ "禁用" }}</el-tag>
<el-tag type="info">{{ position.schoolName || "-" }}</el-tag>
<el-tag type="info">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="info">{{ position.grade || "-" }}</el-tag>
<el-tag type="info">{{ position.className || "-" }}</el-tag>
<el-tag type="info">{{ position.subjectName || "-" }}</el-tag>
<el-tag type="info">{{ position.name || "-" }}</el-tag>
<el-tag type="info">{{ position.endTime }}</el-tag>
</div>
<div class="subjectTagEnableDiv" v-else>
<el-tag>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="warning">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="success">{{ position.grade || "-" }}</el-tag>
<el-tag type="primary" class="classTag">{{
position.className || "-"
}}</el-tag>
<el-tag type="info" class="subjectTag">{{
position.subjectName || "-"
}}</el-tag>
<el-tag type="danger">{{ position.name || "-" }}</el-tag>
</div>
</div>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleSubmitForm()"
>立即提交</el-button
>
<el-button @click="handleResetForm()">重置</el-button>
</el-form-item>
</el-form>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
ref="PositionCheckFromDialog"
:title="dialog.title"
v-model="dialog.visible"
:width="dialog.width"
:close-on-click-modal="dialog.close"
:close-on-press-escape="dialog.close"
append-to-body
>
<PositionForm
:userType="form.userType"
:positions="PositionFormIds"
@handleCheckCallback="handleCheckCallback"
/>
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import {} from "@/api/user";
import {
cloudSchoolCombo,
getUserInfo,
editUser,
Position
} from "@/api/userCenter";
import PositionForm from "./positionForm.vue";
import { getenum, getenumDic } from "@/api/enum";
import { ruleRequired, rulePhone } from "@/utils/rules";
import { ElMessage } from "element-plus";
import { ComboModel } from "@/components/hTable/hTable";
interface FormData {
id: number;
account: string;
userType: number;
level: number;
passWord: string;
realName: string;
studentId: string;
templateId: number;
phone: string | number;
cloudSchoolId: number;
subjectLevels: any[];
subjectLevel: Record<string, any>;
positionIds: number[];
positionFormIds?: number[];
gLSubject?: number;
gSubject1?: number;
gSubject2?: number;
idCard?: string;
pointPenSN?: string;
}
interface DialogConfig {
close: boolean;
title: string;
visible: boolean;
width: string;
}
defineOptions({
name: "UserEditForm"
});
const props = defineProps<{
id: number;
}>();
const formLabelWidth = "120px";
const size = "small";
const loading = ref(false);
const subject1 = ref<ComboModel[]>([
{ value: 4, text: "物理" },
{ value: 8, text: "历史" }
]);
const subject2 = ref<ComboModel[]>([
{ value: 5, text: "化学" },
{ value: 6, text: "生物" },
{ value: 9, text: "地理" },
{ value: 7, text: "政治" }
]);
const userTypeList = ref<ComboModel[]>([]);
const userLevelList = ref<ComboModel[]>([]);
const subjectLEnum = ref<Record<string, string>>({});
const positionList = ref<Position[]>([]);
const CloudSchoolArr = ref<ComboModel[]>([]);
const Template = ref<any[]>([]);
const PositionFormIds = ref<number[]>([]);
const defaultSubjectLevel = reactive({
UserId: 0,
Level: 0,
Subject1: 0,
Subject2: 0,
Subject3: 0,
Subject4: 0,
Subject5: 0,
Subject6: 0,
Subject7: 0,
Subject8: 0,
Subject9: 0,
CreatePositionId: 1
});
const form = ref<FormData>({
id: props.id,
account: "",
userType: 2,
level: 0,
passWord: "",
realName: "",
studentId: "",
templateId: 0,
phone: "",
cloudSchoolId: 0,
subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel },
positionIds: [],
positionFormIds: []
});
const dialog = reactive<DialogConfig>({
close: false,
title: "",
visible: false,
width: "1200px"
});
const customeRules = reactive({
mobile: [
{ required: false, message: "手机号必填", trigger: "blur" },
{
pattern: /^1[3456789]\d{9}$/,
message: "手机号码格式不正确",
trigger: "blur"
}
]
});
const getUserSubjectLevel = (obj: Record<string, any>) => {
if (!obj.id) {
form.value.subjectLevel = { ...defaultSubjectLevel };
obj = form.value.subjectLevel;
}
return Object.entries(obj).filter(s => s[0].includes("Subject"));
};
const userLevel2subject = (str: string) => {
const name = str.match(/[0-9]+/)?.[0] || "";
return subjectLEnum.value[name];
};
const handlePagedCallback = () => {
// Emit event to parent if needed
};
const handleSubmitForm = () => {
// Form validation and submission logic
loading.value = true;
const formData = {
id: form.value.id || 0,
userType: form.value.userType || 1,
level: form.value.level || 0,
account: form.value.account || "",
// PassWord: form.value.id === 0 ? md5(form.value.PassWord).toUpperCase() : "",
realName: form.value.realName || "",
studentId: form.value.studentId || "",
templateId: form.value.templateId || 0,
subjectLevels: form.value.subjectLevels || [],
subjectLevel: form.value.subjectLevel || { ...defaultSubjectLevel },
positionIds: form.value.positionIds || [],
gLSubject: form.value.gLSubject,
gSubject1: form.value.gSubject1,
gSubject2: form.value.gSubject2,
idCard: form.value.idCard,
cloudSchoolId: form.value.cloudSchoolId,
phone: form.value.phone,
pointPenSN: form.value.pointPenSN
};
editUser(formData).then(res => {
loading.value = false;
if (res.code === 200) {
ElMessage.success("操作成功");
handlePagedCallback();
} else {
ElMessage.error(res.message);
}
});
};
const handleResetForm = () => {
Object.assign(form, {
id: props.id,
account: "",
userType: 1,
level: 0,
passWord: "",
realName: "",
studentId: "",
subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel },
positionIds: [],
idCard: "",
phone: "",
cloudSchoolId: "",
pointPenSN: ""
});
positionList.value = [];
};
const fetchInitData = async () => {
//
const levelRes = await getenum("StudentLevelEnum");
userLevelList.value = levelRes.data;
const typeRes = await getenum("UserTypeEnum");
userTypeList.value = typeRes.data;
//
const schoolRes = await cloudSchoolCombo();
if (schoolRes.code === 200) {
CloudSchoolArr.value = schoolRes.data;
}
const enumDicRes = await getenumDic("SubjectEnum");
subjectLEnum.value = enumDicRes.data;
};
const fetchFormData = () => {
handleResetForm();
if (props.id !== 0) {
getUserInfo(props.id).then(res => {
if (res.code === 200) {
if (res.data.SubjectLevel && res.data.SubjectLevel.CreatePositionId) {
delete res.data.SubjectLevel.CreatePositionId;
}
Object.assign(form.value, {
id: res.data.id,
userType: res.data.userType,
level: res.data.level,
account: res.data.account,
passWord: res.data.passWord,
realName: res.data.realName,
studentId: res.data.studentId,
templateId: res.data.templateId,
subjectLevels: res.data.subjectLevels,
subjectLevel: res.data.subjectLevel,
positionIds: res.data.positions
.filter((s: any) => s.enable !== false)
.map((w: any) => w.id),
gLSubject: res.data.gLSubject,
gSubject1: res.data.gSubject1,
gSubject2: res.data.gSubject2,
idCard: res.data.idCard,
phone: res.data.phone,
cloudSchoolId: res.data.cloudSchoolId,
pointPenSN: res.data.pointPenSN
});
positionList.value = res.data.positions;
PositionFormIds.value = res.data.positions
.filter((s: any) => s.Enable !== false)
.map((s: any) => s.id);
}
});
}
};
const userTypeChange = () => {
if (form.value.userType === 2) {
form.value.studentId = "";
}
customeRules.mobile[0].required = form.value.userType !== 1;
};
const CheckPosition = () => {
dialog.title = "选择职位";
dialog.visible = true;
PositionFormIds.value = positionList.value
.filter(s => s.enable !== false)
.map(s => s.id);
};
const handleCheckCallback = (checkPosition: Position[]) => {
dialog.visible = false;
positionList.value = checkPosition;
form.value.positionIds = positionList.value.map(w => w.id);
};
onMounted(async () => {
await fetchInitData();
fetchFormData();
});
</script>
<style scoped>
.userform_ul {
list-style: none;
padding: 0;
margin: 0;
}
.userform_ul li {
margin-bottom: 10px;
}
.subjectTagEnableDiv {
margin-top: 5px;
}
.subjectTagEnableDiv .el-tag {
margin-right: 5px;
}
.classTag {
background-color: #409eff;
color: white;
}
.subjectTag {
background-color: #909399;
color: white;
}
.classTag {
color: #a3bf08 !important;
background-color: #f4fbd1 !important;
border-color: #f4fbd1 !important;
}
.subjectTagEnableDiv {
padding: 1px;
}
.subjectTag {
color: #eb0de4 !important;
background-color: #fbd9ff !important;
border-color: #fbd9ff !important;
}
.userform_ul {
list-style: none;
}
</style>

View File

@ -0,0 +1,555 @@
<template>
<div>
<el-form
ref="userEditForm"
:model="form"
:label-width="formLabelWidth"
clearable
>
<el-row>
<el-col :span="12">
<el-form-item label="账号:" prop="Account">
<el-input
type="text"
v-model="form.account"
autocomplete="off"
minlength="6"
maxlength="33"
:show-word-limit="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电话号码" :rules="rulePhone" prop="phone">
<el-input type="text" v-model="form.phone" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="姓名:" prop="realName" :rules="ruleRequired">
<el-input
type="text"
v-model="form.realName"
autocomplete="off"
maxlength="20"
:show-word-limit="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属云校" prop="cloudSchoolId">
<el-select
v-model="form.cloudSchoolId"
filterable
style="width: 180px"
>
<el-option
v-for="(item, i) in CloudSchoolArr"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="form.userType === 1">
<el-col :span="24">
<div style="display: flex; gap: 10px">
<label for="Level" class="el-form-item__label" style="width: 120px"
>新高考</label
>
<el-select
v-model="form.gLSubject"
filterable
placeholder="历史/地理"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject1"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
<el-select
v-model="form.gSubject1"
filterable
placeholder="小学科"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject2"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
<el-select
v-model="form.gSubject2"
filterable
placeholder="小学科"
style="width: 180px"
>
<el-option
v-for="(item, i) in subject2"
:key="i"
autocomplete="off"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</div>
</el-col>
</el-row>
<el-row v-if="form.userType === 1">
<el-col :span="24">
<el-form-item label="学生层次:" prop="level">
<el-radio-group v-model="form.subjectLevel.Level">
<el-radio
v-for="(item, i) in userLevelList"
:key="i"
:label="item.value"
>{{ item.text }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.userType === 1">
<el-col :span="24">
<el-form-item label="科目层次:">
<ul class="userform_ul">
<li
v-for="(value, i) in getUserSubjectLevel(form.subjectLevel)"
:key="i"
>
{{ userLevel2subject(value[0]) }}:
<el-radio-group v-model="form.subjectLevel[value[0]]">
<el-radio
v-for="(level, j) in userLevelList"
:key="j"
:label="level.value"
>{{ level.text }}</el-radio
>
</el-radio-group>
</li>
</ul>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12"> </el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="职位:" prop="positionIds" :rules="ruleRequired">
<el-button type="success" @click="CheckPosition()"
>分配职位</el-button
>
</el-form-item>
</el-col>
</el-row>
<div
v-for="(position, index) in positionList"
:key="index"
style="padding-left: 120px; padding-bottom: 20px"
>
<div class="subjectTagEnableDiv" v-if="position.enable === false">
<el-tag type="info">{{ "禁用" }}</el-tag>
<el-tag type="info">{{ position.schoolName || "-" }}</el-tag>
<el-tag type="info">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="info">{{ position.grade || "-" }}</el-tag>
<el-tag type="info">{{ position.className || "-" }}</el-tag>
<el-tag type="info">{{ position.subjectName || "-" }}</el-tag>
<el-tag type="info">{{ position.name || "-" }}</el-tag>
<el-tag type="info">{{ position.endTime }}</el-tag>
</div>
<div class="subjectTagEnableDiv" v-else>
<el-tag>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="warning">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="success">{{ position.grade || "-" }}</el-tag>
<el-tag type="primary" class="classTag">{{
position.className || "-"
}}</el-tag>
<el-tag type="info" class="subjectTag">{{
position.subjectName || "-"
}}</el-tag>
<el-tag type="danger">{{ position.name || "-" }}</el-tag>
</div>
</div>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleSubmitForm()"
>立即提交</el-button
>
<el-button @click="handleResetForm()">重置</el-button>
</el-form-item>
</el-form>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
ref="PositionCheckFromDialog"
:title="dialog.title"
v-model="dialog.visible"
:width="dialog.width"
:close-on-click-modal="dialog.close"
:close-on-press-escape="dialog.close"
append-to-body
>
<PositionForm
:userType="form.userType"
:positions="PositionFormIds"
@handleCheckCallback="handleCheckCallback"
/>
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import {} from "@/api/user";
import {
cloudSchoolCombo,
getUserInfo,
editUser,
Position
} from "@/api/userCenter";
import PositionForm from "./positionForm.vue";
import { getenum, getenumDic } from "@/api/enum";
import { ruleRequired, rulePhone } from "@/utils/rules";
import { ElMessage } from "element-plus";
import { ComboModel } from "@/components/hTable/hTable";
interface FormData {
id: number;
account: string;
userType: number;
level: number;
passWord: string;
realName: string;
studentId: string;
templateId: number;
phone: string | number;
cloudSchoolId: number;
subjectLevels: any[];
subjectLevel: Record<string, any>;
positionIds: number[];
positionFormIds?: number[];
gLSubject?: number;
gSubject1?: number;
gSubject2?: number;
idCard?: string;
pointPenSN?: string;
}
interface DialogConfig {
close: boolean;
title: string;
visible: boolean;
width: string;
}
defineOptions({
name: "UserEditForm"
});
const props = defineProps<{
id: number;
}>();
const formLabelWidth = "120px";
const size = "small";
const loading = ref(false);
const subject1 = ref<ComboModel[]>([
{ value: 4, text: "物理" },
{ value: 8, text: "历史" }
]);
const subject2 = ref<ComboModel[]>([
{ value: 5, text: "化学" },
{ value: 6, text: "生物" },
{ value: 9, text: "地理" },
{ value: 7, text: "政治" }
]);
const userTypeList = ref<ComboModel[]>([]);
const userLevelList = ref<ComboModel[]>([]);
const subjectLEnum = ref<Record<string, string>>({});
const positionList = ref<Position[]>([]);
const CloudSchoolArr = ref<ComboModel[]>([]);
const Template = ref<any[]>([]);
const PositionFormIds = ref<number[]>([]);
const defaultSubjectLevel = reactive({
UserId: 0,
Level: 0,
Subject1: 0,
Subject2: 0,
Subject3: 0,
Subject4: 0,
Subject5: 0,
Subject6: 0,
Subject7: 0,
Subject8: 0,
Subject9: 0,
CreatePositionId: 1
});
const form = ref<FormData>({
id: props.id,
account: "",
userType: 2,
level: 0,
passWord: "",
realName: "",
studentId: "",
templateId: 0,
phone: "",
cloudSchoolId: 0,
subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel },
positionIds: [],
positionFormIds: []
});
const dialog = reactive<DialogConfig>({
close: false,
title: "",
visible: false,
width: "1200px"
});
const customeRules = reactive({
mobile: [
{ required: false, message: "手机号必填", trigger: "blur" },
{
pattern: /^1[3456789]\d{9}$/,
message: "手机号码格式不正确",
trigger: "blur"
}
]
});
const getUserSubjectLevel = (obj: Record<string, any>) => {
if (!obj.id) {
form.value.subjectLevel = { ...defaultSubjectLevel };
obj = form.value.subjectLevel;
}
return Object.entries(obj).filter(s => s[0].includes("Subject"));
};
const userLevel2subject = (str: string) => {
const name = str.match(/[0-9]+/)?.[0] || "";
return subjectLEnum.value[name];
};
const handlePagedCallback = () => {
// Emit event to parent if needed
};
const handleSubmitForm = () => {
// Form validation and submission logic
loading.value = true;
const formData = {
id: form.value.id || 0,
userType: form.value.userType || 1,
level: form.value.level || 0,
account: form.value.account || "",
// PassWord: form.value.id === 0 ? md5(form.value.PassWord).toUpperCase() : "",
realName: form.value.realName || "",
studentId: form.value.studentId || "",
templateId: form.value.templateId || 0,
subjectLevels: form.value.subjectLevels || [],
subjectLevel: form.value.subjectLevel || { ...defaultSubjectLevel },
positionIds: form.value.positionIds || [],
gLSubject: form.value.gLSubject,
gSubject1: form.value.gSubject1,
gSubject2: form.value.gSubject2,
idCard: form.value.idCard,
cloudSchoolId: form.value.cloudSchoolId,
phone: form.value.phone,
pointPenSN: form.value.pointPenSN
};
editUser(formData).then(res => {
loading.value = false;
if (res.code === 200) {
ElMessage.success("操作成功");
handlePagedCallback();
} else {
ElMessage.error(res.message);
}
});
};
const handleResetForm = () => {
Object.assign(form, {
id: props.id,
account: "",
userType: 1,
level: 0,
passWord: "",
realName: "",
studentId: "",
subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel },
positionIds: [],
idCard: "",
phone: "",
cloudSchoolId: "",
pointPenSN: ""
});
positionList.value = [];
};
const fetchInitData = async () => {
//
const levelRes = await getenum("StudentLevelEnum");
userLevelList.value = levelRes.data;
const typeRes = await getenum("UserTypeEnum");
userTypeList.value = typeRes.data;
//
const schoolRes = await cloudSchoolCombo();
if (schoolRes.code === 200) {
CloudSchoolArr.value = schoolRes.data;
}
const enumDicRes = await getenumDic("SubjectEnum");
subjectLEnum.value = enumDicRes.data;
};
const fetchFormData = () => {
handleResetForm();
if (props.id !== 0) {
getUserInfo(props.id).then(res => {
if (res.code === 200) {
if (res.data.SubjectLevel && res.data.SubjectLevel.CreatePositionId) {
delete res.data.SubjectLevel.CreatePositionId;
}
Object.assign(form.value, {
id: res.data.id,
userType: res.data.userType,
level: res.data.level,
account: res.data.account,
passWord: res.data.passWord,
realName: res.data.realName,
studentId: res.data.studentId,
templateId: res.data.templateId,
subjectLevels: res.data.subjectLevels,
subjectLevel: res.data.subjectLevel,
positionIds: res.data.positions
.filter((s: any) => s.enable !== false)
.map((w: any) => w.id),
gLSubject: res.data.gLSubject,
gSubject1: res.data.gSubject1,
gSubject2: res.data.gSubject2,
idCard: res.data.idCard,
phone: res.data.phone,
cloudSchoolId: res.data.cloudSchoolId,
pointPenSN: res.data.pointPenSN
});
positionList.value = res.data.positions;
PositionFormIds.value = res.data.positions
.filter((s: any) => s.Enable !== false)
.map((s: any) => s.id);
}
});
}
};
const userTypeChange = () => {
if (form.value.userType === 2) {
form.value.studentId = "";
}
customeRules.mobile[0].required = form.value.userType !== 1;
};
const CheckPosition = () => {
dialog.title = "选择职位";
dialog.visible = true;
PositionFormIds.value = positionList.value
.filter(s => s.enable !== false)
.map(s => s.id);
};
const handleCheckCallback = (checkPosition: Position[]) => {
dialog.visible = false;
positionList.value = checkPosition;
form.value.positionIds = positionList.value.map(w => w.id);
};
onMounted(async () => {
await fetchInitData();
fetchFormData();
});
</script>
<style scoped>
.userform_ul {
list-style: none;
padding: 0;
margin: 0;
}
.userform_ul li {
margin-bottom: 10px;
}
.subjectTagEnableDiv {
margin-top: 5px;
}
.subjectTagEnableDiv .el-tag {
margin-right: 5px;
}
.classTag {
background-color: #409eff;
color: white;
}
.subjectTag {
background-color: #909399;
color: white;
}
.classTag {
color: #a3bf08 !important;
background-color: #f4fbd1 !important;
border-color: #f4fbd1 !important;
}
.subjectTagEnableDiv {
padding: 1px;
}
.subjectTag {
color: #eb0de4 !important;
background-color: #fbd9ff !important;
border-color: #fbd9ff !important;
}
.userform_ul {
list-style: none;
}
</style>

859
src/views/teacher/index.vue Normal file
View File

@ -0,0 +1,859 @@
<template>
<div class="app-container" style="padding: 5px">
<div class="search-container" style="padding-top: 5px">
<!-- 搜索项目 -->
<el-form :inline="true" :model="search">
<el-form-item>
<el-input v-model="search.searchStr" placeholder="姓名/账号/学号" />
</el-form-item>
<!-- <el-form-item style="width: 100px">
<el-select
v-model="search.userType"
placeholder="用户类型"
clearable
filterable
@change="userTypeChange"
>
<el-option
v-for="item in userTypeList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item> -->
<el-form-item v-show="search.userType === 1" style="width: 100px">
<el-select
v-model="search.level"
placeholder="学生层次"
clearable
filterable
>
<el-option
v-for="item in userLevelList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.schoolId"
placeholder="学校"
clearable
filterable
@change="schoolChange"
>
<el-option
v-for="item in schoolList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item style="width: 100px">
<el-select
v-model="search.grade"
placeholder="年级"
clearable
filterable
@change="gradeChange"
>
<el-option
v-for="item in gradeList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item style="width: 100px">
<el-select
v-model="search.classId"
placeholder="班级"
clearable
filterable
>
<el-option
v-for="item in classList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item style="width: 100px">
<el-select
v-model="search.subjectId"
placeholder="科目"
clearable
filterable
>
<el-option
v-for="item in subjectList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleReloadPaged" :icon="Search"
>查询</el-button
>
</el-form-item>
<el-form-item v-show="selectUser">
<el-button
type="success"
@click="selectUserCallBack()"
icon="el-icon-check"
>选择用户</el-button
>
</el-form-item>
</el-form>
</div>
<div class="toolbar-container" v-show="!selectUser">
<!-- 按钮组 -->
<el-button type="success" @click="importData">导入用户</el-button>
<el-button type="default" @click="downLoadImportUsersTemplate"
>下载导入用户模板</el-button
>
<el-button title="根据当前筛选条件导出" type="success" @click="exportUser"
>导出用户</el-button
>
</div>
<div class="toolbar-container" v-show="!selectUser">
<!-- 按钮组 -->
<el-button type="success" @click="AddDialog" plain>新增</el-button>
</div>
<el-table
@row-dblclick="setCurrent"
@row-click="selectUserClick"
ref="selectUserTable"
:data="table.data"
@selection-change="handleSelectionChange"
style="width: 100%"
:max-height="maxTableHeight"
>
<!-- <el-table-column type="selection" width="40" /> -->
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button text type="primary" @click="EditDialog(scope.row)" plain
>修改</el-button
>
</template>
</el-table-column>
<el-table-column prop="id" label="用户Id" width="100" />
<el-table-column label="用户信息" width="200">
<template #default="scope">
<el-tag
:type="getUserTypeTag(scope.row.userType)"
style="margin-right: 5px"
>{{
userTypeList.find(s => s.value == scope.row.userType)?.text
}}</el-tag
>
<span>{{ scope.row.realName }} </span>
</template>
</el-table-column>
<el-table-column prop="account" label="账号" width="120" />
<el-table-column prop="phone" label="手机号" width="120" />
<!-- <el-table-column prop="studentId" label="学号" width="120" />
<el-table-column prop="gKSubject" label="新高考学科" width="150" /> -->
<el-table-column label="职位">
<template #default="scope">
<div
v-for="(position, index) in scope.row.positions"
:key="'Position' + index"
v-show="
index < 3 || (index >= 3 && showAllPosition.includes(scope.row))
"
>
<div v-if="position.enable === false">
<el-tag type="info">{{ position.schoolName || "-" }}</el-tag>
<el-tag type="info">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="info">{{ position.grade || "-" }}</el-tag>
<el-tag type="info">{{ position.className || "-" }}</el-tag>
<el-tag type="info">{{ position.subjectName || "-" }}</el-tag>
<el-tag type="info">{{ position.name || "-" }}</el-tag>
</div>
<div class="subjectTagEnableDiv" v-else>
<el-tag>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="warning">{{
position.graduationYear ? position.graduationYear + "届" : "-"
}}</el-tag>
<el-tag type="success">{{ position.grade || "-" }}</el-tag>
<el-tag type="primary" class="classTag">{{
position.className || "-"
}}</el-tag>
<el-tag type="info" class="subjectTag">{{
position.subjectName || "-"
}}</el-tag>
<el-tag type="danger">{{ position.name || "-" }}</el-tag>
</div>
</div>
<div
v-if="
scope.row.positions != undefined && scope.row.positions.length > 3
"
@click="showPosition(scope.row)"
class="userTagRow"
>
<el-icon
title="折叠职位"
class="userTagRowItop"
v-if="showAllPosition.includes(scope.row)"
><ArrowDownBold
/></el-icon>
<el-icon v-else title="展开更多职位"><ArrowDownBold /></el-icon>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="pageSizeChange"
@current-change="pageIndexChange"
:current-page="pagination.total + 1"
:page-sizes="[10, 20, 40, 80, 100]"
:page-size="pagination.size"
layout="prev, pager, next,sizes, total"
:total="pagination.total"
/>
<div class="dialog-container">
<el-dialog
v-if="dialog.update.visible"
ref="UserEditFromDialog"
:title="dialog.update.title"
v-model="dialog.update.visible"
:width="dialog.update.width"
:close-on-click-modal="dialog.close"
:close-on-press-escape="dialog.close"
append-to-body
>
<UserForm :id="editId" @handlePagedCallback="handleAddCallback" />
</el-dialog>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { UploadProps } from "element-plus";
import UserForm from "./edit.vue";
import {
getSchoolData,
getClassCombo,
getSubjectData,
getPageUserList,
UserDetail,
Position
} from "@/api/userCenter";
import { getenum } from "@/api/enum";
import { hTableAPI } from "@/api/hTable";
import { text } from "stream/consumers";
import {
Check,
Delete,
Edit,
Message,
ArrowDownBold,
Search,
Star
} from "@element-plus/icons-vue";
import { ComboModel } from "@/components/hTable/hTable";
const classAPI = new hTableAPI("usercenter/back/classes");
const schoolsAPI = new hTableAPI("usercenter/back/schools");
interface SearchParams {
searchStr: string;
userType: string | number;
level: string | number;
schoolId: string | number;
graduationYear: string | number;
grade: string;
classId: string | number;
subjectId: string | number;
positionId: string | number;
}
interface TableData {
data: UserDetail[];
selectRows: UserDetail[];
sort: string;
border: boolean;
}
interface PaginationData {
index: number;
size: number;
total: number;
}
interface DialogData {
close: boolean;
update: {
title: string;
visible: boolean;
width: string;
};
editLevel: {
userIds: number[];
title: string;
visible: boolean;
width: string;
};
editSubjectLevel: {
userIds: number[];
title: string;
visible: boolean;
width: string;
};
bindUser: {
title: string;
visible: boolean;
width: string;
height: string;
};
userBindInfo: {
title: string;
visible: boolean;
width: string;
height: string;
};
}
const props = defineProps({
selectUser: {
type: Boolean,
default: false
},
selectCallBack: {
type: Function,
default: () => {}
},
maxTableHeight: {
type: Number,
default: 580
},
searchData: {
type: Object as () => SearchParams,
default: undefined
}
});
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const excelImportUsersUrl = `${baseUrl}api/back/users/downloadimportusersexceltemplate`;
const excelImportMeetingUrl = `${baseUrl}api/back/users/downloadimportmeetingexceltemplate`;
const excelImportOrdersUrl = `${baseUrl}api/back/users/downloadimportordersexceltemplate`;
const editId = ref(0);
const showAllPosition = ref<UserDetail[]>([]);
const selectUserTable = ref();
const search = reactive<SearchParams>({
searchStr: "",
userType: "",
level: "",
schoolId: "",
graduationYear: "",
grade: "",
classId: "",
subjectId: "",
positionId: ""
});
const userTypeList = ref<ComboModel[]>([
{ value: 1, text: "学生" },
{ value: 2, text: "教师" },
{ value: 3, text: "管理员" }
]);
const userLevelList = ref<ComboModel[]>([]);
const schoolList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>([
{ value: "初一", text: "初一" },
{ value: "初二", text: "初二" },
{ value: "初三", text: "初三" },
{ value: "高一", text: "高一" },
{ value: "高二", text: "高二" },
{ value: "高三", text: "高三" }
]);
const classList = ref<ComboModel[]>([]);
const subjectList = ref<ComboModel[]>([]);
const positionList = ref<any[]>([]);
const table = reactive<TableData>({
data: [],
selectRows: [],
sort: "",
border: true
});
const pagination = reactive<PaginationData>({
index: 1,
size: 10,
total: 0
});
const dialog = reactive<DialogData>({
close: false,
update: {
title: "",
visible: false,
width: "800px"
},
editLevel: {
userIds: [],
title: "",
visible: false,
width: "400px"
},
editSubjectLevel: {
userIds: [],
title: "",
visible: false,
width: "450px"
},
bindUser: {
title: "分配权限码",
visible: false,
width: "1150px",
height: ""
},
userBindInfo: {
title: "用户权限码",
visible: false,
width: "1150px",
height: ""
}
});
const checkUserBindInfo = () => {
if (table.selectRows.length != 1) {
ElMessage.warning("请选择一个用户");
return;
}
dialog.userBindInfo.visible = true;
};
const showPosition = (row: UserDetail) => {
if (showAllPosition.value.includes(row)) {
showAllPosition.value.splice(showAllPosition.value.indexOf(row), 1);
} else {
showAllPosition.value.push(row);
}
};
const codeBindUser = () => {
if (table.selectRows.length == 0) {
ElMessage.warning("请选择需要分配权限的用户");
return;
}
dialog.bindUser.visible = true;
};
const initSearchData = () => {
if (props.searchData !== undefined) {
for (const key in props.searchData) {
search[key] = props.searchData[key];
}
}
};
const selectUserClick = (row: UserDetail) => {
if (props.selectUser) {
selectUserTable.value.toggleRowSelection(row);
}
};
const setCurrent = (row: UserDetail) => {
selectUserTable.value.toggleRowSelection(row);
};
const selectUserCallBack = () => {
const u = table.selectRows;
props.selectCallBack(u);
selectUserTable.value.clearSelection();
};
const exportUser = async () => {
const data = {
SearchStr: search.searchStr,
UserType: search.userType || 0,
Level: search.level || 0,
SchoolId: search.schoolId || 0,
GraduationYear: search.graduationYear || 0,
Grade: search.grade,
ClassId: search.classId || 0,
SubjectId: search.subjectId || 0,
PositionId: search.positionId || 0,
PageIndex: pagination.index,
PageSize: pagination.size
};
// const res = await exportUserApi(data);
// if (res.type === "application/json") {
// const json = await readerBlob(res);
// if (json !== undefined && json.code !== 200) {
// ElMessage.error(json.Message);
// }
// } else if (res !== undefined && res.size !== 0) {
// ElMessage.success("🕖");
// const url = 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);
// } else {
// ElMessage.warning(",");
// }
};
const fetchInitData = async () => {
schoolList.value = (await getSchoolData()).data;
subjectList.value = (await getenum("SubjectEnum")).data;
userLevelList.value = (await getenum("StudentLevelEnum")).data;
userTypeList.value = (await getenum("UserTypeEnum")).data;
};
const userTypeChange = () => {
search.level = "";
};
const schoolChange = () => {
search.graduationYear = "";
search.grade = "";
search.classId = "";
search.subjectId = "";
getClass();
};
const gradeChange = () => {
search.classId = "";
search.subjectId = "";
getClass();
};
const getClass = () => {
const data = {
schoolId: search.schoolId || 0,
graduationYear: search.graduationYear || 0,
grade: search.grade
};
getClassCombo(data).then(res => {
if (res.code === 200) {
classList.value = res.data;
}
});
};
const fetchPagedData = (searchUnUse = false) => {
const data = {
SearchStr: search.searchStr,
UserType: 2,
Level: search.level || 0,
SchoolId: search.schoolId || 0,
GraduationYear: search.graduationYear || 0,
Grade: search.grade,
ClassId: search.classId || 0,
SubjectId: search.subjectId || 0,
PositionId: search.positionId || 0,
PageIndex: pagination.index,
PageSize: pagination.size,
UnUsed: searchUnUse
};
getPageUserList(data).then(res => {
if (res.code === 200) {
pagination.total = res.data.total;
res.data.data.forEach(item => {
if (item.positions) {
item.positions = PositionsSort(item.positions);
}
});
table.data = res.data.data;
}
});
};
const handleSelectionChange = (selection: UserDetail[]) => {
table.selectRows = selection;
};
const handleDelete = () => {
// if (table.selectRows.length === 0) {
// ElMessage.warning("");
// return;
// }
// const ids: number[] = [];
// table.selectRows.forEach(it => {
// ids.push(it.Id);
// });
// ElMessageBox.confirm(", ?", "", {
// confirmButtonText: "",
// cancelButtonText: "",
// type: "warning"
// }).then(() => {
// delUser(ids).then(res => {
// if (res.code === 200) {
// handleReloadPaged();
// ElMessage.success("");
// } else {
// ElMessage.error(res.Message);
// }
// });
// });
};
const handleRestPass = async () => {
// if (table.selectRows.length === 0) {
// ElMessage.warning("");
// } else {
// const ids: number[] = [];
// table.selectRows.forEach(it => {
// ids.push(it.Id);
// });
// try {
// await ElMessageBox.confirm("?", "", {
// confirmButtonText: "",
// cancelButtonText: "",
// type: "warning"
// });
// const res = await restUserPass(ids);
// if (res.code === 200) {
// ElMessage.success("");
// } else {
// ElMessage.error("");
// }
// } catch (error) {
// // User cancelled
// }
// }
};
const getUserTypeTag = (type: number) => {
return type === 1 ? "info" : "warning";
};
const getUserLevelTag = (level: number) => {
return level === 0
? "info"
: level === 1
? "success"
: level === 2
? "warning"
: "error";
};
const getUserLevelText = (level: number) => {
const r = userLevelList.value.filter(w => w.value === level);
if (r.length > 0) {
return r[0].text;
}
return "";
};
const PositionsSort = (arr: Position[]) => {
arr.sort((a, b) => {
if (a.enable === b.enable) {
return 0;
} else if (a.enable) {
return -1;
} else {
return 1;
}
});
return arr;
};
const handleReloadPaged = (event?: any, searchUnUse?: boolean) => {
pagination.index = 1;
table.selectRows = [];
fetchPagedData(searchUnUse);
};
function pageSizeChange(o) {
pagination.size = o;
fetchPagedData();
}
function pageIndexChange(o) {
pagination.index = o - 1;
fetchPagedData();
}
const AddDialog = () => {
editId.value = 0;
dialog.update.title = "添加用户";
dialog.update.visible = true;
};
const EditDialog = (row?) => {
if (row == null && table.selectRows.length !== 1) {
ElMessage.warning("请选择要修改用户");
return;
}
dialog.update.title = "修改用户";
editId.value = row != null ? row.id : table.selectRows[0].id;
dialog.update.visible = true;
};
const handleAddCallback = () => {
dialog.update.visible = false;
handleReloadPaged();
};
const handleEditLevel = () => {
if (table.selectRows.length === 0) {
ElMessage.warning("请选择要修改用户");
return;
}
dialog.editLevel.title = "修改学生层次";
dialog.editLevel.userIds = table.selectRows.map(w => w.id);
dialog.editLevel.visible = true;
};
const handleEditLevelCallback = () => {
dialog.editLevel.visible = false;
handleReloadPaged();
};
const handleEditSubjectLevel = () => {
if (table.selectRows.length === 0) {
ElMessage.warning("请选择要修改用户");
return;
}
dialog.editSubjectLevel.title = "修改学生科目层次";
dialog.editSubjectLevel.userIds = table.selectRows.map(w => w.id);
dialog.editSubjectLevel.visible = true;
};
const handleEditSubjectLevelCallback = () => {
dialog.editSubjectLevel.visible = false;
handleReloadPaged();
};
const importData = () => {
// const fileE = document.createElement("input");
// fileE.type = "file";
// const formData = new FormData();
// fileE.onchange = async function () {
// formData.append("File", fileE.files![0]);
// const res = await ImporExportTemplate(formData);
// if (res.type === "application/json") {
// const json = await readerBlob(res);
// if (json !== undefined && json.code !== 200) {
// return ElMessage.error(json.Message);
// } else {
// return ElMessage.success(",");
// }
// } else if (res === undefined || res.size === 0) {
// ElMessage.success(",");
// return;
// }
// 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) {
// console.error(error);
// }
};
const readerBlob = (data: Blob): Promise<any> => {
return new Promise(resolve => {
const reader = new FileReader();
reader.readAsText(data, "utf-8");
reader.onload = function () {
const dd = JSON.parse(reader.result as string);
resolve(dd);
};
});
};
const downLoadImportUsersTemplate = () => {
window.open(excelImportUsersUrl, "_blank");
};
onMounted(async () => {
await initSearchData();
await fetchInitData();
fetchPagedData();
});
</script>
<style lang="scss" scoped>
.userTagRowItop {
transform: rotate(180deg) !important;
}
.userTagRow i {
padding-top: 3px;
transform: rotate(0deg);
font-size: 1.3rem;
cursor: pointer;
}
.userTagRow {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
.subjectTagEnableDiv {
display: flex;
padding: 3px;
gap: 5px;
}
.classTag {
color: #a3bf08 !important;
background-color: #f4fbd1 !important;
border-color: #f4fbd1 !important;
}
.subjectTag {
color: #eb0de4 !important;
background-color: #fbd9ff !important;
border-color: #fbd9ff !important;
}
.subjectlevel_ul {
margin: 0;
padding: 0;
list-style: none;
}
.toolbar-container {
margin-bottom: 5px;
}
</style>

View File

@ -0,0 +1,522 @@
<template>
<div class="app-container">
<div
style="color: #606266; font-size: 1.5em; font-weight: bold"
v-if="selectionCount == 1"
>
双击选中职位
</div>
<h2></h2>
<div class="search-container1">
<!-- 搜索项目 -->
<el-form :inline="true" :model="search">
<el-form-item>
<el-select
v-model="search.schoolId"
placeholder="学校"
clearable
filterable
@change="schoolChange"
>
<el-option
v-for="item in schoolList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.positionType"
placeholder="职位类型"
clearable
filterable
:disabled="userType > 0"
>
<el-option
v-for="item in positionList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.grade"
placeholder="年级"
clearable
filterable
@change="gradeChange"
>
<el-option
v-for="item in gradeList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.classId"
placeholder="班级"
clearable
filterable
>
<el-option
v-for="item in classList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.subjectId"
placeholder="科目"
clearable
filterable
>
<el-option
v-for="item in subjectList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="search.status"
placeholder="状态"
clearable
filterable
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleReloadPaged"
icon="el-icon-search"
>查询</el-button
>
</el-form-item>
<el-form-item>
<el-button type="success" @click="asyncPosition"
>选择勾选职位</el-button
>
</el-form-item>
</el-form>
</div>
<!-- <div class="toolbar-container">
<el-button type="success" plain @click="handleAdd"
>新增</el-button
>
<el-button type="primary" plain @click="handleEdit"
>修改</el-button
>
</div> -->
<div class="tableBox">
<el-table
:data="table.data"
ref="positionTb"
row-key="id"
:border="table.border"
@row-dblclick="rowDblclick"
height="600px"
style="width: 768px; max-width: 768px"
:expand-row-keys="tableExpandRowKeys"
:tree-props="{ children: 'children' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" />
<el-table-column
prop="name"
label="职位名称[双击行快速选择]"
width="220"
>
<template #default="scope">
<span
>{{ scope.row.name }}
<el-tag v-show="selectPositions.find(s => scope.row.Id == s.id)"
>已选</el-tag
>
</span>
</template>
</el-table-column>
<el-table-column prop="positionLevel" label="职级" width="80">
<template #default="scope">
<el-tag v-if="scope.row.positionLevel === 1" type="danger"
>教委</el-tag
>
<el-tag v-else-if="scope.row.positionLevel === 2" type="warning"
>校级</el-tag
>
<el-tag v-else-if="scope.row.positionLevel === 3">年级</el-tag>
<el-tag v-else-if="scope.row.positionLevel === 4" type="success"
>班级</el-tag
>
<el-tag v-else-if="scope.row.positionLevel === 5" type="info"
>教师</el-tag
>
<el-tag v-else-if="scope.row.positionType === 1" type="info"
>学生</el-tag
>
</template>
</el-table-column>
<el-table-column label="学校 - 年级 - 班级 - 学科">
<template #default="scope">
<el-tag v-if="!scope.row.status" type="danger">锁定</el-tag>
{{ scope.row.schoolName }} {{ scope.row.grade }}
{{ scope.row.className }} {{ scope.row.subjectName }}
</template>
</el-table-column>
</el-table>
<el-card class="box-card">
<div class="clearfix clearfixCss">
<span style="line-height: 32px; font-weight: 600"
>已选职位[{{ selectPositions.length }}]</span
>
<el-button
style="float: right"
type="success"
@click="handleConfirm"
icon="el-icon-check"
>提交分配职位</el-button
>
</div>
<div class="positionGap">
<el-tag
v-for="(p, o) in selectPositions"
:key="o"
closable
@close="tagClose(p)"
>
{{ p.name }} {{ p.schoolName }} {{ p.grade }} {{ p.className }}
{{ p.subjectName }}
</el-tag>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { FormInstance } from "element-plus";
import {
getSchoolData,
getClassCombo,
getSubjectData,
getPositionList,
getPositions,
Position
} from "@/api/userCenter";
import { ComboModel } from "@/components/hTable/hTable";
interface SearchParams {
schoolId: string | number;
positionType: string | number;
grade: string;
classId: string | number;
subjectId: string | number;
status: string | number;
}
interface TableData {
data: Position[];
selectRows: Position[];
border: boolean;
}
interface Dialog {
id: number;
parentPosition: Position | null;
close: boolean;
title: string;
visible: boolean;
width: string;
}
const props = defineProps({
userType: {
type: Number,
default: 0
},
positions: {
type: Array as () => number[],
default: () => []
},
selectionCount: {
type: Number,
default: 999
}
});
const emit = defineEmits(["handleCheckCallback", "handleCheckCallback"]);
const search = reactive<SearchParams>({
schoolId: "",
positionType: "",
grade: "",
classId: "",
subjectId: "",
status: ""
});
const selectPositions = ref<Position[]>([]);
const schoolList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>([
{ value: "初一", text: "初一" },
{ value: "初二", text: "初二" },
{ value: "初三", text: "初三" },
{ value: "高一", text: "高一" },
{ value: "高二", text: "高二" },
{ value: "高三", text: "高三" }
]);
const classList = ref<ComboModel[]>([]);
const subjectList = ref<ComboModel[]>([]);
const tableExpandRowKeys = ref<string[]>([]);
const positionList = ref<ComboModel[]>([
{ text: "学生", value: 1 },
{ text: "教师", value: 2 },
{ text: "管理员", value: 3 }
]);
const statusList = ref<ComboModel[]>([
{ text: "正常", value: 1 },
{ text: "锁定", value: 2 }
]);
const table = reactive<TableData>({
data: [],
selectRows: [],
border: true
});
const dialog = reactive<Dialog>({
id: 0,
parentPosition: null,
close: false,
title: "编辑职位",
visible: false,
width: "800px"
});
const authDialog = reactive<Dialog>({
id: 0,
parentPosition: null,
close: false,
title: "职位授权",
visible: false,
width: "400px"
});
const positionTb = ref<FormInstance>();
const userTypeToPosition = () => {
switch (props.userType) {
case 1:
search.positionType = 1;
break;
case 2:
search.positionType = 2;
break;
case 15:
search.positionType = 3;
break;
default:
search.positionType = -1;
break;
}
};
const rowDblclick = (row: Position) => {
if (props.selectionCount === 1) {
emit("handleCheckCallback", [row]);
} else {
asyncPosition(null, [row]);
}
};
const handleReloadPaged = () => {
fetchPagedData();
};
const handleAdd = () => {
if (table.selectRows.length > 0) {
dialog.parentPosition = table.selectRows[0];
dialog.title = `${table.selectRows[0].name}-添加附属职位`;
} else {
dialog.title = "添加职位";
dialog.parentPosition = null;
}
dialog.id = 0;
dialog.visible = true;
};
const handleEdit = () => {
if (table.selectRows.length === 0) {
ElMessage.warning("未勾选记录");
return;
}
if (table.selectRows.length > 1) {
ElMessage.warning("当前操作只支持勾选一条记录");
return;
}
dialog.id = table.selectRows[0].id;
dialog.parentPosition = null;
dialog.title = "编辑职位";
dialog.visible = true;
};
const handleRefreshCallback = () => {
dialog.visible = false;
authDialog.visible = false;
handleReloadPaged();
};
const handleSelectionChange = (selection: Position[]) => {
table.selectRows = selection;
};
const fetchInitData = async () => {
const schoolRes = await getSchoolData();
if (schoolRes.code === 200) {
schoolList.value = schoolRes.data;
if (schoolList.value.length > 0) {
search.schoolId = schoolList.value[0].value;
schoolChange();
fetchPagedData();
}
}
const subjectRes = await getSubjectData();
if (subjectRes.code === 200) {
subjectList.value = subjectRes.data;
}
};
const schoolChange = () => {
search.grade = "";
search.classId = "";
search.subjectId = "";
getClass();
};
const gradeChange = () => {
search.classId = "";
search.subjectId = "";
getClass();
};
const getClass = async () => {
const data = {
schoolId: search.schoolId || 0,
grade: search.grade
};
const res = await getClassCombo(data);
if (res.code === 200) {
classList.value = res.data;
}
};
const fetchPagedData = async () => {
const data = {
SchoolId: search.schoolId || 0,
Grade: search.grade,
ClassId: search.classId || 0,
SubjectId: search.subjectId || 0,
PositionType: search.positionType || 0,
Status: search.status || 0
};
const res = await getPositionList(data);
if (res.code === 200) {
table.data = res.data;
tableExpandRowKeys.value = table.data.map(s => s.id.toString());
}
};
const handleConfirm = () => {
if (selectPositions.value.length === 0) {
ElMessage.warning("请选择要分配的职位");
return;
}
emit("handleCheckCallback", selectPositions.value);
};
const tagClose = (p: Position) => {
selectPositions.value = selectPositions.value.filter(s => s !== p);
};
const asyncPosition = (event: Event | null, rows?: Position[]) => {
const datas = rows || table.selectRows;
if (datas.length === 0) {
ElMessage.warning("请选择要分配的职位");
return;
}
const pIds = selectPositions.value.map(s => s.id);
selectPositions.value = selectPositions.value.concat(
datas.filter(s => !pIds.includes(s.id))
);
};
onMounted(async () => {
userTypeToPosition();
if (props.positions && props.positions.length > 0) {
const res = await getPositions(props.positions);
selectPositions.value = res.data.map(s => ({ ...s }));
}
fetchInitData();
});
</script>
<style scoped>
.tableBox {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: flex-start;
align-items: flex-start;
gap: 10px;
}
:deep(.el-card__header) {
padding: 8px 8px;
}
.clearfixCss {
width: 350px;
padding-bottom: 10px;
}
.positionGap {
height: calc(600px - 90px);
overflow-x: auto;
width: 350px;
gap: 10px;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: flex-start;
}
</style>