feat(M5): P0 契约层 + 审批/应用设置下线

- types/homeworkV2.ts:V2 全量类型契约(五态/枚举/矩阵/版本/通知)
- teacherHomework/studentHomework/notification 三客户端(28 端点)
- §6 审批功能下线:删 ApprovalList/ApprovalSetting/ApprovalFlowEditor,
  types/api/menu/menuRoute/App/mock 同步清理,DROPPED_MOD_IDS={5,6}
- §7 应用设置下线:删 HomeworkSetting/TypeSetBox/UserCheckInput,
  SettingApi 与 dict/contacts 死代码清理,DROPPED_MOD_IDS 扩 {21}
- 新增 V2 组件:TeacherHomeworks(已发布+草稿箱)、ReviewModal(批阅弹窗)、
  AttachmentUpload(附件上传)——尚未挂路由,页面切换随后续阶段
This commit is contained in:
wanghu
2026-09-16 02:29:32 +08:00
parent d75aab8d07
commit e6639f7925
40 changed files with 7734 additions and 1535 deletions
+129
View File
@@ -0,0 +1,129 @@
<template>
<!-- V2 附件上传:emit AttachmentItem[](fileId/fileUrl/fileType/fileName/sort)。
fileUrl 统一存 /api/file/Preview?id=(浏览器可直接渲染;下载走 Down 直链)。 -->
<div class="attachment-upload">
<Upload
:on-success="onSuccess"
:on-remove="onRemove"
:default-file-list="fileList"
:action="uploadUrl"
:accept="accept"
:multiple="multiple"
:format="formatArr"
:max-size="204800"
:on-format-error="onFormatError"
:on-exceeded-size="onExceeded"
>
<Button><Icon type="ios-cloud-upload-outline"></Icon>{{ buttonText || (imageOnly ? "拍照/上传图片" : "选择文件") }}</Button>
</Upload>
<span class="upload-tip">大小不超过 200MB</span>
</div>
</template>
<script lang="ts">
import { Message } from "view-ui-plus";
import config from "@/assets/config/host.config";
import type { AttachmentItem } from "@/types/homeworkV2";
/** base/FileUpload(form-data: file)返回体:{code,msg,data:{fileid,name,fileurl,host,type,size}} */
interface FileUploadResp {
code: number;
msg?: string;
data: { fileid: number; name: string; fileurl: string; host: string; type: string; size: number };
}
export default {
name: "AttachmentUpload",
props: {
/** 已选附件(v-model) */
modelValue: { type: Array as () => AttachmentItem[], default: () => [] },
/** 仅图片(拍照提交场景) */
imageOnly: { type: Boolean, default: false },
multiple: { type: Boolean, default: true },
/** 留空时按 imageOnly 自动取「拍照/上传图片」或「选择文件」 */
buttonText: { type: String, default: "" },
},
emits: ["update:modelValue"],
data() {
return {
uploadUrl: (config.isMock ? "" : config.serviceHost) + "/api/file/Upload",
};
},
computed: {
accept(): string {
return this.imageOnly ? "image/*" : "";
},
formatArr(): string[] {
return this.imageOnly
? ["jpg", "jpeg", "gif", "png", "bmp", "webp"]
: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "xls", "doc", "txt", "docx", "xlsx", "zip", "rar", "pdf", "ppt", "pptx", "wps", "mp3", "mp4"];
},
/** iview Upload 回显列表(只读 name;移除按项过滤) */
fileList(): any[] {
return (this.modelValue || []).filter((a) => a.fileName).map((a) => ({ name: a.fileName, url: this.previewUrl(a), _item: a }));
},
},
methods: {
previewUrl(a: AttachmentItem): string {
return a.fileId ? `${config.isMock ? "" : config.serviceHost}/api/file/Preview?id=${a.fileId}` : a.fileUrl;
},
onSuccess(resp: FileUploadResp, file: any) {
if (!resp || resp.code !== 0 || !resp.data) {
Message.error(resp?.msg || "上传失败");
return;
}
const d = resp.data;
const item: AttachmentItem = {
fileId: d.fileid,
fileUrl: `/api/file/Preview?id=${d.fileid}`,
fileType: this.imageType(d) ? "image" : "file",
fileName: d.name || file.name,
sort: this.modelValue.length,
};
this.$emit("update:modelValue", [...this.modelValue, item]);
},
imageType(d: FileUploadResp["data"]): boolean {
const t = String(d.type || "").toLowerCase();
if (t.startsWith("image")) return true;
return /\.(jpe?g|gif|png|bmp|webp)$/i.test(d.name || d.fileurl || "");
},
onRemove(file: any) {
// 回显项挂 _item;刚上传项从 response.data.fileid 匹配
const target: AttachmentItem | undefined = file._item || (file.response?.data ? { fileId: file.response.data.fileid } as AttachmentItem : undefined);
if (!target) return;
const arr = this.modelValue.filter((a) => a !== target && (target.fileId ? a.fileId !== target.fileId : a.fileUrl !== target.fileUrl));
this.$emit("update:modelValue", arr.map((a, i) => ({ ...a, sort: i })));
},
onFormatError() {
Message.error(this.imageOnly ? "仅支持图片文件" : "不支持的上传类型!");
},
onExceeded() {
Message.warning("文件大小超过 200MB 限制");
},
},
};
</script>
<style lang="scss" scoped>
.attachment-upload {
display: inline-block;
width: 100%;
:deep(.ivu-upload) {
display: inline-flex;
align-items: center;
}
:deep(.ivu-upload-list) {
display: block;
margin-top: 8px;
}
:deep(.ivu-upload-list-file) {
padding: 4px 0;
}
.upload-tip {
margin-left: 10px;
color: #f90;
font-size: 12px;
line-height: 1;
}
}
</style>