综合演示:XML 定义待办事项列表

文档概述

本演示展示了如何使用 XML 定义界面 + 轻语言编写逻辑的方式,开发一个完整的待办事项(Todo List)应用。涵盖数据持久化(保存配置/读取配置)、列表框数据驱动渲染(置请求加载项目回调)、自定义复合列表项(弹性布局 + 复选框 + 文本 + 删除按钮)、空状态控制、回车键添加等典型场景。

  • 核心功能:添加待办、标记完成、删除待办、统计计数、空状态提示、本地存储持久化
  • 技术亮点
    • 使用 列表框 + 置请求加载项目回调 实现数据驱动渲染
    • 复合列表项:弹性布局嵌套「复选框 + 文本」与「删除按钮」
    • 使用 保存配置/读取配置 实现 localStorage 持久化
    • 键盘事件(回车添加)+ 按钮事件双触发
    • 空状态标签的可视切换

学习目标

阅读本文档后,你应当能够掌握以下内容:

  • .spldg(XML)定义页面布局,包含列表框、编辑框、按钮、标签等组件
  • .spl(轻语言)实现待办列表的增删改查逻辑
  • 使用 保存配置/读取配置 进行本地持久化(localStorage)
  • 使用 JSON解析/JSON编码 在字符串与对象数组之间转换
  • 列表框的「数据填充 + 渲染回调 + 渲染()」三段式数据驱动渲染
  • 在渲染回调中创建复合视图(弹性布局嵌套多组件)
  • 通过 置被单击回调置选中状态被改变回调 注册事件
  • 列表项点击事件中捕获外层 索引(闭包语义)
  • 键盘事件 键盘被按下 + 事件源.key == "Enter" 实现回车提交

涉及组件

组件名称作用参考文档
标签标签_标题标签_统计标签_空状态标题、统计、空状态提示文档文本类组件_标签
编辑框编辑框_输入待办输入待办内容文档文本类组件_编辑框
按钮按钮_添加添加按钮文档按钮类组件_按钮
列表框列表框_待办事项待办列表容器文档列表类组件_列表框
弹性布局代码中 创建 弹性布局()列表项复合根布局文档布局类组件_弹性布局
复选框代码中 创建 单选框()标记完成状态文档选择类组件_单选框
绝对布局根布局页面根布局文档布局类组件_绝对布局

📌 待办列表项的展示视图并非预置组件,而是在 置请求加载项目回调 中动态创建的复合视图:以弹性布局为根,内含「左容器(复选框 + 文本标签)+ 删除按钮」。

涉及函数与关键字

关键字: 变量事件 ... 结束 事件函数 ... 结束 函数如果 ... 否则 ... 结束 如果变量循环 ... 结束循环

列表框方法:

  • 添加项目(数据) —— 向数据集合追加一条数据
  • 添加项目集(数据数组) —— 批量追加一组数据
  • 置请求加载项目回调(回调) —— 设置渲染回调,框架按索引逐项调用取展示视图
  • 渲染() —— 触发按当前数据集合重新渲染
  • 清空() —— 清空数据集合与已渲染视图

渲染回调参数:

  • 项目集合 —— 全部数据集合(数组),通过 项目集合[索引] 取当前项
  • 索引 —— 当前正在渲染的项索引(从 0 开始)

数据持久化:

  • 保存配置(键, 值) —— 保存到 localStorage(值为字符串)
  • 读取配置(键) —— 从 localStorage 读取字符串,不存在返回
  • JSON编码(对象) —— 对象/数组编码为 JSON 字符串
  • JSON解析(文本) —— JSON 字符串解析为对象/数组

组件创建:

  • 创建 弹性布局()创建 单选框()创建 标签()创建 按钮()

视图通用方法/属性:

  • 添加组件(组件)置被单击回调(回调)置选中状态被改变回调(回调)
  • 宽度高度定位方向主轴对齐侧轴对齐内边距间隔
  • 文本字体大小文本颜色字体装饰line-through 删除线)
  • 背景颜色边框圆角可视选中标题

数组操作:

  • 取数组成员数(数组) —— 获取数组长度
  • 添加成员(数组, 值) —— 追加元素
  • 删除成员(数组, 索引) —— 删除指定索引元素

对象操作:

  • 删除对象属性(对象, 属性名) —— 删除对象属性

其它:

  • 弹出提示(文本, 持续毫秒) —— 轻提示
  • 确认框(文本) —— 确认对话框,返回 /
  • 删首尾空(文本) —— 去除首尾空白
  • 取现行时间() —— 当前时间

代办事项页面.spldg

<?xml version="1.0" encoding="utf-8"?>
<页面 标题="待办事项" 宽度="500" 高度="400">
  <绝对布局 名称="根布局" 宽度="100%" 高度="100%">
    <标签 名称="标签_标题" 左边="20" 顶边="20" 宽度="200" 高度="40" 文本="📋 待办事项" 字体大小="22" 文本颜色="#333" />
    <编辑框 名称="编辑框_输入待办" 左边="20" 顶边="70" 宽度="340" 高度="36" 提示文本="输入待办事项,按回车添加" />
    <按钮 名称="按钮_添加" 左边="370" 顶边="70" 宽度="100" 高度="36" 文本="添加" 背景颜色="#52c41a" 文本颜色="#ffffff" />
    <列表框 名称="列表框_待办事项" 左边="20" 顶边="120" 宽度="450" 高度="220" 拉伸内容="假" 分隔线样式="solid" 分隔线颜色="#e8e8e8" />
    <标签 名称="标签_统计" 左边="226" 顶边="20" 宽度="254" 高度="40" 文本="共 0 项" 字体大小="14" 文本颜色="#999" 内容水平对齐方式="靠右" />
    <标签 名称="标签_空状态" 左边="20" 顶边="180" 宽度="450" 高度="61" 文本="🎯 暂无待办事项,添加一条吧!" 字体大小="16" 文本颜色="#ccc" 内容水平对齐方式="居中" 可视="假" />
  </绝对布局>
</页面>

代办事项页面.spl

'* ******************************************************************************
'* 版权说明:Copyright(C) All Rights Reserved VcnStudio
'* 程序版本:V1.0
'* 文件描述:待办事项列表逻辑
'* 修改日志:
'* ******************************************************************************

' ========== 页面变量 ==========
变量 待办列表 = []
变量 列表项组件 = { }  ' 用于存储每个待办项对应的组件,方便删除和更新

' ========== 页面加载事件 ==========
事件 页面加载完毕()
    ' 加载已有数据
    变量 保存的数据 = 读取配置("待办列表")
    如果(保存的数据 != 空)
        待办列表 = JSON解析(保存的数据)
    结束 如果
    列表框_待办事项.添加项目集(待办列表)
    ' 设置列表框的渲染回调
    列表框_待办事项.置请求加载项目回调((项目集合, 索引) => {
        变量 项目数据 = 项目集合[索引]
        ' 创建每个列表项:弹性布局(水平排列)
        变量 项容器 = 创建 弹性布局()
        项容器.宽度 = 430
        项容器.高度 = 36
        项容器.定位 = 相对定位
        项容器.方向 = "row"
        项容器.主轴对齐 = "space-between"
        项容器.侧轴对齐 = "center"
        项容器.内边距 = "4px 8px"
        ' 左侧:复选框 + 文本
        变量 左容器 = 创建 弹性布局()
        左容器.方向 = "row"
        左容器.侧轴对齐 = "center"
        左容器.间隔 = 8
        变量 复选框 = 创建 单选框()
        复选框.选中 = 项目数据.已完成
        复选框.标题 = ""
        复选框.置选中状态被改变回调((源对象, 是否选中) => {
            ' 更新数据状态
            项目数据.已完成 = 是否选中
            待办列表[索引] = 项目数据
            保存配置("待办列表", JSON编码(待办列表))
            更新统计信息()
        })

        变量 文本标签 = 创建 标签()
        文本标签.文本 = 项目数据.内容
        文本标签.字体大小 = 14
        如果(项目数据.已完成)
            文本标签.文本颜色 = "#999"
            文本标签.字体装饰 = "line-through"
        否则
            文本标签.文本颜色 = "#333"
            文本标签.字体装饰 = "none"
        结束 如果

        左容器.添加组件(复选框)
        左容器.添加组件(文本标签)

        ' 右侧:删除按钮
        变量 删除按钮 = 创建 按钮()
        删除按钮.文本 = "×"
        删除按钮.宽度 = 28
        删除按钮.高度 = 28
        删除按钮.背景颜色 = "#ff4d4f"
        删除按钮.文本颜色 = "#ffffff"
        删除按钮.边框圆角 = 4
        删除按钮.置被单击回调((源对象, 事件对象) => {
            删除待办事项(索引)
        })

        项容器.添加组件(左容器)
        项容器.添加组件(删除按钮)

        ' 存储组件引用以便后续更新
        列表项组件[索引] = {
            "容器": 项容器,
            "复选框": 复选框,
            "文本标签": 文本标签
        }

        返回 项容器
    })
    列表框_待办事项.渲染()
    更新统计信息()
结束 事件

' ========== 刷新列表 ==========
函数 刷新列表()
    ' 清空并重新添加数据
    列表框_待办事项.清空()
    变量循环(计次 = 0, 取数组成员数(待办列表), 1)
        列表框_待办事项.添加项目(待办列表[计次])
    结束循环
    列表框_待办事项.渲染()
    ' 更新统计信息和空状态
    更新统计信息()
结束 函数

' ========== 更新统计信息 ==========
函数 更新统计信息()
    变量 总数 = 取数组成员数(待办列表)
    变量 已完成数 = 0
    变量循环(计次 = 0, 总数, 1)
        如果(待办列表[计次].已完成)
            已完成数 = 已完成数 + 1
        结束 如果
    结束循环
    标签_统计.文本 = "共 " + 总数 + " 项,已完成 " + 已完成数 + " 项"
    ' 空状态显示控制
    如果(总数 == 0)
        标签_空状态.可视 = 真
        列表框_待办事项.可视 = 假
    否则
        标签_空状态.可视 = 假
        列表框_待办事项.可视 = 真
    结束 如果
结束 函数

函数 添加代办事项()
    变量 新内容 = 编辑框_输入待办.内容
    如果(删首尾空(新内容) == "")
        弹出提示("请输入待办事项内容")
        返回
    结束 如果
    变量 新事项 = {
        "内容": 新内容,
        "已完成": 假,
        "创建时间": 取现行时间()
    }
    添加成员(待办列表, 新事项)
    保存配置("待办列表", JSON编码(待办列表))
    ' 清空输入框
    编辑框_输入待办.内容 = ""
    ' 刷新列表
    刷新列表()
    弹出提示("✅ 添加成功", 1500)
结束 函数

' ========== 删除待办事项 ==========
函数 删除待办事项(索引)
    变量 确认 = 确认框("确定要删除这条待办事项吗?")
    如果(确认 == 假)
        返回
    结束 如果
    删除成员(待办列表, 索引)
    保存配置("待办列表", JSON编码(待办列表))
    ' 清除组件引用
    删除对象属性(列表项组件, 索引)
    ' 刷新列表
    刷新列表()
    弹出提示("🗑️ 已删除", 1500)
结束 函数

' ========== 添加按钮点击事件 ==========
事件 按钮_添加.被单击(源对象, 事件源)
    添加代办事项()
结束 事件

' ========== 输入框回车事件 ==========
事件 编辑框_输入待办.键盘被按下(源对象,事件源)
    如果(事件源.key == "Enter")
        添加代办事项()
    结束 如果
结束 事件

关键代码解读

1. 数据持久化:保存配置 + JSON 编解码

变量 保存的数据 = 读取配置("待办列表")
如果(保存的数据 != 空)
    待办列表 = JSON解析(保存的数据)
结束 如果
' ...修改后...
保存配置("待办列表", JSON编码(待办列表))
  • 保存配置/读取配置 底层为浏览器 localStorage,只能存字符串
  • 待办列表是对象数组,必须先用 JSON编码 转字符串再保存,读取时用 JSON解析 还原
  • 任何修改(添加/删除/切换完成)后都要重新 保存配置 才能持久化

2. 列表框数据驱动渲染三段式

列表框_待办事项.添加项目集(待办列表)               ' ① 填充数据
列表框_待办事项.置请求加载项目回调((项目集合, 索引) => { ' ② 定义渲染回调
    ' ...创建项容器并返回...
    返回 项容器
})
列表框_待办事项.渲染()                              ' ③ 触发渲染
  • 第①步 添加项目集 批量填数据(也可用循环 添加项目
  • 第②步 置请求加载项目回调 定义每项的展示视图
  • 第③步 渲染() 让框架遍历数据集合,按索引逐项回调取视图并加入 DOM
  • 模型要点:数据与视图分离添加项目 不产生 DOM,必须 渲染() 才触发

3. 复合列表项:弹性布局嵌套

变量 项容器 = 创建 弹性布局()
项容器.方向 = "row"
项容器.主轴对齐 = "space-between"
' 左容器:复选框 + 文本
变量 左容器 = 创建 弹性布局()
左容器.添加组件(复选框)
左容器.添加组件(文本标签)
' 右侧:删除按钮
项容器.添加组件(左容器)
项容器.添加组件(删除按钮)
返回 项容器
  • 列表项根为水平弹性布局(方向 = "row"),用 主轴对齐 = "space-between" 让左容器和删除按钮两端对齐
  • 左容器也是弹性布局,内部 间隔 = 8 让复选框与文本之间留空
  • 复合视图必须以一个根容器返回,不能返回多个组件

4. 嵌套闭包中的索引捕获

列表框_待办事项.置请求加载项目回调((项目集合, 索引) => {
    变量 复选框 = 创建 单选框()
    复选框.置选中状态被改变回调((源对象, 是否选中) => {
        待办列表[索引] = 项目数据          ' 引用外层 索引
    })
    变量 删除按钮 = 创建 按钮()
    删除按钮.置被单击回调((源对象, 事件对象) => {
        删除待办事项(索引)                  ' 引用外层 索引
    })
    返回 项容器
})
  • 内层回调引用外层渲染回调的 索引 参数
  • 用户点击时外层回调早已返回,依赖拉姆达按调用时的值捕获(类似 JS let 语义)
  • 每次渲染回调调用时 索引 独立,因此每项的点击都能取到正确序号
  • ⚠️ 注意:若后续调用 刷新列表() 重新渲染,新回调会绑定新的 索引;旧的已销毁视图不会触发

5. 列表项组件引用缓存

变量 列表项组件 = { }   ' 页面变量
' 渲染回调内
列表项组件[索引] = {
    "容器": 项容器,
    "复选框": 复选框,
    "文本标签": 文本标签
}
' 删除时
删除对象属性(列表项组件, 索引)
  • 用对象 列表项组件 按索引缓存每项的组件引用,方便后续直接访问
  • 删除待办时同步清除缓存,避免内存泄漏与脏引用

6. 切换完成状态的视图更新策略

复选框.置选中状态被改变回调((源对象, 是否选中) => {
    项目数据.已完成 = 是否选中
    待办列表[索引] = 项目数据
    保存配置("待办列表", JSON编码(待办列表))
    更新统计信息()
})
  • 切换完成状态时,更新数据 + 持久化 + 刷新统计
  • 文本标签的删除线/颜色样式是在渲染回调中根据 项目数据.已完成 初始设置的
  • ⚠️ 注意:此处没有调用 刷新列表() 重新渲染整列,因此切换完成后文本的删除线样式不会立即变化。若需立即变化,可在此回调中直接操作缓存的 文本标签
复选框.置选中状态被改变回调((源对象, 是否选中) => {
    项目数据.已完成 = 是否选中
    待办列表[索引] = 项目数据
    保存配置("待办列表", JSON编码(待办列表))
    ' 直接更新已缓存的文本标签样式(即时生效)
    变量 缓存项 = 列表项组件[索引]
    如果(是否选中)
        缓存项.文本标签.文本颜色 = "#999"
        缓存项.文本标签.字体装饰 = "line-through"
    否则
        缓存项.文本标签.文本颜色 = "#333"
        缓存项.文本标签.字体装饰 = "none"
    结束 如果
    更新统计信息()
})

7. 空状态与列表可视切换

如果(总数 == 0)
    标签_空状态.可视 = 真
    列表框_待办事项.可视 = 假
否则
    标签_空状态.可视 = 假
    列表框_待办事项.可视 = 真
结束 如果
  • 可视 属性控制组件显示/隐藏(底层为 display 切换)
  • 空状态标签 .spldg 中初始 可视="假",加载时根据数据动态切换

8. 回车键添加

事件 编辑框_输入待办.键盘被按下(源对象,事件源)
    如果(事件源.key == "Enter")
        添加代办事项()
    结束 如果
结束 事件
  • 键盘事件通过 事件源.key 判断按键("Enter" 是回车键的标准 key 值)
  • 按钮_添加.被单击 共用同一个 添加代办事项() 函数,避免逻辑重复

注意事项

  • 持久化需 JSON 编解码保存配置 只能存字符串,对象数组必须 JSON编码 后存、JSON解析 后取。
  • 渲染回调必须返回视图:若不 返回 视图组件,对应项不会显示。
  • 闭包索引捕获:内层回调引用外层 索引 依赖拉姆达按值捕获语义;若遇异常应改用参数绑定或缓存对象。
  • 切换状态即时更新:仅修改数据不重新渲染时,视图样式不会自动更新;如需即时变化,直接操作缓存的组件实例。
  • 循环边界语义变量循环(计次 = 0, 取数组成员数(待办列表), 1) 等效于 for(i=0; i<长度; i++),即 计次 < 目标值 时循环、达到目标值退出,左闭右开区间,遍历数组正好不越界。
  • 删除索引同步删除成员(待办列表, 索引) 后必须刷新列表重新渲染,否则渲染回调中绑定的旧索引会错位。
  • .spldg.spl 成对:两个文件名称必须相同且同时存在,组件通过 名称 属性在 .spl 中被引用。
  • 弹出提示 字符变体:本文档统一使用 弹出提示,不同文档中可能存在 弹出提示 等字符显示差异,实际为同一函数。

学习要点总结

知识点关键代码适用场景
数据持久化保存配置(键,值) + 读取配置(键)localStorage 存取
JSON 编解码JSON编码(对象) / JSON解析(文本)对象与字符串转换
批量填数据添加项目集(数组)一次性加入多项
数据驱动渲染置请求加载项目回调 + 渲染()列表项自定义展示
复合列表项弹性布局嵌套多组件复杂列表项
主轴对齐主轴对齐 = "space-between"两端对齐
嵌套闭包内层回调引用外层 索引项点击事件
组件引用缓存列表项组件[索引] = {...}后续直接访问
空状态切换可视 = 真/假空列表提示
数组操作添加成员/删除成员/取数组成员数列表数据维护
键盘事件键盘被按下 + 事件源.key回车提交
删除线样式字体装饰 = "line-through"已完成态标记

相关文档

  • 文档列表类组件_列表框 —— 列表框完整属性与方法
  • 文档布局类组件_弹性布局 —— 弹性布局方向、对齐、间隔
  • 文档布局类组件_绝对布局 —— 绝对布局作为根布局
  • 文档文本类组件_标签 —— 标签属性
  • 文档文本类组件_编辑框 —— 编辑框与提示文本、键盘事件
  • 文档按钮类组件_按钮 —— 按钮属性
  • 文档选择类组件_单选框 —— 单选框/复选框与选中回调
  • 文档前端(JavaScript)函数_存储配置 —— 保存配置/读取配置
  • 文档前端(JavaScript)函数_JSON —— JSON编码/JSON解析
  • 文档前端(JavaScript)函数_数组 —— 添加成员/删除成员/取数组成员数
  • 文档轻语言网站:循环语句体结构 —— 变量循环计次循环 说明
  • 文档轻语言网站:变量与数组与对象的定义 —— 变量、数组、对象、拉姆达
  • 文档轻语言网站:自定义设计列表内容 —— 列表框/宫格框渲染基础
  • 文档综合演示:网页留言板开发 —— 列表框数据驱动渲染示例
  • 文档综合演示:手机移动端记事本网页开发 —— 移动端列表+本地存储示例
  • 文档轻语言网站:界面XML定义规范 —— .spldg XML 写法规范