updates
This commit is contained in:
parent
f24b40eea0
commit
94d28b4805
4
.env.dev
4
.env.dev
|
|
@ -1,6 +1,6 @@
|
|||
# backend service base url, test environment
|
||||
#VITE_SERVICE_BASE_URL=http://localhost:8080
|
||||
VITE_SERVICE_BASE_URL=http://10.13.13.1:8090
|
||||
VITE_SERVICE_BASE_URL=http://localhost:8080
|
||||
# VITE_SERVICE_BASE_URL=http://10.13.13.1:8090
|
||||
|
||||
VITE_APP_BASE_API=/dev-api
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# 任务执行摘要
|
||||
|
||||
## 会话 ID: 2026-03-22-01
|
||||
- [2026-03-22 09:32:12]
|
||||
- **执行原因**: 解决动态路由 system_user_auth-role 视图缺失报错
|
||||
- **执行过程**:
|
||||
1. 新增用户授权角色视图文件。
|
||||
2. 运行路由生成以更新 imports/routes/types。
|
||||
- **执行结果**: 视图映射已补齐,路由转换不再报错。
|
||||
|
||||
## 会话 ID: 2026-03-22-02
|
||||
- [2026-03-22 09:37:37]
|
||||
- **执行原因**: 修复动态路由模式下跳转 `system_oss-config` 无匹配路由报错
|
||||
- **执行过程**:
|
||||
1. 提取并扁平化嵌套路由中标记为 `constant` 的页面。
|
||||
2. 动态模式初始化常量路由时合并上述嵌套常量路由并去重。
|
||||
- **执行结果**: 动态模式下 `system_oss-config` 已注册到路由表,跳转不再报错。
|
||||
|
|
@ -23,22 +23,8 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||
"social-callback": () => import("@/views/_builtin/social-callback/index.vue"),
|
||||
"user-center": () => import("@/views/_builtin/user-center/index.vue"),
|
||||
about: () => import("@/views/about/index.vue"),
|
||||
"art_history-score-control-line": () => import("@/views/art/history-score-control-line/index.vue"),
|
||||
art_major: () => import("@/views/art/major/index.vue"),
|
||||
"art_school-recruit-major-history": () => import("@/views/art/school-recruit-major-history/index.vue"),
|
||||
"art_school-recruit-major": () => import("@/views/art/school-recruit-major/index.vue"),
|
||||
art_school: () => import("@/views/art/school/index.vue"),
|
||||
"art_school_modules_school-campus": () => import("@/views/art/school/modules/school-campus/index.vue"),
|
||||
"art_school_modules_school-college": () => import("@/views/art/school/modules/school-college/index.vue"),
|
||||
"art_school_modules_school-detail-json": () => import("@/views/art/school/modules/school-detail-json/index.vue"),
|
||||
"art_school_modules_school-detail": () => import("@/views/art/school/modules/school-detail/index.vue"),
|
||||
"art_school_modules_school-dorm": () => import("@/views/art/school/modules/school-dorm/index.vue"),
|
||||
"art_school_modules_school-enroll-plan": () => import("@/views/art/school/modules/school-enroll-plan/index.vue"),
|
||||
"art_school_modules_school-major-tag": () => import("@/views/art/school/modules/school-major-tag/index.vue"),
|
||||
"art_school_modules_school-major": () => import("@/views/art/school/modules/school-major/index.vue"),
|
||||
"art_school_modules_school-media": () => import("@/views/art/school/modules/school-media/index.vue"),
|
||||
"art_school_modules_school-name": () => import("@/views/art/school/modules/school-name/index.vue"),
|
||||
"art_school_modules_school-tag": () => import("@/views/art/school/modules/school-tag/index.vue"),
|
||||
"client_platform-user": () => import("@/views/client/platform-user/index.vue"),
|
||||
client_user: () => import("@/views/client/user/index.vue"),
|
||||
demo_demo: () => import("@/views/demo/demo/index.vue"),
|
||||
demo_tree: () => import("@/views/demo/tree/index.vue"),
|
||||
home: () => import("@/views/home/index.vue"),
|
||||
|
|
|
|||
|
|
@ -51,168 +51,30 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'art',
|
||||
path: '/art',
|
||||
name: 'client',
|
||||
path: '/client',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'art',
|
||||
i18nKey: 'route.art'
|
||||
title: 'client',
|
||||
i18nKey: 'route.client'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'art_history-score-control-line',
|
||||
path: '/art/history-score-control-line',
|
||||
component: 'view.art_history-score-control-line',
|
||||
name: 'client_platform-user',
|
||||
path: '/client/platform-user',
|
||||
component: 'view.client_platform-user',
|
||||
meta: {
|
||||
title: 'art_history-score-control-line',
|
||||
i18nKey: 'route.art_history-score-control-line'
|
||||
title: 'client_platform-user',
|
||||
i18nKey: 'route.client_platform-user'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_major',
|
||||
path: '/art/major',
|
||||
component: 'view.art_major',
|
||||
name: 'client_user',
|
||||
path: '/client/user',
|
||||
component: 'view.client_user',
|
||||
meta: {
|
||||
title: 'art_major',
|
||||
i18nKey: 'route.art_major'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school',
|
||||
path: '/art/school',
|
||||
component: 'view.art_school',
|
||||
meta: {
|
||||
title: 'art_school',
|
||||
i18nKey: 'route.art_school'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'art_school_modules',
|
||||
path: '/art/school/modules',
|
||||
meta: {
|
||||
title: 'art_school_modules',
|
||||
i18nKey: 'route.art_school_modules'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'art_school_modules_school-campus',
|
||||
path: '/art/school/modules/school-campus',
|
||||
component: 'view.art_school_modules_school-campus',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-campus',
|
||||
i18nKey: 'route.art_school_modules_school-campus'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-college',
|
||||
path: '/art/school/modules/school-college',
|
||||
component: 'view.art_school_modules_school-college',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-college',
|
||||
i18nKey: 'route.art_school_modules_school-college'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-detail',
|
||||
path: '/art/school/modules/school-detail',
|
||||
component: 'view.art_school_modules_school-detail',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-detail',
|
||||
i18nKey: 'route.art_school_modules_school-detail'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-detail-json',
|
||||
path: '/art/school/modules/school-detail-json',
|
||||
component: 'view.art_school_modules_school-detail-json',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-detail-json',
|
||||
i18nKey: 'route.art_school_modules_school-detail-json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-dorm',
|
||||
path: '/art/school/modules/school-dorm',
|
||||
component: 'view.art_school_modules_school-dorm',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-dorm',
|
||||
i18nKey: 'route.art_school_modules_school-dorm'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-enroll-plan',
|
||||
path: '/art/school/modules/school-enroll-plan',
|
||||
component: 'view.art_school_modules_school-enroll-plan',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-enroll-plan',
|
||||
i18nKey: 'route.art_school_modules_school-enroll-plan'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-major',
|
||||
path: '/art/school/modules/school-major',
|
||||
component: 'view.art_school_modules_school-major',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-major',
|
||||
i18nKey: 'route.art_school_modules_school-major'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-major-tag',
|
||||
path: '/art/school/modules/school-major-tag',
|
||||
component: 'view.art_school_modules_school-major-tag',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-major-tag',
|
||||
i18nKey: 'route.art_school_modules_school-major-tag'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-media',
|
||||
path: '/art/school/modules/school-media',
|
||||
component: 'view.art_school_modules_school-media',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-media',
|
||||
i18nKey: 'route.art_school_modules_school-media'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-name',
|
||||
path: '/art/school/modules/school-name',
|
||||
component: 'view.art_school_modules_school-name',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-name',
|
||||
i18nKey: 'route.art_school_modules_school-name'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school_modules_school-tag',
|
||||
path: '/art/school/modules/school-tag',
|
||||
component: 'view.art_school_modules_school-tag',
|
||||
meta: {
|
||||
title: 'art_school_modules_school-tag',
|
||||
i18nKey: 'route.art_school_modules_school-tag'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'art_school-recruit-major',
|
||||
path: '/art/school-recruit-major',
|
||||
component: 'view.art_school-recruit-major',
|
||||
meta: {
|
||||
title: 'art_school-recruit-major',
|
||||
i18nKey: 'route.art_school-recruit-major'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'art_school-recruit-major-history',
|
||||
path: '/art/school-recruit-major-history',
|
||||
component: 'view.art_school-recruit-major-history',
|
||||
meta: {
|
||||
title: 'art_school-recruit-major-history',
|
||||
i18nKey: 'route.art_school-recruit-major-history'
|
||||
title: 'client_user',
|
||||
i18nKey: 'route.client_user'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -171,24 +171,9 @@ const routeMap: RouteMap = {
|
|||
"404": "/404",
|
||||
"500": "/500",
|
||||
"about": "/about",
|
||||
"art": "/art",
|
||||
"art_history-score-control-line": "/art/history-score-control-line",
|
||||
"art_major": "/art/major",
|
||||
"art_school": "/art/school",
|
||||
"art_school_modules": "/art/school/modules",
|
||||
"art_school_modules_school-campus": "/art/school/modules/school-campus",
|
||||
"art_school_modules_school-college": "/art/school/modules/school-college",
|
||||
"art_school_modules_school-detail": "/art/school/modules/school-detail",
|
||||
"art_school_modules_school-detail-json": "/art/school/modules/school-detail-json",
|
||||
"art_school_modules_school-dorm": "/art/school/modules/school-dorm",
|
||||
"art_school_modules_school-enroll-plan": "/art/school/modules/school-enroll-plan",
|
||||
"art_school_modules_school-major": "/art/school/modules/school-major",
|
||||
"art_school_modules_school-major-tag": "/art/school/modules/school-major-tag",
|
||||
"art_school_modules_school-media": "/art/school/modules/school-media",
|
||||
"art_school_modules_school-name": "/art/school/modules/school-name",
|
||||
"art_school_modules_school-tag": "/art/school/modules/school-tag",
|
||||
"art_school-recruit-major": "/art/school-recruit-major",
|
||||
"art_school-recruit-major-history": "/art/school-recruit-major-history",
|
||||
"client": "/client",
|
||||
"client_platform-user": "/client/platform-user",
|
||||
"client_user": "/client/user",
|
||||
"demo": "/demo",
|
||||
"demo_demo": "/demo/demo",
|
||||
"demo_tree": "/demo/tree",
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取历年省控线列表 */
|
||||
export function fetchGetHistoryScoreControlLineList(params?: Api.Art.HistoryScoreControlLineSearchParams) {
|
||||
return request<Api.Art.HistoryScoreControlLineList>({
|
||||
url: '/art/historyScoreControlLine/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增历年省控线 */
|
||||
export function fetchCreateHistoryScoreControlLine(data: Api.Art.HistoryScoreControlLineOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/historyScoreControlLine',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改历年省控线 */
|
||||
export function fetchUpdateHistoryScoreControlLine(data: Api.Art.HistoryScoreControlLineOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/historyScoreControlLine',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除历年省控线 */
|
||||
export function fetchBatchDeleteHistoryScoreControlLine(controlIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/historyScoreControlLine/${controlIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取艺术专业库列表 */
|
||||
export function fetchGetMajorList(params?: Api.Art.MajorSearchParams) {
|
||||
return request<Api.Art.MajorList>({
|
||||
url: '/art/major/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增艺术专业库 */
|
||||
export function fetchCreateMajor(data: Api.Art.MajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/major',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改艺术专业库 */
|
||||
export function fetchUpdateMajor(data: Api.Art.MajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/major',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除艺术专业库 */
|
||||
export function fetchBatchDeleteMajor(majorIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/major/${majorIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校校区列表 */
|
||||
export function fetchGetSchoolCampusList(params?: Api.Art.SchoolCampusSearchParams) {
|
||||
return request<Api.Art.SchoolCampusList>({
|
||||
url: '/art/schoolCampus/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校校区 */
|
||||
export function fetchCreateSchoolCampus(data: Api.Art.SchoolCampusOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolCampus',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校校区 */
|
||||
export function fetchUpdateSchoolCampus(data: Api.Art.SchoolCampusOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolCampus',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校校区 */
|
||||
export function fetchBatchDeleteSchoolCampus(campusIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolCampus/${campusIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校学院列表 */
|
||||
export function fetchGetSchoolCollegeList(params?: Api.Art.SchoolCollegeSearchParams) {
|
||||
return request<Api.Art.SchoolCollegeList>({
|
||||
url: '/art/schoolCollege/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校学院 */
|
||||
export function fetchCreateSchoolCollege(data: Api.Art.SchoolCollegeOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolCollege',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校学院 */
|
||||
export function fetchUpdateSchoolCollege(data: Api.Art.SchoolCollegeOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolCollege',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校学院 */
|
||||
export function fetchBatchDeleteSchoolCollege(collegeIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolCollege/${collegeIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校详细信息列表 */
|
||||
export function fetchGetSchoolDetailList(params?: Api.Art.SchoolDetailSearchParams) {
|
||||
return request<Api.Art.SchoolDetailList>({
|
||||
url: '/art/schoolDetail/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据学校ID获取学校详情信息(单条) */
|
||||
export async function fetchGetSchoolDetailBySchoolId(schoolId: CommonType.IdType) {
|
||||
const result = await fetchGetSchoolDetailList({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
schoolId,
|
||||
params: {}
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
return { ...result, data: null as Api.Art.SchoolDetail | null };
|
||||
}
|
||||
|
||||
const detail = result.data.rows[0] ?? null;
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: detail
|
||||
};
|
||||
}
|
||||
/** 新增学校详细信息 */
|
||||
export function fetchCreateSchoolDetail(data: Api.Art.SchoolDetailOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校详细信息 */
|
||||
export function fetchUpdateSchoolDetail(data: Api.Art.SchoolDetailOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取满意度明细JSON */
|
||||
export function fetchGetSchoolDetailSatisfactionJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailSatisfactionPayload>({
|
||||
url: '/art/schoolDetail/json/satisfaction',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新满意度明细JSON */
|
||||
export function fetchUpdateSchoolDetailSatisfactionJson(data: Api.Art.SchoolDetailSatisfactionPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/satisfaction',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取奖学金JSON */
|
||||
export function fetchGetSchoolDetailScholarshipJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailScholarshipPayload>({
|
||||
url: '/art/schoolDetail/json/scholarship',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新奖学金JSON */
|
||||
export function fetchUpdateSchoolDetailScholarshipJson(data: Api.Art.SchoolDetailScholarshipPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/scholarship',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取特色专业JSON */
|
||||
export function fetchGetSchoolDetailSpecialMajorJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailSpecialMajorPayload>({
|
||||
url: '/art/schoolDetail/json/specialMajor',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新特色专业JSON */
|
||||
export function fetchUpdateSchoolDetailSpecialMajorJson(data: Api.Art.SchoolDetailSpecialMajorPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/specialMajor',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取就业报告JSON */
|
||||
export function fetchGetSchoolDetailEmploymentReportJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailEmploymentReportPayload>({
|
||||
url: '/art/schoolDetail/json/employmentReport',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新就业报告JSON */
|
||||
export function fetchUpdateSchoolDetailEmploymentReportJson(data: Api.Art.SchoolDetailEmploymentReportPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/employmentReport',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取学校图片JSON */
|
||||
export function fetchGetSchoolDetailPhotoJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailPhotoPayload>({
|
||||
url: '/art/schoolDetail/json/photo',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新学校图片JSON */
|
||||
export function fetchUpdateSchoolDetailPhotoJson(data: Api.Art.SchoolDetailPhotoPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/photo',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取建筑配套JSON */
|
||||
export function fetchGetSchoolDetailAccommodationJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailAccommodationPayload>({
|
||||
url: '/art/schoolDetail/json/accommodation',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新建筑配套JSON */
|
||||
export function fetchUpdateSchoolDetailAccommodationJson(data: Api.Art.SchoolDetailAccommodationPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/accommodation',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取学科评估JSON */
|
||||
export function fetchGetSchoolDetailSubjectReviewsJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailSubjectReviewsPayload>({
|
||||
url: '/art/schoolDetail/json/subjectReviews',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新学科评估JSON */
|
||||
export function fetchUpdateSchoolDetailSubjectReviewsJson(data: Api.Art.SchoolDetailSubjectReviewsPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/subjectReviews',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取科研信息JSON */
|
||||
export function fetchGetSchoolDetailResearchJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailResearchPayload>({
|
||||
url: '/art/schoolDetail/json/research',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新科研信息JSON */
|
||||
export function fetchUpdateSchoolDetailResearchJson(data: Api.Art.SchoolDetailResearchPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/research',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取专业标签JSON */
|
||||
export function fetchGetSchoolDetailUnivMajorsJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailUnivMajorsPayload>({
|
||||
url: '/art/schoolDetail/json/univMajors',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新专业标签JSON */
|
||||
export function fetchUpdateSchoolDetailUnivMajorsJson(data: Api.Art.SchoolDetailUnivMajorsPayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/univMajors',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取保研信息JSON */
|
||||
export function fetchGetSchoolDetailUnivPostgraduateJson(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailUnivPostgraduatePayload>({
|
||||
url: '/art/schoolDetail/json/univPostgraduate',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新保研信息JSON */
|
||||
export function fetchUpdateSchoolDetailUnivPostgraduateJson(data: Api.Art.SchoolDetailUnivPostgraduatePayload) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDetail/json/univPostgraduate',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取院校详情JSONB全量 */
|
||||
export function fetchGetSchoolDetailJsonAll(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.SchoolDetailJsonAllPayload>({
|
||||
url: '/art/schoolDetail/json/all',
|
||||
method: 'get',
|
||||
params: { schoolId }
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校详细信息 */
|
||||
export function fetchBatchDeleteSchoolDetail(detailIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolDetail/${detailIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取校区宿舍条件列表 */
|
||||
export function fetchGetSchoolDormList(params?: Api.Art.SchoolDormSearchParams) {
|
||||
return request<Api.Art.SchoolDormList>({
|
||||
url: '/art/schoolDorm/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增校区宿舍条件 */
|
||||
export function fetchCreateSchoolDorm(data: Api.Art.SchoolDormOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDorm',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改校区宿舍条件 */
|
||||
export function fetchUpdateSchoolDorm(data: Api.Art.SchoolDormOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolDorm',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除校区宿舍条件 */
|
||||
export function fetchBatchDeleteSchoolDorm(dormIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolDorm/${dormIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校招生计划列表 */
|
||||
export function fetchGetSchoolEnrollPlanList(params?: Api.Art.SchoolEnrollPlanSearchParams) {
|
||||
return request<Api.Art.SchoolEnrollPlanList>({
|
||||
url: '/art/schoolEnrollPlan/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校招生计划 */
|
||||
export function fetchCreateSchoolEnrollPlan(data: Api.Art.SchoolEnrollPlanOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolEnrollPlan',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校招生计划 */
|
||||
export function fetchUpdateSchoolEnrollPlan(data: Api.Art.SchoolEnrollPlanOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolEnrollPlan',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校招生计划 */
|
||||
export function fetchBatchDeleteSchoolEnrollPlan(planIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolEnrollPlan/${planIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取专业标签列表 */
|
||||
export function fetchGetSchoolMajorTagList(params?: Api.Art.SchoolMajorTagSearchParams) {
|
||||
return request<Api.Art.SchoolMajorTagList>({
|
||||
url: '/art/schoolMajorTag/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增专业标签 */
|
||||
export function fetchCreateSchoolMajorTag(data: Api.Art.SchoolMajorTagOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMajorTag',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改专业标签 */
|
||||
export function fetchUpdateSchoolMajorTag(data: Api.Art.SchoolMajorTagOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMajorTag',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除专业标签 */
|
||||
export function fetchBatchDeleteSchoolMajorTag(majorTagIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolMajorTag/${majorTagIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校专业列表 */
|
||||
export function fetchGetSchoolMajorList(params?: Api.Art.SchoolMajorSearchParams) {
|
||||
return request<Api.Art.SchoolMajorList>({
|
||||
url: '/art/schoolMajor/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校专业 */
|
||||
export function fetchCreateSchoolMajor(data: Api.Art.SchoolMajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMajor',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校专业 */
|
||||
export function fetchUpdateSchoolMajor(data: Api.Art.SchoolMajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMajor',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校专业 */
|
||||
export function fetchBatchDeleteSchoolMajor(majorIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolMajor/${majorIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校媒体资源列表 */
|
||||
export function fetchGetSchoolMediaList(params?: Api.Art.SchoolMediaSearchParams) {
|
||||
return request<Api.Art.SchoolMediaList>({
|
||||
url: '/art/schoolMedia/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校媒体资源 */
|
||||
export function fetchCreateSchoolMedia(data: Api.Art.SchoolMediaOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMedia',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校媒体资源 */
|
||||
export function fetchUpdateSchoolMedia(data: Api.Art.SchoolMediaOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolMedia',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校媒体资源 */
|
||||
export function fetchBatchDeleteSchoolMedia(mediaIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolMedia/${mediaIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校多名称列表 */
|
||||
export function fetchGetSchoolNameList(params?: Api.Art.SchoolNameSearchParams) {
|
||||
return request<Api.Art.SchoolNameList>({
|
||||
url: '/art/schoolName/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校多名称 */
|
||||
export function fetchCreateSchoolName(data: Api.Art.SchoolNameOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolName',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校多名称 */
|
||||
export function fetchUpdateSchoolName(data: Api.Art.SchoolNameOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolName',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校多名称 */
|
||||
export function fetchBatchDeleteSchoolName(schoolNameIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolName/${schoolNameIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取院校招录专业历年录取数据列表 */
|
||||
export function fetchGetSchoolRecruitMajorHistoryList(params?: Api.Art.SchoolRecruitMajorHistorySearchParams) {
|
||||
return request<Api.Art.SchoolRecruitMajorHistoryList>({
|
||||
url: '/art/schoolRecruitMajorHistory/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增院校招录专业历年录取数据 */
|
||||
export function fetchCreateSchoolRecruitMajorHistory(data: Api.Art.SchoolRecruitMajorHistoryOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolRecruitMajorHistory',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改院校招录专业历年录取数据 */
|
||||
export function fetchUpdateSchoolRecruitMajorHistory(data: Api.Art.SchoolRecruitMajorHistoryOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolRecruitMajorHistory',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除院校招录专业历年录取数据 */
|
||||
export function fetchBatchDeleteSchoolRecruitMajorHistory(historyIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolRecruitMajorHistory/${historyIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取院校招录专业列表 */
|
||||
export function fetchGetSchoolRecruitMajorList(params?: Api.Art.SchoolRecruitMajorSearchParams) {
|
||||
return request<Api.Art.SchoolRecruitMajorList>({
|
||||
url: '/art/schoolRecruitMajor/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增院校招录专业 */
|
||||
export function fetchCreateSchoolRecruitMajor(data: Api.Art.SchoolRecruitMajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolRecruitMajor',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改院校招录专业 */
|
||||
export function fetchUpdateSchoolRecruitMajor(data: Api.Art.SchoolRecruitMajorOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolRecruitMajor',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除院校招录专业 */
|
||||
export function fetchBatchDeleteSchoolRecruitMajor(recruitMajorIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolRecruitMajor/${recruitMajorIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校标签列表 */
|
||||
export function fetchGetSchoolTagList(params?: Api.Art.SchoolTagSearchParams) {
|
||||
return request<Api.Art.SchoolTagList>({
|
||||
url: '/art/schoolTag/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增学校标签 */
|
||||
export function fetchCreateSchoolTag(data: Api.Art.SchoolTagOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolTag',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校标签 */
|
||||
export function fetchUpdateSchoolTag(data: Api.Art.SchoolTagOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/art/schoolTag',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校标签 */
|
||||
export function fetchBatchDeleteSchoolTag(schoolTagIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/schoolTag/${schoolTagIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取学校基础信息主表列表 */
|
||||
export function fetchGetSchoolList(params?: Api.Art.SchoolSearchParams) {
|
||||
return request<Api.Art.SchoolList>({
|
||||
url: '/art/school/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据学校ID获取学校基础信息 */
|
||||
export function fetchGetSchoolInfo(schoolId: CommonType.IdType) {
|
||||
return request<Api.Art.School>({
|
||||
url: `/art/school/${schoolId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增学校基础信息主表 */
|
||||
export function fetchCreateSchool(data: Api.Art.SchoolWithDetailOperateParams) {
|
||||
return request<boolean | Api.Art.SchoolWithDetailSubmitStatus>({
|
||||
url: '/art/school',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改学校基础信息主表 */
|
||||
export function fetchUpdateSchool(data: Api.Art.SchoolWithDetailOperateParams) {
|
||||
return request<boolean | Api.Art.SchoolWithDetailSubmitStatus>({
|
||||
url: '/art/school',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除学校基础信息主表 */
|
||||
export function fetchBatchDeleteSchool(schoolIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/art/school/${schoolIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/** 导入预检 */
|
||||
export function fetchImportPreviewSchool(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return request<Api.Art.SchoolImportPreviewResult>({
|
||||
url: '/art/school/importPreview',
|
||||
method: 'post',
|
||||
data: formData
|
||||
});
|
||||
}
|
||||
|
||||
/** 导入执行 */
|
||||
export function fetchImportSchoolData(params: Api.Art.SchoolImportParams) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', params.file);
|
||||
|
||||
if (params.replaceAll !== undefined) {
|
||||
formData.append('replaceAll', String(params.replaceAll));
|
||||
}
|
||||
|
||||
params.replaceMainCodes?.forEach(mainCode => {
|
||||
formData.append('replaceMainCodes', mainCode);
|
||||
});
|
||||
|
||||
return request<Api.Art.SchoolImportExecuteResult>({
|
||||
url: '/art/school/importData',
|
||||
method: 'post',
|
||||
data: formData
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取平台用户关联(微信/抖音小程序用户信息)列表 */
|
||||
export function fetchGetPlatformUserList (params?: Api.Client.PlatformUserSearchParams) {
|
||||
return request<Api.Client.PlatformUserList>({
|
||||
url: '/client/platformUser/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增平台用户关联(微信/抖音小程序用户信息) */
|
||||
export function fetchCreatePlatformUser (data: Api.Client.PlatformUserOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/client/platformUser',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改平台用户关联(微信/抖音小程序用户信息) */
|
||||
export function fetchUpdatePlatformUser (data: Api.Client.PlatformUserOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/client/platformUser',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除平台用户关联(微信/抖音小程序用户信息) */
|
||||
export function fetchBatchDeletePlatformUser (ids: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/client/platformUser/${ids.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取客户用户基础信息列表 */
|
||||
export function fetchGetUserList (params?: Api.Client.UserSearchParams) {
|
||||
return request<Api.Client.UserList>({
|
||||
url: '/client/user/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 新增客户用户基础信息 */
|
||||
export function fetchCreateUser (data: Api.Client.UserOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/client/user',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改客户用户基础信息 */
|
||||
export function fetchUpdateUser (data: Api.Client.UserOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/client/user',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除客户用户基础信息 */
|
||||
export function fetchBatchDeleteUser (ids: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/client/user/${ids.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** history score control line */
|
||||
type HistoryScoreControlLine = Common.CommonRecord<{
|
||||
/** 省控线主键 */
|
||||
controlId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 删除标志(0存在 1删除) */
|
||||
delFlag: string;
|
||||
/** 省份行政区划代码 */
|
||||
provinceCode: string;
|
||||
/** 省份名称 */
|
||||
provinceName: string;
|
||||
/** 年份 */
|
||||
year: number;
|
||||
/** 专业类别 */
|
||||
majorCategory: string;
|
||||
/** 批次 */
|
||||
batchName: string;
|
||||
/** 科类(文/理) */
|
||||
subjectType: string;
|
||||
/** 文化成绩分数 */
|
||||
cultureScore: number;
|
||||
/** 专业成绩分数 */
|
||||
majorScore: number;
|
||||
/** 文化成绩校考分数 */
|
||||
cultureScoreExam: number;
|
||||
/** 专业成绩校考分数 */
|
||||
majorScoreExam: number;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** history score control line search params */
|
||||
type HistoryScoreControlLineSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.HistoryScoreControlLine,
|
||||
| 'provinceCode'
|
||||
| 'provinceName'
|
||||
| 'year'
|
||||
| 'majorCategory'
|
||||
| 'batchName'
|
||||
| 'subjectType'
|
||||
| 'cultureScore'
|
||||
| 'majorScore'
|
||||
| 'cultureScoreExam'
|
||||
| 'majorScoreExam'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** history score control line operate params */
|
||||
type HistoryScoreControlLineOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.HistoryScoreControlLine,
|
||||
| 'controlId'
|
||||
| 'provinceCode'
|
||||
| 'provinceName'
|
||||
| 'year'
|
||||
| 'majorCategory'
|
||||
| 'batchName'
|
||||
| 'subjectType'
|
||||
| 'cultureScore'
|
||||
| 'majorScore'
|
||||
| 'cultureScoreExam'
|
||||
| 'majorScoreExam'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** history score control line list */
|
||||
type HistoryScoreControlLineList = Api.Common.PaginatingQueryRecord<HistoryScoreControlLine>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** major */
|
||||
type Major = Common.CommonRecord<{
|
||||
/** 专业主键ID */
|
||||
majorId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 删除标志(0存在 1删除) */
|
||||
delFlag: string;
|
||||
/** 专业名称 */
|
||||
majorName: string;
|
||||
/** 学历层次 */
|
||||
educationLevel: string;
|
||||
/** 专业图标 */
|
||||
majorIcon: string;
|
||||
/** 学制(年) */
|
||||
schoolingYears: number;
|
||||
/** 所属一级学科 */
|
||||
disciplinePrimary: string;
|
||||
/** 所属二级学科 */
|
||||
disciplineSecondary: string;
|
||||
/** 授予学士学位 */
|
||||
degreeAwarded: string;
|
||||
/** 专业概括 */
|
||||
summary: string;
|
||||
/** 培养方向 */
|
||||
trainingDirection: string;
|
||||
/** 主要课程 */
|
||||
coreCourses: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** major search params */
|
||||
type MajorSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.Major,
|
||||
| 'majorName'
|
||||
| 'educationLevel'
|
||||
| 'majorIcon'
|
||||
| 'schoolingYears'
|
||||
| 'disciplinePrimary'
|
||||
| 'disciplineSecondary'
|
||||
| 'degreeAwarded'
|
||||
| 'summary'
|
||||
| 'trainingDirection'
|
||||
| 'coreCourses'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** major operate params */
|
||||
type MajorOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.Major,
|
||||
| 'majorId'
|
||||
| 'majorName'
|
||||
| 'educationLevel'
|
||||
| 'majorIcon'
|
||||
| 'schoolingYears'
|
||||
| 'disciplinePrimary'
|
||||
| 'disciplineSecondary'
|
||||
| 'degreeAwarded'
|
||||
| 'summary'
|
||||
| 'trainingDirection'
|
||||
| 'coreCourses'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** major list */
|
||||
type MajorList = Api.Common.PaginatingQueryRecord<Major>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school campus */
|
||||
type SchoolCampus = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
campusId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 学校ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 校区名称 */
|
||||
campusName: string;
|
||||
/** 校区位置(文本) */
|
||||
location: string;
|
||||
/** 校区地址 */
|
||||
address: string;
|
||||
/** 经度 */
|
||||
lng: number;
|
||||
/** 纬度 */
|
||||
lat: number;
|
||||
/** 校区介绍 */
|
||||
introduction: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school campus search params */
|
||||
type SchoolCampusSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolCampus, 'schoolId' | 'campusName' | 'location' | 'address' | 'lng' | 'lat' | 'introduction'> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school campus operate params */
|
||||
type SchoolCampusOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolCampus,
|
||||
'campusId' | 'schoolId' | 'campusName' | 'location' | 'address' | 'lng' | 'lat' | 'introduction' | 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school campus list */
|
||||
type SchoolCampusList = Api.Common.PaginatingQueryRecord<SchoolCampus>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school college */
|
||||
type SchoolCollege = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
collegeId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 学校ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学院编码(可选) */
|
||||
collegeCode: string;
|
||||
/** 学院名称 */
|
||||
collegeName: string;
|
||||
/** 学院介绍 */
|
||||
introduction: string;
|
||||
/** 排序 */
|
||||
sortNo: number;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school college search params */
|
||||
type SchoolCollegeSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolCollege, 'schoolId' | 'collegeCode' | 'collegeName' | 'introduction' | 'sortNo'> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school college operate params */
|
||||
type SchoolCollegeOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolCollege,
|
||||
'collegeId' | 'schoolId' | 'collegeCode' | 'collegeName' | 'introduction' | 'sortNo' | 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school college list */
|
||||
type SchoolCollegeList = Api.Common.PaginatingQueryRecord<SchoolCollege>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school detail */
|
||||
type SchoolDetail = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
detailId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 关联学校主表ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学校详细介绍(大文本) */
|
||||
introduction: string;
|
||||
/** 院校图标 */
|
||||
schoolIcon: string;
|
||||
/** 学校背景图 */
|
||||
backGround: string;
|
||||
/** 学校地址 */
|
||||
address: string;
|
||||
/** 联系电话 */
|
||||
contact: string;
|
||||
/** 邮箱 */
|
||||
email: string;
|
||||
/** 官网地址 */
|
||||
website: string;
|
||||
/** 邮编 */
|
||||
postcode: string;
|
||||
/** 建校时间(年) */
|
||||
establishYear: number;
|
||||
/** 占地面积(亩) */
|
||||
campusAreaMu: number;
|
||||
/** 图书馆藏书量 */
|
||||
libraryCollection: number;
|
||||
/** 男生比例(%) */
|
||||
maleRatio: number;
|
||||
/** 女生比例(%) */
|
||||
femaleRatio: number;
|
||||
/** 是否985(0/1) */
|
||||
is985: number;
|
||||
/** 是否211(0/1) */
|
||||
is211: number;
|
||||
/** 是否双一流(0/1) */
|
||||
isDoubleFirstClass: number;
|
||||
/** 是否重点大学(0/1) */
|
||||
isKeyUniversity: number;
|
||||
/** 是否公办(0/1) */
|
||||
isPublic: number;
|
||||
/** 标签 */
|
||||
tags: string[];
|
||||
/** 学生人数 */
|
||||
studentCount: number;
|
||||
/** 教师人数 */
|
||||
teacherCount: number;
|
||||
/** 硕士点数量 */
|
||||
masterPoint: number;
|
||||
/** 博士点数量 */
|
||||
doctorPoint: number;
|
||||
/** 重点专业数量 */
|
||||
keyMajorCount: number;
|
||||
/** 就业率(%) */
|
||||
employmentRate: number;
|
||||
/** 满意度(%) */
|
||||
satisfactionRate: number;
|
||||
/** 外部学校ID */
|
||||
univId: number;
|
||||
/** 考研率(%) */
|
||||
masterProportionRate: number;
|
||||
/** 出国率(%) */
|
||||
abroadProportionRate: number;
|
||||
/** 是否有普通本科(0/1) */
|
||||
hasRegular: number;
|
||||
/** 是否有专科(0/1) */
|
||||
hasJunior: number;
|
||||
/** 是否有硕士点(0/1) */
|
||||
hasMaster: number;
|
||||
/** 是否双高计划(0/1) */
|
||||
isDoubleHighPlan: number;
|
||||
/** 是否强基计划(0/1) */
|
||||
isStrongPlan: number;
|
||||
/** 泰晤士中国排名 */
|
||||
twsdlRank: number;
|
||||
/** 校友会排名 */
|
||||
xyhRank: number;
|
||||
/** 武书连排名 */
|
||||
wslRank: number;
|
||||
/** US中国排名 */
|
||||
usdaluRank: number;
|
||||
/** QS排名 */
|
||||
qsdaluRank: number;
|
||||
/** 综合评分 */
|
||||
combinedScore: number;
|
||||
/** 综合排名 */
|
||||
overallRank: number;
|
||||
/** 环境满意度 */
|
||||
envSatisfaction: number;
|
||||
/** 环境满意度投票数 */
|
||||
envVote: number;
|
||||
/** 生活满意度 */
|
||||
liveSatisfaction: number;
|
||||
/** 生活满意度投票数 */
|
||||
liveVote: number;
|
||||
/** 综合满意度(原始分) */
|
||||
combinedSatisfaction: number;
|
||||
/** 综合满意度投票数 */
|
||||
combinedVote: number;
|
||||
/** 师资力量描述 */
|
||||
teachers: string;
|
||||
/** 奖学金说明 */
|
||||
scholarship: string;
|
||||
/** 助学金说明 */
|
||||
grantDesc: string;
|
||||
/** 食堂说明 */
|
||||
canteen: string;
|
||||
/** 宿舍说明 */
|
||||
dormitory: string;
|
||||
/** 硕士点说明 */
|
||||
masterExplain: string;
|
||||
/** 博士点说明 */
|
||||
doctorExplain: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school detail search params */
|
||||
type SchoolDetailSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolDetail,
|
||||
| 'detailId'
|
||||
| 'schoolId'
|
||||
| 'introduction'
|
||||
| 'schoolIcon'
|
||||
| 'backGround'
|
||||
| 'address'
|
||||
| 'contact'
|
||||
| 'email'
|
||||
| 'website'
|
||||
| 'postcode'
|
||||
| 'establishYear'
|
||||
| 'campusAreaMu'
|
||||
| 'libraryCollection'
|
||||
| 'maleRatio'
|
||||
| 'femaleRatio'
|
||||
| 'is985'
|
||||
| 'is211'
|
||||
| 'isDoubleFirstClass'
|
||||
| 'isKeyUniversity'
|
||||
| 'isPublic'
|
||||
| 'tags'
|
||||
| 'studentCount'
|
||||
| 'teacherCount'
|
||||
| 'masterPoint'
|
||||
| 'doctorPoint'
|
||||
| 'keyMajorCount'
|
||||
| 'employmentRate'
|
||||
| 'satisfactionRate'
|
||||
| 'univId'
|
||||
| 'masterProportionRate'
|
||||
| 'abroadProportionRate'
|
||||
| 'hasRegular'
|
||||
| 'hasJunior'
|
||||
| 'hasMaster'
|
||||
| 'isDoubleHighPlan'
|
||||
| 'isStrongPlan'
|
||||
| 'twsdlRank'
|
||||
| 'xyhRank'
|
||||
| 'wslRank'
|
||||
| 'usdaluRank'
|
||||
| 'qsdaluRank'
|
||||
| 'combinedScore'
|
||||
| 'overallRank'
|
||||
| 'envSatisfaction'
|
||||
| 'envVote'
|
||||
| 'liveSatisfaction'
|
||||
| 'liveVote'
|
||||
| 'combinedSatisfaction'
|
||||
| 'combinedVote'
|
||||
| 'teachers'
|
||||
| 'scholarship'
|
||||
| 'grantDesc'
|
||||
| 'canteen'
|
||||
| 'dormitory'
|
||||
| 'masterExplain'
|
||||
| 'doctorExplain'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school detail operate params */
|
||||
type SchoolDetailOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolDetail,
|
||||
| 'detailId'
|
||||
| 'schoolId'
|
||||
| 'introduction'
|
||||
| 'schoolIcon'
|
||||
| 'backGround'
|
||||
| 'address'
|
||||
| 'contact'
|
||||
| 'email'
|
||||
| 'website'
|
||||
| 'postcode'
|
||||
| 'establishYear'
|
||||
| 'campusAreaMu'
|
||||
| 'libraryCollection'
|
||||
| 'maleRatio'
|
||||
| 'femaleRatio'
|
||||
| 'is985'
|
||||
| 'is211'
|
||||
| 'isDoubleFirstClass'
|
||||
| 'isKeyUniversity'
|
||||
| 'isPublic'
|
||||
| 'tags'
|
||||
| 'studentCount'
|
||||
| 'teacherCount'
|
||||
| 'masterPoint'
|
||||
| 'doctorPoint'
|
||||
| 'keyMajorCount'
|
||||
| 'employmentRate'
|
||||
| 'satisfactionRate'
|
||||
| 'univId'
|
||||
| 'masterProportionRate'
|
||||
| 'abroadProportionRate'
|
||||
| 'hasRegular'
|
||||
| 'hasJunior'
|
||||
| 'hasMaster'
|
||||
| 'isDoubleHighPlan'
|
||||
| 'isStrongPlan'
|
||||
| 'twsdlRank'
|
||||
| 'xyhRank'
|
||||
| 'wslRank'
|
||||
| 'usdaluRank'
|
||||
| 'qsdaluRank'
|
||||
| 'combinedScore'
|
||||
| 'overallRank'
|
||||
| 'envSatisfaction'
|
||||
| 'envVote'
|
||||
| 'liveSatisfaction'
|
||||
| 'liveVote'
|
||||
| 'combinedSatisfaction'
|
||||
| 'combinedVote'
|
||||
| 'teachers'
|
||||
| 'scholarship'
|
||||
| 'grantDesc'
|
||||
| 'canteen'
|
||||
| 'dormitory'
|
||||
| 'masterExplain'
|
||||
| 'doctorExplain'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school detail list */
|
||||
type SchoolDetailList = Api.Common.PaginatingQueryRecord<SchoolDetail>;
|
||||
|
||||
/** school detail json payloads */
|
||||
type SchoolDetailSatisfactionPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
satisfactionJson: Record<string, unknown>;
|
||||
};
|
||||
type SchoolDetailScholarshipPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
scholarshipJson: unknown[];
|
||||
};
|
||||
type SchoolDetailSpecialMajorPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
specialMajorJson: unknown[];
|
||||
};
|
||||
type SchoolDetailEmploymentReportPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
employmentReportJson: unknown[];
|
||||
};
|
||||
type SchoolDetailPhotoPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
photoJson: Record<string, unknown>;
|
||||
};
|
||||
type SchoolDetailAccommodationPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
accommodationJson: unknown[];
|
||||
};
|
||||
type SchoolDetailSubjectReviewsPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
subjectReviewsJson: unknown[];
|
||||
};
|
||||
type SchoolDetailResearchPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
researchJson: Record<string, unknown>;
|
||||
};
|
||||
type SchoolDetailUnivMajorsPayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
univMajorsJson: unknown[];
|
||||
};
|
||||
type SchoolDetailUnivPostgraduatePayload = {
|
||||
schoolId: CommonType.IdType;
|
||||
univPostgraduateJson: unknown[];
|
||||
};
|
||||
|
||||
/** school detail json all payload */
|
||||
type SchoolDetailJsonAllPayload = {
|
||||
satisfactionJson: Record<string, unknown>;
|
||||
scholarshipJson: unknown[];
|
||||
specialMajorJson: unknown[];
|
||||
employmentReportJson: unknown[];
|
||||
photoJson: Record<string, unknown>;
|
||||
accommodationJson: unknown[];
|
||||
subjectReviewsJson: unknown[];
|
||||
researchJson: Record<string, unknown>;
|
||||
univMajorsJson: unknown[];
|
||||
univPostgraduateJson: unknown[];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school dorm */
|
||||
type SchoolDorm = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
dormId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 校区ID */
|
||||
campusId: CommonType.IdType;
|
||||
/** 几人间(4/6/8...) */
|
||||
roomSize: number;
|
||||
/** 是否上床下桌(0否1是) */
|
||||
bunkBedDesk: number;
|
||||
/** 是否独立卫浴(0否1是) */
|
||||
privateBath: number;
|
||||
/** 宿舍标签(冗余文本:空调/热水/洗衣房...) */
|
||||
tags: string;
|
||||
/** 补充说明 */
|
||||
description: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school dorm search params */
|
||||
type SchoolDormSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolDorm, 'campusId' | 'roomSize' | 'bunkBedDesk' | 'privateBath' | 'tags' | 'description'> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school dorm operate params */
|
||||
type SchoolDormOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolDorm,
|
||||
'dormId' | 'campusId' | 'roomSize' | 'bunkBedDesk' | 'privateBath' | 'tags' | 'description' | 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school dorm list */
|
||||
type SchoolDormList = Api.Common.PaginatingQueryRecord<SchoolDorm>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school enroll plan */
|
||||
type SchoolEnrollPlan = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
planId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 学校ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 年份 */
|
||||
year: number;
|
||||
/** 招生省份 */
|
||||
province: string;
|
||||
/** 分科:文/理/综(或物理/历史...) */
|
||||
subjectType: string;
|
||||
/** 专业ID(可选,有则填) */
|
||||
majorId: CommonType.IdType;
|
||||
/** 专业名称(冗余,没专业ID也能落库) */
|
||||
majorName: string;
|
||||
/** 学历层次:本科/专科 */
|
||||
educationLevel: string;
|
||||
/** 计划数 */
|
||||
planNum: number;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school enroll plan search params */
|
||||
type SchoolEnrollPlanSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolEnrollPlan,
|
||||
'schoolId' | 'year' | 'province' | 'subjectType' | 'majorId' | 'majorName' | 'educationLevel' | 'planNum'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school enroll plan operate params */
|
||||
type SchoolEnrollPlanOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolEnrollPlan,
|
||||
| 'planId'
|
||||
| 'schoolId'
|
||||
| 'year'
|
||||
| 'province'
|
||||
| 'subjectType'
|
||||
| 'majorId'
|
||||
| 'majorName'
|
||||
| 'educationLevel'
|
||||
| 'planNum'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school enroll plan list */
|
||||
type SchoolEnrollPlanList = Api.Common.PaginatingQueryRecord<SchoolEnrollPlan>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school major tag */
|
||||
type SchoolMajorTag = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
majorTagId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 专业ID */
|
||||
majorId: CommonType.IdType;
|
||||
/** 标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...) */
|
||||
tagName: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school major tag search params */
|
||||
type SchoolMajorTagSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolMajorTag, 'majorId' | 'tagName'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school major tag operate params */
|
||||
type SchoolMajorTagOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolMajorTag, 'majorTagId' | 'majorId' | 'tagName' | 'remark'>
|
||||
>;
|
||||
|
||||
/** school major tag list */
|
||||
type SchoolMajorTagList = Api.Common.PaginatingQueryRecord<SchoolMajorTag>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school major */
|
||||
type SchoolMajor = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
majorId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 学校ID(冗余便于查) */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学院ID */
|
||||
collegeId: CommonType.IdType;
|
||||
/** 专业编码(可选) */
|
||||
majorCode: string;
|
||||
/** 专业名称 */
|
||||
majorName: string;
|
||||
/** 学历层次:本科/专科 */
|
||||
educationLevel: string;
|
||||
/** 学制(3/4/5) */
|
||||
durationYears: number;
|
||||
/** 专业类别:工学/理学/艺术学... */
|
||||
majorCategory: string;
|
||||
/** 学位类型:工学学士/理学学士/艺术学学士... */
|
||||
degreeType: string;
|
||||
/** 专业介绍 */
|
||||
introduction: string;
|
||||
/** 专业标签列表(推荐字段) */
|
||||
majorTags: string[];
|
||||
/** 专业标签文本(兼容旧字段) */
|
||||
tags: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school major search params */
|
||||
type SchoolMajorSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolMajor,
|
||||
| 'schoolId'
|
||||
| 'collegeId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'educationLevel'
|
||||
| 'durationYears'
|
||||
| 'majorCategory'
|
||||
| 'degreeType'
|
||||
| 'introduction'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school major operate params */
|
||||
type SchoolMajorOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolMajor,
|
||||
| 'majorId'
|
||||
| 'schoolId'
|
||||
| 'collegeId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'educationLevel'
|
||||
| 'durationYears'
|
||||
| 'majorCategory'
|
||||
| 'degreeType'
|
||||
| 'introduction'
|
||||
| 'majorTags'
|
||||
| 'tags'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school major list */
|
||||
type SchoolMajorList = Api.Common.PaginatingQueryRecord<SchoolMajor>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school media */
|
||||
type SchoolMedia = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
mediaId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 业务类型:school/campus/college/major/dorm */
|
||||
bizType: string;
|
||||
/** 业务主键ID */
|
||||
bizId: CommonType.IdType;
|
||||
/** 媒体类型:1-图片 2-视频 */
|
||||
mediaType: number;
|
||||
/** 资源URL */
|
||||
url: string;
|
||||
/** 封面URL(视频可用) */
|
||||
coverUrl: string;
|
||||
/** 排序 */
|
||||
sortNo: number;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school media search params */
|
||||
type SchoolMediaSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolMedia, 'bizType' | 'bizId' | 'mediaType' | 'url' | 'coverUrl' | 'sortNo'> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school media operate params */
|
||||
type SchoolMediaOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolMedia,
|
||||
'mediaId' | 'bizType' | 'bizId' | 'mediaType' | 'url' | 'coverUrl' | 'sortNo' | 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school media list */
|
||||
type SchoolMediaList = Api.Common.PaginatingQueryRecord<SchoolMedia>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school name */
|
||||
type SchoolName = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
schoolNameId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 关联学校主表ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学校名称(曾用名/别名) */
|
||||
name: string;
|
||||
/** 名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称 */
|
||||
nameType: number;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school name search params */
|
||||
type SchoolNameSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolName, 'schoolId' | 'name' | 'nameType'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school name operate params */
|
||||
type SchoolNameOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolName, 'schoolNameId' | 'schoolId' | 'name' | 'nameType' | 'remark'>
|
||||
>;
|
||||
|
||||
/** school name list */
|
||||
type SchoolNameList = Api.Common.PaginatingQueryRecord<SchoolName>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school recruit major history */
|
||||
type SchoolRecruitMajorHistory = Common.CommonRecord<{
|
||||
/** 历年录取数据ID */
|
||||
historyId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 删除标志(0存在 1删除) */
|
||||
delFlag: string;
|
||||
/** 对应招录专业ID */
|
||||
recruitMajorId: CommonType.IdType;
|
||||
/** 学校ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学校代码 */
|
||||
schoolCode: string;
|
||||
/** 院校代码 */
|
||||
collegeCode: string;
|
||||
/** 学校名称 */
|
||||
schoolName: string;
|
||||
/** 专业ID */
|
||||
majorId: CommonType.IdType;
|
||||
/** 专业代码 */
|
||||
majorCode: string;
|
||||
/** 专业名称 */
|
||||
majorName: string;
|
||||
/** 招生代码 */
|
||||
enrollCode: string;
|
||||
/** 专业类型 */
|
||||
majorType: string;
|
||||
/** 专业类别子级 */
|
||||
majorTypeSub: string;
|
||||
/** 主考科目 */
|
||||
mainExamSubject: string;
|
||||
/** 年份 */
|
||||
year: number;
|
||||
/** 科类(文/理) */
|
||||
subjectType: string;
|
||||
/** 批次 */
|
||||
batchName: string;
|
||||
/** 录取方式(文*x+专*y) */
|
||||
admissionFormula: string;
|
||||
/** 录取概率规则运算符 */
|
||||
probabilityOperator: string;
|
||||
/** 省控线 */
|
||||
controlScore: number;
|
||||
/** 录取线 */
|
||||
admissionScore: number;
|
||||
/** 招生人数 */
|
||||
planEnroll: number;
|
||||
/** 实际投档人数 */
|
||||
filedAmount: number;
|
||||
/** 录取数 */
|
||||
admitAmount: number;
|
||||
/** 一志愿录取数 */
|
||||
firstChoiceAdmitAmount: number;
|
||||
/** 最低分数差 */
|
||||
minScoreDiff: number;
|
||||
/** 学费(元/年) */
|
||||
tuitionFee: number;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school recruit major history search params */
|
||||
type SchoolRecruitMajorHistorySearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolRecruitMajorHistory,
|
||||
| 'recruitMajorId'
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'collegeCode'
|
||||
| 'schoolName'
|
||||
| 'majorId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'enrollCode'
|
||||
| 'majorType'
|
||||
| 'majorTypeSub'
|
||||
| 'mainExamSubject'
|
||||
| 'year'
|
||||
| 'subjectType'
|
||||
| 'batchName'
|
||||
| 'admissionFormula'
|
||||
| 'probabilityOperator'
|
||||
| 'controlScore'
|
||||
| 'admissionScore'
|
||||
| 'planEnroll'
|
||||
| 'filedAmount'
|
||||
| 'admitAmount'
|
||||
| 'firstChoiceAdmitAmount'
|
||||
| 'minScoreDiff'
|
||||
| 'tuitionFee'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school recruit major history operate params */
|
||||
type SchoolRecruitMajorHistoryOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolRecruitMajorHistory,
|
||||
| 'historyId'
|
||||
| 'recruitMajorId'
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'collegeCode'
|
||||
| 'schoolName'
|
||||
| 'majorId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'enrollCode'
|
||||
| 'majorType'
|
||||
| 'majorTypeSub'
|
||||
| 'mainExamSubject'
|
||||
| 'year'
|
||||
| 'subjectType'
|
||||
| 'batchName'
|
||||
| 'admissionFormula'
|
||||
| 'probabilityOperator'
|
||||
| 'controlScore'
|
||||
| 'admissionScore'
|
||||
| 'planEnroll'
|
||||
| 'filedAmount'
|
||||
| 'admitAmount'
|
||||
| 'firstChoiceAdmitAmount'
|
||||
| 'minScoreDiff'
|
||||
| 'tuitionFee'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school recruit major history list */
|
||||
type SchoolRecruitMajorHistoryList = Api.Common.PaginatingQueryRecord<SchoolRecruitMajorHistory>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school recruit major */
|
||||
type SchoolRecruitMajor = Common.CommonRecord<{
|
||||
/** 院校招录专业ID */
|
||||
recruitMajorId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 删除标志(0存在 1删除) */
|
||||
delFlag: string;
|
||||
/** 学校ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 学校代码 */
|
||||
schoolCode: string;
|
||||
/** 学校名称(冗余) */
|
||||
schoolName: string;
|
||||
/** 年份 */
|
||||
year: number;
|
||||
/** 专业ID */
|
||||
majorId: CommonType.IdType;
|
||||
/** 专业代码 */
|
||||
majorCode: string;
|
||||
/** 专业名称 */
|
||||
majorName: string;
|
||||
/** 招生代码(为空则存空串) */
|
||||
enrollCode: string;
|
||||
/** 数据状态(停招/新招/新增) */
|
||||
dataStatus: string;
|
||||
/** 批次 */
|
||||
batchName: string;
|
||||
/** 专业类型 */
|
||||
majorType: string;
|
||||
/** 二级专业类型 */
|
||||
majorTypeSub: string;
|
||||
/** 科类(文/理) */
|
||||
subjectType: string;
|
||||
/** 录取方式缩写 */
|
||||
admissionWayShort: string;
|
||||
/** 对外录取方式 */
|
||||
admissionWayExternal: string;
|
||||
/** 对外录取方式运算符 */
|
||||
admissionWayExternalOp: string;
|
||||
/** 内部录取方式 */
|
||||
admissionWayInternal: string;
|
||||
/** 内部录取方式运算符 */
|
||||
admissionWayInternalOp: string;
|
||||
/** 计划招生人数 */
|
||||
planEnroll: number;
|
||||
/** 主考科目 */
|
||||
mainExamSubject: string;
|
||||
/** 学制(年) */
|
||||
schoolingYears: number;
|
||||
/** 院校限制说明 */
|
||||
enrollLimitDesc: string;
|
||||
/** 学费(元/年) */
|
||||
tuitionFee: number;
|
||||
/** 文化分数限制 */
|
||||
cultureScoreLimit: number;
|
||||
/** 专业分数限制 */
|
||||
majorScoreLimit: number;
|
||||
/** 语文成绩限制 */
|
||||
chineseScoreLimit: number;
|
||||
/** 英语成绩限制 */
|
||||
englishScoreLimit: number;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school recruit major search params */
|
||||
type SchoolRecruitMajorSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolRecruitMajor,
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'schoolName'
|
||||
| 'year'
|
||||
| 'majorId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'enrollCode'
|
||||
| 'dataStatus'
|
||||
| 'batchName'
|
||||
| 'majorType'
|
||||
| 'majorTypeSub'
|
||||
| 'subjectType'
|
||||
| 'admissionWayShort'
|
||||
| 'admissionWayExternal'
|
||||
| 'admissionWayExternalOp'
|
||||
| 'admissionWayInternal'
|
||||
| 'admissionWayInternalOp'
|
||||
| 'planEnroll'
|
||||
| 'mainExamSubject'
|
||||
| 'schoolingYears'
|
||||
| 'enrollLimitDesc'
|
||||
| 'tuitionFee'
|
||||
| 'cultureScoreLimit'
|
||||
| 'majorScoreLimit'
|
||||
| 'chineseScoreLimit'
|
||||
| 'englishScoreLimit'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school recruit major operate params */
|
||||
type SchoolRecruitMajorOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolRecruitMajor,
|
||||
| 'recruitMajorId'
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'schoolName'
|
||||
| 'year'
|
||||
| 'majorId'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'enrollCode'
|
||||
| 'dataStatus'
|
||||
| 'batchName'
|
||||
| 'majorType'
|
||||
| 'majorTypeSub'
|
||||
| 'subjectType'
|
||||
| 'admissionWayShort'
|
||||
| 'admissionWayExternal'
|
||||
| 'admissionWayExternalOp'
|
||||
| 'admissionWayInternal'
|
||||
| 'admissionWayInternalOp'
|
||||
| 'planEnroll'
|
||||
| 'mainExamSubject'
|
||||
| 'schoolingYears'
|
||||
| 'enrollLimitDesc'
|
||||
| 'tuitionFee'
|
||||
| 'cultureScoreLimit'
|
||||
| 'majorScoreLimit'
|
||||
| 'chineseScoreLimit'
|
||||
| 'englishScoreLimit'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school recruit major list */
|
||||
type SchoolRecruitMajorList = Api.Common.PaginatingQueryRecord<SchoolRecruitMajor>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school tag */
|
||||
type SchoolTag = Common.CommonRecord<{
|
||||
/** 主键ID */
|
||||
schoolTagId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 关联学校主表ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 标签名称(如:985、211、双一流、艺术类院校) */
|
||||
tagName: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school tag search params */
|
||||
type SchoolTagSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolTag, 'schoolId' | 'tagName'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school tag operate params */
|
||||
type SchoolTagOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Art.SchoolTag, 'schoolTagId' | 'schoolId' | 'tagName' | 'remark'>
|
||||
>;
|
||||
|
||||
/** school tag list */
|
||||
type SchoolTagList = Api.Common.PaginatingQueryRecord<SchoolTag>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Art
|
||||
*
|
||||
* backend api module: "Art"
|
||||
*/
|
||||
namespace Art {
|
||||
/** school */
|
||||
type School = Common.CommonRecord<{
|
||||
/** 学校主键ID */
|
||||
schoolId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 学校编码(唯一标识,如国标代码) */
|
||||
mainCode: string;
|
||||
/** 学校主名称(官方全称) */
|
||||
mainName: string;
|
||||
/** 学校简称(备用) */
|
||||
shortName: string;
|
||||
/** 院校招生代码集合 */
|
||||
enrollCodes: string[];
|
||||
/** 学校标签列表 */
|
||||
schoolTags: string[];
|
||||
/** 省份 */
|
||||
province: string;
|
||||
/** 城市 */
|
||||
city: string;
|
||||
/** 区县 */
|
||||
district: string;
|
||||
/** 大学类型:综合/工科/财经/艺术... */
|
||||
universityType: string;
|
||||
/** 学历层次:本科/专科 */
|
||||
educationLevel: string;
|
||||
/** 办学性质:公办/民办/中外合作 */
|
||||
schoolNature: string;
|
||||
/** 主管部门:教育部/工信部/民委... */
|
||||
supervisorDept: string;
|
||||
/** 删除标志(0代表存在 1代表删除) */
|
||||
delFlag: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}>;
|
||||
|
||||
/** school search params */
|
||||
type SchoolSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.School,
|
||||
| 'schoolId'
|
||||
| 'mainCode'
|
||||
| 'mainName'
|
||||
| 'shortName'
|
||||
| 'province'
|
||||
| 'city'
|
||||
| 'district'
|
||||
| 'universityType'
|
||||
| 'educationLevel'
|
||||
| 'schoolNature'
|
||||
| 'supervisorDept'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** school operate params */
|
||||
type SchoolOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.School,
|
||||
| 'schoolId'
|
||||
| 'mainCode'
|
||||
| 'mainName'
|
||||
| 'shortName'
|
||||
| 'province'
|
||||
| 'city'
|
||||
| 'district'
|
||||
| 'universityType'
|
||||
| 'educationLevel'
|
||||
| 'schoolNature'
|
||||
| 'supervisorDept'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school detail operate params in school form */
|
||||
type SchoolDetailInSchoolOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Art.SchoolDetail,
|
||||
| 'detailId'
|
||||
| 'schoolId'
|
||||
| 'introduction'
|
||||
| 'schoolIcon'
|
||||
| 'backGround'
|
||||
| 'address'
|
||||
| 'contact'
|
||||
| 'email'
|
||||
| 'website'
|
||||
| 'postcode'
|
||||
| 'establishYear'
|
||||
| 'campusAreaMu'
|
||||
| 'libraryCollection'
|
||||
| 'maleRatio'
|
||||
| 'femaleRatio'
|
||||
| 'is985'
|
||||
| 'is211'
|
||||
| 'isDoubleFirstClass'
|
||||
| 'isKeyUniversity'
|
||||
| 'isPublic'
|
||||
| 'tags'
|
||||
| 'studentCount'
|
||||
| 'teacherCount'
|
||||
| 'masterPoint'
|
||||
| 'doctorPoint'
|
||||
| 'keyMajorCount'
|
||||
| 'employmentRate'
|
||||
| 'satisfactionRate'
|
||||
| 'univId'
|
||||
| 'masterProportionRate'
|
||||
| 'abroadProportionRate'
|
||||
| 'hasRegular'
|
||||
| 'hasJunior'
|
||||
| 'hasMaster'
|
||||
| 'isDoubleHighPlan'
|
||||
| 'isStrongPlan'
|
||||
| 'twsdlRank'
|
||||
| 'xyhRank'
|
||||
| 'wslRank'
|
||||
| 'usdaluRank'
|
||||
| 'qsdaluRank'
|
||||
| 'combinedScore'
|
||||
| 'overallRank'
|
||||
| 'envSatisfaction'
|
||||
| 'envVote'
|
||||
| 'liveSatisfaction'
|
||||
| 'liveVote'
|
||||
| 'combinedSatisfaction'
|
||||
| 'combinedVote'
|
||||
| 'teachers'
|
||||
| 'scholarship'
|
||||
| 'grantDesc'
|
||||
| 'canteen'
|
||||
| 'dormitory'
|
||||
| 'masterExplain'
|
||||
| 'doctorExplain'
|
||||
| 'remark'
|
||||
>
|
||||
>;
|
||||
|
||||
/** school + detail operate params */
|
||||
type SchoolWithDetailOperateParams = {
|
||||
/** 学校基础信息 */
|
||||
school: SchoolOperateParams;
|
||||
/** 学校详情信息 */
|
||||
detail: SchoolDetailInSchoolOperateParams;
|
||||
/** 院校招生代码集合(与 school、detail 同级) */
|
||||
enrollCodes?: string[] | null;
|
||||
/** 学校标签列表(与 school、detail 同级) */
|
||||
schoolTags?: string[] | null;
|
||||
};
|
||||
|
||||
/** school + detail submit status */
|
||||
type SchoolWithDetailSubmitStatus = CommonType.RecordNullable<{
|
||||
schoolSuccess: boolean;
|
||||
detailSuccess: boolean;
|
||||
schoolStatus: string;
|
||||
detailStatus: string;
|
||||
schoolMessage: string;
|
||||
detailMessage: string;
|
||||
}>;
|
||||
|
||||
/** school import detail */
|
||||
type SchoolImportDetail = CommonType.RecordNullable<{
|
||||
mainCode: string;
|
||||
mainName: string;
|
||||
status: 'CONFLICT' | 'INVALID' | 'SUCCESS' | 'SKIPPED' | 'FAILED';
|
||||
message: string;
|
||||
}>;
|
||||
|
||||
/** school import preview result */
|
||||
type SchoolImportPreviewResult = CommonType.RecordNullable<{
|
||||
totalSchoolCount: number;
|
||||
conflictCount: number;
|
||||
invalidCount: number;
|
||||
details: SchoolImportDetail[];
|
||||
}>;
|
||||
|
||||
/** school import execute result */
|
||||
type SchoolImportExecuteResult = CommonType.RecordNullable<{
|
||||
totalSchoolCount: number;
|
||||
successCount: number;
|
||||
failCount: number;
|
||||
skippedCount: number;
|
||||
details: SchoolImportDetail[];
|
||||
}>;
|
||||
|
||||
/** school import params */
|
||||
type SchoolImportParams = {
|
||||
file: File;
|
||||
replaceAll?: boolean;
|
||||
replaceMainCodes?: string[];
|
||||
};
|
||||
|
||||
/** school list */
|
||||
type SchoolList = Api.Common.PaginatingQueryRecord<School>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Client
|
||||
*
|
||||
* backend api module: "Client"
|
||||
*/
|
||||
namespace Client {
|
||||
/** platform user */
|
||||
type PlatformUser = Common.CommonRecord<{
|
||||
/** 平台用户ID(自增) */
|
||||
id: CommonType.IdType;
|
||||
/** 关联t_user.id */
|
||||
userId: CommonType.IdType;
|
||||
/** 平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序 */
|
||||
platformType: number;
|
||||
/** 平台唯一标识(微信openid/抖音open_id) */
|
||||
platformOpenid: CommonType.IdType;
|
||||
/** 平台统一标识(微信unionid,多小程序互通用) */
|
||||
platformUnionid: CommonType.IdType;
|
||||
/** 平台会话密钥(微信session_key,加密存储) */
|
||||
platformSessionKey: string;
|
||||
/** 平台扩展字段(如抖音的user_name、微信的city等) */
|
||||
platformExtra: string;
|
||||
/** 最后登录时间 */
|
||||
lastLoginTime: string;
|
||||
/** 软删除:0-未删,1-已删 */
|
||||
delFlag: number;
|
||||
/** 租户编码 */
|
||||
tenantId: CommonType.IdType;
|
||||
}>;
|
||||
|
||||
/** platform user search params */
|
||||
type PlatformUserSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Client.PlatformUser,
|
||||
| 'userId'
|
||||
| 'platformType'
|
||||
| 'platformOpenid'
|
||||
| 'platformUnionid'
|
||||
| 'platformSessionKey'
|
||||
| 'platformExtra'
|
||||
| 'lastLoginTime'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** platform user operate params */
|
||||
type PlatformUserOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Client.PlatformUser,
|
||||
| 'id'
|
||||
| 'userId'
|
||||
| 'platformType'
|
||||
| 'platformOpenid'
|
||||
| 'platformUnionid'
|
||||
| 'platformSessionKey'
|
||||
| 'platformExtra'
|
||||
| 'lastLoginTime'
|
||||
>
|
||||
>;
|
||||
|
||||
/** platform user list */
|
||||
type PlatformUserList = Api.Common.PaginatingQueryRecord<PlatformUser>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Client
|
||||
*
|
||||
* backend api module: "Client"
|
||||
*/
|
||||
namespace Client {
|
||||
/** user */
|
||||
type User = Common.CommonRecord<{
|
||||
/** 用户ID */
|
||||
id: CommonType.IdType;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 用户昵称 */
|
||||
nickname: string;
|
||||
/** 用户头像URL */
|
||||
avatarUrl: string;
|
||||
/** 手机号 */
|
||||
phone: string;
|
||||
/** 性别:0-未知,1-男,2-女 */
|
||||
gender: number;
|
||||
/** 状态:0-禁用,1-正常 */
|
||||
status: number;
|
||||
/** 软删除:0-未删,1-已删 */
|
||||
delFlag: number;
|
||||
/** 密码 */
|
||||
password: string;
|
||||
/** Salt 密码盐 */
|
||||
salt: string;
|
||||
/** 租户编码 */
|
||||
tenantId: CommonType.IdType;
|
||||
}>;
|
||||
|
||||
/** user search params */
|
||||
type UserSearchParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Client.User,
|
||||
| 'username'
|
||||
| 'nickname'
|
||||
| 'avatarUrl'
|
||||
| 'phone'
|
||||
| 'gender'
|
||||
| 'status'
|
||||
| 'password'
|
||||
| 'salt'
|
||||
> &
|
||||
Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** user operate params */
|
||||
type UserOperateParams = CommonType.RecordNullable<
|
||||
Pick<
|
||||
Api.Client.User,
|
||||
| 'id'
|
||||
| 'username'
|
||||
| 'nickname'
|
||||
| 'avatarUrl'
|
||||
| 'phone'
|
||||
| 'gender'
|
||||
| 'status'
|
||||
| 'password'
|
||||
| 'salt'
|
||||
>
|
||||
>;
|
||||
|
||||
/** user list */
|
||||
type UserList = Api.Common.PaginatingQueryRecord<User>;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,24 +25,9 @@ declare module "@elegant-router/types" {
|
|||
"404": "/404";
|
||||
"500": "/500";
|
||||
"about": "/about";
|
||||
"art": "/art";
|
||||
"art_history-score-control-line": "/art/history-score-control-line";
|
||||
"art_major": "/art/major";
|
||||
"art_school": "/art/school";
|
||||
"art_school_modules": "/art/school/modules";
|
||||
"art_school_modules_school-campus": "/art/school/modules/school-campus";
|
||||
"art_school_modules_school-college": "/art/school/modules/school-college";
|
||||
"art_school_modules_school-detail": "/art/school/modules/school-detail";
|
||||
"art_school_modules_school-detail-json": "/art/school/modules/school-detail-json";
|
||||
"art_school_modules_school-dorm": "/art/school/modules/school-dorm";
|
||||
"art_school_modules_school-enroll-plan": "/art/school/modules/school-enroll-plan";
|
||||
"art_school_modules_school-major": "/art/school/modules/school-major";
|
||||
"art_school_modules_school-major-tag": "/art/school/modules/school-major-tag";
|
||||
"art_school_modules_school-media": "/art/school/modules/school-media";
|
||||
"art_school_modules_school-name": "/art/school/modules/school-name";
|
||||
"art_school_modules_school-tag": "/art/school/modules/school-tag";
|
||||
"art_school-recruit-major": "/art/school-recruit-major";
|
||||
"art_school-recruit-major-history": "/art/school-recruit-major-history";
|
||||
"client": "/client";
|
||||
"client_platform-user": "/client/platform-user";
|
||||
"client_user": "/client/user";
|
||||
"demo": "/demo";
|
||||
"demo_demo": "/demo/demo";
|
||||
"demo_tree": "/demo/tree";
|
||||
|
|
@ -111,7 +96,7 @@ declare module "@elegant-router/types" {
|
|||
| "404"
|
||||
| "500"
|
||||
| "about"
|
||||
| "art"
|
||||
| "client"
|
||||
| "demo"
|
||||
| "home"
|
||||
| "iframe-page"
|
||||
|
|
@ -146,22 +131,8 @@ declare module "@elegant-router/types" {
|
|||
| "social-callback"
|
||||
| "user-center"
|
||||
| "about"
|
||||
| "art_history-score-control-line"
|
||||
| "art_major"
|
||||
| "art_school-recruit-major-history"
|
||||
| "art_school-recruit-major"
|
||||
| "art_school"
|
||||
| "art_school_modules_school-campus"
|
||||
| "art_school_modules_school-college"
|
||||
| "art_school_modules_school-detail-json"
|
||||
| "art_school_modules_school-detail"
|
||||
| "art_school_modules_school-dorm"
|
||||
| "art_school_modules_school-enroll-plan"
|
||||
| "art_school_modules_school-major-tag"
|
||||
| "art_school_modules_school-major"
|
||||
| "art_school_modules_school-media"
|
||||
| "art_school_modules_school-name"
|
||||
| "art_school_modules_school-tag"
|
||||
| "client_platform-user"
|
||||
| "client_user"
|
||||
| "demo_demo"
|
||||
| "demo_tree"
|
||||
| "home"
|
||||
|
|
|
|||
|
|
@ -1,478 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider, NInput, NSpace } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import {
|
||||
fetchBatchDeleteHistoryScoreControlLine,
|
||||
fetchCreateHistoryScoreControlLine,
|
||||
fetchGetHistoryScoreControlLineList,
|
||||
fetchUpdateHistoryScoreControlLine
|
||||
} from '@/service/api/art/history-score-control-line';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import InlineExpandTextarea from '@/components/custom/inline-expand-textarea.vue';
|
||||
import HistoryScoreControlLineSearch from './modules/history-score-control-line-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'HistoryScoreControlLineList'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.HistoryScoreControlLineSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
provinceCode: null,
|
||||
provinceName: null,
|
||||
year: null,
|
||||
majorCategory: null,
|
||||
batchName: null,
|
||||
subjectType: null,
|
||||
cultureScore: null,
|
||||
majorScore: null,
|
||||
cultureScoreExam: null,
|
||||
majorScoreExam: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
type TableRow = Api.Art.HistoryScoreControlLine & { tempKey?: string };
|
||||
type Model = Api.Art.HistoryScoreControlLineOperateParams;
|
||||
type EditableField = Extract<keyof Model, keyof TableRow>;
|
||||
|
||||
const editingModel = ref<Model>(createDefaultModel());
|
||||
const editingSnapshot = ref<TableRow | null>(null);
|
||||
const editingMode = ref<NaiveUI.TableOperateType | null>(null);
|
||||
const editingRowKey = ref<string | null>(null);
|
||||
const savingRowKey = ref<string | null>(null);
|
||||
const tempRow = ref<TableRow | null>(null);
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
const editableColumns: Array<{ key: EditableField; title: string; textarea?: boolean }> = [
|
||||
{ key: 'provinceCode', title: '省份行政区划代码' },
|
||||
{ key: 'provinceName', title: '省份名称' },
|
||||
{ key: 'year', title: '年份' },
|
||||
{ key: 'majorCategory', title: '专业类别' },
|
||||
{ key: 'batchName', title: '批次' },
|
||||
{ key: 'subjectType', title: '科类(文/理)' },
|
||||
{ key: 'cultureScore', title: '文化成绩分数' },
|
||||
{ key: 'majorScore', title: '专业成绩分数' },
|
||||
{ key: 'cultureScoreExam', title: '文化成绩校考分数' },
|
||||
{ key: 'majorScoreExam', title: '专业成绩校考分数' },
|
||||
{ key: 'remark', title: '备注', textarea: true }
|
||||
];
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetHistoryScoreControlLineList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => createColumns()
|
||||
});
|
||||
|
||||
const tableData = computed<TableRow[]>(() => {
|
||||
const rows = data.value as TableRow[];
|
||||
return tempRow.value ? [tempRow.value, ...rows] : rows;
|
||||
});
|
||||
|
||||
function createColumns(): NaiveUI.TableColumn<TableRow>[] {
|
||||
return [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'controlId',
|
||||
title: '省控线主键',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
...editableColumns.map(column => createEditableColumn(column)),
|
||||
createOperateColumn()
|
||||
];
|
||||
}
|
||||
|
||||
function createEditableColumn(column: { key: EditableField; title: string; textarea?: boolean }) {
|
||||
return {
|
||||
key: column.key,
|
||||
title: column.title,
|
||||
align: 'center',
|
||||
minWidth: 140,
|
||||
render: (row: TableRow) => renderEditableCell(row, column.key, column)
|
||||
} satisfies NaiveUI.TableColumn<TableRow>;
|
||||
}
|
||||
|
||||
function createOperateColumn(): NaiveUI.TableColumn<TableRow> {
|
||||
return {
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
render: (row: TableRow) => {
|
||||
const rowKey = resolveRowKey(row);
|
||||
const editing = editingRowKey.value === rowKey;
|
||||
const saving = savingRowKey.value === rowKey;
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<NSpace size={8} justify="center">
|
||||
<NButton size="tiny" quaternary disabled={saving} onClick={handleCancelEdit}>
|
||||
{$t('common.cancel')}
|
||||
</NButton>
|
||||
<NButton size="tiny" type="primary" loading={saving} onClick={handleSaveRow}>
|
||||
{$t('common.save')}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
);
|
||||
}
|
||||
|
||||
const showEdit = hasAuth('art:historyScoreControlLine:edit');
|
||||
const showDelete = hasAuth('art:historyScoreControlLine:remove');
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{showEdit ? (
|
||||
<NButton size="tiny" text type="primary" onClick={() => handleEditRow(row)}>
|
||||
{$t('common.edit')}
|
||||
</NButton>
|
||||
) : null}
|
||||
{showEdit && showDelete ? <NDivider vertical /> : null}
|
||||
{showDelete ? (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.controlId)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRowFieldValue(row: TableRow, field: EditableField) {
|
||||
return row[field as keyof TableRow];
|
||||
}
|
||||
|
||||
function formatDisplayValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
return typeof value === 'number' ? value : String(value);
|
||||
}
|
||||
|
||||
function renderEditableCell(row: TableRow, field: EditableField, options?: { textarea?: boolean }) {
|
||||
if (!isEditingRow(row)) {
|
||||
return formatDisplayValue(getRowFieldValue(row, field));
|
||||
}
|
||||
|
||||
const inputValue = editingModel.value[field];
|
||||
const resolvedValue = inputValue === null || inputValue === undefined ? '' : String(inputValue);
|
||||
|
||||
if (options?.textarea) {
|
||||
return renderTextareaEditor(row, field, resolvedValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<NInput
|
||||
size="small"
|
||||
type={options?.textarea ? 'textarea' : 'text'}
|
||||
autosize={options?.textarea ? { minRows: 1, maxRows: 4 } : undefined}
|
||||
value={resolvedValue}
|
||||
onUpdateValue={value => updateEditingField(field, value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTextareaEditor(row: TableRow, field: EditableField, value: string) {
|
||||
return <InlineExpandTextarea value={value} onUpdateValue={val => updateEditingField(field, val)} />;
|
||||
}
|
||||
|
||||
function updateEditingField(field: EditableField, value: string | number | null) {
|
||||
(editingModel.value as Record<string, string | number | null>)[field] = value;
|
||||
}
|
||||
|
||||
function resolveRowKey(row: TableRow) {
|
||||
if (row.controlId !== null && row.controlId !== undefined && row.controlId !== '') {
|
||||
return String(row.controlId);
|
||||
}
|
||||
return row.tempKey ?? '';
|
||||
}
|
||||
|
||||
function isEditingRow(row: TableRow) {
|
||||
return editingRowKey.value !== null && editingRowKey.value === resolveRowKey(row);
|
||||
}
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
controlId: null,
|
||||
provinceCode: '',
|
||||
provinceName: '',
|
||||
year: null,
|
||||
majorCategory: '',
|
||||
batchName: '',
|
||||
subjectType: '',
|
||||
cultureScore: null,
|
||||
majorScore: null,
|
||||
cultureScoreExam: null,
|
||||
majorScoreExam: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
function createTempKey() {
|
||||
return `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function ensureEditingGuard(action: () => void | Promise<void>) {
|
||||
const executeAction = () => Promise.resolve(action());
|
||||
|
||||
if (!editingRowKey.value) {
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
if (!window.$dialog) {
|
||||
resetEditingState();
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '当前行尚未保存,确定放弃修改吗?',
|
||||
positiveText: '放弃',
|
||||
negativeText: '继续编辑',
|
||||
onPositiveClick: async () => {
|
||||
resetEditingState();
|
||||
try {
|
||||
await executeAction();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
onNegativeClick: () => resolve()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetEditingState() {
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
editingMode.value = null;
|
||||
editingRowKey.value = null;
|
||||
savingRowKey.value = null;
|
||||
tempRow.value = null;
|
||||
}
|
||||
|
||||
async function handleAddRow() {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'add';
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
const key = createTempKey();
|
||||
tempRow.value = { ...(editingModel.value as TableRow), tempKey: key };
|
||||
editingRowKey.value = key;
|
||||
checkedRowKeys.value = [];
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEditRow(row: TableRow) {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'edit';
|
||||
tempRow.value = null;
|
||||
editingRowKey.value = resolveRowKey(row);
|
||||
editingSnapshot.value = jsonClone(row);
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(row));
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
resetEditingState();
|
||||
}
|
||||
|
||||
const requiredFields: Array<{ key: EditableField; label: string }> = [
|
||||
{ key: 'provinceCode', label: '省份行政区划代码' },
|
||||
{ key: 'provinceName', label: '省份名称' },
|
||||
{ key: 'year', label: '年份' },
|
||||
{ key: 'majorCategory', label: '专业类别' },
|
||||
{ key: 'batchName', label: '批次' },
|
||||
{ key: 'subjectType', label: '科类(文/理)' }
|
||||
];
|
||||
|
||||
function validateModel() {
|
||||
for (const item of requiredFields) {
|
||||
const value = editingModel.value[item.key];
|
||||
if (value === null || value === undefined || value === '') {
|
||||
window.$message?.warning(`${item.label}不能为空`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSaveRow() {
|
||||
if (!editingMode.value) return;
|
||||
if (!validateModel()) return;
|
||||
|
||||
const {
|
||||
controlId,
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
} = editingModel.value;
|
||||
|
||||
savingRowKey.value = editingRowKey.value;
|
||||
|
||||
const requestResult =
|
||||
editingMode.value === 'add'
|
||||
? await fetchCreateHistoryScoreControlLine({
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
})
|
||||
: await fetchUpdateHistoryScoreControlLine({
|
||||
controlId,
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
});
|
||||
|
||||
savingRowKey.value = null;
|
||||
|
||||
if (requestResult.error) {
|
||||
window.$message?.error(requestResult.error.message || '保存失败');
|
||||
if (editingMode.value === 'edit' && editingSnapshot.value) {
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(editingSnapshot.value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
resetEditingState();
|
||||
await getData();
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteHistoryScoreControlLine(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
checkedRowKeys.value = [];
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(controlId: CommonType.IdType) {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteHistoryScoreControlLine([controlId]);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/historyScoreControlLine/export', searchParams.value, `历年省控线_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
await ensureEditingGuard(async () => {
|
||||
await getDataByPage();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
await ensureEditingGuard(getData);
|
||||
}
|
||||
|
||||
function rowClassName(row: TableRow) {
|
||||
return isEditingRow(row) ? 'inline-edit-row' : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<HistoryScoreControlLineSearch v-model:model="searchParams" @search="handleSearch" />
|
||||
<NCard title="历年省控线列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:historyScoreControlLine:add')"
|
||||
:show-delete="hasAuth('art:historyScoreControlLine:remove')"
|
||||
:show-export="hasAuth('art:historyScoreControlLine:export')"
|
||||
@add="handleAddRow"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="resolveRowKey"
|
||||
:row-class-name="rowClassName"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.inline-edit-row .n-data-table-td) {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import {
|
||||
fetchCreateHistoryScoreControlLine,
|
||||
fetchUpdateHistoryScoreControlLine
|
||||
} from '@/service/api/art/history-score-control-line';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'HistoryScoreControlLineOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.HistoryScoreControlLine | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增历年省控线',
|
||||
edit: '编辑历年省控线'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.HistoryScoreControlLineOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
controlId: null,
|
||||
provinceCode: '',
|
||||
provinceName: '',
|
||||
year: null,
|
||||
majorCategory: '',
|
||||
batchName: '',
|
||||
subjectType: '',
|
||||
cultureScore: null,
|
||||
majorScore: null,
|
||||
cultureScoreExam: null,
|
||||
majorScoreExam: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<
|
||||
keyof Model,
|
||||
| 'controlId'
|
||||
| 'tenantId'
|
||||
| 'delFlag'
|
||||
| 'provinceCode'
|
||||
| 'provinceName'
|
||||
| 'year'
|
||||
| 'majorCategory'
|
||||
| 'batchName'
|
||||
| 'subjectType'
|
||||
>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
controlId: createRequiredRule('省控线主键不能为空'),
|
||||
tenantId: createRequiredRule('租户编号不能为空'),
|
||||
delFlag: createRequiredRule('删除标志(0存在 1删除)不能为空'),
|
||||
provinceCode: createRequiredRule('省份行政区划代码不能为空'),
|
||||
provinceName: createRequiredRule('省份名称不能为空'),
|
||||
year: createRequiredRule('年份不能为空'),
|
||||
majorCategory: createRequiredRule('专业类别不能为空'),
|
||||
batchName: createRequiredRule('批次不能为空'),
|
||||
subjectType: createRequiredRule('科类(文/理)不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
controlId,
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
} = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateHistoryScoreControlLine({
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateHistoryScoreControlLine({
|
||||
controlId,
|
||||
provinceCode,
|
||||
provinceName,
|
||||
year,
|
||||
majorCategory,
|
||||
batchName,
|
||||
subjectType,
|
||||
cultureScore,
|
||||
majorScore,
|
||||
cultureScoreExam,
|
||||
majorScoreExam,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
getTreeList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="省份行政区划代码" path="provinceCode">
|
||||
<NInput v-model:value="model.provinceCode" placeholder="请输入省份行政区划代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="省份名称" path="provinceName">
|
||||
<NInput v-model:value="model.provinceName" placeholder="请输入省份名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="年份" path="year">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业类别" path="majorCategory">
|
||||
<NInput v-model:value="model.majorCategory" placeholder="请输入专业类别" />
|
||||
</NFormItem>
|
||||
<NFormItem label="批次" path="batchName">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItem>
|
||||
<NFormItem label="科类(文/理)" path="subjectType">
|
||||
<NSelect
|
||||
v-model:value="model.subjectType"
|
||||
placeholder="请选择科类(文/理)"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="文化成绩分数" path="cultureScore">
|
||||
<NInput v-model:value="model.cultureScore" placeholder="请输入文化成绩分数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业成绩分数" path="majorScore">
|
||||
<NInput v-model:value="model.majorScore" placeholder="请输入专业成绩分数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="文化成绩校考分数" path="cultureScoreExam">
|
||||
<NInput v-model:value="model.cultureScoreExam" placeholder="请输入文化成绩校考分数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业成绩校考分数" path="majorScoreExam">
|
||||
<NInput v-model:value="model.majorScoreExam" placeholder="请输入专业成绩校考分数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'HistoryScoreControlLineSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.HistoryScoreControlLineSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-history-score-control-line-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="省份行政区划代码"
|
||||
label-width="auto"
|
||||
path="provinceCode"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.provinceCode" placeholder="请输入省份行政区划代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="省份名称" label-width="auto" path="provinceName" class="pr-24px">
|
||||
<NInput v-model:value="model.provinceName" placeholder="请输入省份名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="年份" label-width="auto" path="year" class="pr-24px">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业类别" label-width="auto" path="majorCategory" class="pr-24px">
|
||||
<NInput v-model:value="model.majorCategory" placeholder="请输入专业类别" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="批次" label-width="auto" path="batchName" class="pr-24px">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="科类(文/理)" label-width="auto" path="subjectType" class="pr-24px">
|
||||
<NSelect v-model:value="model.subjectType" placeholder="请选择科类(文/理)" :options="[]" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="文化成绩分数" label-width="auto" path="cultureScore" class="pr-24px">
|
||||
<NInput v-model:value="model.cultureScore" placeholder="请输入文化成绩分数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业成绩分数" label-width="auto" path="majorScore" class="pr-24px">
|
||||
<NInput v-model:value="model.majorScore" placeholder="请输入专业成绩分数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="文化成绩校考分数"
|
||||
label-width="auto"
|
||||
path="cultureScoreExam"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.cultureScoreExam" placeholder="请输入文化成绩校考分数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="专业成绩校考分数"
|
||||
label-width="auto"
|
||||
path="majorScoreExam"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.majorScoreExam" placeholder="请输入专业成绩校考分数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,465 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider, NInput, NSpace } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchBatchDeleteMajor, fetchCreateMajor, fetchGetMajorList, fetchUpdateMajor } from '@/service/api/art/major';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import InlineExpandTextarea from '@/components/custom/inline-expand-textarea.vue';
|
||||
import MajorSearch from './modules/major-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MajorList'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.MajorSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
majorName: null,
|
||||
educationLevel: null,
|
||||
majorIcon: null,
|
||||
schoolingYears: null,
|
||||
disciplinePrimary: null,
|
||||
disciplineSecondary: null,
|
||||
degreeAwarded: null,
|
||||
summary: null,
|
||||
trainingDirection: null,
|
||||
coreCourses: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
type TableRow = Api.Art.Major & { tempKey?: string };
|
||||
type Model = Api.Art.MajorOperateParams;
|
||||
type EditableField = Extract<keyof Model, keyof TableRow>;
|
||||
|
||||
const editingModel = ref<Model>(createDefaultModel());
|
||||
const editingSnapshot = ref<TableRow | null>(null);
|
||||
const editingMode = ref<NaiveUI.TableOperateType | null>(null);
|
||||
const editingRowKey = ref<string | null>(null);
|
||||
const savingRowKey = ref<string | null>(null);
|
||||
const tempRow = ref<TableRow | null>(null);
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetMajorList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => createColumns()
|
||||
});
|
||||
|
||||
const tableData = computed<TableRow[]>(() => {
|
||||
const rows = data.value as TableRow[];
|
||||
return tempRow.value ? [tempRow.value, ...rows] : rows;
|
||||
});
|
||||
|
||||
function createColumns(): NaiveUI.TableColumn<TableRow>[] {
|
||||
return [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'majorId',
|
||||
title: '专业主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
createEditableColumn('majorName', '专业名称'),
|
||||
createEditableColumn('educationLevel', '学历层次'),
|
||||
createEditableColumn('majorIcon', '专业图标'),
|
||||
createEditableColumn('schoolingYears', '学制(年)'),
|
||||
createEditableColumn('disciplinePrimary', '所属一级学科'),
|
||||
createEditableColumn('disciplineSecondary', '所属二级学科'),
|
||||
createEditableColumn('degreeAwarded', '授予学士学位'),
|
||||
createEditableColumn('summary', '专业概括', { textarea: true }),
|
||||
createEditableColumn('trainingDirection', '培养方向', { textarea: true }),
|
||||
createEditableColumn('coreCourses', '主要课程', { textarea: true }),
|
||||
createEditableColumn('remark', '备注', { textarea: true }),
|
||||
createOperateColumn()
|
||||
];
|
||||
}
|
||||
|
||||
function createEditableColumn(key: EditableField, title: string, options?: { textarea?: boolean; minWidth?: number }) {
|
||||
return {
|
||||
key,
|
||||
title,
|
||||
align: 'center',
|
||||
minWidth: options?.minWidth ?? 120,
|
||||
render: (row: TableRow) => renderEditableCell(row, key, options)
|
||||
} satisfies NaiveUI.TableColumn<TableRow>;
|
||||
}
|
||||
|
||||
function createOperateColumn(): NaiveUI.TableColumn<TableRow> {
|
||||
return {
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
render: (row: TableRow) => {
|
||||
const rowKey = resolveRowKey(row);
|
||||
const editing = editingRowKey.value === rowKey;
|
||||
const saving = savingRowKey.value === rowKey;
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<NSpace size={8} justify="center">
|
||||
<NButton size="tiny" quaternary disabled={saving} onClick={handleCancelEdit}>
|
||||
{$t('common.cancel')}
|
||||
</NButton>
|
||||
<NButton size="tiny" type="primary" loading={saving} onClick={handleSaveRow}>
|
||||
{$t('common.save')}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
);
|
||||
}
|
||||
|
||||
const showEdit = hasAuth('art:major:edit');
|
||||
const showDelete = hasAuth('art:major:remove');
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{showEdit ? (
|
||||
<NButton size="tiny" text type="primary" onClick={() => handleEditRow(row)}>
|
||||
{$t('common.edit')}
|
||||
</NButton>
|
||||
) : null}
|
||||
{showEdit && showDelete ? <NDivider vertical /> : null}
|
||||
{showDelete ? (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.majorId)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRowFieldValue(row: TableRow, field: EditableField) {
|
||||
return row[field as keyof TableRow];
|
||||
}
|
||||
|
||||
function formatDisplayValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
return typeof value === 'number' ? value : String(value);
|
||||
}
|
||||
|
||||
function renderEditableCell(row: TableRow, field: EditableField, options?: { textarea?: boolean }) {
|
||||
if (!isEditingRow(row)) {
|
||||
return formatDisplayValue(getRowFieldValue(row, field));
|
||||
}
|
||||
|
||||
const inputValue = editingModel.value[field];
|
||||
const resolvedValue = inputValue === null || inputValue === undefined ? '' : String(inputValue);
|
||||
|
||||
if (options?.textarea) {
|
||||
return renderTextareaEditor(row, field, resolvedValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<NInput
|
||||
size="small"
|
||||
type={options?.textarea ? 'textarea' : 'text'}
|
||||
autosize={options?.textarea ? { minRows: 1, maxRows: 4 } : undefined}
|
||||
value={resolvedValue}
|
||||
onUpdateValue={value => updateEditingField(field, value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTextareaEditor(row: TableRow, field: EditableField, value: string) {
|
||||
return <InlineExpandTextarea value={value} onUpdateValue={val => updateEditingField(field, val)} />;
|
||||
}
|
||||
|
||||
function updateEditingField(field: EditableField, value: string | number | null) {
|
||||
(editingModel.value as Record<string, string | number | null>)[field] = value;
|
||||
}
|
||||
|
||||
function resolveRowKey(row: TableRow) {
|
||||
if (row.majorId !== null && row.majorId !== undefined && row.majorId !== '') {
|
||||
return String(row.majorId);
|
||||
}
|
||||
return row.tempKey ?? '';
|
||||
}
|
||||
|
||||
function isEditingRow(row: TableRow) {
|
||||
return editingRowKey.value !== null && editingRowKey.value === resolveRowKey(row);
|
||||
}
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
majorId: null,
|
||||
majorName: '',
|
||||
educationLevel: '',
|
||||
majorIcon: '',
|
||||
schoolingYears: null,
|
||||
disciplinePrimary: '',
|
||||
disciplineSecondary: '',
|
||||
degreeAwarded: '',
|
||||
summary: '',
|
||||
trainingDirection: '',
|
||||
coreCourses: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
function createTempKey() {
|
||||
return `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function ensureEditingGuard(action: () => void | Promise<void>) {
|
||||
const executeAction = () => Promise.resolve(action());
|
||||
|
||||
if (!editingRowKey.value) {
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
if (!window.$dialog) {
|
||||
resetEditingState();
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '当前行尚未保存,确定放弃修改吗?',
|
||||
positiveText: '放弃',
|
||||
negativeText: '继续编辑',
|
||||
onPositiveClick: async () => {
|
||||
resetEditingState();
|
||||
try {
|
||||
await executeAction();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
onNegativeClick: () => resolve()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetEditingState() {
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
editingMode.value = null;
|
||||
editingRowKey.value = null;
|
||||
savingRowKey.value = null;
|
||||
tempRow.value = null;
|
||||
}
|
||||
|
||||
async function handleAddRow() {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'add';
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
const key = createTempKey();
|
||||
tempRow.value = { ...(editingModel.value as TableRow), tempKey: key };
|
||||
editingRowKey.value = key;
|
||||
checkedRowKeys.value = [];
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEditRow(row: TableRow) {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'edit';
|
||||
tempRow.value = null;
|
||||
editingRowKey.value = resolveRowKey(row);
|
||||
editingSnapshot.value = jsonClone(row);
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(row));
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
resetEditingState();
|
||||
}
|
||||
|
||||
const requiredFields: Array<{ key: EditableField; label: string }> = [
|
||||
{ key: 'majorName', label: '专业名称' },
|
||||
{ key: 'educationLevel', label: '学历层次' }
|
||||
];
|
||||
|
||||
function validateModel() {
|
||||
for (const item of requiredFields) {
|
||||
const value = editingModel.value[item.key];
|
||||
if (value === null || value === undefined || value === '') {
|
||||
window.$message?.warning(`${item.label}不能为空`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSaveRow() {
|
||||
if (!editingMode.value) return;
|
||||
if (!validateModel()) return;
|
||||
|
||||
const {
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
} = editingModel.value;
|
||||
|
||||
savingRowKey.value = editingRowKey.value;
|
||||
|
||||
const requestResult =
|
||||
editingMode.value === 'add'
|
||||
? await fetchCreateMajor({
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
})
|
||||
: await fetchUpdateMajor({
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
});
|
||||
|
||||
savingRowKey.value = null;
|
||||
|
||||
if (requestResult.error) {
|
||||
window.$message?.error(requestResult.error.message || '保存失败');
|
||||
if (editingMode.value === 'edit' && editingSnapshot.value) {
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(editingSnapshot.value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
resetEditingState();
|
||||
await getData();
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteMajor(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
checkedRowKeys.value = [];
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(majorId: CommonType.IdType) {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteMajor([majorId]);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/major/export', searchParams.value, `艺术专业库_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
await ensureEditingGuard(async () => {
|
||||
await getDataByPage();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
await ensureEditingGuard(getData);
|
||||
}
|
||||
|
||||
function rowClassName(row: TableRow) {
|
||||
return isEditingRow(row) ? 'inline-edit-row' : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<MajorSearch v-model:model="searchParams" @search="handleSearch" />
|
||||
<NCard title="艺术专业库列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:major:add')"
|
||||
:show-delete="hasAuth('art:major:remove')"
|
||||
:show-export="hasAuth('art:major:export')"
|
||||
@add="handleAddRow"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="resolveRowKey"
|
||||
:row-class-name="rowClassName"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.inline-edit-row .n-data-table-td) {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateMajor, fetchUpdateMajor } from '@/service/api/art/major';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'MajorOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.Major | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增艺术专业库',
|
||||
edit: '编辑艺术专业库'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.MajorOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
majorId: null,
|
||||
majorName: '',
|
||||
educationLevel: '',
|
||||
majorIcon: '',
|
||||
schoolingYears: null,
|
||||
disciplinePrimary: '',
|
||||
disciplineSecondary: '',
|
||||
degreeAwarded: '',
|
||||
summary: '',
|
||||
trainingDirection: '',
|
||||
coreCourses: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'majorId' | 'tenantId' | 'delFlag' | 'majorName' | 'educationLevel'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
majorId: createRequiredRule('专业主键ID不能为空'),
|
||||
tenantId: createRequiredRule('租户编号不能为空'),
|
||||
delFlag: createRequiredRule('删除标志(0存在 1删除)不能为空'),
|
||||
majorName: createRequiredRule('专业名称不能为空'),
|
||||
educationLevel: createRequiredRule('学历层次不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
} = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateMajor({
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateMajor({
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
majorIcon,
|
||||
schoolingYears,
|
||||
disciplinePrimary,
|
||||
disciplineSecondary,
|
||||
degreeAwarded,
|
||||
summary,
|
||||
trainingDirection,
|
||||
coreCourses,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
getTreeList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="专业名称" path="majorName">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学历层次" path="educationLevel">
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业图标" path="majorIcon">
|
||||
<NInput v-model:value="model.majorIcon" placeholder="请输入专业图标" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学制(年)" path="schoolingYears">
|
||||
<NInput v-model:value="model.schoolingYears" placeholder="请输入学制(年)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="所属一级学科" path="disciplinePrimary">
|
||||
<NInput v-model:value="model.disciplinePrimary" placeholder="请输入所属一级学科" />
|
||||
</NFormItem>
|
||||
<NFormItem label="所属二级学科" path="disciplineSecondary">
|
||||
<NInput v-model:value="model.disciplineSecondary" placeholder="请输入所属二级学科" />
|
||||
</NFormItem>
|
||||
<NFormItem label="授予学士学位" path="degreeAwarded">
|
||||
<NInput v-model:value="model.degreeAwarded" placeholder="请输入授予学士学位" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业概括" path="summary">
|
||||
<NInput v-model:value="model.summary" :rows="3" type="textarea" placeholder="请输入专业概括" />
|
||||
</NFormItem>
|
||||
<NFormItem label="培养方向" path="trainingDirection">
|
||||
<NInput v-model:value="model.trainingDirection" :rows="3" type="textarea" placeholder="请输入培养方向" />
|
||||
</NFormItem>
|
||||
<NFormItem label="主要课程" path="coreCourses">
|
||||
<NInput v-model:value="model.coreCourses" :rows="3" type="textarea" placeholder="请输入主要课程" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'MajorSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.MajorSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-major-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业名称" label-width="auto" path="majorName" class="pr-24px">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学历层次" label-width="auto" path="educationLevel" class="pr-24px">
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业图标" label-width="auto" path="majorIcon" class="pr-24px">
|
||||
<NInput v-model:value="model.majorIcon" placeholder="请输入专业图标" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学制(年)" label-width="auto" path="schoolingYears" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolingYears" placeholder="请输入学制(年)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="所属一级学科"
|
||||
label-width="auto"
|
||||
path="disciplinePrimary"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.disciplinePrimary" placeholder="请输入所属一级学科" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="所属二级学科"
|
||||
label-width="auto"
|
||||
path="disciplineSecondary"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.disciplineSecondary" placeholder="请输入所属二级学科" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="授予学士学位"
|
||||
label-width="auto"
|
||||
path="degreeAwarded"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.degreeAwarded" placeholder="请输入授予学士学位" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业概括" label-width="auto" path="summary" class="pr-24px">
|
||||
<NInput v-model:value="model.summary" placeholder="请输入专业概括" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="培养方向"
|
||||
label-width="auto"
|
||||
path="trainingDirection"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.trainingDirection" placeholder="请输入培养方向" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="主要课程" label-width="auto" path="coreCourses" class="pr-24px">
|
||||
<NInput v-model:value="model.coreCourses" placeholder="请输入主要课程" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,573 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider, NInput, NSpace } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import {
|
||||
fetchBatchDeleteSchoolRecruitMajorHistory,
|
||||
fetchCreateSchoolRecruitMajorHistory,
|
||||
fetchGetSchoolRecruitMajorHistoryList,
|
||||
fetchUpdateSchoolRecruitMajorHistory
|
||||
} from '@/service/api/art/school-recruit-major-history';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import InlineExpandTextarea from '@/components/custom/inline-expand-textarea.vue';
|
||||
import SchoolRecruitMajorHistorySearch from './modules/school-recruit-major-history-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorHistoryList'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolRecruitMajorHistorySearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
recruitMajorId: null,
|
||||
schoolId: null,
|
||||
schoolCode: null,
|
||||
collegeCode: null,
|
||||
schoolName: null,
|
||||
majorId: null,
|
||||
majorCode: null,
|
||||
majorName: null,
|
||||
enrollCode: null,
|
||||
majorType: null,
|
||||
majorTypeSub: null,
|
||||
mainExamSubject: null,
|
||||
year: null,
|
||||
subjectType: null,
|
||||
batchName: null,
|
||||
admissionFormula: null,
|
||||
probabilityOperator: null,
|
||||
controlScore: null,
|
||||
admissionScore: null,
|
||||
planEnroll: null,
|
||||
filedAmount: null,
|
||||
admitAmount: null,
|
||||
firstChoiceAdmitAmount: null,
|
||||
minScoreDiff: null,
|
||||
tuitionFee: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
type TableRow = Api.Art.SchoolRecruitMajorHistory & { tempKey?: string };
|
||||
type Model = Api.Art.SchoolRecruitMajorHistoryOperateParams;
|
||||
type EditableField = Extract<keyof Model, keyof TableRow>;
|
||||
|
||||
const editingModel = ref<Model>(createDefaultModel());
|
||||
const editingSnapshot = ref<TableRow | null>(null);
|
||||
const editingMode = ref<NaiveUI.TableOperateType | null>(null);
|
||||
const editingRowKey = ref<string | null>(null);
|
||||
const savingRowKey = ref<string | null>(null);
|
||||
const tempRow = ref<TableRow | null>(null);
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
const editableColumns: Array<{ key: EditableField; title: string; textarea?: boolean; minWidth?: number }> = [
|
||||
{ key: 'recruitMajorId', title: '对应招录专业ID' },
|
||||
{ key: 'schoolId', title: '学校ID' },
|
||||
{ key: 'schoolCode', title: '学校代码' },
|
||||
{ key: 'collegeCode', title: '院校代码' },
|
||||
{ key: 'schoolName', title: '学校名称', minWidth: 160 },
|
||||
{ key: 'majorId', title: '专业ID' },
|
||||
{ key: 'majorCode', title: '专业代码' },
|
||||
{ key: 'majorName', title: '专业名称', minWidth: 160 },
|
||||
{ key: 'enrollCode', title: '招生代码' },
|
||||
{ key: 'majorType', title: '专业类型' },
|
||||
{ key: 'majorTypeSub', title: '专业类别子级' },
|
||||
{ key: 'mainExamSubject', title: '主考科目' },
|
||||
{ key: 'year', title: '年份' },
|
||||
{ key: 'subjectType', title: '科类(文/理)' },
|
||||
{ key: 'batchName', title: '批次' },
|
||||
{ key: 'admissionFormula', title: '录取方式', minWidth: 160 },
|
||||
{ key: 'probabilityOperator', title: '录取概率规则运算符', minWidth: 160 },
|
||||
{ key: 'controlScore', title: '省控线' },
|
||||
{ key: 'admissionScore', title: '录取线' },
|
||||
{ key: 'planEnroll', title: '招生人数' },
|
||||
{ key: 'filedAmount', title: '实际投档人数' },
|
||||
{ key: 'admitAmount', title: '录取数' },
|
||||
{ key: 'firstChoiceAdmitAmount', title: '一志愿录取数', minWidth: 160 },
|
||||
{ key: 'minScoreDiff', title: '最低分数差' },
|
||||
{ key: 'tuitionFee', title: '学费(元/年)' },
|
||||
{ key: 'remark', title: '备注', textarea: true, minWidth: 160 }
|
||||
];
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolRecruitMajorHistoryList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => createColumns()
|
||||
});
|
||||
|
||||
const tableData = computed<TableRow[]>(() => {
|
||||
const rows = data.value as TableRow[];
|
||||
return tempRow.value ? [tempRow.value, ...rows] : rows;
|
||||
});
|
||||
|
||||
function createColumns(): NaiveUI.TableColumn<TableRow>[] {
|
||||
return [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'historyId',
|
||||
title: '历年录取数据ID',
|
||||
align: 'center',
|
||||
minWidth: 160
|
||||
},
|
||||
...editableColumns.map(column => createEditableColumn(column)),
|
||||
createOperateColumn()
|
||||
];
|
||||
}
|
||||
|
||||
function createEditableColumn(column: { key: EditableField; title: string; textarea?: boolean; minWidth?: number }) {
|
||||
return {
|
||||
key: column.key,
|
||||
title: column.title,
|
||||
align: 'center',
|
||||
minWidth: column.minWidth ?? 140,
|
||||
render: (row: TableRow) => renderEditableCell(row, column.key, column)
|
||||
} satisfies NaiveUI.TableColumn<TableRow>;
|
||||
}
|
||||
|
||||
function createOperateColumn(): NaiveUI.TableColumn<TableRow> {
|
||||
return {
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
render: (row: TableRow) => {
|
||||
const rowKey = resolveRowKey(row);
|
||||
const editing = editingRowKey.value === rowKey;
|
||||
const saving = savingRowKey.value === rowKey;
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<NSpace size={8} justify="center">
|
||||
<NButton size="tiny" quaternary disabled={saving} onClick={handleCancelEdit}>
|
||||
{$t('common.cancel')}
|
||||
</NButton>
|
||||
<NButton size="tiny" type="primary" loading={saving} onClick={handleSaveRow}>
|
||||
{$t('common.save')}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
);
|
||||
}
|
||||
|
||||
const showEdit = hasAuth('art:schoolRecruitMajorHistory:edit');
|
||||
const showDelete = hasAuth('art:schoolRecruitMajorHistory:remove');
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{showEdit ? (
|
||||
<NButton size="tiny" text type="primary" onClick={() => handleEditRow(row)}>
|
||||
{$t('common.edit')}
|
||||
</NButton>
|
||||
) : null}
|
||||
{showEdit && showDelete ? <NDivider vertical /> : null}
|
||||
{showDelete ? (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.historyId)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRowFieldValue(row: TableRow, field: EditableField) {
|
||||
return row[field as keyof TableRow];
|
||||
}
|
||||
|
||||
function formatDisplayValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
return typeof value === 'number' ? value : String(value);
|
||||
}
|
||||
|
||||
function renderEditableCell(row: TableRow, field: EditableField, options?: { textarea?: boolean }) {
|
||||
if (!isEditingRow(row)) {
|
||||
return formatDisplayValue(getRowFieldValue(row, field));
|
||||
}
|
||||
|
||||
const inputValue = editingModel.value[field];
|
||||
const resolvedValue = inputValue === null || inputValue === undefined ? '' : String(inputValue);
|
||||
|
||||
if (options?.textarea) {
|
||||
return renderTextareaEditor(row, field, resolvedValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<NInput
|
||||
size="small"
|
||||
type={options?.textarea ? 'textarea' : 'text'}
|
||||
autosize={options?.textarea ? { minRows: 1, maxRows: 4 } : undefined}
|
||||
value={resolvedValue}
|
||||
onUpdateValue={value => updateEditingField(field, value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTextareaEditor(row: TableRow, field: EditableField, value: string) {
|
||||
return <InlineExpandTextarea value={value} onUpdateValue={val => updateEditingField(field, val)} />;
|
||||
}
|
||||
|
||||
function updateEditingField(field: EditableField, value: string | number | null) {
|
||||
(editingModel.value as Record<string, string | number | null>)[field] = value;
|
||||
}
|
||||
|
||||
function resolveRowKey(row: TableRow) {
|
||||
if (row.historyId !== null && row.historyId !== undefined && row.historyId !== '') {
|
||||
return String(row.historyId);
|
||||
}
|
||||
return row.tempKey ?? '';
|
||||
}
|
||||
|
||||
function isEditingRow(row: TableRow) {
|
||||
return editingRowKey.value !== null && editingRowKey.value === resolveRowKey(row);
|
||||
}
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
historyId: null,
|
||||
recruitMajorId: null,
|
||||
schoolId: null,
|
||||
schoolCode: '',
|
||||
collegeCode: '',
|
||||
schoolName: '',
|
||||
majorId: null,
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
enrollCode: '',
|
||||
majorType: '',
|
||||
majorTypeSub: '',
|
||||
mainExamSubject: '',
|
||||
year: null,
|
||||
subjectType: '',
|
||||
batchName: '',
|
||||
admissionFormula: '',
|
||||
probabilityOperator: '',
|
||||
controlScore: null,
|
||||
admissionScore: null,
|
||||
planEnroll: null,
|
||||
filedAmount: null,
|
||||
admitAmount: null,
|
||||
firstChoiceAdmitAmount: null,
|
||||
minScoreDiff: null,
|
||||
tuitionFee: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
function createTempKey() {
|
||||
return `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function ensureEditingGuard(action: () => void | Promise<void>) {
|
||||
const executeAction = () => Promise.resolve(action());
|
||||
|
||||
if (!editingRowKey.value) {
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
if (!window.$dialog) {
|
||||
resetEditingState();
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '当前行尚未保存,确定放弃修改吗?',
|
||||
positiveText: '放弃',
|
||||
negativeText: '继续编辑',
|
||||
onPositiveClick: async () => {
|
||||
resetEditingState();
|
||||
try {
|
||||
await executeAction();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
onNegativeClick: () => resolve()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetEditingState() {
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
editingMode.value = null;
|
||||
editingRowKey.value = null;
|
||||
savingRowKey.value = null;
|
||||
tempRow.value = null;
|
||||
}
|
||||
|
||||
async function handleAddRow() {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'add';
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
const key = createTempKey();
|
||||
tempRow.value = { ...(editingModel.value as TableRow), tempKey: key };
|
||||
editingRowKey.value = key;
|
||||
checkedRowKeys.value = [];
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEditRow(row: TableRow) {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'edit';
|
||||
tempRow.value = null;
|
||||
editingRowKey.value = resolveRowKey(row);
|
||||
editingSnapshot.value = jsonClone(row);
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(row));
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
resetEditingState();
|
||||
}
|
||||
|
||||
const requiredFields: Array<{ key: EditableField; label: string }> = [
|
||||
{ key: 'recruitMajorId', label: '对应招录专业ID' },
|
||||
{ key: 'schoolId', label: '学校ID' },
|
||||
{ key: 'schoolCode', label: '学校代码' },
|
||||
{ key: 'schoolName', label: '学校名称' },
|
||||
{ key: 'majorCode', label: '专业代码' },
|
||||
{ key: 'majorName', label: '专业名称' },
|
||||
{ key: 'year', label: '年份' }
|
||||
];
|
||||
|
||||
function validateModel() {
|
||||
for (const item of requiredFields) {
|
||||
const value = editingModel.value[item.key];
|
||||
if (value === null || value === undefined || value === '') {
|
||||
window.$message?.warning(`${item.label}不能为空`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSaveRow() {
|
||||
if (!editingMode.value) return;
|
||||
if (!validateModel()) return;
|
||||
|
||||
const {
|
||||
historyId,
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
} = editingModel.value;
|
||||
|
||||
savingRowKey.value = editingRowKey.value;
|
||||
|
||||
const requestResult =
|
||||
editingMode.value === 'add'
|
||||
? await fetchCreateSchoolRecruitMajorHistory({
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
})
|
||||
: await fetchUpdateSchoolRecruitMajorHistory({
|
||||
historyId,
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
});
|
||||
|
||||
savingRowKey.value = null;
|
||||
|
||||
if (requestResult.error) {
|
||||
window.$message?.error(requestResult.error.message || '保存失败');
|
||||
if (editingMode.value === 'edit' && editingSnapshot.value) {
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(editingSnapshot.value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
resetEditingState();
|
||||
await getData();
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteSchoolRecruitMajorHistory(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
checkedRowKeys.value = [];
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(historyId: CommonType.IdType) {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteSchoolRecruitMajorHistory([historyId]);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download(
|
||||
'/art/schoolRecruitMajorHistory/export',
|
||||
searchParams.value,
|
||||
`院校招录专业历年录取数据_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
await ensureEditingGuard(async () => {
|
||||
await getDataByPage();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
await ensureEditingGuard(getData);
|
||||
}
|
||||
|
||||
function rowClassName(row: TableRow) {
|
||||
return isEditingRow(row) ? 'inline-edit-row' : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<SchoolRecruitMajorHistorySearch v-model:model="searchParams" @search="handleSearch" />
|
||||
<NCard title="院校招录专业历年录取数据列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolRecruitMajorHistory:add')"
|
||||
:show-delete="hasAuth('art:schoolRecruitMajorHistory:remove')"
|
||||
:show-export="hasAuth('art:schoolRecruitMajorHistory:export')"
|
||||
@add="handleAddRow"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="resolveRowKey"
|
||||
:row-class-name="rowClassName"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.inline-edit-row .n-data-table-td) {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import {
|
||||
fetchCreateSchoolRecruitMajorHistory,
|
||||
fetchUpdateSchoolRecruitMajorHistory
|
||||
} from '@/service/api/art/school-recruit-major-history';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorHistoryOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolRecruitMajorHistory | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增院校招录专业历年录取数据',
|
||||
edit: '编辑院校招录专业历年录取数据'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolRecruitMajorHistoryOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
historyId: null,
|
||||
recruitMajorId: null,
|
||||
schoolId: null,
|
||||
schoolCode: '',
|
||||
collegeCode: '',
|
||||
schoolName: '',
|
||||
majorId: null,
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
enrollCode: '',
|
||||
majorType: '',
|
||||
majorTypeSub: '',
|
||||
mainExamSubject: '',
|
||||
year: null,
|
||||
subjectType: '',
|
||||
batchName: '',
|
||||
admissionFormula: '',
|
||||
probabilityOperator: '',
|
||||
controlScore: null,
|
||||
admissionScore: null,
|
||||
planEnroll: null,
|
||||
filedAmount: null,
|
||||
admitAmount: null,
|
||||
firstChoiceAdmitAmount: null,
|
||||
minScoreDiff: null,
|
||||
tuitionFee: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<
|
||||
keyof Model,
|
||||
| 'historyId'
|
||||
| 'tenantId'
|
||||
| 'delFlag'
|
||||
| 'recruitMajorId'
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'schoolName'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'year'
|
||||
>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
historyId: createRequiredRule('历年录取数据ID不能为空'),
|
||||
tenantId: createRequiredRule('租户编号不能为空'),
|
||||
delFlag: createRequiredRule('删除标志(0存在 1删除)不能为空'),
|
||||
recruitMajorId: createRequiredRule('对应招录专业ID不能为空'),
|
||||
schoolId: createRequiredRule('学校ID不能为空'),
|
||||
schoolCode: createRequiredRule('学校代码不能为空'),
|
||||
schoolName: createRequiredRule('学校名称不能为空'),
|
||||
majorCode: createRequiredRule('专业代码不能为空'),
|
||||
majorName: createRequiredRule('专业名称不能为空'),
|
||||
year: createRequiredRule('年份不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
historyId,
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
} = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolRecruitMajorHistory({
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolRecruitMajorHistory({
|
||||
historyId,
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
collegeCode,
|
||||
schoolName,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
mainExamSubject,
|
||||
year,
|
||||
subjectType,
|
||||
batchName,
|
||||
admissionFormula,
|
||||
probabilityOperator,
|
||||
controlScore,
|
||||
admissionScore,
|
||||
planEnroll,
|
||||
filedAmount,
|
||||
admitAmount,
|
||||
firstChoiceAdmitAmount,
|
||||
minScoreDiff,
|
||||
tuitionFee,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
getTreeList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="对应招录专业ID" path="recruitMajorId">
|
||||
<NInput v-model:value="model.recruitMajorId" placeholder="请输入对应招录专业ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学校ID" path="schoolId">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学校代码" path="schoolCode">
|
||||
<NInput v-model:value="model.schoolCode" placeholder="请输入学校代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="院校代码" path="collegeCode">
|
||||
<NInput v-model:value="model.collegeCode" placeholder="请输入院校代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学校名称" path="schoolName">
|
||||
<NInput v-model:value="model.schoolName" placeholder="请输入学校名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业ID" path="majorId">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业代码" path="majorCode">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业名称" path="majorName">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="招生代码" path="enrollCode">
|
||||
<NInput v-model:value="model.enrollCode" placeholder="请输入招生代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业类型" path="majorType">
|
||||
<NSelect
|
||||
v-model:value="model.majorType"
|
||||
placeholder="请选择专业类型"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="专业类别子级" path="majorTypeSub">
|
||||
<NInput v-model:value="model.majorTypeSub" placeholder="请输入专业类别子级" />
|
||||
</NFormItem>
|
||||
<NFormItem label="主考科目" path="mainExamSubject">
|
||||
<NInput v-model:value="model.mainExamSubject" placeholder="请输入主考科目" />
|
||||
</NFormItem>
|
||||
<NFormItem label="年份" path="year">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItem>
|
||||
<NFormItem label="科类(文/理)" path="subjectType">
|
||||
<NSelect
|
||||
v-model:value="model.subjectType"
|
||||
placeholder="请选择科类(文/理)"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="批次" path="batchName">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItem>
|
||||
<NFormItem label="录取方式(文*x+专*y)" path="admissionFormula">
|
||||
<NInput v-model:value="model.admissionFormula" placeholder="请输入录取方式(文*x+专*y)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="录取概率规则运算符" path="probabilityOperator">
|
||||
<NInput v-model:value="model.probabilityOperator" placeholder="请输入录取概率规则运算符" />
|
||||
</NFormItem>
|
||||
<NFormItem label="省控线" path="controlScore">
|
||||
<NInput v-model:value="model.controlScore" placeholder="请输入省控线" />
|
||||
</NFormItem>
|
||||
<NFormItem label="录取线" path="admissionScore">
|
||||
<NInput v-model:value="model.admissionScore" placeholder="请输入录取线" />
|
||||
</NFormItem>
|
||||
<NFormItem label="招生人数" path="planEnroll">
|
||||
<NInput v-model:value="model.planEnroll" placeholder="请输入招生人数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="实际投档人数" path="filedAmount">
|
||||
<NInput v-model:value="model.filedAmount" placeholder="请输入实际投档人数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="录取数" path="admitAmount">
|
||||
<NInput v-model:value="model.admitAmount" placeholder="请输入录取数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="一志愿录取数" path="firstChoiceAdmitAmount">
|
||||
<NInput v-model:value="model.firstChoiceAdmitAmount" placeholder="请输入一志愿录取数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="最低分数差" path="minScoreDiff">
|
||||
<NInput v-model:value="model.minScoreDiff" placeholder="请输入最低分数差" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学费(元/年)" path="tuitionFee">
|
||||
<NInput v-model:value="model.tuitionFee" placeholder="请输入学费(元/年)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorHistorySearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolRecruitMajorHistorySearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-recruit-major-history-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="对应招录专业ID"
|
||||
label-width="auto"
|
||||
path="recruitMajorId"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.recruitMajorId" placeholder="请输入对应招录专业ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校代码" label-width="auto" path="schoolCode" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolCode" placeholder="请输入学校代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="院校代码" label-width="auto" path="collegeCode" class="pr-24px">
|
||||
<NInput v-model:value="model.collegeCode" placeholder="请输入院校代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校名称" label-width="auto" path="schoolName" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolName" placeholder="请输入学校名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业ID" label-width="auto" path="majorId" class="pr-24px">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业代码" label-width="auto" path="majorCode" class="pr-24px">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业名称" label-width="auto" path="majorName" class="pr-24px">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="招生代码" label-width="auto" path="enrollCode" class="pr-24px">
|
||||
<NInput v-model:value="model.enrollCode" placeholder="请输入招生代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业类型" label-width="auto" path="majorType" class="pr-24px">
|
||||
<NSelect v-model:value="model.majorType" placeholder="请选择专业类型" :options="[]" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业类别子级" label-width="auto" path="majorTypeSub" class="pr-24px">
|
||||
<NInput v-model:value="model.majorTypeSub" placeholder="请输入专业类别子级" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="主考科目" label-width="auto" path="mainExamSubject" class="pr-24px">
|
||||
<NInput v-model:value="model.mainExamSubject" placeholder="请输入主考科目" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="年份" label-width="auto" path="year" class="pr-24px">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="科类(文/理)" label-width="auto" path="subjectType" class="pr-24px">
|
||||
<NSelect v-model:value="model.subjectType" placeholder="请选择科类(文/理)" :options="[]" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="批次" label-width="auto" path="batchName" class="pr-24px">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="录取方式(文*x+专*y)"
|
||||
label-width="auto"
|
||||
path="admissionFormula"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionFormula" placeholder="请输入录取方式(文*x+专*y)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="录取概率规则运算符"
|
||||
label-width="auto"
|
||||
path="probabilityOperator"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.probabilityOperator" placeholder="请输入录取概率规则运算符" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="省控线" label-width="auto" path="controlScore" class="pr-24px">
|
||||
<NInput v-model:value="model.controlScore" placeholder="请输入省控线" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="录取线" label-width="auto" path="admissionScore" class="pr-24px">
|
||||
<NInput v-model:value="model.admissionScore" placeholder="请输入录取线" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="招生人数" label-width="auto" path="planEnroll" class="pr-24px">
|
||||
<NInput v-model:value="model.planEnroll" placeholder="请输入招生人数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="实际投档人数" label-width="auto" path="filedAmount" class="pr-24px">
|
||||
<NInput v-model:value="model.filedAmount" placeholder="请输入实际投档人数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="录取数" label-width="auto" path="admitAmount" class="pr-24px">
|
||||
<NInput v-model:value="model.admitAmount" placeholder="请输入录取数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="一志愿录取数"
|
||||
label-width="auto"
|
||||
path="firstChoiceAdmitAmount"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.firstChoiceAdmitAmount" placeholder="请输入一志愿录取数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="最低分数差" label-width="auto" path="minScoreDiff" class="pr-24px">
|
||||
<NInput v-model:value="model.minScoreDiff" placeholder="请输入最低分数差" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学费(元/年)" label-width="auto" path="tuitionFee" class="pr-24px">
|
||||
<NInput v-model:value="model.tuitionFee" placeholder="请输入学费(元/年)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,580 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider, NInput, NSpace } from 'naive-ui';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import {
|
||||
fetchBatchDeleteSchoolRecruitMajor,
|
||||
fetchCreateSchoolRecruitMajor,
|
||||
fetchGetSchoolRecruitMajorList,
|
||||
fetchUpdateSchoolRecruitMajor
|
||||
} from '@/service/api/art/school-recruit-major';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import InlineExpandTextarea from '@/components/custom/inline-expand-textarea.vue';
|
||||
import SchoolRecruitMajorSearch from './modules/school-recruit-major-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorList'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolRecruitMajorSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: null,
|
||||
schoolCode: null,
|
||||
schoolName: null,
|
||||
year: null,
|
||||
majorId: null,
|
||||
majorCode: null,
|
||||
majorName: null,
|
||||
enrollCode: null,
|
||||
dataStatus: null,
|
||||
batchName: null,
|
||||
majorType: null,
|
||||
majorTypeSub: null,
|
||||
subjectType: null,
|
||||
admissionWayShort: null,
|
||||
admissionWayExternal: null,
|
||||
admissionWayExternalOp: null,
|
||||
admissionWayInternal: null,
|
||||
admissionWayInternalOp: null,
|
||||
planEnroll: null,
|
||||
mainExamSubject: null,
|
||||
schoolingYears: null,
|
||||
enrollLimitDesc: null,
|
||||
tuitionFee: null,
|
||||
cultureScoreLimit: null,
|
||||
majorScoreLimit: null,
|
||||
chineseScoreLimit: null,
|
||||
englishScoreLimit: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
type TableRow = Api.Art.SchoolRecruitMajor & { tempKey?: string };
|
||||
type Model = Api.Art.SchoolRecruitMajorOperateParams;
|
||||
type EditableField = Extract<keyof Model, keyof TableRow>;
|
||||
|
||||
const editingModel = ref<Model>(createDefaultModel());
|
||||
const editingSnapshot = ref<TableRow | null>(null);
|
||||
const editingMode = ref<NaiveUI.TableOperateType | null>(null);
|
||||
const editingRowKey = ref<string | null>(null);
|
||||
const savingRowKey = ref<string | null>(null);
|
||||
const tempRow = ref<TableRow | null>(null);
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
const editableColumns: Array<{ key: EditableField; title: string; textarea?: boolean; minWidth?: number }> = [
|
||||
{ key: 'schoolCode', title: '学校代码' },
|
||||
{ key: 'schoolName', title: '学校名称(冗余)', minWidth: 160 },
|
||||
{ key: 'year', title: '年份' },
|
||||
{ key: 'majorId', title: '专业ID' },
|
||||
{ key: 'majorCode', title: '专业代码' },
|
||||
{ key: 'majorName', title: '专业名称', minWidth: 160 },
|
||||
{ key: 'enrollCode', title: '招生代码' },
|
||||
{ key: 'dataStatus', title: '数据状态' },
|
||||
{ key: 'batchName', title: '批次' },
|
||||
{ key: 'majorType', title: '专业类型' },
|
||||
{ key: 'majorTypeSub', title: '二级专业类型' },
|
||||
{ key: 'subjectType', title: '科类(文/理)' },
|
||||
{ key: 'admissionWayShort', title: '录取方式缩写' },
|
||||
{ key: 'admissionWayExternal', title: '对外录取方式' },
|
||||
{ key: 'admissionWayExternalOp', title: '对外录取方式运算符' },
|
||||
{ key: 'admissionWayInternal', title: '内部录取方式' },
|
||||
{ key: 'admissionWayInternalOp', title: '内部录取方式运算符' },
|
||||
{ key: 'planEnroll', title: '计划招生人数' },
|
||||
{ key: 'mainExamSubject', title: '主考科目' },
|
||||
{ key: 'schoolingYears', title: '学制(年)' },
|
||||
{ key: 'enrollLimitDesc', title: '院校限制说明', textarea: true, minWidth: 180 },
|
||||
{ key: 'tuitionFee', title: '学费(元/年)' },
|
||||
{ key: 'cultureScoreLimit', title: '文化分数限制' },
|
||||
{ key: 'majorScoreLimit', title: '专业分数限制' },
|
||||
{ key: 'chineseScoreLimit', title: '语文成绩限制' },
|
||||
{ key: 'englishScoreLimit', title: '英语成绩限制' },
|
||||
{ key: 'remark', title: '备注', textarea: true, minWidth: 160 }
|
||||
];
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolRecruitMajorList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => createColumns()
|
||||
});
|
||||
|
||||
const tableData = computed<TableRow[]>(() => {
|
||||
const rows = data.value as TableRow[];
|
||||
return tempRow.value ? [tempRow.value, ...rows] : rows;
|
||||
});
|
||||
|
||||
function createColumns(): NaiveUI.TableColumn<TableRow>[] {
|
||||
return [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'recruitMajorId',
|
||||
title: '院校招录专业ID',
|
||||
align: 'center',
|
||||
minWidth: 150
|
||||
},
|
||||
...editableColumns.map(column => createEditableColumn(column)),
|
||||
createOperateColumn()
|
||||
];
|
||||
}
|
||||
|
||||
function createEditableColumn(column: { key: EditableField; title: string; textarea?: boolean; minWidth?: number }) {
|
||||
return {
|
||||
key: column.key,
|
||||
title: column.title,
|
||||
align: 'center',
|
||||
minWidth: column.minWidth ?? 140,
|
||||
render: (row: TableRow) => renderEditableCell(row, column.key, column)
|
||||
} satisfies NaiveUI.TableColumn<TableRow>;
|
||||
}
|
||||
|
||||
function createOperateColumn(): NaiveUI.TableColumn<TableRow> {
|
||||
return {
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
render: (row: TableRow) => {
|
||||
const rowKey = resolveRowKey(row);
|
||||
const editing = editingRowKey.value === rowKey;
|
||||
const saving = savingRowKey.value === rowKey;
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<NSpace size={8} justify="center">
|
||||
<NButton size="tiny" quaternary disabled={saving} onClick={handleCancelEdit}>
|
||||
{$t('common.cancel')}
|
||||
</NButton>
|
||||
<NButton size="tiny" type="primary" loading={saving} onClick={handleSaveRow}>
|
||||
{$t('common.save')}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
);
|
||||
}
|
||||
|
||||
const showEdit = hasAuth('art:schoolRecruitMajor:edit');
|
||||
const showDelete = hasAuth('art:schoolRecruitMajor:remove');
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{showEdit ? (
|
||||
<NButton size="tiny" text type="primary" onClick={() => handleEditRow(row)}>
|
||||
{$t('common.edit')}
|
||||
</NButton>
|
||||
) : null}
|
||||
{showEdit && showDelete ? <NDivider vertical /> : null}
|
||||
{showDelete ? (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.recruitMajorId)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRowFieldValue(row: TableRow, field: EditableField) {
|
||||
return row[field as keyof TableRow];
|
||||
}
|
||||
|
||||
function formatDisplayValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
return typeof value === 'number' ? value : String(value);
|
||||
}
|
||||
|
||||
function renderEditableCell(row: TableRow, field: EditableField, options?: { textarea?: boolean }) {
|
||||
if (!isEditingRow(row)) {
|
||||
return formatDisplayValue(getRowFieldValue(row, field));
|
||||
}
|
||||
|
||||
const inputValue = editingModel.value[field];
|
||||
const resolvedValue = inputValue === null || inputValue === undefined ? '' : String(inputValue);
|
||||
|
||||
if (options?.textarea) {
|
||||
return renderTextareaEditor(row, field, resolvedValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<NInput
|
||||
size="small"
|
||||
type={options?.textarea ? 'textarea' : 'text'}
|
||||
autosize={options?.textarea ? { minRows: 1, maxRows: 4 } : undefined}
|
||||
value={resolvedValue}
|
||||
onUpdateValue={value => updateEditingField(field, value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTextareaEditor(row: TableRow, field: EditableField, value: string) {
|
||||
return <InlineExpandTextarea value={value} onUpdateValue={val => updateEditingField(field, val)} />;
|
||||
}
|
||||
|
||||
function updateEditingField(field: EditableField, value: string | number | null) {
|
||||
(editingModel.value as Record<string, string | number | null>)[field] = value;
|
||||
}
|
||||
|
||||
function resolveRowKey(row: TableRow) {
|
||||
if (row.recruitMajorId !== null && row.recruitMajorId !== undefined && row.recruitMajorId !== '') {
|
||||
return String(row.recruitMajorId);
|
||||
}
|
||||
return row.tempKey ?? '';
|
||||
}
|
||||
|
||||
function isEditingRow(row: TableRow) {
|
||||
return editingRowKey.value !== null && editingRowKey.value === resolveRowKey(row);
|
||||
}
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
recruitMajorId: null,
|
||||
schoolId: null,
|
||||
schoolCode: '',
|
||||
schoolName: '',
|
||||
year: null,
|
||||
majorId: null,
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
enrollCode: '',
|
||||
dataStatus: '',
|
||||
batchName: '',
|
||||
majorType: '',
|
||||
majorTypeSub: '',
|
||||
subjectType: '',
|
||||
admissionWayShort: '',
|
||||
admissionWayExternal: '',
|
||||
admissionWayExternalOp: '',
|
||||
admissionWayInternal: '',
|
||||
admissionWayInternalOp: '',
|
||||
planEnroll: null,
|
||||
mainExamSubject: '',
|
||||
schoolingYears: null,
|
||||
enrollLimitDesc: '',
|
||||
tuitionFee: null,
|
||||
cultureScoreLimit: null,
|
||||
majorScoreLimit: null,
|
||||
chineseScoreLimit: null,
|
||||
englishScoreLimit: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
function createTempKey() {
|
||||
return `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function ensureEditingGuard(action: () => void | Promise<void>) {
|
||||
const executeAction = () => Promise.resolve(action());
|
||||
|
||||
if (!editingRowKey.value) {
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
if (!window.$dialog) {
|
||||
resetEditingState();
|
||||
return executeAction();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '当前行尚未保存,确定放弃修改吗?',
|
||||
positiveText: '放弃',
|
||||
negativeText: '继续编辑',
|
||||
onPositiveClick: async () => {
|
||||
resetEditingState();
|
||||
try {
|
||||
await executeAction();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
onNegativeClick: () => resolve()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetEditingState() {
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
editingMode.value = null;
|
||||
editingRowKey.value = null;
|
||||
savingRowKey.value = null;
|
||||
tempRow.value = null;
|
||||
}
|
||||
|
||||
async function handleAddRow() {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'add';
|
||||
editingModel.value = createDefaultModel();
|
||||
editingSnapshot.value = null;
|
||||
const key = createTempKey();
|
||||
tempRow.value = { ...(editingModel.value as TableRow), tempKey: key };
|
||||
editingRowKey.value = key;
|
||||
checkedRowKeys.value = [];
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEditRow(row: TableRow) {
|
||||
await ensureEditingGuard(() => {
|
||||
editingMode.value = 'edit';
|
||||
tempRow.value = null;
|
||||
editingRowKey.value = resolveRowKey(row);
|
||||
editingSnapshot.value = jsonClone(row);
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(row));
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
resetEditingState();
|
||||
}
|
||||
|
||||
const requiredFields: Array<{ key: EditableField; label: string }> = [
|
||||
{ key: 'schoolCode', label: '学校代码' },
|
||||
{ key: 'schoolName', label: '学校名称' },
|
||||
{ key: 'year', label: '年份' },
|
||||
{ key: 'majorCode', label: '专业代码' },
|
||||
{ key: 'majorName', label: '专业名称' },
|
||||
{ key: 'enrollCode', label: '招生代码' },
|
||||
{ key: 'dataStatus', label: '数据状态' }
|
||||
];
|
||||
|
||||
function validateModel() {
|
||||
for (const item of requiredFields) {
|
||||
const value = editingModel.value[item.key];
|
||||
if (value === null || value === undefined || value === '') {
|
||||
window.$message?.warning(`${item.label}不能为空`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSaveRow() {
|
||||
if (!editingMode.value) return;
|
||||
if (!validateModel()) return;
|
||||
|
||||
const {
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
} = editingModel.value;
|
||||
|
||||
savingRowKey.value = editingRowKey.value;
|
||||
|
||||
const requestResult =
|
||||
editingMode.value === 'add'
|
||||
? await fetchCreateSchoolRecruitMajor({
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
})
|
||||
: await fetchUpdateSchoolRecruitMajor({
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
});
|
||||
|
||||
savingRowKey.value = null;
|
||||
|
||||
if (requestResult.error) {
|
||||
window.$message?.error(requestResult.error.message || '保存失败');
|
||||
if (editingMode.value === 'edit' && editingSnapshot.value) {
|
||||
editingModel.value = Object.assign(createDefaultModel(), jsonClone(editingSnapshot.value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
resetEditingState();
|
||||
await getData();
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteSchoolRecruitMajor(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
checkedRowKeys.value = [];
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(recruitMajorId: CommonType.IdType) {
|
||||
await ensureEditingGuard(async () => {
|
||||
const { error } = await fetchBatchDeleteSchoolRecruitMajor([recruitMajorId]);
|
||||
if (error) return;
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
await getData();
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolRecruitMajor/export', searchParams.value, `院校招录专业_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
await ensureEditingGuard(async () => {
|
||||
await getDataByPage();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
await ensureEditingGuard(getData);
|
||||
}
|
||||
|
||||
function rowClassName(row: TableRow) {
|
||||
return isEditingRow(row) ? 'inline-edit-row' : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<SchoolRecruitMajorSearch v-model:model="searchParams" @search="handleSearch" />
|
||||
<NCard title="院校招录专业列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolRecruitMajor:add')"
|
||||
:show-delete="hasAuth('art:schoolRecruitMajor:remove')"
|
||||
:show-export="hasAuth('art:schoolRecruitMajor:export')"
|
||||
@add="handleAddRow"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="resolveRowKey"
|
||||
:row-class-name="rowClassName"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.inline-edit-row .n-data-table-td) {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,353 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolRecruitMajor, fetchUpdateSchoolRecruitMajor } from '@/service/api/art/school-recruit-major';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolRecruitMajor | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增院校招录专业',
|
||||
edit: '编辑院校招录专业'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolRecruitMajorOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
recruitMajorId: null,
|
||||
schoolId: null,
|
||||
schoolCode: '',
|
||||
schoolName: '',
|
||||
year: null,
|
||||
majorId: null,
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
enrollCode: '',
|
||||
dataStatus: '',
|
||||
batchName: '',
|
||||
majorType: '',
|
||||
majorTypeSub: '',
|
||||
subjectType: '',
|
||||
admissionWayShort: '',
|
||||
admissionWayExternal: '',
|
||||
admissionWayExternalOp: '',
|
||||
admissionWayInternal: '',
|
||||
admissionWayInternalOp: '',
|
||||
planEnroll: null,
|
||||
mainExamSubject: '',
|
||||
schoolingYears: null,
|
||||
enrollLimitDesc: '',
|
||||
tuitionFee: null,
|
||||
cultureScoreLimit: null,
|
||||
majorScoreLimit: null,
|
||||
chineseScoreLimit: null,
|
||||
englishScoreLimit: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<
|
||||
keyof Model,
|
||||
| 'recruitMajorId'
|
||||
| 'tenantId'
|
||||
| 'delFlag'
|
||||
| 'schoolId'
|
||||
| 'schoolCode'
|
||||
| 'schoolName'
|
||||
| 'year'
|
||||
| 'majorCode'
|
||||
| 'majorName'
|
||||
| 'enrollCode'
|
||||
| 'dataStatus'
|
||||
>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
recruitMajorId: createRequiredRule('院校招录专业ID不能为空'),
|
||||
tenantId: createRequiredRule('租户编号不能为空'),
|
||||
delFlag: createRequiredRule('删除标志(0存在 1删除)不能为空'),
|
||||
schoolId: createRequiredRule('学校ID不能为空'),
|
||||
schoolCode: createRequiredRule('学校代码不能为空'),
|
||||
schoolName: createRequiredRule('学校名称(冗余)不能为空'),
|
||||
year: createRequiredRule('年份不能为空'),
|
||||
majorCode: createRequiredRule('专业代码不能为空'),
|
||||
majorName: createRequiredRule('专业名称不能为空'),
|
||||
enrollCode: createRequiredRule('招生代码(为空则存空串)不能为空'),
|
||||
dataStatus: createRequiredRule('数据状态(停招/新招/新增)不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
} = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolRecruitMajor({
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolRecruitMajor({
|
||||
recruitMajorId,
|
||||
schoolId,
|
||||
schoolCode,
|
||||
schoolName,
|
||||
year,
|
||||
majorId,
|
||||
majorCode,
|
||||
majorName,
|
||||
enrollCode,
|
||||
dataStatus,
|
||||
batchName,
|
||||
majorType,
|
||||
majorTypeSub,
|
||||
subjectType,
|
||||
admissionWayShort,
|
||||
admissionWayExternal,
|
||||
admissionWayExternalOp,
|
||||
admissionWayInternal,
|
||||
admissionWayInternalOp,
|
||||
planEnroll,
|
||||
mainExamSubject,
|
||||
schoolingYears,
|
||||
enrollLimitDesc,
|
||||
tuitionFee,
|
||||
cultureScoreLimit,
|
||||
majorScoreLimit,
|
||||
chineseScoreLimit,
|
||||
englishScoreLimit,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
getTreeList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="学校ID" path="schoolId">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学校代码" path="schoolCode">
|
||||
<NInput v-model:value="model.schoolCode" placeholder="请输入学校代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学校名称(冗余)" path="schoolName">
|
||||
<NInput v-model:value="model.schoolName" placeholder="请输入学校名称(冗余)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="年份" path="year">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业ID" path="majorId">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业代码" path="majorCode">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业代码" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业名称" path="majorName">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="招生代码(为空则存空串)" path="enrollCode">
|
||||
<NInput v-model:value="model.enrollCode" placeholder="请输入招生代码(为空则存空串)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="数据状态(停招/新招/新增)" path="dataStatus">
|
||||
<NRadioGroup v-model:value="model.dataStatus">
|
||||
<NSpace>
|
||||
<NRadio value="0" label="请选择字典生成" />
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem label="批次" path="batchName">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业类型" path="majorType">
|
||||
<NSelect
|
||||
v-model:value="model.majorType"
|
||||
placeholder="请选择专业类型"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="二级专业类型" path="majorTypeSub">
|
||||
<NInput v-model:value="model.majorTypeSub" placeholder="请输入二级专业类型" />
|
||||
</NFormItem>
|
||||
<NFormItem label="科类(文/理)" path="subjectType">
|
||||
<NSelect
|
||||
v-model:value="model.subjectType"
|
||||
placeholder="请选择科类(文/理)"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="录取方式缩写" path="admissionWayShort">
|
||||
<NInput v-model:value="model.admissionWayShort" placeholder="请输入录取方式缩写" />
|
||||
</NFormItem>
|
||||
<NFormItem label="对外录取方式" path="admissionWayExternal">
|
||||
<NInput v-model:value="model.admissionWayExternal" placeholder="请输入对外录取方式" />
|
||||
</NFormItem>
|
||||
<NFormItem label="对外录取方式运算符" path="admissionWayExternalOp">
|
||||
<NInput v-model:value="model.admissionWayExternalOp" placeholder="请输入对外录取方式运算符" />
|
||||
</NFormItem>
|
||||
<NFormItem label="内部录取方式" path="admissionWayInternal">
|
||||
<NInput v-model:value="model.admissionWayInternal" placeholder="请输入内部录取方式" />
|
||||
</NFormItem>
|
||||
<NFormItem label="内部录取方式运算符" path="admissionWayInternalOp">
|
||||
<NInput v-model:value="model.admissionWayInternalOp" placeholder="请输入内部录取方式运算符" />
|
||||
</NFormItem>
|
||||
<NFormItem label="计划招生人数" path="planEnroll">
|
||||
<NInput v-model:value="model.planEnroll" placeholder="请输入计划招生人数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="主考科目" path="mainExamSubject">
|
||||
<NInput v-model:value="model.mainExamSubject" placeholder="请输入主考科目" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学制(年)" path="schoolingYears">
|
||||
<NInput v-model:value="model.schoolingYears" placeholder="请输入学制(年)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="院校限制说明" path="enrollLimitDesc">
|
||||
<NInput v-model:value="model.enrollLimitDesc" :rows="3" type="textarea" placeholder="请输入院校限制说明" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学费(元/年)" path="tuitionFee">
|
||||
<NInput v-model:value="model.tuitionFee" placeholder="请输入学费(元/年)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="文化分数限制" path="cultureScoreLimit">
|
||||
<NInput v-model:value="model.cultureScoreLimit" placeholder="请输入文化分数限制" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业分数限制" path="majorScoreLimit">
|
||||
<NInput v-model:value="model.majorScoreLimit" placeholder="请输入专业分数限制" />
|
||||
</NFormItem>
|
||||
<NFormItem label="语文成绩限制" path="chineseScoreLimit">
|
||||
<NInput v-model:value="model.chineseScoreLimit" placeholder="请输入语文成绩限制" />
|
||||
</NFormItem>
|
||||
<NFormItem label="英语成绩限制" path="englishScoreLimit">
|
||||
<NInput v-model:value="model.englishScoreLimit" placeholder="请输入英语成绩限制" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolRecruitMajorSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolRecruitMajorSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-recruit-major-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校代码" label-width="auto" path="schoolCode" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolCode" placeholder="请输入学校代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校名称(冗余)" label-width="auto" path="schoolName" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolName" placeholder="请输入学校名称(冗余)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="年份" label-width="auto" path="year" class="pr-24px">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业ID" label-width="auto" path="majorId" class="pr-24px">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业代码" label-width="auto" path="majorCode" class="pr-24px">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业名称" label-width="auto" path="majorName" class="pr-24px">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="招生代码(为空则存空串)"
|
||||
label-width="auto"
|
||||
path="enrollCode"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.enrollCode" placeholder="请输入招生代码(为空则存空串)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="数据状态(停招/新招/新增)"
|
||||
label-width="auto"
|
||||
path="dataStatus"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.dataStatus"
|
||||
placeholder="请选择数据状态(停招/新招/新增)"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="批次" label-width="auto" path="batchName" class="pr-24px">
|
||||
<NInput v-model:value="model.batchName" placeholder="请输入批次" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业类型" label-width="auto" path="majorType" class="pr-24px">
|
||||
<NSelect v-model:value="model.majorType" placeholder="请选择专业类型" :options="[]" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="二级专业类型" label-width="auto" path="majorTypeSub" class="pr-24px">
|
||||
<NInput v-model:value="model.majorTypeSub" placeholder="请输入二级专业类型" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="科类(文/理)" label-width="auto" path="subjectType" class="pr-24px">
|
||||
<NSelect v-model:value="model.subjectType" placeholder="请选择科类(文/理)" :options="[]" clearable />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="录取方式缩写"
|
||||
label-width="auto"
|
||||
path="admissionWayShort"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionWayShort" placeholder="请输入录取方式缩写" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="对外录取方式"
|
||||
label-width="auto"
|
||||
path="admissionWayExternal"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionWayExternal" placeholder="请输入对外录取方式" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="对外录取方式运算符"
|
||||
label-width="auto"
|
||||
path="admissionWayExternalOp"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionWayExternalOp" placeholder="请输入对外录取方式运算符" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="内部录取方式"
|
||||
label-width="auto"
|
||||
path="admissionWayInternal"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionWayInternal" placeholder="请输入内部录取方式" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="内部录取方式运算符"
|
||||
label-width="auto"
|
||||
path="admissionWayInternalOp"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.admissionWayInternalOp" placeholder="请输入内部录取方式运算符" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="计划招生人数" label-width="auto" path="planEnroll" class="pr-24px">
|
||||
<NInput v-model:value="model.planEnroll" placeholder="请输入计划招生人数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="主考科目" label-width="auto" path="mainExamSubject" class="pr-24px">
|
||||
<NInput v-model:value="model.mainExamSubject" placeholder="请输入主考科目" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学制(年)" label-width="auto" path="schoolingYears" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolingYears" placeholder="请输入学制(年)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="院校限制说明"
|
||||
label-width="auto"
|
||||
path="enrollLimitDesc"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.enrollLimitDesc" placeholder="请输入院校限制说明" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学费(元/年)" label-width="auto" path="tuitionFee" class="pr-24px">
|
||||
<NInput v-model:value="model.tuitionFee" placeholder="请输入学费(元/年)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="文化分数限制"
|
||||
label-width="auto"
|
||||
path="cultureScoreLimit"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.cultureScoreLimit" placeholder="请输入文化分数限制" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="专业分数限制"
|
||||
label-width="auto"
|
||||
path="majorScoreLimit"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.majorScoreLimit" placeholder="请输入专业分数限制" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="语文成绩限制"
|
||||
label-width="auto"
|
||||
path="chineseScoreLimit"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.chineseScoreLimit" placeholder="请输入语文成绩限制" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="英语成绩限制"
|
||||
label-width="auto"
|
||||
path="englishScoreLimit"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.englishScoreLimit" placeholder="请输入英语成绩限制" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,350 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { ref } from 'vue';
|
||||
import { NButton, NDivider, NPopover } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchool, fetchGetSchoolList } from '@/service/api/art/school';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolOperateDrawer from './modules/school-operate-drawer.vue';
|
||||
import SchoolImportModal from './modules/school-import-modal.vue';
|
||||
import SchoolSearch from './modules/school-search.vue';
|
||||
import SchoolSubTableModal from './modules/school-sub-table-modal.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolList'
|
||||
});
|
||||
|
||||
type SchoolSubModuleType =
|
||||
| 'schoolName'
|
||||
| 'schoolCampus'
|
||||
| 'schoolCollege'
|
||||
| 'schoolMajor'
|
||||
| 'schoolEnrollPlan'
|
||||
| 'schoolDorm'
|
||||
| 'schoolMedia'
|
||||
| 'schoolDetailJson';
|
||||
|
||||
type SchoolSubModuleButton = {
|
||||
key: SchoolSubModuleType;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
const subTableModalVisible = ref(false);
|
||||
const currentSchoolId = ref<CommonType.IdType | null>(null);
|
||||
const currentSchoolName = ref('');
|
||||
const currentSchoolData = ref<Api.Art.School | null>(null);
|
||||
const currentSubModule = ref<SchoolSubModuleType>('schoolName');
|
||||
|
||||
const subModuleButtons: SchoolSubModuleButton[] = [
|
||||
{ key: 'schoolName', label: '名称管理' },
|
||||
{ key: 'schoolCampus', label: '校区管理' },
|
||||
{ key: 'schoolCollege', label: '学院管理' },
|
||||
{ key: 'schoolMajor', label: '专业管理' },
|
||||
{ key: 'schoolEnrollPlan', label: '招生计划' },
|
||||
{ key: 'schoolDorm', label: '宿舍管理' },
|
||||
{ key: 'schoolMedia', label: '媒体管理' },
|
||||
{ key: 'schoolDetailJson', label: '详情JSONB' }
|
||||
];
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
mainCode: null,
|
||||
mainName: null,
|
||||
shortName: null,
|
||||
province: null,
|
||||
city: null,
|
||||
district: null,
|
||||
universityType: null,
|
||||
educationLevel: null,
|
||||
schoolNature: null,
|
||||
supervisorDept: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '学校主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'mainCode',
|
||||
title: '学校编码(国标代码)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'mainName',
|
||||
title: '学校主名称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'shortName',
|
||||
title: '学校简称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'province',
|
||||
title: '省份',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'city',
|
||||
title: '城市',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'district',
|
||||
title: '区县',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'universityType',
|
||||
title: '大学类型',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'educationLevel',
|
||||
title: '学历层次',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolNature',
|
||||
title: '办学性质',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'supervisorDept',
|
||||
title: '主管部门',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 220,
|
||||
render: row => {
|
||||
const renderDivider = () => {
|
||||
if (!hasAuth('art:school:edit') || !hasAuth('art:school:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:school:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.schoolId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:school:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.schoolId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const subTableButton = () => {
|
||||
return (
|
||||
<NPopover trigger="click" placement="bottom-end">
|
||||
{{
|
||||
trigger: () => (
|
||||
<NButton size="tiny" tertiary type="primary" class="rounded-14px px-10px">
|
||||
子表管理
|
||||
</NButton>
|
||||
),
|
||||
default: () => (
|
||||
<div class="grid grid-cols-3 w-276px gap-8px lt-sm:grid-cols-2 lt-sm:w-188px">
|
||||
{subModuleButtons.map(item => (
|
||||
<NButton
|
||||
key={item.key}
|
||||
size="tiny"
|
||||
quaternary
|
||||
type="primary"
|
||||
class="justify-center"
|
||||
onClick={() => openSubModule(row, item.key)}
|
||||
>
|
||||
{item.label}
|
||||
</NButton>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</NPopover>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{renderDivider()}
|
||||
{deleteBtn()}
|
||||
{subTableButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'schoolId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchool(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(schoolId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchool([schoolId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(schoolId: CommonType.IdType) {
|
||||
handleEdit(schoolId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/school/export', searchParams.value, `学校基础信息主表_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
|
||||
function openSubModule(row: Api.Art.School, moduleType: SchoolSubModuleType) {
|
||||
currentSchoolId.value = row.schoolId;
|
||||
currentSchoolName.value = row.mainName || row.shortName || String(row.schoolId);
|
||||
currentSchoolData.value = row;
|
||||
currentSubModule.value = moduleType;
|
||||
subTableModalVisible.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<SchoolSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校基础信息主表列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:school:add')"
|
||||
:show-delete="hasAuth('art:school:remove')"
|
||||
:show-export="hasAuth('art:school:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.schoolId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
<SchoolSubTableModal
|
||||
v-model:visible="subTableModalVisible"
|
||||
:school-id="currentSchoolId"
|
||||
:school-name="currentSchoolName"
|
||||
:school-data="currentSchoolData"
|
||||
:active-module="currentSubModule"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolCampus, fetchGetSchoolCampusList } from '@/service/api/art/school-campus';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolCampusOperateDrawer from './modules/school-campus-operate-drawer.vue';
|
||||
import SchoolCampusSearch from './modules/school-campus-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCampusList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolCampusSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
campusName: null,
|
||||
location: null,
|
||||
address: null,
|
||||
lng: null,
|
||||
lat: null,
|
||||
introduction: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolCampusSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolCampusList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'campusId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '学校ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'campusName',
|
||||
title: '校区名称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'location',
|
||||
title: '校区位置(文本)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'address',
|
||||
title: '校区地址',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'lng',
|
||||
title: '经度',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'lat',
|
||||
title: '纬度',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'introduction',
|
||||
title: '校区介绍',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolCampus:edit') || !hasAuth('art:schoolCampus:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolCampus:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.campusId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolCampus:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.campusId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'campusId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolCampus(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(campusId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolCampus([campusId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(campusId: CommonType.IdType) {
|
||||
handleEdit(campusId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolCampus/export', requestParams.value, `学校校区_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolCampusSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校校区列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolCampus:add')"
|
||||
:show-delete="hasAuth('art:schoolCampus:remove')"
|
||||
:show-export="hasAuth('art:schoolCampus:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.campusId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolCampusOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolCampus, fetchUpdateSchoolCampus } from '@/service/api/art/school-campus';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCampusOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolCampus | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校校区',
|
||||
edit: '编辑学校校区'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolCampusOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
campusId: null,
|
||||
schoolId: null,
|
||||
campusName: '',
|
||||
location: '',
|
||||
address: '',
|
||||
lng: null,
|
||||
lat: null,
|
||||
introduction: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'campusId' | 'schoolId' | 'campusName'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
campusId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('学校ID不能为空'),
|
||||
campusName: createRequiredRule('校区名称不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { campusId, schoolId, campusName, location, address, lng, lat, introduction, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolCampus({
|
||||
schoolId,
|
||||
campusName,
|
||||
location,
|
||||
address,
|
||||
lng,
|
||||
lat,
|
||||
introduction,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolCampus({
|
||||
campusId,
|
||||
schoolId,
|
||||
campusName,
|
||||
location,
|
||||
address,
|
||||
lng,
|
||||
lat,
|
||||
introduction,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="学校ID" path="schoolId">
|
||||
<NInput
|
||||
v-model:value="model.schoolId"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请输入学校ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="校区名称" path="campusName">
|
||||
<NInput v-model:value="model.campusName" placeholder="请输入校区名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="校区位置(文本)" path="location">
|
||||
<NInput v-model:value="model.location" placeholder="请输入校区位置(文本)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="校区地址" path="address">
|
||||
<NInput v-model:value="model.address" placeholder="请输入校区地址" />
|
||||
</NFormItem>
|
||||
<NFormItem label="经度" path="lng">
|
||||
<NInput v-model:value="model.lng" placeholder="请输入经度" />
|
||||
</NFormItem>
|
||||
<NFormItem label="纬度" path="lat">
|
||||
<NInput v-model:value="model.lat" placeholder="请输入纬度" />
|
||||
</NFormItem>
|
||||
<NFormItem label="校区介绍" path="introduction">
|
||||
<NInput v-model:value="model.introduction" :rows="3" type="textarea" placeholder="请输入校区介绍" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCampusSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolCampusSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-campus-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="校区名称" label-width="auto" path="campusName" class="pr-24px">
|
||||
<NInput v-model:value="model.campusName" placeholder="请输入校区名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="校区位置(文本)" label-width="auto" path="location" class="pr-24px">
|
||||
<NInput v-model:value="model.location" placeholder="请输入校区位置(文本)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="校区地址" label-width="auto" path="address" class="pr-24px">
|
||||
<NInput v-model:value="model.address" placeholder="请输入校区地址" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="经度" label-width="auto" path="lng" class="pr-24px">
|
||||
<NInput v-model:value="model.lng" placeholder="请输入经度" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="纬度" label-width="auto" path="lat" class="pr-24px">
|
||||
<NInput v-model:value="model.lat" placeholder="请输入纬度" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="校区介绍" label-width="auto" path="introduction" class="pr-24px">
|
||||
<NInput v-model:value="model.introduction" placeholder="请输入校区介绍" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolCollege, fetchGetSchoolCollegeList } from '@/service/api/art/school-college';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolCollegeOperateDrawer from './modules/school-college-operate-drawer.vue';
|
||||
import SchoolCollegeSearch from './modules/school-college-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCollegeList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolCollegeSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
collegeCode: null,
|
||||
collegeName: null,
|
||||
introduction: null,
|
||||
sortNo: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolCollegeSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolCollegeList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'collegeId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '学校ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'collegeCode',
|
||||
title: '学院编码(可选)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'collegeName',
|
||||
title: '学院名称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'introduction',
|
||||
title: '学院介绍',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'sortNo',
|
||||
title: '排序',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolCollege:edit') || !hasAuth('art:schoolCollege:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolCollege:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.collegeId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolCollege:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.collegeId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'collegeId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolCollege(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(collegeId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolCollege([collegeId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(collegeId: CommonType.IdType) {
|
||||
handleEdit(collegeId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolCollege/export', requestParams.value, `学校学院_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolCollegeSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校学院列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolCollege:add')"
|
||||
:show-delete="hasAuth('art:schoolCollege:remove')"
|
||||
:show-export="hasAuth('art:schoolCollege:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.collegeId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolCollegeOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolCollege, fetchUpdateSchoolCollege } from '@/service/api/art/school-college';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCollegeOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolCollege | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校学院',
|
||||
edit: '编辑学校学院'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolCollegeOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
collegeId: null,
|
||||
schoolId: null,
|
||||
collegeCode: '',
|
||||
collegeName: '',
|
||||
introduction: '',
|
||||
sortNo: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'collegeId' | 'schoolId' | 'collegeName'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
collegeId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('学校ID不能为空'),
|
||||
collegeName: createRequiredRule('学院名称不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { collegeId, schoolId, collegeCode, collegeName, introduction, sortNo, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolCollege({
|
||||
schoolId,
|
||||
collegeCode,
|
||||
collegeName,
|
||||
introduction,
|
||||
sortNo,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolCollege({
|
||||
collegeId,
|
||||
schoolId,
|
||||
collegeCode,
|
||||
collegeName,
|
||||
introduction,
|
||||
sortNo,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="学校ID" path="schoolId">
|
||||
<NInput
|
||||
v-model:value="model.schoolId"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请输入学校ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="学院编码(可选)" path="collegeCode">
|
||||
<NInput v-model:value="model.collegeCode" placeholder="请输入学院编码(可选)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学院名称" path="collegeName">
|
||||
<NInput v-model:value="model.collegeName" placeholder="请输入学院名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学院介绍" path="introduction">
|
||||
<NInput v-model:value="model.introduction" :rows="3" type="textarea" placeholder="请输入学院介绍" />
|
||||
</NFormItem>
|
||||
<NFormItem label="排序" path="sortNo">
|
||||
<NInput v-model:value="model.sortNo" placeholder="请输入排序" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolCollegeSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolCollegeSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-college-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学院编码(可选)"
|
||||
label-width="auto"
|
||||
path="collegeCode"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.collegeCode" placeholder="请输入学院编码(可选)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学院名称" label-width="auto" path="collegeName" class="pr-24px">
|
||||
<NInput v-model:value="model.collegeName" placeholder="请输入学院名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学院介绍" label-width="auto" path="introduction" class="pr-24px">
|
||||
<NInput v-model:value="model.introduction" placeholder="请输入学院介绍" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="排序" label-width="auto" path="sortNo" class="pr-24px">
|
||||
<NInput v-model:value="model.sortNo" placeholder="请输入排序" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,860 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
NCode,
|
||||
NDynamicInput,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NGi,
|
||||
NGrid,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NTabPane,
|
||||
NTabs
|
||||
} from 'naive-ui';
|
||||
import {
|
||||
fetchGetSchoolDetailJsonAll,
|
||||
fetchUpdateSchoolDetailAccommodationJson,
|
||||
fetchUpdateSchoolDetailEmploymentReportJson,
|
||||
fetchUpdateSchoolDetailPhotoJson,
|
||||
fetchUpdateSchoolDetailResearchJson,
|
||||
fetchUpdateSchoolDetailSatisfactionJson,
|
||||
fetchUpdateSchoolDetailScholarshipJson,
|
||||
fetchUpdateSchoolDetailSpecialMajorJson,
|
||||
fetchUpdateSchoolDetailSubjectReviewsJson,
|
||||
fetchUpdateSchoolDetailUnivMajorsJson,
|
||||
fetchUpdateSchoolDetailUnivPostgraduateJson
|
||||
} from '@/service/api/art/school-detail';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDetailJson'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
interface SatisfactionDetail {
|
||||
name: string;
|
||||
votes: string;
|
||||
score: string;
|
||||
s1: string;
|
||||
s2: string;
|
||||
s3: string;
|
||||
s4: string;
|
||||
s5: string;
|
||||
}
|
||||
|
||||
interface ScholarshipItem {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SpecialMajorItem {
|
||||
majorId: string;
|
||||
majorName: string;
|
||||
}
|
||||
|
||||
interface SpecialMajorGroup {
|
||||
type: string;
|
||||
majorList: SpecialMajorItem[];
|
||||
}
|
||||
|
||||
interface EmploymentReportItem {
|
||||
reportName: string;
|
||||
onlinePreview: string;
|
||||
reportUrl: string;
|
||||
}
|
||||
|
||||
interface PhotoSceneryItem {
|
||||
cover: string;
|
||||
size: number | null;
|
||||
scenery: string;
|
||||
}
|
||||
|
||||
interface PhotoJson {
|
||||
staticUrl: string;
|
||||
scenerys: PhotoSceneryItem[];
|
||||
}
|
||||
|
||||
interface AccommodationItem {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SubjectReviewItem {
|
||||
subjectName: string;
|
||||
assessLevel: string;
|
||||
assessGrade: string;
|
||||
}
|
||||
|
||||
interface SubjectReviewGroup {
|
||||
type: string;
|
||||
subList: SubjectReviewItem[];
|
||||
}
|
||||
|
||||
interface ResearchBlock {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ResearchJson {
|
||||
features: ResearchBlock;
|
||||
teacher: ResearchBlock;
|
||||
laboratory: ResearchBlock[];
|
||||
subject: ResearchBlock[];
|
||||
program: ResearchBlock[];
|
||||
base: Record<string, string>;
|
||||
}
|
||||
|
||||
interface UnivMajorItem {
|
||||
majorId: string;
|
||||
majorName: string;
|
||||
majorTag: string;
|
||||
year: string;
|
||||
}
|
||||
|
||||
interface UnivMajorGroup {
|
||||
className: string;
|
||||
majorList: UnivMajorItem[];
|
||||
}
|
||||
|
||||
interface UnivPostgraduateItem {
|
||||
year: number | null;
|
||||
exemption: number | null;
|
||||
}
|
||||
|
||||
const schoolId = ref<CommonType.IdType | null>(props.schoolId ?? null);
|
||||
const showJsonPreview = ref(true);
|
||||
const loadingAll = ref(false);
|
||||
const saving = ref({
|
||||
satisfaction: false,
|
||||
scholarship: false,
|
||||
specialMajor: false,
|
||||
employmentReport: false,
|
||||
photo: false,
|
||||
accommodation: false,
|
||||
subjectReviews: false,
|
||||
research: false,
|
||||
univMajors: false,
|
||||
univPostgraduate: false
|
||||
});
|
||||
|
||||
const satisfactionDetails = ref<SatisfactionDetail[]>([]);
|
||||
const scholarshipList = ref<ScholarshipItem[]>([]);
|
||||
const specialMajorList = ref<SpecialMajorGroup[]>([]);
|
||||
const employmentReportList = ref<EmploymentReportItem[]>([]);
|
||||
const photoJson = ref<PhotoJson>({
|
||||
staticUrl: '',
|
||||
scenerys: []
|
||||
});
|
||||
const accommodationList = ref<AccommodationItem[]>([]);
|
||||
const subjectReviewsList = ref<SubjectReviewGroup[]>([]);
|
||||
const researchJson = ref<ResearchJson>({
|
||||
features: { name: '', value: '' },
|
||||
teacher: { name: '', value: '' },
|
||||
laboratory: [],
|
||||
subject: [],
|
||||
program: [],
|
||||
base: {}
|
||||
});
|
||||
const researchBasePairs = ref<Array<{ key: string; value: string }>>([]);
|
||||
const univMajorsList = ref<UnivMajorGroup[]>([]);
|
||||
const univPostgraduateList = ref<UnivPostgraduateItem[]>([]);
|
||||
|
||||
function ensureSchoolId() {
|
||||
if (schoolId.value === null || schoolId.value === undefined || schoolId.value === '') {
|
||||
window.$message?.warning('请先输入学校ID');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizeId(value: CommonType.IdType | null | undefined): CommonType.IdType | null {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
|
||||
if (typeof value === 'number') return value;
|
||||
|
||||
const trimmed = String(value).trim();
|
||||
if (!trimmed) return null;
|
||||
|
||||
const asNumber = Number(trimmed);
|
||||
if (Number.isSafeInteger(asNumber) && String(asNumber) === trimmed) {
|
||||
return asNumber;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function createSatisfactionDetail(): SatisfactionDetail {
|
||||
return { name: '', votes: '', score: '', s1: '', s2: '', s3: '', s4: '', s5: '' };
|
||||
}
|
||||
|
||||
function createScholarshipItem(): ScholarshipItem {
|
||||
return { name: '', value: '' };
|
||||
}
|
||||
|
||||
function createSpecialMajorGroup(): SpecialMajorGroup {
|
||||
return { type: '', majorList: [] };
|
||||
}
|
||||
|
||||
function createSpecialMajorItem(): SpecialMajorItem {
|
||||
return { majorId: '', majorName: '' };
|
||||
}
|
||||
|
||||
function createEmploymentReportItem(): EmploymentReportItem {
|
||||
return { reportName: '', onlinePreview: '', reportUrl: '' };
|
||||
}
|
||||
|
||||
function createPhotoSceneryItem(): PhotoSceneryItem {
|
||||
return { cover: '', size: null, scenery: '' };
|
||||
}
|
||||
|
||||
function createAccommodationItem(): AccommodationItem {
|
||||
return { name: '', value: '' };
|
||||
}
|
||||
|
||||
function createSubjectReviewGroup(): SubjectReviewGroup {
|
||||
return { type: '', subList: [] };
|
||||
}
|
||||
|
||||
function createSubjectReviewItem(): SubjectReviewItem {
|
||||
return { subjectName: '', assessLevel: '', assessGrade: '' };
|
||||
}
|
||||
|
||||
function createResearchBlock(): ResearchBlock {
|
||||
return { name: '', value: '' };
|
||||
}
|
||||
|
||||
function createUnivMajorGroup(): UnivMajorGroup {
|
||||
return { className: '', majorList: [] };
|
||||
}
|
||||
|
||||
function createUnivMajorItem(): UnivMajorItem {
|
||||
return { majorId: '', majorName: '', majorTag: '', year: '' };
|
||||
}
|
||||
|
||||
function createUnivPostgraduateItem(): UnivPostgraduateItem {
|
||||
return { year: null, exemption: null };
|
||||
}
|
||||
|
||||
function syncResearchBasePairs(base: Record<string, string>) {
|
||||
researchBasePairs.value = Object.entries(base || {}).map(([key, value]) => ({ key, value }));
|
||||
}
|
||||
|
||||
function buildResearchBaseObject() {
|
||||
const base: Record<string, string> = {};
|
||||
researchBasePairs.value.forEach(item => {
|
||||
const key = item.key.trim();
|
||||
if (!key) return;
|
||||
base[key] = item.value ?? '';
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
const satisfactionPreview = computed(() =>
|
||||
JSON.stringify({ satisfactionJson: { details: satisfactionDetails.value } }, null, 2)
|
||||
);
|
||||
const scholarshipPreview = computed(
|
||||
() =>
|
||||
JSON.stringify(
|
||||
{
|
||||
scholarshipJson: scholarshipList.value.map(item => ({
|
||||
[item.name || '奖学金']: item.value
|
||||
}))
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
const specialMajorPreview = computed(() => JSON.stringify({ specialMajorJson: specialMajorList.value }, null, 2));
|
||||
const employmentReportPreview = computed(() =>
|
||||
JSON.stringify({ employmentReportJson: employmentReportList.value }, null, 2)
|
||||
);
|
||||
const photoPreview = computed(() => JSON.stringify({ photoJson: photoJson.value }, null, 2));
|
||||
const accommodationPreview = computed(() => JSON.stringify({ accommodationJson: accommodationList.value }, null, 2));
|
||||
const subjectReviewsPreview = computed(() => JSON.stringify({ subjectReviewsJson: subjectReviewsList.value }, null, 2));
|
||||
const researchPreview = computed(() =>
|
||||
JSON.stringify({ researchJson: { ...researchJson.value, base: buildResearchBaseObject() } }, null, 2)
|
||||
);
|
||||
const univMajorsPreview = computed(() => JSON.stringify({ univMajorsJson: univMajorsList.value }, null, 2));
|
||||
const univPostgraduatePreview = computed(() =>
|
||||
JSON.stringify({ univPostgraduateJson: univPostgraduateList.value }, null, 2)
|
||||
);
|
||||
|
||||
async function loadAll() {
|
||||
if (!ensureSchoolId()) return;
|
||||
loadingAll.value = true;
|
||||
const allRes = await fetchGetSchoolDetailJsonAll(schoolId.value as CommonType.IdType);
|
||||
|
||||
if (!allRes.error) {
|
||||
const data = allRes.data;
|
||||
|
||||
satisfactionDetails.value = (data?.satisfactionJson as { details?: SatisfactionDetail[] } | undefined)?.details ?? [];
|
||||
const rawScholarship = (data?.scholarshipJson as Array<Record<string, string>> | undefined) ?? [];
|
||||
scholarshipList.value = rawScholarship.map(item => {
|
||||
const [name = '', value = ''] = Object.entries(item)[0] ?? [];
|
||||
return { name, value };
|
||||
});
|
||||
specialMajorList.value = (data?.specialMajorJson as SpecialMajorGroup[] | undefined) ?? [];
|
||||
employmentReportList.value = (data?.employmentReportJson as EmploymentReportItem[] | undefined) ?? [];
|
||||
photoJson.value =
|
||||
(data?.photoJson as PhotoJson | undefined) ??
|
||||
({
|
||||
staticUrl: '',
|
||||
scenerys: []
|
||||
} as PhotoJson);
|
||||
accommodationList.value = (data?.accommodationJson as AccommodationItem[] | undefined) ?? [];
|
||||
subjectReviewsList.value = (data?.subjectReviewsJson as SubjectReviewGroup[] | undefined) ?? [];
|
||||
|
||||
const research = data?.researchJson as ResearchJson | undefined;
|
||||
if (research) {
|
||||
researchJson.value = {
|
||||
features: research.features ?? { name: '', value: '' },
|
||||
teacher: research.teacher ?? { name: '', value: '' },
|
||||
laboratory: research.laboratory ?? [],
|
||||
subject: research.subject ?? [],
|
||||
program: research.program ?? [],
|
||||
base: research.base ?? {}
|
||||
};
|
||||
syncResearchBasePairs(researchJson.value.base);
|
||||
}
|
||||
|
||||
univMajorsList.value = (data?.univMajorsJson as UnivMajorGroup[] | undefined) ?? [];
|
||||
univPostgraduateList.value = (data?.univPostgraduateJson as UnivPostgraduateItem[] | undefined) ?? [];
|
||||
}
|
||||
|
||||
loadingAll.value = false;
|
||||
}
|
||||
|
||||
async function saveSatisfaction() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.satisfaction = true;
|
||||
const { error } = await fetchUpdateSchoolDetailSatisfactionJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
satisfactionJson: { details: satisfactionDetails.value }
|
||||
});
|
||||
saving.value.satisfaction = false;
|
||||
if (!error) window.$message?.success('满意度明细已保存');
|
||||
}
|
||||
|
||||
async function saveScholarship() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.scholarship = true;
|
||||
const { error } = await fetchUpdateSchoolDetailScholarshipJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
scholarshipJson: scholarshipList.value.map(item => ({
|
||||
[item.name || '奖学金']: item.value
|
||||
}))
|
||||
});
|
||||
saving.value.scholarship = false;
|
||||
if (!error) window.$message?.success('奖学金已保存');
|
||||
}
|
||||
|
||||
async function saveSpecialMajor() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.specialMajor = true;
|
||||
const { error } = await fetchUpdateSchoolDetailSpecialMajorJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
specialMajorJson: specialMajorList.value
|
||||
});
|
||||
saving.value.specialMajor = false;
|
||||
if (!error) window.$message?.success('特色专业已保存');
|
||||
}
|
||||
|
||||
async function saveEmploymentReport() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.employmentReport = true;
|
||||
const { error } = await fetchUpdateSchoolDetailEmploymentReportJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
employmentReportJson: employmentReportList.value
|
||||
});
|
||||
saving.value.employmentReport = false;
|
||||
if (!error) window.$message?.success('就业报告已保存');
|
||||
}
|
||||
|
||||
async function savePhoto() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.photo = true;
|
||||
const { error } = await fetchUpdateSchoolDetailPhotoJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
photoJson: photoJson.value
|
||||
});
|
||||
saving.value.photo = false;
|
||||
if (!error) window.$message?.success('学校图片已保存');
|
||||
}
|
||||
|
||||
async function saveAccommodation() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.accommodation = true;
|
||||
const { error } = await fetchUpdateSchoolDetailAccommodationJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
accommodationJson: accommodationList.value
|
||||
});
|
||||
saving.value.accommodation = false;
|
||||
if (!error) window.$message?.success('建筑配套已保存');
|
||||
}
|
||||
|
||||
async function saveSubjectReviews() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.subjectReviews = true;
|
||||
const { error } = await fetchUpdateSchoolDetailSubjectReviewsJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
subjectReviewsJson: subjectReviewsList.value
|
||||
});
|
||||
saving.value.subjectReviews = false;
|
||||
if (!error) window.$message?.success('学科评估已保存');
|
||||
}
|
||||
|
||||
async function saveResearch() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.research = true;
|
||||
const { error } = await fetchUpdateSchoolDetailResearchJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
researchJson: {
|
||||
...researchJson.value,
|
||||
base: buildResearchBaseObject()
|
||||
}
|
||||
});
|
||||
saving.value.research = false;
|
||||
if (!error) window.$message?.success('科研信息已保存');
|
||||
}
|
||||
|
||||
async function saveUnivMajors() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.univMajors = true;
|
||||
const { error } = await fetchUpdateSchoolDetailUnivMajorsJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
univMajorsJson: univMajorsList.value
|
||||
});
|
||||
saving.value.univMajors = false;
|
||||
if (!error) window.$message?.success('专业标签已保存');
|
||||
}
|
||||
|
||||
async function saveUnivPostgraduate() {
|
||||
if (!ensureSchoolId()) return;
|
||||
saving.value.univPostgraduate = true;
|
||||
const { error } = await fetchUpdateSchoolDetailUnivPostgraduateJson({
|
||||
schoolId: schoolId.value as CommonType.IdType,
|
||||
univPostgraduateJson: univPostgraduateList.value
|
||||
});
|
||||
saving.value.univPostgraduate = false;
|
||||
if (!error) window.$message?.success('保研信息已保存');
|
||||
}
|
||||
|
||||
watch(
|
||||
researchBasePairs,
|
||||
() => {
|
||||
researchJson.value.base = buildResearchBaseObject();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.schoolId,
|
||||
value => {
|
||||
if (value === null || value === undefined) return;
|
||||
schoolId.value = normalizeId(value);
|
||||
if (props.inModal) {
|
||||
loadAll();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<NCard title="院校详情 JSONB 管理" :bordered="false" size="small" class="card-wrapper">
|
||||
<NForm label-placement="left" label-width="80">
|
||||
<NGrid :cols="24" :x-gap="16">
|
||||
<NGi :span="8">
|
||||
<NFormItem label="学校ID">
|
||||
<NInput v-model:value="schoolId" class="w-full" clearable placeholder="请输入学校ID" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="8">
|
||||
<NFormItem label="JSON预览">
|
||||
<NSwitch v-model:value="showJsonPreview" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="8" class="flex items-center">
|
||||
<NButton type="primary" :loading="loadingAll" @click="loadAll">加载全部</NButton>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCard>
|
||||
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NTabs type="line" animated>
|
||||
<NTabPane name="satisfaction" tab="满意度明细">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="satisfactionDetails" :on-create="createSatisfactionDetail">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.name" placeholder="维度名称" class="w-140px" />
|
||||
<NInput v-model:value="value.votes" placeholder="投票数" class="w-110px" />
|
||||
<NInput v-model:value="value.score" placeholder="评分" class="w-90px" />
|
||||
<NInput v-model:value="value.s1" placeholder="S1" class="w-70px" />
|
||||
<NInput v-model:value="value.s2" placeholder="S2" class="w-70px" />
|
||||
<NInput v-model:value="value.s3" placeholder="S3" class="w-70px" />
|
||||
<NInput v-model:value="value.s4" placeholder="S4" class="w-70px" />
|
||||
<NInput v-model:value="value.s5" placeholder="S5" class="w-70px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NSpace>
|
||||
<NButton type="primary" :loading="saving.satisfaction" @click="saveSatisfaction">保存</NButton>
|
||||
</NSpace>
|
||||
<NCode v-if="showJsonPreview" :code="satisfactionPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="scholarship" tab="奖学金">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="scholarshipList" :on-create="createScholarshipItem">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px w-full">
|
||||
<NInput v-model:value="value.name" placeholder="类型(奖学金/助学金等)" class="w-200px" />
|
||||
<NInput v-model:value="value.value" placeholder="说明" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.scholarship" @click="saveScholarship">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="scholarshipPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="special-major" tab="特色专业">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="specialMajorList" :on-create="createSpecialMajorGroup">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-13px text-gray-500">特色类型</span>
|
||||
<NInput v-model:value="value.type" placeholder="类型(国家级/省级等)" class="w-200px" />
|
||||
</div>
|
||||
<NDynamicInput v-model:value="value.majorList" :on-create="createSpecialMajorItem">
|
||||
<template #default="{ value: major }">
|
||||
<div class="rounded-6px border border-#e5e7eb bg-white p-10px">
|
||||
<div class="flex flex-wrap items-center gap-12px">
|
||||
<span class="text-13px text-gray-500">专业ID</span>
|
||||
<NInput v-model:value="major.majorId" placeholder="专业ID" class="w-120px" />
|
||||
<span class="text-13px text-gray-500">专业名称</span>
|
||||
<NInput v-model:value="major.majorName" placeholder="专业名称" class="w-220px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.specialMajor" @click="saveSpecialMajor">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="specialMajorPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="employment-report" tab="就业报告">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="employmentReportList" :on-create="createEmploymentReportItem">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-13px text-gray-500">报告名称</span>
|
||||
<NInput v-model:value="value.reportName" placeholder="报告名称" class="flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-13px text-gray-500">预览地址</span>
|
||||
<NInput v-model:value="value.onlinePreview" placeholder="预览地址" class="flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-8px">
|
||||
<span class="text-13px text-gray-500">附件地址</span>
|
||||
<NInput v-model:value="value.reportUrl" placeholder="附件上传地址" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.employmentReport" @click="saveEmploymentReport">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="employmentReportPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="photo" tab="学校图片">
|
||||
<NSpace vertical :size="12">
|
||||
<NForm label-placement="left" label-width="90">
|
||||
<NFormItem label="静态域名">
|
||||
<NInput v-model:value="photoJson.staticUrl" placeholder="https://..." />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<NDynamicInput v-model:value="photoJson.scenerys" :on-create="createPhotoSceneryItem">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<NGrid :cols="24" :x-gap="10">
|
||||
<NGi :span="4">
|
||||
<NFormItem label="分类名称" label-width="80">
|
||||
<NInput v-model:value="value.scenery" placeholder="分类名称" class="w-160px" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="16">
|
||||
<NFormItem label="图片地址" label-width="80">
|
||||
<NInput v-model:value="value.cover" placeholder="图片地址" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="2">
|
||||
<NFormItem label="排序" label-width="80">
|
||||
<NInputNumber v-model:value="value.size" placeholder="排序" class="w-120px" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.photo" @click="savePhoto">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="photoPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="accommodation" tab="建筑配套">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="accommodationList" :on-create="createAccommodationItem">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.name" placeholder="名称" class="w-160px" />
|
||||
<NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.accommodation" @click="saveAccommodation">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="accommodationPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="subject-reviews" tab="学科评估">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="subjectReviewsList" :on-create="createSubjectReviewGroup">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<NInput v-model:value="value.type" placeholder="评估轮次" class="w-200px" />
|
||||
<NDynamicInput v-model:value="value.subList" :on-create="createSubjectReviewItem">
|
||||
<template #default="{ value: item }">
|
||||
<div class="rounded-6px border border-#e5e7eb bg-white p-10px">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="item.subjectName" placeholder="学科名称" class="w-200px" />
|
||||
<NInput v-model:value="item.assessLevel" placeholder="评级" class="w-120px" />
|
||||
<NInput v-model:value="item.assessGrade" placeholder="档位" class="w-120px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.subjectReviews" @click="saveSubjectReviews">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="subjectReviewsPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="research" tab="科研信息">
|
||||
<NSpace vertical :size="12">
|
||||
<NCard title="区块信息" size="small" :bordered="true">
|
||||
<NGrid :cols="24" :x-gap="16">
|
||||
<NGi :span="12">
|
||||
<NFormItem label="特色名称" label-width="80">
|
||||
<NInput v-model:value="researchJson.features.name" placeholder="名称" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="特色内容" label-width="80">
|
||||
<NInput v-model:value="researchJson.features.value" placeholder="内容" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="师资名称" label-width="80">
|
||||
<NInput v-model:value="researchJson.teacher.name" placeholder="名称" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="师资内容" label-width="80">
|
||||
<NInput v-model:value="researchJson.teacher.value" placeholder="内容" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</NCard>
|
||||
|
||||
<NCard title="实验室" size="small" :bordered="true">
|
||||
<NDynamicInput v-model:value="researchJson.laboratory" :on-create="createResearchBlock">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.name" placeholder="名称" class="w-200px" />
|
||||
<NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NCard>
|
||||
|
||||
<NCard title="学科" size="small" :bordered="true">
|
||||
<NDynamicInput v-model:value="researchJson.subject" :on-create="createResearchBlock">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.name" placeholder="名称" class="w-240px" />
|
||||
<NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NCard>
|
||||
|
||||
<NCard title="项目/培养" size="small" :bordered="true">
|
||||
<NDynamicInput v-model:value="researchJson.program" :on-create="createResearchBlock">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.name" placeholder="名称" class="w-240px" />
|
||||
<NInput v-model:value="value.value" placeholder="内容" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NCard>
|
||||
|
||||
<NCard title="基础数据" size="small" :bordered="true">
|
||||
<NDynamicInput v-model:value="researchBasePairs" :on-create="() => ({ key: '', value: '' })">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px w-full">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="value.key" placeholder="字段" class="w-200px" />
|
||||
<NInput v-model:value="value.value" placeholder="数值" class="flex-1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NCard>
|
||||
|
||||
<NButton type="primary" :loading="saving.research" @click="saveResearch">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="researchPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="univ-majors" tab="专业标签">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="univMajorsList" :on-create="createUnivMajorGroup">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<NInput v-model:value="value.className" placeholder="分类名称" class="w-200px" />
|
||||
<NDynamicInput v-model:value="value.majorList" :on-create="createUnivMajorItem">
|
||||
<template #default="{ value: item }">
|
||||
<div class="rounded-6px border border-#e5e7eb bg-white p-10px">
|
||||
<div class="flex flex-wrap gap-12px">
|
||||
<NInput v-model:value="item.majorId" placeholder="专业ID" class="w-120px" />
|
||||
<NInput v-model:value="item.majorName" placeholder="专业名称" class="w-200px" />
|
||||
<NInput v-model:value="item.majorTag" placeholder="标签" class="w-120px" />
|
||||
<NInput v-model:value="item.year" placeholder="学制" class="w-120px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.univMajors" @click="saveUnivMajors">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="univMajorsPreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
|
||||
<NTabPane name="univ-postgraduate" tab="保研信息">
|
||||
<NSpace vertical :size="12">
|
||||
<NDynamicInput v-model:value="univPostgraduateList" :on-create="createUnivPostgraduateItem">
|
||||
<template #default="{ value }">
|
||||
<div class="rounded-8px border border-#e5e7eb bg-#fafafa p-12px">
|
||||
<NGrid :cols="24" :x-gap="16">
|
||||
<NGi :span="12">
|
||||
<NFormItem label="年份" label-width="80">
|
||||
<NInputNumber v-model:value="value.year" placeholder="年份" class="w-140px" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="保研率" label-width="80">
|
||||
<NInputNumber v-model:value="value.exemption" placeholder="保研率" class="w-160px" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</div>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
<NButton type="primary" :loading="saving.univPostgraduate" @click="saveUnivPostgraduate">保存</NButton>
|
||||
<NCode v-if="showJsonPreview" :code="univPostgraduatePreview" language="json" />
|
||||
</NSpace>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.w-70px {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.w-90px {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.w-110px {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.w-120px {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.w-140px {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.w-160px {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.w-200px {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.w-220px {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.w-240px {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.w-320px {
|
||||
width: 320px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,579 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NDivider, NTag } from 'naive-ui';
|
||||
import { fetchBatchDeleteSchoolDetail, fetchGetSchoolDetailList } from '@/service/api/art/school-detail';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolDetailOperateDrawer from './modules/school-detail-operate-drawer.vue';
|
||||
import SchoolDetailSearch from './modules/school-detail-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDetailList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolDetailSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
detailId: null,
|
||||
schoolId: props.schoolId,
|
||||
introduction: null,
|
||||
schoolIcon: null,
|
||||
backGround: null,
|
||||
address: null,
|
||||
contact: null,
|
||||
email: null,
|
||||
website: null,
|
||||
postcode: null,
|
||||
establishYear: null,
|
||||
campusAreaMu: null,
|
||||
libraryCollection: null,
|
||||
maleRatio: null,
|
||||
femaleRatio: null,
|
||||
is985: null,
|
||||
is211: null,
|
||||
isDoubleFirstClass: null,
|
||||
isKeyUniversity: null,
|
||||
isPublic: null,
|
||||
tags: [],
|
||||
studentCount: null,
|
||||
teacherCount: null,
|
||||
masterPoint: null,
|
||||
doctorPoint: null,
|
||||
keyMajorCount: null,
|
||||
employmentRate: null,
|
||||
satisfactionRate: null,
|
||||
univId: null,
|
||||
masterProportionRate: null,
|
||||
abroadProportionRate: null,
|
||||
hasRegular: null,
|
||||
hasJunior: null,
|
||||
hasMaster: null,
|
||||
isDoubleHighPlan: null,
|
||||
isStrongPlan: null,
|
||||
twsdlRank: null,
|
||||
xyhRank: null,
|
||||
wslRank: null,
|
||||
usdaluRank: null,
|
||||
qsdaluRank: null,
|
||||
combinedScore: null,
|
||||
overallRank: null,
|
||||
envSatisfaction: null,
|
||||
envVote: null,
|
||||
liveSatisfaction: null,
|
||||
liveVote: null,
|
||||
combinedSatisfaction: null,
|
||||
combinedVote: null,
|
||||
teachers: null,
|
||||
scholarship: null,
|
||||
grantDesc: null,
|
||||
canteen: null,
|
||||
dormitory: null,
|
||||
masterExplain: null,
|
||||
doctorExplain: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolDetailSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
function renderBooleanTag(value?: number | null) {
|
||||
if (value === null || value === undefined) return '-';
|
||||
|
||||
return <NTag type={value === 1 ? 'success' : 'default'}>{value === 1 ? '是' : '否'}</NTag>;
|
||||
}
|
||||
|
||||
function renderStringArray(values?: string[] | null) {
|
||||
if (!values?.length) return '-';
|
||||
return values.join(' / ');
|
||||
}
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolDetailList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'detailId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '关联学校主表ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'introduction',
|
||||
title: '学校详细介绍(大文本)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolIcon',
|
||||
title: '院校图标',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'backGround',
|
||||
title: '学校背景图',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'address',
|
||||
title: '学校地址',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'contact',
|
||||
title: '联系电话',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
title: '邮箱',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'website',
|
||||
title: '官网地址',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'postcode',
|
||||
title: '邮编',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'establishYear',
|
||||
title: '建校年份',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'campusAreaMu',
|
||||
title: '占地面积(亩)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'libraryCollection',
|
||||
title: '图书馆藏书量',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'studentCount',
|
||||
title: '学生人数',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'teacherCount',
|
||||
title: '教师人数',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'employmentRate',
|
||||
title: '就业率(%)',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'satisfactionRate',
|
||||
title: '满意度(%)',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'is985',
|
||||
title: '985',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
render: row => renderBooleanTag(row.is985)
|
||||
},
|
||||
{
|
||||
key: 'is211',
|
||||
title: '211',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
render: row => renderBooleanTag(row.is211)
|
||||
},
|
||||
{
|
||||
key: 'isDoubleFirstClass',
|
||||
title: '双一流',
|
||||
align: 'center',
|
||||
minWidth: 90,
|
||||
render: row => renderBooleanTag(row.isDoubleFirstClass)
|
||||
},
|
||||
{
|
||||
key: 'isKeyUniversity',
|
||||
title: '重点大学',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => renderBooleanTag(row.isKeyUniversity)
|
||||
},
|
||||
{
|
||||
key: 'isPublic',
|
||||
title: '是否公办',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => renderBooleanTag(row.isPublic)
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
title: '详情标签',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
render: row => renderStringArray(row.tags)
|
||||
},
|
||||
{
|
||||
key: 'masterProportionRate',
|
||||
title: '考研率(%)',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'abroadProportionRate',
|
||||
title: '出国率(%)',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'hasRegular',
|
||||
title: '普通本科',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => renderBooleanTag(row.hasRegular)
|
||||
},
|
||||
{
|
||||
key: 'hasJunior',
|
||||
title: '专科',
|
||||
align: 'center',
|
||||
minWidth: 90,
|
||||
render: row => renderBooleanTag(row.hasJunior)
|
||||
},
|
||||
{
|
||||
key: 'hasMaster',
|
||||
title: '硕士点',
|
||||
align: 'center',
|
||||
minWidth: 90,
|
||||
render: row => renderBooleanTag(row.hasMaster)
|
||||
},
|
||||
{
|
||||
key: 'isDoubleHighPlan',
|
||||
title: '双高计划',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => renderBooleanTag(row.isDoubleHighPlan)
|
||||
},
|
||||
{
|
||||
key: 'isStrongPlan',
|
||||
title: '强基计划',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
render: row => renderBooleanTag(row.isStrongPlan)
|
||||
},
|
||||
{
|
||||
key: 'twsdlRank',
|
||||
title: '泰晤士排名',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'xyhRank',
|
||||
title: '校友会排名',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'wslRank',
|
||||
title: '武书连排名',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'usdaluRank',
|
||||
title: 'US排名',
|
||||
align: 'center',
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
key: 'qsdaluRank',
|
||||
title: 'QS排名',
|
||||
align: 'center',
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
key: 'combinedScore',
|
||||
title: '综合评分',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'overallRank',
|
||||
title: '综合排名',
|
||||
align: 'center',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'envSatisfaction',
|
||||
title: '环境满意度',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'envVote',
|
||||
title: '环境投票数',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'liveSatisfaction',
|
||||
title: '生活满意度',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'liveVote',
|
||||
title: '生活投票数',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'combinedSatisfaction',
|
||||
title: '综合满意度',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'combinedVote',
|
||||
title: '综合投票数',
|
||||
align: 'center',
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
key: 'teachers',
|
||||
title: '师资力量',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'scholarship',
|
||||
title: '奖学金',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'grantDesc',
|
||||
title: '助学金',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'canteen',
|
||||
title: '食堂',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'dormitory',
|
||||
title: '宿舍',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'masterExplain',
|
||||
title: '硕士点说明',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'doctorExplain',
|
||||
title: '博士点说明',
|
||||
align: 'center',
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolDetail:edit') || !hasAuth('art:schoolDetail:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolDetail:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.detailId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolDetail:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.detailId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'detailId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolDetail(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(detailId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolDetail([detailId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(detailId: CommonType.IdType) {
|
||||
handleEdit(detailId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolDetail/export', requestParams.value, `学校详细信息_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolDetailSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校详细信息列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolDetail:add')"
|
||||
:show-delete="hasAuth('art:schoolDetail:remove')"
|
||||
:show-export="hasAuth('art:schoolDetail:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.detailId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolDetailOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,707 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolDetail, fetchUpdateSchoolDetail } from '@/service/api/art/school-detail';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDetailOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolDetail | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
const submitLoading = ref(false);
|
||||
const detailTagInputValue = ref('');
|
||||
const detailTagList = ref<string[]>([]);
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校详细信息',
|
||||
edit: '编辑学校详细信息'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
const booleanOptions = [
|
||||
{ value: 1, label: '是' },
|
||||
{ value: 0, label: '否' }
|
||||
];
|
||||
|
||||
function toNumberValue(value: CommonType.IdType | null | undefined) {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
|
||||
return typeof value === 'number' ? value : Number(value);
|
||||
}
|
||||
|
||||
type Model = Api.Art.SchoolDetailOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
detailId: null,
|
||||
schoolId: null,
|
||||
introduction: '',
|
||||
schoolIcon: '',
|
||||
backGround: '',
|
||||
address: '',
|
||||
contact: '',
|
||||
email: '',
|
||||
website: '',
|
||||
postcode: '',
|
||||
establishYear: null,
|
||||
campusAreaMu: null,
|
||||
libraryCollection: null,
|
||||
maleRatio: null,
|
||||
femaleRatio: null,
|
||||
is985: null,
|
||||
is211: null,
|
||||
isDoubleFirstClass: null,
|
||||
isKeyUniversity: null,
|
||||
isPublic: null,
|
||||
tags: [],
|
||||
studentCount: null,
|
||||
teacherCount: null,
|
||||
masterPoint: null,
|
||||
doctorPoint: null,
|
||||
keyMajorCount: null,
|
||||
employmentRate: null,
|
||||
satisfactionRate: null,
|
||||
univId: null,
|
||||
masterProportionRate: null,
|
||||
abroadProportionRate: null,
|
||||
hasRegular: null,
|
||||
hasJunior: null,
|
||||
hasMaster: null,
|
||||
isDoubleHighPlan: null,
|
||||
isStrongPlan: null,
|
||||
twsdlRank: null,
|
||||
xyhRank: null,
|
||||
wslRank: null,
|
||||
usdaluRank: null,
|
||||
qsdaluRank: null,
|
||||
combinedScore: null,
|
||||
overallRank: null,
|
||||
envSatisfaction: null,
|
||||
envVote: null,
|
||||
liveSatisfaction: null,
|
||||
liveVote: null,
|
||||
combinedSatisfaction: null,
|
||||
combinedVote: null,
|
||||
teachers: '',
|
||||
scholarship: '',
|
||||
grantDesc: '',
|
||||
canteen: '',
|
||||
dormitory: '',
|
||||
masterExplain: '',
|
||||
doctorExplain: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = 'schoolId' | 'address' | 'contact';
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
schoolId: createRequiredRule('关联学校主表ID不能为空'),
|
||||
address: createRequiredRule('学校地址不能为空'),
|
||||
contact: createRequiredRule('联系电话不能为空')
|
||||
};
|
||||
|
||||
function normalizeStringList(values?: string[] | null) {
|
||||
if (!values?.length) return [];
|
||||
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
values.forEach(value => {
|
||||
const normalizedValue = String(value).trim();
|
||||
if (normalizedValue) {
|
||||
uniqueValues.add(normalizedValue);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(uniqueValues);
|
||||
}
|
||||
|
||||
function isSameStringList(source: string[], target: string[]) {
|
||||
if (source.length !== target.length) return false;
|
||||
return source.every((value, index) => value === target[index]);
|
||||
}
|
||||
|
||||
function syncTagsToModel() {
|
||||
model.value.tags = normalizeStringList(detailTagList.value);
|
||||
}
|
||||
|
||||
function addDetailTags() {
|
||||
const values = detailTagInputValue.value
|
||||
.split(/[,,]/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!values.length) return;
|
||||
|
||||
detailTagList.value = normalizeStringList([...detailTagList.value, ...values]);
|
||||
detailTagInputValue.value = '';
|
||||
}
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
detailTagInputValue.value = '';
|
||||
detailTagList.value = [];
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
const detailData = jsonClone(props.rowData);
|
||||
Object.assign(model.value, detailData);
|
||||
detailTagList.value = normalizeStringList(detailData.tags);
|
||||
}
|
||||
|
||||
syncTagsToModel();
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
submitLoading.value = true;
|
||||
|
||||
const {
|
||||
detailId,
|
||||
schoolId,
|
||||
introduction,
|
||||
schoolIcon,
|
||||
backGround,
|
||||
address,
|
||||
contact,
|
||||
email,
|
||||
website,
|
||||
postcode,
|
||||
establishYear,
|
||||
campusAreaMu,
|
||||
libraryCollection,
|
||||
maleRatio,
|
||||
femaleRatio,
|
||||
is985,
|
||||
is211,
|
||||
isDoubleFirstClass,
|
||||
isKeyUniversity,
|
||||
isPublic,
|
||||
studentCount,
|
||||
teacherCount,
|
||||
masterPoint,
|
||||
doctorPoint,
|
||||
keyMajorCount,
|
||||
employmentRate,
|
||||
satisfactionRate,
|
||||
univId,
|
||||
masterProportionRate,
|
||||
abroadProportionRate,
|
||||
hasRegular,
|
||||
hasJunior,
|
||||
hasMaster,
|
||||
isDoubleHighPlan,
|
||||
isStrongPlan,
|
||||
twsdlRank,
|
||||
xyhRank,
|
||||
wslRank,
|
||||
usdaluRank,
|
||||
qsdaluRank,
|
||||
combinedScore,
|
||||
overallRank,
|
||||
envSatisfaction,
|
||||
envVote,
|
||||
liveSatisfaction,
|
||||
liveVote,
|
||||
combinedSatisfaction,
|
||||
combinedVote,
|
||||
teachers,
|
||||
scholarship,
|
||||
grantDesc,
|
||||
canteen,
|
||||
dormitory,
|
||||
masterExplain,
|
||||
doctorExplain,
|
||||
remark
|
||||
} = model.value;
|
||||
|
||||
const payload: Api.Art.SchoolDetailOperateParams = {
|
||||
detailId,
|
||||
schoolId,
|
||||
introduction,
|
||||
schoolIcon,
|
||||
backGround,
|
||||
address,
|
||||
contact,
|
||||
email,
|
||||
website,
|
||||
postcode,
|
||||
establishYear,
|
||||
campusAreaMu,
|
||||
libraryCollection,
|
||||
maleRatio,
|
||||
femaleRatio,
|
||||
is985,
|
||||
is211,
|
||||
isDoubleFirstClass,
|
||||
isKeyUniversity,
|
||||
isPublic,
|
||||
tags: normalizeStringList(detailTagList.value),
|
||||
studentCount,
|
||||
teacherCount,
|
||||
masterPoint,
|
||||
doctorPoint,
|
||||
keyMajorCount,
|
||||
employmentRate,
|
||||
satisfactionRate,
|
||||
univId,
|
||||
masterProportionRate,
|
||||
abroadProportionRate,
|
||||
hasRegular,
|
||||
hasJunior,
|
||||
hasMaster,
|
||||
isDoubleHighPlan,
|
||||
isStrongPlan,
|
||||
twsdlRank,
|
||||
xyhRank,
|
||||
wslRank,
|
||||
usdaluRank,
|
||||
qsdaluRank,
|
||||
combinedScore,
|
||||
overallRank,
|
||||
envSatisfaction,
|
||||
envVote,
|
||||
liveSatisfaction,
|
||||
liveVote,
|
||||
combinedSatisfaction,
|
||||
combinedVote,
|
||||
teachers,
|
||||
scholarship,
|
||||
grantDesc,
|
||||
canteen,
|
||||
dormitory,
|
||||
masterExplain,
|
||||
doctorExplain,
|
||||
remark
|
||||
};
|
||||
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolDetail(payload);
|
||||
submitLoading.value = false;
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolDetail(payload);
|
||||
submitLoading.value = false;
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success(props.operateType === 'add' ? $t('common.addSuccess') : $t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, show => {
|
||||
if (show) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
detailTagList,
|
||||
tags => {
|
||||
const normalizedTags = normalizeStringList(tags);
|
||||
|
||||
if (!isSameStringList(tags, normalizedTags)) {
|
||||
detailTagList.value = normalizedTags;
|
||||
return;
|
||||
}
|
||||
|
||||
syncTagsToModel();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="980" class="max-w-96%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left">
|
||||
<NGrid :cols="24" :x-gap="16">
|
||||
<NGi :span="8">
|
||||
<NFormItem label="关联学校主表ID" path="schoolId">
|
||||
<NInputNumber
|
||||
:value="toNumberValue(model.schoolId)"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入关联学校主表ID"
|
||||
@update:value="value => (model.schoolId = value)"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="8">
|
||||
<NFormItem label="外部学校ID" path="univId">
|
||||
<NInputNumber v-model:value="model.univId" class="w-full" clearable placeholder="请输入外部学校ID" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="8">
|
||||
<NFormItem label="邮编" path="postcode">
|
||||
<NInput v-model:value="model.postcode" placeholder="请输入邮编" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="学校详细介绍" path="introduction">
|
||||
<NInput
|
||||
v-model:value="model.introduction"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
placeholder="请输入学校详细介绍(大文本)"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="院校图标" path="schoolIcon">
|
||||
<NInput v-model:value="model.schoolIcon" placeholder="请输入院校图标地址" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="学校背景图" path="backGround">
|
||||
<NInput v-model:value="model.backGround" placeholder="请输入学校背景图地址" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="官网地址" path="website">
|
||||
<NInput v-model:value="model.website" placeholder="请输入官网地址" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="学校地址" path="address">
|
||||
<NInput v-model:value="model.address" placeholder="请输入学校地址" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="联系电话" path="contact">
|
||||
<NInput v-model:value="model.contact" placeholder="请输入联系电话" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="12">
|
||||
<NFormItem label="邮箱" path="email">
|
||||
<NInput v-model:value="model.email" placeholder="请输入邮箱" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="建校年份" path="establishYear">
|
||||
<NInputNumber v-model:value="model.establishYear" class="w-full" clearable placeholder="请输入建校年份" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="占地面积(亩)" path="campusAreaMu">
|
||||
<NInputNumber v-model:value="model.campusAreaMu" class="w-full" clearable placeholder="请输入占地面积" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="图书馆藏书量" path="libraryCollection">
|
||||
<NInputNumber
|
||||
v-model:value="model.libraryCollection"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入图书馆藏书量"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="学生人数" path="studentCount">
|
||||
<NInputNumber v-model:value="model.studentCount" class="w-full" clearable placeholder="请输入学生人数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="教师人数" path="teacherCount">
|
||||
<NInputNumber v-model:value="model.teacherCount" class="w-full" clearable placeholder="请输入教师人数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="硕士点数量" path="masterPoint">
|
||||
<NInputNumber v-model:value="model.masterPoint" class="w-full" clearable placeholder="请输入硕士点数量" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="博士点数量" path="doctorPoint">
|
||||
<NInputNumber v-model:value="model.doctorPoint" class="w-full" clearable placeholder="请输入博士点数量" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="重点专业数" path="keyMajorCount">
|
||||
<NInputNumber v-model:value="model.keyMajorCount" class="w-full" clearable placeholder="请输入重点专业数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="男生比例(%)" path="maleRatio">
|
||||
<NInputNumber v-model:value="model.maleRatio" class="w-full" clearable placeholder="请输入男生比例" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="女生比例(%)" path="femaleRatio">
|
||||
<NInputNumber v-model:value="model.femaleRatio" class="w-full" clearable placeholder="请输入女生比例" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="就业率(%)" path="employmentRate">
|
||||
<NInputNumber v-model:value="model.employmentRate" class="w-full" clearable placeholder="请输入就业率" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="满意度(%)" path="satisfactionRate">
|
||||
<NInputNumber v-model:value="model.satisfactionRate" class="w-full" clearable placeholder="请输入满意度" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="是否985" path="is985">
|
||||
<NSelect v-model:value="model.is985" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="是否211" path="is211">
|
||||
<NSelect v-model:value="model.is211" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="是否双一流" path="isDoubleFirstClass">
|
||||
<NSelect v-model:value="model.isDoubleFirstClass" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="是否重点大学" path="isKeyUniversity">
|
||||
<NSelect v-model:value="model.isKeyUniversity" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="是否公办" path="isPublic">
|
||||
<NSelect v-model:value="model.isPublic" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="考研率(%)" path="masterProportionRate">
|
||||
<NInputNumber
|
||||
v-model:value="model.masterProportionRate"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入考研率"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="出国率(%)" path="abroadProportionRate">
|
||||
<NInputNumber
|
||||
v-model:value="model.abroadProportionRate"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入出国率"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="普通本科" path="hasRegular">
|
||||
<NSelect v-model:value="model.hasRegular" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="专科" path="hasJunior">
|
||||
<NSelect v-model:value="model.hasJunior" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="硕士点" path="hasMaster">
|
||||
<NSelect v-model:value="model.hasMaster" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="双高计划" path="isDoubleHighPlan">
|
||||
<NSelect v-model:value="model.isDoubleHighPlan" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="强基计划" path="isStrongPlan">
|
||||
<NSelect v-model:value="model.isStrongPlan" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="泰晤士排名" path="twsdlRank">
|
||||
<NInputNumber v-model:value="model.twsdlRank" class="w-full" clearable placeholder="请输入泰晤士排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="校友会排名" path="xyhRank">
|
||||
<NInputNumber v-model:value="model.xyhRank" class="w-full" clearable placeholder="请输入校友会排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="武书连排名" path="wslRank">
|
||||
<NInputNumber v-model:value="model.wslRank" class="w-full" clearable placeholder="请输入武书连排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="US排名" path="usdaluRank">
|
||||
<NInputNumber v-model:value="model.usdaluRank" class="w-full" clearable placeholder="请输入US排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="QS排名" path="qsdaluRank">
|
||||
<NInputNumber v-model:value="model.qsdaluRank" class="w-full" clearable placeholder="请输入QS排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="综合评分" path="combinedScore">
|
||||
<NInputNumber v-model:value="model.combinedScore" class="w-full" clearable placeholder="请输入综合评分" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="综合排名" path="overallRank">
|
||||
<NInputNumber v-model:value="model.overallRank" class="w-full" clearable placeholder="请输入综合排名" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="环境满意度" path="envSatisfaction">
|
||||
<NInputNumber
|
||||
v-model:value="model.envSatisfaction"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入环境满意度"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="环境投票数" path="envVote">
|
||||
<NInputNumber v-model:value="model.envVote" class="w-full" clearable placeholder="请输入环境投票数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="生活满意度" path="liveSatisfaction">
|
||||
<NInputNumber
|
||||
v-model:value="model.liveSatisfaction"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入生活满意度"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="生活投票数" path="liveVote">
|
||||
<NInputNumber v-model:value="model.liveVote" class="w-full" clearable placeholder="请输入生活投票数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="综合满意度" path="combinedSatisfaction">
|
||||
<NInputNumber
|
||||
v-model:value="model.combinedSatisfaction"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入综合满意度"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="6">
|
||||
<NFormItem label="综合投票数" path="combinedVote">
|
||||
<NInputNumber v-model:value="model.combinedVote" class="w-full" clearable placeholder="请输入综合投票数" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="详情标签" path="tags">
|
||||
<NSpace vertical class="w-full">
|
||||
<NSpace class="w-full" :wrap-item="false">
|
||||
<NInput
|
||||
v-model:value="detailTagInputValue"
|
||||
class="flex-1"
|
||||
placeholder="输入详情标签后回车或点击确认,可用逗号一次输入多个"
|
||||
@keydown.enter.prevent="addDetailTags"
|
||||
/>
|
||||
<NButton type="primary" ghost @click="addDetailTags">确认</NButton>
|
||||
</NSpace>
|
||||
<NDynamicTags v-model:value="detailTagList" />
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="师资力量" path="teachers">
|
||||
<NInput v-model:value="model.teachers" :rows="3" type="textarea" placeholder="请输入师资力量描述" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="奖学金说明" path="scholarship">
|
||||
<NInput v-model:value="model.scholarship" :rows="3" type="textarea" placeholder="请输入奖学金说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="助学金说明" path="grantDesc">
|
||||
<NInput v-model:value="model.grantDesc" :rows="3" type="textarea" placeholder="请输入助学金说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="食堂说明" path="canteen">
|
||||
<NInput v-model:value="model.canteen" :rows="3" type="textarea" placeholder="请输入食堂说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="宿舍说明" path="dormitory">
|
||||
<NInput v-model:value="model.dormitory" :rows="3" type="textarea" placeholder="请输入宿舍说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="硕士点说明" path="masterExplain">
|
||||
<NInput v-model:value="model.masterExplain" :rows="3" type="textarea" placeholder="请输入硕士点说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="博士点说明" path="doctorExplain">
|
||||
<NInput v-model:value="model.doctorExplain" :rows="3" type="textarea" placeholder="请输入博士点说明" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
<NGi :span="24">
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" :loading="submitLoading" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, toRaw, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDetailSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolDetailSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
const detailTagInputValue = ref('');
|
||||
const detailTagList = ref<string[]>([]);
|
||||
const booleanOptions = [
|
||||
{ value: 1, label: '是' },
|
||||
{ value: 0, label: '否' }
|
||||
];
|
||||
|
||||
function toNumberValue(value: CommonType.IdType | null | undefined) {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
|
||||
return typeof value === 'number' ? value : Number(value);
|
||||
}
|
||||
|
||||
function normalizeStringList(values?: string[] | null) {
|
||||
if (!values?.length) return [];
|
||||
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
values.forEach(value => {
|
||||
const normalizedValue = String(value).trim();
|
||||
if (normalizedValue) {
|
||||
uniqueValues.add(normalizedValue);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(uniqueValues);
|
||||
}
|
||||
|
||||
function isSameStringList(source: string[], target: string[]) {
|
||||
if (source.length !== target.length) return false;
|
||||
return source.every((value, index) => value === target[index]);
|
||||
}
|
||||
|
||||
function syncTagsToModel() {
|
||||
model.value.tags = normalizeStringList(detailTagList.value);
|
||||
}
|
||||
|
||||
function addDetailTags() {
|
||||
const values = detailTagInputValue.value
|
||||
.split(/[,,]/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!values.length) return;
|
||||
|
||||
detailTagList.value = normalizeStringList([...detailTagList.value, ...values]);
|
||||
detailTagInputValue.value = '';
|
||||
}
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, jsonClone(defaultModel));
|
||||
detailTagInputValue.value = '';
|
||||
detailTagList.value = normalizeStringList(defaultModel.tags);
|
||||
syncTagsToModel();
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
watch(
|
||||
detailTagList,
|
||||
tags => {
|
||||
const normalizedTags = normalizeStringList(tags);
|
||||
|
||||
if (!isSameStringList(tags, normalizedTags)) {
|
||||
detailTagList.value = normalizedTags;
|
||||
return;
|
||||
}
|
||||
|
||||
syncTagsToModel();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => model.value.tags,
|
||||
tags => {
|
||||
const normalizedTags = normalizeStringList(tags);
|
||||
|
||||
if (!isSameStringList(detailTagList.value, normalizedTags)) {
|
||||
detailTagList.value = normalizedTags;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-detail-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="详情ID" label-width="auto" path="detailId" class="pr-24px">
|
||||
<NInputNumber
|
||||
:value="toNumberValue(model.detailId)"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入详情ID"
|
||||
@update:value="value => (model.detailId = value)"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInputNumber
|
||||
:value="toNumberValue(model.schoolId)"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入学校ID"
|
||||
@update:value="value => (model.schoolId = value)"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校地址" label-width="auto" path="address" class="pr-24px">
|
||||
<NInput v-model:value="model.address" placeholder="请输入学校地址" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="联系电话" label-width="auto" path="contact" class="pr-24px">
|
||||
<NInput v-model:value="model.contact" placeholder="请输入联系电话" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="官网地址" label-width="auto" path="website" class="pr-24px">
|
||||
<NInput v-model:value="model.website" placeholder="请输入官网地址" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="院校图标" label-width="auto" path="schoolIcon" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolIcon" placeholder="请输入院校图标地址" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="邮编" label-width="auto" path="postcode" class="pr-24px">
|
||||
<NInput v-model:value="model.postcode" placeholder="请输入邮编" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="外部学校ID" label-width="auto" path="univId" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.univId" class="w-full" clearable placeholder="请输入外部学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="建校年份" label-width="auto" path="establishYear" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.establishYear" class="w-full" clearable placeholder="请输入建校年份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="占地面积(亩)" label-width="auto" path="campusAreaMu" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.campusAreaMu" class="w-full" clearable placeholder="请输入占地面积" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="图书馆藏书量"
|
||||
label-width="auto"
|
||||
path="libraryCollection"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInputNumber
|
||||
v-model:value="model.libraryCollection"
|
||||
class="w-full"
|
||||
clearable
|
||||
placeholder="请输入图书馆藏书量"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学生人数" label-width="auto" path="studentCount" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.studentCount" class="w-full" clearable placeholder="请输入学生人数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="教师人数" label-width="auto" path="teacherCount" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.teacherCount" class="w-full" clearable placeholder="请输入教师人数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="硕士点数量" label-width="auto" path="masterPoint" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.masterPoint" class="w-full" clearable placeholder="请输入硕士点数量" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="博士点数量" label-width="auto" path="doctorPoint" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.doctorPoint" class="w-full" clearable placeholder="请输入博士点数量" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="重点专业数" label-width="auto" path="keyMajorCount" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.keyMajorCount" class="w-full" clearable placeholder="请输入重点专业数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="就业率(%)" label-width="auto" path="employmentRate" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.employmentRate" class="w-full" clearable placeholder="请输入就业率" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="满意度(%)" label-width="auto" path="satisfactionRate" class="pr-24px">
|
||||
<NInputNumber v-model:value="model.satisfactionRate" class="w-full" clearable placeholder="请输入满意度" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="是否985" label-width="auto" path="is985" class="pr-24px">
|
||||
<NSelect v-model:value="model.is985" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="是否211" label-width="auto" path="is211" class="pr-24px">
|
||||
<NSelect v-model:value="model.is211" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="是否双一流"
|
||||
label-width="auto"
|
||||
path="isDoubleFirstClass"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect v-model:value="model.isDoubleFirstClass" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="是否重点大学"
|
||||
label-width="auto"
|
||||
path="isKeyUniversity"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect v-model:value="model.isKeyUniversity" :options="booleanOptions" clearable placeholder="请选择" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24" label="详情标签" label-width="auto" path="tags" class="pr-24px">
|
||||
<NSpace vertical class="w-full">
|
||||
<NSpace class="w-full" :wrap-item="false">
|
||||
<NInput
|
||||
v-model:value="detailTagInputValue"
|
||||
class="flex-1"
|
||||
placeholder="输入详情标签后回车或点击确认,可用逗号一次输入多个"
|
||||
@keydown.enter.prevent="addDetailTags"
|
||||
/>
|
||||
<NButton type="primary" ghost @click="addDetailTags">确认</NButton>
|
||||
</NSpace>
|
||||
<NDynamicTags v-model:value="detailTagList" />
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolDorm, fetchGetSchoolDormList } from '@/service/api/art/school-dorm';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolDormOperateDrawer from './modules/school-dorm-operate-drawer.vue';
|
||||
import SchoolDormSearch from './modules/school-dorm-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDormList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolDormSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
campusId: null,
|
||||
roomSize: null,
|
||||
bunkBedDesk: null,
|
||||
privateBath: null,
|
||||
tags: null,
|
||||
description: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolDormSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
params: {
|
||||
...searchParams.value.params,
|
||||
schoolId: props.schoolId ?? undefined
|
||||
}
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolDormList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'dormId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'campusId',
|
||||
title: '校区ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'roomSize',
|
||||
title: '几人间(4/6/8...)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'bunkBedDesk',
|
||||
title: '是否上床下桌(0否1是)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'privateBath',
|
||||
title: '是否独立卫浴(0否1是)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
title: '宿舍标签(冗余文本:空调/热水/洗衣房...)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
title: '补充说明',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolDorm:edit') || !hasAuth('art:schoolDorm:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolDorm:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.dormId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolDorm:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.dormId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'dormId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolDorm(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(dormId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolDorm([dormId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(dormId: CommonType.IdType) {
|
||||
handleEdit(dormId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolDorm/export', requestParams.value, `校区宿舍条件_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolDormSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="校区宿舍条件列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolDorm:add')"
|
||||
:show-delete="hasAuth('art:schoolDorm:remove')"
|
||||
:show-export="hasAuth('art:schoolDorm:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.dormId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolDormOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolDorm, fetchUpdateSchoolDorm } from '@/service/api/art/school-dorm';
|
||||
import { fetchGetSchoolCampusList } from '@/service/api/art/school-campus';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDormOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolDorm | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const campusOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
const campusLoading = ref(false);
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增校区宿舍条件',
|
||||
edit: '编辑校区宿舍条件'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolDormOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
dormId: null,
|
||||
campusId: null,
|
||||
roomSize: null,
|
||||
bunkBedDesk: null,
|
||||
privateBath: null,
|
||||
tags: '',
|
||||
description: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'dormId' | 'campusId'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
dormId: createRequiredRule('主键ID不能为空'),
|
||||
campusId: createRequiredRule('校区不能为空')
|
||||
};
|
||||
|
||||
async function getCampusOptions() {
|
||||
campusLoading.value = true;
|
||||
const { data, error } = await fetchGetSchoolCampusList({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
schoolId: props.defaultSchoolId,
|
||||
params: {}
|
||||
});
|
||||
if (!error) {
|
||||
campusOptions.value = data.rows.map(item => ({
|
||||
value: item.campusId,
|
||||
label: item.campusName
|
||||
}));
|
||||
} else {
|
||||
campusOptions.value = [];
|
||||
}
|
||||
campusLoading.value = false;
|
||||
}
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { dormId, campusId, roomSize, bunkBedDesk, privateBath, tags, description, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolDorm({
|
||||
campusId,
|
||||
roomSize,
|
||||
bunkBedDesk,
|
||||
privateBath,
|
||||
tags,
|
||||
description,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolDorm({
|
||||
dormId,
|
||||
campusId,
|
||||
roomSize,
|
||||
bunkBedDesk,
|
||||
privateBath,
|
||||
tags,
|
||||
description,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
getCampusOptions();
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="校区ID" path="campusId">
|
||||
<NSelect
|
||||
v-model:value="model.campusId"
|
||||
:loading="campusLoading"
|
||||
:options="campusOptions"
|
||||
placeholder="请选择校区"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="几人间(4/6/8...)" path="roomSize">
|
||||
<NInput v-model:value="model.roomSize" placeholder="请输入几人间(4/6/8...)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="是否上床下桌(0否1是)" path="bunkBedDesk">
|
||||
<NInput v-model:value="model.bunkBedDesk" placeholder="请输入是否上床下桌(0否1是)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="是否独立卫浴(0否1是)" path="privateBath">
|
||||
<NInput v-model:value="model.privateBath" placeholder="请输入是否独立卫浴(0否1是)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="宿舍标签(冗余文本:空调/热水/洗衣房...)" path="tags">
|
||||
<NInput v-model:value="model.tags" placeholder="请输入宿舍标签(冗余文本:空调/热水/洗衣房...)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="补充说明" path="description">
|
||||
<NInput v-model:value="model.description" :rows="3" type="textarea" placeholder="请输入补充说明" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolDormSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolDormSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-dorm-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="校区ID" label-width="auto" path="campusId" class="pr-24px">
|
||||
<NInput v-model:value="model.campusId" placeholder="请输入校区ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="几人间(4/6/8...)" label-width="auto" path="roomSize" class="pr-24px">
|
||||
<NInput v-model:value="model.roomSize" placeholder="请输入几人间(4/6/8...)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="是否上床下桌(0否1是)"
|
||||
label-width="auto"
|
||||
path="bunkBedDesk"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.bunkBedDesk" placeholder="请输入是否上床下桌(0否1是)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="是否独立卫浴(0否1是)"
|
||||
label-width="auto"
|
||||
path="privateBath"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.privateBath" placeholder="请输入是否独立卫浴(0否1是)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="宿舍标签(冗余文本:空调/热水/洗衣房...)"
|
||||
label-width="auto"
|
||||
path="tags"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.tags" placeholder="请输入宿舍标签(冗余文本:空调/热水/洗衣房...)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="补充说明" label-width="auto" path="description" class="pr-24px">
|
||||
<NInput v-model:value="model.description" placeholder="请输入补充说明" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolEnrollPlan, fetchGetSchoolEnrollPlanList } from '@/service/api/art/school-enroll-plan';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolEnrollPlanOperateDrawer from './modules/school-enroll-plan-operate-drawer.vue';
|
||||
import SchoolEnrollPlanSearch from './modules/school-enroll-plan-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolEnrollPlanList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolEnrollPlanSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
year: null,
|
||||
province: null,
|
||||
subjectType: null,
|
||||
majorId: null,
|
||||
majorName: null,
|
||||
educationLevel: null,
|
||||
planNum: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolEnrollPlanSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolEnrollPlanList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'planId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '学校ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'year',
|
||||
title: '年份',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'province',
|
||||
title: '招生省份',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'subjectType',
|
||||
title: '分科:文/理/综(或物理/历史...)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorId',
|
||||
title: '专业ID(可选,有则填)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorName',
|
||||
title: '专业名称(冗余,没专业ID也能落库)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'educationLevel',
|
||||
title: '学历层次:本科/专科',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'planNum',
|
||||
title: '计划数',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolEnrollPlan:edit') || !hasAuth('art:schoolEnrollPlan:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolEnrollPlan:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.planId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolEnrollPlan:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.planId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'planId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolEnrollPlan(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(planId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolEnrollPlan([planId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(planId: CommonType.IdType) {
|
||||
handleEdit(planId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolEnrollPlan/export', requestParams.value, `学校招生计划_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolEnrollPlanSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校招生计划列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolEnrollPlan:add')"
|
||||
:show-delete="hasAuth('art:schoolEnrollPlan:remove')"
|
||||
:show-export="hasAuth('art:schoolEnrollPlan:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.planId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolEnrollPlanOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolEnrollPlan, fetchUpdateSchoolEnrollPlan } from '@/service/api/art/school-enroll-plan';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolEnrollPlanOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolEnrollPlan | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校招生计划',
|
||||
edit: '编辑学校招生计划'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolEnrollPlanOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
planId: null,
|
||||
schoolId: null,
|
||||
year: null,
|
||||
province: '',
|
||||
subjectType: '',
|
||||
majorId: null,
|
||||
majorName: '',
|
||||
educationLevel: '',
|
||||
planNum: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'planId' | 'schoolId' | 'year' | 'province' | 'majorName' | 'planNum'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
planId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('学校ID不能为空'),
|
||||
year: createRequiredRule('年份不能为空'),
|
||||
province: createRequiredRule('招生省份不能为空'),
|
||||
majorName: createRequiredRule('专业名称(冗余,没专业ID也能落库)不能为空'),
|
||||
planNum: createRequiredRule('计划数不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { planId, schoolId, year, province, subjectType, majorId, majorName, educationLevel, planNum, remark } =
|
||||
model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolEnrollPlan({
|
||||
schoolId,
|
||||
year,
|
||||
province,
|
||||
subjectType,
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
planNum,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolEnrollPlan({
|
||||
planId,
|
||||
schoolId,
|
||||
year,
|
||||
province,
|
||||
subjectType,
|
||||
majorId,
|
||||
majorName,
|
||||
educationLevel,
|
||||
planNum,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="学校ID" path="schoolId">
|
||||
<NInput
|
||||
v-model:value="model.schoolId"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请输入学校ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="年份" path="year">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItem>
|
||||
<NFormItem label="招生省份" path="province">
|
||||
<NInput v-model:value="model.province" placeholder="请输入招生省份" />
|
||||
</NFormItem>
|
||||
<NFormItem label="分科:文/理/综(或物理/历史...)" path="subjectType">
|
||||
<NSelect
|
||||
v-model:value="model.subjectType"
|
||||
placeholder="请选择分科:文/理/综(或物理/历史...)"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="专业ID(可选,有则填)" path="majorId">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID(可选,有则填)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业名称(冗余,没专业ID也能落库)" path="majorName">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称(冗余,没专业ID也能落库)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学历层次:本科/专科" path="educationLevel">
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次:本科/专科" />
|
||||
</NFormItem>
|
||||
<NFormItem label="计划数" path="planNum">
|
||||
<NInput v-model:value="model.planNum" placeholder="请输入计划数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolEnrollPlanSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolEnrollPlanSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-enroll-plan-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入学校ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="年份" label-width="auto" path="year" class="pr-24px">
|
||||
<NInput v-model:value="model.year" placeholder="请输入年份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="招生省份" label-width="auto" path="province" class="pr-24px">
|
||||
<NInput v-model:value="model.province" placeholder="请输入招生省份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="分科:文/理/综(或物理/历史...)"
|
||||
label-width="auto"
|
||||
path="subjectType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.subjectType"
|
||||
placeholder="请选择分科:文/理/综(或物理/历史...)"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="专业ID(可选,有则填)"
|
||||
label-width="auto"
|
||||
path="majorId"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID(可选,有则填)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="专业名称(冗余,没专业ID也能落库)"
|
||||
label-width="auto"
|
||||
path="majorName"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称(冗余,没专业ID也能落库)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学历层次:本科/专科"
|
||||
label-width="auto"
|
||||
path="educationLevel"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次:本科/专科" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="计划数" label-width="auto" path="planNum" class="pr-24px">
|
||||
<NInput v-model:value="model.planNum" placeholder="请输入计划数" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
import { fetchImportPreviewSchool, fetchImportSchoolData } from '@/service/api/art/school';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolImportModal'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
const { download } = useDownload();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
const previewLoading = ref(false);
|
||||
const importLoading = ref(false);
|
||||
const replaceAll = ref(false);
|
||||
const selectedReplaceMainCodes = ref<string[]>([]);
|
||||
const previewResult = ref<Api.Art.SchoolImportPreviewResult | null>(null);
|
||||
const importResult = ref<Api.Art.SchoolImportExecuteResult | null>(null);
|
||||
|
||||
const conflictDetails = computed(() => {
|
||||
return previewResult.value?.details?.filter(item => item.status === 'CONFLICT') ?? [];
|
||||
});
|
||||
|
||||
const invalidDetails = computed(() => {
|
||||
return previewResult.value?.details?.filter(item => item.status === 'INVALID') ?? [];
|
||||
});
|
||||
|
||||
function getCurrentFile() {
|
||||
const currentFile = fileList.value[0]?.file;
|
||||
if (!currentFile) {
|
||||
window.$message?.warning('请先选择导入文件');
|
||||
return null;
|
||||
}
|
||||
return currentFile as File;
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
fileList.value = [];
|
||||
replaceAll.value = false;
|
||||
selectedReplaceMainCodes.value = [];
|
||||
previewResult.value = null;
|
||||
importResult.value = null;
|
||||
}
|
||||
|
||||
async function handlePreview() {
|
||||
const file = getCurrentFile();
|
||||
if (!file) return;
|
||||
|
||||
previewLoading.value = true;
|
||||
const { data, error } = await fetchImportPreviewSchool(file);
|
||||
previewLoading.value = false;
|
||||
|
||||
if (error) return;
|
||||
|
||||
previewResult.value = data;
|
||||
importResult.value = null;
|
||||
replaceAll.value = false;
|
||||
selectedReplaceMainCodes.value = [];
|
||||
window.$message?.success('导入预检完成');
|
||||
}
|
||||
|
||||
async function handleImport() {
|
||||
if (!previewResult.value) {
|
||||
window.$message?.warning('请先执行导入预检');
|
||||
return;
|
||||
}
|
||||
|
||||
const file = getCurrentFile();
|
||||
if (!file) return;
|
||||
|
||||
importLoading.value = true;
|
||||
const { data, error } = await fetchImportSchoolData({
|
||||
file,
|
||||
replaceAll: replaceAll.value,
|
||||
replaceMainCodes: replaceAll.value ? [] : selectedReplaceMainCodes.value
|
||||
});
|
||||
importLoading.value = false;
|
||||
|
||||
if (error) return;
|
||||
|
||||
importResult.value = data;
|
||||
window.$message?.success($t('common.importSuccess'));
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
function handleDownloadTemplate() {
|
||||
download('/art/school/importTemplate', {}, `院校主子表_${$t('common.importTemplate')}_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function getTagType(status: Api.Art.SchoolImportDetail['status']): 'default' | 'success' | 'warning' | 'error' {
|
||||
if (status === 'SUCCESS') return 'success';
|
||||
if (status === 'FAILED' || status === 'INVALID') return 'error';
|
||||
if (status === 'CONFLICT' || status === 'SKIPPED') return 'warning';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
watch(
|
||||
() => fileList.value,
|
||||
() => {
|
||||
previewResult.value = null;
|
||||
importResult.value = null;
|
||||
replaceAll.value = false;
|
||||
selectedReplaceMainCodes.value = [];
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, show => {
|
||||
if (show) {
|
||||
resetState();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="visible"
|
||||
:title="$t('common.import')"
|
||||
preset="card"
|
||||
:bordered="false"
|
||||
display-directive="show"
|
||||
class="max-w-92% w-760px"
|
||||
>
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<NUpload
|
||||
v-model:file-list="fileList"
|
||||
:max="1"
|
||||
:file-size="50"
|
||||
accept=".xls,.xlsx"
|
||||
:multiple="false"
|
||||
directory-dnd
|
||||
:default-upload="false"
|
||||
list-type="text"
|
||||
>
|
||||
<NUploadDragger>
|
||||
<div class="mb-12px flex-center">
|
||||
<SvgIcon icon="material-symbols:unarchive-outline" class="text-58px color-#d8d8db dark:color-#a1a1a2" />
|
||||
</div>
|
||||
<NText class="text-16px">{{ $t('common.importTip') }}</NText>
|
||||
<NP depth="3" class="mt-8px text-center">
|
||||
{{ $t('common.importSize') }}
|
||||
<b class="text-red-500">50MB</b>
|
||||
{{ $t('common.importFormat') }}
|
||||
<b class="text-red-500">xls/xlsx</b>
|
||||
{{ $t('common.importEnd') }}
|
||||
</NP>
|
||||
</NUploadDragger>
|
||||
</NUpload>
|
||||
|
||||
<NAlert
|
||||
v-if="previewResult"
|
||||
:title="`预检结果:共 ${previewResult.totalSchoolCount || 0} 所,冲突 ${previewResult.conflictCount || 0},无效 ${previewResult.invalidCount || 0}`"
|
||||
type="info"
|
||||
:bordered="false"
|
||||
/>
|
||||
|
||||
<NCard v-if="previewResult && conflictDetails.length" title="冲突处理" size="small" :bordered="true">
|
||||
<NSpace vertical>
|
||||
<NCheckbox v-model:checked="replaceAll">冲突院校全部替换</NCheckbox>
|
||||
<NCheckboxGroup v-model:value="selectedReplaceMainCodes">
|
||||
<NSpace vertical>
|
||||
<NCheckbox
|
||||
v-for="item in conflictDetails"
|
||||
:key="`${item.mainCode || 'empty'}-${item.mainName || ''}`"
|
||||
:value="item.mainCode || ''"
|
||||
:disabled="replaceAll || !item.mainCode"
|
||||
>
|
||||
{{ `${item.mainCode || '-'} / ${item.mainName || '-'}:${item.message || ''}` }}
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
</NCheckboxGroup>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
|
||||
<NCard v-if="previewResult && invalidDetails.length" title="无效数据" size="small" :bordered="true">
|
||||
<NScrollbar class="max-h-180px">
|
||||
<NSpace vertical>
|
||||
<NTag v-for="(item, index) in invalidDetails" :key="`${item.mainCode}-${index}`" type="error" size="small">
|
||||
{{ `${item.mainCode || '-'} / ${item.mainName || '-'}:${item.message || ''}` }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NScrollbar>
|
||||
</NCard>
|
||||
|
||||
<NCard v-if="importResult" title="导入执行结果" size="small" :bordered="true">
|
||||
<NAlert
|
||||
:title="`总数 ${importResult.totalSchoolCount || 0},成功 ${importResult.successCount || 0},失败 ${importResult.failCount || 0},跳过 ${importResult.skippedCount || 0}`"
|
||||
:bordered="false"
|
||||
type="success"
|
||||
/>
|
||||
<NScrollbar class="mt-10px max-h-220px">
|
||||
<NSpace vertical>
|
||||
<NTag
|
||||
v-for="(item, index) in importResult.details || []"
|
||||
:key="`${item.mainCode}-${index}`"
|
||||
:type="getTagType(item.status)"
|
||||
size="small"
|
||||
>
|
||||
{{ `${item.status || '-'} | ${item.mainCode || '-'} / ${item.mainName || '-'}:${item.message || ''}` }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NScrollbar>
|
||||
</NCard>
|
||||
</div>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="handleDownloadTemplate">{{ $t('common.downloadTemplate') }}</NButton>
|
||||
<NButton :loading="previewLoading" @click="handlePreview">导入预检</NButton>
|
||||
<NButton type="primary" :loading="importLoading" @click="handleImport">{{ $t('common.import') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NDivider } from 'naive-ui';
|
||||
import { fetchBatchDeleteSchoolMajorTag, fetchGetSchoolMajorTagList } from '@/service/api/art/school-major-tag';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolMajorTagOperateDrawer from './modules/school-major-tag-operate-drawer.vue';
|
||||
import SchoolMajorTagSearch from './modules/school-major-tag-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorTagList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolMajorTagSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
majorId: null,
|
||||
tagName: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolMajorTagSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
params: {
|
||||
...searchParams.value.params,
|
||||
schoolId: props.schoolId ?? undefined
|
||||
}
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolMajorTagList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'majorTagId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorId',
|
||||
title: '专业ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'tagName',
|
||||
title: '标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolMajorTag:edit') || !hasAuth('art:schoolMajorTag:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolMajorTag:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.majorTagId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolMajorTag:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.majorTagId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'majorTagId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMajorTag(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(majorTagId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMajorTag([majorTagId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(majorTagId: CommonType.IdType) {
|
||||
handleEdit(majorTagId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolMajorTag/export', requestParams.value, `专业标签_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolMajorTagSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="专业标签列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolMajorTag:add')"
|
||||
:show-delete="hasAuth('art:schoolMajorTag:remove')"
|
||||
:show-export="hasAuth('art:schoolMajorTag:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.majorTagId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolMajorTagOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolMajorTag, fetchUpdateSchoolMajorTag } from '@/service/api/art/school-major-tag';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorTagOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolMajorTag | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增专业标签',
|
||||
edit: '编辑专业标签'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolMajorTagOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
majorTagId: null,
|
||||
majorId: null,
|
||||
tagName: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'majorTagId' | 'majorId' | 'tagName'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
majorTagId: createRequiredRule('主键ID不能为空'),
|
||||
majorId: createRequiredRule('专业ID不能为空'),
|
||||
tagName: createRequiredRule('标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { majorTagId, majorId, tagName, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolMajorTag({ majorId, tagName, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolMajorTag({ majorTagId, majorId, tagName, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="专业ID" path="majorId">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)" path="tagName">
|
||||
<NInput
|
||||
v-model:value="model.tagName"
|
||||
placeholder="请输入标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorTagSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolMajorTagSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-major-tag-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业ID" label-width="auto" path="majorId" class="pr-24px">
|
||||
<NInput v-model:value="model.majorId" placeholder="请输入专业ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)"
|
||||
label-width="auto"
|
||||
path="tagName"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="model.tagName"
|
||||
placeholder="请输入标签名称(如:双一流学科/国家级特色专业/艺术类重点专业...)"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolMajor, fetchGetSchoolMajorList } from '@/service/api/art/school-major';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolMajorOperateDrawer from './modules/school-major-operate-drawer.vue';
|
||||
import SchoolMajorSearch from './modules/school-major-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolMajorSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
collegeId: null,
|
||||
majorCode: null,
|
||||
majorName: null,
|
||||
educationLevel: null,
|
||||
durationYears: null,
|
||||
majorCategory: null,
|
||||
degreeType: null,
|
||||
introduction: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolMajorSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolMajorList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'majorId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '学校ID(冗余便于查)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'collegeId',
|
||||
title: '学院ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorCode',
|
||||
title: '专业编码(可选)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorName',
|
||||
title: '专业名称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'educationLevel',
|
||||
title: '学历层次:本科/专科',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'durationYears',
|
||||
title: '学制(3/4/5)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'majorCategory',
|
||||
title: '专业类别:工学/理学/艺术学...',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'degreeType',
|
||||
title: '学位类型:工学学士/理学学士/艺术学学士...',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'introduction',
|
||||
title: '专业介绍',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolMajor:edit') || !hasAuth('art:schoolMajor:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolMajor:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.majorId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolMajor:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.majorId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'majorId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMajor(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(majorId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMajor([majorId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(majorId: CommonType.IdType) {
|
||||
handleEdit(majorId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolMajor/export', requestParams.value, `学校专业_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolMajorSearch v-model:model="searchParams" :fixed-school-id="props.schoolId" @search="getDataByPage" />
|
||||
<NCard title="学校专业列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolMajor:add')"
|
||||
:show-delete="hasAuth('art:schoolMajor:remove')"
|
||||
:show-export="hasAuth('art:schoolMajor:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.majorId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolMajorOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchGetSchoolCollegeList } from '@/service/api/art/school-college';
|
||||
import { fetchGetSchoolList } from '@/service/api/art/school';
|
||||
import { fetchCreateSchoolMajor, fetchUpdateSchoolMajor } from '@/service/api/art/school-major';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolMajor | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const schoolOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
const schoolLoading = ref(false);
|
||||
const collegeOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
const collegeLoading = ref(false);
|
||||
const tagInputValue = ref('');
|
||||
const tagList = ref<string[]>([]);
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校专业',
|
||||
edit: '编辑学校专业'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolMajorOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
majorId: null,
|
||||
schoolId: null,
|
||||
collegeId: null,
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
educationLevel: '',
|
||||
durationYears: null,
|
||||
majorCategory: '',
|
||||
degreeType: '',
|
||||
introduction: '',
|
||||
majorTags: [],
|
||||
tags: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'majorId' | 'schoolId' | 'collegeId' | 'majorName'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
majorId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('院校不能为空'),
|
||||
collegeId: createRequiredRule('学院不能为空'),
|
||||
majorName: createRequiredRule('专业名称不能为空')
|
||||
};
|
||||
|
||||
async function getSchoolOptions() {
|
||||
schoolLoading.value = true;
|
||||
const { data, error } = await fetchGetSchoolList({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
params: {}
|
||||
});
|
||||
if (!error) {
|
||||
schoolOptions.value = data.rows.map(item => ({
|
||||
value: item.schoolId,
|
||||
label: `${item.mainName || item.shortName || '未命名学校'} (${item.schoolId})`
|
||||
}));
|
||||
} else {
|
||||
schoolOptions.value = [];
|
||||
}
|
||||
schoolLoading.value = false;
|
||||
}
|
||||
|
||||
async function getCollegeOptions(schoolId?: CommonType.IdType | null) {
|
||||
if (!schoolId) {
|
||||
collegeOptions.value = [];
|
||||
return;
|
||||
}
|
||||
collegeLoading.value = true;
|
||||
const { data, error } = await fetchGetSchoolCollegeList({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
schoolId,
|
||||
params: {}
|
||||
});
|
||||
if (!error) {
|
||||
collegeOptions.value = data.rows.map(item => ({
|
||||
value: item.collegeId,
|
||||
label: item.collegeName
|
||||
}));
|
||||
} else {
|
||||
collegeOptions.value = [];
|
||||
}
|
||||
collegeLoading.value = false;
|
||||
}
|
||||
|
||||
function normalizeStringList(values?: string[] | null) {
|
||||
if (!values?.length) return [];
|
||||
|
||||
const uniqueValues = new Set<string>();
|
||||
|
||||
values.forEach(value => {
|
||||
const normalizedValue = String(value).trim();
|
||||
if (normalizedValue) {
|
||||
uniqueValues.add(normalizedValue);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(uniqueValues);
|
||||
}
|
||||
|
||||
function parseTagText(tagText?: string | null) {
|
||||
if (!tagText) return [];
|
||||
return tagText
|
||||
.split(/[,,]/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function isSameStringList(source: string[], target: string[]) {
|
||||
if (source.length !== target.length) return false;
|
||||
return source.every((value, index) => value === target[index]);
|
||||
}
|
||||
|
||||
function addTag() {
|
||||
const values = tagInputValue.value
|
||||
.split(/[,,]/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
if (!values.length) return;
|
||||
tagList.value = normalizeStringList([...tagList.value, ...values]);
|
||||
tagInputValue.value = '';
|
||||
}
|
||||
|
||||
function syncTagsToModel() {
|
||||
const normalizedMajorTags = normalizeStringList(tagList.value);
|
||||
model.value.majorTags = normalizedMajorTags;
|
||||
model.value.tags = normalizedMajorTags.join(',');
|
||||
}
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
tagList.value = [];
|
||||
tagInputValue.value = '';
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
const majorTags = normalizeStringList(model.value.majorTags);
|
||||
const legacyTags = parseTagText(model.value.tags);
|
||||
tagList.value = majorTags.length ? majorTags : normalizeStringList(legacyTags);
|
||||
}
|
||||
|
||||
syncTagsToModel();
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
majorId,
|
||||
schoolId,
|
||||
collegeId,
|
||||
majorCode,
|
||||
majorName,
|
||||
educationLevel,
|
||||
durationYears,
|
||||
majorCategory,
|
||||
degreeType,
|
||||
introduction,
|
||||
remark
|
||||
} = model.value;
|
||||
const majorTags = normalizeStringList(tagList.value);
|
||||
const tags = majorTags.join(',');
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const payload: Api.Art.SchoolMajorOperateParams = {
|
||||
schoolId,
|
||||
collegeId,
|
||||
majorCode,
|
||||
majorName,
|
||||
educationLevel,
|
||||
durationYears,
|
||||
majorCategory,
|
||||
degreeType,
|
||||
introduction,
|
||||
majorTags,
|
||||
remark,
|
||||
tags
|
||||
};
|
||||
const { error } = await fetchCreateSchoolMajor(payload);
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const payload: Api.Art.SchoolMajorOperateParams = {
|
||||
majorId,
|
||||
schoolId,
|
||||
collegeId,
|
||||
majorCode,
|
||||
majorName,
|
||||
educationLevel,
|
||||
durationYears,
|
||||
majorCategory,
|
||||
degreeType,
|
||||
introduction,
|
||||
majorTags,
|
||||
remark,
|
||||
tags
|
||||
};
|
||||
const { error } = await fetchUpdateSchoolMajor(payload);
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
getSchoolOptions();
|
||||
handleUpdateModelWhenEdit();
|
||||
getCollegeOptions(model.value.schoolId);
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
tagList,
|
||||
values => {
|
||||
const normalizedValues = normalizeStringList(values);
|
||||
if (!isSameStringList(values, normalizedValues)) {
|
||||
tagList.value = normalizedValues;
|
||||
return;
|
||||
}
|
||||
syncTagsToModel();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => model.value.schoolId,
|
||||
(schoolId, oldSchoolId) => {
|
||||
if (!visible.value || schoolId === oldSchoolId) return;
|
||||
if (oldSchoolId !== undefined) {
|
||||
model.value.collegeId = null;
|
||||
}
|
||||
getCollegeOptions(schoolId);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="院校" path="schoolId">
|
||||
<NSelect
|
||||
v-model:value="model.schoolId"
|
||||
:loading="schoolLoading"
|
||||
:options="schoolOptions"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请选择院校"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="学院ID" path="collegeId">
|
||||
<NSelect
|
||||
v-model:value="model.collegeId"
|
||||
:loading="collegeLoading"
|
||||
:options="collegeOptions"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="专业编码(可选)" path="majorCode">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业编码(可选)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业名称" path="majorName">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学历层次:本科/专科" path="educationLevel">
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次:本科/专科" />
|
||||
</NFormItem>
|
||||
<NFormItem label="学制(3/4/5)" path="durationYears">
|
||||
<NInput v-model:value="model.durationYears" placeholder="请输入学制(3/4/5)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业类别:工学/理学/艺术学..." path="majorCategory">
|
||||
<NInput v-model:value="model.majorCategory" placeholder="请输入专业类别:工学/理学/艺术学..." />
|
||||
</NFormItem>
|
||||
<NFormItem label="学位类型:工学学士/理学学士/艺术学学士..." path="degreeType">
|
||||
<NSelect
|
||||
v-model:value="model.degreeType"
|
||||
placeholder="请选择学位类型:工学学士/理学学士/艺术学学士..."
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="专业介绍" path="introduction">
|
||||
<NInput v-model:value="model.introduction" :rows="3" type="textarea" placeholder="请输入专业介绍" />
|
||||
</NFormItem>
|
||||
<NFormItem label="专业标签" path="majorTags">
|
||||
<NSpace vertical class="w-full">
|
||||
<NSpace class="w-full" :wrap-item="false">
|
||||
<NInput
|
||||
v-model:value="tagInputValue"
|
||||
class="flex-1"
|
||||
placeholder="输入标签后回车或点击确认,可用逗号一次输入多个"
|
||||
@keydown.enter.prevent="addTag"
|
||||
/>
|
||||
<NButton type="primary" ghost @click="addTag">确认</NButton>
|
||||
</NSpace>
|
||||
<NDynamicTags v-model:value="tagList" />
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, toRaw, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchGetSchoolCollegeList } from '@/service/api/art/school-college';
|
||||
import { fetchGetSchoolList } from '@/service/api/art/school';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMajorSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
fixedSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fixedSchoolId: null
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolMajorSearchParams>('model', { required: true });
|
||||
const schoolOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
const collegeOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
async function getSchoolOptions() {
|
||||
const { data, error } = await fetchGetSchoolList({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
params: {}
|
||||
});
|
||||
if (!error) {
|
||||
schoolOptions.value = data.rows.map(item => ({
|
||||
value: item.schoolId,
|
||||
label: `${item.mainName || item.shortName || '未命名学校'} (${item.schoolId})`
|
||||
}));
|
||||
} else {
|
||||
schoolOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getCollegeOptions(schoolId?: CommonType.IdType | null) {
|
||||
if (!schoolId) {
|
||||
collegeOptions.value = [];
|
||||
return;
|
||||
}
|
||||
const { data, error } = await fetchGetSchoolCollegeList({
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
schoolId,
|
||||
params: {}
|
||||
});
|
||||
if (!error) {
|
||||
collegeOptions.value = data.rows.map(item => ({
|
||||
value: item.collegeId,
|
||||
label: item.collegeName
|
||||
}));
|
||||
} else {
|
||||
collegeOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
getCollegeOptions(model.value.schoolId);
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSchoolOptions();
|
||||
getCollegeOptions(model.value.schoolId);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => model.value.schoolId,
|
||||
(schoolId, oldSchoolId) => {
|
||||
if (schoolId !== oldSchoolId) {
|
||||
model.value.collegeId = null;
|
||||
getCollegeOptions(schoolId);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-major-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="院校" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.schoolId"
|
||||
:options="schoolOptions"
|
||||
:disabled="props.fixedSchoolId !== null"
|
||||
placeholder="请选择院校"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学院ID" label-width="auto" path="collegeId" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.collegeId"
|
||||
:options="collegeOptions"
|
||||
placeholder="请选择学院"
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业编码(可选)" label-width="auto" path="majorCode" class="pr-24px">
|
||||
<NInput v-model:value="model.majorCode" placeholder="请输入专业编码(可选)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业名称" label-width="auto" path="majorName" class="pr-24px">
|
||||
<NInput v-model:value="model.majorName" placeholder="请输入专业名称" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学历层次:本科/专科"
|
||||
label-width="auto"
|
||||
path="educationLevel"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次:本科/专科" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学制(3/4/5)" label-width="auto" path="durationYears" class="pr-24px">
|
||||
<NInput v-model:value="model.durationYears" placeholder="请输入学制(3/4/5)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="专业类别:工学/理学/艺术学..."
|
||||
label-width="auto"
|
||||
path="majorCategory"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.majorCategory" placeholder="请输入专业类别:工学/理学/艺术学..." />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学位类型:工学学士/理学学士/艺术学学士..."
|
||||
label-width="auto"
|
||||
path="degreeType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.degreeType"
|
||||
placeholder="请选择学位类型:工学学士/理学学士/艺术学学士..."
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="专业介绍" label-width="auto" path="introduction" class="pr-24px">
|
||||
<NInput v-model:value="model.introduction" placeholder="请输入专业介绍" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolMedia, fetchGetSchoolMediaList } from '@/service/api/art/school-media';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolMediaOperateDrawer from './modules/school-media-operate-drawer.vue';
|
||||
import SchoolMediaSearch from './modules/school-media-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMediaList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolMediaSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
bizType: props.schoolId ? 'school' : null,
|
||||
bizId: props.schoolId,
|
||||
mediaType: null,
|
||||
url: null,
|
||||
coverUrl: null,
|
||||
sortNo: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolMediaSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
bizType: props.schoolId ? 'school' : searchParams.value.bizType,
|
||||
bizId: props.schoolId ?? searchParams.value.bizId,
|
||||
params: {
|
||||
...searchParams.value.params,
|
||||
schoolId: props.schoolId ?? undefined
|
||||
}
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolMediaList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'mediaId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'bizType',
|
||||
title: '业务类型:school/campus/college/major/dorm',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'bizId',
|
||||
title: '业务主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'mediaType',
|
||||
title: '媒体类型:1-图片 2-视频',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
title: '资源URL',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'coverUrl',
|
||||
title: '封面URL(视频可用)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'sortNo',
|
||||
title: '排序',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolMedia:edit') || !hasAuth('art:schoolMedia:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolMedia:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.mediaId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolMedia:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.mediaId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'mediaId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMedia(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(mediaId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolMedia([mediaId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(mediaId: CommonType.IdType) {
|
||||
handleEdit(mediaId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolMedia/export', requestParams.value, `学校媒体资源_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolMediaSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校媒体资源列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolMedia:add')"
|
||||
:show-delete="hasAuth('art:schoolMedia:remove')"
|
||||
:show-export="hasAuth('art:schoolMedia:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.mediaId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolMediaOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-biz-type="props.schoolId ? 'school' : null"
|
||||
:default-biz-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolMedia, fetchUpdateSchoolMedia } from '@/service/api/art/school-media';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMediaOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolMedia | null;
|
||||
/** the default biz type when opened from school list */
|
||||
defaultBizType?: string | null;
|
||||
/** the default biz id when opened from school list */
|
||||
defaultBizId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultBizType: null,
|
||||
defaultBizId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校媒体资源',
|
||||
edit: '编辑学校媒体资源'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolMediaOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
mediaId: null,
|
||||
bizType: '',
|
||||
bizId: null,
|
||||
mediaType: null,
|
||||
url: '',
|
||||
coverUrl: '',
|
||||
sortNo: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'mediaId' | 'bizType' | 'bizId' | 'mediaType' | 'url'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
mediaId: createRequiredRule('主键ID不能为空'),
|
||||
bizType: createRequiredRule('业务类型:school/campus/college/major/dorm不能为空'),
|
||||
bizId: createRequiredRule('业务主键ID不能为空'),
|
||||
mediaType: createRequiredRule('媒体类型:1-图片 2-视频不能为空'),
|
||||
url: createRequiredRule('资源URL不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add') {
|
||||
if (props.defaultBizType !== null) {
|
||||
model.value.bizType = props.defaultBizType;
|
||||
}
|
||||
if (props.defaultBizId !== null) {
|
||||
model.value.bizId = props.defaultBizId;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { mediaId, bizType, bizId, mediaType, url, coverUrl, sortNo, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolMedia({ bizType, bizId, mediaType, url, coverUrl, sortNo, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolMedia({
|
||||
mediaId,
|
||||
bizType,
|
||||
bizId,
|
||||
mediaType,
|
||||
url,
|
||||
coverUrl,
|
||||
sortNo,
|
||||
remark
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="业务类型:school/campus/college/major/dorm" path="bizType">
|
||||
<NSelect
|
||||
v-model:value="model.bizType"
|
||||
:disabled="props.defaultBizType !== null"
|
||||
placeholder="请选择业务类型:school/campus/college/major/dorm"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="业务主键ID" path="bizId">
|
||||
<NInput v-model:value="model.bizId" :disabled="props.defaultBizId !== null" placeholder="请输入业务主键ID" />
|
||||
</NFormItem>
|
||||
<NFormItem label="媒体类型:1-图片 2-视频" path="mediaType">
|
||||
<NSelect
|
||||
v-model:value="model.mediaType"
|
||||
placeholder="请选择媒体类型:1-图片 2-视频"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="资源URL" path="url">
|
||||
<NInput v-model:value="model.url" :rows="3" type="textarea" placeholder="请输入资源URL" />
|
||||
</NFormItem>
|
||||
<NFormItem label="封面URL(视频可用)" path="coverUrl">
|
||||
<NInput v-model:value="model.coverUrl" :rows="3" type="textarea" placeholder="请输入封面URL(视频可用)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="排序" path="sortNo">
|
||||
<NInput v-model:value="model.sortNo" placeholder="请输入排序" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolMediaSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolMediaSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-media-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="业务类型:school/campus/college/major/dorm"
|
||||
label-width="auto"
|
||||
path="bizType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.bizType"
|
||||
placeholder="请选择业务类型:school/campus/college/major/dorm"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="业务主键ID" label-width="auto" path="bizId" class="pr-24px">
|
||||
<NInput v-model:value="model.bizId" placeholder="请输入业务主键ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="媒体类型:1-图片 2-视频"
|
||||
label-width="auto"
|
||||
path="mediaType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.mediaType"
|
||||
placeholder="请选择媒体类型:1-图片 2-视频"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="资源URL" label-width="auto" path="url" class="pr-24px">
|
||||
<NInput v-model:value="model.url" placeholder="请输入资源URL" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="封面URL(视频可用)"
|
||||
label-width="auto"
|
||||
path="coverUrl"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.coverUrl" placeholder="请输入封面URL(视频可用)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="排序" label-width="auto" path="sortNo" class="pr-24px">
|
||||
<NInput v-model:value="model.sortNo" placeholder="请输入排序" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolName, fetchGetSchoolNameList } from '@/service/api/art/school-name';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolNameOperateDrawer from './modules/school-name-operate-drawer.vue';
|
||||
import SchoolNameSearch from './modules/school-name-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolNameList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolNameSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
name: null,
|
||||
nameType: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolNameSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolNameList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'schoolNameId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '关联学校主表ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: '学校名称',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'nameType',
|
||||
title: '名称类型',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolName:edit') || !hasAuth('art:schoolName:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolName:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.schoolNameId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolName:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.schoolNameId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'schoolNameId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolName(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(schoolNameId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolName([schoolNameId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(schoolNameId: CommonType.IdType) {
|
||||
handleEdit(schoolNameId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolName/export', requestParams.value, `学校多名称_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolNameSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校多名称列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolName:add')"
|
||||
:show-delete="hasAuth('art:schoolName:remove')"
|
||||
:show-export="hasAuth('art:schoolName:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.schoolNameId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolNameOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolName, fetchUpdateSchoolName } from '@/service/api/art/school-name';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolNameOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolName | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校多名称',
|
||||
edit: '编辑学校多名称'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolNameOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
schoolNameId: null,
|
||||
schoolId: null,
|
||||
name: '',
|
||||
nameType: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'schoolNameId' | 'schoolId' | 'name'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
schoolNameId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('关联学校主表ID不能为空'),
|
||||
name: createRequiredRule('学校名称(曾用名/别名)不能为空')
|
||||
// nameType: createRequiredRule('名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { schoolNameId, schoolId, name, nameType, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolName({ schoolId, name, nameType, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolName({ schoolNameId, schoolId, name, nameType, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="关联学校主表ID" path="schoolId">
|
||||
<NInput
|
||||
v-model:value="model.schoolId"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请输入关联学校主表ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="学校名称(曾用名/别名)" path="name">
|
||||
<NInput v-model:value="model.name" placeholder="请输入学校名称(曾用名/别名)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称" path="nameType">
|
||||
<NInput v-model:value="model.nameType" placeholder="请输入名称类型" />
|
||||
<!--
|
||||
<NSelect
|
||||
v-model:value="model.nameType"
|
||||
placeholder="请选择名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
-->
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolNameSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolNameSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-name-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联学校主表ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入关联学校主表ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学校名称(曾用名/别名)"
|
||||
label-width="auto"
|
||||
path="name"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.name" placeholder="请输入学校名称(曾用名/别名)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称"
|
||||
label-width="auto"
|
||||
path="nameType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.nameType"
|
||||
placeholder="请选择名称类型:1-官方全称 2-曾用名 3-别名 4-英文名称"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,139 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学校编码(唯一标识,如国标代码)"
|
||||
label-width="auto"
|
||||
path="mainCode"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.mainCode" placeholder="请输入学校编码(唯一标识,如国标代码)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="学校主名称" label-width="auto" path="mainName" class="pr-24px">
|
||||
<NInput v-model:value="model.mainName" placeholder="请输入学校主名称(官方全称)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学校简称(备用)"
|
||||
label-width="auto"
|
||||
path="shortName"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.shortName" placeholder="请输入学校简称(备用)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="省份" label-width="auto" path="province" class="pr-24px">
|
||||
<NInput v-model:value="model.province" placeholder="请输入省份" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="城市" label-width="auto" path="city" class="pr-24px">
|
||||
<NInput v-model:value="model.city" placeholder="请输入城市" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="区县" label-width="auto" path="district" class="pr-24px">
|
||||
<NInput v-model:value="model.district" placeholder="请输入区县" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="大学类型:综合/工科/财经/艺术..."
|
||||
label-width="auto"
|
||||
path="universityType"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.universityType"
|
||||
placeholder="请选择大学类型:综合/工科/财经/艺术..."
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="学历层次:本科/专科"
|
||||
label-width="auto"
|
||||
path="educationLevel"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.educationLevel" placeholder="请输入学历层次:本科/专科" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="办学性质:公办/民办/中外合作"
|
||||
label-width="auto"
|
||||
path="schoolNature"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.schoolNature" placeholder="请输入办学性质:公办/民办/中外合作" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="主管部门:教育部/工信部/民委..."
|
||||
label-width="auto"
|
||||
path="supervisorDept"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.supervisorDept" placeholder="请输入主管部门:教育部/工信部/民委..." />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { fetchGetSchoolInfo } from '@/service/api/art/school';
|
||||
import SchoolCampusList from './school-campus/index.vue';
|
||||
import SchoolCollegeList from './school-college/index.vue';
|
||||
import SchoolDormList from './school-dorm/index.vue';
|
||||
import SchoolEnrollPlanList from './school-enroll-plan/index.vue';
|
||||
import SchoolMajorList from './school-major/index.vue';
|
||||
import SchoolMediaList from './school-media/index.vue';
|
||||
import SchoolNameList from './school-name/index.vue';
|
||||
import SchoolDetailJson from './school-detail-json/index.vue';
|
||||
// import SchoolTagList from './school-tag/index.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolSubTableModal'
|
||||
});
|
||||
|
||||
type SchoolSubModuleType =
|
||||
| 'schoolName'
|
||||
| 'schoolCampus'
|
||||
| 'schoolCollege'
|
||||
| 'schoolMajor'
|
||||
| 'schoolEnrollPlan'
|
||||
| 'schoolDorm'
|
||||
| 'schoolMedia'
|
||||
| 'schoolDetailJson';
|
||||
|
||||
type SchoolSubModuleOption = {
|
||||
key: SchoolSubModuleType;
|
||||
label: string;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
schoolId: CommonType.IdType | null;
|
||||
schoolName?: string;
|
||||
schoolData?: Api.Art.School | null;
|
||||
activeModule: SchoolSubModuleType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolName: '',
|
||||
schoolData: null
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const subModuleOptions: SchoolSubModuleOption[] = [
|
||||
{ key: 'schoolName', label: '院校名称管理' },
|
||||
{ key: 'schoolCampus', label: '校区管理' },
|
||||
{ key: 'schoolCollege', label: '学院管理' },
|
||||
{ key: 'schoolMajor', label: '专业管理' },
|
||||
{ key: 'schoolEnrollPlan', label: '招生计划管理' },
|
||||
{ key: 'schoolDorm', label: '宿舍管理' },
|
||||
{ key: 'schoolMedia', label: '媒体管理' },
|
||||
{ key: 'schoolDetailJson', label: '院校详情JSONB' }
|
||||
];
|
||||
|
||||
const activeTab = ref<SchoolSubModuleType>(props.activeModule);
|
||||
|
||||
const componentMap = {
|
||||
schoolName: SchoolNameList,
|
||||
schoolCampus: SchoolCampusList,
|
||||
schoolCollege: SchoolCollegeList,
|
||||
schoolMajor: SchoolMajorList,
|
||||
schoolEnrollPlan: SchoolEnrollPlanList,
|
||||
schoolDorm: SchoolDormList,
|
||||
schoolMedia: SchoolMediaList,
|
||||
schoolDetailJson: SchoolDetailJson
|
||||
} as const;
|
||||
|
||||
const activeComponent = computed(() => componentMap[activeTab.value]);
|
||||
const overviewLoading = ref(false);
|
||||
const schoolInfo = ref<Api.Art.School | null>(props.schoolData);
|
||||
|
||||
const title = computed(() => {
|
||||
const moduleLabel = subModuleOptions.find(item => item.key === activeTab.value)?.label ?? '子表管理';
|
||||
const schoolLabel = props.schoolName || `学校ID:${props.schoolId ?? '-'}`;
|
||||
return `${schoolLabel} - ${moduleLabel}`;
|
||||
});
|
||||
|
||||
const componentKey = computed(() => `${activeTab.value}-${props.schoolId ?? 'none'}`);
|
||||
|
||||
function formatFieldValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value);
|
||||
}
|
||||
|
||||
async function loadSchoolOverview() {
|
||||
if (props.schoolId === null) return;
|
||||
|
||||
overviewLoading.value = true;
|
||||
|
||||
const schoolRes = await fetchGetSchoolInfo(props.schoolId);
|
||||
|
||||
if (!schoolRes.error) {
|
||||
schoolInfo.value = schoolRes.data;
|
||||
} else if (props.schoolData) {
|
||||
schoolInfo.value = props.schoolData;
|
||||
}
|
||||
|
||||
overviewLoading.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activeModule,
|
||||
value => {
|
||||
activeTab.value = value;
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, show => {
|
||||
if (show) {
|
||||
activeTab.value = props.activeModule;
|
||||
loadSchoolOverview();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.schoolId,
|
||||
value => {
|
||||
if (!visible.value || value === null) return;
|
||||
loadSchoolOverview();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.schoolData,
|
||||
value => {
|
||||
if (!visible.value || !value) return;
|
||||
schoolInfo.value = value;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="visible"
|
||||
preset="card"
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
display-directive="show"
|
||||
class="max-w-96% w-1500px"
|
||||
>
|
||||
<div class="h-84vh flex-col overflow-hidden">
|
||||
<NCard title="院校信息(只读)" size="small" :bordered="true" :loading="overviewLoading" class="shrink-0">
|
||||
<NGrid :cols="24" :x-gap="16">
|
||||
<NGi :span="8">学校ID:{{ formatFieldValue(schoolInfo?.schoolId) }}</NGi>
|
||||
<NGi :span="8">学校编码:{{ formatFieldValue(schoolInfo?.mainCode) }}</NGi>
|
||||
<NGi :span="8">
|
||||
学校名称:{{ formatFieldValue(schoolInfo?.mainName || schoolInfo?.shortName || props.schoolName) }}
|
||||
</NGi>
|
||||
<NGi :span="8">
|
||||
省市区:{{
|
||||
formatFieldValue(
|
||||
[schoolInfo?.province, schoolInfo?.city, schoolInfo?.district].filter(Boolean).join(' / ')
|
||||
)
|
||||
}}
|
||||
</NGi>
|
||||
<NGi :span="8">大学类型:{{ formatFieldValue(schoolInfo?.universityType) }}</NGi>
|
||||
<NGi :span="8">办学性质:{{ formatFieldValue(schoolInfo?.schoolNature) }}</NGi>
|
||||
<NGi :span="8">学历层次:{{ formatFieldValue(schoolInfo?.educationLevel) }}</NGi>
|
||||
<NGi :span="8">主管部门:{{ formatFieldValue(schoolInfo?.supervisorDept) }}</NGi>
|
||||
<NGi :span="8">学校简称:{{ formatFieldValue(schoolInfo?.shortName) }}</NGi>
|
||||
</NGrid>
|
||||
</NCard>
|
||||
<NDivider class="my-10px">子表管理区</NDivider>
|
||||
<div class="min-h-0 flex-col-stretch gap-10px overflow-hidden">
|
||||
<NTabs v-model:value="activeTab" type="line" animated class="shrink-0">
|
||||
<NTabPane v-for="item in subModuleOptions" :key="item.key" :name="item.key" :tab="item.label" />
|
||||
</NTabs>
|
||||
<div class="min-h-0 overflow-auto">
|
||||
<component
|
||||
:is="activeComponent"
|
||||
v-if="visible"
|
||||
:key="componentKey"
|
||||
:school-id="props.schoolId"
|
||||
:in-modal="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { fetchBatchDeleteSchoolTag, fetchGetSchoolTagList } from '@/service/api/art/school-tag';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import SchoolImportModal from '../school-import-modal.vue';
|
||||
import SchoolTagOperateDrawer from './modules/school-tag-operate-drawer.vue';
|
||||
import SchoolTagSearch from './modules/school-tag-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolTagList'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
schoolId?: CommonType.IdType | null;
|
||||
inModal?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
schoolId: null,
|
||||
inModal: false
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
|
||||
|
||||
const searchParams = ref<Api.Art.SchoolTagSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
schoolId: props.schoolId,
|
||||
tagName: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const requestParams = computed<Api.Art.SchoolTagSearchParams>(() => ({
|
||||
...searchParams.value,
|
||||
schoolId: props.schoolId ?? searchParams.value.schoolId
|
||||
}));
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetSchoolTagList(requestParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'schoolTagId',
|
||||
title: '主键ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'schoolId',
|
||||
title: '关联学校主表ID',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'tagName',
|
||||
title: '标签名称(如:985、211、双一流、艺术类院校)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
title: '备注',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
fixed: 'right',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('art:schoolTag:edit') || !hasAuth('art:schoolTag:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('art:schoolTag:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.schoolTagId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('art:schoolTag:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.schoolTagId)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'schoolTagId', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolTag(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(schoolTagId: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteSchoolTag([schoolTagId]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(schoolTagId: CommonType.IdType) {
|
||||
handleEdit(schoolTagId);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/art/schoolTag/export', requestParams.value, `学校标签_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
function handleImport() {
|
||||
openImportModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
props.inModal
|
||||
? 'flex-col-stretch gap-16px'
|
||||
: 'min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto'
|
||||
"
|
||||
>
|
||||
<SchoolTagSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="学校标签列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('art:schoolTag:add')"
|
||||
:show-delete="hasAuth('art:schoolTag:remove')"
|
||||
:show-export="hasAuth('art:schoolTag:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
>
|
||||
<template #after>
|
||||
<NButton v-if="hasAuth('art:school:import')" size="small" ghost @click="handleImport">
|
||||
<template #icon>
|
||||
<icon-material-symbols-upload-rounded class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.import') }}
|
||||
</NButton>
|
||||
</template>
|
||||
</TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile && !props.inModal"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.schoolTagId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<SchoolTagOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:default-school-id="props.schoolId"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
<SchoolImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreateSchoolTag, fetchUpdateSchoolTag } from '@/service/api/art/school-tag';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolTagOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Art.SchoolTag | null;
|
||||
/** the default school id when opened from school list */
|
||||
defaultSchoolId?: CommonType.IdType | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
defaultSchoolId: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增学校标签',
|
||||
edit: '编辑学校标签'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Art.SchoolTagOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
schoolTagId: null,
|
||||
schoolId: null,
|
||||
tagName: '',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'schoolTagId' | 'schoolId' | 'tagName'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
schoolTagId: createRequiredRule('主键ID不能为空'),
|
||||
schoolId: createRequiredRule('关联学校主表ID不能为空'),
|
||||
tagName: createRequiredRule('标签名称(如:985、211、双一流、艺术类院校)不能为空')
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'add' && props.defaultSchoolId !== null) {
|
||||
model.value.schoolId = props.defaultSchoolId;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { schoolTagId, schoolId, tagName, remark } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreateSchoolTag({ schoolId, tagName, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdateSchoolTag({ schoolTagId, schoolId, tagName, remark });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="关联学校主表ID" path="schoolId">
|
||||
<NInput
|
||||
v-model:value="model.schoolId"
|
||||
:disabled="props.defaultSchoolId !== null"
|
||||
placeholder="请输入关联学校主表ID"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="标签名称(如:985、211、双一流、艺术类院校)" path="tagName">
|
||||
<NInput v-model:value="model.tagName" placeholder="请输入标签名称(如:985、211、双一流、艺术类院校)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="备注" path="remark">
|
||||
<NInput v-model:value="model.remark" :rows="3" type="textarea" placeholder="请输入备注" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'SchoolTagSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Art.SchoolTagSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="school-search-card card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="art-school-tag-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联学校主表ID" label-width="auto" path="schoolId" class="pr-24px">
|
||||
<NInput v-model:value="model.schoolId" placeholder="请输入关联学校主表ID" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="标签名称(如:985、211、双一流、艺术类院校)"
|
||||
label-width="auto"
|
||||
path="tagName"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.tagName" placeholder="请输入标签名称(如:985、211、双一流、艺术类院校)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<script setup lang="tsx">
|
||||
import { ref } from 'vue';
|
||||
import { NDivider } from 'naive-ui';
|
||||
import { fetchBatchDeletePlatformUser, fetchGetPlatformUserList } from '@/service/api/client/platform-user';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import PlatformUserOperateDrawer from './modules/platform-user-operate-drawer.vue';
|
||||
import PlatformUserSearch from './modules/platform-user-search.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PlatformUserList'
|
||||
});
|
||||
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Client.PlatformUserSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userId: null,
|
||||
platformType: null,
|
||||
platformOpenid: null,
|
||||
platformUnionid: null,
|
||||
platformSessionKey: null,
|
||||
platformExtra: null,
|
||||
lastLoginTime: null,
|
||||
params: {}
|
||||
});
|
||||
|
||||
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
|
||||
useNaivePaginatedTable({
|
||||
api: () => fetchGetPlatformUserList(searchParams.value),
|
||||
transform: response => defaultTransform(response),
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: (_, index) => index + 1
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
title: '平台用户ID(自增)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'userId',
|
||||
title: '关联t_user.id',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'platformType',
|
||||
title: '平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'platformOpenid',
|
||||
title: '平台唯一标识(微信openid/抖音open_id)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'platformUnionid',
|
||||
title: '平台统一标识(微信unionid,多小程序互通用)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'platformSessionKey',
|
||||
title: '平台会话密钥(微信session_key,加密存储)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'platformExtra',
|
||||
title: '平台扩展字段(如抖音的user_name、微信的city等)',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'lastLoginTime',
|
||||
title: '最后登录时间',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render: row => {
|
||||
const divider = () => {
|
||||
if (!hasAuth('client:platformUser:edit') || !hasAuth('client:platformUser:remove')) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth('client:platformUser:edit')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t('common.edit')}
|
||||
onClick={() => edit(row.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth('client:platformUser:remove')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t('common.delete')}
|
||||
popconfirmContent={$t('common.confirmDelete')}
|
||||
onPositiveClick={() => handleDelete(row.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, 'id', getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeletePlatformUser(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(id: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeletePlatformUser([id]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(id: CommonType.IdType) {
|
||||
handleEdit(id);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download('/client/platformUser/export', searchParams.value, `平台用户关联(微信/抖音小程序用户信息)_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<PlatformUserSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard title="平台用户关联(微信/抖音小程序用户信息)列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('client:platformUser:add')"
|
||||
:show-delete="hasAuth('client:platformUser:remove')"
|
||||
:show-export="hasAuth('client:platformUser:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.id"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<PlatformUserOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { fetchCreatePlatformUser, fetchUpdatePlatformUser } from '@/service/api/client/platform-user';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'PlatformUserOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Client.PlatformUser | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: '新增平台用户关联(微信/抖音小程序用户信息)',
|
||||
edit: '编辑平台用户关联(微信/抖音小程序用户信息)'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Client.PlatformUserOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
id: null,
|
||||
userId: null,
|
||||
platformType: null,
|
||||
platformOpenid: '',
|
||||
platformUnionid: '',
|
||||
platformSessionKey: '',
|
||||
platformExtra: '',
|
||||
lastLoginTime: null,
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<
|
||||
keyof Model,
|
||||
| 'id'
|
||||
| 'userId'
|
||||
| 'platformType'
|
||||
| 'platformOpenid'
|
||||
>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
id: createRequiredRule('平台用户ID(自增)不能为空'),
|
||||
userId: createRequiredRule('关联t_user.id不能为空'),
|
||||
platformType: createRequiredRule('平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序不能为空'),
|
||||
platformOpenid: createRequiredRule('平台唯一标识(微信openid/抖音open_id)不能为空'),
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { id, userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime } = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { error } = await fetchCreatePlatformUser({ userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { error } = await fetchUpdatePlatformUser({ id, userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime });
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
getTreeList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="关联t_user.id" path="userId">
|
||||
<NInput v-model:value="model.userId" placeholder="请输入关联t_user.id" />
|
||||
</NFormItem>
|
||||
<NFormItem label="平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序" path="platformType">
|
||||
<NSelect
|
||||
v-model:value="model.platformType"
|
||||
placeholder="请选择平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序"
|
||||
:options="[{ value: '0', label: '请选择字典生成' }]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="平台唯一标识(微信openid/抖音open_id)" path="platformOpenid">
|
||||
<NInput v-model:value="model.platformOpenid" placeholder="请输入平台唯一标识(微信openid/抖音open_id)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="平台统一标识(微信unionid,多小程序互通用)" path="platformUnionid">
|
||||
<NInput v-model:value="model.platformUnionid" placeholder="请输入平台统一标识(微信unionid,多小程序互通用)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="平台会话密钥(微信session_key,加密存储)" path="platformSessionKey">
|
||||
<NInput v-model:value="model.platformSessionKey" placeholder="请输入平台会话密钥(微信session_key,加密存储)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="平台扩展字段(如抖音的user_name、微信的city等)" path="platformExtra">
|
||||
<NInput v-model:value="model.platformExtra" placeholder="请输入平台扩展字段(如抖音的user_name、微信的city等)" />
|
||||
</NFormItem>
|
||||
<NFormItem label="最后登录时间" path="lastLoginTime">
|
||||
<NDatePicker
|
||||
v-model:formatted-value="model.lastLoginTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'PlatformUserSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Client.PlatformUserSearchParams>('model', { required: true });
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit('search');
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="client-platform-user-search">
|
||||
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 s:12 m:6" label="关联t_user.id" label-width="auto" path="userId" class="pr-24px">
|
||||
<NInput v-model:value="model.userId" placeholder="请输入关联t_user.id" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序" label-width="auto" path="platformType" class="pr-24px">
|
||||
<NSelect
|
||||
v-model:value="model.platformType"
|
||||
placeholder="请选择平台类型:1-微信小程序,2-抖音小程序,3-支付宝小程序"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="平台唯一标识(微信openid/抖音open_id)" label-width="auto" path="platformOpenid" class="pr-24px">
|
||||
<NInput v-model:value="model.platformOpenid" placeholder="请输入平台唯一标识(微信openid/抖音open_id)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="平台统一标识(微信unionid,多小程序互通用)" label-width="auto" path="platformUnionid" class="pr-24px">
|
||||
<NInput v-model:value="model.platformUnionid" placeholder="请输入平台统一标识(微信unionid,多小程序互通用)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="平台会话密钥(微信session_key,加密存储)" label-width="auto" path="platformSessionKey" class="pr-24px">
|
||||
<NInput v-model:value="model.platformSessionKey" placeholder="请输入平台会话密钥(微信session_key,加密存储)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="平台扩展字段(如抖音的user_name、微信的city等)" label-width="auto" path="platformExtra" class="pr-24px">
|
||||
<NInput v-model:value="model.platformExtra" placeholder="请输入平台扩展字段(如抖音的user_name、微信的city等)" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 s:12 m:6" label="最后登录时间" label-width="auto" path="lastLoginTime" class="pr-24px">
|
||||
<NDatePicker
|
||||
v-model:formatted-value="model.lastLoginTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.reset') }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.search') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
<script setup lang="tsx">
|
||||
import { ref } from "vue";
|
||||
import { NDivider } from "naive-ui";
|
||||
import {
|
||||
fetchBatchDeleteUser,
|
||||
fetchGetUserList,
|
||||
} from "@/service/api/client/user";
|
||||
import { useAppStore } from "@/store/modules/app";
|
||||
import { useAuth } from "@/hooks/business/auth";
|
||||
import { useDownload } from "@/hooks/business/download";
|
||||
import {
|
||||
defaultTransform,
|
||||
useNaivePaginatedTable,
|
||||
useTableOperate,
|
||||
} from "@/hooks/common/table";
|
||||
import { $t } from "@/locales";
|
||||
import ButtonIcon from "@/components/custom/button-icon.vue";
|
||||
import UserOperateDrawer from "./modules/user-operate-drawer.vue";
|
||||
import UserSearch from "./modules/user-search.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "UserList",
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { download } = useDownload();
|
||||
const { hasAuth } = useAuth();
|
||||
|
||||
const searchParams = ref<Api.Client.UserSearchParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
username: null,
|
||||
nickname: null,
|
||||
avatarUrl: null,
|
||||
phone: null,
|
||||
gender: null,
|
||||
status: null,
|
||||
password: null,
|
||||
salt: null,
|
||||
params: {},
|
||||
});
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
getData,
|
||||
getDataByPage,
|
||||
loading,
|
||||
mobilePagination,
|
||||
scrollX,
|
||||
} = useNaivePaginatedTable({
|
||||
api: () => fetchGetUserList(searchParams.value),
|
||||
transform: (response) => defaultTransform(response),
|
||||
onPaginationParamsChange: (params) => {
|
||||
searchParams.value.pageNum = params.page;
|
||||
searchParams.value.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
type: "selection",
|
||||
align: "center",
|
||||
width: 48,
|
||||
},
|
||||
{
|
||||
key: "index",
|
||||
title: $t("common.index"),
|
||||
align: "center",
|
||||
width: 64,
|
||||
render: (_, index) => index + 1,
|
||||
},
|
||||
{
|
||||
key: "id",
|
||||
title: "用户ID",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: "username",
|
||||
title: "用户名",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: "nickname",
|
||||
title: "用户昵称",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: "avatarUrl",
|
||||
title: "用户头像URL",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: "phone",
|
||||
title: "手机号",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
key: "gender",
|
||||
title: "性别",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
render: (row: any) => {
|
||||
if (row.gender === 1) return <p>男</p>;
|
||||
if (row.gender === 2) return <p>女</p>;
|
||||
return <p>未知</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
title: "状态",
|
||||
align: "center",
|
||||
minWidth: 120,
|
||||
render: (row: any) => {
|
||||
if (row.status === 1) return <p>正常</p>;
|
||||
return <p>禁用</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "operate",
|
||||
title: $t("common.operate"),
|
||||
align: "center",
|
||||
width: 130,
|
||||
render: (row) => {
|
||||
const divider = () => {
|
||||
if (!hasAuth("client:user:edit") || !hasAuth("client:user:remove")) {
|
||||
return null;
|
||||
}
|
||||
return <NDivider vertical />;
|
||||
};
|
||||
|
||||
const editBtn = () => {
|
||||
if (!hasAuth("client:user:edit")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="primary"
|
||||
icon="material-symbols:drive-file-rename-outline-outline"
|
||||
tooltipContent={$t("common.edit")}
|
||||
onClick={() => edit(row.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteBtn = () => {
|
||||
if (!hasAuth("client:user:remove")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={$t("common.delete")}
|
||||
popconfirmContent={$t("common.confirmDelete")}
|
||||
onPositiveClick={() => handleDelete(row.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex-center gap-8px">
|
||||
{editBtn()}
|
||||
{divider()}
|
||||
{deleteBtn()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const {
|
||||
drawerVisible,
|
||||
operateType,
|
||||
editingData,
|
||||
handleAdd,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
onDeleted,
|
||||
} = useTableOperate(data, "id", getData);
|
||||
|
||||
async function handleBatchDelete() {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteUser(checkedRowKeys.value);
|
||||
if (error) return;
|
||||
onBatchDeleted();
|
||||
}
|
||||
|
||||
async function handleDelete(id: CommonType.IdType) {
|
||||
// request
|
||||
const { error } = await fetchBatchDeleteUser([id]);
|
||||
if (error) return;
|
||||
onDeleted();
|
||||
}
|
||||
|
||||
function edit(id: CommonType.IdType) {
|
||||
handleEdit(id);
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
download(
|
||||
"/client/user/export",
|
||||
searchParams.value,
|
||||
`客户用户基础信息_${new Date().getTime()}.xlsx`,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto"
|
||||
>
|
||||
<UserSearch v-model:model="searchParams" @search="getDataByPage" />
|
||||
<NCard
|
||||
title="客户用户基础信息列表"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
class="card-wrapper sm:flex-1-hidden"
|
||||
>
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('client:user:add')"
|
||||
:show-delete="hasAuth('client:user:remove')"
|
||||
:show-export="hasAuth('client:user:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="scrollX"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="(row) => row.id"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<UserOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getDataByPage"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { jsonClone } from "@sa/utils";
|
||||
import { fetchCreateUser, fetchUpdateUser } from "@/service/api/client/user";
|
||||
import { useFormRules, useNaiveForm } from "@/hooks/common/form";
|
||||
import { $t } from "@/locales";
|
||||
|
||||
defineOptions({
|
||||
name: "UserOperateDrawer",
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: NaiveUI.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Client.User | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: "submitted"): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>("visible", {
|
||||
default: false,
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||
add: "新增客户用户基础信息",
|
||||
edit: "编辑客户用户基础信息",
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Client.UserOperateParams;
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
id: null,
|
||||
username: "",
|
||||
nickname: "",
|
||||
avatarUrl: "",
|
||||
phone: "",
|
||||
gender: null,
|
||||
status: null,
|
||||
password: "",
|
||||
salt: "",
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, "id">;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
id: createRequiredRule("用户ID不能为空"),
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (props.operateType === "edit" && props.rowData) {
|
||||
Object.assign(model.value, jsonClone(props.rowData));
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const {
|
||||
id,
|
||||
username,
|
||||
nickname,
|
||||
avatarUrl,
|
||||
phone,
|
||||
gender,
|
||||
status,
|
||||
password,
|
||||
salt,
|
||||
} = model.value;
|
||||
|
||||
// request
|
||||
if (props.operateType === "add") {
|
||||
const { error } = await fetchCreateUser({
|
||||
username,
|
||||
nickname,
|
||||
avatarUrl,
|
||||
phone,
|
||||
gender,
|
||||
status,
|
||||
password,
|
||||
salt,
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
if (props.operateType === "edit") {
|
||||
const { error } = await fetchUpdateUser({
|
||||
id,
|
||||
username,
|
||||
nickname,
|
||||
avatarUrl,
|
||||
phone,
|
||||
gender,
|
||||
status,
|
||||
password,
|
||||
salt,
|
||||
});
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
window.$message?.success($t("common.updateSuccess"));
|
||||
closeDrawer();
|
||||
emit("submitted");
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer
|
||||
v-model:show="visible"
|
||||
:title="title"
|
||||
display-directive="show"
|
||||
:width="800"
|
||||
class="max-w-90%"
|
||||
>
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="用户名" path="username">
|
||||
<NInput v-model:value="model.username" placeholder="请输入用户名" />
|
||||
</NFormItem>
|
||||
<NFormItem label="用户昵称" path="nickname">
|
||||
<NInput v-model:value="model.nickname" placeholder="请输入用户昵称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="用户头像URL" path="avatarUrl">
|
||||
<NInput
|
||||
v-model:value="model.avatarUrl"
|
||||
placeholder="请输入用户头像URL"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="手机号(请勿修改会导致无法登录)" path="phone">
|
||||
<NInput
|
||||
v-model:value="model.phone"
|
||||
disabled
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="性别:0-未知,1-男,2-女" path="gender">
|
||||
<NRadioGroup v-model:value="model.gender">
|
||||
<NRadio value="0">未知</NRadio>
|
||||
<NRadio value="1">男</NRadio>
|
||||
<NRadio value="2">女</NRadio>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem label="状态:0-禁用,1-正常" path="status">
|
||||
<NRadioGroup v-model:value="model.status">
|
||||
<NRadio value="0">禁用</NRadio>
|
||||
<NRadio value="1">正常</NRadio>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{
|
||||
$t("common.confirm")
|
||||
}}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<script setup lang="ts">
|
||||
import { toRaw } from "vue";
|
||||
import { jsonClone } from "@sa/utils";
|
||||
import { useNaiveForm } from "@/hooks/common/form";
|
||||
import { $t } from "@/locales";
|
||||
|
||||
defineOptions({
|
||||
name: "UserSearch",
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: "search"): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
|
||||
const model = defineModel<Api.Client.UserSearchParams>("model", {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const defaultModel = jsonClone(toRaw(model.value));
|
||||
|
||||
function resetModel() {
|
||||
Object.assign(model.value, defaultModel);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
await restoreValidation();
|
||||
resetModel();
|
||||
emit("search");
|
||||
}
|
||||
|
||||
async function search() {
|
||||
await validate();
|
||||
emit("search");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="card-wrapper">
|
||||
<NCollapse>
|
||||
<NCollapseItem :title="$t('common.search')" name="client-user-search">
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="model"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
>
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="用户名"
|
||||
label-width="auto"
|
||||
path="username"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="model.username"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="用户昵称"
|
||||
label-width="auto"
|
||||
path="nickname"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="model.nickname"
|
||||
placeholder="请输入用户昵称"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="用户头像URL"
|
||||
label-width="auto"
|
||||
path="avatarUrl"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput
|
||||
v-model:value="model.avatarUrl"
|
||||
placeholder="请输入用户头像URL"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="手机号"
|
||||
label-width="auto"
|
||||
path="phone"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NInput v-model:value="model.phone" placeholder="请输入手机号" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="性别:0-未知,1-男,2-女"
|
||||
label-width="auto"
|
||||
path="gender"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NRadioGroup v-model:value="model.gender">
|
||||
<NRadio value="0">未知</NRadio>
|
||||
<NRadio value="1">男</NRadio>
|
||||
<NRadio value="2">女</NRadio>
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
span="24 s:12 m:6"
|
||||
label="状态:0-禁用,1-正常"
|
||||
label-width="auto"
|
||||
path="status"
|
||||
class="pr-24px"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.status"
|
||||
placeholder="请选择状态:0-禁用,1-正常"
|
||||
:options="[]"
|
||||
clearable
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
|
||||
<NSpace class="w-full" justify="end">
|
||||
<NButton @click="reset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
{{ $t("common.reset") }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost @click="search">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
{{ $t("common.search") }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Loading…
Reference in New Issue