feat: 提交课程作业前端工程源码(Vue3 + Vite + view-ui-plus)
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
import { Method } from "alova";
|
||||
import { alovaInstance } from "@/utils/alova/alova-iview";
|
||||
import conf from "@/assets/config/host.config";
|
||||
import type {
|
||||
HomeworkVo,
|
||||
HomeworkDetailVo,
|
||||
HomeworkPageResult,
|
||||
BasePageResult,
|
||||
UpFileResultVo,
|
||||
UpFileVo,
|
||||
InitializeRedVo,
|
||||
NextPrevVo,
|
||||
ReadGroup,
|
||||
HomeworkStatisticsVo,
|
||||
HomeworkRequest,
|
||||
HomeworkQuery,
|
||||
DictOption,
|
||||
DeptManageItem,
|
||||
ContactGroupPage,
|
||||
ContactUserPage,
|
||||
SchoolTreeNode,
|
||||
ClassTreeNode,
|
||||
SubmissionsResultVo,
|
||||
SubmissionDetailVo,
|
||||
GradeRequest,
|
||||
MyHomeworksVo,
|
||||
MySubmissionVo,
|
||||
SubmitRequest,
|
||||
} from "@/types/homework";
|
||||
|
||||
/**
|
||||
* 课程作业接口封装(P0 标准化后新契约,P1-7 TS 契约化)。
|
||||
*
|
||||
* 前缀说明:conf.serviceHost 本地为空(走 vite 代理),线上为基础平台域名;
|
||||
* platform 固定使用 "api",与后端 Config.API_PATH 中的 api 匹配。
|
||||
* 统一响应由 alova-iview 解包为 data(code==0),页面直接使用返回值。
|
||||
*
|
||||
* 变更要点(P0):
|
||||
* 1. 作业域去掉 Homework_list 二级段(/api/homework/Homework_list/List → /api/homework/List)
|
||||
* 2. 文件域从 base/ 迁移到 file/(/api/base/FileDown → /api/file/Down)
|
||||
* 3. 字典域从 base/ 迁移到 dict/,GET 写操作改 POST(/api/base/DelDept → /api/dict/Delete)
|
||||
* 4. postJSON 用于 @RequestBody 接口;postForm 用于 @RequestParam 接口(表单提交)
|
||||
* 5. 参数更名:chaoSend→fileIds、topmost→smsFlag
|
||||
* 6. Delete 入参从 ids 逗号串改为 List<Integer> JSON 数组
|
||||
*
|
||||
* 变更要点(P1-7):
|
||||
* 7. 全部方法加返回值泛型标注,替换 Record<string, any>
|
||||
* 8. 列表分页 content 字段对齐后端 BasePageResult(非旧 list)
|
||||
*/
|
||||
|
||||
const HOMEWORK = "/api/homework";
|
||||
const BASE = "/api/base";
|
||||
const DICT = "/api/dict";
|
||||
const FILE = "/api/file";
|
||||
const CONTACTS = "/api/contacts";
|
||||
|
||||
/** GET 请求,T 为返回数据类型 */
|
||||
function get<T = any>(url: string, params?: Record<string, any>): Method<T> {
|
||||
return new Method<T>("GET", alovaInstance, url, { params });
|
||||
}
|
||||
|
||||
/** POST JSON body(与后端 @RequestBody 对齐),T 为返回数据类型 */
|
||||
function postJSON<T = any>(url: string, data?: Record<string, any> | any[]): Method<T> {
|
||||
return new Method<T>(
|
||||
"POST",
|
||||
alovaInstance,
|
||||
url,
|
||||
{ headers: { "Content-Type": "application/json;charset=UTF-8" } },
|
||||
data ? JSON.stringify(data) : undefined
|
||||
);
|
||||
}
|
||||
|
||||
/** POST 表单(与后端 @RequestParam 对齐,application/x-www-form-urlencoded),T 为返回数据类型 */
|
||||
function postForm<T = any>(url: string, data?: Record<string, any>): Method<T> {
|
||||
const pairs = data
|
||||
? Object.entries(data).filter(([, v]) => v !== undefined && v !== null)
|
||||
: [];
|
||||
const form = pairs.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
||||
// 同时把参数拼到 URL query:后端 @RequestParam 表单/查询参数都能接收,
|
||||
// 且本地 vite-plugin-mock 不解析 urlencoded body,靠 query 才能拿到参数(不影响线上)。
|
||||
const fullUrl = form ? `${url}${url.includes("?") ? "&" : "?"}${form}` : url;
|
||||
return new Method<T>(
|
||||
"POST",
|
||||
alovaInstance,
|
||||
fullUrl,
|
||||
{ headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" } },
|
||||
form || undefined
|
||||
);
|
||||
}
|
||||
|
||||
/** 附件下载直链(浏览器 location.href 跳转) */
|
||||
function fileDownUrl(fileId: number): string {
|
||||
return `${conf.serviceHost}${FILE}/Down?id=${fileId}`;
|
||||
}
|
||||
|
||||
/** 作业全部附件打包下载直链 */
|
||||
function allFileDownUrl(dataid: number): string {
|
||||
return `${conf.serviceHost}${HOMEWORK}/AllFileDown?dataid=${dataid}`;
|
||||
}
|
||||
|
||||
class BaseApi {
|
||||
// ========== 用户信息 & 模块权限 ==========
|
||||
/** 获取当前登录用户信息 */
|
||||
getUserInfo() {
|
||||
return get<SchoolTreeNode>(`${BASE}/GetUserInfo`);
|
||||
}
|
||||
/** 获取当前用户已授权的模块列表 */
|
||||
getUserMod() {
|
||||
return get<SchoolTreeNode[]>(`${BASE}/GetUserMod`);
|
||||
}
|
||||
/** 当前学校组织树(部门→教师,用于选择用户) */
|
||||
getSchoolAll() {
|
||||
return get<SchoolTreeNode[]>(`${BASE}/GetSchoolAll`);
|
||||
}
|
||||
/** 年级→班级树(学生场景:选择通知对象到班级一级) */
|
||||
getClassTree() {
|
||||
return get<ClassTreeNode[]>(`${BASE}/GetClassTree`);
|
||||
}
|
||||
/** 端分离入口:切换当前视角(teacher / student / parent),需在拉取用户信息前调用 */
|
||||
setRole(as: "teacher" | "student" | "parent") {
|
||||
return postJSON<{ role: string }>(`${BASE}/SetRole`, { as });
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkApi {
|
||||
// ========== 收件箱 ==========
|
||||
/** 已收作业列表 */
|
||||
list(params: HomeworkQuery) {
|
||||
return get<HomeworkPageResult<HomeworkVo>>(`${HOMEWORK}/List`, params);
|
||||
}
|
||||
/** 清单:一键全部已读 */
|
||||
initializeRed() {
|
||||
return postForm<InitializeRedVo>(`${HOMEWORK}/InitializeRed`);
|
||||
}
|
||||
|
||||
// ========== 已发/全部作业 ==========
|
||||
/** 已发/全部作业列表(scope="user" 只看自己,不传=全部) */
|
||||
sendList(params: HomeworkQuery) {
|
||||
return get<BasePageResult<HomeworkVo>>(`${HOMEWORK}/SendList`, params);
|
||||
}
|
||||
|
||||
// ========== 详情 ==========
|
||||
/** 查看详情(type=1 标读;type=0 编辑不标读) */
|
||||
modify(serid: number, type = 1) {
|
||||
return get<HomeworkDetailVo>(`${HOMEWORK}/Detail`, { serid, type });
|
||||
}
|
||||
/** 编辑回显(不标读) */
|
||||
getHomeworkDetails(serid: number) {
|
||||
return get<HomeworkDetailVo>(`${HOMEWORK}/GetDetail`, { serid });
|
||||
}
|
||||
/** 上一篇/下一篇 */
|
||||
getNextPrevHomework(serid: number, type: string) {
|
||||
return get<NextPrevVo>(`${HOMEWORK}/GetNextPrev`, { serid, type });
|
||||
}
|
||||
/** 阅读情况(已读/未读分组) */
|
||||
orgizeModify(serid: number) {
|
||||
return get<ReadGroup[]>(`${HOMEWORK}/ReadDetail`, { serid });
|
||||
}
|
||||
/** 追加接收人 */
|
||||
receiveUsers(serid: number, users: string) {
|
||||
return postForm<void>(`${HOMEWORK}/ReceiveUsers`, { serid, users });
|
||||
}
|
||||
|
||||
// ========== 发布/编辑/删除 ==========
|
||||
/** 新建作业(JSON body,fileIds 为附件ID逗号串,smsFlag 为短信标记) */
|
||||
create(data: HomeworkRequest) {
|
||||
return postJSON<number>(`${HOMEWORK}/Create`, data);
|
||||
}
|
||||
/** 编辑作业(JSON body,含 id) */
|
||||
editHomework(data: HomeworkRequest) {
|
||||
return postJSON<void>(`${HOMEWORK}/Edit`, data);
|
||||
}
|
||||
/** 删除作业(ids 数组,支持批量) */
|
||||
deleteHomework(ids: number | string) {
|
||||
// 兼容页面传单个 id 或逗号串,统一转成数组
|
||||
const idArr = String(ids).split(",").map(Number).filter(Boolean);
|
||||
return postJSON<void>(`${HOMEWORK}/Delete`, idArr);
|
||||
}
|
||||
/** 撤回 */
|
||||
userPull(serid: number) {
|
||||
return postForm<void>(`${HOMEWORK}/UserPull`, { serid });
|
||||
}
|
||||
/** 置顶/取消置顶 */
|
||||
setTopmost(serid: number, topmost = 1) {
|
||||
return postForm<void>(`${HOMEWORK}/SetTopmost`, { serid, topmost });
|
||||
}
|
||||
/** 催收未读 */
|
||||
expedite(serid: number) {
|
||||
return postForm<void>(`${HOMEWORK}/Expedite`, { serid });
|
||||
}
|
||||
|
||||
// ========== 课程作业 v1:提交情况 / 批改 / 催交 ==========
|
||||
/** 某作业的提交名单(按班级分组)+ 汇总;submitStatus: all/unsubmitted/submitted/pending/graded/needRedo */
|
||||
submissions(serid: number, submitStatus = "all") {
|
||||
return get<SubmissionsResultVo>(`${HOMEWORK}/Submissions`, { serid, submitStatus });
|
||||
}
|
||||
/** 单份提交详情(批改工作台右侧) */
|
||||
submissionDetail(subId: number) {
|
||||
return get<SubmissionDetailVo>(`${HOMEWORK}/SubmissionDetail`, { subId });
|
||||
}
|
||||
/** 批改(JSON body):action=grade/redo/excellent */
|
||||
grade(data: GradeRequest) {
|
||||
return postJSON<void>(`${HOMEWORK}/Grade`, data);
|
||||
}
|
||||
/** 催交未交学生 */
|
||||
remind(serid: number) {
|
||||
return postForm<{ reminded: number }>(`${HOMEWORK}/Remind`, { serid });
|
||||
}
|
||||
|
||||
// ========== v2 学生端 ==========
|
||||
/** 今日/我的作业(当前学生视角,含提交与批改态 + 分桶计数) */
|
||||
myHomeworks() {
|
||||
return get<MyHomeworksVo>(`${HOMEWORK}/MyHomeworks`);
|
||||
}
|
||||
/** 我对某作业的详情(作业信息 + 我的提交/批改) */
|
||||
mySubmission(serid: number) {
|
||||
return get<MySubmissionVo>(`${HOMEWORK}/MySubmission`, { serid });
|
||||
}
|
||||
/** 提交 / 订正重交(JSON body:serid, text, images, attachments) */
|
||||
submit(data: SubmitRequest) {
|
||||
return postJSON<{ subId: number; submitStatus: string }>(`${HOMEWORK}/Submit`, data);
|
||||
}
|
||||
|
||||
// ========== 附件 ==========
|
||||
fileDownUrl = fileDownUrl;
|
||||
allFileDownUrl = allFileDownUrl;
|
||||
|
||||
// ========== 部门字典 ==========
|
||||
/** 发文部门下拉 */
|
||||
getSysDic() {
|
||||
return get<DictOption[]>(`${DICT}/List`, { type: 1 });
|
||||
}
|
||||
/** 是否开通短信(迁移到 dict/Sync 查询,保持旧调用名不变) */
|
||||
getSchModuleIsSms() {
|
||||
return get<boolean>(`${DICT}/Sync`);
|
||||
}
|
||||
/** 作业统计 */
|
||||
statistics(params?: { starttime?: string; endtime?: string }) {
|
||||
return get<HomeworkStatisticsVo>(`${HOMEWORK}/Statis`, params);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingApi {
|
||||
// ========== 发文部门管理 ==========
|
||||
/** 获取发文部门管理列表 */
|
||||
getDictMangeList() {
|
||||
return get<DeptManageItem[]>(`${DICT}/ManageList`, { type: "dept" });
|
||||
}
|
||||
/** 新增发文部门 */
|
||||
addDeptDic(name: string) {
|
||||
return postForm<void>(`${DICT}/Add`, { name });
|
||||
}
|
||||
/** 更新发文部门(名称、启用状态) */
|
||||
updateDeptDic(SerID: number, Name: string, Enable = 1) {
|
||||
return postForm<void>(`${DICT}/Update`, { SerID, Name, Enable });
|
||||
}
|
||||
/** 删除发文部门(原 GET DelDept,已 POST 化) */
|
||||
delDept(serid: number) {
|
||||
return postForm<void>(`${DICT}/Delete`, { serid });
|
||||
}
|
||||
/** 发文部门排序(原 GET OrdbyDicIds,已 POST 化) */
|
||||
sortDeptDic(ids: string) {
|
||||
return postForm<void>(`${DICT}/Sort`, { ids });
|
||||
}
|
||||
// ========== 自定义组管理 ==========
|
||||
/** 获取联系组列表 */
|
||||
getGroup(page: number, count: number) {
|
||||
return get<ContactGroupPage>(`${CONTACTS}/GetGroup`, { page, count });
|
||||
}
|
||||
/** 新增联系组 */
|
||||
addGroup(name: string) {
|
||||
return postForm<void>(`${CONTACTS}/AddGroup`, { name });
|
||||
}
|
||||
/** 编辑联系组 */
|
||||
editGroup(id: number, name: string) {
|
||||
return postForm<void>(`${CONTACTS}/EditGroup`, { name, id });
|
||||
}
|
||||
/** 删除联系组 */
|
||||
delGroup(ids: string) {
|
||||
return postForm<void>(`${CONTACTS}/DelGroup`, { ids });
|
||||
}
|
||||
/** 获取联系组用户 */
|
||||
getContactsTea(gid: number, page = 0, count = 0) {
|
||||
return get<ContactUserPage>(`${CONTACTS}/GetContactsTea`, { gid, page, count });
|
||||
}
|
||||
/** 设置联系组用户 */
|
||||
setContactsTea(gid: number, ids: string) {
|
||||
return postForm<void>(`${CONTACTS}/SetContactsTea`, { gid, ids });
|
||||
}
|
||||
/** 联系组用户树(选择用户-自定义组 tab) */
|
||||
getGroupTree() {
|
||||
return get<SchoolTreeNode[]>(`${CONTACTS}/GetGroupTree`, { type: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
export const baseApi = new BaseApi();
|
||||
export const homeworkApi = new HomeworkApi();
|
||||
export const settingApi = new SettingApi();
|
||||
export default homeworkApi;
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { IModule } from "@/store";
|
||||
|
||||
/**
|
||||
* 课程作业左侧菜单数据(对照后端 GetUserMod 现行模块)。
|
||||
* 当前由 layout 直接使用;接入后端 GetUserMod 后,以接口返回为准。
|
||||
* 注:课程作业不做审批,故不含「审批列表/审核设置」;平台模块配置若仍下发 modId 5/6,
|
||||
* 由 utils/menuRoute 的 DROPPED_MOD_IDS 统一剔除(见 classwork-frontend-m5-plan.md §6)。
|
||||
*/
|
||||
export function getMenuList(): Promise<IModule[]> {
|
||||
const menu: IModule[] = [
|
||||
{ id: 1, modId: 1, name: "已收到", page: "/" },
|
||||
{ id: 2, modId: 2, name: "已发送", page: "/list" },
|
||||
{ id: 3, modId: 3, name: "全部作业", page: "/homeworklist" },
|
||||
{ id: 4, modId: 4, name: "作业统计", page: "/stats" },
|
||||
{ id: 21, modId: 21, name: "应用设置", page: "/setting" },
|
||||
];
|
||||
return Promise.resolve(menu);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="type_set-box">
|
||||
<div class="type_name">
|
||||
<i class="iconfont icon-stop fl mr-10" v-if="!isEdit && !active" style="color: #ff5959"></i>
|
||||
<Input v-if="isEdit" v-model="type.name" ref="type_input" @on-blur="update" @on-enter="update" />
|
||||
<span v-else class="fl">{{ type.name }}</span>
|
||||
</div>
|
||||
<div class="type_action flex">
|
||||
<div class="action_item" @click="open">
|
||||
<span v-if="active">停用</span>
|
||||
<span v-else>启用</span>
|
||||
</div>
|
||||
<div class="action_edit" @click="edit">
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div class="action_delete" @click="remove">
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="divider2"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TypeSetBox",
|
||||
props: {
|
||||
type: {
|
||||
name: { type: String, default: "" },
|
||||
id: { type: String, default: "" },
|
||||
},
|
||||
active: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEdit: false,
|
||||
eventType: "",
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
type: {
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
if (this.isEdit) {
|
||||
this.isEdit = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
"type.name"(newVal) {
|
||||
if (this.isEdit) {
|
||||
this.isEdit = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$emit("active", this.type.id);
|
||||
},
|
||||
edit() {
|
||||
this.isEdit = !this.isEdit;
|
||||
if (this.isEdit) this.$nextTick(() => this.$refs["type_input"].focus());
|
||||
},
|
||||
update(e) {
|
||||
if (!this.type.name) {
|
||||
this.$Message.error("值不得为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.eventType == "") {
|
||||
this.eventType = e.type;
|
||||
this.isEdit = false;
|
||||
this.$emit("edit", this.type.id, this.type.name);
|
||||
setTimeout(() => {
|
||||
this.eventType = "";
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
remove() {
|
||||
this.$emit("remove", this.type.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.type_set-box {
|
||||
width: 222px;
|
||||
background: #ffffff;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 15px;
|
||||
.type_name {
|
||||
height: 66px;
|
||||
border: 1px solid #e9e9e9;
|
||||
border-bottom: none;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
line-height: 66px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
span {
|
||||
height: 24px;
|
||||
font-size: 15px;
|
||||
font-family: PingFangSC-Semibold, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #272829;
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 222px;
|
||||
display: inline-block;
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
.type_action {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 222px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
background: #f7f7f7;
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
border-top: none;
|
||||
border: 1px solid #e9e9e9;
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
div:hover {
|
||||
color: #6c91f0;
|
||||
}
|
||||
}
|
||||
.divider {
|
||||
// position: relative;
|
||||
height: 14px;
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
// left: 73px;
|
||||
// top: -23px;
|
||||
float: left;
|
||||
margin-left: 71px;
|
||||
margin-top: -22px;
|
||||
}
|
||||
.divider2 {
|
||||
// position: relative;
|
||||
// left: 146px;
|
||||
// top: -37px;
|
||||
height: 14px;
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
float: left;
|
||||
margin-left: 147px;
|
||||
margin-top: -22px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<!-- 作业对象选择:全部用户 / 指定用户(自旧前端 user_check_input.vue 适配) -->
|
||||
<div class="check-user-wrap">
|
||||
<div class="check-user-box">
|
||||
<RadioGroup v-model="isCheckUserAll" class="radio" @on-change="userAllCheck">
|
||||
<Radio :disabled="editDisabled" :label="0">{{ isEdit ? "现有" : "全部" }}用户</Radio>
|
||||
<Radio :label="1">指定用户</Radio>
|
||||
</RadioGroup>
|
||||
<Input
|
||||
type="text"
|
||||
v-model="userNameStr"
|
||||
readonly
|
||||
:disabled="!isCheckUserAll"
|
||||
placeholder="选择指定的用户"
|
||||
class="input"
|
||||
></Input>
|
||||
<Button icon="md-add" @click="checkUserShow" :disabled="!isCheckUserAll"></Button>
|
||||
<Button icon="md-trash" @click="resetUser" :disabled="!isCheckUserAll"></Button>
|
||||
</div>
|
||||
<UserCheckModal
|
||||
ref="userCheckModal"
|
||||
:sources="sources"
|
||||
:userids="tempUserIds"
|
||||
@on-submit="submitUser"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/*
|
||||
* 作业对象选择输入(自旧前端 oldfront/src/components/user_check_input.vue 适配 Vue3)。
|
||||
* v-model 值为 "all" 或用户 id 逗号串;切换到「指定用户」直接弹出选择用户弹窗。
|
||||
*/
|
||||
import UserCheckModal from "@/components/UserCheckModal.vue";
|
||||
import { baseApi, settingApi } from "@/api/module/homework";
|
||||
|
||||
export default {
|
||||
name: "UserCheckInput",
|
||||
components: { UserCheckModal },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "all",
|
||||
},
|
||||
/** 编辑态:全部用户选项禁用为「现有用户」 */
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
isCheckUserAll: 0 as number,
|
||||
tempUserIds: [] as any[],
|
||||
userNameStr: "",
|
||||
editDisabled: false,
|
||||
sources: [
|
||||
{
|
||||
name: "基础平台",
|
||||
loader: () => baseApi.getSchoolAll().send() as Promise<any[]>,
|
||||
},
|
||||
{
|
||||
name: "自定义组",
|
||||
loader: () => settingApi.getGroupTree().send() as Promise<any[]>,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.modelValue === "all") {
|
||||
this.isCheckUserAll = 0;
|
||||
this.userNameStr = this.isEdit ? "现有用户" : "全部用户";
|
||||
} else {
|
||||
this.isCheckUserAll = 1;
|
||||
this.tempUserIds = this.modelValue.split(",").map((x) => parseInt(x)).filter((x) => !isNaN(x));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkUserShow() {
|
||||
(this.$refs.userCheckModal as any).show();
|
||||
},
|
||||
resetUser() {
|
||||
//清除用户
|
||||
this.tempUserIds = [];
|
||||
this.userNameStr = "";
|
||||
this.$emit("update:modelValue", "");
|
||||
},
|
||||
userAllCheck(type: number) {
|
||||
let tempIds: any = String(this.modelValue || "")
|
||||
.split(",")
|
||||
.map((x) => parseInt(x))
|
||||
.filter((x) => !isNaN(x));
|
||||
if (type == 0) {
|
||||
if (this.isEdit) {
|
||||
this.tempUserIds = tempIds;
|
||||
this.userNameStr = "现有用户";
|
||||
this.$emit("update:modelValue", String(tempIds));
|
||||
} else {
|
||||
this.tempUserIds = [];
|
||||
this.userNameStr = "全部用户";
|
||||
this.$emit("update:modelValue", "all");
|
||||
}
|
||||
} else {
|
||||
if (this.isEdit) {
|
||||
this.editDisabled = true;
|
||||
}
|
||||
this.tempUserIds = tempIds;
|
||||
this.userNameStr = "";
|
||||
this.checkUserShow();
|
||||
}
|
||||
},
|
||||
submitUser(arr: any[]) {
|
||||
//确认用户
|
||||
this.userNameStr = arr.map((x) => x.name).join("、");
|
||||
const ids = arr.map((x) => x.id).join(",");
|
||||
this.$emit("update:modelValue", ids || "all");
|
||||
if (!arr.length) {
|
||||
// 清空后回到全部用户
|
||||
this.isCheckUserAll = 0;
|
||||
this.userNameStr = "全部用户";
|
||||
this.$emit("update:modelValue", "all");
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(val: string) {
|
||||
if (val === "all") {
|
||||
this.isCheckUserAll = 0;
|
||||
this.userNameStr = this.isEdit ? "现有用户" : "全部用户";
|
||||
} else if (val === "") {
|
||||
this.userNameStr = "";
|
||||
} else {
|
||||
// 编辑回显:指定用户 id 串
|
||||
this.isCheckUserAll = 1;
|
||||
this.tempUserIds = val
|
||||
.split(",")
|
||||
.map((x) => parseInt(x))
|
||||
.filter((x) => !isNaN(x));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.check-user-wrap {
|
||||
width: 100%;
|
||||
.check-user-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.radio {
|
||||
width: 180px;
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
line-height: 32px;
|
||||
}
|
||||
.input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
|
||||
/**
|
||||
* 课程作业模块路由(扁平短路由,与基础平台模块 path 一致)。
|
||||
* 页面间参数通过 query 传递:
|
||||
* - /list/new?serid= (编辑)
|
||||
* - /list/new?fid= (转发)
|
||||
* - /list/new?tempid=(模板引用)
|
||||
* - /list/detail?serid=&type=、/homeworklist/detail?serid=&type=
|
||||
* type: 0=编辑视角(不标读) 1=普通查看(标读)
|
||||
*
|
||||
* modId 是菜单 ↔ 路由的稳定契约(对照后端 GetUserMod 现行数据):
|
||||
* 1=已收到 2=已发送 3=全部作业 4=作业统计 21=应用设置
|
||||
* 发作业为「已发送」的深层子页(modId 22 不在菜单树,meta.parentId=2 关联高亮),
|
||||
* 与 UserBase「应用管理>应用类型管理」范式一致。
|
||||
*/
|
||||
const routes: RouteRecordRaw[] = [
|
||||
// v1 教师默认落地:我布置的
|
||||
{ path: "/", redirect: "/list" },
|
||||
// 学生收件箱(v2 保留,不在菜单)
|
||||
{
|
||||
path: "/accept",
|
||||
name: "homework_accept",
|
||||
component: () => import("@/views/homework/HomeworkAccept.vue"),
|
||||
meta: { title: "已收到", modId: 1 },
|
||||
},
|
||||
// 我布置的(教师端作业列表)
|
||||
{
|
||||
path: "/list",
|
||||
name: "homework_list",
|
||||
component: () => import("@/views/homework/HomeworkList.vue"),
|
||||
meta: { title: "我布置的", modId: 2 },
|
||||
},
|
||||
// 提交情况(某作业的已交/未交名单;从列表行或菜单进入,无 serid 时页内选择作业)
|
||||
{
|
||||
path: "/submissions",
|
||||
name: "homework_submissions",
|
||||
component: () => import("@/views/homework/Submissions.vue"),
|
||||
meta: { title: "提交情况", modId: 25 },
|
||||
},
|
||||
// 批改中心(待批作业→逐份批改工作台;无 serid 时先看待批作业列表)
|
||||
{
|
||||
path: "/grade",
|
||||
name: "homework_grade",
|
||||
component: () => import("@/views/homework/Grade.vue"),
|
||||
meta: { title: "批改中心", modId: 26 },
|
||||
},
|
||||
// 发作业(新建/编辑/转发/模板):已发送的深层子页,不在菜单里,
|
||||
// layout.vue 按.meta.parentId 推导面包屑「已发送>发作业」并高亮父菜单
|
||||
{
|
||||
path: "/list/new",
|
||||
name: "new_homework",
|
||||
component: () => import("@/views/homework/NewHomework.vue"),
|
||||
meta: { title: "发作业", modId: 22, parentId: 2 },
|
||||
},
|
||||
// 全部作业(管理端)
|
||||
{
|
||||
path: "/homeworklist",
|
||||
name: "homework_manage",
|
||||
component: () => import("@/views/homework/HomeworkList.vue"),
|
||||
meta: { title: "全部作业", modId: 3 },
|
||||
},
|
||||
// 作业详情(同一组件挂两个路由,面包屑/菜单高亮由 meta.parentId 确定父节点:
|
||||
// 已发送下 /list/detail,全部作业下 /homeworklist/detail;type 区分是否标读)
|
||||
{
|
||||
path: "/list/detail",
|
||||
name: "homework_list_detail",
|
||||
component: () => import("@/views/homework/HomeworkDetails.vue"),
|
||||
meta: { title: "作业详情", modId: 23, parentId: 2 },
|
||||
},
|
||||
{
|
||||
path: "/homeworklist/detail",
|
||||
name: "homework_manage_detail",
|
||||
component: () => import("@/views/homework/HomeworkDetails.vue"),
|
||||
meta: { title: "作业详情", modId: 24, parentId: 3 },
|
||||
},
|
||||
// 作业统计(旧版行为:整页跳转外部统计系统)
|
||||
{
|
||||
path: "/stats",
|
||||
name: "homework_stats",
|
||||
component: () => import("@/views/homework/HomeworkStats.vue"),
|
||||
meta: { title: "作业统计", modId: 4 },
|
||||
},
|
||||
// 应用设置(发文部门 + 自定义组)
|
||||
{
|
||||
path: "/setting",
|
||||
name: "homework_setting",
|
||||
component: () => import("@/views/homework/HomeworkSetting.vue"),
|
||||
meta: { title: "应用设置", modId: 21 },
|
||||
},
|
||||
|
||||
/* ---- v2 学生端 ---- */
|
||||
// 学生·今日作业
|
||||
{
|
||||
path: "/stu/today",
|
||||
name: "student_today",
|
||||
component: () => import("@/views/homework/StudentHomeworks.vue"),
|
||||
meta: { title: "今日作业", modId: 31, role: "student" },
|
||||
},
|
||||
// 学生·待订正(复用同一页,默认过滤待订正)
|
||||
{
|
||||
path: "/stu/redo",
|
||||
name: "student_redo",
|
||||
component: () => import("@/views/homework/StudentHomeworks.vue"),
|
||||
meta: { title: "待订正", modId: 32, role: "student", initTab: "redo" },
|
||||
},
|
||||
|
||||
/* ---- 旧版路径兼容重定向(书签、残留的平台模块配置) ---- */
|
||||
{ path: "/details", redirect: "/list/detail" },
|
||||
{ path: "/newhomework", redirect: "/list/new" },
|
||||
{ path: "/edit", redirect: "/list/new" },
|
||||
{ path: "/manage", redirect: "/homeworklist" },
|
||||
{ path: "/homework", redirect: "/accept" },
|
||||
{ path: "/homework/newhomework", redirect: "/list/new" },
|
||||
{ path: "/homework/edit", redirect: "/list/new" },
|
||||
{ path: "/homework/homework_accept", redirect: "/accept" },
|
||||
{ path: "/homework/homework_list", redirect: "/list" },
|
||||
{ path: "/homework/manage", redirect: "/homeworklist" },
|
||||
{ path: "/homework/homework_details", redirect: "/list/detail" },
|
||||
{ path: "/homework/statistics", redirect: "/stats" },
|
||||
{ path: "/homework/setting", redirect: "/setting" },
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* 课程作业前端类型契约(P1-7)
|
||||
*
|
||||
* 与后端 VO/DTO 一一对齐:
|
||||
* - HomeworkVo → 后端 model/homework/HomeworkVo.java(Jackson 默认驼峰序列化)
|
||||
* - HomeworkDetailVo → 后端 model/homework/HomeworkDetailVo.java(@JsonProperty 保留旧字段名)
|
||||
* - BasePageResult → 后端 model/BasePageResult.java
|
||||
* - HomeworkPageResult extends BasePageResult → 后端 model/homework/HomeworkPageResult.java
|
||||
* - UpFileResultVo → 后端 model/homework/UpFileResultVo.java(@JsonProperty)
|
||||
* - UpFileVo → 后端 model/homework/UpFileVo.java
|
||||
* - InitializeRedVo → 后端 model/homework/InitializeRedVo.java(@JsonProperty)
|
||||
* - HomeworkStatisticsVo → 后端 model/homework/HomeworkStatisticsVo.java
|
||||
* - HomeworkRequest → 后端 model/homework/HomeworkRequest.java
|
||||
* - HomeworkQuery → 后端 model/homework/HomeworkQuery.java + BasePageQuery.java
|
||||
*/
|
||||
|
||||
// ============================ 分页外壳 ============================
|
||||
|
||||
/** 统一分页响应外壳(对齐 BasePageResult.java) */
|
||||
export interface BasePageResult<T> {
|
||||
total: number;
|
||||
content: T[];
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
hasNext: boolean;
|
||||
}
|
||||
|
||||
/** 作业分页结果(对齐 HomeworkPageResult.java,扩展 unreadCount) */
|
||||
export interface HomeworkPageResult<T> extends BasePageResult<T> {
|
||||
/** 当前用户未读作业总数 */
|
||||
unreadCount: number;
|
||||
}
|
||||
|
||||
// ============================ 作业列表项 ============================
|
||||
|
||||
/** 作业视图对象(对齐 HomeworkVo.java,Jackson 默认驼峰序列化) */
|
||||
export interface HomeworkVo {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
type: number;
|
||||
typeDesc: string;
|
||||
publisherAccount: string;
|
||||
publisherName: string;
|
||||
deptId: string;
|
||||
deptName: string;
|
||||
status: number;
|
||||
statusDesc: string;
|
||||
topmost: number;
|
||||
/** 已读人数 */
|
||||
readCount: number;
|
||||
/** 应读人数 */
|
||||
allowCount: number;
|
||||
/** 当前用户是否已读 */
|
||||
read: boolean;
|
||||
/** 是否撤回:0 否 / 1 是 */
|
||||
isPulled: number;
|
||||
/** 定时发送时间 */
|
||||
timingSendTime: string | null;
|
||||
/** 父作业ID(转发时关联原作业) */
|
||||
parentId: number | null;
|
||||
/** 短信发送标记 */
|
||||
smsFlag: number;
|
||||
/** 接收对象类型:class=班级(学生场景) */
|
||||
receiveType?: string;
|
||||
/** 接收列表:"all" 或 班级 id 逗号串 */
|
||||
receiveList: string | null;
|
||||
/** ===== 课程作业 v1(教师核心闭环)===== */
|
||||
/** 学科 */
|
||||
subject?: string;
|
||||
/** 截止时间 */
|
||||
deadline?: string | null;
|
||||
/** 提交方式:text/image/attach/none */
|
||||
submitType?: string[];
|
||||
/** 作业流程状态:draft 草稿 / collecting 收集中 / closed 已截止 / done 已完成 */
|
||||
hwStatus?: HomeworkStatus;
|
||||
hwStatusDesc?: string;
|
||||
/** 提交/批改进度(草稿或仅查阅为 null) */
|
||||
progress?: HomeworkProgress | null;
|
||||
createBy: string | null;
|
||||
createTime: string | null;
|
||||
updateBy: string | null;
|
||||
updateTime: string | null;
|
||||
}
|
||||
|
||||
// ============================ 作业详情 ============================
|
||||
|
||||
/** 作业详情视图对象(对齐 HomeworkDetailVo.java,@JsonProperty 保留旧字段名) */
|
||||
export interface HomeworkDetailVo {
|
||||
serid: number;
|
||||
title: string;
|
||||
/** 作业内容(旧字段名 Contents) */
|
||||
Contents: string;
|
||||
/** 发布部门ID(旧字段名 DeptID) */
|
||||
DeptID: string;
|
||||
/** 发布部门名称(旧字段名 DeptName) */
|
||||
DeptName: string;
|
||||
/** 发布人姓名(旧字段名 TeaName) */
|
||||
TeaName: string;
|
||||
/** 发布时间(旧字段名 EditDate,已格式化为字符串) */
|
||||
EditDate: string;
|
||||
/** 应读人数 */
|
||||
numcount: number;
|
||||
/** 已读人数 */
|
||||
readnumcount: number;
|
||||
/** 接收人列表 */
|
||||
ReceiveList: string | null;
|
||||
/** 定时发送时间(旧字段名 DsfsTime,已格式化) */
|
||||
DsfsTime: string | null;
|
||||
/** 是否定时发送 */
|
||||
isDsfs: boolean;
|
||||
/** ===== 课程作业 v1 ===== */
|
||||
subject?: string;
|
||||
deadline?: string | null;
|
||||
submitType?: string[];
|
||||
hwStatus?: HomeworkStatus;
|
||||
hwStatusDesc?: string;
|
||||
progress?: HomeworkProgress | null;
|
||||
/** 附件列表(旧字段名 ChaoSend) */
|
||||
ChaoSend: HomeworkDetailFileItem[];
|
||||
/** 接收对象列表:"all" 或 班级 id 数组 */
|
||||
tlist: string | string[];
|
||||
/** 接收对象类型:class=班级 */
|
||||
receiveType?: string;
|
||||
/** 接收对象可读文本(全部班级 / 班级名串) */
|
||||
receiveText?: string;
|
||||
}
|
||||
|
||||
/** 作业详情附件项(对齐 HomeworkDetailVo.FileItem) */
|
||||
export interface HomeworkDetailFileItem {
|
||||
id: number;
|
||||
name: string;
|
||||
size: number;
|
||||
fileid: number;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
// ============================ 文件上传 ============================
|
||||
|
||||
/** 文件上传结果(对齐 UpFileResultVo.java,@JsonProperty) */
|
||||
export interface UpFileResultVo {
|
||||
fileid: number;
|
||||
name: string;
|
||||
fileurl: string;
|
||||
host: string;
|
||||
type: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
/** 附件视图对象(对齐 UpFileVo.java) */
|
||||
export interface UpFileVo {
|
||||
id: number;
|
||||
fileTrueName: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
fileType: string;
|
||||
topicId: number;
|
||||
topicType: string;
|
||||
}
|
||||
|
||||
// ============================ 一键已读 ============================
|
||||
|
||||
/** 一键已读结果(对齐 InitializeRedVo.java,@JsonProperty) */
|
||||
export interface InitializeRedVo {
|
||||
count: number;
|
||||
}
|
||||
|
||||
// ============================ 上一篇/下一篇 ============================
|
||||
|
||||
/** 上一篇/下一篇作业ID */
|
||||
export interface NextPrevVo {
|
||||
prevId: number;
|
||||
nextId: number;
|
||||
}
|
||||
|
||||
// ============================ 阅读情况 ============================
|
||||
|
||||
/** 阅读情况分组项(后端 Map 返回,字段名固定) */
|
||||
export interface ReadGroup {
|
||||
GroupName: string;
|
||||
teaslist: ReadGroupUser[];
|
||||
}
|
||||
|
||||
/** 阅读情况用户项(后端 Map 返回) */
|
||||
export interface ReadGroupUser {
|
||||
account: string;
|
||||
teaname: string;
|
||||
states: 'read' | 'unread';
|
||||
}
|
||||
|
||||
// ============================ 统计 ============================
|
||||
|
||||
/** 作业统计视图(对齐 HomeworkStatisticsVo.java) */
|
||||
export interface HomeworkStatisticsVo {
|
||||
total: number;
|
||||
publishedCount: number;
|
||||
totalReadRecords: number;
|
||||
unreadForCurrentUser: number;
|
||||
byPublisher: { publisherName: string; count: number }[];
|
||||
byMonth: { year: number; month: number; count: number }[];
|
||||
// ===== 课程作业口径(v1,可选) =====
|
||||
/** 我布置的作业数 */
|
||||
myAssignCount?: number;
|
||||
/** 待批改总量 */
|
||||
pendingGrade?: number;
|
||||
/** 平均提交率(%) */
|
||||
avgSubmitRate?: number;
|
||||
/** 按作业进度 */
|
||||
byHomework?: { id: number; title: string; subject: string; hwStatus: HomeworkStatus } & HomeworkProgress[];
|
||||
/** 分数分布 */
|
||||
scoreDist?: { label: string; key: string; count: number }[];
|
||||
}
|
||||
|
||||
// ============================ 课程作业 v1:提交 / 批改 ============================
|
||||
|
||||
/** 作业流程状态 */
|
||||
export type HomeworkStatus = "draft" | "collecting" | "closed" | "done";
|
||||
|
||||
/** 单个提交状态 */
|
||||
export type SubmitStatus = "unsubmitted" | "submitted" | "graded" | "needRedo" | "redone";
|
||||
|
||||
/** 提交/批改进度 */
|
||||
export interface HomeworkProgress {
|
||||
/** 应交人数 */
|
||||
should: number;
|
||||
/** 已交人数 */
|
||||
submitted: number;
|
||||
/** 已批改人数 */
|
||||
graded: number;
|
||||
/** 待批改人数 */
|
||||
pendingGrade: number;
|
||||
/** 待订正人数 */
|
||||
needRedo: number;
|
||||
/** 已订正人数 */
|
||||
redone: number;
|
||||
/** 未交人数 */
|
||||
unsubmitted: number;
|
||||
/** 提交率(%) */
|
||||
submitRate: number;
|
||||
/** 批改率(%) */
|
||||
gradeRate: number;
|
||||
}
|
||||
|
||||
/** 提交名单项(Submissions 接口返回,精简) */
|
||||
export interface SubmissionBrief {
|
||||
subId: number;
|
||||
studentId: number;
|
||||
studentName: string;
|
||||
submitStatus: SubmitStatus;
|
||||
submittedAt: string | null;
|
||||
hasGrade: boolean;
|
||||
score: number | null;
|
||||
level: string | null;
|
||||
isExcellent: boolean;
|
||||
}
|
||||
|
||||
/** 提交名单按班级分组 */
|
||||
export interface SubmissionGroup {
|
||||
className: string;
|
||||
students: SubmissionBrief[];
|
||||
}
|
||||
|
||||
/** 某作业提交情况结果 */
|
||||
export interface SubmissionsResultVo {
|
||||
homeworkId: number;
|
||||
title: string;
|
||||
subject: string;
|
||||
deadline: string | null;
|
||||
hwStatus: HomeworkStatus;
|
||||
hwStatusDesc: string;
|
||||
summary: HomeworkProgress;
|
||||
groups: SubmissionGroup[];
|
||||
}
|
||||
|
||||
/** 批改结果 */
|
||||
export interface GradeResult {
|
||||
score: number | null;
|
||||
level: string | null;
|
||||
comment: string;
|
||||
isExcellent: boolean;
|
||||
markedImages: string[];
|
||||
}
|
||||
|
||||
/** 单份提交详情(批改工作台用) */
|
||||
export interface SubmissionDetailVo {
|
||||
subId: number;
|
||||
homeworkId: number;
|
||||
homeworkTitle: string;
|
||||
subject: string;
|
||||
studentId: number;
|
||||
studentName: string;
|
||||
classId: string;
|
||||
className: string;
|
||||
submitStatus: SubmitStatus;
|
||||
submittedAt: string | null;
|
||||
text: string;
|
||||
images: string[];
|
||||
attachments: string[];
|
||||
grade: GradeResult | null;
|
||||
}
|
||||
|
||||
/** 批改入参 */
|
||||
export interface GradeRequest {
|
||||
subId: number;
|
||||
/** grade 保存批改 / redo 要求订正 / excellent 标为优秀 */
|
||||
action?: "grade" | "redo" | "excellent";
|
||||
score?: number | string;
|
||||
level?: string;
|
||||
comment?: string;
|
||||
isExcellent?: boolean;
|
||||
markedImages?: string[];
|
||||
}
|
||||
|
||||
// ============================ 请求入参 ============================
|
||||
|
||||
/** 作业发布/编辑入参(对齐 HomeworkRequest.java) */
|
||||
export interface HomeworkRequest {
|
||||
title: string;
|
||||
content?: string;
|
||||
/** 学科 */
|
||||
subject?: string;
|
||||
/** 截止时间 yyyy-MM-dd HH:mm:ss */
|
||||
deadline?: string;
|
||||
/** 提交方式:text/image/attach/none */
|
||||
submitType?: string[] | string;
|
||||
type?: number;
|
||||
deptId?: string;
|
||||
/** 是否允许逾期补交 */
|
||||
allowLate?: boolean;
|
||||
/** 截止提醒:如 ["1d","1h"] */
|
||||
remindBefore?: string[];
|
||||
/** 存草稿:true 则不发布 */
|
||||
draft?: boolean;
|
||||
deptName?: string;
|
||||
topmost?: number;
|
||||
/** 接收对象:"all" 或 班级 id 逗号串(receiveType=class) */
|
||||
receiveList?: string;
|
||||
/** 接收对象类型:class=班级 */
|
||||
receiveType?: string;
|
||||
/** 附件文件ID列表(逗号分隔串) */
|
||||
fileIds?: string;
|
||||
timingSendTime?: string;
|
||||
/** 短信发送标记 */
|
||||
smsFlag?: number;
|
||||
/** 转发源作业ID */
|
||||
sourceId?: number;
|
||||
}
|
||||
|
||||
/** 作业列表查询参数(对齐 HomeworkQuery.java + BasePageQuery.java) */
|
||||
export interface HomeworkQuery {
|
||||
pageIndex?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
status?: number;
|
||||
type?: number;
|
||||
/** 列表范围(管理端):"user" 只看自己发布的,不传=全部 */
|
||||
scope?: string;
|
||||
deptId?: string;
|
||||
/** 学科筛选 */
|
||||
subject?: string;
|
||||
/** 作业流程状态筛选:draft/collecting/closed/done,缺省或 all=全部 */
|
||||
hwStatus?: string;
|
||||
publisherAccount?: string;
|
||||
/** 开始时间(yyyy-MM-dd 短日期) */
|
||||
startTime?: string;
|
||||
/** 结束时间(yyyy-MM-dd 短日期) */
|
||||
endTime?: string;
|
||||
/** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */
|
||||
readType?: number;
|
||||
/** 排序:1 时间倒序 / 2 时间正序 */
|
||||
orderByTime?: string;
|
||||
}
|
||||
|
||||
// ============================ 字典/联系组 ============================
|
||||
|
||||
/** 系统字典下拉项 */
|
||||
export interface DictOption {
|
||||
k: number | string;
|
||||
v: string | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/** 发文部门管理列表项 */
|
||||
export interface DeptManageItem {
|
||||
SerID: number;
|
||||
CodeID: string;
|
||||
Name: string;
|
||||
Enable: number;
|
||||
}
|
||||
|
||||
/** 联系组列表项 */
|
||||
export interface ContactGroupItem {
|
||||
serid: number;
|
||||
GroupName: string;
|
||||
}
|
||||
|
||||
/** 联系组分页结果 */
|
||||
export interface ContactGroupPage {
|
||||
list: ContactGroupItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
/** 联系组用户项 */
|
||||
export interface ContactUser {
|
||||
account: string;
|
||||
name: string;
|
||||
userId?: number;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
/** 联系组用户分页结果 */
|
||||
export interface ContactUserPage {
|
||||
list: ContactUser[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
/** 组织树节点(基础平台返回) */
|
||||
export interface SchoolTreeNode {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ============================ 班级 ============================
|
||||
|
||||
/** 年级→班级树节点(叶子=班级,id 为数字班级 id,附学生数) */
|
||||
export interface ClassTreeNode {
|
||||
id: number;
|
||||
name: string;
|
||||
isParent: boolean;
|
||||
studentCount?: number;
|
||||
children?: ClassTreeNode[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ============================ v2 学生端 ============================
|
||||
|
||||
/** 当前视角角色(家长即登录学生账号,不单列) */
|
||||
export type ViewerRole = "teacher" | "student";
|
||||
|
||||
/** 学生对某作业的提交/批改态 */
|
||||
export interface StudentSubmissionInfo {
|
||||
subId: number;
|
||||
submitStatus: SubmitStatus;
|
||||
submittedAt: string | null;
|
||||
text: string;
|
||||
score: number | null;
|
||||
level: string | null;
|
||||
comment: string;
|
||||
isExcellent: boolean;
|
||||
images?: string[];
|
||||
attachments?: string[];
|
||||
grade?: GradeResult | null;
|
||||
}
|
||||
|
||||
/** 学生视角作业项 */
|
||||
export interface StudentHomeworkVo {
|
||||
id: number;
|
||||
title: string;
|
||||
subject: string;
|
||||
publisherName: string;
|
||||
deptName: string;
|
||||
deadline: string | null;
|
||||
createTime: string | null;
|
||||
content: string;
|
||||
submitType: string[];
|
||||
viewOnly: boolean;
|
||||
hwStatus?: HomeworkStatus;
|
||||
allowLate?: boolean;
|
||||
my: StudentSubmissionInfo | null;
|
||||
}
|
||||
|
||||
/** 今日/我的作业列表 */
|
||||
export interface MyHomeworksVo {
|
||||
buckets: { todo: number; submitted: number; graded: number; redo: number; all: number };
|
||||
list: StudentHomeworkVo[];
|
||||
}
|
||||
|
||||
/** 我的某作业详情(作业信息 + 我的提交/批改) */
|
||||
export interface MySubmissionVo {
|
||||
homework: { id: number; title: string; subject: string; content: string; deadline: string | null; submitType: string[]; publisherName: string; deptName: string };
|
||||
my: StudentSubmissionInfo | null;
|
||||
}
|
||||
|
||||
/** 学生提交作业入参 */
|
||||
export interface SubmitRequest {
|
||||
serid: number;
|
||||
text?: string;
|
||||
/** 图片 URL / dataURL 列表(逗号串或数组) */
|
||||
images?: string[] | string;
|
||||
/** 附件 id 列表(逗号串或数组) */
|
||||
attachments?: string[] | string;
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import draggable from "vuedraggable";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import UserCheckModal from "@/components/UserCheckModal.vue";
|
||||
import TypeSetBox from "@/components/TypeSetBox.vue";
|
||||
import { settingApi, baseApi } from "@/api/module/homework";
|
||||
import type { DeptManageItem, ContactGroupPage, ContactUserPage, SchoolTreeNode } from "@/types/homework";
|
||||
import Utils from "@/utils";
|
||||
|
||||
/* ============ Tab 切换(规范 §1:tabs-wrapper + Tabs type=card,切换只 v-show 内容区) ============ */
|
||||
const currentType = ref(1);
|
||||
|
||||
function tabChange(name: string) {
|
||||
currentType.value = Number(name);
|
||||
if (currentType.value === 1) {
|
||||
getDeptList();
|
||||
} else if (currentType.value === 3) {
|
||||
getGroupList();
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 发文部门(直接复用 UserBase 同源 TypeSetBox 卡片组件) ============ */
|
||||
interface DeptItem {
|
||||
SerID: number;
|
||||
CodeID: string;
|
||||
Name: string;
|
||||
Enable: number;
|
||||
}
|
||||
const deptData = ref<DeptItem[]>([]);
|
||||
const deptModal = ref(false);
|
||||
const deptForm = reactive({ name: "" });
|
||||
const deptFormRef = ref();
|
||||
const deptRule = {
|
||||
name: [{ required: true, message: "请输入发文部门名称", trigger: "blur" }],
|
||||
};
|
||||
|
||||
async function getDeptList() {
|
||||
try {
|
||||
const data: DeptManageItem[] = await settingApi.getDictMangeList().send();
|
||||
deptData.value = (data || []).map((d) => ({
|
||||
SerID: d.SerID,
|
||||
CodeID: d.CodeID,
|
||||
Name: d.Name,
|
||||
Enable: d.Enable ?? 1,
|
||||
}));
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "获取发文部门列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
function addDeptShow() {
|
||||
deptForm.name = "";
|
||||
deptModal.value = true;
|
||||
}
|
||||
|
||||
async function deptModalSave() {
|
||||
const valid = await deptFormRef.value?.validate();
|
||||
if (!valid) {
|
||||
Message.info("请填写红色星号内容!");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await settingApi.addDeptDic(deptForm.name.trim()).send();
|
||||
Message.success("操作成功!");
|
||||
deptModal.value = false;
|
||||
await getDeptList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
function findDept(id: string): DeptItem | undefined {
|
||||
return deptData.value.find((x) => String(x.SerID) === id);
|
||||
}
|
||||
|
||||
/** TypeSetBox 启用/停用(保留确认弹窗) */
|
||||
function onDeptToggle(id: string) {
|
||||
const item = findDept(id);
|
||||
if (item) toggleDeptStatus(item);
|
||||
}
|
||||
|
||||
/** TypeSetBox 内联编辑部门名(组件已拦截空值) */
|
||||
async function onDeptRename(id: string, name: string) {
|
||||
const item = findDept(id);
|
||||
if (!item) return;
|
||||
try {
|
||||
await settingApi.updateDeptDic(item.SerID, name.trim(), item.Enable).send();
|
||||
Message.success("操作成功!");
|
||||
await getDeptList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
function onDeptRemove(id: string) {
|
||||
const item = findDept(id);
|
||||
if (item) deleteDept(item);
|
||||
}
|
||||
|
||||
async function deleteDept(item: DeptItem) {
|
||||
await MyConfirm({
|
||||
title: "删除",
|
||||
msg: `确定删除“ ${item.Name} ”这个发文部门吗?`,
|
||||
info: "删除后不可恢复",
|
||||
async onOk() {
|
||||
try {
|
||||
await settingApi.delDept(item.SerID).send();
|
||||
Message.success("操作成功!");
|
||||
await getDeptList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "删除失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleDeptStatus(item: DeptItem) {
|
||||
const enable = item.Enable === 1;
|
||||
await MyConfirm({
|
||||
msg: `确定${enable ? "停用" : "启用"}发文部门「${item.Name}」吗?`,
|
||||
title: "修改状态",
|
||||
async onOk() {
|
||||
try {
|
||||
await settingApi.updateDeptDic(item.SerID, item.Name, enable ? 0 : 1).send();
|
||||
item.Enable = enable ? 0 : 1;
|
||||
Message.success("操作成功!");
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "操作失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* 拖动排序:提交当前顺序的 SerID 数组 */
|
||||
async function deptSortSave() {
|
||||
const ids = deptData.value.map((x: DeptItem) => x.SerID);
|
||||
try {
|
||||
await settingApi.sortDeptDic(ids.join(",")).send();
|
||||
Message.success("排序成功");
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "排序失败");
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 自定义组(规范 §5 联系组管理页形态) ============ */
|
||||
interface GroupItem {
|
||||
serid: number;
|
||||
GroupName: string;
|
||||
count: number;
|
||||
}
|
||||
const groupData = ref<GroupItem[]>([]);
|
||||
const groupTotal = ref(0);
|
||||
const groupPage = ref(1);
|
||||
const groupCount = ref(parseInt(localStorage.getItem("customPageCount") || "10"));
|
||||
|
||||
async function getGroupList() {
|
||||
try {
|
||||
const data: any = await settingApi.getGroup(groupPage.value, groupCount.value).send();
|
||||
groupData.value = (data.list || []).map((d: any) => ({
|
||||
serid: d.serid,
|
||||
GroupName: d.GroupName,
|
||||
count: d.count ?? 0,
|
||||
}));
|
||||
groupTotal.value = data.total || 0;
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "获取自定义组列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
const groupModal = ref(false);
|
||||
const groupEditingId = ref<number | null>(null);
|
||||
const groupForm = reactive({ name: "" });
|
||||
const groupFormRef = ref();
|
||||
const groupRule = {
|
||||
name: [{ required: true, message: "请输入联系组名称", trigger: "blur" }],
|
||||
};
|
||||
|
||||
function addGroupShow() {
|
||||
groupEditingId.value = null;
|
||||
groupForm.name = "";
|
||||
groupModal.value = true;
|
||||
}
|
||||
|
||||
function editGroupShow(row: GroupItem) {
|
||||
groupEditingId.value = row.serid;
|
||||
groupForm.name = row.GroupName;
|
||||
groupModal.value = true;
|
||||
}
|
||||
|
||||
async function groupModalSave() {
|
||||
const valid = await groupFormRef.value?.validate();
|
||||
if (!valid) {
|
||||
Message.info("请填写红色星号内容!");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (groupEditingId.value == null) {
|
||||
await settingApi.addGroup(groupForm.name.trim()).send();
|
||||
} else {
|
||||
await settingApi.editGroup(groupEditingId.value, groupForm.name.trim()).send();
|
||||
}
|
||||
Message.success("操作成功!");
|
||||
groupModal.value = false;
|
||||
await getGroupList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGroup(row: GroupItem) {
|
||||
await MyConfirm({
|
||||
title: "删除",
|
||||
msg: `确定删除“ ${row.GroupName} ”这个组吗?`,
|
||||
info: "删除后不可恢复",
|
||||
async onOk() {
|
||||
try {
|
||||
await settingApi.delGroup(String(row.serid)).send();
|
||||
Message.success("操作成功!");
|
||||
// 删除后智能翻页:当前页仅剩一条则回退一页
|
||||
if (groupData.value.length <= 1 && groupPage.value > 1) groupPage.value -= 1;
|
||||
await getGroupList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "删除失败");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- 查看联系组用户 ---------- */
|
||||
const seeUserModal = ref(false);
|
||||
const seeUserGid = ref(0);
|
||||
const seeUserGroupName = ref("");
|
||||
const seeUserData = ref<{ account: string; name: string }[]>([]);
|
||||
const seeUserTotal = ref(0);
|
||||
const seeUserPage = ref(1);
|
||||
const seeUserColumns = [
|
||||
{ title: "序号", type: "index", width: 70 },
|
||||
{ title: "账号", key: "account", align: "center" },
|
||||
{ title: "真实姓名", key: "name", width: 160, align: "center" },
|
||||
];
|
||||
|
||||
async function seeUserShow(row: GroupItem) {
|
||||
seeUserGid.value = row.serid;
|
||||
seeUserGroupName.value = row.GroupName;
|
||||
seeUserPage.value = 1;
|
||||
await loadSeeUser();
|
||||
seeUserModal.value = true;
|
||||
}
|
||||
|
||||
async function loadSeeUser() {
|
||||
try {
|
||||
const data: ContactUserPage = await settingApi.getContactsTea(seeUserGid.value, seeUserPage.value, 5).send();
|
||||
seeUserData.value = (data.list || []).map((d) => ({ account: d.account, name: d.name }));
|
||||
seeUserTotal.value = data.total || 0;
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "获取用户列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 设置联系组用户(规范选人组件,打开前回显已有成员) ---------- */
|
||||
const setUserCheckRef = ref();
|
||||
const setUserGid = ref(0);
|
||||
const setUserIds = ref<number[]>([]);
|
||||
// 设置自定义组成员只需要基础平台组织树,无需「基础平台/自定义组」两个 tab
|
||||
const setUserSources = [
|
||||
{
|
||||
name: "基础平台",
|
||||
loader: () => baseApi.getSchoolAll().send(),
|
||||
},
|
||||
];
|
||||
|
||||
async function settingUserShow(row: GroupItem) {
|
||||
setUserGid.value = row.serid;
|
||||
setUserIds.value = [];
|
||||
try {
|
||||
const data: ContactUserPage = await settingApi.getContactsTea(row.serid, 0, 0).send();
|
||||
setUserIds.value = (data.list || [])
|
||||
.map((d) => Number(d.userId ?? d.id))
|
||||
.filter((n: number) => Number.isFinite(n) && n > 0);
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "获取已有用户失败");
|
||||
}
|
||||
await nextTick();
|
||||
setUserCheckRef.value.show();
|
||||
}
|
||||
|
||||
async function settingUserSubmit(users: SchoolTreeNode[]) {
|
||||
if (!users.length) {
|
||||
Message.info("请选择用户!");
|
||||
return;
|
||||
}
|
||||
// on-submit 返回的是用户对象数组({id,name,group}),取 id 拼接
|
||||
const ids = users.map((x) => (typeof x === "object" && x != null ? x.id : x)).join(",");
|
||||
try {
|
||||
await settingApi.setContactsTea(setUserGid.value, ids).send();
|
||||
Message.success("设置成功");
|
||||
// 刷新组列表,组员数即时更新
|
||||
await getGroupList();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "设置失败");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDeptList();
|
||||
});
|
||||
|
||||
/* ---------- 自定义组表格(规范 §5:序号/组名/操作图标列 width 250) ---------- */
|
||||
const groupColumns = [
|
||||
{ title: "序号", type: "index", width: 80, align: "center" },
|
||||
{ title: "组名", key: "GroupName", align: "left" },
|
||||
{ title: "组员数", key: "count", width: 100, align: "center" },
|
||||
{
|
||||
title: "操作",
|
||||
width: 250,
|
||||
align: "center",
|
||||
render: (h: any, params: any) => {
|
||||
const row: GroupItem = params.row;
|
||||
return [
|
||||
Utils.operatingHandle(h, "查看用户", "icon-browse", () => seeUserShow(row)),
|
||||
Utils.operatingHandle(h, "编辑", "icon-edit", () => editGroupShow(row)),
|
||||
Utils.operatingHandle(h, "删除", "icon-ashbin", () => deleteGroup(row)),
|
||||
Utils.operatingHandle(h, "设置用户", "icon-user-filling", () => settingUserShow(row)),
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isSearch: true, isPage: true }));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tabs-wrapper">
|
||||
<Tabs class="tabs-panel" type="card" :value="String(currentType)" @on-click="tabChange">
|
||||
<TabPane label="发文部门" name="1"></TabPane>
|
||||
<TabPane label="自定义组" name="3"></TabPane>
|
||||
</Tabs>
|
||||
|
||||
<!-- 发文部门 -->
|
||||
<div class="main-container" v-show="currentType === 1">
|
||||
<Alert show-icon>设置说明:拖动卡片调整部门排序;停用的部门不出现在「作业发送」的部门下拉中</Alert>
|
||||
<div class="search-wrap fix">
|
||||
<Button class="fl" type="primary" @click="addDeptShow">新增部门</Button>
|
||||
</div>
|
||||
<draggable
|
||||
class="type_set-wrap fix"
|
||||
tag="ul"
|
||||
v-model="deptData"
|
||||
group="homeworkDept"
|
||||
item-key="SerID"
|
||||
@change="deptSortSave"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<li>
|
||||
<TypeSetBox
|
||||
:type="{ id: String(element.SerID), name: element.Name }"
|
||||
:active="element.Enable === 1"
|
||||
@active="onDeptToggle"
|
||||
@edit="onDeptRename"
|
||||
@remove="onDeptRemove"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<!-- 自定义组 -->
|
||||
<div class="main-container" v-show="currentType === 3">
|
||||
<div class="search-wrap fix">
|
||||
<Button class="fl" type="primary" @click="addGroupShow">新建联系组</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="groupColumns"
|
||||
:data="groupData"
|
||||
class="link-table table-bottom-box"
|
||||
:height="groupTableHeight"
|
||||
no-data-text="暂无数据"
|
||||
></Table>
|
||||
<div class="page-panel">
|
||||
<div class="fl ml-15 align-middle"></div>
|
||||
<Page
|
||||
class="page-box fr"
|
||||
:total="groupTotal"
|
||||
:current="groupPage"
|
||||
:page-size="groupCount"
|
||||
:page-size-opts="[10, 20, 30, 40]"
|
||||
@on-page-size-change="
|
||||
(size: number) => {
|
||||
groupPage = 1;
|
||||
groupCount = size;
|
||||
localStorage.setItem('customPageCount', String(size));
|
||||
getGroupList();
|
||||
}
|
||||
"
|
||||
@on-change="
|
||||
(p: number) => {
|
||||
groupPage = p;
|
||||
getGroupList();
|
||||
}
|
||||
"
|
||||
placement="top"
|
||||
show-total
|
||||
show-sizer
|
||||
></Page>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增发文部门 Modal(编辑走 TypeSetBox 内联编辑) -->
|
||||
<Modal v-model="deptModal" :mask-closable="false" width="450px" title="新增部门">
|
||||
<Form ref="deptFormRef" :model="deptForm" :rules="deptRule" :label-width="110">
|
||||
<FormItem label="发文部门名称:" prop="name">
|
||||
<Input placeholder="请输入发文部门名称" v-model="deptForm.name" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="primary" @click="deptModalSave">提交</Button>
|
||||
<Button @click="deptModal = false">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- 新建/编辑联系组 Modal(规范 §5:450px + label-width 100 + 提交/取消) -->
|
||||
<Modal v-model="groupModal" :mask-closable="false" width="450px" :title="groupEditingId == null ? '新建联系组' : '编辑联系组'">
|
||||
<Form ref="groupFormRef" :model="groupForm" :rules="groupRule" :label-width="100">
|
||||
<FormItem label="联系组名称:" prop="name">
|
||||
<Input placeholder="请输入联系组名称" v-model="groupForm.name" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="primary" @click="groupModalSave">提交</Button>
|
||||
<Button @click="groupModal = false">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- 查看联系组用户 Modal(规范 §5:Modal 内小表格 + small 分页) -->
|
||||
<Modal v-model="seeUserModal" :title="`查看联系组用户${seeUserGroupName ? ' - ' + seeUserGroupName : ''}`">
|
||||
<Table height="260" :columns="seeUserColumns" :data="seeUserData" no-data-text="暂无用户"></Table>
|
||||
<div class="small-pager">
|
||||
<Page
|
||||
:current="seeUserPage"
|
||||
:page-size="5"
|
||||
:total="seeUserTotal"
|
||||
@on-change="
|
||||
(p: number) => {
|
||||
seeUserPage = p;
|
||||
loadSeeUser();
|
||||
}
|
||||
"
|
||||
size="small"
|
||||
show-total
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button @click="seeUserModal = false">关闭</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- 选择用户弹窗(设置联系组用户) -->
|
||||
<UserCheckModal ref="setUserCheckRef" :sources="setUserSources" :userids="setUserIds" @on-submit="settingUserSubmit" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tab-body {
|
||||
min-height: 300px;
|
||||
}
|
||||
.type_set-wrap {
|
||||
margin: 12px 0 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
/* 拖动排序手型(与 UserBase AppCategory 页面级样式一致,组件内不含) */
|
||||
.type_set-wrap :deep(.type_name) {
|
||||
cursor: move;
|
||||
}
|
||||
.small-pager {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user