328 lines
21 KiB
Plaintext
328 lines
21 KiB
Plaintext
// 多页面校园演示:七个普通托管 Page 共享 CampusCourse/CampusWork 业务对象,所有课程与学生数据均虚构。
|
|
// 无服务器、账号登录或真实上传;提交/评分按钮只应用预设字段值,其他页通过绑定读同一状态。
|
|
// 作为一次完整输入提交;七页共用同一VM,会话重置后数据消失。
|
|
// 白色圆角卡片容器,继承 Column 的排版,只覆盖自身背景绘制。
|
|
class CampusPanel: Column {
|
|
// 按当前出现位置 geometry 创建一条背景命令,子项由展示遍历继续绘制。
|
|
function paint(self: CampusPanel, mount: Mount, geometry: Bounds): DisplayList {
|
|
// source_paint 为每个卡片出现调用;fill 是本轮新结果,允许写字段,不能在这里修改课程或作业模型。
|
|
fill = PaintCommand()
|
|
fill.kind = "roundedRect"
|
|
fill.x = geometry.x
|
|
fill.y = geometry.y
|
|
fill.width = geometry.width
|
|
fill.height = geometry.height
|
|
fill.radius = 10
|
|
fill.color = "#FFFFFF"
|
|
return DisplayList(ObjectList(fill))
|
|
}
|
|
}
|
|
// 通过额外 width 字段固定表格列的期望宽,保持成绩表头和行对齐。
|
|
class CampusCell: Column {
|
|
// 这是源子类的期望宽度策略,不是某个 Mount 的最终宽度;相同列宽仍要服从父 Constraints。
|
|
width: Number
|
|
// 先算子内容自然高度,再改新测量结果宽度,保留真实字体引起的高度。
|
|
function measure(self: CampusCell, mount: Mount, constraints: Constraints): MeasureResult {
|
|
// source_layout 先调用覆盖,再显式委托 Column 得到新 MeasureResult;只改返回结果,不写 content 的业务状态。
|
|
result = Column.measure(self, mount, constraints)
|
|
result.width = self.width
|
|
return result
|
|
}
|
|
}
|
|
// 页面内容里的演示导航栏,与应用自身的原生页面侧栏是两棵不同控件树。
|
|
class CampusSidebar: Column {
|
|
// 返回固定168x620期望尺寸,外层 ScrollBlock 承担超出区域的滚动。
|
|
function measure(self: CampusSidebar, mount: Mount, constraints: Constraints): MeasureResult {
|
|
// 每个侧栏出现的只读测量入口;固定返回值仍受父约束处理,不是强制原生窗口至少 620 高。
|
|
return MeasureResult(168, 620)
|
|
}
|
|
// 为导航区创建深红背景,位置/大小来自当前Mount,文字等子项保持独立命令。
|
|
function paint(self: CampusSidebar, mount: Mount, geometry: Bounds): DisplayList {
|
|
// 侧栏背景命令只覆盖当前容器,子按钮和文字通过独立出现继续绘制,避免在父命令里重复画子树。
|
|
fill = PaintCommand()
|
|
fill.kind = "roundedRect"
|
|
fill.x = geometry.x
|
|
fill.y = geometry.y
|
|
fill.width = geometry.width
|
|
fill.height = geometry.height
|
|
fill.radius = 10
|
|
fill.color = "#852B38"
|
|
return DisplayList(ObjectList(fill))
|
|
}
|
|
}
|
|
// Button 子类增加背景字段,固定导航按钮尺寸,并保留原生事件目标能力。
|
|
class CampusNav: Button {
|
|
background: String
|
|
// 导航按钮期望144x40,确保四个入口宽高一致。
|
|
function measure(self: CampusNav, mount: Mount, constraints: Constraints): MeasureResult {
|
|
// source_layout 调用此固定尺寸策略,实际测量结果仍由宿主验证,再交给父导航容器安排。
|
|
return MeasureResult(144, 40)
|
|
}
|
|
// 取原生按钮背景命令改色/圆角;文字由后面 replace 后的子项绘制,不在此重复生成。
|
|
function paint(self: CampusNav, mount: Mount, geometry: Bounds): DisplayList {
|
|
// 原生按钮生成本轮临时命令,at(0) 只取背景;下面 campusNav 替换的白字孩子由统一遍历画出。
|
|
fill = Button.paint(self, mount, geometry).commands.at(0)
|
|
fill.color = self.background
|
|
fill.radius = 7
|
|
return DisplayList(ObjectList(fill))
|
|
}
|
|
}
|
|
// 课程业务记录,grade/progress 被多个页面绑定读取,其余静态字段构建时直接读取。
|
|
class CampusCourse {
|
|
// grade/progress 用 ui.textOf 注册实时依赖;其余字段传给 TextBlock 时只读取构造当时的值。
|
|
title: String
|
|
code: String
|
|
teacher: String
|
|
grade: String
|
|
progress: String
|
|
}
|
|
// 实验状态集中在一个对象,course 指向同一课程模型,评分因此能影响全站课程摘要。
|
|
class CampusWork {
|
|
status: String
|
|
score: String
|
|
feedback: String
|
|
submission: String
|
|
todo: String
|
|
result: String
|
|
course: CampusCourse
|
|
// 模拟提交只改状态、文件名提示、待办和结果文字,不访问磁盘或网络。
|
|
function submit(self: CampusWork, event: Event): Unit {
|
|
// campusSubmitButton 的 click 连接调用;没有文件选择或成绩计算,重复点击会再次写相同预设状态。
|
|
self.status = "已提交 · 等待评分"
|
|
self.submission = "lab01.endor · 已提交示例内容"
|
|
self.todo = "已完成本次实验提交"
|
|
self.result = "示例提交成功,可以切到作业中心查看同一条记录。"
|
|
}
|
|
// 应用预设92分和反馈,并更新所属课程成绩/进度;绑定让仪表盘、课程、成绩页同步观察。
|
|
function assess(self: CampusWork, event: Event): Unit {
|
|
// campusAssessButton 派发到这里,course 引用 campusProgramming;只改该课程,数学与英语保持原值。
|
|
self.status = "已评分"
|
|
self.score = "92 / 100"
|
|
self.feedback = "结构清晰,边界情况处理完整。建议补充函数说明。"
|
|
self.submission = "lab01.endor · 已提交示例内容"
|
|
self.todo = "本次实验已评分"
|
|
self.course.grade = "88.5%"
|
|
self.course.progress = "已完成 4 / 6 个学习单元"
|
|
self.result = "已应用预设评分;仪表盘、课程主页和成绩总览同步更新。"
|
|
}
|
|
// 恢复初始实验与课程状态,保留所有原对象和绑定,便于反复演示。
|
|
function reset(self: CampusWork, event: Event): Unit {
|
|
// campusResetButton 的事件入口恢复共享模型字段,不重新创建七页,不需要逐页重新注册文字绑定。
|
|
self.status = "未提交"
|
|
self.score = "— / 100"
|
|
self.feedback = "提交后可在此查看教师反馈。"
|
|
self.submission = "尚无提交内容"
|
|
self.todo = "1 项实验待提交"
|
|
self.course.grade = "86.0%"
|
|
self.course.progress = "已完成 3 / 6 个学习单元"
|
|
self.result = "已恢复实验、课程成绩和学习进度的示例状态。"
|
|
}
|
|
}
|
|
// 用统一包装构造表格单元格;传入的content是已有Block引用。
|
|
function campusCell(content: Block, width: Number): Block {
|
|
// campusGradeRow 和成绩表头调用;width 放在原生 Column 的单个孩子之后,属于源字段而非额外孩子。
|
|
return CampusCell(0, 0, content, width)
|
|
}
|
|
// 显式事件适配器把click的Event与Page.open可选Window参数分开,不能直接把Event误当窗口。
|
|
class CampusTarget {
|
|
page: Page
|
|
// 事件参数在适配器这一层消化,Page.open 只接收其声明的可选 Window。
|
|
// 消费事件但不传给Page.open;无参open表示在合适窗口打开/激活目标页。
|
|
// campusLink/campusNav 保存此绑定方法;点击时以已保存的真实 Page 为目标,不按标题字符串重新找页。
|
|
function open(self: CampusTarget, event: Event): Unit { self.page.open() }
|
|
}
|
|
// 为普通按钮创建持有目标Page的适配器,并把其绑定方法注册为click回调。
|
|
function campusLink(label: String, target: Page): Button {
|
|
// 由卡片、课程正文和作业详情等组合函数调用,每次生成独立按钮/适配器,但复用传入 target 页身份。
|
|
button = Button(label)
|
|
action = CampusTarget(target)
|
|
// 连接持有绑定接收者所需引用,action 局部变量离开函数不代表回调丢失目标;调用方不需额外保存适配器。
|
|
button.on("click", action.open)
|
|
return button
|
|
}
|
|
// 构造带指定背景的导航按钮,替换原生文字子槽为白字,然后复用事件适配器连接目标页。
|
|
function campusNav(label: String, target: Page, background: String): Button {
|
|
// campusNavigation 调用,先用源字段构造导航按钮,再替换稳定槽 0 的文字,不把背景字符串误传成孩子。
|
|
button = CampusNav(label, background)
|
|
button.replace(0, TextBlock(label, 1, "#FFFFFF"))
|
|
action = CampusTarget(target)
|
|
button.on("click", action.open)
|
|
return button
|
|
}
|
|
// 用目标Page和四个色调参数构建整块导航;不同页面复用目标资源但用不同高亮背景。
|
|
function campusNavigation(home: Page, course: Page, assignments: Page, grades: Page, homeTone: String, courseTone: String, workTone: String, gradeTone: String): Block {
|
|
// 顶层调用四次建立不同高亮版本;四个 Page 参数只提供导航目标,此函数不直接 open 或 render 它们。
|
|
return CampusSidebar(12, 10,
|
|
TextBlock("Endor", 2, "#FFFFFF"),
|
|
TextBlock("Campus", 1, "#E7BAC0"), Spacer(16),
|
|
campusNav("仪表盘", home, homeTone),
|
|
campusNav("课程", course, courseTone),
|
|
campusNav("作业中心", assignments, workTone),
|
|
campusNav("成绩总览", grades, gradeTone), Spacer(100),
|
|
TextBlock("林沐", 1, "#FFFFFF"),
|
|
TextBlock("2026 秋季学期", 1, "#E7BAC0"),
|
|
TextBlock("本地演示", 1, "#E7BAC0"))
|
|
}
|
|
// 把演示导航与标题/正文横排,外层有限ScrollBlock启用双轴,容纳固定宽度演示内容。
|
|
function campusShell(navigation: Block, title: String, subtitle: String, body: Block): Block {
|
|
// 七个 render 的参数求值调用本函数,各自新建外层滚动对象;传入 navigation 可共享,偏移则按出现分别保存。
|
|
scroll = ScrollBlock(1120, 680,
|
|
Row(24, 28, navigation,
|
|
Column(0, 16,
|
|
TextBlock(title, 2, "#263444"),
|
|
TextBlock(subtitle, 1, "#7C8793"),
|
|
RectBlock(800, 1, 0, "#E0E5EB"), body)))
|
|
scroll.axis = "both"
|
|
return scroll
|
|
}
|
|
// 卡片静态显示课程代码/标题/教师,grade 用绑定随评分变化,入口按钮引用真实课程Page。
|
|
function campusCourseCard(course: CampusCourse, page: Page, color: String): Block {
|
|
// 仪表盘为三门课各调用一次;color 同时用于静态色条与成绩文字,grade 的实时值仍来自传入 course。
|
|
return CampusPanel(0, 0,
|
|
RectBlock(248, 44, 8, color),
|
|
Column(16, 8,
|
|
TextBlock(course.code, 1, "#7C8793"),
|
|
TextBlock(course.title, 1, "#263444"),
|
|
TextBlock(course.teacher, 1, "#7C8793"),
|
|
ui.textOf(course, "grade", 2, color),
|
|
campusLink("进入课程", page)))
|
|
}
|
|
// 成绩表一行复用固定列宽单元格,当前grade为响应式文字,详情链接指向指定课程页。
|
|
function campusGradeRow(course: CampusCourse, target: Page): Block {
|
|
// 成绩总览逐门课程调用,创建新的表格视觉行,不复制 CampusCourse;详情按钮保留对应课程 Page。
|
|
return Column(0, 12,
|
|
Row(0, 0,
|
|
campusCell(TextBlock(course.title), 240),
|
|
campusCell(TextBlock(course.code, 1, "#7C8793"), 140),
|
|
campusCell(ui.textOf(course, "grade", 1, "#315E86"), 140),
|
|
campusLink("课程详情", target)),
|
|
RectBlock(752, 1, 0, "#EDF0F3"))
|
|
}
|
|
// 组合课程概况/进度和静态学习单元;上方两个链接复用作业/成绩Page,不创建新页面。
|
|
function campusCourseBody(course: CampusCourse, intro: String, moduleOne: String, moduleTwo: String, moduleThree: String, assignments: Page, grades: Page): Block {
|
|
// 三个课程页调用同一构造函数,各自传不同课程模型/静态单元文案;只有明确 textOf 的字段会随模型改动重算。
|
|
return Column(0, 20,
|
|
Row(0, 16, campusLink("作业与提交", assignments), campusLink("查看成绩", grades)),
|
|
CampusPanel(20, 12,
|
|
TextBlock("课程概况", 1, "#263444"),
|
|
TextBlock(intro, 1, "#6D7987"),
|
|
TextBlock(course.teacher, 1, "#6D7987"),
|
|
Row(0, 24, TextBlock("当前成绩", 1, "#7C8793"), ui.textOf(course, "grade", 2, "#315E86")),
|
|
ui.textOf(course, "progress", 1, "#6D7987")),
|
|
CampusPanel(20, 14,
|
|
TextBlock("学习单元", 1, "#263444"),
|
|
TextBlock(moduleOne, 1, "#526372"),
|
|
RectBlock(752, 1, 0, "#EDF0F3"),
|
|
TextBlock(moduleTwo, 1, "#526372"),
|
|
RectBlock(752, 1, 0, "#EDF0F3"),
|
|
TextBlock(moduleThree, 1, "#526372")))
|
|
}
|
|
// 共享对象图只构造一次,各Page的文字绑定投影同一业务字段。
|
|
// 只创建三份课程模型和一份实验模型,后续各页读取这些对象,避免页面间状态副本不一致。
|
|
campusProgramming = CampusCourse("计算机程序设计", "CS 101", "陈老师 · 周二 / 周四", "86.0%", "已完成 3 / 6 个学习单元")
|
|
campusMathematics = CampusCourse("高等数学 I", "MATH 101", "周老师 · 周一 / 周三", "91.0%", "已完成 4 / 6 个学习单元")
|
|
campusEnglish = CampusCourse("学术英语", "ENG 102", "许老师 · 周五", "89.0%", "已完成 3 / 5 个学习单元")
|
|
campusWork = CampusWork("未提交", "— / 100", "提交后可在此查看教师反馈。", "尚无提交内容", "1 项实验待提交", "这是一个本地提交与评分演示。", campusProgramming)
|
|
// course 只关联程序设计模型;课程标题/成绩等对象引用与页面资源随后通过各组合函数串成同一托管对象图。
|
|
// 先创建全部七个Page,允许导航按钮互相引用;随后逐一render建立内容。
|
|
campusDashboard = Page()
|
|
campusDashboard.title = "Campus · 仪表盘"
|
|
campusProgrammingPage = Page()
|
|
campusProgrammingPage.title = "CS 101 · 程序设计"
|
|
campusMathPage = Page()
|
|
campusMathPage.title = "MATH 101 · 高等数学"
|
|
campusEnglishPage = Page()
|
|
campusEnglishPage.title = "ENG 102 · 学术英语"
|
|
campusAssignments = Page()
|
|
campusAssignments.title = "Campus · 作业中心"
|
|
campusGrades = Page()
|
|
campusGrades.title = "Campus · 成绩总览"
|
|
campusWorkPage = Page()
|
|
campusWorkPage.title = "实验 01 · 提交详情"
|
|
// 预建四种高亮导航树,课程三页共享course导航,Page切换时各自拥有独立Mount。
|
|
campusHomeNav = campusNavigation(campusDashboard, campusProgrammingPage, campusAssignments, campusGrades, "#A64350", "#852B38", "#852B38", "#852B38")
|
|
campusCourseNav = campusNavigation(campusDashboard, campusProgrammingPage, campusAssignments, campusGrades, "#852B38", "#A64350", "#852B38", "#852B38")
|
|
campusAssignmentNav = campusNavigation(campusDashboard, campusProgrammingPage, campusAssignments, campusGrades, "#852B38", "#852B38", "#A64350", "#852B38")
|
|
campusGradeNav = campusNavigation(campusDashboard, campusProgrammingPage, campusAssignments, campusGrades, "#852B38", "#852B38", "#852B38", "#A64350")
|
|
// 仪表盘展示课程卡和当前实验摘要,status/todo均从同一campusWork绑定读取。
|
|
campusDashboard.render(campusShell(campusHomeNav, "仪表盘", "林沐,欢迎回来。这里是你的课程与近期学习安排。",
|
|
Column(0, 20,
|
|
Row(0, 16,
|
|
campusCourseCard(campusProgramming, campusProgrammingPage, "#326A8F"),
|
|
campusCourseCard(campusMathematics, campusMathPage, "#547C67"),
|
|
campusCourseCard(campusEnglish, campusEnglishPage, "#88647D")),
|
|
CampusPanel(20, 12,
|
|
TextBlock("近期作业", 1, "#263444"),
|
|
Row(0, 24, TextBlock("CS 101 · 实验 01:对象与函数"), ui.textOf(campusWork, "status", 1, "#315E86")),
|
|
ui.textOf(campusWork, "todo", 1, "#7C8793"),
|
|
campusLink("打开作业详情", campusWorkPage)),
|
|
TextBlock("虚构课程与学生数据 · Endor 本地多页面演示", 1, "#8C959F"))))
|
|
// 程序设计课程附加实验入口;数学和英语页复用相同概况结构但读取不同课程模型。
|
|
campusProgrammingPage.render(campusShell(campusCourseNav, "计算机程序设计", "课程 / CS 101 / 主页",
|
|
Column(0, 16,
|
|
campusCourseBody(campusProgramming, "通过实际编程理解对象、函数与交互。", "01 对象与字段 · 已完成", "02 函数与方法 · 已完成", "03 事件与可视界面 · 进行中", campusAssignments, campusGrades),
|
|
campusLink("实验 01:对象与函数", campusWorkPage))))
|
|
campusMathPage.render(campusShell(campusCourseNav, "高等数学 I", "课程 / MATH 101 / 主页",
|
|
campusCourseBody(campusMathematics, "学习极限、微分与积分的基本方法。", "01 函数与极限 · 已完成", "02 导数及其应用 · 已完成", "03 积分方法 · 进行中", campusAssignments, campusGrades)))
|
|
campusEnglishPage.render(campusShell(campusCourseNav, "学术英语", "课程 / ENG 102 / 主页",
|
|
campusCourseBody(campusEnglish, "阅读学术文本,练习清晰的论证与表达。", "01 Academic reading · 已完成", "02 Evidence and arguments · 已完成", "03 Research writing · 进行中", campusAssignments, campusGrades)))
|
|
// 作业中心只有程序设计实验为共享动态模型,其余两项是固定演示文字。
|
|
campusAssignments.render(campusShell(campusAssignmentNav, "作业中心", "本学期 / 全部课程 / 作业与提交",
|
|
Column(0, 20,
|
|
CampusPanel(20, 12,
|
|
TextBlock("实验 01:对象与函数", 1, "#263444"),
|
|
TextBlock("CS 101 · 10 月 30 日 23:59 截止 · 满分 100", 1, "#7C8793"),
|
|
Row(0, 24, ui.textOf(campusWork, "status", 1, "#315E86"), ui.textOf(campusWork, "score")),
|
|
campusLink("查看提交详情", campusWorkPage)),
|
|
CampusPanel(20, 12,
|
|
TextBlock("习题 04:微分与应用", 1, "#263444"),
|
|
TextBlock("MATH 101 · 已提交 · 90 / 100", 1, "#7C8793")),
|
|
CampusPanel(20, 12,
|
|
TextBlock("Reading response", 1, "#263444"),
|
|
TextBlock("ENG 102 · 已评分 · 88 / 100", 1, "#7C8793")))))
|
|
// 成绩总览复用课程grade绑定和实验score/feedback,评分无需逐页手动写UI。
|
|
campusGrades.render(campusShell(campusGradeNav, "成绩总览", "2026 秋季学期 / 当前课程成绩",
|
|
Column(0, 20,
|
|
CampusPanel(20, 16,
|
|
Row(0, 0,
|
|
campusCell(TextBlock("课程", 1, "#7C8793"), 240),
|
|
campusCell(TextBlock("课程编号", 1, "#7C8793"), 140),
|
|
campusCell(TextBlock("当前成绩", 1, "#7C8793"), 140)),
|
|
campusGradeRow(campusProgramming, campusProgrammingPage),
|
|
campusGradeRow(campusMathematics, campusMathPage),
|
|
campusGradeRow(campusEnglish, campusEnglishPage)),
|
|
CampusPanel(20, 12,
|
|
TextBlock("CS 101 · 作业评分与反馈", 1, "#263444"),
|
|
Row(0, 24, TextBlock("实验 01:对象与函数"), ui.textOf(campusWork, "score", 1, "#315E86")),
|
|
ui.textOf(campusWork, "feedback", 1, "#6D7987"),
|
|
campusLink("打开作业与反馈", campusWorkPage)))))
|
|
// 三个操作按钮连接同一个实验对象的submit/assess/reset,均为真实源方法回调。
|
|
campusSubmitButton = Button("模拟提交实验")
|
|
campusSubmitButton.on("click", campusWork.submit)
|
|
campusAssessButton = Button("应用示例评分")
|
|
campusAssessButton.on("click", campusWork.assess)
|
|
campusResetButton = Button("恢复演示数据")
|
|
campusResetButton.on("click", campusWork.reset)
|
|
// 注册三个方法只建立监听,不立即执行预设提交/评分/恢复;首次所有绑定读取上面的模型初值。
|
|
// 详情页组合要求、提交状态、操作按钮和反馈;所有输入仍只是预设本地状态转换。
|
|
campusWorkPage.render(campusShell(campusAssignmentNav, "实验 01:对象与函数", "CS 101 / 作业 / 提交详情",
|
|
Column(0, 20,
|
|
Row(0, 16, campusLink("返回课程", campusProgrammingPage), campusLink("全部作业", campusAssignments), campusLink("成绩总览", campusGrades)),
|
|
CampusPanel(20, 12,
|
|
TextBlock("实验要求", 1, "#263444"),
|
|
TextBlock("使用 Endor 定义一个对象,并通过方法更新其字段。", 1, "#6D7987"),
|
|
TextBlock("为同一个对象建立两个可视展示,验证修改后同时更新。", 1, "#6D7987"),
|
|
TextBlock("截止:10 月 30 日 23:59 满分:100", 1, "#7C8793")),
|
|
CampusPanel(20, 12,
|
|
TextBlock("提交记录", 1, "#263444"),
|
|
ui.textOf(campusWork, "submission"),
|
|
ui.textOf(campusWork, "status", 1, "#315E86"),
|
|
Row(0, 12, campusSubmitButton, campusAssessButton, campusResetButton)),
|
|
CampusPanel(20, 12,
|
|
Row(0, 24, TextBlock("成绩与反馈"), ui.textOf(campusWork, "score", 2, "#315E86")),
|
|
ui.textOf(campusWork, "feedback", 1, "#6D7987")),
|
|
ui.textOf(campusWork, "result", 1, "#7C8793"))))
|
|
// 逐页render会改变当前活动页,最后显式选择仪表盘作为打开后的首页。
|
|
campusDashboard.activate()
|
|
// 激活只请求展示已有仪表盘,保留其余六页及共享课程/实验对象;所有页面都没有独立数据库或独立 VM。
|