Files
endorphin-tessera-gui/examples/student-grades.endor
T

146 lines
8.6 KiB
Plaintext

// 学生成绩中心为纯内存演示,记录均虚构,不连接学校数据库。
// 按钮操作 GradeCenter/Student 原对象;成绩行与摘要用 textOf 绑定,同一学生在全部/复核视图中复用业务身份。
// 整个文件作为一次完整源码输入提交;状态只存在本次 VM,会话重置后不保留。
// 为 Column 增加期望宽度字段,使手工表格各列对齐,而非让文字自然宽度决定列边界。
class GradeCell: Column {
// 自有源字段控制期望列宽;每个调用包装不同内容对象,父约束可进一步限制实际分配。
width: Number
// 先保留 Column 的自然高度,再把新测量结果宽度设为字段值,最终仍由父约束协商。
function measure(self: GradeCell, mount: Mount, constraints: Constraints): MeasureResult {
// source_layout 调用覆盖,Column.measure 返回新临时结果;改 size.width 不修改 student 或其绑定文字。
size = Column.measure(self, mount, constraints)
size.width = self.width
return size
}
}
// 背景绘制由源覆盖,子项仍按普通 Column 排列与绘制。
class GradePanel: Column {
// 按本次 geometry 生成白色圆角底板;不重绘或复制子项命令,后代由统一遍历处理。
function paint(self: GradePanel, mount: Mount, geometry: Bounds): DisplayList {
// source_paint 的只读入口创建新命令,不在绘制阶段修改成绩/表格;绘制顺序由父背景后接孩子命令保证。
fill = PaintCommand()
fill.kind = "roundedRect"
fill.x = geometry.x
fill.y = geometry.y
fill.width = geometry.width
fill.height = geometry.height
fill.radius = 12
fill.color = "#FFFFFF"
return DisplayList(ObjectList(fill))
}
}
// 姓名/学号和各科成绩/状态是业务字段;数值修改受 Number 声明检查。
class Student {
// 同一 Student 可由多个 gradeRow 引用,响应式成绩/状态绑定始终读取原对象,没有每行一份成绩副本。
name: String
number: String
chinese: Number
math: Number
english: Number
status: String
}
// 用统一无 padding/gap 的单格容器包装任意 Block,末尾 width 是源字段构造参数。
function gradeCell(content: Block, width: Number): Block {
// gradeRow 和 gradeHeader 调用,传入孩子之后的 width 是 GradeCell 源字段,不能混进 Column 视觉子项列表。
return GradeCell(0, 0, content, width)
}
// 姓名/学号直接取初值,成绩和状态用响应式 textOf;行末分隔线与期望列宽组织表格。
// 修改 student.name/number 不会自动更新已构造的 TextBlock;需要像成绩一样显式绑定或重新构造行。
function gradeRow(student: Student): Block {
// 顶层 allGradeRows 调用六次,attentionGradeRows 另为 studentLin 调用一次;视觉树不同,学生业务身份可相同。
return Column(0, 12,
Row(0, 0,
gradeCell(TextBlock(student.name), 150),
gradeCell(TextBlock(student.number, 1, "#7A8390"), 120),
gradeCell(ui.textOf(student, "chinese"), 100),
gradeCell(ui.textOf(student, "math"), 100),
gradeCell(ui.textOf(student, "english"), 100),
gradeCell(ui.textOf(student, "status", 1, "#526C83"), 160)),
RectBlock(730, 1, 0, "#EDF0F3"))
}
// 保存全部/复核两棵现成子树与可替换 table,同时持有要复核的真实 Student。
class GradeCenter {
// all/attention 两棵子树都由模型强引用保留,切掉某个展示根不会丢失它;student 是要复核的 studentLin。
table: Column
all: Column
attention: Column
student: Student
pending: Number
message: String
// 替换稳定槽0的内容为全部行,复用已有学生,不重新请求/重建数据。
function showAll(self: GradeCenter, event: Event): Unit {
// showAllGrades 的绑定事件入口,替换容器当前槽 0 内容,不把 all 的孩子逐个拷贝到 table。
self.table.replace(0, self.all)
self.message = "正在查看全部 6 位学生"
}
// 切换到预先构造的复核行;这是特定示例子树,不是动态过滤算法。
function showAttention(self: GradeCenter, event: Event): Unit {
// showAttentionGrades 调用,选中既有 attention 子树;pending 变成零也不会自动删除这棵预建视图。
self.table.replace(0, self.attention)
self.message = "林沐的数学成绩有一条复核记录"
}
// 写预设数学成绩68及状态,清待复核数;相关绑定在两个视图都读同一 studentLin。
// 不检查 pending 或当前表格模式,重复 review 仍写相同预设值;这不是逐条审核队列。
function review(self: GradeCenter, event: Event): Unit {
// reviewGrade 事件入口写入同一学生,隐藏的另一行也通过绑定观察新值;没有遍历所有行手工改文字。
self.student.math = 68
self.student.status = "已复核"
self.pending = 0
self.message = "已录入复核结果:林沐 · 数学 58 → 68"
}
// 恢复初始学生值/计数并显示全部列表,便于重复观察同一操作。
function reset(self: GradeCenter, event: Event): Unit {
// resetGrades 派发时恢复字段并切回 all;保留 Student、GradeCenter 和按钮身份,便于继续点击演示。
self.student.math = 58
self.student.status = "待复核"
self.pending = 1
self.table.replace(0, self.all)
self.message = "已恢复示例数据"
}
}
// 下面六条数据均本地构造;allGradeRows 与 attentionGradeRows 对 studentLin 建立不同视觉行但共享其业务值。
studentLin = Student("林沐", "2026001", 86, 58, 91, "待复核")
studentChen = Student("陈知夏", "2026002", 94, 96, 93, "已确认")
studentXu = Student("许星河", "2026003", 82, 88, 85, "已确认")
studentZhou = Student("周予安", "2026004", 91, 89, 95, "已确认")
studentSu = Student("苏映雪", "2026005", 88, 92, 90, "已确认")
studentLu = Student("陆景行", "2026006", 79, 84, 87, "已确认")
allGradeRows = Column(0, 14, gradeRow(studentLin), gradeRow(studentChen), gradeRow(studentXu), gradeRow(studentZhou), gradeRow(studentSu), gradeRow(studentLu))
attentionGradeRows = Column(0, 14, gradeRow(studentLin))
// gradeTable 始终只包含一个内容槽,后续 replace 在这个边界换整棵视图;表头独立放在外层 GradePanel。
gradeTable = Column(0, 0, allGradeRows)
// table 最初含一个稳定子槽,后续 replace(0,...) 正是切换这个槽。
gradeCenter = GradeCenter(gradeTable, allGradeRows, attentionGradeRows, studentLin, 1, "正在查看全部 6 位学生")
showAllGrades = Button("全部学生")
showAttentionGrades = Button("查看复核记录")
reviewGrade = Button("录入复核成绩")
resetGrades = Button("恢复示例数据")
// 四个回调均接受 Event,绑定到同一 GradeCenter 接收者。
showAllGrades.on("click", gradeCenter.showAll)
showAttentionGrades.on("click", gradeCenter.showAttention)
reviewGrade.on("click", gradeCenter.review)
resetGrades.on("click", gradeCenter.reset)
// 四次注册不立即调用方法,所以初始化仍显示全部学生、待复核数 1 和林沐数学 58。
// 表头复用与数据行完全相同的列宽,摘要仅 pending 响应式,6/3是固定示例统计。
gradeHeader = Row(0, 0,
gradeCell(TextBlock("学生", 1, "#7A8390"), 150),
gradeCell(TextBlock("学号", 1, "#7A8390"), 120),
gradeCell(TextBlock("语文", 1, "#7A8390"), 100),
gradeCell(TextBlock("数学", 1, "#7A8390"), 100),
gradeCell(TextBlock("英语", 1, "#7A8390"), 100),
gradeCell(TextBlock("状态", 1, "#7A8390"), 160))
gradeSummary = Row(0, 40,
Column(0, 6, TextBlock("学生人数", 1, "#7A8390"), TextBlock("6", 2)),
Column(0, 6, TextBlock("考试科目", 1, "#7A8390"), TextBlock("3", 2)),
Column(0, 6, TextBlock("待复核", 1, "#7A8390"), ui.textOf(gradeCenter, "pending", 2)))
// 最终一次 render 组合摘要、控制区和表格面板;message 绑定显示最近业务操作结果。
gradesPage = Page()
gradesPage.title = "学生成绩管理中心"
gradesPage.render(Column(32, 20,
TextBlock("学生成绩管理中心", 2, "#273444"),
TextBlock("高一(3)班 · 2026 秋季阶段测评 · 虚构示例数据", 1, "#7A8390"),
gradeSummary,
Row(0, 10, showAllGrades, showAttentionGrades, reviewGrade, resetGrades),
GradePanel(20, 16, gradeHeader, RectBlock(730, 1, 0, "#E5EAF0"), gradeTable),
ui.textOf(gradeCenter, "message", 1, "#526C83")))