|
@@ -0,0 +1,401 @@
|
|
|
|
+type Store = {
|
|
|
|
+ /** 当前用户 */
|
|
|
|
+ user: {
|
|
|
|
+ id: number;
|
|
|
|
+ /** 身份证号 */
|
|
|
|
+ identityNumber: string;
|
|
|
|
+ /** 学号 */
|
|
|
|
+ studentCodeList: string[];
|
|
|
|
+ /** 显示的姓名 */
|
|
|
|
+ displayName: string;
|
|
|
|
+ /** @deprecated 姓名,但可能为空 */
|
|
|
|
+ name: string;
|
|
|
|
+ /** 学生手机号 */
|
|
|
|
+ phoneNumber: string;
|
|
|
|
+ /** 属于那个学校 */
|
|
|
|
+ schoolDomain: SchoolDomain;
|
|
|
|
+ /** 顶级机构id */
|
|
|
|
+ rootOrgId: number;
|
|
|
|
+ /** 底照URL */
|
|
|
|
+ photoPath: string;
|
|
|
|
+ /** 机构名称 */
|
|
|
|
+ orgName: string;
|
|
|
|
+ /** 用户的另一个id */
|
|
|
|
+ key: string;
|
|
|
|
+ /** 登录认证信息 */
|
|
|
|
+ token: string;
|
|
|
|
+ /** 专业 */
|
|
|
|
+ specialty: string;
|
|
|
|
+ };
|
|
|
|
+ /** 学生端配置。 Q代表qmth */
|
|
|
|
+ QECSConfig: {
|
|
|
|
+ /** 学号登录别名 */
|
|
|
|
+ STUDENT_CODE_LOGIN_ALIAS: string;
|
|
|
|
+ /** 身份证号登录别名 */
|
|
|
|
+ IDENTITY_NUMBER_LOGIN_ALIAS: string;
|
|
|
|
+ /** 防作弊配置 !!!需对json进行转换!!! */
|
|
|
|
+ PREVENT_CHEATING_CONFIG: Partial<
|
|
|
|
+ [
|
|
|
|
+ "FULL_SCREEN_TOP",
|
|
|
|
+ "DISABLE_REMOTE_ASSISTANCE",
|
|
|
|
+ "DISABLE_VIRTUAL_CAMERA",
|
|
|
|
+ "DISABLE_MULTISCREEN"
|
|
|
|
+ ]
|
|
|
|
+ >;
|
|
|
|
+ /** 学校是否有定制logo !!!需对json进行转换!!! */
|
|
|
|
+ IS_CUSTOM_MENU_LOGO: boolean;
|
|
|
|
+ /** 定制logo的URL */
|
|
|
|
+ CUS_MENU_LOGO_FILE_URL: string;
|
|
|
|
+ /** 学生端控制台设置 */
|
|
|
|
+ STUDENT_CLIENT_CONSOLE_CONFIG: string;
|
|
|
|
+ /** 首页背景图 */
|
|
|
|
+ LOGO_FILE_URL: string;
|
|
|
|
+ /** 产品名称,登录页显示 */
|
|
|
|
+ OE_STUDENT_SYS_NAME: string;
|
|
|
|
+ /** 登录页可选的登录类型 !!!需对json进行转换!!! */
|
|
|
|
+ LOGIN_TYPE: Partial<["STUDENT_CODE", "IDENTITY_NUMBER"]>;
|
|
|
|
+ ROOT_ORG_ID: string;
|
|
|
|
+ /** @deprecated 登录支持的客户端类型。新版只支持Electron包。 */
|
|
|
|
+ LOGIN_SUPPORT: Partial<["NATIVE", "BROWSER"]>;
|
|
|
|
+ };
|
|
|
|
+ /** 电脑时间管理 */
|
|
|
|
+ sysTime: {
|
|
|
|
+ /** 与服务器差异 */
|
|
|
|
+ difference: number;
|
|
|
|
+ /** 网络延迟 通过网络请求来判断 */
|
|
|
|
+ rtt: number;
|
|
|
|
+ /** 上次的Date.now() 有的电脑上存在Date.now()不更新 */
|
|
|
|
+ lastTime: number;
|
|
|
|
+ /** 判断时钟是否不走动了 */
|
|
|
|
+ isTimerFrozen: boolean;
|
|
|
|
+ };
|
|
|
|
+ /** 网络状况 */
|
|
|
|
+ network: {
|
|
|
|
+ /** 网络状况是即时的,如何获取? */
|
|
|
|
+ /** computed */
|
|
|
|
+ isOK: boolean;
|
|
|
|
+ /** 百度不通,则网络不通 */
|
|
|
|
+ pingBaidu: boolean;
|
|
|
|
+ /** 云平台api是否通畅 */
|
|
|
|
+ pingQmth: boolean;
|
|
|
|
+ /** 阿里云文件存储 阿里云日志 分开? */
|
|
|
|
+ pingAli: boolean;
|
|
|
|
+ /** websocket */
|
|
|
|
+ pingWebSocket: boolean;
|
|
|
|
+ };
|
|
|
|
+ /** 菜单项 */
|
|
|
|
+ menus: Array<{
|
|
|
|
+ /** 菜单名称 */
|
|
|
|
+ name: string;
|
|
|
|
+ /** 菜单指向链接,是path */
|
|
|
|
+ link: string;
|
|
|
|
+ /** 此处要细化为 type = 'STU_NOTICE' | 'STU_MODIFY_PWD',在router中也要用 */
|
|
|
|
+ routeCode: string;
|
|
|
|
+ }>;
|
|
|
|
+ /** app下载 */
|
|
|
|
+ appDownload: {
|
|
|
|
+ /** 是否开放下载 */
|
|
|
|
+ enabled: boolean;
|
|
|
|
+ /** 下载链接 */
|
|
|
|
+ url: string;
|
|
|
|
+ };
|
|
|
|
+ /** 站内消息 */
|
|
|
|
+ siteMessage: {
|
|
|
|
+ messages: SiteMessage[];
|
|
|
|
+ /** 第一个未读消息 popup用 computed */
|
|
|
|
+ firstUnreadMessage: SiteMessage;
|
|
|
|
+ /** 忽略的站内消息,不弹popup */
|
|
|
|
+ ignoreMessageIds: number[];
|
|
|
|
+ /** 未读消息总数 */
|
|
|
|
+ unreadCount: number;
|
|
|
|
+ };
|
|
|
|
+ /** 在线考试待考列表 */
|
|
|
|
+ examList: OnlineExam[];
|
|
|
|
+ /** 在线考试已结束的考试列表 */
|
|
|
|
+ endedExamList: OnlineExam[];
|
|
|
|
+ /** 在线作业 */
|
|
|
|
+ homeworkList: OnlineExam[];
|
|
|
|
+ /** 在线练习 */
|
|
|
|
+ practiceList: PracticeExam[];
|
|
|
|
+ /** 当前开考的exam,非断点续考才会需要发出这么网络请求,正常开考是从前页面带过来的 */
|
|
|
|
+ exam: {
|
|
|
|
+ /** 考生的考试记录id */
|
|
|
|
+ examRecordDataId: number;
|
|
|
|
+ /** 考试批次id */
|
|
|
|
+ examId: number;
|
|
|
|
+ /** 课程名称 */
|
|
|
|
+ courseName: string;
|
|
|
|
+ /** 是否开启微信作答 */
|
|
|
|
+ weixinAnswerEnabled: boolean;
|
|
|
|
+ /** 是否开启人脸比对 */
|
|
|
|
+ faceCheckEnabled: boolean;
|
|
|
|
+ /** 是否开启活体检测 */
|
|
|
|
+ faceLivenessEnabled: boolean;
|
|
|
|
+ /** 活体检测选项 */
|
|
|
|
+ faceLivenessOption: {
|
|
|
|
+ /** 进入考试后多久开启活体检测(分钟) */
|
|
|
|
+ faceVerifyMinute: number;
|
|
|
|
+ /** 活体检测类型 S1: FaceID S2: 自研活体 */
|
|
|
|
+ identificationOfLivingBodyScheme: "S1" | "S2";
|
|
|
|
+ };
|
|
|
|
+ /** 抓拍间隔(秒) */
|
|
|
|
+ SNAPSHOT_INTERVAL: number;
|
|
|
|
+ /** 考试冻结(秒) */
|
|
|
|
+ FREEZE_TIME: number;
|
|
|
|
+ /** 练习显示答案的类型 IN_PRACTICE:在练习过程中显示答案 NO_ANSWER:练习过程中不显示答案 */
|
|
|
|
+ PRACTICE_TYPE: "IN_PRACTICE" | "NO_ANSWER";
|
|
|
|
+ /** 试卷结构 */
|
|
|
|
+ paperStruct: PaperStruct;
|
|
|
|
+ /** 试题结构 */
|
|
|
|
+ examQuestionList: ExamQuestion[];
|
|
|
|
+ };
|
|
|
|
+ // /** 考试中的状态 */
|
|
|
|
+ // examing: {};
|
|
|
|
+ // /** 获取到的camera source */
|
|
|
|
+ camera: {
|
|
|
|
+ /** 获取摄像头,且在多页面共享,减少打开摄像头失败的次数 */
|
|
|
|
+ stream: MediaStream | null;
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type SchoolDomain = `${string}.ecs.qmth.com.cn`;
|
|
|
|
+type SiteMessage = { id: number; hasRead: boolean; content: string };
|
|
|
|
+
|
|
|
|
+type BaseExam = {
|
|
|
|
+ /** 考试批次id */
|
|
|
|
+ examId: number;
|
|
|
|
+ /** 考生id,用户的每场考试都有一个考生id */
|
|
|
|
+ examStudentId: number;
|
|
|
|
+ /** 考生的考试记录id */
|
|
|
|
+ examRecordDataId: number;
|
|
|
|
+ /** 考试类型 */
|
|
|
|
+ examType: "ONLINE" | "ONLINE_HOMEWORK" | "PRACTICE";
|
|
|
|
+ /** 课程id */
|
|
|
|
+ courseId: number;
|
|
|
|
+ /** 课程名称 */
|
|
|
|
+ courseName: string;
|
|
|
|
+ /** 课程层次 */
|
|
|
|
+ courseLevel: string;
|
|
|
|
+ /** 专业名称 */
|
|
|
|
+ specialtyName: string;
|
|
|
|
+ /** 考试开始时间。日期时间字符串,以前叫后台改为数字,但后台不改,遗留的设计错误。 */
|
|
|
|
+ startTime: string;
|
|
|
|
+ /** 考试结束时间 */
|
|
|
|
+ endTime: string;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type ExamCycle = {
|
|
|
|
+ /** 考试周期是否开启循环 */
|
|
|
|
+ examCycleEnabled: boolean;
|
|
|
|
+ /** 周循环中周几开启考试 */
|
|
|
|
+ examCycleWeek: (1 | 2 | 3 | 4 | 5 | 6 | 7)[];
|
|
|
|
+ /** 周期循环中开启的时间段 HHmm [['08:00', '10:30'], ['15:00', '17:00']] */
|
|
|
|
+ examCycleTimeRange: [string, string][];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type OnlineExam = BaseExam &
|
|
|
|
+ ExamCycle & {
|
|
|
|
+ /** 是否显示考生承诺书 */
|
|
|
|
+ showUndertaking: boolean;
|
|
|
|
+ /** 是否启用人脸比对 */
|
|
|
|
+ faceEnable: boolean;
|
|
|
|
+ /** 剩余考试次数 */
|
|
|
|
+ allowExamCount: number;
|
|
|
|
+ /** 是否允许查看客观分 */
|
|
|
|
+ isObjScoreView: boolean;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+type PracticeExam = BaseExam &
|
|
|
|
+ ExamCycle & {
|
|
|
|
+ /** 考试批次名称 */
|
|
|
|
+ examName: string;
|
|
|
|
+ /** 练习次数 */
|
|
|
|
+ practiceCount: number;
|
|
|
|
+ /** 剩余练习次数 */
|
|
|
|
+ allowExamCount: number;
|
|
|
|
+ /** 最近正确率(后端已乘100) */
|
|
|
|
+ recentObjectiveAccuracy: number;
|
|
|
|
+ /** 平均正确率(后端已乘100) */
|
|
|
|
+ aveObjectiveAccuracy: number;
|
|
|
|
+ /** 最高正确率(后端已乘100) */
|
|
|
|
+ maxObjectiveAccuracy: number;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+type OfflineExam = BaseExam & {
|
|
|
|
+ /** 机构名称 */
|
|
|
|
+ orgName: string;
|
|
|
|
+ /** 考试周期是否开启循环 */
|
|
|
|
+ offlineFiles: Array<{ offlineFileUrl: string; originalFileName: string }>;
|
|
|
|
+ /** 试卷id */
|
|
|
|
+ paperId: string;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type OnlinePracticeRecord = {
|
|
|
|
+ id: number;
|
|
|
|
+ /** 考试开始时间。日期时间字符串,以前叫后台改为数字,但后台不改,遗留的设计错误。 */
|
|
|
|
+ startTime: string;
|
|
|
|
+ /** 考试结束时间 */
|
|
|
|
+ endTime: string;
|
|
|
|
+ /** 练习时长 类型待确认??? */
|
|
|
|
+ usedExamTime: any;
|
|
|
|
+ /** 总题量 */
|
|
|
|
+ totalQuestionCount: number;
|
|
|
|
+ /** 正确的数量 */
|
|
|
|
+ succQuestionNum: number;
|
|
|
|
+ /** 错误的数量 */
|
|
|
|
+ failQuestionNum: number;
|
|
|
|
+ /** 未答的数量 */
|
|
|
|
+ notAnsweredCount: number;
|
|
|
|
+ /** 客观题正确率(后端已乘100) */
|
|
|
|
+ objectiveAccuracy: number;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type OnlinePracticeRecordResult = {
|
|
|
|
+ courseName: string;
|
|
|
|
+ /** 客观题正确率(后端已乘100) */
|
|
|
|
+ objectiveAccuracy: string;
|
|
|
|
+ paperStructInfos: Array<{
|
|
|
|
+ index: number;
|
|
|
|
+ /** 题目分类 */
|
|
|
|
+ title: string;
|
|
|
|
+ /** 题量 */
|
|
|
|
+ questionCount: number;
|
|
|
|
+ /** 正确的数量 */
|
|
|
|
+ succQuestionNum: number;
|
|
|
|
+ /** 错误的数量 */
|
|
|
|
+ failQuestionNum: number;
|
|
|
|
+ /** 未答的数量 */
|
|
|
|
+ notAnsweredCount: number;
|
|
|
|
+ }>;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type PaperStruct = {
|
|
|
|
+ defaultPaper: {
|
|
|
|
+ /** index = mainNumber - 1 ??待确定 */
|
|
|
|
+ questionGroupList: Array<{
|
|
|
|
+ groupName: string;
|
|
|
|
+ groupScore: number;
|
|
|
|
+ questionWrapperList: Array<{
|
|
|
|
+ questionId: string;
|
|
|
|
+ body: string;
|
|
|
|
+ /** 小题的列表,学生端用不着,只用到它的length */
|
|
|
|
+ questionUnitWrapperList: Array<{ id: string }>;
|
|
|
|
+ questionUnitList: Array<{
|
|
|
|
+ eqs: ExamQuestion[];
|
|
|
|
+ body: string;
|
|
|
|
+ }>;
|
|
|
|
+ }>;
|
|
|
|
+ }>;
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type ExamQuestion = {
|
|
|
|
+ /** 试题id */
|
|
|
|
+ questionId: string;
|
|
|
|
+ /** 题目序号 */
|
|
|
|
+ order: number;
|
|
|
|
+ /** 题目在答题中的序号。 old groupOrder */
|
|
|
|
+ inGroupOrder: number;
|
|
|
|
+ /** 限制音频播放次数。从paperStruct[][]['limitedPlayTimes']得到。前端添加。 */
|
|
|
|
+ limitedPlayTimes: number;
|
|
|
|
+ questionScore: number;
|
|
|
|
+ /** 学生填写的答案,和C端、App端结构一致 */
|
|
|
|
+ studentAnswer: string;
|
|
|
|
+ /** 试题类型 */
|
|
|
|
+ questionType:
|
|
|
|
+ | "SINGLE_CHOICE"
|
|
|
|
+ | "MULTIPLE_CHOICE"
|
|
|
|
+ | "TRUE_OR_FALSE"
|
|
|
|
+ | "FILL_UP"
|
|
|
|
+ | "ESSAY";
|
|
|
|
+ /** 小题乱序。细化??? */
|
|
|
|
+ optionPermutation: number[];
|
|
|
|
+ /** 大题名称 */
|
|
|
|
+ groupName: string;
|
|
|
|
+ /** 什么的总分??? */
|
|
|
|
+ groupTotal: number;
|
|
|
|
+ /** 是否被标上星号 */
|
|
|
|
+ isStarred: boolean;
|
|
|
|
+ /** 大题号 */
|
|
|
|
+ mainNumber: number;
|
|
|
|
+ /** 小题号 */
|
|
|
|
+ subNumber: number;
|
|
|
|
+ /** 试题内容。重点要重构音频、填空题###的处理逻辑 */
|
|
|
|
+ body: string;
|
|
|
|
+ /** 是否为套题。此处要梳理数据结构,重新计算!!! */
|
|
|
|
+ isNestedQuestion: boolean;
|
|
|
|
+ /** 文本作答的题目,是否开启音频作答。和考试的weixinAnswerEnabled有关。 */
|
|
|
|
+ answerType: "SINGLE_AUDIO" | null;
|
|
|
|
+ /** 练习时有正确答案 */
|
|
|
|
+ rightAnswer?: boolean | number | string;
|
|
|
|
+ /** 后端给的类型前端好像用不着,待确认??? */
|
|
|
|
+ questionOptionList: any;
|
|
|
|
+ /** 题目中是否有音频 */
|
|
|
|
+ hasAudio: boolean;
|
|
|
|
+ /** 试题内容。通过网络获取。 */
|
|
|
|
+ questionContent: string;
|
|
|
|
+ /** 只有第一题有此数据,用来像服务器保存音频播放次数 */
|
|
|
|
+ audioPlayTimes: { audioName: string; times: number }[];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+type ExamInProgress = {
|
|
|
|
+ /** "S-101000" 请重试 */
|
|
|
|
+ code: "000000" | "S-101000";
|
|
|
|
+ /** 是否超过断点次数限制 */
|
|
|
|
+ isExceed: boolean;
|
|
|
|
+ /** 允许的最大断点次数 */
|
|
|
|
+ maxInterruptNum: number;
|
|
|
|
+ /** 允许的最大切屏次数 */
|
|
|
|
+ maxSwitchScreenCount: number;
|
|
|
|
+ examId: number;
|
|
|
|
+ examRecordDataId: number;
|
|
|
|
+};
|
|
|
|
+// export const REMOTE_APPS = [
|
|
|
|
+// ["qq", "QQ"],
|
|
|
|
+// ["teamviewer", "TeamViewer"],
|
|
|
|
+// ["lookmypc", "LookMyPC"],
|
|
|
|
+// ["xt", "协通"],
|
|
|
|
+// ["winaw32", "Symantec PCAnywhere"],
|
|
|
|
+// ["pcaquickconnect", "Symantec PCAnywhere"],
|
|
|
|
+// ["sessioncontroller", "Symantec PCAnywhere"],
|
|
|
|
+// [/sunloginclient/gi, "向日葵"],
|
|
|
|
+// [/sunloginremote/gi, "向日葵"],
|
|
|
|
+// [/选择免安装运行,截图识别/gi, "向日葵"],
|
|
|
|
+// ["wemeetapp", "腾讯会议"],
|
|
|
|
+// ["wechat", "微信"],
|
|
|
|
+// ] as const;
|
|
|
|
+
|
|
|
|
+// define process.env
|
|
|
|
+
|
|
|
|
+// clientVersion.ts 管理可用客户端版本。
|
|
|
|
+// api: getCurrentClientVersion isSupportedClientVersion
|
|
|
|
+
|
|
|
|
+/** 得到当前客户端版本 1.9.* */
|
|
|
|
+type GetCurrentClientVersion = () => string;
|
|
|
|
+type IsSupportedClientVersion = () => boolean;
|
|
|
|
+
|
|
|
|
+// updateManager.ts 应用(非客户端)是否应该更新了
|
|
|
|
+// api: isNewWebAppAvailable isNewBackendAvailable getNewBackendDesc
|
|
|
|
+
|
|
|
|
+// native.ts 管理原生及系统命令,详细说明path和相对path
|
|
|
|
+// api: isElectron execCmd readFile getRemoteApps getVirtualCams
|
|
|
|
+
|
|
|
|
+// runtimeMonitor.ts 检测当前运行的环境是否合法
|
|
|
|
+// 检测是否打开了控制台;检测是否使用了高版本的chrome(feature检测)
|
|
|
|
+// 检测onResize;截屏
|
|
|
|
+
|
|
|
|
+// cache design
|
|
|
|
+// service worker ; image cache; audio cache/prefetch
|
|
|
|
+
|
|
|
|
+// router design 严格受控的页面跳转,既考虑刷新,也考虑缓存
|
|
|
|
+
|
|
|
|
+// localStorage design
|
|
|
|
+// loginType accountValue
|
|
|
|
+
|
|
|
|
+// sessionStorage design
|
|
|
|
+// store 防破解?仅保存 user, QECSConfig 信息
|
|
|
|
+
|
|
|
|
+// 开考流程
|
|
|
|
+// 1. 确保每一次请求的结果都得到充分利用,即在页面中保存好信息,不做多余的重试,不在中途刷新页面,除敏感信息外要从上个页面带到下个页面
|
|
|
|
+// 2. 开考和考试中的信息获取要隔离,方便从不同的页面中进入考试
|
|
|
|
+// 3. 进入页面后,不轻易退出,要做足够的重试。退出的条件要设置充分,比如网络断了。
|