优化条件筛选
This commit is contained in:
@@ -237,6 +237,10 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
int? page,
|
int? page,
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
|
Guid? collegeId,
|
||||||
|
Guid? majorId,
|
||||||
|
int? grade,
|
||||||
|
bool? isEnabled,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (page.HasValue || pageSize.HasValue)
|
if (page.HasValue || pageSize.HasValue)
|
||||||
@@ -245,6 +249,11 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var source = db.AdministrativeClasses.AsNoTracking();
|
var source = db.AdministrativeClasses.AsNoTracking();
|
||||||
|
if (collegeId.HasValue)
|
||||||
|
source = source.Where(x => x.Major!.CollegeId == collegeId.Value);
|
||||||
|
if (majorId.HasValue) source = source.Where(x => x.MajorId == majorId.Value);
|
||||||
|
if (grade.HasValue) source = source.Where(x => x.Grade == grade.Value);
|
||||||
|
if (isEnabled.HasValue) source = source.Where(x => x.IsEnabled == isEnabled.Value);
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
{
|
{
|
||||||
keyword = keyword.Trim();
|
keyword = keyword.Trim();
|
||||||
@@ -511,6 +520,11 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
int? page,
|
int? page,
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
|
Guid? campusId,
|
||||||
|
Guid? buildingId,
|
||||||
|
int? minimumCapacity,
|
||||||
|
TeachingVenueNature? teachingVenueNature,
|
||||||
|
bool? isEnabled,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (page.HasValue || pageSize.HasValue)
|
if (page.HasValue || pageSize.HasValue)
|
||||||
@@ -519,6 +533,14 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var source = db.Classrooms.AsNoTracking();
|
var source = db.Classrooms.AsNoTracking();
|
||||||
|
if (campusId.HasValue)
|
||||||
|
source = source.Where(x => x.Building!.CampusId == campusId.Value);
|
||||||
|
if (buildingId.HasValue) source = source.Where(x => x.BuildingId == buildingId.Value);
|
||||||
|
if (minimumCapacity.HasValue)
|
||||||
|
source = source.Where(x => x.Capacity >= minimumCapacity.Value);
|
||||||
|
if (teachingVenueNature.HasValue)
|
||||||
|
source = source.Where(x => (x.TeachingVenueNature & teachingVenueNature.Value) != 0);
|
||||||
|
if (isEnabled.HasValue) source = source.Where(x => x.IsEnabled == isEnabled.Value);
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
{
|
{
|
||||||
keyword = keyword.Trim();
|
keyword = keyword.Trim();
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ public sealed class GradeAnalyticsController(
|
|||||||
public async Task<ActionResult> GetTeachingClasses(
|
public async Task<ActionResult> GetTeachingClasses(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
string? keyword,
|
string? keyword,
|
||||||
|
Guid? collegeId = null,
|
||||||
|
string? teacherKeyword = null,
|
||||||
|
bool? riskOnly = null,
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 20,
|
int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -105,12 +108,23 @@ public sealed class GradeAnalyticsController(
|
|||||||
.Where(x => VisibleTeachingTasks().Any(task => task.Id == x.TeachingTaskId));
|
.Where(x => VisibleTeachingTasks().Any(task => task.Id == x.TeachingTaskId));
|
||||||
if (academicTermId.HasValue)
|
if (academicTermId.HasValue)
|
||||||
source = source.Where(x => x.AcademicTermId == academicTermId);
|
source = source.Where(x => x.AcademicTermId == academicTermId);
|
||||||
|
if (collegeId.HasValue)
|
||||||
|
source = source.Where(x => x.TeachingTask!.Course!.CollegeId == collegeId.Value);
|
||||||
if (keyword is not null)
|
if (keyword is not null)
|
||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.TeachingTask!.TaskNumber.Contains(keyword) ||
|
x.TeachingTask!.TaskNumber.Contains(keyword) ||
|
||||||
x.TeachingTask.Name.Contains(keyword) ||
|
x.TeachingTask.Name.Contains(keyword) ||
|
||||||
x.TeachingTask.Course!.Code.Contains(keyword) ||
|
x.TeachingTask.Course!.Code.Contains(keyword) ||
|
||||||
x.TeachingTask.Course.Name.Contains(keyword));
|
x.TeachingTask.Course.Name.Contains(keyword));
|
||||||
|
if (!string.IsNullOrWhiteSpace(teacherKeyword))
|
||||||
|
{
|
||||||
|
teacherKeyword = teacherKeyword.Trim();
|
||||||
|
source = source.Where(x => x.TeachingTask!.Teachers.Any(item =>
|
||||||
|
item.Teacher!.Name.Contains(teacherKeyword) ||
|
||||||
|
item.Teacher.TeacherNumber.Contains(teacherKeyword)));
|
||||||
|
}
|
||||||
|
if (riskOnly == true)
|
||||||
|
source = source.Where(x => x.PassRate < 60 || x.AverageScore < 60);
|
||||||
|
|
||||||
var total = await source.CountAsync(cancellationToken);
|
var total = await source.CountAsync(cancellationToken);
|
||||||
var items = await source
|
var items = await source
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ public sealed class UsersController(
|
|||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 20,
|
int pageSize = 20,
|
||||||
string? keyword = null,
|
string? keyword = null,
|
||||||
|
string? roleName = null,
|
||||||
|
Guid? collegeId = null,
|
||||||
|
bool? isEnabled = null,
|
||||||
|
bool? hasLoggedIn = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
@@ -42,6 +46,21 @@ public sealed class UsersController(
|
|||||||
where userRole.UserId == user.Id && role.Name!.Contains(keyword)
|
where userRole.UserId == user.Id && role.Name!.Contains(keyword)
|
||||||
select role.Id).Any());
|
select role.Id).Any());
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(roleName))
|
||||||
|
{
|
||||||
|
roleName = roleName.Trim();
|
||||||
|
query = query.Where(user =>
|
||||||
|
(from userRole in db.UserRoles
|
||||||
|
join candidateRole in db.Roles on userRole.RoleId equals candidateRole.Id
|
||||||
|
where userRole.UserId == user.Id && candidateRole.Name == roleName
|
||||||
|
select userRole.RoleId).Any());
|
||||||
|
}
|
||||||
|
if (collegeId.HasValue) query = query.Where(user => user.CollegeId == collegeId.Value);
|
||||||
|
if (isEnabled.HasValue) query = query.Where(user => user.IsEnabled == isEnabled.Value);
|
||||||
|
if (hasLoggedIn.HasValue)
|
||||||
|
query = hasLoggedIn.Value
|
||||||
|
? query.Where(user => user.LastLoginAt != null)
|
||||||
|
: query.Where(user => user.LastLoginAt == null);
|
||||||
|
|
||||||
var total = await query.CountAsync(cancellationToken);
|
var total = await query.CountAsync(cancellationToken);
|
||||||
var users = await query
|
var users = await query
|
||||||
|
|||||||
@@ -77,6 +77,16 @@ const references = reactive<Record<string, any[]>>({
|
|||||||
campuses: [], colleges: [], majors: [], buildings: [], counselors: [],
|
campuses: [], colleges: [], majors: [], buildings: [], counselors: [],
|
||||||
})
|
})
|
||||||
const form = reactive<Record<string, any>>({})
|
const form = reactive<Record<string, any>>({})
|
||||||
|
const listFilters = reactive({
|
||||||
|
collegeId: undefined as string | undefined,
|
||||||
|
majorId: undefined as string | undefined,
|
||||||
|
grade: undefined as number | undefined,
|
||||||
|
campusId: undefined as string | undefined,
|
||||||
|
buildingId: undefined as string | undefined,
|
||||||
|
minimumCapacity: undefined as number | undefined,
|
||||||
|
teachingVenueNature: undefined as number | undefined,
|
||||||
|
isEnabled: undefined as boolean | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const title = computed(() => tabs.value.find((x) => x.key === active.value)?.label ?? '')
|
const title = computed(() => tabs.value.find((x) => x.key === active.value)?.label ?? '')
|
||||||
const canManage = computed(() =>
|
const canManage = computed(() =>
|
||||||
@@ -90,6 +100,10 @@ const filteredRows = computed(() => {
|
|||||||
.filter(Boolean).some((value) => String(value).toLowerCase().includes(q)),
|
.filter(Boolean).some((value) => String(value).toLowerCase().includes(q)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
const filteredMajors = computed(() => references.majors.filter((item: any) =>
|
||||||
|
!listFilters.collegeId || item.collegeId === listFilters.collegeId))
|
||||||
|
const filteredBuildings = computed(() => references.buildings.filter((item: any) =>
|
||||||
|
!listFilters.campusId || item.campusId === listFilters.campusId))
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
@@ -154,6 +168,18 @@ async function load(resetPage = true) {
|
|||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
keyword: keyword.value.trim() || undefined,
|
keyword: keyword.value.trim() || undefined,
|
||||||
|
...(active.value === 'classes' ? {
|
||||||
|
collegeId: listFilters.collegeId,
|
||||||
|
majorId: listFilters.majorId,
|
||||||
|
grade: listFilters.grade,
|
||||||
|
isEnabled: listFilters.isEnabled,
|
||||||
|
} : {
|
||||||
|
campusId: listFilters.campusId,
|
||||||
|
buildingId: listFilters.buildingId,
|
||||||
|
minimumCapacity: listFilters.minimumCapacity,
|
||||||
|
teachingVenueNature: listFilters.teachingVenueNature,
|
||||||
|
isEnabled: listFilters.isEnabled,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
@@ -187,6 +213,11 @@ async function loadReferences() {
|
|||||||
|
|
||||||
async function changeTab() {
|
async function changeTab() {
|
||||||
keyword.value = ''
|
keyword.value = ''
|
||||||
|
Object.assign(listFilters, {
|
||||||
|
collegeId: undefined, majorId: undefined, grade: undefined,
|
||||||
|
campusId: undefined, buildingId: undefined, minimumCapacity: undefined,
|
||||||
|
teachingVenueNature: undefined, isEnabled: undefined,
|
||||||
|
})
|
||||||
await load()
|
await load()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +419,32 @@ watch(
|
|||||||
|
|
||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索编码、名称或所属单位" />
|
<el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索编码、名称或所属单位" />
|
||||||
|
<template v-if="active === 'classes'">
|
||||||
|
<el-select v-model="listFilters.collegeId" clearable placeholder="全部学院" @change="() => { listFilters.majorId = undefined; load() }">
|
||||||
|
<el-option v-for="item in references.colleges" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="listFilters.majorId" clearable filterable placeholder="全部专业" @change="load()">
|
||||||
|
<el-option v-for="item in filteredMajors" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
<el-input-number v-model="listFilters.grade" :min="1900" :max="2200" controls-position="right" placeholder="入学年级" @change="load()" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="active === 'classrooms'">
|
||||||
|
<el-select v-model="listFilters.campusId" clearable placeholder="全部校区" @change="() => { listFilters.buildingId = undefined; load() }">
|
||||||
|
<el-option v-for="item in references.campuses" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="listFilters.buildingId" clearable filterable placeholder="全部教学楼" @change="load()">
|
||||||
|
<el-option v-for="item in filteredBuildings" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="listFilters.teachingVenueNature" clearable placeholder="场地性质" @change="load()">
|
||||||
|
<el-option v-for="item in venueNatureOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input-number v-model="listFilters.minimumCapacity" :min="1" controls-position="right" placeholder="最小容量" @change="load()" />
|
||||||
|
</template>
|
||||||
|
<el-select v-if="serverPaged" v-model="listFilters.isEnabled" clearable placeholder="全部状态" @change="load()">
|
||||||
|
<el-option label="启用" :value="true" />
|
||||||
|
<el-option label="停用" :value="false" />
|
||||||
|
</el-select>
|
||||||
|
<el-button :icon="Search" @click="load()">查询</el-button>
|
||||||
<el-button :icon="Refresh" @click="() => load(false)">刷新</el-button>
|
<el-button :icon="Refresh" @click="() => load(false)">刷新</el-button>
|
||||||
<template v-if="canManage">
|
<template v-if="canManage">
|
||||||
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ interface TeachingClassItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const terms = ref<any[]>([])
|
const terms = ref<any[]>([])
|
||||||
|
const colleges = ref<any[]>([])
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const canManageSchedule = computed(() => auth.user?.roles.some(role => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ?? false)
|
const canManageSchedule = computed(() => auth.user?.roles.some(role => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ?? false)
|
||||||
const classes = ref<TeachingClassItem[]>([])
|
const classes = ref<TeachingClassItem[]>([])
|
||||||
@@ -46,6 +47,9 @@ const selected = ref<TeachingClassItem>()
|
|||||||
const report = ref<any>()
|
const report = ref<any>()
|
||||||
const termId = ref<string>()
|
const termId = ref<string>()
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
|
const collegeId = ref<string>()
|
||||||
|
const teacherKeyword = ref('')
|
||||||
|
const riskOnly = ref(false)
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const pageSize = 12
|
const pageSize = 12
|
||||||
@@ -109,6 +113,9 @@ async function loadClasses(reset = false) {
|
|||||||
params: {
|
params: {
|
||||||
academicTermId: termId.value,
|
academicTermId: termId.value,
|
||||||
keyword: keyword.value.trim() || undefined,
|
keyword: keyword.value.trim() || undefined,
|
||||||
|
collegeId: collegeId.value,
|
||||||
|
teacherKeyword: teacherKeyword.value.trim() || undefined,
|
||||||
|
riskOnly: riskOnly.value || undefined,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize,
|
pageSize,
|
||||||
},
|
},
|
||||||
@@ -343,7 +350,12 @@ watch(historyMetric, async () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
terms.value = (await http.get('/base-data/terms')).data
|
const [termResponse, collegeResponse] = await Promise.all([
|
||||||
|
http.get('/base-data/terms'),
|
||||||
|
http.get('/base-data/colleges'),
|
||||||
|
])
|
||||||
|
terms.value = termResponse.data
|
||||||
|
colleges.value = collegeResponse.data
|
||||||
termId.value = defaultAcademicTermId(terms.value)
|
termId.value = defaultAcademicTermId(terms.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(apiErrorMessage(error))
|
ElMessage.error(apiErrorMessage(error))
|
||||||
@@ -409,7 +421,12 @@ onBeforeUnmount(() => {
|
|||||||
<el-select v-model="termId" clearable placeholder="全部学期" style="width: 240px" @change="loadClasses(true)">
|
<el-select v-model="termId" clearable placeholder="全部学期" style="width: 240px" @change="loadClasses(true)">
|
||||||
<el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" />
|
<el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-select v-model="collegeId" clearable placeholder="全部开课学院" style="width: 190px" @change="loadClasses(true)">
|
||||||
|
<el-option v-for="college in colleges" :key="college.id" :label="college.name" :value="college.id" />
|
||||||
|
</el-select>
|
||||||
<el-input v-model="keyword" clearable placeholder="课程、教学班名称或编号" :prefix-icon="Search" @keyup.enter="loadClasses(true)" />
|
<el-input v-model="keyword" clearable placeholder="课程、教学班名称或编号" :prefix-icon="Search" @keyup.enter="loadClasses(true)" />
|
||||||
|
<el-input v-model="teacherKeyword" clearable placeholder="任课教师姓名或工号" :prefix-icon="Search" @keyup.enter="loadClasses(true)" @clear="loadClasses(true)" />
|
||||||
|
<el-checkbox v-model="riskOnly" @change="loadClasses(true)">仅看预警班</el-checkbox>
|
||||||
<el-button type="primary" @click="loadClasses(true)">查询</el-button>
|
<el-button type="primary" @click="loadClasses(true)">查询</el-button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const editingRoles = ref<string[]>([])
|
|||||||
const editingStaffNumber = ref('')
|
const editingStaffNumber = ref('')
|
||||||
const editingCollegeId = ref<string>()
|
const editingCollegeId = ref<string>()
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
|
const roleFilter = ref<string>()
|
||||||
|
const collegeFilter = ref<string>()
|
||||||
|
const enabledFilter = ref<boolean>()
|
||||||
|
const loginFilter = ref<boolean>()
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const pageSize = 20
|
const pageSize = 20
|
||||||
@@ -69,6 +73,10 @@ async function load(resetPage = false) {
|
|||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize,
|
pageSize,
|
||||||
keyword: keyword.value.trim() || undefined,
|
keyword: keyword.value.trim() || undefined,
|
||||||
|
roleName: roleFilter.value,
|
||||||
|
collegeId: collegeFilter.value,
|
||||||
|
isEnabled: enabledFilter.value,
|
||||||
|
hasLoggedIn: loginFilter.value,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
http.get('/users/roles'),
|
http.get('/users/roles'),
|
||||||
@@ -231,6 +239,20 @@ onMounted(load)
|
|||||||
@clear="load(true)"
|
@clear="load(true)"
|
||||||
@keyup.enter="load(true)"
|
@keyup.enter="load(true)"
|
||||||
/>
|
/>
|
||||||
|
<el-select v-model="roleFilter" clearable placeholder="全部角色" @change="load(true)">
|
||||||
|
<el-option v-for="role in roles" :key="role.name" :label="role.name" :value="role.name" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="collegeFilter" clearable placeholder="全部学院" @change="load(true)">
|
||||||
|
<el-option v-for="college in colleges" :key="college.id" :label="college.name" :value="college.id" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="enabledFilter" clearable placeholder="全部账号状态" @change="load(true)">
|
||||||
|
<el-option label="启用" :value="true" />
|
||||||
|
<el-option label="停用" :value="false" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="loginFilter" clearable placeholder="全部登录情况" @change="load(true)">
|
||||||
|
<el-option label="已登录过" :value="true" />
|
||||||
|
<el-option label="从未登录" :value="false" />
|
||||||
|
</el-select>
|
||||||
<el-button :icon="Search" @click="load(true)">查询</el-button>
|
<el-button :icon="Search" @click="load(true)">查询</el-button>
|
||||||
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
||||||
<el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button>
|
<el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button>
|
||||||
|
|||||||
Reference in New Issue
Block a user