Compare commits

...

39 Commits

Author SHA1 Message Date
hy e75843bcf8 Merge pull request 'staging' (#23) from staging into master
Reviewed-on: #23
2025-10-14 11:13:16 +08:00
hy 67aa84d0b3 Merge pull request '新增 管理员导入功能' (#22) from dev into staging
Reviewed-on: #22
2025-10-14 11:08:03 +08:00
小肥羊 a1865d4dbb 新增 管理员导入功能 2025-10-14 10:57:08 +08:00
hy e2eb3b079e Merge pull request '修复 老师管理年级显示' (#21) from dev into staging
Reviewed-on: #21
2025-09-25 17:16:34 +08:00
小肥羊 2663daeeeb 修复 老师管理年级显示 2025-09-25 17:13:18 +08:00
hy 2df9611b90 Merge pull request 'dev' (#20) from dev into staging
Reviewed-on: #20
2025-09-25 17:05:32 +08:00
小肥羊 7210002c69 优化 学校预览界面 2025-09-25 16:57:12 +08:00
小肥羊 f7276250f4 删除 debugger 2025-09-25 16:44:25 +08:00
hy de8c416266 Merge pull request '优化 赴校任务超时时间' (#19) from dev into staging
Reviewed-on: #19
2025-09-25 16:41:34 +08:00
小肥羊 e5b5958b97 优化 赴校任务超时时间 2025-09-25 16:38:10 +08:00
hy 7cf08fcf14 Merge pull request 'dev' (#18) from dev into staging
Reviewed-on: #18
2025-09-25 15:48:08 +08:00
小肥羊 19bdc1dc65 新增 创建赴校信息时候 结束事件未录入 2025-09-25 14:00:41 +08:00
小肥羊 4bc9a0a606 修复 数据更改后没折叠表单 2025-09-25 13:51:19 +08:00
小肥羊 b56c9f819a 新增 学校参与项目 2025-09-25 11:39:05 +08:00
hy fd2fcc2c1b Merge pull request '优化 班级成绩分析的 分段展示' (#17) from dev into staging
Reviewed-on: #17
2025-09-24 19:18:56 +08:00
小肥羊 14b6a200af 优化 班级成绩分析的 分段展示 2025-09-24 19:17:01 +08:00
hy 823a8defc8 Merge pull request 'dev' (#16) from dev into staging
Reviewed-on: #16
2025-09-14 15:47:28 +08:00
小肥羊 c66ef64b90 优化 学校教师 新增编辑职位的流程 2025-09-12 18:31:58 +08:00
小肥羊 85173e34ee 修改 新增用户完善描述 2025-09-11 18:28:11 +08:00
小肥羊 f29c76ba84 优化 学生老师的新增表单 2025-09-11 18:27:06 +08:00
小肥羊 cf856c98f7 新增 赴校信息问题记录
修复部分 年级显示为->高2027届
2025-09-05 18:16:22 +08:00
小肥羊 b8db637311 新增 考试学科排序 2025-09-04 18:36:47 +08:00
小肥羊 492a3e8163 新增 排序功能
优化 旧版本配置
2025-09-04 18:34:14 +08:00
小肥羊 85a4ee0ef6 更新 动态表单的配置项 2025-09-02 18:07:28 +08:00
小肥羊 eb306eab72 新增 选择职位时的重置按钮 2025-09-01 17:56:58 +08:00
hy 3ce926319b Merge pull request '新增 预览学校的功能' (#14) from dev into staging
Reviewed-on: #14
2025-08-27 17:19:39 +08:00
hy 5cad1c2d2b Merge pull request '修复 导入Excel时出现异常 未能获取到提示信息' (#13) from dev into staging
Reviewed-on: #13
2025-08-27 15:46:12 +08:00
hy f92ba97062 Merge pull request 'dev' (#12) from dev into staging
Reviewed-on: #12
2025-08-27 10:12:19 +08:00
hy c80928a602 Merge pull request 'dev' (#9) from dev into staging
Reviewed-on: #9
2025-08-26 20:09:40 +08:00
hy d5d8961c8b Merge pull request 'dev' (#7) from dev into staging
Reviewed-on: #7
2025-08-26 18:58:58 +08:00
tiananlin bf49072325 更新 Dockerfiles/Dockerfile-production/Dockerfile 2025-08-26 15:22:12 +08:00
tiananlin 4a77cebe93 添加 Dockerfiles/Dockerfile-production/default.conf 2025-08-26 15:17:23 +08:00
tiananlin 96a74b0107 添加 Dockerfiles/Dockerfile-production/Dockerfile 2025-08-26 15:16:55 +08:00
hy 23b9f0a1d1 Merge pull request 'dev' (#6) from dev into staging
Reviewed-on: #6
2025-08-26 14:38:25 +08:00
hy f43d582530 Merge pull request 'dev' (#5) from dev into staging
Reviewed-on: #5
2025-08-26 10:23:47 +08:00
hy 832b6303f0 Merge pull request '完善 新的学生补助字段' (#4) from dev into staging
Reviewed-on: #4
2025-08-25 16:53:10 +08:00
tiananlin 0e7784970a Merge pull request '更新 Dockerfiles/Dockerfile-staging/default.conf' (#3) from dev into staging
Reviewed-on: #3
2025-08-25 15:31:30 +08:00
tiananlin 20cbc5b7b8 更新 Dockerfiles/Dockerfile-staging/default.conf 2025-08-25 15:30:48 +08:00
hy f8f6e298cd Merge pull request '删除 登录时的默认信息' (#2) from dev into staging
Reviewed-on: #2
2025-08-25 15:23:16 +08:00
34 changed files with 2156 additions and 1004 deletions

View File

@ -7,8 +7,12 @@ VITE_PUBLIC_PATH = /
# 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数" # 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash" VITE_ROUTER_HISTORY = "hash"
VITE_API_BASEURL = "http://192.168.2.33:5199/api"
# 接口地址 # 接口地址
# VITE_API_BASEURL = "http://localhost:5199/api" VITE_API_BASEURL = "http://192.168.2.33:5199/api"
#数据中心后台地址 #数据中心后台地址
VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api" VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api"
# # 接口地址
# VITE_API_BASEURL = "https://learn-archives-admin-dev.23544.com/api"
# #数据中心后台地址
# VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api"

25
src/api/admin.ts Normal file
View File

@ -0,0 +1,25 @@
import { http } from "@/utils/http";
import { Res } from "@/utils/http/types";
// import type { Res } from "@/utils/http/types";
/**
* @description
* @return {object}
*/
export function ImportAdminInfo( file: File) {
let formData = new FormData();
formData.append("file", file);
return http.request<any>(
"post",
`Admin/Import`,
{
data: formData
},
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
responseType: "blob"
}
);
}

View File

@ -1,4 +1,5 @@
import { http } from "@/utils/http"; import { http } from "@/utils/http";
import { Res } from "@/utils/http/types";
// import type { Res } from "@/utils/http/types"; // import type { Res } from "@/utils/http/types";
/** /**
@ -29,7 +30,31 @@ export function ImportExamInfo(id: number, file: File) {
* @return {object} * @return {object}
*/ */
export function DeleteExamInfo(data: { classId: number; examId: number }) { export function DeleteExamInfo(data: { classId: number; examId: number }) {
return http.request<any>("post", `ExamClassInfo/DeleteExamInfo`, { return http.request<Res<any>>("post", `ExamClassInfo/DeleteExamInfo`, {
data data
}); });
} }
/**
* @description
* @return {object}
*/
export function RecalculateExamRankings(examId: Number) {
return http.request<Res<any>>(
"get",
`ExamClassInfo/RecalculateExamRankings`,
{
params: { examId }
}
);
}
/**
* @description
* @return {object}
*/
export function ClassRanking(examId: Number,classId: Number) {
return http.request<Res<any>>("get", `ExamClassInfo/ClassRanking`, {
params: { examId, classId }
});
}

View File

@ -22,7 +22,7 @@ export class hTableAPI {
delete(data) { delete(data) {
return http.request<Res<any>>("post", `${this.url}/Del`, { data }); return http.request<Res<any>>("post", `${this.url}/Del`, { data });
} }
querycombo(data) { querycombo(data = {}) {
return http.request<Res<ComboModel[]>>("post", `${this.url}/QueryCombo`, { return http.request<Res<ComboModel[]>>("post", `${this.url}/QueryCombo`, {
data data
}); });

View File

@ -72,3 +72,13 @@ export function PageList(data) {
export function StudentInfo(uid) { export function StudentInfo(uid) {
return http.request<Res<any>>("get", `Student/Info?uid=${uid}`); return http.request<Res<any>>("get", `Student/Info?uid=${uid}`);
} }
/**
* @description id
* @return {object}
*/
export function PosititonIds(data:any[]) {
return http.request<Res<number[]>>("post", `Student/PosititonIds`, {
data
});
}

View File

@ -1,6 +1,7 @@
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel } from "@/components/hTable/hTable";
import { http } from "@/utils/http"; import { http } from "@/utils/http";
import type { Res, ResPage } from "@/utils/http/types"; import type { Res, ResPage } from "@/utils/http/types";
import { Ref } from "vue";
/** /**
* @description * @description
* @param {string} type type=StatusEnum * @param {string} type type=StatusEnum
@ -151,12 +152,14 @@ export interface Position {
graduationYear: number; graduationYear: number;
grade: string; grade: string;
classId: number; classId: number;
gradeLevel: string;
className: string; className: string;
subjectId: number; subjectId: number;
subjectName: string; subjectName: string;
positionType: number; positionType: number;
positionLevel: number; positionLevel: number;
status: boolean; status: boolean;
classList?: ComboModel[];
} }
/** /**

View File

@ -1,3 +1,5 @@
import { array } from "vue-types";
export interface Dialog { export interface Dialog {
/** 对话框是否可见 */ /** 对话框是否可见 */
visible: boolean; visible: boolean;
@ -93,13 +95,18 @@ export enum ConditionalType {
} }
/** 字段设置项 */ /** 字段设置项 */
export interface FieldSetting { export class FieldSetting {
constructor() {
this.datasource = [];
this.mapValue= "value",
this.maplabel= "text"
}
/**map 时Value的取值的属性 */ /**map 时Value的取值的属性 */
mapValue?: string; mapValue?: string;
/**map 时label的取值的属性 */ /**map 时label的取值的属性 */
maplabel?: string; maplabel?: string;
/** 数据源 请求方式 */
datasourceStr?: string;
/** /**
* *
* @param value * @param value
@ -116,44 +123,152 @@ export interface ComboModel {
value: any; value: any;
text: string; text: string;
} }
/** 表格列配置 */ /** 新增修改的配置 */
export interface TableColumn { export class TableColumnEdit {
/** 显示标签 */ /**
label: string; *
/** 是否可搜索 */ * @param add
search: boolean; * @param edit
/** 搜索类型 */ * @param multiple [type=dropdown]
searchType?: ConditionalType; * @param editShow
* @param editRows [type=textarea]
* @param rules
*/
constructor(
add: boolean = false,
edit: boolean = false,
multiple: boolean = false,
editShow: boolean = true,
editRows: number = 3,
editDefault: any = "",
rules: any | Array<any> = null,
change: () => void = null
) {
this.add = add;
this.edit = edit;
this.multiple = multiple;
this.editShow = editShow;
this.editRows = editRows;
this.editDefault = editDefault;
this.rules = rules;
this.change = change;
}
/** 是否允许添加 [false]*/ /** 是否允许添加 [false]*/
add?: boolean; add?: boolean;
/** 是否允许修改 [false]*/ /** 是否允许修改 [false]*/
edit?: boolean; edit?: boolean;
/** 列宽度 [auto]*/ /** [type=dropdown]是否多选 */
width?: string;
/** 字段类型 */
type?: "string" | "dropdown" | "switch" | "img" | "datetime" | "textarea";
/** 是否多选 */
multiple?: boolean; multiple?: boolean;
/** 编辑时显示列 */ /** 编辑时显示列 */
editShow?: boolean; editShow?: boolean;
/**校验规则 */ /**校验规则 */
rules?: any | Array<any>; rules?: any | Array<any>;
/** 显示列 */ /** [type=textarea]编辑时的行数 */
editRows?: number;
/** 新增编辑时的缓存值 */
valueE?: Array<string> | string | number | boolean | Date;
/** 新增编辑时的默认 */
editDefault?: Array<string> | string | number | boolean | Date;
/**编辑时值发生变化 */
change?: () => void;
}
/** 查询的配置 */
export class TableColumnSearch {
/**
*
* @param yes
* @param sort
* @param searchType
*/
constructor(yes: boolean = false,
searchType: ConditionalType = ConditionalType.Like,sort: boolean = false){
this.yes = yes;
this.sort = sort;
this.searchType = searchType;
}
/**可以查询 [false] */
yes?: boolean;
/** 可以排序 [false]*/
sort?: boolean;
/** 搜索类型 */
searchType?: ConditionalType;
/** 查询缓存值 */
value?: Array<string> | string | number | boolean | Date;
}
/** 表格列配置 */
export class TableColumn {
constructor() {
this.type = "string";
this.show = true;
this.search = new TableColumnSearch();
this.edit = new TableColumnEdit();
this.fixed = false;
this.setting = {};
}
/** 显示标签 */
label: string;
fixed?: 'left' | 'right'| boolean;
/** 查询配置 */
search?: TableColumnSearch;
/** 编辑配置 */
edit?: TableColumnEdit;
/** Table中展示宽度 [auto]*/
width?: string;
/** 字段类型 */
type?: "string" | "dropdown" | "switch" | "img" | "datetime" | "textarea";
/** 显示列 [不会动态计算]*/
show?: boolean; show?: boolean;
/** 字段设置 */ /** 字段设置 */
setting?: FieldSetting; setting?: FieldSetting;
/** 修改时的编辑值 */
valueE?: Array<string> | string | number | boolean | Date;
/** 查询值 */
value?: Array<string> | string | number | boolean | Date;
/** textarea编辑时的行数 */
editRows?: number;
/**编辑时值发生变化 */
change?: () => void;
/**列值初始化时 如何获取默认取对应列*/ /**列值初始化时 如何获取默认取对应列*/
custom?: (row: any) => string; custom?: (row: any) => string;
} }
/** 表格列配置 */
// export interface TableColumn {
// /** 显示标签 */
// label: string;
// /** 是否可搜索 */
// search: boolean;
// /** 搜索类型 */
// searchType?: ConditionalType;
// /** 是否允许添加 [false]*/
// add?: boolean;
// /** 是否允许修改 [false]*/
// edit?: boolean;
// /** Table中展示宽度 [auto]*/
// width?: string;
// /** 字段类型 */
// type?: "string" | "dropdown" | "switch" | "img" | "datetime" | "textarea";
// /** 是否多选 */
// multiple?: boolean;
// /** 编辑时显示列 */
// editShow?: boolean;
// /**校验规则 */
// rules?: any | Array<any>;
// /** 显示列 */
// show?: boolean;
// /** 字段设置 */
// setting?: FieldSetting;
// /** 修改时的编辑值 */
// valueE?: Array<string> | string | number | boolean | Date;
// /** 查询值 */
// value?: Array<string> | string | number | boolean | Date;
// /** textarea编辑时的行数 */
// editRows?: number;
// /**编辑时值发生变化 */
// change?: () => void;
// /**列值初始化时 如何获取默认取对应列*/
// custom?: (row: any) => string;
// }
/** 分页数据 */ /** 分页数据 */
export interface PageData { export interface PageData {
/** 总条数 */ /** 总条数 */
@ -173,24 +288,39 @@ export interface ConditionalModel {
} }
/** 搜索条件 */ /** 搜索条件 */
export interface SearchConditions { export class SearchConditions {
/**
*
*/
constructor() {
this.show = true;
this.showPage = true;
this.PageIndex = 0;
this.PageSize = 20;
this.OrderBy = "Id";
this.OrderByType = 1;
this.defaultConditions = [];
this.Conditions = [];
}
/** 是否显示搜索 */ /** 是否显示搜索 */
show: boolean; show?: boolean;
/** 显示分页器 */
showPage?:boolean;
/** 当前页码 */ /** 当前页码 */
PageIndex: number; PageIndex?: number;
/** 每页大小 */ /** 每页大小 */
PageSize: number; PageSize?: number;
/** 排序字段 */ /** 排序字段 */
OrderBy: string; OrderBy?: string;
/** /**
* @tips 0:升序 1:降序 * @tips 0:升序 1:降序
* @默认 = 1 * @默认 = 1
*/ */
OrderByType?: 0 | 1; OrderByType?: 0 | 1;
/** 默认查询条件 */ /** 默认查询条件 */
defaultConditions: ConditionalModel[]; defaultConditions?: ConditionalModel[];
/** 查询条件 */ /** 查询条件 */
Conditions: any[]; Conditions?: any[];
} }
/** 表格配置 */ /** 表格配置 */
@ -199,6 +329,8 @@ export interface TableConfig {
searchCallback?: (s: SearchConditions) => void; searchCallback?: (s: SearchConditions) => void;
/** 新增/修改回调函数 */ /** 新增/修改回调函数 */
editCallback?: (from: any) => void; editCallback?: (from: any) => void;
/** 展开行的回调 */
expandChange?: (row: any, expandedRows: any[]) => void;
/** API地址 */ /** API地址 */
apiUrl: string; apiUrl: string;
/** 是否显示选择列 */ /** 是否显示选择列 */
@ -207,6 +339,10 @@ export interface TableConfig {
search: SearchConditions; search: SearchConditions;
/** 是否显示操作列 */ /** 是否显示操作列 */
operationColumn: boolean; operationColumn: boolean;
/** 操作列是否固定列 */
operationColumnFixed?: "left" | "right" | boolean;
/** 是否允许展开列 */
expandColumn?: boolean;
/** 操作按钮配置 */ /** 操作按钮配置 */
operationColumnData: OperationButton[]; operationColumnData: OperationButton[];
/** 列配置 */ /** 列配置 */
@ -224,3 +360,92 @@ export interface TableConfig {
/**是否显示 */ /**是否显示 */
show?: boolean; show?: boolean;
} }
/** 初始化表格数据 */
export function intTableData(tValue: TableConfig): TableConfig {
if (!tValue.data) tValue.data = [];
if (!tValue.selectRows) tValue.selectRows = [];
if (tValue.border == null) tValue.border = true;
if (tValue.operationColumnFixed == null) tValue.operationColumnFixed = false;
if (tValue.expandColumn == null) tValue.expandColumn = false;
if (!tValue.pageData) tValue.pageData = { total: 0 };
if (tValue.operationTop === undefined) tValue.operationTop = true;
//分页查询配置
tValue.search= { ...new SearchConditions(), ...tValue.search };
// 处理 column 的属性
for (const key in tValue.column) {
tValue.column[key] = { ...new TableColumn(), ...tValue.column[key] };
const element = tValue.column[key];
element.edit = { ...new TableColumnEdit(), ...element.edit };
element.search = { ...new TableColumnSearch(), ...element.search };
element.setting = { ...new FieldSetting(), ...element.setting };
//已有的情况下以 传入为主
if (element.custom != undefined) continue;
switch (element.type) {
case "switch":
case "dropdown": {
element.custom = row => {
const value = Array.isArray(row[key]) ? row[key] : [row[key]];
const res = value.map(item => {
const sc = element.setting.datasource.find(
s => s[element.setting.mapValue] + "" == item + ""
);
return !sc ? item : sc[element.setting.maplabel];
});
return res.join(",");
};
break;
}
case "string":
case undefined: {
element.custom = row => row[key];
break;
}
default: {
element.custom = row => row[key];
break;
}
}
}
// 处理 operationColumnData 的属性
for (const key in tValue.operationColumnData) {
const element = tValue.operationColumnData[key];
if (element.show === undefined) element.show = true;
}
return tValue;
}
/**
*
*/
export function gradeComboModel(): ComboModel[] {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
const isAfterAugust = currentMonth >= 9;
const baseYear = isAfterAugust ? currentYear : currentYear - 1;
const gradeOffsets = [
{ baseGradeName: "初", yearOffset: 0, grade: "初一" }, // 初一
{ baseGradeName: "初", yearOffset: 1, grade: "初二" }, // 初二
{ baseGradeName: "初", yearOffset: 2, grade: "初三" }, // 初三
{ baseGradeName: "高", yearOffset: 0, grade: "高一" }, // 高一
{ baseGradeName: "高", yearOffset: 1, grade: "高二" }, // 高二
{ baseGradeName: "高", yearOffset: 2, grade: "高三" }, // 高三
];
return gradeOffsets.map(
(item): ComboModel => {
const entranceYear = baseYear - item.yearOffset;
const graduationYear = entranceYear + 3;
let v = `${item.baseGradeName}${graduationYear}`;
return { text: v + ` [${item.grade}]`, value: v };
}
);
}

View File

@ -55,11 +55,11 @@ function intiColumn() {
for (const key in editData.value.table.column) { for (const key in editData.value.table.column) {
const element = editData.value.table.column[key]; const element = editData.value.table.column[key];
if (editData.value.isedit) { if (editData.value.isedit) {
if (element.edit) { if (element.edit.edit) {
column.value[key] = element; column.value[key] = element;
} }
} else { } else {
if (element.add) { if (element.edit.add) {
column.value[key] = element; column.value[key] = element;
} }
} }
@ -81,8 +81,8 @@ function handleSubmitForm() {
} else form.id = 0; } else form.id = 0;
for (const key in column.value) { for (const key in column.value) {
const element = column.value[key]; const element = column.value[key];
if (element.valueE !== null && element.valueE !== "") { if (element.edit.valueE !== null && element.edit.valueE !== "") {
form[key] = element.valueE; form[key] = element.edit.valueE;
} }
} }
if (editData.value.table.editCallback) { if (editData.value.table.editCallback) {
@ -102,28 +102,29 @@ function handleSubmitForm() {
function handleResetForm() { function handleResetForm() {
for (const key in column.value) { for (const key in column.value) {
let item = column.value[key]; let item = column.value[key];
if (Array.isArray(item.valueE)) { if (item.edit.editDefault != null) item.edit.valueE = item.edit.editDefault;
item.valueE = []; else if (Array.isArray(item.edit.valueE)) {
} else if (typeof item.valueE === "number") { item.edit.valueE = [];
item.valueE = ""; } else if (typeof item.edit.valueE === "number") {
} else if (typeof item.valueE === "boolean") { item.edit.valueE = "";
item.valueE = false; } else if (typeof item.edit.valueE === "boolean") {
item.edit.valueE = false;
} else { } else {
item.valueE = ""; item.edit.valueE = "";
} }
} }
} }
function fetchInitData() {} function fetchInitData() {}
function fetchFormData() { function fetchFormData() {
editData.value.loading = false; editData.value.loading = false;
if (editData.value.isedit) {
handleResetForm(); handleResetForm();
if (editData.value.isedit) {
Api.Info(props.id).then((res) => { Api.Info(props.id).then((res) => {
if (res.code === 200) { if (res.code === 200) {
editData.value.frorm = res.data; editData.value.frorm = res.data;
for (const key in column.value) { for (const key in column.value) {
const element = column.value[key]; const element = column.value[key];
element.valueE = res.data[key]; element.edit.valueE = res.data[key];
} }
} }
editData.value.loading = true; editData.value.loading = true;
@ -145,32 +146,32 @@ function fetchFormData() {
> >
<el-form-item <el-form-item
v-for="(o, k, i) in column" v-for="(o, k, i) in column"
v-show="execute(o['editShow'], o)" v-show="execute(o.edit['editShow'], o)"
:key="i" :key="i"
:rules="o.rules" :rules="o.edit.rules"
:prop="'' + k + '.valueE'" :prop="'' + k + '.edit.valueE'"
:label="o.label" :label="o.label"
> >
<div v-if="o.type.trim() == 'datetime'"> <div v-if="o.type.trim() == 'datetime'">
<el-date-picker <el-date-picker
v-model="o.valueE as Date" v-model="o.edit.valueE as Date"
type="date" type="date"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:placeholder="o.label" :placeholder="o.label"
class="elWidth" class="elWidth"
@change="o.change" @change="o.edit.change"
/> />
</div> </div>
<div v-else-if="o.type.trim() == 'dropdown'"> <div v-else-if="o.type.trim() == 'dropdown'">
<el-select <el-select
v-model="o.valueE" v-model="o.edit.valueE"
:multiple="o.multiple" :multiple="o.edit.multiple"
clearable clearable
filterable filterable
:placeholder="o.label" :placeholder="o.label"
class="elWidth" class="elWidth"
@change="o.change" @change="o.edit.change"
> >
<el-option <el-option
v-for="item in o.setting.datasource" v-for="item in o.setting.datasource"
@ -183,25 +184,29 @@ function fetchFormData() {
</div> </div>
<div v-else-if="o.type.trim() == 'textarea'"> <div v-else-if="o.type.trim() == 'textarea'">
<el-input <el-input
v-model="o.valueE as string" v-model="o.edit.valueE as string"
:rows="o.editRows || 4" :rows="o.edit.editRows || 4"
type="textarea" type="textarea"
:placeholder="o.label" :placeholder="o.label"
class="elWidth" class="elWidth"
@change="o.change" @change="o.edit.change"
/> />
</div> </div>
<div v-else-if="o.type.trim() == 'switch'"> <div v-else-if="o.type.trim() == 'switch'">
<el-switch <el-switch
v-model="o.valueE as boolean" v-model="o.edit.valueE as boolean"
active-text="启用" active-text="启用"
inactive-text="禁用" inactive-text="禁用"
class="elWidth" class="elWidth"
@change="o.change" @change="o.edit.change"
/> />
</div> </div>
<div v-else> <div v-else>
<el-input v-model="o.valueE as string" class="elWidth" :placeholder="o.label" /> <el-input
v-model="o.edit.valueE as string"
class="elWidth"
:placeholder="o.label"
/>
</div> </div>
</el-form-item> </el-form-item>

View File

@ -15,13 +15,16 @@ import {
} from "vue"; } from "vue";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox, TableColumnCtx } from "element-plus";
import { defineAsyncComponent, AsyncComponentLoader } from "vue"; import { defineAsyncComponent, AsyncComponentLoader } from "vue";
import { import {
ButtonCustomConfig, ButtonCustomConfig,
ConditionalType, ConditionalType,
Dialog, Dialog,
intTableData,
TableColumn, TableColumn,
TableColumnEdit,
TableColumnSearch,
TableConfig, TableConfig,
} from "./hTable"; } from "./hTable";
import hTableEdit from "./hTableEdit.vue"; import hTableEdit from "./hTableEdit.vue";
@ -41,7 +44,7 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
}); });
const expands = ref<any[]>();
const table = ref<TableConfig>(props.tableConfig); const table = ref<TableConfig>(props.tableConfig);
const tableShowColumn = ref<Record<string, TableColumn>>(); const tableShowColumn = ref<Record<string, TableColumn>>();
onBeforeMount(() => { onBeforeMount(() => {
@ -74,6 +77,7 @@ let Api: hTableAPI = null;
const init = ref(false); const init = ref(false);
const tableHeight = ref(0); const tableHeight = ref(0);
let defaultOrderBy = null;
const dialog = ref<Dialog>({ const dialog = ref<Dialog>({
visible: false, visible: false,
@ -107,36 +111,7 @@ function appStyle() {
} }
function intdata() { function intdata() {
if (!table.value.data) table.value.data = []; intTableData(table.value);
if (!table.value.selectRows) table.value.selectRows = [];
if (table.value.border == null) table.value.border = true;
if (!table.value.pageData) table.value.pageData = { total: 0 };
if (table.value.operationTop === undefined) table.value.operationTop = true;
// column
for (const key in table.value.column) {
const element = table.value.column[key];
if (element.add === undefined) element.add = false;
if (element.edit === undefined) element.edit = false;
// Vue 3 $set
if (element.valueE === undefined) element.valueE = element.multiple ? [] : "";
if (element.value === undefined) element.value = element.multiple ? [] : "";
if (!element.type) element.type = "string";
if (element.show === undefined) element.show = true;
if (element.editShow === undefined) element.editShow = true;
if (!element.setting)
element.setting = { datasource: [], mapValue: "value", maplabel: "text" };
if (!element.setting.mapValue) element.setting.mapValue = "value";
if (!element.setting.maplabel) element.setting.maplabel = "text";
if (!element.change) element.change = () => {};
}
// operationColumnData
for (const key in table.value.operationColumnData) {
const element = table.value.operationColumnData[key];
if (element.show === undefined) element.show = true;
}
} }
function rowKeyFun(row) { function rowKeyFun(row) {
@ -232,7 +207,8 @@ function handleDelete(obj, row) {
row.forEach((it) => { row.forEach((it) => {
ids.push(it.id); ids.push(it.id);
}); });
ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?").then(() => { ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?")
.then(() => {
Api.delete(ids).then((res) => { Api.delete(ids).then((res) => {
if (res.code === 200) { if (res.code === 200) {
handleReloadPaged(); handleReloadPaged();
@ -241,6 +217,9 @@ function handleDelete(obj, row) {
ElMessage.error(res.message); ElMessage.error(res.message);
} }
}); });
})
.catch(() => {
ElMessage.info("取消删除");
}); });
} }
// //
@ -256,13 +235,13 @@ function handleAddCallback() {
function handleResetForm() { function handleResetForm() {
for (const key in table.value.column) { for (const key in table.value.column) {
let item = table.value.column[key]; let item = table.value.column[key];
if (Array.isArray(item.valueE)) { if (Array.isArray(item.edit.valueE)) {
item.valueE = []; item.edit.valueE = [];
} else if (typeof item.valueE === "number") { } else if (typeof item.edit.valueE === "number") {
item.valueE = ""; item.edit.valueE = "";
} else if (typeof item.valueE === "boolean") { } else if (typeof item.edit.valueE === "boolean") {
} else { } else {
item.valueE = ""; item.edit.valueE = "";
} }
} }
} }
@ -277,6 +256,21 @@ function pageIndexChange(o) {
table.value.search.PageIndex = o - 1; table.value.search.PageIndex = o - 1;
fetchPagedData(); fetchPagedData();
} }
/**
* 排序变化时
* @param selection
*/
function sortChange(data: { column: TableColumnCtx<any>; prop: string; order: any }) {
if (data.order == undefined) {
table.value.search.OrderBy = defaultOrderBy.OrderBy;
table.value.search.OrderByType = defaultOrderBy.OrderByType;
} else {
table.value.search.OrderBy = data.prop;
table.value.search.OrderByType = data.order === "ascending" ? 1 : 0;
}
handleReloadPaged();
}
// //
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
console.log("selection-change", selection); console.log("selection-change", selection);
@ -287,17 +281,23 @@ function searchReload() {
for (let name in table.value.column) { for (let name in table.value.column) {
if ( if (
!table.value.column[name].search || !table.value.column[name].search ||
table.value.column[name].value === undefined || table.value.column[name].search.value === undefined ||
table.value.column[name].value === null || table.value.column[name].search.value === null ||
table.value.column[name].value === "" table.value.column[name].search.value === ""
) { ) {
continue; continue;
} }
table.value.column[name].value = ""; table.value.column[name].search.value = "";
} }
} }
// //
function handleReloadPaged(reload = true) { function handleReloadPaged(reload = true) {
if (defaultOrderBy == null) {
defaultOrderBy = {
OrderBy: table.value.search.OrderBy,
OrderByType: table.value.search.OrderByType,
};
}
if (table.value.search === undefined || table.value.search.PageIndex === undefined) { if (table.value.search === undefined || table.value.search.PageIndex === undefined) {
table.value.search.PageIndex = 0; table.value.search.PageIndex = 0;
table.value.search.PageSize = 20; table.value.search.PageSize = 20;
@ -306,9 +306,9 @@ function handleReloadPaged(reload = true) {
for (let name in table.value.column) { for (let name in table.value.column) {
if ( if (
!table.value.column[name].search || !table.value.column[name].search ||
table.value.column[name].value === undefined || table.value.column[name].search.value === undefined ||
table.value.column[name].value === null || table.value.column[name].search.value === null ||
table.value.column[name].value === "" table.value.column[name].search.value === ""
) { ) {
continue; continue;
} }
@ -323,10 +323,10 @@ function handleReloadPaged(reload = true) {
} else data.ConditionalType = ConditionalType.Equal; } else data.ConditionalType = ConditionalType.Equal;
data.FieldName = name.charAt(0).toUpperCase() + name.slice(1); data.FieldName = name.charAt(0).toUpperCase() + name.slice(1);
data.FieldValue = table.value.column[name].value.toString(); data.FieldValue = table.value.column[name].search.value.toString();
if (table.value.column[name].searchType != undefined) { if (table.value.column[name].search.searchType != undefined) {
let v: number = table.value.column[name].searchType || 0; let v: number = table.value.column[name].search.searchType || 0;
data.ConditionalType = v; data.ConditionalType = v;
} }
table.value.search.Conditions.push(data); table.value.search.Conditions.push(data);
@ -338,41 +338,12 @@ function handleReloadPaged(reload = true) {
if (table.value.searchCallback) { if (table.value.searchCallback) {
table.value.searchCallback(table.value.search); table.value.searchCallback(table.value.search);
} }
//
if (table.value.expandColumn) expands.value = [];
fetchPagedData(); fetchPagedData();
} }
// //
async function fetchInitData() { async function fetchInitData() {
for (const key in table.value.column) {
const element = table.value.column[key];
if (element.type === "dropdown") {
if (!element.setting.datasource) {
//
// let rdata = await eval(element.setting.datasourceStr);
// element.setting.datasource = rdata.data;
// console.log(key + " " + element.setting.datasourceStr, rdata);
}
}
if (
element.custom == undefined &&
(element.type === "switch" ||
element.type === "dropdown" ||
element.type === "string" ||
element.type === undefined)
) {
if (element.type === "string" || element.type === undefined)
element.custom = (row) => row[key];
else {
element.custom = (row) => {
let sc = element.setting.datasource.find(
(s) => s[element.setting.mapValue] + "" == row[key] + ""
);
return !sc ? row[key] : sc[element.setting.maplabel];
};
}
} else if (element.custom == undefined) {
element.custom = (row) => row[key];
}
}
setTimeout(() => { setTimeout(() => {
init.value = true; init.value = true;
appStyle(); appStyle();
@ -415,10 +386,10 @@ function fetchPagedData() {
<div ref="appB_S" class="search-container1"> <div ref="appB_S" class="search-container1">
<!-- 搜索项目 --> <!-- 搜索项目 -->
<el-form v-if="table.search.show" :inline="true" :model="table.search"> <el-form v-if="table.search.show" :inline="true" :model="table.search">
<el-form-item v-for="(o, n, i) in table.column" v-show="o.search" :key="i"> <el-form-item v-for="(o, n, i) in table.column" v-show="o.search.yes" :key="i">
<el-date-picker <el-date-picker
v-if="o.type.trim() == 'datetime'" v-if="o.type.trim() == 'datetime'"
v-model="o.value as Date" v-model="o.search.value as Date"
type="date" type="date"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
@ -427,8 +398,8 @@ function fetchPagedData() {
/> />
<el-select <el-select
v-else-if="o.type.trim() == 'dropdown'" v-else-if="o.type.trim() == 'dropdown'"
v-model="o.value" v-model="o.search.value"
:multiple="o.multiple" :multiple="o.edit.multiple"
clearable clearable
filterable filterable
:placeholder="o.label" :placeholder="o.label"
@ -444,7 +415,7 @@ function fetchPagedData() {
</el-select> </el-select>
<el-select <el-select
v-else-if="o.type.trim() == 'switch'" v-else-if="o.type.trim() == 'switch'"
v-model="o.value" v-model="o.search.value"
clearable clearable
filterable filterable
:placeholder="o.label" :placeholder="o.label"
@ -454,7 +425,7 @@ function fetchPagedData() {
<el-option autocomplete="off" :label="'禁用'" :value="false" /> <el-option autocomplete="off" :label="'禁用'" :value="false" />
</el-select> </el-select>
<div v-else-if="o.type.trim() == 'img'" v-show="false" /> <div v-else-if="o.type.trim() == 'img'" v-show="false" />
<el-input v-else v-model="o.value as string" :placeholder="o.label" /> <el-input v-else v-model="o.search.value as string" :placeholder="o.label" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" :icon="Search" @click="handleReloadPaged(false)" <el-button type="primary" :icon="Search" @click="handleReloadPaged(false)"
@ -464,39 +435,6 @@ function fetchPagedData() {
<el-button type="default" @click="searchReload()">重置</el-button> <el-button type="default" @click="searchReload()">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
v-model="dialog.visible"
:class="dialog.title ? '' : 'noHeader'"
:title="dialog.title"
:width="dialog.width"
:close-on-click-modal="false"
:close-on-press-escape="dialog.close"
:before-close="tableClose"
append-to-body
>
<hTableEdit
v-if="dialog.edit.visible"
:id="dialog.edit.id"
:tableData="table"
:row="dialog.edit.row"
:tagData="dialog.edit.tagData"
@handlePagedCallback="handleAddCallback"
/>
<component
:is="dialog.custom.component"
v-if="dialog.custom.visible"
:style="{ height: 'calc( ' + dialog.custom.height + ' - 84px )' }"
:iscomponent="true"
:custom="dialog.custom.custom"
:CancelCallback="handleAddCallback"
:data="dialog.custom.data"
@handlePagedCallback="handleAddCallback"
/>
</el-dialog>
</div>
</div> </div>
<div v-if="table.operationTop" class="toolbar-container"> <div v-if="table.operationTop" class="toolbar-container">
@ -519,9 +457,11 @@ function fetchPagedData() {
:data="table.data" :data="table.data"
:border="table.border" :border="table.border"
:highlight-current-row="true" :highlight-current-row="true"
style="width: 100%"
:row-key="rowKeyFun" :row-key="rowKeyFun"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@sort-change="sortChange"
@expand-change="table.expandChange"
:expand-row-keys="expands"
> >
<el-table-column v-if="table.selectColumn" type="selection" width="40" /> <el-table-column v-if="table.selectColumn" type="selection" width="40" />
<el-table-column <el-table-column
@ -529,6 +469,7 @@ function fetchPagedData() {
table.operationColumn && table.operationColumn &&
table.operationColumnData.filter((s) => !s.topBtn).length > 0 table.operationColumnData.filter((s) => !s.topBtn).length > 0
" "
:fixed="table.operationColumnFixed"
label="操作" label="操作"
:width="getOperationColumnWidth()" :width="getOperationColumnWidth()"
> >
@ -551,12 +492,22 @@ function fetchPagedData() {
<!-- 行内按钮组 --> <!-- 行内按钮组 -->
</template> </template>
</el-table-column> </el-table-column>
<!-- 拓展列 -->
<el-table-column v-if="table.expandColumn" type="expand">
<template #default="props">
<slot name="expandSlot" :props="props"></slot>
</template>
</el-table-column>
<el-table-column <el-table-column
v-for="(item, name, i) of tableShowColumn" v-for="(item, name, i) of tableShowColumn"
:key="i" :key="i"
:prop="name" :prop="name"
:label="item.label" :label="item.label"
:width="item.width" :width="item.width"
:fixed="item.fixed"
:sortable="item.search.sort ? `custom` : false"
> >
<template v-slot="scope"> <template v-slot="scope">
<div class="columnTemplate"> <div class="columnTemplate">
@ -596,6 +547,7 @@ function fetchPagedData() {
</el-table-column> </el-table-column>
</el-table> </el-table>
<div <div
v-if="table.search.showPage"
style=" style="
padding-top: 15px; padding-top: 15px;
display: flex; display: flex;
@ -613,6 +565,40 @@ function fetchPagedData() {
@current-change="pageIndexChange" @current-change="pageIndexChange"
/> />
</div> </div>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
v-model="dialog.visible"
:class="dialog.title ? '' : 'noHeader'"
:title="dialog.title"
:width="dialog.width"
:close-on-click-modal="false"
:close-on-press-escape="dialog.close"
:before-close="tableClose"
class="max-w-[95vw] overflow-x-auto"
append-to-body
>
<hTableEdit
v-if="dialog.edit.visible"
:id="dialog.edit.id"
:tableData="table"
:row="dialog.edit.row"
:tagData="dialog.edit.tagData"
@handlePagedCallback="handleAddCallback"
/>
<component
:is="dialog.custom.component"
v-if="dialog.custom.visible"
:style="{ height: 'calc( ' + dialog.custom.height + ' - 84px )' }"
:iscomponent="true"
:custom="dialog.custom.custom"
:CancelCallback="handleAddCallback"
:data="dialog.custom.data"
@handlePagedCallback="handleAddCallback"
/>
</el-dialog>
</div>
</div> </div>
</template> </template>

View File

@ -76,7 +76,7 @@ const convertKeysToCamelCase = <T>(data: any): T => {
const defaultConfig: AxiosRequestConfig = { const defaultConfig: AxiosRequestConfig = {
baseURL: import.meta.env.VITE_API_BASEURL, baseURL: import.meta.env.VITE_API_BASEURL,
// 请求超时时间 // 请求超时时间
timeout: 20 * 1000, timeout: 30 * 1000,
headers: { headers: {
Accept: "application/json, text/plain, */*", Accept: "application/json, text/plain, */*",
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -40,6 +40,14 @@ export const ruleNumber = [
trigger: "blur" trigger: "blur"
} }
]; ];
export const ruleClassName = [
{
pattern: /^[1-9]\d*班$/,
message: "请输入正确班级名称[例:101班]",
trigger: "blur"
},
...ruleRequired
];
export const ruleRequiredNumber = [ export const ruleRequiredNumber = [
{ required: true, message: "不能为空", trigger: "blur" }, { required: true, message: "不能为空", trigger: "blur" },
{ {

View File

@ -1,9 +1,11 @@
<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, intTableData, 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 { ElMessage } from "element-plus";
import { ImportAdminInfo } from "@/api/admin";
import { import {
ruleAccount, ruleAccount,
rulePassword, rulePassword,
@ -29,7 +31,7 @@ function searchCallback(data) {
} }
const RoleApi = new hTableAPI("AdminRole"); const RoleApi = new hTableAPI("AdminRole");
const table = ref<{ initTable: (config: TableConfig) => void }>(null); const table = ref<{ initTable: (config: TableConfig) => void }>(null);
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -60,73 +62,62 @@ const tableData: TableConfig = {
}, },
{ {
topBtn: false, // topBtn: false, //
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
}, },
{
topBtn: true, //
label: "导入",
click:entryAdminnfo,
btnStyle: "info", // topBtn: true success danger
},
{
topBtn: true, //
label: "导入模板",
click:DwImportTemplate,
btnStyle: "info", // topBtn: true success danger
},
], ],
column: { column: {
// //
id: { id: {
label: "编号", label: "编号",
search: true, search: { yes: true, searchType: ConditionalType.Equal },
add: false, //
edit: false, //
width: "150px", width: "150px",
}, },
name: { name: {
label: "名称", label: "名称",
width: "180px", width: "180px",
rules: ruleRequiredI(12, 2), search: { yes: true, searchType: ConditionalType.Like },
search: true, edit: { add: true, edit: true, rules: ruleRequiredI(12, 2) },
searchType: ConditionalType.Like,
add: true, //
edit: true, //
}, },
phone: { phone: {
label: "手机号", label: "手机号",
rules: rulePhone,
width: "200px", width: "200px",
search: true, search: { yes: true, searchType: ConditionalType.Like },
add: true, // edit: { add: true, edit: true, rules: rulePhone },
edit: true, //
}, },
account: { account: {
label: "账号", label: "账号",
rules: ruleRequiredI(20, 8), search: { yes: true },
search: true, edit: { add: true, edit: false, rules: ruleRequiredI(20, 8) },
add: true, //
edit: false, //
}, },
password: { password: {
label: "密码", label: "密码",
show: false, show: false,
/**长度必须大于6 */ edit: { add: true, edit: false, rules: ruleRequiredI(32, 6) },
rules: ruleRequiredI(32, 6),
search: false,
add: true, //
edit: false, //
}, },
enable: { enable: {
label: "启用", label: "启用",
type: "switch", type: "switch",
search: false, edit: { add: true, edit: true, editDefault: true },
add: true, //
edit: true, //
valueE: true, //
}, },
roleId: { roleId: {
label: "角色", label: "角色",
type: "dropdown", type: "dropdown",
rules: ruleRequired, search: { yes: true },
search: true, edit: { add: true, edit: false, rules: ruleRequired },
add: true, //
edit: false, //
setting: {
datasource: [],
},
}, },
}, },
data: [], data: [],
@ -134,8 +125,46 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
function entryAdminnfo(eid: number) {
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 ImportAdminInfo(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 = JSON.parse(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.warning("导入失败,已导出错误数据");
};
try {
fileE.click();
} catch (error) { }
}
function DwImportTemplate(obj, row, callBack) {
const baseUrl = import.meta.env.VITE_API_BASEURL;
const excelImportUsersUrl = `${baseUrl}/Admin/DwImportTemplate`;
window.open(excelImportUsersUrl, "_blank");
}
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
// //

View File

@ -261,7 +261,6 @@ const handlePagedCallback = () => {
}; };
const handleSubmitForm = async () => { const handleSubmitForm = async () => {
debugger;
if (props.id <= 0 || ClassNames.value.length < 1) { if (props.id <= 0 || ClassNames.value.length < 1) {
ElMessage.error("班级列表为空!"); ElMessage.error("班级列表为空!");
return; return;

View File

@ -1,11 +1,21 @@
<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 {
ComboModel,
ConditionalType,
gradeComboModel,
intTableData,
TableColumn,
TableColumnEdit,
TableColumnSearch,
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 { getenum } from "@/api/enum"; import { getenum } from "@/api/enum";
import { ruleRequired } from "@/utils/rules"; import { ruleClassName, ruleRequired } from "@/utils/rules";
import { text } from "node:stream/consumers";
const ControllerName = "classes"; const ControllerName = "classes";
defineOptions({ defineOptions({
@ -16,7 +26,7 @@ const SchoolApi = new hTableAPI("usercenter/back/schools");
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: "usercenter/back/classes", apiUrl: "usercenter/back/classes",
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -66,117 +76,63 @@ const tableData: TableConfig = {
// //
id: { id: {
label: "编号", label: "编号",
search: true,
add: false, //
edit: false, //
width: "150px", width: "150px",
}, },
schoolId: { schoolId: {
label: "学校", label: "学校",
rules: ruleRequired,
width: "180px", width: "180px",
search: true,
type: "dropdown", type: "dropdown",
add: true, // search: new TableColumnSearch(true),
edit: true, // edit: {
setting: {},
},
name: {
label: "名称",
rules: ruleRequired, rules: ruleRequired,
width: "180px", },
search: true,
searchType: ConditionalType.Like, //
add: true, //
edit: true, //
}, },
Grade: { Grade: {
label: "年级", label: "年级",
rules: ruleRequired, width: "220px",
width: "180px",
type: "dropdown", type: "dropdown",
custom: (row) => `${row.grade ?? ""}`, custom: (row) => `${row.gradeLevel + row.graduationYear}届 [${row.grade ?? ""}]`,
// `${row.grade ?? ""} ${row.gradeLevel + row.graduationYear}`, search: new TableColumnSearch(true),
search: true, edit: { add: true, edit: false, rules: ruleRequired },
setting: {}, },
add: true, // name: {
edit: false, // label: "名称",
width: "180px",
search: {
yes: true,
searchType: ConditionalType.Like, //
},
edit: {
add: true,
edit: true,
rules: ruleClassName,
},
}, },
// GradeLevel: {
// label: "",
// search: false,
// type: "dropdown",
// searchType: ConditionalType.Like,
// add: true, //
// edit: true, //
// width: "70px",
// setting: {
// datasource: [
// { text: "", value: "" },
// { text: "", value: "" },
// { text: "", value: "" }
// ]
// }
// },
// GraduationYear: {
// label: "",
// search: false,
// add: true, //
// edit: true, //
// width: "80px"
// },
type: { type: {
label: "类型", label: "类型",
rules: ruleRequired,
// width: "150px",
type: "dropdown", type: "dropdown",
search: true, search: {
add: true, // yes: true,
edit: true, // },
setting: {}, edit: {
add: true,
edit: true,
rules: ruleRequired,
},
}, },
// createTime: {
// label: "",
// width: "180px",
// type: "datetime",
// search: true,
// add: false, //
// edit: false //
// },
// remark: {
// label: "",
// type: "textarea",
// editRows: 3,
// search: false,
// add: true, //
// edit: true //
// }
}, },
data: [], data: [],
pageData: { pageData: {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
// //
tableData.column.Grade.setting.datasource = [ tableData.column.Grade.setting.datasource = gradeComboModel();
{ text: "初一", value: "初一" },
{ text: "初二", value: "初二" },
{ text: "初三", value: "初三" },
{ text: "高一", value: "高一" },
{ text: "高二", value: "高二" },
{ text: "高三", value: "高三" },
{ text: "一年级", value: "一年级" },
{ text: "二年级", value: "二年级" },
{ text: "三年级", value: "三年级" },
{ text: "四年级", value: "四年级" },
{ text: "五年级", value: "五年级" },
{ text: "六年级", value: "六年级" },
];
tableData.column.type.setting.datasource = (await getenum("ClassTypeEnum")).data; tableData.column.type.setting.datasource = (await getenum("ClassTypeEnum")).data;
tableData.column.schoolId.setting.datasource = ( tableData.column.schoolId.setting.datasource = (
await SchoolApi.querycombo({ TextName: "Name", ValueName: "Id" }) await SchoolApi.querycombo({ TextName: "Name", ValueName: "Id" })

View File

@ -1,6 +1,11 @@
<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,
intTableData,
TableColumnSearch,
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";
@ -21,7 +26,7 @@ const props = defineProps<{
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -48,7 +53,7 @@ const tableData: TableConfig = {
show: true, show: true,
label: "删除", label: "删除",
click: deleteInfo, click: deleteInfo,
btnStyle: "danger", // topBtn: true success danger btnStyle: "warning", // topBtn: true success danger
}, },
{ {
topBtn: false, // topBtn: false, //
@ -75,33 +80,23 @@ const tableData: TableConfig = {
// //
schoolName: { schoolName: {
label: "学校", label: "学校",
search: true,
searchType: ConditionalType.Like, //
add: false, //
edit: false, //
width: "180px", width: "180px",
search: new TableColumnSearch(true, ConditionalType.Like),
}, },
grade: { grade: {
label: "年级", label: "年级",
width: "100px", width: "100px",
search: true, custom: (row) => row.gradeLevel + row.gradeYear + "届",
add: false, // search: new TableColumnSearch(true),
edit: false, //
}, },
className: { className: {
label: "班级", label: "班级",
width: "150px", width: "150px",
search: true, search: new TableColumnSearch(true, ConditionalType.Like),
searchType: ConditionalType.Like, //
add: false, //
edit: false, //
}, },
peopleCount: { peopleCount: {
label: "参考人数", label: "参考人数",
width: "100px", width: "100px",
search: false,
add: false, //
edit: false, //
}, },
// entryPerson: { // entryPerson: {
// label: "", // label: "",
@ -113,9 +108,6 @@ const tableData: TableConfig = {
createTime: { createTime: {
label: "录入时间", label: "录入时间",
width: "200px", width: "200px",
search: false,
add: false, //
edit: false, //
}, },
}, },
data: [], data: [],
@ -123,7 +115,7 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
async function deleteInfo(o, row, c) { async function deleteInfo(o, row, c) {
try { try {

View File

@ -1,11 +1,18 @@
<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,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } 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 { getenum } from "@/api/enum"; import { getenum } from "@/api/enum";
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules"; import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
import { ClassRanking } from "@/api/exam";
import { ElMessage } from "element-plus";
const ControllerName = "ExamClassInfo"; const ControllerName = "ExamClassInfo";
defineOptions({ defineOptions({
@ -18,20 +25,17 @@ const props = defineProps<{
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
expandColumn: true,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
searchCallback: searchCallback, searchCallback: searchCallback,
expandChange: expandChange,
search: { search: {
// //
show: true, show: true,
PageIndex: 0, PageSize: 999,
PageSize: 20,
OrderBy: "Id", //
OrderByType: 1, //
defaultConditions: [], //
Conditions: [],
}, },
operationColumn: true, // operationColumn: true, //
operationColumnData: [ operationColumnData: [
@ -42,7 +46,7 @@ const tableData: TableConfig = {
btnType: "custom", btnType: "custom",
btnStyle: "primary", btnStyle: "primary",
custom: { custom: {
title: "考试学生班级详情", // title title: "考试班级详情", // title
src: "exam/classExamRecord", // src: "exam/classExamRecord", //
width: "1600px", // width: "1600px", //
height: "800px", // height: "800px", //
@ -53,52 +57,26 @@ const tableData: TableConfig = {
// //
schoolName: { schoolName: {
label: "学校", label: "学校",
search: true,
searchType: ConditionalType.Like, //
add: false, //
edit: false, //
width: "180px", width: "180px",
search: new TableColumnSearch(true, ConditionalType.Like),
}, },
grade: { grade: {
label: "年级", label: "年级",
width: "120px", width: "120px",
// custom: (s) => `${s.gradeLevel}${s.gradeYear}`, custom: (row) => row.gradeLevel + row.gradeYear + "届",
search: true, search: new TableColumnSearch(true),
add: false, //
edit: false, //
}, },
className: { className: {
label: "班级", label: "班级",
width: "80px", width: "80px",
search: true, search: new TableColumnSearch(true, ConditionalType.Like),
searchType: ConditionalType.Like, //
add: false, //
edit: false, //
}, },
examName: { examName: {
label: "最近考试", label: "最近考试",
width: "200px", width: "200px",
search: false,
}, },
peopleCount: { peopleCount: {
label: "参考人数", label: "参考人数",
width: "100px",
search: false,
},
onLineCount: {
label: "重本人数",
width: "100px",
search: false,
},
onLineRate: {
label: "重本率",
width: "100px",
custom: (row) => `${row.onLineRate * 100}%`,
search: false,
},
onLineRanking: {
label: "重本率排名",
search: false,
}, },
}, },
data: [], data: [],
@ -106,7 +84,7 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
@ -114,8 +92,275 @@ onMounted(async () => {
showTable.value = true; showTable.value = true;
}); });
// types/exam.ts
export interface ExamClassTag {
Name: string; //
SubjectStr?: string; // null
OnLineRanking: number; // 线
OnLineRate: number; // 线C# decimalTS number
OnLineCount: number; // 线
}
async function expandChange(row: any, expandedRows: any[]) {
if (row.rankingList != null) return;
let res = await ClassRanking(row.examId, row.classId);
if (res.code != 200) return ElMessage.error(res.message || "获取数据失败");
row.rankingList = res.data;
}
// CSS
const getSubjectClass = (subjectStr) => {
if (!subjectStr) return "";
const subjectMap = {
总分: "total",
数学: "math",
英语: "english",
物理: "physics",
化学: "chemistry",
生物: "biology",
};
return subjectMap[subjectStr] || "";
};
</script> </script>
<template> <template>
<div><ahTable v-if="showTable" ref="table" :tableConfig="tableData" /></div> <div>
<ahTable v-if="showTable" ref="table" :tableConfig="tableData">
<template #expandSlot="{ props }">
<!-- 拓展内容 -->
<div class="expanded-content expandSlot">
<div
v-if="props.row.rankingList && props.row.rankingList.length > 0"
class="ranking-list"
>
<div
v-for="(tag, tagIndex) in props.row.rankingList"
:key="tagIndex"
class="tag-card"
:class="getSubjectClass(tag.subjectStr)"
>
<div class="tag-name">
<span>{{ tag.name }}</span>
<span class="subject-badge">{{ tag.subjectStr || "总分" }}</span>
</div>
<div class="tag-details">
<div class="detail-item">
<span class="detail-label">上线排名:</span>
<span class="detail-value"> {{ tag.onLineRanking }} </span>
</div>
<div class="detail-item">
<span class="detail-label">上线人数:</span>
<span class="detail-value">{{ tag.onLineCount }} </span>
</div>
<div class="detail-item">
<span class="detail-label">上线率:</span>
<span class="detail-value"
>{{ (tag.onLineRate * 100).toFixed(0) }}%</span
>
</div>
<div class="progress-container">
<div
class="progress-bar"
:style="{ width: tag.onLineRate * 100 + '%' }"
></div>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<i class="fas fa-inbox" style="font-size: 3rem; margin-bottom: 15px"></i>
<p>暂无标签数据</p>
</div>
</div>
</template>
</ahTable>
</div>
</template> </template>
<style>
.expandSlot {
padding: 10px 20px;
background: #f5f7fa;
}
.container {
width: 100%;
max-width: 1200px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(90deg, #3498db, #2c3e50);
color: white;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-weight: 600;
font-size: 1.8rem;
}
.header .subtitle {
opacity: 0.9;
font-size: 1rem;
}
.table-container {
padding: 20px;
}
.main-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.main-table th {
background-color: #f8f9fa;
padding: 15px;
text-align: left;
font-weight: 600;
color: #2c3e50;
border-bottom: 2px solid #e9ecef;
}
.main-table td {
padding: 15px;
border-bottom: 1px solid #e9ecef;
vertical-align: top;
}
.main-table tr:hover {
background-color: #f8f9fa;
}
.expand-btn {
background: #3498db;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.3s;
}
.expand-btn:hover {
background: #2980b9;
transform: translateY(-2px);
}
.ranking-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 15px;
margin-top: 10px;
}
.tag-card {
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
border-left: 4px solid #3498db;
transition: transform 0.3s, box-shadow 0.3s;
}
.tag-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
}
.tag-card.total {
border-left-color: #777f8a;
}
.tag-card.math {
border-left-color: #e74c3c;
}
.tag-card.english {
border-left-color: #f39c12;
}
.tag-card.physics {
border-left-color: #9b59b6;
}
.tag-card.chemistry {
border-left-color: #1abc9c;
}
.tag-card.biology {
border-left-color: #2ecc71;
}
.tag-name {
font-weight: 600;
font-size: 1.1rem;
color: #2c3e50;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.subject-badge {
padding: 3px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
background: #ecf0f1;
}
.tag-details {
margin-top: 10px;
}
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 0px;
font-size: 0.9rem;
}
.detail-label {
color: #7f8c8d;
}
.detail-value {
font-weight: 500;
color: #2c3e50;
}
.progress-container {
height: 8px;
background: #ecf0f1;
border-radius: 4px;
margin-top: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #3498db, #2ecc71);
border-radius: 4px;
transition: width 0.5s ease;
}
.empty-state {
text-align: center;
padding: 30px;
color: #7f8c8d;
font-style: italic;
}
</style>

View File

@ -1,6 +1,11 @@
<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,
intTableData,
TableColumnSearch,
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";
@ -22,7 +27,7 @@ const props = defineProps<{
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: `ExamClassInfo`, apiUrl: `ExamClassInfo`,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -31,7 +36,7 @@ const tableData: TableConfig = {
// //
show: true, show: true,
PageIndex: 0, PageIndex: 0,
PageSize: 20, PageSize: 999,
OrderByType: 1, // OrderByType: 1, //
OrderBy: "Id", // OrderBy: "Id", //
defaultConditions: [ defaultConditions: [
@ -63,77 +68,64 @@ const tableData: TableConfig = {
// //
examName: { examName: {
label: "考试名称", label: "考试名称",
search: true,
width: "150px", width: "150px",
search: new TableColumnSearch(true),
}, },
type: { type: {
label: "考试类型", label: "考试类型",
search: true,
type: "dropdown",
setting: {},
width: "80px", width: "80px",
type: "dropdown",
search: new TableColumnSearch(true),
}, },
testPaperType: { testPaperType: {
label: "试卷类型", label: "试卷类型",
search: true,
type: "dropdown",
setting: {},
width: "80px", width: "80px",
type: "dropdown",
search: new TableColumnSearch(true),
}, },
grade: { grade: {
label: "年级", label: "年级",
search: true, width: "90px",
// type: "dropdown", custom: (row) => row.gradeLevel + row.gradeYear + "届",
// setting: {}, search: new TableColumnSearch(true),
width: "60px",
}, },
onLineCount: { // onLineCount: {
label: "重本人数", // label: "",
search: false, // width: "80px",
width: "80px", // },
}, // onLineRate: {
onLineRate: { // label: "",
label: "重本率", // custom: (row) => `${Math.round(row.onLineRate * 100)}%`,
search: false, // width: "80px",
custom: (row) => `${Math.round(row.onLineRate * 100)}%`, // },
width: "80px", // onLineRanking: {
}, // label: "",
onLineRanking: { // width: "100px",
label: "重本率排名", // },
search: false,
width: "100px",
},
maxScore: { maxScore: {
label: "最高分[赋分]", label: "最高分[赋分]",
search: false, width: "130px",
width: "140px",
}, },
minScore: { minScore: {
label: "最低分[赋分]", label: "最低分[赋分]",
search: false, width: "130px",
width: "140px",
}, },
average: { average: {
label: "总平均分[赋分]", label: "总平均分[赋分]",
search: false,
custom: (row) => `${Math.round(row.average)}`, custom: (row) => `${Math.round(row.average)}`,
width: "140px", width: "130px",
}, },
average1: { average1: {
label: "资源校平均分[赋分]", label: "资源校平均分[赋分]",
search: false, width: "100px",
width: "160px",
}, },
averageRank: { averageRank: {
label: "总平均分排名", label: "总平均分排名",
search: false,
width: "110px", width: "110px",
}, },
rank: { rank: {
label: "远端平均/资源校平均", label: "远端平均/资源校平均",
search: false,
width: "95px",
custom: (row) => custom: (row) =>
`${ `${
row.baseSchoolScore == 0 row.baseSchoolScore == 0
@ -147,7 +139,7 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {

188
src/views/exam/examTags.vue Normal file
View File

@ -0,0 +1,188 @@
<script setup lang="ts">
import ahTable from "@/components/hTable/index.vue";
import {
ConditionalType,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue";
import { fa } from "element-plus/es/locales.mjs";
import { hTableAPI } from "@/api/hTable";
import { getenum } from "@/api/enum";
import {
ruleNumber,
ruleRequired,
ruleRequiredGrade,
ruleRequiredI,
ruleRequiredNumber,
} from "@/utils/rules";
import { ImportExamInfo, RecalculateExamRankings } from "@/api/exam";
import { ElMessage, ElMessageBox } from "element-plus";
import { entryExamInfo } from "./examFun";
const ControllerName = "ExamTags";
defineOptions({
name: ControllerName,
});
const props = defineProps<{
data: any;
}>();
function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = intTableData({
apiUrl: ControllerName,
selectColumn: false, //
border: false, //
searchCallback: searchCallback,
editCallback: async (from) => {
if (from.subjectId == -1) from.subjectId = null;
from.examId = props.data[0].id;
},
search: {
//
show: false,
showPage: false,
PageIndex: 0,
PageSize: 1000,
OrderBy: "Id", //
defaultConditions: [
{
FieldName: "ExamId",
FieldValue: props.data[0].id + "",
ConditionalType: ConditionalType.Equal,
},
], //
Conditions: [],
},
operationColumn: true, //
operationColumnData: [
// {
// //
// topBtn: false, //
// label: "",
// btnType: "edit", // add edit del custom
// },
{
//
topBtn: true, //
label: "添加",
btnStyle: "success",
btnType: "add", // add edit del custom
},
{
topBtn: false,
label: "删除",
btnType: "del",
btnStyle: "danger",
},
{
topBtn: true,
label: "重新计算考试上线数据",
click: RExamRankings,
btnStyle: "info",
},
],
column: {
examId: {
label: "考试",
width: "180px",
search: new TableColumnSearch(true, ConditionalType.Equal), //
type: "dropdown",
},
tagName: {
label: "分段名称",
width: "180px",
edit: {
add: true,
rules: ruleRequiredI(20, 1),
},
},
subjectId: {
label: "学科",
width: "100px",
search: new TableColumnSearch(true),
type: "dropdown",
custom: (row) =>
row.subjectId
? tableData.column.subjectId.setting.datasource.find(
(s) => s.value == row.subjectId
)?.text ?? "--"
: "总分",
edit: {
add: true,
rules: ruleRequired,
editDefault: -1,
},
},
maxScore: {
label: "最大分数",
width: "120px",
edit: {
add: true,
rules: ruleRequiredNumber,
},
},
minScore: {
label: "最小分数",
width: "120px",
edit: {
add: true,
rules: ruleRequiredNumber,
},
},
createTime: {
label: "创建时间",
type: "datetime",
custom: (row) => row.createTime?.replace("T", " ").substring(0, 10) ?? "",
},
},
data: [],
pageData: {
total: 0,
},
selectRows: [],
});
const Api = new hTableAPI(`Exam`);
const showTable = ref(false);
onMounted(async () => {
//
tableData.column.examId.setting.datasource = (await Api.querycombo()).data;
tableData.column.subjectId.setting.datasource = [
{ text: "总分", value: -1 },
...(await getenum("SubjectEnum")).data,
];
showTable.value = true;
});
async function RExamRankings() {
try {
await ElMessageBox.confirm(
`重新计算考试上线数据,可能需要较长时间,是否继续?`,
"确认",
{
confirmButtonText: "继续",
cancelButtonText: "取消",
type: "warning",
}
);
const res = await RecalculateExamRankings(props.data[0].id);
if (res.code == 200) {
ElMessage.success("操作成功");
} else {
ElMessage.error(res.message || "操作失败");
}
} catch (error) {
ElMessage.info("取消操作");
}
}
</script>
<template>
<div><ahTable v-if="showTable" ref="table" :tableConfig="tableData" /></div>
</template>

View File

@ -1,6 +1,11 @@
<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,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } 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";
@ -24,7 +29,7 @@ defineOptions({
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -73,12 +78,25 @@ const tableData: TableConfig = {
height: "800px", // height: "800px", //
}, },
}, },
{
topBtn: false, //
show: true,
label: "分段",
btnType: "custom", // add edit del
btnStyle: "info",
custom: {
title: "考试分段", // title
src: "exam/examTags", //
width: "1200px", //
height: "800px", //
},
},
{ {
topBtn: false, // topBtn: false, //
show: true, show: true,
label: "录入成绩", label: "录入成绩",
click: entryExam, click: entryExam,
btnStyle: "primary", // topBtn: true success danger btnStyle: "warning", // topBtn: true success danger
}, },
{ {
topBtn: true, // topBtn: true, //
@ -92,81 +110,98 @@ const tableData: TableConfig = {
// //
id: { id: {
label: "编号", label: "编号",
search: true, search: new TableColumnSearch(true),
add: false, //
edit: false, //
width: "150px", width: "150px",
}, },
name: { name: {
label: "考试名称", label: "考试名称",
rules: ruleRequired,
width: "200px", width: "200px",
search: true, search: new TableColumnSearch(true, ConditionalType.Like), //
searchType: ConditionalType.Like, // edit: {
add: true, // add: true,
edit: true, // edit: true,
setting: {}, rules: ruleRequiredI(20, 2),
}, },
grade: { },
gradeLevel: {
label: "年级", label: "年级",
rules: ruleRequiredGrade,
width: "100px", width: "100px",
search: true, type: "dropdown",
add: true, // custom: (row) => row.gradeLevel + row.gradeYear + "届",
edit: false, // search: new TableColumnSearch(true),
edit: {
add: true,
edit: false,
rules: ruleRequired,
},
},
gradeYear: {
label: "毕业年份",
width: "100px",
show: false,
search: new TableColumnSearch(true),
edit: {
add: true,
edit: false,
rules: ruleRequiredNumber,
},
}, },
testPaperType: { testPaperType: {
label: "试卷类型", label: "试卷类型",
rules: ruleRequired,
width: "100px", width: "100px",
type: "dropdown", type: "dropdown",
setting: {}, search: new TableColumnSearch(true),
search: true, edit: {
add: true, // add: true,
edit: true, // edit: true,
rules: ruleRequired,
},
}, },
type: { type: {
label: "考试类型", label: "考试类型",
rules: ruleRequired,
width: "100px", width: "100px",
type: "dropdown", type: "dropdown",
setting: {}, search: new TableColumnSearch(true),
search: true, edit: {
add: true, // add: true,
edit: true, // edit: true,
rules: ruleRequired,
},
}, },
scoreLine: { scoreLine: {
label: "划线分数", label: "划线分数",
rules: ruleNumber,
search: false,
width: "100px", width: "100px",
add: true, // edit: {
edit: true, // add: true,
edit: true,
rules: ruleNumber,
},
}, },
baseSchoolScore: { baseSchoolScore: {
label: "资源校平均分", label: "资源校平均分",
rules: ruleNumber,
search: false,
width: "150px", width: "150px",
add: true, // edit: {
edit: true, // add: true,
edit: true,
rules: ruleNumber,
},
}, },
startTime: { startTime: {
label: "考试时间", label: "考试时间",
width: "210px", width: "180px",
rules: ruleRequired,
search: true,
type: "datetime", type: "datetime",
setting: {}, custom: (row) => row.startTime?.replace("T", " ").substring(0, 10) ?? "",
add: true, // search: new TableColumnSearch(true),
edit: true, // edit: {
add: true,
edit: true,
rules: ruleRequired,
},
}, },
createTime: { createTime: {
label: "创建时间", label: "创建时间",
type: "datetime", type: "datetime",
search: false, custom: (row) => row.startTime?.replace("T", " ").substring(0, 10) ?? "",
add: false, //
edit: false, //
}, },
}, },
data: [], data: [],
@ -174,7 +209,7 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
function DwImportTemplate(obj, row, callBack) { function DwImportTemplate(obj, row, callBack) {
const baseUrl = import.meta.env.VITE_API_BASEURL; const baseUrl = import.meta.env.VITE_API_BASEURL;
@ -188,7 +223,11 @@ const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
// //
// tableData.column.level.setting.datasource = (await getenum("GradeEnum")).data; tableData.column.gradeLevel.setting.datasource = (
await getenum("GradeLevelEnum")
).data.map((s) => {
return { value: s.text, text: s.text };
});
tableData.column.testPaperType.setting.datasource = ( tableData.column.testPaperType.setting.datasource = (
await getenum("TestPaperTypeEnum") await getenum("TestPaperTypeEnum")

View File

@ -1,6 +1,11 @@
<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,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } 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";
@ -18,7 +23,7 @@ const props = defineProps<{
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -26,8 +31,8 @@ const tableData: TableConfig = {
search: { search: {
// //
show: true, show: true,
PageIndex: 0, showPage: false,
PageSize: 60, PageSize: 9999,
OrderBy: "AssignRanking", // OrderBy: "AssignRanking", //
OrderByType: 0, OrderByType: 0,
defaultConditions: [ defaultConditions: [
@ -44,26 +49,6 @@ const tableData: TableConfig = {
}, },
operationColumn: true, // operationColumn: true, //
operationColumnData: [ operationColumnData: [
{
//
topBtn: true, //
label: "成绩升序",
btnStyle: "primary",
click: (o, r, c) => {
tableData.search.OrderByType = 1;
c();
},
},
{
//
topBtn: true, //
label: "成绩降序",
btnStyle: "primary",
click: (o, r, c) => {
tableData.search.OrderByType = 0;
c();
},
},
{ {
topBtn: false, // topBtn: false, //
label: "个人详情", label: "个人详情",
@ -81,73 +66,72 @@ const tableData: TableConfig = {
// //
userName: { userName: {
label: "姓名", label: "姓名",
search: true,
searchType: ConditionalType.Like, //
width: "180px", width: "180px",
search: new TableColumnSearch(true, ConditionalType.Like),
}, },
语文: { 语文: {
label: "语文", label: "语文",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.语文, custom: (row) => row.subjectDic.语文,
search: { sort: true },
}, },
数学: { 数学: {
label: "数学", label: "数学",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.数学, custom: (row) => row.subjectDic.数学,
search: { sort: true },
}, },
英语: { 英语: {
label: "英语", label: "英语",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.英语, custom: (row) => row.subjectDic.英语,
search: { sort: true },
}, },
物理: { 物理: {
label: "物理", label: "物理",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.物理, custom: (row) => row.subjectDic.物理,
search: { sort: true },
}, },
化学: { 化学: {
label: "化学", label: "化学",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.化学, custom: (row) => row.subjectDic.化学,
search: { sort: true },
}, },
生物: { 生物: {
label: "生物", label: "生物",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.生物, custom: (row) => row.subjectDic.生物,
search: { sort: true },
}, },
政治: { 政治: {
label: "政治", label: "政治",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.政治, custom: (row) => row.subjectDic.政治,
search: { sort: true },
}, },
历史: { 历史: {
label: "历史", label: "历史",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.历史, custom: (row) => row.subjectDic.历史,
search: { sort: true },
}, },
地理: { 地理: {
label: "地理", label: "地理",
search: false,
width: "100px", width: "100px",
custom: (row) => row.subjectDic.地理 ?? "--", custom: (row) => row.subjectDic.地理 ?? "--",
search: { sort: true },
}, },
assignScore: { assignScore: {
label: "赋分总分", label: "赋分总分",
search: false,
width: "180px", width: "180px",
search: { sort: true },
}, },
assignRanking: { assignRanking: {
label: "赋分后的排名", label: "赋分后的排名",
search: false,
width: "200px", width: "200px",
search: { sort: true },
}, },
}, },
data: [], data: [],
@ -155,12 +139,11 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
// //
showTable.value = true; showTable.value = true;
}); });
const exam = props.data[0]; const exam = props.data[0];
@ -170,7 +153,7 @@ const exam = props.data[0];
<div> <div>
<div class="p-[10px] text-[1.5rem]"> <div class="p-[10px] text-[1.5rem]">
<strong>学校</strong>{{ exam.schoolName }}&nbsp;&nbsp; <strong>年级</strong <strong>学校</strong>{{ exam.schoolName }}&nbsp;&nbsp; <strong>年级</strong
>{{ exam.gradeLevel + exam.gradeYear }}&nbsp;&nbsp; <strong>班级</strong >{{ exam.gradeLevel + exam.gradeYear }}&nbsp;&nbsp; <strong>班级</strong
>{{ exam.className }}&nbsp;&nbsp; <strong>考试名称</strong >{{ exam.className }}&nbsp;&nbsp; <strong>考试名称</strong
>{{ exam.examName }}&nbsp;&nbsp; >{{ exam.examName }}&nbsp;&nbsp;
<!-- <strong>考试类型</strong>{{exam.className}}&nbsp;&nbsp; <!-- <strong>考试类型</strong>{{exam.className}}&nbsp;&nbsp;

View File

@ -1,6 +1,11 @@
<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,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } 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";
@ -19,7 +24,7 @@ const props = defineProps<{
function searchCallback(data) {} function searchCallback(data) {}
const table = ref<{ initTable: (config: TableConfig) => void }>(); const table = ref<{ initTable: (config: TableConfig) => void }>();
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: ControllerName, apiUrl: ControllerName,
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
@ -27,8 +32,9 @@ const tableData: TableConfig = {
search: { search: {
// //
show: true, show: true,
showPage:false,
PageIndex: 0, PageIndex: 0,
PageSize: 60, PageSize: 9999,
OrderBy: "Id", // OrderBy: "Id", //
OrderByType: 1, OrderByType: 1,
defaultConditions: [ defaultConditions: [
@ -45,95 +51,91 @@ const tableData: TableConfig = {
// //
examName: { examName: {
label: "考试名称", label: "考试名称",
search: true, search: new TableColumnSearch(true, ConditionalType.Like),
searchType: ConditionalType.Like, //
width: "180px", width: "180px",
}, },
// //
type: { type: {
label: "考试类型", label: "考试类型",
search: true,
type: "dropdown",
setting: {},
width: "150px", width: "150px",
type: "dropdown",
search: new TableColumnSearch(true),
}, },
// //
grade: { grade: {
label: "考试阶段", label: "考试阶段",
search: true,
searchType: ConditionalType.Like, //
width: "120px", width: "120px",
search: new TableColumnSearch(true, ConditionalType.Like),
}, // }, //
testPaperType: { testPaperType: {
label: "试卷类型", label: "试卷类型",
search: true,
type: "dropdown",
setting: {},
width: "150px", width: "150px",
type: "dropdown",
search: new TableColumnSearch(true),
}, },
语文: { 语文: {
label: "语文", label: "语文",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.语文 ?? "--", custom: (row) => row.subjectDic.语文 ?? "--",
search: { sort: true },
}, },
数学: { 数学: {
label: "数学", label: "数学",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.数学 ?? "--", custom: (row) => row.subjectDic.数学 ?? "--",
search: { sort: true },
}, },
英语: { 英语: {
label: "英语", label: "英语",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.英语 ?? "--", custom: (row) => row.subjectDic.英语 ?? "--",
search: { sort: true },
}, },
物理: { 物理: {
label: "物理", label: "物理",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.物理 ?? "--", custom: (row) => row.subjectDic.物理 ?? "--",
search: { sort: true },
}, },
化学: { 化学: {
label: "化学", label: "化学",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.化学 ?? "--", custom: (row) => row.subjectDic.化学 ?? "--",
search: { sort: true },
}, },
生物: { 生物: {
label: "生物", label: "生物",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.生物 ?? "--", custom: (row) => row.subjectDic.生物 ?? "--",
search: { sort: true },
}, },
政治: { 政治: {
label: "政治", label: "政治",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.政治 ?? "--", custom: (row) => row.subjectDic.政治 ?? "--",
search: { sort: true },
}, },
历史: { 历史: {
label: "历史", label: "历史",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.历史 ?? "--", custom: (row) => row.subjectDic.历史 ?? "--",
search: { sort: true },
}, },
地理: { 地理: {
label: "地理", label: "地理",
search: false,
width: "80px", width: "80px",
custom: (row) => row.subjectDic.地理 ?? "--", custom: (row) => row.subjectDic.地理 ?? "--",
search: { sort: true },
}, },
assignScore: { assignScore: {
label: "赋分总分", label: "赋分总分",
search: false,
width: "80px", width: "80px",
search: { sort: true },
}, },
assignRanking: { assignRanking: {
label: "赋分后的排名", label: "赋分后的排名",
search: false,
width: "120px", width: "120px",
search: { sort: true },
}, },
}, },
data: [], data: [],
@ -141,7 +143,7 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false); const showTable = ref(false);
const exam = props.data[0]; const exam = props.data[0];

View File

@ -9,7 +9,7 @@ import { ruleRequired } from "@/utils/rules";
const ControllerName = "Grade"; const ControllerName = "Grade";
defineOptions({ defineOptions({
name: ControllerName name: ControllerName,
}); });
const SchoolApi = new hTableAPI("usercenter/back/schools"); const SchoolApi = new hTableAPI("usercenter/back/schools");
@ -28,7 +28,7 @@ const tableData: TableConfig = {
PageSize: 20, PageSize: 20,
OrderBy: "CreateTime", // OrderBy: "CreateTime", //
defaultConditions: [], // defaultConditions: [], //
Conditions: [] Conditions: [],
}, },
operationColumn: true, // operationColumn: true, //
operationColumnData: [ operationColumnData: [
@ -36,92 +36,90 @@ 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: {
// // //
id: { // id: {
label: "编号", // label: "",
search: true, // search: true,
add: false, // // add: false, //
edit: false, // // edit: false, //
width: "150px" // width: "150px"
}, // },
schoolId: { // schoolId: {
label: "学校", // label: "",
rules: ruleRequired, // rules: ruleRequired,
width: "200px", // width: "200px",
search: true, // search: true,
type: "dropdown", // type: "dropdown",
add: true, // // add: true, //
edit: true, // // edit: true, //
setting: {} // setting: {}
}, // },
name: { // name: {
label: "名称[动态]", // label: "[]",
rules: ruleRequired, // rules: ruleRequired,
width: "100px", // width: "100px",
search: false, // search: false,
searchType: ConditionalType.Like, // searchType: ConditionalType.Like,
add: false, // // add: false, //
edit: false // // edit: false //
}, // },
level: { // level: {
label: "年级", // label: "",
rules: ruleRequired, // rules: ruleRequired,
width: "80px", // width: "80px",
type: "dropdown", // type: "dropdown",
search: true, // search: true,
setting: {}, // setting: {},
add: true, // // add: true, //
edit: true // // edit: true //
}, // },
year: { // year: {
label: "毕业届", // label: "",
width: "80px", // width: "80px",
rules: ruleRequired, // rules: ruleRequired,
search: true, // search: true,
setting: {}, // setting: {},
add: true, // // add: true, //
edit: true // // edit: true //
}, // },
createTime: { // createTime: {
label: "创建时间", // label: "",
type: "datetime", // type: "datetime",
search: true, // search: true,
add: false, // // add: false, //
edit: false // // edit: false //
} // }
}, },
data: [], data: [],
pageData: { pageData: {
total: 0 total: 0,
}, },
selectRows: [] selectRows: [],
}; };
const showTable = ref(false); const showTable = ref(false);
onMounted(async () => { onMounted(async () => {
// //
tableData.column.level.setting.datasource = ( tableData.column.level.setting.datasource = (await getenum("GradeLevelEnum")).data;
await getenum("GradeLevelEnum")
).data;
tableData.column.schoolId.setting.datasource = ( tableData.column.schoolId.setting.datasource = (
await SchoolApi.querycombo({ TextName: "Name", ValueName: "Id" }) await SchoolApi.querycombo({ TextName: "Name", ValueName: "Id" })

View File

@ -1,22 +1,28 @@
<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,
intTableData,
TableColumnSearch,
TableConfig,
} from "@/components/hTable/hTable";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { fa } from "element-plus/es/locales.mjs"; import { fa } from "element-plus/es/locales.mjs";
import { getenum } from "@/api/enum";
defineOptions({ defineOptions({
name: "School", name: "School",
}); });
onMounted(() => {});
function searchCallback(data) { function searchCallback(data) {
//let c = data.Conditions.find((s) => s.FieldName == "Pname"); //let c = data.Conditions.find((s) => s.FieldName == "Pname");
} }
const table = ref<{ initTable: (config: TableConfig) => void }>(null); const table = ref<{ initTable: (config: TableConfig) => void }>(null);
const tableData: TableConfig = { const tableData: TableConfig = intTableData({
apiUrl: "usercenter/back/schools", apiUrl: "usercenter/back/schools",
selectColumn: false, // selectColumn: false, //
border: false, // border: false, //
searchCallback: searchCallback, searchCallback: searchCallback,
editCallback: function (from) {},
search: { search: {
// //
show: true, show: true,
@ -32,11 +38,13 @@ const tableData: TableConfig = {
// //
topBtn: false, // topBtn: false, //
label: "修改", label: "修改",
perms: "学校修改",
btnType: "edit", // add edit del custom btnType: "edit", // add edit del custom
}, },
{ {
topBtn: true, // topBtn: true, //
label: "新增学校", label: "新增学校",
perms: "学校新增",
btnType: "custom", // add edit del custom btnType: "custom", // add edit del custom
btnStyle: "success", // topBtn: true success danger btnStyle: "success", // topBtn: true success danger
custom: { custom: {
@ -52,34 +60,41 @@ const tableData: TableConfig = {
// //
id: { id: {
label: "编号", label: "编号",
search: true,
add: false, //
edit: false, //
width: "150px", width: "150px",
search: new TableColumnSearch(true),
}, },
name: { name: {
label: "学校名称", label: "学校名称",
width: "300px", width: "300px",
search: true, search: new TableColumnSearch(true),
searchType: ConditionalType.Like, edit: {
add: true, // add: true,
edit: true, // edit: true,
},
}, },
pname: { pname: {
label: "地区", label: "地区",
width: "300px", width: "300px",
search: false,
custom: (row) => `${row.pname}-${row.cname}-${row.rname}`, custom: (row) => `${row.pname}-${row.cname}-${row.rname}`,
add: false, // },
edit: false, // projectType: {
label: "学校项目",
width: "200px",
type: "dropdown",
edit: {
multiple: true,
edit: true,
},
}, },
enable: { enable: {
label: "启用", label: "启用",
type: "switch", type: "switch",
search: true,
custom: (row) => (row.enable ? "启用" : "禁用"), custom: (row) => (row.enable ? "启用" : "禁用"),
add: true, // search: new TableColumnSearch(true),
edit: true, // edit: {
add: true,
edit: true,
},
}, },
}, },
data: [], data: [],
@ -87,11 +102,19 @@ const tableData: TableConfig = {
total: 0, total: 0,
}, },
selectRows: [], selectRows: [],
}; });
const showTable = ref(false);
onMounted(async () => {
let res = await getenum("SchoolProjectEnum");
tableData.column.projectType.setting.datasource = res.data;
//
showTable.value = true;
});
</script> </script>
<template> <template>
<div> <div>
<ahTable ref="table" :tableConfig="tableData" /> <ahTable v-if="showTable" ref="table" :tableConfig="tableData" />
</div> </div>
</template> </template>

View File

@ -1,79 +0,0 @@
<script setup lang="ts">
import ahTable from "@/components/hTable/index.vue";
import { ConditionalType, TableConfig } from "@/components/hTable/hTable";
import { onMounted, ref } from "vue";
import { fa } from "element-plus/es/locales.mjs";
defineOptions({
name: "SchoolPreview",
});
const showTable = ref(false);
onMounted(() => {
showTable.value = true;
});
function searchCallback(data) {
//let c = data.Conditions.find((s) => s.FieldName == "Pname");
}
const table = ref<{ initTable: (config: TableConfig) => void }>(null);
const tableData: TableConfig = {
apiUrl: "usercenter/back/schools",
selectColumn: false, //
border: false, //
searchCallback: searchCallback,
search: {
//
show: true,
PageIndex: 0,
PageSize: 20,
OrderBy: "CreateTime", //
defaultConditions: [], //
Conditions: [],
},
operationColumn: true, //
operationColumnData: [],
column: {
//
id: {
label: "编号",
search: true,
add: false, //
edit: false, //
width: "150px",
},
name: {
label: "学校名称",
width: "300px",
search: true,
searchType: ConditionalType.Like,
add: true, //
edit: true, //
},
pname: {
label: "地区",
width: "300px",
search: false,
custom: (row) => `${row.pname}-${row.cname}-${row.rname}`,
add: false, //
edit: false, //
},
enable: {
label: "启用",
type: "switch",
search: true,
custom: (row) => (row.enable ? "启用" : "禁用"),
add: true, //
edit: true, //
},
},
data: [],
pageData: {
total: 0,
},
selectRows: [],
};
</script>
<template>
<div>
<ahTable v-if="showTable" ref="table" :tableConfig="tableData" />
</div>
</template>

View File

@ -19,7 +19,6 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="入班时间:" prop="joinTime"> <el-form-item label="入班时间:" prop="joinTime">
@ -185,41 +184,94 @@
</el-row> </el-row>
<el-row class="pt-4"> <el-row class="pt-4">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="就读班级:" prop="positionIds" :rules="ruleRequired"> <el-form-item label="就读班级:" prop="positionList" :rules="ruleRequired">
<el-button type="success" @click="CheckPosition()">选择就读班级</el-button> <el-button type="success" @click="AddPosition()">添加就读班级</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<div class="max-h-[300px] overflow-auto pb-1">
<span class="pl-[120px]"
>提示:就读信息所有项值<span class="text-red-500">必填</span>,
如若查询不到对应班级,请先添加
</span>
<el-form :model="positionList" inline ref="positionEditForm">
<div <div
v-for="(position, index) in positionList" v-for="(position, index) in positionList"
:key="index" :key="index"
style="padding-left: 120px; padding-bottom: 20px" class="subjectTagEnableDiv"
style=""
> >
<div class="subjectTagEnableDiv" v-if="position.enable === false"> <el-form-item>
<el-tag type="info">{{ "禁用" }}</el-tag> <el-button
<el-tag type="info">{{ position.schoolName || "-" }}</el-tag> title="删除就读班级"
<el-tag type="info">{{ class="mr-[5px]!"
position.graduationYear ? position.graduationYear + "届" : "-" type="danger"
}}</el-tag> :icon="Delete"
<el-tag type="info">{{ position.grade || "-" }}</el-tag> @click="positionClose(position)"
<el-tag type="info">{{ position.className || "-" }}</el-tag> circle
<el-tag type="info">{{ position.subjectName || "-" }}</el-tag> />
<el-tag type="info">{{ position.name || "-" }}</el-tag> </el-form-item>
<el-tag type="info">{{ position.endTime }}</el-tag>
</div> <el-form-item :prop="`${index}.schoolId`" :rules="ruleRequired">
<div class="subjectTagEnableDiv" v-else> <el-select
<el-tag>{{ position.schoolName || "-" }}</el-tag> :disabled="position.id != undefined"
<el-tag type="warning">{{ class="w-[250px]!"
position.graduationYear ? position.graduationYear + "届" : "-" v-model="position.schoolId"
}}</el-tag> placeholder="学校"
<el-tag type="success">{{ position.grade || "-" }}</el-tag> clearable
<el-tag type="primary" class="classTag">{{ position.className || "-" }}</el-tag> filterable
<el-tag type="info" class="subjectTag">{{ @change="() => schoolChange(position)"
position.subjectName || "-" >
}}</el-tag> <el-option
<el-tag type="danger">{{ position.name || "-" }}</el-tag> v-for="item in schoolList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item :prop="`${index}.grade`" :rules="ruleRequired">
<el-select
:disabled="position.id != undefined"
class="w-[150px]!"
v-model="position.grade"
placeholder="年级"
clearable
filterable
@change="() => gradeChange(position)"
>
<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 :prop="`${index}.classId`" :rules="ruleRequired">
<el-select
:disabled="position.id != undefined"
class="w-[120px]!"
v-model="position.classId"
placeholder="班级"
clearable
filterable
>
<el-option
v-for="item in position.classList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</div> </div>
</el-form>
</div> </div>
<el-form-item> <el-form-item>
<el-button type="primary" :loading="loading" @click="handleSubmitForm()" <el-button type="primary" :loading="loading" @click="handleSubmitForm()"
@ -228,35 +280,27 @@
<el-button @click="handleResetForm()">重置</el-button> <el-button @click="handleResetForm()">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </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> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted } from "vue";
import { EditStudent, StudentInfo } from "@/api/student"; import { EditStudent, StudentInfo } from "@/api/student";
import { cloudSchoolCombo, getUserInfo, editUser, Position } from "@/api/userCenter"; import {
cloudSchoolCombo,
getUserInfo,
editUser,
Position,
getClassCombo,
getSchoolData,
} from "@/api/userCenter";
import PositionForm from "../teacher/positionForm.vue"; import PositionForm from "../teacher/positionForm.vue";
import { getenum, getenumDic } from "@/api/enum"; import { getenum, getenumDic } from "@/api/enum";
import { ruleRequired, rulePhone } from "@/utils/rules"; import { ruleRequired, rulePhone } from "@/utils/rules";
import { ElMessage, FormInstance } from "element-plus"; import { ElMessage, FormInstance } from "element-plus";
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel, gradeComboModel } from "@/components/hTable/hTable";
import { Check, Delete, Edit, Message, Search, Star } from "@element-plus/icons-vue";
import { PosititonIds } from "@/api/student";
interface FormData { interface FormData {
id: number; id: number;
@ -273,6 +317,7 @@ interface FormData {
subjectLevels: any[]; subjectLevels: any[];
subjectLevel: Record<string, any>; subjectLevel: Record<string, any>;
positionIds: number[]; positionIds: number[];
positionList: Position[];
positionFormIds?: number[]; positionFormIds?: number[];
gLSubject?: number; gLSubject?: number;
gSubject1?: number; gSubject1?: number;
@ -286,7 +331,7 @@ interface FormData {
amountRelief?: number; amountRelief?: number;
reliefSubTime?: number; reliefSubTime?: number;
reliefType?: number; reliefType?: number;
reliefApplication?: number; reliefApplication?: boolean;
} }
interface DialogConfig { interface DialogConfig {
@ -330,6 +375,37 @@ const positionList = ref<Position[]>([]);
const CloudSchoolArr = ref<ComboModel[]>([]); const CloudSchoolArr = ref<ComboModel[]>([]);
const Template = ref<any[]>([]); const Template = ref<any[]>([]);
const PositionFormIds = ref<number[]>([]); const PositionFormIds = ref<number[]>([]);
const positionEditForm = ref<FormInstance>();
const schoolList = ref<ComboModel[]>([]);
const classList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>(gradeComboModel());
const subjectList = ref<ComboModel[]>([]);
function schoolChange(p: Position) {
p.graduationYear = null;
p.grade = "";
p.classId = null;
p.subjectId = null;
getClass(p);
}
function gradeChange(p: Position) {
p.classId = null;
p.subjectId = null;
getClass(p);
}
function getClass(p: Position) {
const data = {
schoolId: p.schoolId || 0,
graduationYear: p.graduationYear || 0,
grade: p.grade,
};
getClassCombo(data).then((res) => {
if (res.code === 200) {
p.classList = res.data;
}
});
}
const defaultSubjectLevel = reactive({ const defaultSubjectLevel = reactive({
UserId: 0, UserId: 0,
@ -349,7 +425,7 @@ const defaultSubjectLevel = reactive({
const form = ref<FormData>({ const form = ref<FormData>({
id: props.id, id: props.id,
account: "", account: "",
userType: 1, userType: 2,
level: 0, level: 0,
passWord: "", passWord: "",
realName: "", realName: "",
@ -357,6 +433,7 @@ const form = ref<FormData>({
templateId: 0, templateId: 0,
phone: "", phone: "",
cloudSchoolId: 0, cloudSchoolId: 0,
positionList: [],
subjectLevels: [], subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel }, subjectLevel: { ...defaultSubjectLevel },
positionIds: [], positionIds: [],
@ -381,29 +458,74 @@ const customeRules = reactive({
], ],
}); });
const getUserSubjectLevel = (obj: Record<string, any>) => { function positionClose(p: Position) {
if (!obj.id) { positionList.value = positionList.value.filter((s) => s != p);
form.value.subjectLevel = { ...defaultSubjectLevel }; }
obj = form.value.subjectLevel;
}
return Object.entries(obj).filter((s) => s[0].includes("Subject"));
};
const userLevel2subject = (str: string) => { const AddPositionArr: Position[] = [];
const name = str.match(/[0-9]+/)?.[0] || ""; function AddPosition() {
return subjectLEnum.value[name]; let p = {
}; userId: form.value.id,
schoolId: null,
enable: false,
graduationYear: null,
gradeLevel: "",
classId: null,
subjectId: null,
positionType: 1,
positionLevel: 4,
status: true,
};
let p1 = (p as unknown) as Position;
form.value.positionList.push(p1);
AddPositionArr.push(p1);
positionList.value.push(p1);
}
const emit = defineEmits(["handlePagedCallback"]); const emit = defineEmits(["handlePagedCallback"]);
const handlePagedCallback = () => { const handlePagedCallback = () => {
// Emit event to parent if needed // Emit event to parent if needed
emit("handlePagedCallback"); emit("handlePagedCallback");
}; };
const handleSubmitForm = () => { const handleSubmitForm = async () => {
userEditForm.value.validate(async (valid) => { try {
if (valid) { const valid1 = await userEditForm.value.validate();
const valid = await positionEditForm.value.validate();
if (!valid || !valid1) return;
} catch (error) {
ElMessage.warning("表单验证未通过,请检查!");
return;
}
try {
loading.value = true; loading.value = true;
let ac = form.value.account || new Date().getTime() + "";
const postIdArr = positionList.value.map((s) => s.id).filter((s) => s != null);
const addPArr = positionList.value
.filter((s) => s.id == null)
.map((s) => {
return {
positionType: 1,
positionLevel: 4,
subjectId: s.subjectId,
schoolId: s.schoolId,
classId: s.classId,
gradeLevel: s.grade[0],
graduationYear: parseInt(s.grade.slice(1, 5)),
};
});
if (addPArr.length > 0) {
const resPId = await PosititonIds(addPArr);
if (
resPId.code != 200 ||
resPId.data.length == 0 ||
resPId.data.length != addPArr.length
) {
ElMessage.warning("校验添加的职位数据异常,请删除后重新选择!");
return;
}
postIdArr.push(...resPId.data);
}
const formData = { const formData = {
id: form.value.uId || 0, id: form.value.uId || 0,
userType: form.value.userType || 1, userType: form.value.userType || 1,
@ -415,13 +537,13 @@ const handleSubmitForm = () => {
templateId: form.value.templateId || 0, templateId: form.value.templateId || 0,
subjectLevels: form.value.subjectLevels || [], subjectLevels: form.value.subjectLevels || [],
subjectLevel: form.value.subjectLevel || { ...defaultSubjectLevel }, subjectLevel: form.value.subjectLevel || { ...defaultSubjectLevel },
positionIds: form.value.positionIds || [],
gLSubject: form.value.gLSubject, gLSubject: form.value.gLSubject,
gSubject1: form.value.gSubject1, gSubject1: form.value.gSubject1,
gSubject2: form.value.gSubject2, gSubject2: form.value.gSubject2,
idCard: form.value.idCard, idCard: form.value.idCard,
cloudSchoolId: form.value.cloudSchoolId, cloudSchoolId: form.value.cloudSchoolId,
phone: form.value.phone, phone: form.value.phone,
positionIds: postIdArr,
}; };
let res = await editUser(formData); let res = await editUser(formData);
@ -444,13 +566,13 @@ const handleSubmitForm = () => {
ElMessage.success("操作成功"); ElMessage.success("操作成功");
handlePagedCallback(); handlePagedCallback();
//edit info //edit info
} } catch (error) {}
});
// Form validation and submission logic // Form validation and submission logic
}; };
const handleResetForm = () => { const handleResetForm = () => {
Object.assign(form, { form.value = {
id: props.id, id: props.id,
account: "", account: "",
userType: 1, userType: 1,
@ -458,14 +580,24 @@ const handleResetForm = () => {
passWord: "", passWord: "",
realName: "", realName: "",
studentId: "", studentId: "",
templateId: 0,
phone: "",
cloudSchoolId: 0,
positionList: [],
subjectLevels: [], subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel }, subjectLevel: { ...defaultSubjectLevel },
positionIds: [], positionIds: [],
idCard: "", idCard: "",
phone: "", exitTime: null,
cloudSchoolId: "", joinTime: null,
pointPenSN: "", remark: null,
}); studentType: null,
status: null,
amountRelief: null,
reliefSubTime: null,
reliefType: null,
reliefApplication: false,
};
positionList.value = []; positionList.value = [];
}; };
@ -500,6 +632,12 @@ const fetchInitData = async () => {
const [value, text] = s.trim().split("."); const [value, text] = s.trim().split(".");
return { value: parseInt(value.trim()), text: text.trim() }; return { value: parseInt(value.trim()), text: text.trim() };
}); });
const typeRes = await getenum("UserTypeEnum");
userTypeList.value = typeRes.data;
schoolList.value = (await getSchoolData()).data;
subjectList.value = (await getenum("SubjectEnum")).data;
}; };
const fetchFormData = async () => { const fetchFormData = async () => {
@ -510,6 +648,13 @@ const fetchFormData = async () => {
delete res.data.SubjectLevel.CreatePositionId; delete res.data.SubjectLevel.CreatePositionId;
} }
let sInfo = await StudentInfo(props.id); let sInfo = await StudentInfo(props.id);
positionList.value = res.data.positions
.filter((s: Position) => s.enable)
.map((s: Position) => {
if (s.positionLevel > 2)
s.grade = (s.gradeLevel ?? s.grade[0]) + s.graduationYear + "届";
return s;
});
Object.assign(form.value, { Object.assign(form.value, {
id: res.data.id, id: res.data.id,
uId: res.data.id, uId: res.data.id,
@ -532,13 +677,21 @@ const fetchFormData = async () => {
phone: res.data.phone, phone: res.data.phone,
cloudSchoolId: res.data.cloudSchoolId, cloudSchoolId: res.data.cloudSchoolId,
pointPenSN: res.data.pointPenSN, pointPenSN: res.data.pointPenSN,
positionList: positionList.value,
...sInfo.data, ...sInfo.data,
}); });
positionList.value = res.data.positions
positionList.value = res.data.positions; .filter((s: Position) => s.enable)
.map((s: Position) => {
s.grade = (s.gradeLevel ?? s.grade[0]) + s.graduationYear + "届";
return s;
});
PositionFormIds.value = res.data.positions PositionFormIds.value = res.data.positions
.filter((s: any) => s.Enable !== false) .filter((s: Position) => s.enable)
.map((s: any) => s.id); .map((s: any) => s.id);
for (const element of positionList.value) {
getClass(element);
}
} }
}; };
@ -582,9 +735,14 @@ onMounted(async () => {
.subjectTagEnableDiv { .subjectTagEnableDiv {
margin-top: 5px; margin-top: 5px;
padding-left: 120px !important;
} }
.subjectTagEnableDiv .el-tag { .subjectTagEnableDiv .el-form-item {
margin-right: 0px !important;
margin-bottom: 10px !important;
}
.subjectTagEnableDiv .el-select {
margin-right: 5px; margin-right: 5px;
} }

View File

@ -25,7 +25,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-show="search.schoolId != 0" style="width: 100px"> <el-form-item v-show="search.schoolId != 0" style="width: 145px">
<el-select <el-select
v-model="search.grade" v-model="search.grade"
placeholder="年级" placeholder="年级"
@ -109,7 +109,11 @@
<div class="subjectTagEnableDiv"> <div class="subjectTagEnableDiv">
<el-tag v-if="position.enable === false" type="info">已禁用</el-tag> <el-tag v-if="position.enable === false" type="info">已禁用</el-tag>
<el-tag>{{ position.schoolName || "-" }}</el-tag> <el-tag>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="success">{{ position.grade || "-" }}</el-tag> <el-tag type="success">{{
position.graduationYear
? position.gradeLevel + position.graduationYear + "届"
: "-"
}}</el-tag>
<el-tag type="primary" class="classTag">{{ <el-tag type="primary" class="classTag">{{
position.className || "-" position.className || "-"
}}</el-tag> }}</el-tag>
@ -199,7 +203,7 @@ import {
Search, Search,
Star, Star,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel, gradeComboModel } from "@/components/hTable/hTable";
import { ImportStudent, PageList } from "@/api/student"; import { ImportStudent, PageList } from "@/api/student";
const classAPI = new hTableAPI("usercenter/back/classes"); const classAPI = new hTableAPI("usercenter/back/classes");
@ -312,14 +316,7 @@ const userTypeList = ref<ComboModel[]>([
const userLevelList = ref<ComboModel[]>([]); const userLevelList = ref<ComboModel[]>([]);
const schoolList = ref<ComboModel[]>([]); const schoolList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>([ const gradeList = ref<ComboModel[]>(gradeComboModel());
{ 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[]>([]);
const positionList = ref<any[]>([]); const positionList = ref<any[]>([]);
@ -342,7 +339,7 @@ const dialog = reactive<DialogData>({
update: { update: {
title: "", title: "",
visible: false, visible: false,
width: "800px", width: "1000px",
}, },
editLevel: { editLevel: {
userIds: [], userIds: [],

View File

@ -35,33 +35,139 @@
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="任教信息:" prop="positionIds" :rules="ruleRequired"> <el-form-item label="任教信息:" prop="positionList" :rules="ruleRequired">
<el-button type="success" @click="CheckPosition()">分配职位</el-button> <el-button type="success" @click="AddPosition()">添加职位</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<div class="max-h-[400px] overflow-auto pb-1"> <div class="max-h-[400px] overflow-auto pb-1">
<span class="pl-[120px]"
>提示:任职信息所有项值<span class="text-red-500">必填</span>,
如若查询不到对应班级,请先添加
</span>
<el-form :model="positionList" inline ref="positionEditForm">
<div <div
v-for="(position, index) in positionList" v-for="(position, index) in positionList"
:key="index" :key="index"
style="padding-left: 120px; padding-bottom: 20px" class="subjectTagEnableDiv"
style=""
> >
<div class="subjectTagEnableDiv"> <el-form-item>
<el-tag v-if="!position.enable" type="info">已禁用</el-tag> <el-button
<el-tag>{{ position.schoolName || "-" }}</el-tag> title="删除职位"
<el-tag type="warning">{{ class="mr-[5px]!"
position.graduationYear ? position.graduationYear + "届" : "-" type="danger"
}}</el-tag> :icon="Delete"
<el-tag type="success" v-show="position.grade">{{ position.grade }}</el-tag> @click="positionClose(position)"
<el-tag type="primary" v-show="position.className" class="classTag">{{ circle
position.className />
}}</el-tag> </el-form-item>
<el-tag type="info" v-show="position.subjectName" class="subjectTag">{{ <el-form-item :prop="`${index}.positionLevel`" :rules="ruleRequired">
position.subjectName <el-select
}}</el-tag> :disabled="position.id != undefined"
<el-tag type="danger">{{ position.name || "-" }}</el-tag> class="w-[100px]!"
</div> v-model="position.positionLevel"
placeholder="类型"
clearable
filterable
>
<el-option
v-for="item in positionEnumList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item :prop="`${index}.schoolId`" :rules="ruleRequired">
<el-select
:disabled="position.id != undefined"
class="w-[250px]!"
v-model="position.schoolId"
placeholder="学校"
clearable
filterable
@change="() => schoolChange(position)"
>
<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 :prop="`${index}.grade`" :rules="ruleRequired">
<el-select
:disabled="position.id != undefined"
class="w-[150px]!"
v-model="position.grade"
placeholder="年级"
clearable
filterable
@change="() => gradeChange(position)"
>
<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
:prop="`${index}.classId`"
:rules="position.positionLevel > 3 ? ruleRequired : []"
>
<el-select
:disabled="position.id != undefined"
v-show="position.positionLevel > 3"
class="w-[120px]!"
v-model="position.classId"
placeholder="班级"
clearable
filterable
>
<el-option
v-for="item in position.classList"
:key="item.value"
:label="item.text"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item
:prop="`${index}.subjectId`"
:rules="position.positionLevel == 5 ? ruleRequired : []"
>
<el-select
:disabled="position.id != undefined"
v-show="position.positionLevel == 5"
class="w-[100px]!"
v-model="position.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>
</div> </div>
</el-form>
</div> </div>
<el-form-item> <el-form-item>
<el-button type="primary" :loading="loading" @click="handleSubmitForm()" <el-button type="primary" :loading="loading" @click="handleSubmitForm()"
@ -70,36 +176,27 @@
<el-button @click="handleResetForm()">重置</el-button> <el-button @click="handleResetForm()">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </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> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted } from "vue";
import {} from "@/api/user"; import {} from "@/api/user";
import { cloudSchoolCombo, getUserInfo, editUser, Position } from "@/api/userCenter"; import {
cloudSchoolCombo,
getUserInfo,
editUser,
Position,
getSchoolData,
getClassCombo,
} from "@/api/userCenter";
import PositionForm from "./positionForm.vue"; import PositionForm from "./positionForm.vue";
import { getenum, getenumDic } from "@/api/enum"; import { getenum, getenumDic } from "@/api/enum";
import { PosititonIds } from "@/api/student";
import { ruleRequired, rulePhone } from "@/utils/rules"; import { ruleRequired, rulePhone } from "@/utils/rules";
import { ElMessage, FormInstance } from "element-plus"; import { ElMessage, FormInstance } from "element-plus";
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel, gradeComboModel } from "@/components/hTable/hTable";
import { InformationEvent } from "http"; import { Check, Delete, Edit, Message, Search, Star } from "@element-plus/icons-vue";
import { de } from "element-plus/es/locales.mjs";
interface FormData { interface FormData {
id: number; id: number;
@ -115,6 +212,7 @@ interface FormData {
subjectLevels: any[]; subjectLevels: any[];
subjectLevel: Record<string, any>; subjectLevel: Record<string, any>;
positionIds: number[]; positionIds: number[];
positionList: Position[];
positionFormIds?: number[]; positionFormIds?: number[];
gLSubject?: number; gLSubject?: number;
gSubject1?: number; gSubject1?: number;
@ -161,6 +259,42 @@ const positionList = ref<Position[]>([]);
const CloudSchoolArr = ref<ComboModel[]>([]); const CloudSchoolArr = ref<ComboModel[]>([]);
const Template = ref<any[]>([]); const Template = ref<any[]>([]);
const PositionFormIds = ref<number[]>([]); const PositionFormIds = ref<number[]>([]);
const positionEditForm = ref<FormInstance>();
const schoolList = ref<ComboModel[]>([]);
const classList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>(gradeComboModel());
const subjectList = ref<ComboModel[]>([]);
const positionEnumList = ref<ComboModel[]>([
{ text: "年级主任", value: 3 },
{ text: "班主任", value: 4 },
{ text: "科任老师", value: 5 },
]);
function schoolChange(p: Position) {
p.graduationYear = null;
p.grade = "";
p.classId = null;
p.subjectId = null;
getClass(p);
}
function gradeChange(p: Position) {
p.classId = null;
p.subjectId = null;
getClass(p);
}
function getClass(p: Position) {
const data = {
schoolId: p.schoolId || 0,
graduationYear: p.graduationYear || 0,
grade: p.grade,
};
getClassCombo(data).then((res) => {
if (res.code === 200) {
p.classList = res.data;
}
});
}
const defaultSubjectLevel = reactive({ const defaultSubjectLevel = reactive({
UserId: 0, UserId: 0,
@ -188,6 +322,7 @@ const form = ref<FormData>({
templateId: 0, templateId: 0,
phone: "", phone: "",
cloudSchoolId: 0, cloudSchoolId: 0,
positionList: [],
subjectLevels: [], subjectLevels: [],
subjectLevel: { ...defaultSubjectLevel }, subjectLevel: { ...defaultSubjectLevel },
positionIds: [], positionIds: [],
@ -212,34 +347,80 @@ const customeRules = reactive({
], ],
}); });
const getUserSubjectLevel = (obj: Record<string, any>) => { function positionClose(p: Position) {
if (!obj.id) { positionList.value = positionList.value.filter((s) => s != p);
form.value.subjectLevel = { ...defaultSubjectLevel }; }
obj = form.value.subjectLevel;
}
return Object.entries(obj).filter((s) => s[0].includes("Subject"));
};
const userLevel2subject = (str: string) => { const AddPositionArr: Position[] = [];
const name = str.match(/[0-9]+/)?.[0] || ""; function AddPosition() {
return subjectLEnum.value[name]; let p = {
}; userId: form.value.id,
schoolId: null,
enable: false,
graduationYear: null,
gradeLevel: "",
classId: null,
subjectId: null,
positionType: 2,
positionLevel: null,
status: true,
};
let p1 = (p as unknown) as Position;
form.value.positionList.push(p1);
AddPositionArr.push(p1);
positionList.value.push(p1);
}
const handlePagedCallback = () => { const handlePagedCallback = () => {
emit("handlePagedCallback"); emit("handlePagedCallback");
}; };
const userEditForm = ref<FormInstance>(); const userEditForm = ref<FormInstance>();
const handleSubmitForm = () => { const handleSubmitForm = async () => {
userEditForm.value.validate((valid) => { try {
if (valid) { const valid1 = await userEditForm.value.validate();
const valid = await positionEditForm.value.validate();
if (!valid || !valid1) return;
} catch (error) {
ElMessage.warning("表单验证未通过,请检查!");
return;
}
try {
loading.value = true; loading.value = true;
const postIdArr = positionList.value.map((s) => s.id).filter((s) => s != null);
const addPArr = positionList.value
.filter((s) => s.id == null)
.map((s) => {
return {
positionLevel: s.positionLevel,
subjectId: s.subjectId,
schoolId: s.schoolId,
classId: s.classId,
positionType: 2,
gradeLevel: s.grade[0],
graduationYear: parseInt(s.grade.slice(1, 5)),
};
});
if (addPArr.length > 0) {
const resPId = await PosititonIds(addPArr);
if (
resPId.code != 200 ||
resPId.data.length == 0 ||
resPId.data.length != addPArr.length
) {
ElMessage.warning("校验添加的职位数据异常,请删除后重新选择!");
return;
}
postIdArr.push(...resPId.data);
}
const formData = { const formData = {
...form.value, ...form.value,
account: form.value.phone || "", account: form.value.phone || "",
positionIds: form.value.positionIds || [], positionIds: postIdArr,
}; };
editUser(formData).then((res) => { const res = await editUser(formData);
loading.value = false; loading.value = false;
if (res.code === 200) { if (res.code === 200) {
ElMessage.success("操作成功"); ElMessage.success("操作成功");
@ -247,11 +428,10 @@ const handleSubmitForm = () => {
} else { } else {
ElMessage.error(res.message); ElMessage.error(res.message);
} }
}); } catch (error) {
ElMessage.error("请求失败!" + (error.message ?? JSON.stringify(error)));
loading.value = false;
} }
});
// Form validation and submission logic
}; };
const handleResetForm = () => { const handleResetForm = () => {
@ -273,7 +453,6 @@ const handleResetForm = () => {
}); });
positionList.value = []; positionList.value = [];
}; };
const fetchInitData = async () => { const fetchInitData = async () => {
// //
const levelRes = await getenum("StudentLevelEnum"); const levelRes = await getenum("StudentLevelEnum");
@ -282,6 +461,9 @@ const fetchInitData = async () => {
const typeRes = await getenum("UserTypeEnum"); const typeRes = await getenum("UserTypeEnum");
userTypeList.value = typeRes.data; userTypeList.value = typeRes.data;
schoolList.value = (await getSchoolData()).data;
subjectList.value = (await getenum("SubjectEnum")).data.filter((s) => s.value < 10);
// // // //
// const schoolRes = await cloudSchoolCombo(); // const schoolRes = await cloudSchoolCombo();
// if (schoolRes.code === 200) { // if (schoolRes.code === 200) {
@ -301,6 +483,13 @@ const fetchFormData = () => {
delete res.data.SubjectLevel.CreatePositionId; delete res.data.SubjectLevel.CreatePositionId;
} }
positionList.value = res.data.positions
.filter((s: Position) => s.enable)
.map((s: Position) => {
if (s.positionLevel > 2)
s.grade = (s.gradeLevel ?? s.grade[0]) + s.graduationYear + "届";
return s;
});
Object.assign(form.value, { Object.assign(form.value, {
id: res.data.id, id: res.data.id,
userType: res.data.userType, userType: res.data.userType,
@ -322,12 +511,21 @@ const fetchFormData = () => {
phone: res.data.phone, phone: res.data.phone,
cloudSchoolId: res.data.cloudSchoolId, cloudSchoolId: res.data.cloudSchoolId,
pointPenSN: res.data.pointPenSN, pointPenSN: res.data.pointPenSN,
positionList: positionList.value ?? [],
});
positionList.value = res.data.positions
.filter((s: Position) => s.enable)
.map((s: Position) => {
if (s.positionLevel > 2)
s.grade = (s.gradeLevel ?? s.grade[0]) + s.graduationYear + "届";
return s;
}); });
positionList.value = res.data.positions;
PositionFormIds.value = res.data.positions PositionFormIds.value = res.data.positions
.filter((s: any) => s.Enable !== false) .filter((s: Position) => s.enable)
.map((s: any) => s.id); .map((s: any) => s.id);
for (const element of positionList.value) {
if (element.positionLevel > 3) getClass(element);
}
} }
}); });
} }
@ -376,9 +574,14 @@ onMounted(async () => {
.subjectTagEnableDiv { .subjectTagEnableDiv {
margin-top: 5px; margin-top: 5px;
padding-left: 120px !important;
} }
.subjectTagEnableDiv .el-tag { .subjectTagEnableDiv .el-form-item {
margin-right: 0px !important;
margin-bottom: 10px !important;
}
.subjectTagEnableDiv .el-select {
margin-right: 5px; margin-right: 5px;
} }

View File

@ -54,7 +54,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-show="search.schoolId != 0" style="width: 100px"> <el-form-item v-show="search.schoolId != 0" style="width: 145px">
<el-select <el-select
v-model="search.grade" v-model="search.grade"
placeholder="年级" placeholder="年级"
@ -159,29 +159,20 @@
<el-table-column prop="studentId" label="职务" width="120" /> <el-table-column prop="studentId" label="职务" width="120" />
<el-table-column label="任教信息"> <el-table-column label="任教信息" min-width="500">
<template #default="scope"> <template #default="scope">
<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="index < 3 || (index >= 3 && showAllPosition.includes(scope.row))" v-show="index < 3 || (index >= 3 && showAllPosition.includes(scope.row))"
> >
<div v-if="position.enable === false"> <div class="subjectTagEnableDiv">
<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>{{ position.schoolName || "-" }}</el-tag>
<el-tag type="warning">{{ <el-tag type="warning">{{
position.graduationYear ? position.graduationYear + "届" : "-" position.graduationYear
? position.gradeLevel + position.graduationYear + "届"
: "-"
}}</el-tag> }}</el-tag>
<el-tag type="success">{{ position.grade || "-" }}</el-tag>
<el-tag type="primary" class="classTag">{{ <el-tag type="primary" class="classTag">{{
position.className || "-" position.className || "-"
}}</el-tag> }}</el-tag>
@ -260,7 +251,7 @@ import {
Search, Search,
Star, Star,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ComboModel } from "@/components/hTable/hTable"; import { ComboModel, gradeComboModel } from "@/components/hTable/hTable";
import { ImportTeacher } from "@/api/student"; import { ImportTeacher } from "@/api/student";
const classAPI = new hTableAPI("usercenter/back/classes"); const classAPI = new hTableAPI("usercenter/back/classes");
@ -374,14 +365,8 @@ const userTypeList = ref<ComboModel[]>([
const userLevelList = ref<ComboModel[]>([]); const userLevelList = ref<ComboModel[]>([]);
const schoolList = ref<ComboModel[]>([]); const schoolList = ref<ComboModel[]>([]);
const gradeList = ref<ComboModel[]>([
{ value: "初一", text: "初一" }, const gradeList = ref<ComboModel[]>(gradeComboModel());
{ 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[]>([]);
const positionList = ref<any[]>([]); const positionList = ref<any[]>([]);

View File

@ -163,20 +163,20 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-card class="box-card"> <el-card class="box-card">
<div class="clearfix clearfixCss"> <div class="flex justify-between clearfixCss">
<span style="line-height: 32px; font-weight: 600" <span style="line-height: 32px; font-weight: 600"
>{{ userType == 1 ? "就读班级" : "已选职位" }}[{{ >{{ userType == 1 ? "就读班级" : "已选职位" }}[{{
selectPositions.length selectPositions.length
}}]</span }}]</span
> >
<el-button
style="float: right" <div>
type="success" <el-button @click="reload">重置</el-button>
@click="handleConfirm" <el-button type="success" @click="handleConfirm" :icon="Check"
:icon="Check"
>提交分配职位</el-button >提交分配职位</el-button
> >
</div> </div>
</div>
<div class="positionGap"> <div class="positionGap">
<el-tag <el-tag
v-for="(p, o) in selectPositions" v-for="(p, o) in selectPositions"
@ -433,6 +433,9 @@ const fetchPagedData = async () => {
} }
}; };
const reload = () => {
selectPositions.value = [];
};
const handleConfirm = () => { const handleConfirm = () => {
if (selectPositions.value.length === 0) { if (selectPositions.value.length === 0) {
ElMessage.warning("请选择要分配的职位"); ElMessage.warning("请选择要分配的职位");

View File

@ -1,11 +0,0 @@
<script setup lang="ts" name="Testxb">
import { ref } from "vue";
// defineOptions({
// name: "Testxb"
// });
let name = ref("nihao");
</script>
<template>
<div>测试菜单</div>
</template>

View File

@ -28,8 +28,13 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="年级" prop="baseInfo.grade"> <el-form-item label="年级" prop="baseInfo.gradeLevel">
<el-select v-model="form.baseInfo.grade" placeholder="请选择年级" clearable> <el-select
v-model="form.baseInfo.gradeLevel"
placeholder="请年级"
clearable
style="width: 120px"
>
<el-option <el-option
v-for="g in gradeOptions" v-for="g in gradeOptions"
:key="g.value" :key="g.value"
@ -37,6 +42,12 @@
:value="g.value" :value="g.value"
/> />
</el-select> </el-select>
<el-input-number
style="width: 120px"
v-model="form.baseInfo.gradeYear"
:min="2020"
:max="2100"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -69,6 +80,25 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12">
<el-form-item label="预计解决时间" prop="baseInfo.endTime">
<el-date-picker
v-model="form.baseInfo.endTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选预计解决时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<div>
提示:离赴校时间超过<span class="text-yellow-500">{ {{ warningDay }}}</span
>黄色警告, 超过预计完成时间<span class="text-red-500"
>{ {{ errorDay }}}</span
>红色警告
</div>
</el-col>
</el-row> </el-row>
<el-divider>基础工作</el-divider> <el-divider>基础工作</el-divider>
@ -154,6 +184,7 @@ import { ref, reactive, computed, defineProps, defineEmits, watch } from "vue";
import type { FormInstance, FormRules } from "element-plus"; import type { FormInstance, FormRules } from "element-plus";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getSchoolData } from "@/api/userCenter"; import { getSchoolData } from "@/api/userCenter";
import { errorDay, warningDay } from "./config";
import { getSchoolBusinessPeopleListApi, addOrEditApi } from "@/api/toschoolinfomanage"; import { getSchoolBusinessPeopleListApi, addOrEditApi } from "@/api/toschoolinfomanage";
const props = defineProps<{ visible: boolean }>(); const props = defineProps<{ visible: boolean }>();
// const emit = defineEmits<{ (e: "update:visible", value: boolean): void }>(); // const emit = defineEmits<{ (e: "update:visible", value: boolean): void }>();
@ -198,13 +229,11 @@ const getSchoolBusinessPeopleList = () => {
}; };
getSchoolDataFn(); getSchoolDataFn();
getSchoolBusinessPeopleList(); getSchoolBusinessPeopleList();
const gradeOptions = [ const gradeOptions = [
{ label: "初一", value: "初一" }, { label: "高", value: "高" },
{ label: "初二", value: "初二" }, { label: "初", value: "初" },
{ label: "初三", value: "初三" }, { label: "小", value: "小" },
{ label: "高一", value: "高一" },
{ label: "高二", value: "高二" },
{ label: "高三", value: "高三" },
]; ];
type FeedbackKey = "leaders" | "classroom" | "equipment" | "students" | "others"; type FeedbackKey = "leaders" | "classroom" | "equipment" | "students" | "others";
@ -215,8 +244,10 @@ interface FeedbackItem {
interface FormModel { interface FormModel {
baseInfo: { baseInfo: {
school?: string; school?: string;
grade?: string; gradeYear?: number;
gradeLevel?: string;
date?: string; date?: string;
endTime?: string;
people: string[]; people: string[];
}; };
work: { work: {
@ -231,7 +262,8 @@ interface FormModel {
const form = reactive<FormModel>({ const form = reactive<FormModel>({
baseInfo: { baseInfo: {
school: undefined, school: undefined,
grade: undefined, gradeLevel: "高",
gradeYear: 2025,
date: undefined, date: undefined,
people: [], people: [],
}, },
@ -263,7 +295,7 @@ watch(
); );
const rules: FormRules = { const rules: FormRules = {
"baseInfo.school": [{ required: true, message: "请选择学校", trigger: "change" }], "baseInfo.school": [{ required: true, message: "请选择学校", trigger: "change" }],
"baseInfo.grade": [{ required: true, message: "请选择年级", trigger: "change" }], "baseInfo.gradeLevel": [{ required: true, message: "请选择年级", trigger: "change" }],
"baseInfo.date": [{ required: true, message: "请选择赴校时间", trigger: "change" }], "baseInfo.date": [{ required: true, message: "请选择赴校时间", trigger: "change" }],
"baseInfo.people": [ "baseInfo.people": [
{ required: true, message: "请选择赴校人员", trigger: "change" }, { required: true, message: "请选择赴校人员", trigger: "change" },
@ -412,11 +444,12 @@ async function onSubmit() {
id: 0, //id0 id: 0, //id0
schoolId: form.baseInfo.school, schoolId: form.baseInfo.school,
schoolName: schoolOptions.value.find((i) => i.value == form.baseInfo.school).label, schoolName: schoolOptions.value.find((i) => i.value == form.baseInfo.school).label,
grade: form.baseInfo.grade,
// //
gradeLevel: "", gradeLevel: form.baseInfo.gradeLevel,
gradeYear: form.baseInfo.gradeYear,
schoolBusinessUser: form.baseInfo.people, schoolBusinessUser: form.baseInfo.people,
startTime: form.baseInfo.date, startTime: form.baseInfo.date,
endTime: form.baseInfo.endTime,
isDiscussion: form.work.talk, isDiscussion: form.work.talk,
discussion: form.work.talkDetail, discussion: form.work.talkDetail,
isClassMeeting: form.work.classMeeting, isClassMeeting: form.work.classMeeting,
@ -445,7 +478,7 @@ async function onSubmit() {
function resetForm() { function resetForm() {
form.baseInfo.school = undefined; form.baseInfo.school = undefined;
form.baseInfo.grade = undefined; form.baseInfo.gradeLevel = undefined;
form.baseInfo.date = undefined; form.baseInfo.date = undefined;
form.baseInfo.people = []; form.baseInfo.people = [];
form.work.talk = false; form.work.talk = false;

View File

@ -0,0 +1,4 @@
/** 赴校后超时时间 */
export const warningDay = 8;
/** 赴校任务过期时间 */
export const errorDay = 2;

View File

@ -29,7 +29,7 @@
{{ safeDetail.schoolName || safeDetail.school || "-" }} {{ safeDetail.schoolName || safeDetail.school || "-" }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="年级"> <el-descriptions-item label="年级">
{{ safeDetail.grade || safeDetail.gradeLevel || "-" }} {{ safeDetail.gradeLevel + safeDetail.gradeYear }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="赴校人员"> <el-descriptions-item label="赴校人员">
{{ {{
@ -41,6 +41,10 @@
<el-descriptions-item label="赴校时间"> <el-descriptions-item label="赴校时间">
{{ safeDetail.startTimeStr || safeDetail.startTime }} {{ safeDetail.startTimeStr || safeDetail.startTime }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="预计完结时间">
{{ safeDetail.endTime != null ? safeDetail.endTime.split("T")[0] : "--" }}
</el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-divider /> <el-divider />
@ -76,34 +80,63 @@
<span> 未解决问题{{ unresolvedCount }} </span> <span> 未解决问题{{ unresolvedCount }} </span>
</div> </div>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane <el-tab-pane v-for="(i, idx) in questions" :key="idx" :name="idx">
v-for="(i, idx) in sortData(safeDetail.feedbackQuestions || [])" <template #label>
:key="idx" <span class="custom-tabs-label" :class="i.solution ? `text-[#67c23a]` : ``">
:label="'问题' + (idx + 1) + (i.solution ? '(✅已解决)' : '(未解决)')" <span>问题{{ idx + 1 }}</span>
:name="idx" <el-icon>
> <Select v-if="i.solution" />
</el-icon>
</span>
</template>
<div style="font-size: 12px; margin-bottom: 4px"> <div style="font-size: 12px; margin-bottom: 4px">
<span>问题类型</span> <span>{{ queType[i.questionType] }}</span> <span>问题类型</span> <span>{{ queType[i.questionType] }}</span>
</div> </div>
<div style="padding: 10px; background-color: #f3f3f3"> <div style="padding: 10px; background-color: #f3f3f3">
{{ i.question }} {{ i.question }}
</div> </div>
<div v-if="i.solution" style="font-size: 12px; margin-top: 10px"> <div
<span> 解决时间{{ i.endTimeStr || i.endTime }} </span> v-for="(rec, rIdx) in i.recordArr || []"
:key="rIdx"
style="margin-top: 10px"
>
<div>
执行记录{{ rIdx + 1 }}{{ rec.operator || "" }} {{ rec.executionTimeStr }}
</div>
<div style="padding: 5px; background-color: #f3f3f3">
{{ rec.executionRecords }}
</div>
</div>
<div v-if="i.solution" style="font-size: 12px; margin-top: 20px">
<span class="text-amber-400 font-bold">
解决时间{{ i.endTimeStr || i.endTime }}
</span>
<div style="padding: 10px; background-color: #f3f3f3"> <div style="padding: 10px; background-color: #f3f3f3">
{{ i.solution }} {{ i.solution }}
</div> </div>
</div> </div>
<div v-else class="mt-1.5">
<el-button <el-button
v-show="!isDetail" v-show="!isDetail"
type="text" link
v-else :disabled="i.solution"
style="margin-top: 5px; font-size: 12px"
class="markTitle"
@click="addQRecord(i)"
>
添加执行记录
</el-button>
<el-button
v-show="!isDetail"
link
style="margin-top: 5px; font-size: 12px" style="margin-top: 5px; font-size: 12px"
class="markTitle" class="markTitle"
@click="markTitle(i)" @click="markTitle(i)"
> >
标记已解决 标记已解决
</el-button> </el-button>
</div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-divider /> <el-divider />
@ -128,7 +161,7 @@
<!-- 添加按钮区域 --> <!-- 添加按钮区域 -->
<div style="margin-top: 5px; display: flex; gap: 20px"> <div style="margin-top: 5px; display: flex; gap: 20px">
<el-button <el-button
type="text" link
style="margin-top: 5px; font-size: 12px" style="margin-top: 5px; font-size: 12px"
class="markTitle" class="markTitle"
v-show="!isDetail" v-show="!isDetail"
@ -139,8 +172,10 @@
<el-button <el-button
style="margin-top: 5px; font-size: 12px" style="margin-top: 5px; font-size: 12px"
class="markTitle" class="markTitle"
type="text" link
v-show="!isDetail" v-show="!isDetail"
:disabled="unresolvedCount != 0"
title="请先解决所有问题后再添加完结情况"
@click="addFinish" @click="addFinish"
> >
{{ finishRecord ? "修改完结情况" : "添加完结情况" }} {{ finishRecord ? "修改完结情况" : "添加完结情况" }}
@ -160,7 +195,7 @@
border-radius: 4px; border-radius: 4px;
" "
> >
<div style="font-weight: bold; color: #409eff"> <div style="font-weight: bold; color: #67c23a">
执行记录{{ index + 1 }}{{ record.operator }} {{ record.time }} 执行记录{{ index + 1 }}{{ record.operator }} {{ record.time }}
</div> </div>
<div style="margin-top: 5px; white-space: pre-wrap"> <div style="margin-top: 5px; white-space: pre-wrap">
@ -180,9 +215,7 @@
border: 1px solid #b3d8ff; border: 1px solid #b3d8ff;
" "
> >
<div style="font-weight: bold; color: #a69400"> <div class="text-amber-400 font-bold">完结情况{{ finishRecord.time }}</div>
完结情况{{ finishRecord.time }}
</div>
<div style="margin-top: 5px; white-space: pre-wrap"> <div style="margin-top: 5px; white-space: pre-wrap">
{{ finishRecord.content }} {{ finishRecord.content }}
</div> </div>
@ -230,7 +263,7 @@
</template> </template>
<script setup lang="ts" name="EditModal"> <script setup lang="ts" name="EditModal">
import { ref, reactive, computed, defineProps, defineEmits, watch } from "vue"; import { ref, reactive, computed, defineProps, defineEmits, watch, onMounted } 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";
@ -238,13 +271,23 @@ import { getSchoolBusinessPeopleListApi, addOrEditApi } from "@/api/toschoolinfo
import { setFips } from "crypto"; import { setFips } from "crypto";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { isAllEmpty } from "@pureadmin/utils"; import { isAllEmpty } from "@pureadmin/utils";
import { Select, CloseBold } from "@element-plus/icons-vue";
const activeName = ref<any>(0); const activeName = ref<any>(0);
onMounted(() => {
console.log("onMounted ");
questions = ref(sortData(safeDetail.value.feedbackQuestions));
});
const handleClick = (tab: TabsPaneContext, event: Event) => { const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab.props.name, event); console.log(tab.props.name, event);
activeName.value = tab.props.name; activeName.value = tab.props.name;
}; };
const gradeOptions = [
{ label: "高", value: "高" },
{ label: "初", value: "初" },
{ label: "小", value: "小" },
];
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
detailData: any; detailData: any;
@ -305,6 +348,7 @@ const operationContentLabel = computed(() => {
case "markSolved": case "markSolved":
return "解决情况"; return "解决情况";
case "addRecord": case "addRecord":
case "addQRecord":
return "执行记录"; return "执行记录";
case "addFinish": case "addFinish":
return "完结情况"; return "完结情况";
@ -345,10 +389,12 @@ const queType = {
* 获取未解决问题数量 * 获取未解决问题数量
* @param data * @param data
*/ */
const handleUnHandleQust = (data: Array<any>) => { const handleUnHandleQust = (data: Array<any>): number => {
return (data || []).filter((i) => !i?.solution).length; return (data || []).filter((i) => !i?.solution).length;
}; };
const sortData = (data: Array<any>) => { const sortData = (data: Array<any>) => {
if (data == null || data.length == 0) return [];
const categorizedData = [ const categorizedData = [
...data ...data
.filter((item) => item.questionType === 1) .filter((item) => item.questionType === 1)
@ -373,6 +419,7 @@ const statusText = computed(() => (safeDetail.value?.solutionEnd ? "已完结" :
const statusType = computed(() => const statusType = computed(() =>
safeDetail.value?.solutionEnd ? "success" : "warning" safeDetail.value?.solutionEnd ? "success" : "warning"
); );
let questions = ref(sortData(safeDetail.value.feedbackQuestions));
const solutionText = computed({ const solutionText = computed({
get: () => safeDetail.value?.solutionRecord?.solution || "", get: () => safeDetail.value?.solutionRecord?.solution || "",
set: (value: string) => { set: (value: string) => {
@ -388,6 +435,7 @@ 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);
operationType.value = "markSolved"; operationType.value = "markSolved";
@ -398,6 +446,14 @@ const markTitle = (data: any) => {
operationFormRef.value?.clearValidate(); operationFormRef.value?.clearValidate();
operationDialogVisible.value = true; operationDialogVisible.value = true;
}; };
const addQRecord = (data: any) => {
console.log("添加问题执行记录", data);
operationType.value = "addQRecord";
currentMarkedQuestion.value = data;
//
operationFormRef.value?.clearValidate();
operationDialogVisible.value = true;
};
const addRecord = () => { const addRecord = () => {
console.log("添加执行记录"); console.log("添加执行记录");
operationType.value = "addRecord"; operationType.value = "addRecord";
@ -433,7 +489,11 @@ const closeOperationDialog = () => {
// //
currentMarkedQuestion.value = null; currentMarkedQuestion.value = null;
}; };
const userName = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickName)
? useUserStoreHook()?.userName
: useUserStoreHook()?.nickName;
});
const confirmOperation = async () => { const confirmOperation = async () => {
if (!operationFormRef.value) return; if (!operationFormRef.value) return;
@ -441,9 +501,21 @@ const confirmOperation = async () => {
await operationFormRef.value.validate(); await operationFormRef.value.validate();
const { operationTime, operationContent } = operationForm; const { operationTime, operationContent } = operationForm;
// //
switch (operationType.value) { switch (operationType.value) {
case "addQRecord":
//
if (currentMarkedQuestion.value) {
if (currentMarkedQuestion.value.recordArr == undefined) {
currentMarkedQuestion.value.recordArr = [];
}
currentMarkedQuestion.value.recordArr.push({
executionTime: operationTime,
executionRecords: operationContent,
operator: userName.value,
});
}
break;
case "addRecord": case "addRecord":
// //
executionRecords.value.push({ executionRecords.value.push({
@ -454,11 +526,7 @@ const confirmOperation = async () => {
if (!props.detailData.solutionRecord) props.detailData.solutionRecord = {} as any; if (!props.detailData.solutionRecord) 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 = [];
const userName = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickName)
? useUserStoreHook()?.userName
: useUserStoreHook()?.nickName;
});
props.detailData.solutionRecord.record.push({ props.detailData.solutionRecord.record.push({
executionTime: operationTime, executionTime: operationTime,
executionRecords: operationContent, executionRecords: operationContent,
@ -532,6 +600,14 @@ function onClickSave() {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.custom-tabs-label .el-icon {
vertical-align: middle;
}
.custom-tabs-label span {
vertical-align: middle;
margin-left: 4px;
}
.modal-header { .modal-header {
display: flex; display: flex;
align-items: center; align-items: center;
@ -555,7 +631,6 @@ function onClickSave() {
} }
.markTitle:hover { .markTitle:hover {
text-decoration: underline; text-decoration: underline;
cursor: pointer;
user-select: none; user-select: none;
} }
</style> </style>

View File

@ -23,7 +23,7 @@
v-model="query.grade" v-model="query.grade"
placeholder="请选择年级" placeholder="请选择年级"
clearable clearable
style="width: 140px" style="width: 120px"
> >
<el-option <el-option
v-for="g in gradeOptions" v-for="g in gradeOptions"
@ -32,6 +32,12 @@
:value="g.value" :value="g.value"
/> />
</el-select> </el-select>
<el-input-number
v-show="query.grade"
v-model="query.gradeYear"
:min="2020"
:max="2100"
/>
</el-form-item> </el-form-item>
<el-form-item label="赴校人员"> <el-form-item label="赴校人员">
<el-select <el-select
@ -88,7 +94,12 @@
<el-button type="info" @click="downLoadTpl">下载模版</el-button> <el-button type="info" @click="downLoadTpl">下载模版</el-button>
</div> </div>
<!-- 表格区域 --> <!-- 表格区域 -->
<el-table :data="listData" style="width: 100%" :max-height="500"> <el-table
:data="listData"
style="width: 100%"
:max-height="500"
:row-class-name="tableRowClassName"
>
<el-table-column label="操作" width="200"> <el-table-column label="操作" width="200">
<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)"
@ -127,9 +138,15 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="grade" label="年级" min-width="100" /> <el-table-column label="年级" min-width="100">
<template #default="{ row }"> {{ row.gradeLevel + row.gradeYear }} </template>
</el-table-column>
<el-table-column prop="people" label="赴校人员" min-width="120" /> <el-table-column prop="people" label="赴校人员" min-width="120" />
<el-table-column prop="times" label="赴校时间" min-width="140" /> <el-table-column label="赴校时间/解决时间" min-width="140">
<template #default="{ row }">
{{ row.times }}{{ row.endTime ? " / " + row.endTime : "" }}
</template>
</el-table-column>
<el-table-column prop="feedbackTotals" 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="solveTotals" label="解决问题数量" min-width="140" />
@ -155,6 +172,7 @@
<!-- 跟进 --> <!-- 跟进 -->
<EditModal <EditModal
v-model:visible="isShowEditModal" v-model:visible="isShowEditModal"
v-if="isShowEditModal"
:editModalLoading="editModalLoading" :editModalLoading="editModalLoading"
:detailData="detailData" :detailData="detailData"
:isDetail="isDetail" :isDetail="isDetail"
@ -180,13 +198,17 @@ import AddModal from "./addModal.vue";
import { Check, Search } from "@element-plus/icons-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";
import { errorDay, warningDay } from "./config";
interface TableItem { interface TableItem {
id: number; id: number;
school: string; school: string;
grade: string; grade: string;
people: string; people: string;
gradeLevel?: string;
gradeYear?: number;
canOperate: boolean; // canOperate: boolean; //
times: string; // YYYY-MM-DD times: string; // YYYY-MM-DD
endTime: string; //
feedbackTotals: number; feedbackTotals: number;
solveTotals: number; solveTotals: number;
solutionEnd: boolean; // true: , false: solutionEnd: boolean; // true: , false:
@ -196,6 +218,22 @@ interface TableItem {
const schoolOptions = ref([]); const schoolOptions = ref([]);
const peopleOptions = ref([]); const peopleOptions = ref([]);
const isDetail = ref(false); const isDetail = ref(false);
const tableRowClassName = ({ row, rowIndex }: { row: TableItem; rowIndex: number }) => {
if (row.endTime == null || row.endTime == "" || row.solutionEnd) return "";
const nD = new Date();
const d = new Date(row.endTime + " ");
const startTime = new Date(row.times + " ");
const daysDiff1 = (nD.getTime() - startTime.getTime()) / (1000 * 60 * 60 * 24);
const daysDiff = (d.getTime() - nD.getTime()) / (1000 * 60 * 60 * 24);
if (daysDiff <= -errorDay) {
return "error-row";
} else if (daysDiff1 >= warningDay) {
return "warning-row";
}
return "";
};
/** /**
* 获取学校下拉数据 * 获取学校下拉数据
*/ */
@ -236,12 +274,9 @@ onMounted(() => {
}); });
const gradeOptions = [ const gradeOptions = [
{ label: "初一", value: "初一" }, { label: "高", value: "高" },
{ label: "初二", value: "初二" }, { label: "初", value: "初" },
{ label: "初三", value: "初三" }, { label: "小", value: "小" },
{ label: "高一", value: "高一" },
{ label: "高二", value: "高二" },
{ label: "高三", value: "高三" },
]; ];
/** /**
* 新建赴校信息提交 * 新建赴校信息提交
@ -283,6 +318,7 @@ const addOrEdit = () => {
const query = reactive({ const query = reactive({
school: "" as string | undefined, school: "" as string | undefined,
grade: "" as string | undefined, grade: "" as string | undefined,
gradeYear: 2025 as number,
people: "" as string | undefined, people: "" as string | undefined,
solutionEnd: undefined, solutionEnd: undefined,
times: [] as string[], times: [] as string[],
@ -309,8 +345,11 @@ function mapApiItemToRow(item: any): TableItem {
id: item.id, id: item.id,
school: item.schoolName || "", school: item.schoolName || "",
grade: item.grade || "", grade: item.grade || "",
gradeYear: item.gradeYear || "",
gradeLevel: item.gradeLevel || "",
people: peopleArr.join(""), people: peopleArr.join(""),
times: start, times: start,
endTime: item.endTime ? dayjs(item.endTime).format("YYYY-MM-DD") : "",
canOperate: item.canOperate || false, // canOperate: item.canOperate || false, //
feedbackTotals: Number(item.feedbackCount) || 0, feedbackTotals: Number(item.feedbackCount) || 0,
solveTotals: Number(item.solveFeedbackCount) || 0, solveTotals: Number(item.solveFeedbackCount) || 0,
@ -326,7 +365,7 @@ async function loadList() {
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 && query.gradeYear) payload.grade = query.grade + query.gradeYear;
if (query.people) { if (query.people) {
payload.UserName = query.people; payload.UserName = query.people;
} }
@ -401,12 +440,12 @@ function onDelete(row: TableItem) {
*/ */
function onDetailOrFollow(row: TableItem, disabled = false) { function onDetailOrFollow(row: TableItem, disabled = false) {
isDetail.value = disabled; isDetail.value = disabled;
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;
isShowEditModal.value = true;
} }
}) })
.finally(() => { .finally(() => {
@ -504,4 +543,12 @@ function downLoadTpl() {
justify-content: center; justify-content: center;
margin-top: 12px; margin-top: 12px;
} }
:deep(.el-table .warning-row) {
--el-table-tr-bg-color: rgb(253, 246, 236);
}
:deep(.el-table .error-row) {
--el-table-tr-bg-color: rgb(255, 227, 227);
}
</style> </style>