|
|
@@ -9,8 +9,8 @@ const adminGatewayCode = String.raw`
|
|
|
const CLASS_LABELS = {
|
|
|
_User: '用户管理', Company: '帐套管理', Profile: '员工档案', Group: '用户组', Role: '角色', Permission: '权限', Department: '组织部门',
|
|
|
CourseBinding: '课程绑定', CourseAppointment: '课程预约', LessonRecord: '上课记录', DailyStudyRecord: '每日学习记录', PracticeRecord: '练习记录', MemoryPracticeRecord: '抗遗忘记录',
|
|
|
- VocabularyWord: '词库', CommonModel: '通用内容', ContentArticle: '文章内容', ContentPublish: '内容发布', Node: '栏目节点', NodeAuth: '节点权限', Model: '内容模型', ModelField: '模型字段',
|
|
|
- ExamClass: '试题分类', ExamSysQuestions: '考试题库', ExamSysPapers: '试卷', ExamType: '考试类型', ExamPoint: '知识点', PaperQuestions: '试卷题目',
|
|
|
+ VocabularyWord: '词库', CommonModel: '通用内容', ContentArticle: '文章内容', ContentPublish: '报刊内容', Node: '栏目节点', NodeAuth: '节点权限', Model: '内容模型', ModelField: '模型字段', PublishNode: '报刊发布节点',
|
|
|
+ ExamClass: '试题分类', ExamSysQuestions: '考试题库', ExamSysPapers: '考试试卷', ExamType: '考试类型', ExamPoint: '知识点', PaperQuestions: '试卷大题分组',
|
|
|
Datadiccategory: '数据字典分类', Datadic: '数据字典项',
|
|
|
GradeCate: '多级字典分类', Grade: '多级字典选项',
|
|
|
Currency: '货币管理', SysHoliday: '节假日管理',
|
|
|
@@ -20,7 +20,7 @@ const CLASS_LABELS = {
|
|
|
const ALLOWED_CLASSES = new Set([
|
|
|
'PageTemplate','PaperQuestions','PayPlat','Permission','PlatComp','App','DesignAnswer','DesignAsk','Attachment','DesignPage','DesignQuestion','DesignRes','Feedback','DesignScence','StudentAchieve','ContentArticle','DesignSiteInfo','Profile','AdInfo','AdZone','ARoleAuth','Baike','ExamSysQuestions','ContactInfo','VocabularyWord','AssessmentProfile','Agency','MemoryPracticeRecord','DeliveryCenter','CourseBinding','PracticeRecord','CourseAppointment','Account','SurveyItem','SurveyLog','LessonRecord','DailyStudyRecord','CommonModel','ContentPublish','Company','_Role','_User','CRMSAttr','Currency','Datadic','Datadiccategory','DesignTlp','DocModel','DocPermission','ExamClass','ExamSysPapers','ExamType','ExamPoint','ExTeacher','FontPicShape','FontPicShapeType','Grade','GradeCate','Group','GroupModel','GuestBar','Guestbook','Guestcate','MailTemp','Manager','MisProcedure','MisProLevel','MisSign','MisType','PageStyle','Model','ModelField','Node','NodeAuth','NodeModelTemplate','Product','PlatUserRole','Pub','PubTw','PubWTHD','PubZXDC','PublishNode','QuestionsKnowledge','Role','StoreProduct','SafeMobile','Search','SenTask','ServiceSeat','ShopFareTlp','ShopMoneyRegular','Special','StoreApplication','StoreStyle','SysCSSManage','SysHoliday','SysLog','Temp','ThirdPlatInfo','UserCredit','UserDummyPoint','UserFriendGroup','UserLevel','UserSIcon','UserUserPoint','UserExpDomP','UserExpHis'
|
|
|
]);
|
|
|
-const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo','GuestBar','DesignAnswer','Baike']);
|
|
|
+const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo','GuestBar','DesignAnswer','Baike','ExamSysPapers','ExamSysQuestions','PaperQuestions','ExamType','ContentPublish','PublishNode']);
|
|
|
const SYSTEM_FIELDS = new Set(['objectId','createdAt','updatedAt','ACL','company','password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','adminRoleKey','isAdmin','roles','role','isDisabled','legacyUserId','legacyGroupId','legacyUserData','groupId']);
|
|
|
const HIDDEN_FIELDS = new Set(['password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','apiKey','appSecret','secret','token']);
|
|
|
const GENERIC_WRITE_TYPES = new Set(['String','Number','Boolean','Date','Pointer','Object','Array']);
|
|
|
@@ -1327,6 +1327,24 @@ async function handler(request, response) {
|
|
|
Default: '目标没有可按 SType 筛选分页的旧 Content_Chart 记录'
|
|
|
}; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧 ECharts 动作'); fail(501, 'migration_blocked: ' + blockers[action]);
|
|
|
}
|
|
|
+ if (operation === 'legacyExamBlocker') {
|
|
|
+ const action = String(input.action || ''); const blockers = {
|
|
|
+ AddEngLishQuestion: '旧英语试题页依赖复合小题、音频/图片上传和答案解析器;目标仅保留只读 ExamSysQuestions 记录',
|
|
|
+ Setting: '目标 Schema 缺少旧 Exam_Sys_Answer 答卷明细,无法按最近一周统计并重算试题难度',
|
|
|
+ VersionList: '目标 Schema 缺少旧 Exam_Version 教材版本/册/章节树与历史数据', AddVersion: '目标无可定位或新建的 Exam_Version 版本树记录',
|
|
|
+ SchoolManage: '目标 Schema 缺少旧 School 学校主表与省市县数据', AddSchool: '目标无可编辑或新增的 School 记录',
|
|
|
+ ClassRoomManage: '目标 Schema 缺少旧 ClassRoom 班级主表与学校、年级、教师关系', AddClassRoom: '目标无可编辑或新增的 ClassRoom 记录',
|
|
|
+ StudentList: '目标 Schema 缺少 ClassRoomStudent 班级学员关系、审核状态与学员类型',
|
|
|
+ CourseManage: '目标 Schema 缺少旧 Course 课程主表;CourseBinding 是小树课程绑定,语义不等价', AddCourse: '目标无旧 Course 名称、科目、教师与课程结构记录',
|
|
|
+ ToScore: '目标 Schema 缺少旧 Exam_Sys_Answer 主观题答卷与评分状态',
|
|
|
+ Papers_Add: '试卷写入必须原子维护 qids/questList、分类、计时、价格与题目分值;当前只读迁移不允许通用表单破坏结构',
|
|
|
+ Question_Add: '旧试题写入依赖题型答案解析、复合小题、知识点和上传资源;当前只读迁移不能安全保存',
|
|
|
+ Version_GetList: '目标缺少可按 pid 查询的 Exam_Version 版本树', Version_Add: '目标缺少 Exam_Version 版本、册、章节持久层',
|
|
|
+ School_Add: '目标缺少 School 主表,无法保存学校类型、地区、校徽和状态', ClassRoom_Add: '目标缺少 ClassRoom 与学校、教师、年级关系持久层', Course_Add: '目标缺少旧 Course 主表和科目树关系',
|
|
|
+ Setting_Update: '目标缺少 Exam_Sys_Answer 作答统计,不能执行 CountDiffcult 难度重算',
|
|
|
+ PublishDesign: '旧报刊可视化设计器的前端资产、布局协议与渲染运行时未迁入'
|
|
|
+ }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧考试动作'); fail(501, 'migration_blocked: ' + blockers[action]);
|
|
|
+ }
|
|
|
if (operation === 'legacyChatBlocker') {
|
|
|
const action = String(input.action || ''); const blockers = {
|
|
|
Default: '目标 Schema 缺少 ChatMsg 消息表,无法还原用户消息数列表',
|
|
|
@@ -1714,7 +1732,7 @@ async function handler(request, response) {
|
|
|
const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20));
|
|
|
let query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId);
|
|
|
const search = String(input.search || '').trim();
|
|
|
- if (search) { const searchField = ['name','title','nodeName','specName','modelName','fieldName','fieldAlias','groupName','categoryname','dicname','cateName','gradeName','cClassName','testPoint','kName','tname','username','realName','mobile','sourceKey'].find((name) => fields[name] && fields[name].type === 'String'); if (searchField) query.matches(searchField, escapeRegex(search), 'i'); }
|
|
|
+ if (search) { const searchField = ['name','title','pName','pTitle','questionTitle','tName','newsName','nodeName','specName','modelName','fieldName','fieldAlias','groupName','categoryname','dicname','cateName','gradeName','cClassName','testPoint','kName','tname','username','realName','mobile','sourceKey'].find((name) => fields[name] && fields[name].type === 'String'); if (searchField) query.matches(searchField, escapeRegex(search), 'i'); }
|
|
|
const sort = fields[input.sort] ? String(input.sort) : fields.updatedAt ? 'updatedAt' : 'createdAt'; if (input.order === 'asc') query.ascending(sort); else query.descending(sort);
|
|
|
const total = await query.count({ useMasterKey: true }); query.skip((page - 1) * pageSize); query.limit(pageSize);
|
|
|
const results = (await query.find({ useMasterKey: true })).filter(isVisible);
|