兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
# JSBox 学习路线:从入门到实践 (ChatGPT版) ## 前言 这份学习材料根据我们之前互动学习的成果,并结合审阅意见修订而成。它旨在提供一个更准确、更详尽、更符合当前 JSBox API 最佳实践的系统化学习路径。JSBox 是一款强大的 iOS 脚本工具,它允许你使用 JavaScript 语言与 iOS 原生功能深度交互,实现自动化、自定义 UI 和各种实用工具。 本材料将按照以下八个主要阶段展开: 1. 快速开始: 了解 JSBox 的基本哲学和代码运行方式。 2. 基础接口: 掌握与应用、设备、网络和数据存储相关的核心 API。 3. 构建界面: 学习如何使用 JavaScript 定义和布局原生 iOS UI。 4. 控件列表: 深入探索 JSBox 提供的各种 UI 控件及其特定用法。 5. 数据类型与内置函数: 理解 JavaScript 与 Native 数据转换,并掌握常用辅助函数。 6. Promise 与高级特性: 学习异步编程的最佳实践,以及更多强大的扩展功能。 7. 包管理: 了解如何组织大型、模块化、可维护的 JSBox 项目。 8. Objective-C Runtime: 探索 JSBox 的终极武器,直接与 iOS 原生底层交互。 最后,我们将通过一个完整的“提醒应用小程序”案例来综合运用所有知识。 --- ## 阶段一:快速开始 (Quick Start) 本阶段是 JSBox 学习的起点,旨在让你对 JSBox 的基本哲学、代码风格和运行方式有一个初步的认识。 ### 核心概念 - JavaScript 驱动: JSBox 脚本基于 JavaScript (支持 ES6 标准语法)。 - API 风格: 所有 JSBox API 都以 $ 开头(如 $ui, $http)。 - 轻量化与移动优先: API 设计简洁,适合移动端编写。 - 沙盒环境: 每个脚本在独立沙盒中运行。 - 运行方式: App 内编写、URL Scheme 安装、VSCode 同步、AirDrop 传输等。 ### 常用 API 示例 1) 弹出简单提示 ```javascript $ui.alert("Hello, JSBox Learner!"); $ui.alert({ title: "欢迎学习", message: "这是你的第一个 JSBox 提示框。", actions: ["好的"] }); ``` 2) 打印日志到控制台 ```javascript console.log("这是一个普通的日志信息。"); console.info("应用启动成功。"); console.warn("注意:某个参数可能为空。"); console.error("发生了一个错误!"); ``` 3) 获取剪贴板内容并预览 ```javascript const clipboardText = $clipboard.text; if (clipboardText) { $ui.preview({ title: "剪贴板内容", text: clipboardText }); $ui.toast("已获取剪贴板文本并预览。"); } else { $ui.toast("剪贴板中没有文本内容。"); } const clipboardItems = $clipboard.items; if (clipboardItems && clipboardItems.length > 0) { $ui.preview({ title: "剪贴板所有项目", text: JSON.stringify(clipboardItems, null, 2) }); } ``` --- ## 阶段二:基础接口 (Foundation APIs) 涵盖模块:$app, $device, $http, $cache, $clipboard, $file, $thread, $system, $keychain, $l10n。 ### 常用 API 示例 1) $app ```javascript console.log("JSBox 应用信息:", $app.info); console.log("当前脚本运行环境:", $app.env); $app.idleTimerDisabled = true; $ui.toast("屏幕将保持常亮。"); $app.openURL("https://www.apple.com"); $app.openURL("weixin://"); ``` 2) $device ```javascript const nt = $device.networkType; const mapNetworkType = { 0: "无网络", 1: "Wi‑Fi", 2: "蜂窝" }; console.log("当前网络类型:", mapNetworkType[nt] ?? "未知"); const deviceInfo = $device.info; $ui.alert({ title: "设备信息", message: ` 型号: ${deviceInfo.model} 系统版本: ${deviceInfo.version} 屏幕宽度: ${deviceInfo.screen.width} px 屏幕高度: ${deviceInfo.screen.height} px 电池电量: ${(deviceInfo.battery.level * 100).toFixed(0)}% 网络类型: ${(function () { const nt = $device.networkType; const map = { 0: "无网络", 1: "Wi‑Fi", 2: "蜂窝" }; return map[nt] ?? "未知"; })()} 当前是否深色模式: ${$device.isDarkMode ? "是" : "否"} ` }); $device.taptic(0); $ui.toast("设备轻微振动了一下。"); ``` 3) $http ```javascript $ui.toast("将发起网络请求获取今日诗词..."); $ui.loading(true); $http.get({ url: "https://v1.hitokoto.cn/?c=a&c=b&c=c&c=d&c=e&c=f&c=g&c=h&c=i&c=j&c=k&encode=json", handler: function (resp) { $ui.loading(false); if (resp.error) { $ui.alert("请求失败: " + resp.error.localizedDescription); console.error("HTTP 请求错误:", resp.error); return; } const data = resp.data; if (data && data.hitokoto) { $ui.alert({ title: "一言", message: `${data.hitokoto}\n---- ${data.from}` }); console.log("今日一言数据:", data); } else { $ui.alert("未获取到有效数据。"); } } }); // 下载图片并分享(可选) // const imageUrl = $clipboard.link || "https://example.com/example.png"; // if (imageUrl) { // $ui.toast("将下载图片并尝试分享..."); // $ui.loading(true); // $http.download({ // url: imageUrl, // showsProgress: true, // message: "下载图片中...", // handler: function (resp) { // $ui.loading(false); // if (resp.error) return $ui.alert("图片下载失败: " + resp.error.localizedDescription); // if (resp.data) { // $share.sheet(resp.data); // $ui.toast("图片下载完成并已调起分享。"); // } // } // }); // } ``` 4) $file(同步 API) ```javascript const filename = "my_temp_log.txt"; const foldername = "my_data_folder"; const writeSuccess = $file.write({ data: $data({ string: "日志内容:" + new Date().toLocaleString() + "\n" }), path: filename }); if (writeSuccess) { $ui.toast("文件写入成功: " + filename); console.log("绝对路径:", $file.absolutePath(filename)); } else { $ui.alert("文件写入失败!"); } if ($file.exists(filename)) { const fileData = $file.read(filename); if (fileData && fileData.string) { $ui.alert({ title: "文件内容", message: fileData.string }); } } if (!$file.exists(foldername)) { const ok = $file.mkdir(foldername); if (ok) $ui.toast("文件夹创建成功: " + foldername); } console.log("当前脚本沙盒内容:", $file.list("./")); // $file.delete(filename); ``` 5) $cache(同步 API) ```javascript $cache.set("appSettings", { username: "JSBoxUser", theme: "dark", fontSize: 16 }); $ui.toast("设置已缓存!"); console.log("从缓存中读取:", $cache.get("appSettings")); // $cache.clear(); ``` 6) $clipboard ```javascript $ui.alert({ title: "当前剪贴板文本", message: $clipboard.text || "剪贴板为空" }); $clipboard.text = "这段文字是从 JSBox 设置的!"; $ui.toast("剪贴板文本已更新。"); console.log("剪贴板中的链接:", $clipboard.link); console.log("剪贴板中的电话号码:", $clipboard.phoneNumber); ``` 7) $thread 与延时 ```javascript $ui.loading(true); $ui.toast("后台任务进行中..."); $delay(0.1, () => { $thread.background(() => { for (let i = 0; i < 10_000_000; i++) {} $thread.main(() => { $ui.loading(false); $ui.toast("后台任务完成!"); }); }); }); $delay(2, () => $ui.toast("2 秒后执行的任务。")); (async () => { $ui.toast("等待 3 秒..."); await $wait(3); $ui.alert("等待结束,弹出 Alert。"); })(); ``` 8) $system(注意权限与系统限制) ```javascript // $system.brightness = 0.5; // $system.volume = 0.8; // 可能受 iOS 限制而无效 // 拨打电话 // $system.call("10086"); // 短信与邮件(推荐) /* await $message.sms({ recipients: ["10086"], body: "你好,我想查询话费。" }); await $message.mail({ to: ["log.e@qq.com"], subject: "测试邮件", body: "这是来自 JSBox 的测试邮件。" }); */ // mailto // $app.openURL("mailto:xxx@example.com?subject=测试&body=来自JSBox"); ``` 9) $keychain(同步 API) ```javascript const KEY = "mySecretPassword"; const DOMAIN = "com.myapp.domain"; const setOk = $keychain.set(KEY, "your_secure_password_123", DOMAIN); if (setOk) $ui.toast("密码已安全存储。"); const pwd = $keychain.get(KEY, DOMAIN); if (pwd) $ui.alert({ title: "获取到密码", message: pwd }); // $keychain.remove(KEY, DOMAIN); ``` 10) $l10n(strings/ 优先于 $app.strings) ```javascript $app.strings = { "en": { "APP_NAME": "My Applet", "GREETING": "Hello, how are you?", "OK": "OK" }, "zh-Hans": { "APP_NAME": "我的小程序", "GREETING": "你好,你好吗?", "OK": "好的" } }; $ui.alert({ title: $l10n("APP_NAME"), message: $l10n("GREETING"), actions: [$l10n("OK")] }); ``` --- ## 阶段三:构建界面 (Build UI) ### 核心概念 - UI 树结构,$ui.render/$ui.push。 - props/layout/events。 - $(id) 获取视图实例。 - Dark Mode 适配:$color({ light, dark })。 - 修改当前页面背景:$ui.controller.view.bgcolor 或 $ui.window.bgcolor。 ### 示例 1) 基本页面 ```javascript $ui.render({ props: { title: "我的第一个 UI 页面", navBarHidden: false, bgcolor: $color("systemBackground"), theme: "auto" }, views: [ { type: "label", props: { text: "Hello, UI World!", textColor: $color("label"), font: $font("bold", 28), align: $align.center }, layout: (make, view) => { make.centerX.equalTo(view.super); make.centerY.equalTo(view.super).offset(-50); make.width.equalTo(view.super).multipliedBy(0.8); make.height.equalTo(50); }, events: { tapped: () => $ui.toast("你点击了标签!") } } ] }); ``` 2) 页面跳转 ```javascript $ui.render({ props: { title: "主页面" }, views: [ { type: "button", props: { title: "进入详情" }, layout: $layout.center, events: { tapped: () => { $ui.push({ props: { title: "详情页面", bgcolor: $color("systemBackground") }, views: [{ type: "label", props: { text: "这是详情内容", align: $align.center }, layout: $layout.fill }] }); } } } ] }); ``` 3) 动态修改视图 ```javascript $ui.render({ views: [ { type: "label", props: { id: "myDynamicLabel", text: "点击按钮改变我!", textColor: $color("label"), font: $font(20), align: $align.center }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(100); make.width.equalTo(250); make.height.equalTo(40); } }, { type: "button", props: { title: "改变文本" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("myDynamicLabel").bottom).offset(30); make.size.equalTo($size(120, 40)); }, events: { tapped: () => { const label = $("myDynamicLabel"); label.text = "文本已改变!" + new Date().getSeconds(); label.textColor = $color($rgb(Math.random() * 255, Math.random() * 255, Math.random() * 255)); $ui.toast("文本已更新!"); } } } ] }); ``` 4) 复杂布局 ```javascript $ui.render({ views: [ { type: "view", props: { id: "redView", bgcolor: $color("red"), cornerRadius: 10 }, layout: (make) => { make.left.top.inset(20); make.width.equalTo(100); make.height.equalTo(100); } }, { type: "view", props: { id: "blueView", bgcolor: $color("blue"), cornerRadius: 10 }, layout: (make) => { make.top.equalTo($("redView").top); make.left.equalTo($("redView").right).offset(10); make.size.equalTo($("redView")); } }, { type: "view", props: { bgcolor: $color("green"), cornerRadius: 10 }, layout: (make, view) => { make.top.equalTo($("blueView").bottom).offset(10); make.right.inset(20); make.width.equalTo(view.super).multipliedBy(0.5).offset(-20); make.height.equalTo(50); } } ] }); ``` 5) 手势与事件 ```javascript $ui.render({ views: [ { type: "view", props: { bgcolor: $color("purple"), userInteractionEnabled: true }, layout: $layout.center, events: { longPressed: (info) => $ui.toast(`长按!(${info.location.x.toFixed(0)}, ${info.location.y.toFixed(0)})`), doubleTapped: (sender) => { sender.alpha = sender.alpha === 1 ? 0.5 : 1; $ui.toast("双击:透明度切换。"); }, touchesBegan: (s, loc, locs) => console.log("触摸开始,触点数:", locs.length), touchesEnded: () => console.log("触摸结束。") } } ] }); ``` 6) 菜单(上下文与下拉) ```javascript $ui.render({ props: { title: "菜单示例", navButtons: [ { symbol: "ellipsis.circle", tintColor: $color("tintColor"), menu: { title: "更多操作", pullDown: true, items: [ { title: "刷新", symbol: "arrow.clockwise", handler: () => $ui.toast("刷新中...") }, { title: "分享", symbol: "square.and.arrow.up", handler: () => $share.sheet("分享内容") }, { title: "高级", items: [ { title: "设置", symbol: "gearshape", handler: () => $ui.toast("打开设置") }, { title: "关于", symbol: "info.circle", handler: () => $ui.toast("关于脚本") } ] }, { title: "删除数据", symbol: "trash", destructive: true, handler: () => $ui.alert("确认删除?") } ] } } ] }, views: [ { type: "label", props: { text: "长按我查看上下文菜单\n或点击导航栏按钮", align: $align.center, lines: 0, textColor: $color("label"), menu: { title: "上下文操作", items: [ { title: "复制", symbol: "doc.on.doc", handler: () => { $clipboard.text = "复制的内容"; $ui.toast("已复制"); } }, { title: "分享", symbol: "square.and.arrow.up", handler: () => $share.sheet("通过上下文菜单分享的内容") } ] } }, layout: $layout.center } ] }); ``` --- ## 阶段四:控件列表 (Components) 1) label ```javascript { type: "label", props: { text: "这是一个标签", font: $font("ChalkboardSE-Bold", 24), textColor: $color("red"), shadowColor: $color("systemGray"), align: $align.left, lines: 2 }, layout: $layout.fill } ``` 2) button ```javascript { type: "button", props: { title: "点击我", titleColor: $color("white"), bgcolor: $color("blue"), icon: $icon("007", $color("yellow"), $size(20, 20)), symbol: "folder.fill", imageEdgeInsets: $insets(0, 0, 0, 10) }, events: { tapped: () => $ui.toast("按钮被点击了!") }, layout: $layout.center } ``` 3) input ```javascript { type: "input", props: { placeholder: "请输入文本", type: $kbType.default, darkKeyboard: true, text: "初始文本", secure: true }, events: { changed: (sender) => console.log("输入变化:", sender.text), returned: (sender) => $ui.alert(`你输入了: ${sender.text}`) }, layout: $layout.fill } ``` 4) text ```javascript { type: "text", props: { placeholder: "请输入多行文本...", editable: true, selectable: true, insets: $insets(10, 10, 10, 10), styledText: "**粗体** *斜体* [链接](https://jsbox.app)", font: $font(16) }, events: { didChange: (sender) => console.log("文本变化:", sender.text), didChangeSelection: (sender) => console.log("选中区域变化:", sender.selectedRange) }, layout: $layout.fill } ``` 5) list ```javascript { type: "list", props: { id: "list", rowHeight: 70, autoRowHeight: false, separatorHidden: false, separatorColor: $color("separatorColor"), reorder: true, data: [ { title: "水果", rows: [ { label: { text: "苹果" }, icon: { symbol: "apple.logo" } }, { label: { text: "香蕉" }, icon: { symbol: "leaf.fill" } } ] }, { title: "动物", rows: [ { label: { text: "猫" }, icon: { symbol: "pawprint.fill" } }, { label: { text: "狗" }, icon: { symbol: "hare.fill" } } ] } ], template: { views: [ { type: "image", props: { id: "icon", tintColor: $color("tintColor") }, layout: (make) => { make.left.inset(15); make.centerY.equalTo(make.super); make.size.equalTo($size(30, 30)); } }, { type: "label", props: { id: "label" }, layout: (make) => { make.left.equalTo($("icon").right).offset(10); make.right.inset(15); make.centerY.equalTo(make.super); } } ] }, actions: [ { title: "删除", color: $color("red"), handler: (sender, indexPath) => sender.delete(indexPath) }, { title: "编辑", color: $color("blue"), handler: (sender, indexPath) => $ui.toast(`编辑 ${sender.data[indexPath.section].rows[indexPath.row].label.text}`) } ] }, events: { didSelect: (sender, indexPath, data) => $ui.toast(`选择了: ${data.label.text}`), reorderMoved: (fromPath, toPath) => console.log(`从 ${fromPath.section}-${fromPath.row} 到 ${toPath.section}-${toPath.row}`), pulled: (sender) => { $ui.toast("加载更多数据..."); $ui.loading(true); $delay(2, () => { const currentData = $("list").data; const newData = Array(5).fill(0).map((_, i) => ({ label: { text: `新项目 ${currentData[0].rows.length + i + 1}` } })); currentData[0].rows.push(...newData); $("list").data = currentData; sender.endRefreshing(); $ui.loading(false); }); }, didReachBottom: (sender) => { $ui.toast("加载更多数据..."); $ui.loading(true); $delay(1.5, () => { const currentData = $("list").data; const newItems = Array(5).fill(0).map((_, i) => ({ label: { text: `更多项 ${currentData[0].rows.length + i + 1}` }, icon: { symbol: "cloud.fill" } })); currentData[0].rows.push(...newItems); $("list").data = currentData; sender.endFetchingMore(); $ui.loading(false); }); } }, layout: $layout.fill } ``` 6) matrix ```javascript { type: "matrix", props: { columns: 3, itemHeight: 120, spacing: 5, data: Array(10).fill(0).map((_, i) => ({ label: { text: `Item ${i + 1}` }, image: { symbol: `star.fill` } })), template: { views: [ { type: "image", props: { id: "image", tintColor: $color("systemYellow") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(10); make.size.equalTo($size(60, 60)); } }, { type: "label", props: { id: "label", align: $align.center, font: $font(14), textColor: $color("label") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("image").bottom).offset(5); make.width.equalTo(make.super); make.height.equalTo(20); } } ] } }, events: { didSelect: (sender, indexPath, data) => $ui.toast(`点击了: ${data.label.text}`) }, layout: $layout.fill } ``` 7) web ```javascript { type: "web", props: { url: "https://www.apple.com/cn/", showsProgress: true, toolbar: true, scrollEnabled: true, script: function () { const heading = document.querySelector('h1'); if (heading) $notify("webContentLoaded", { text: heading.innerText }); } }, events: { didFinish: () => $ui.toast("网页加载完成!"), didFail: (sender, navigation, error) => $ui.alert("网页加载失败: " + error.localizedDescription), decideNavigation: (sender, action) => { // 若取不到 requestURL,可尝试 action.URL 或 action.url,按当前文档为准 if (action.requestURL && action.requestURL.startsWith("https://apple.com/cn/iphone/")) return false; return true; }, webContentLoaded: (object) => $ui.alert(`来自网页的消息:${object.text}`) }, layout: $layout.fill } ``` 8) picker ```javascript $ui.render({ views: [ { type: "button", props: { title: "选择日期" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(50); make.size.equalTo($size(120, 40)); }, events: { tapped: async () => { const selectedDate = await $picker.date({ mode: 0, date: new Date() }); if (selectedDate) $ui.alert(`你选择了日期: ${selectedDate.toLocaleString()}`); else $ui.toast("取消日期选择。"); } } }, { type: "button", props: { title: "选择颜色" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("button").bottom).offset(20); make.size.equalTo($size(120, 40)); }, events: { tapped: async () => { const selectedColor = await $picker.color({ color: $color("red") }); if (selectedColor) { $ui.alert(`你选择了颜色: ${selectedColor.hexCode}`); $ui.controller.view.bgcolor = selectedColor; } else { $ui.toast("取消颜色选择。"); } } } }, { type: "button", props: { title: "通用选择器" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("button").bottom).offset(20); make.size.equalTo($size(150, 40)); }, events: { tapped: async () => { const result = await $picker.data({ items: [["选项 A1", "选项 A2", "选项 A3"], ["选项 B1", "选项 B2"]] }); if (result) { $ui.alert(`你选择了: ${result.data[0]} 和 ${result.data[1]} (索引: ${result.selectedRows[0]}, ${result.selectedRows[1]})`); } else { $ui.toast("取消通用选择。"); } } } } ] }); ``` --- ## 阶段五:数据类型与内置函数 (Data Types & Built-in Functions) ### 核心概念 - 数据类型构造:$rect, $size, $point, $insets, $color, $font, $data, $image, $icon, $indexPath 等。 - 动态适配:$color({ light, dark })/$image({ light, dark })。 - 全局常量:$align, $env, $blurStyle, $contentMode, $kbType, $mediaType, $alertActionType, $popoverDirection 等。 - 辅助函数:$l10n, $delay, $wait, $props, $desc 等。 ### 示例 1) 几何与颜色 ```javascript $ui.render({ props: { title: "几何与颜色", bgcolor: $color("systemBackground"), theme: "auto" }, views: [ { type: "view", props: { frame: $rect(20, 20, 100, 100), bgcolor: $rgba(255, 0, 0, 0.7), cornerRadius: 10, borderWidth: 2, borderColor: $color({ light: "#0000FF", dark: "#00FFFF" }) } }, { type: "label", props: { text: "动态颜色文本", textColor: $color("label"), font: $font("bold", 20) }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(150); } } ] }); ``` 2) $data/$image/$icon ```javascript const base64String = "SGVsbG8sIFdvcmxkIQ=="; const textData = $data({ base64: base64String, encoding: 4 }); console.log("Base64 解码文本:", textData.string); const transparentGifBase64 = "R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="; $ui.render({ views: [ { type: "image", props: { image: $image("data:image/gif;base64," + transparentGifBase64), symbol: "person.circle.fill", tintColor: $color("systemGreen"), contentMode: $contentMode.scaleAspectFit }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(50); make.size.equalTo($size(80, 80)); } }, { type: "label", props: { icon: $icon("005", $color("systemGreen"), $size(24, 24)), text: " JSBox 内置图标", font: $font(18), textColor: $color("label") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("image").bottom).offset(20); } } ] }); ``` 3) $indexPath/$range ```javascript const p = $indexPath(1, 5); console.log(`区: ${p.section}, 行: ${p.row}`); const r = $range(10, 5); console.log(`范围起始: ${r.location}, 长度: ${r.length}`); ``` 4) 延时 ```javascript $ui.toast("将在 1 秒后消失..."); $delay(1, () => $ui.alert("提示已消失。")); (async () => { $ui.toast("等待 2 秒..."); await $wait(2); $ui.alert("等待结束,弹出 Alert。"); })(); ``` --- ## 阶段六:Promise 与高级特性 (Promise & Advanced Features) ### 核心概念 - Promise/async-await。 - JSBox API 的 required/optional 两种 Promise 模式(optional 需 async: true)。 - 扩展 API:$text, $qrcode, $archiver, $browser, $detector, $share, $push。 - 原生 SDK:$calendar, $reminder, $contact, $location, $photo, $message, $safari。 ### 示例 1) 异步链 ```javascript $ui.render({ props: { title: "Async/Await 演示", theme: "auto" }, views: [ { type: "button", props: { title: "开始异步链" }, layout: $layout.center, events: { tapped: async () => { $ui.loading(true); $ui.toast("步骤 1/3: 请求数据..."); try { const httpResp = await $http.get("https://fanyi.youdao.com/openapi.do?keyfrom=JSBox_Test&key=139365261&type=data&doctype=json&version=1.1&q=hello"); $ui.loading(false); if (httpResp.error) throw new Error("网络请求失败: " + httpResp.error.localizedDescription); const translation = httpResp.data.translation[0]; $ui.toast("步骤 1 完成:翻译成功!"); $ui.loading(true); $ui.toast("步骤 2/3: 请输入文本,结果将 Base64 编码..."); const inputText = await $input.text({ placeholder: "请输入一个短语进行 Base64 编码" }); $ui.loading(false); if (!inputText) return $ui.toast("取消输入,停止操作。"); const encodedText = $text.base64Encode(inputText); $ui.toast("步骤 2 完成:文本已编码!"); $ui.loading(true); $ui.toast("步骤 3/3: 请从相册选取一张图片..."); const photoResp = await $photo.pick({ mediaTypes: [$mediaType.image], async: true }); $ui.loading(false); if (photoResp && photoResp.image) { $ui.alert({ title: "所有步骤完成!", message: ` 原文: ${inputText} -> 翻译: ${translation} 编码后: ${encodedText} 图片选取成功! ` }); } else { $ui.toast("步骤 3 取消或未选取图片。"); } } catch (e) { $ui.loading(false); $ui.alert("操作失败: " + e.message); console.error("异步链错误:", e); } } } } ] }); ``` 2) $text ```javascript console.log("UUID:", $text.uuid); const originalUrl = "https://www.example.com?param=中文内容&key=value"; console.log("URL 编码:", $text.URLEncode(originalUrl)); console.log("URL 解码:", $text.URLDecode($text.URLEncode(originalUrl))); console.log("MD5:", $text.MD5("JSBox")); console.log("SHA1:", $text.SHA1("JSBox")); console.log("SHA256:", $text.SHA256("JSBox")); const markdownText = "# 标题\n**粗体文本** *斜体文本*"; const htmlText = "<h1>标题</h1><b>粗体文本</b><i>斜体文本</i>"; const convertedHtml = $text.markdownToHtml(markdownText); console.log("Markdown 转 HTML:", convertedHtml); (async () => { const markdown = await $text.htmlToMarkdown({ html: htmlText }); console.log("HTML 转 Markdown:", markdown); })(); ``` 3) $qrcode ```javascript $ui.render({ props: { title: "二维码示例" }, views: [ { type: "image", props: { id: "qrcodeImage", image: $qrcode.encode("https://jsbox.app"), contentMode: $contentMode.scaleAspectFit }, layout: (make) => { make.center.equalTo(make.super); make.size.equalTo($size(150, 150)); } }, { type: "button", props: { title: "扫描二维码" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("qrcodeImage").bottom).offset(30); make.size.equalTo($size(120, 40)); }, events: { tapped: async () => { const scanResult = await $qrcode.scan(); if (scanResult) $ui.alert(`扫描结果: ${scanResult}`); else $ui.toast("取消扫描。"); } } } ] }); ``` 4) $archiver ```javascript $file.write({ data: $data({ string: "Hello world for zip test!" }), path: "test.txt" }); $ui.loading(true); $ui.toast("文件压缩中..."); $archiver.zip({ paths: ["test.txt"], dest: "myarchive.zip", handler: async (success) => { $ui.loading(false); if (success) { $ui.toast("文件压缩成功!"); $ui.loading(true); $ui.toast("文件解压中..."); const unzipSuccess = await $archiver.unzip({ path: "myarchive.zip", dest: "unzipped_folder" }); $ui.loading(false); if (unzipSuccess) { $ui.toast("文件解压成功!"); console.log("解压后的文件:", $file.list("unzipped_folder")); } else { $ui.alert("文件解压失败!"); } } else { $ui.alert("文件压缩失败!"); } } }); ``` 5) Native SDK($photo/$calendar) ```javascript $ui.render({ props: { title: "Native SDK 示例" }, views: [ { type: "button", props: { title: "选取照片" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(50); make.size.equalTo($size(120, 40)); }, events: { tapped: async () => { $ui.loading(true); try { const resp = await $photo.pick({ mediaTypes: [$mediaType.image], async: true }); $ui.loading(false); if (resp && resp.image) $ui.alert("选取照片成功!"); else $ui.toast("未选取照片或取消。"); } catch (e) { $ui.loading(false); $ui.alert("选取照片失败: " + e.message); } } } }, { type: "button", props: { title: "创建日历事件" }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("button").bottom).offset(20); make.size.equalTo($size(150, 40)); }, events: { tapped: async () => { $ui.loading(true); try { const now = new Date(); const tomorrow = new Date(now.getTime() + 24 * 3600 * 1000); const resp = await $calendar.create({ title: "JSBox 学习任务", startDate: now, endDate: tomorrow, notes: "完成 JSBox 学习材料的阅读和实践。", url: "https://jsbox.app", async: true }); $ui.loading(false); if (resp && resp.status) $ui.toast("日历事件创建成功!"); else $ui.alert("日历事件创建失败或取消。"); } catch (e) { $ui.loading(false); $ui.alert("创建日历事件失败: " + e.message); } } } } ] }); ``` --- ## 阶段七:包管理 (Package Management) 项目结构示例 ``` MyPackageScript/ ├── main.js ├── config.json ├── strings/ │ ├── en.strings │ └── zh-Hans.strings ├── scripts/ │ └── data_manager.js └── assets/ └── icon.png ``` 1) main.js ```javascript // main.js const dataManager = require('./scripts/data_manager'); $app.strings = { "en": { "APP_TITLE": "My Applet", "WELCOME_MSG": "Welcome to Applet!", "LOAD_DATA": "Load Data", "SAVE_DATA": "Save Data", "COUNT": "Count: ", "OK": "OK" }, "zh-Hans": { "APP_TITLE": "我的小程序", "WELCOME_MSG": "欢迎使用小程序!", "LOAD_DATA": "加载数据", "SAVE_DATA": "保存数据", "COUNT": "数量:", "OK": "好的" } }; $ui.render({ props: { title: $l10n("APP_TITLE"), bgcolor: $color("systemBackground"), theme: "auto" }, views: [ { type: "image", props: { id: "image", src: "assets/icon.png", contentMode: $contentMode.scaleAspectFit }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(50); make.size.equalTo($size(80, 80)); } }, { type: "label", props: { id: "countLabel", text: $l10n("COUNT") + "0", align: $align.center, font: $font(20), textColor: $color("label") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("image").bottom).offset(30); make.width.equalTo(250); make.height.equalTo(30); } }, { type: "button", props: { title: $l10n("LOAD_DATA") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("countLabel").bottom).offset(30); make.size.equalTo($size(150, 40)); }, events: { tapped: async () => { $ui.loading(true); const data = await dataManager.loadData(); $ui.loading(false); $("countLabel").text = $l10n("COUNT") + data.length; $ui.toast("数据加载完成!"); } } }, { type: "button", props: { title: $l10n("SAVE_DATA") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("button").bottom).offset(20); make.size.equalTo($size(150, 40)); }, events: { tapped: async () => { $ui.loading(true); const currentData = await dataManager.loadData(); currentData.push({ timestamp: Date.now() }); const success = await dataManager.saveData(currentData); $ui.loading(false); if (success) { $("countLabel").text = $l10n("COUNT") + currentData.length; $ui.toast("数据保存成功!"); } else { $ui.alert("数据保存失败!"); } } } } ] }); (async () => { const initialData = await dataManager.loadData(); $("countLabel").text = $l10n("COUNT") + initialData.length; })(); ``` 2) scripts/data_manager.js ```javascript // scripts/data_manager.js const DATA_FILE = "my_app_data.json"; async function loadData() { if (!$file.exists(DATA_FILE)) return []; try { const data = $file.read(DATA_FILE); if (data && data.string) return JSON.parse(data.string); } catch (e) { console.error("Error parsing data file:", e); $ui.alert("加载数据失败!"); } return []; } async function saveData(data) { try { const dataString = JSON.stringify(data, null, 2); const success = $file.write({ data: $data({ string: dataString, encoding: 4 }), path: DATA_FILE }); return success; } catch (e) { console.error("Error saving data file:", e); $ui.alert("保存数据失败!"); return false; } } module.exports = { loadData, saveData }; ``` 3) strings/en.strings ``` "APP_TITLE" = "My Package App"; "WELCOME_MSG" = "Welcome!"; "LOAD_DATA" = "Load Data"; "SAVE_DATA" = "Save Data"; "COUNT" = "Count: "; "OK" = "OK"; ``` 4) strings/zh-Hans.strings ``` "APP_TITLE" = "我的打包应用"; "WELCOME_MSG" = "欢迎!"; "LOAD_DATA" = "加载数据"; "SAVE_DATA" = "保存数据"; "COUNT" = "数量:"; "OK" = "好的"; ``` --- ## 阶段八:Objective-C Runtime (Runtime) ### 核心概念 - $objc 获取类/实例;.$method 调用方法。 - jsValue()/ocValue() 在 JS 与 Native 之间转换。 - $define/$delegate 动态定义类与委托。 - $block 封装回调。 ### 示例 1) 调用原生方法 ```javascript const UIColor = $objc("UIColor"); const UIApplication = $objc("UIApplication"); const NSURL = $objc("NSURL"); const app = UIApplication.$sharedApplication(); const url = NSURL.$URLWithString("https://www.google.com"); if (app.$canOpenURL(url)) { app.$openURL(url); $ui.toast("已尝试打开 Google"); } else { $ui.alert("无法打开 Google"); } // const UIScreen = $objc("UIScreen"); // const screen = UIScreen.$mainScreen(); // const currentBrightness = screen.$brightness(); // $ui.alert(`当前屏幕亮度: ${(currentBrightness * 100).toFixed(0)}%`); // screen.$setBrightness(0.5); ``` 2) 原生 Alert + Block ```javascript $ui.render({ props: { title: "原生 Alert 演示" }, views: [ { type: "button", props: { title: "显示原生 Alert" }, layout: $layout.center, events: { tapped: () => { const alertController = $objc("UIAlertController").$alertControllerWithTitle_message_preferredStyle_( "原生 Alert 标题", "这个 Alert 是通过 Objective-C Runtime API 创建的!", 1 ); const defaultAction = $objc("UIAlertAction").$actionWithTitle_style_handler_( "好的", 0, $block("void, id", () => $ui.toast("你点击了 '好的'。")) ); const cancelAction = $objc("UIAlertAction").$actionWithTitle_style_handler_( "取消", 1, $block("void, id", () => $ui.toast("你点击了 '取消'。")) ); alertController.$addAction(defaultAction); alertController.$addAction(cancelAction); const currentVC = $ui.controller.ocValue(); currentVC.$presentViewController_animated_completion_(alertController, true, null); } } } ] }); ``` 3) 混合视图(原生 UILabel) ```javascript $ui.render({ props: { title: "原生标签混合显示", bgcolor: $color("systemBackground"), theme: "auto" }, views: [ { type: "label", props: { id: "jsLabel", text: "这是 JSBox UI 定义的标签", align: $align.center, font: $font("bold", 18), textColor: $color("label") }, layout: (make) => { make.centerX.equalTo(make.super); make.top.inset(50); make.width.equalTo(280); make.height.equalTo(30); } }, { type: "button", props: { id: "runtimeTargetButton", title: "点击查看 Runtime 效果", bgcolor: $color("systemOrange"), titleColor: $color("white"), cornerRadius: 8 }, layout: (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("jsLabel").bottom).offset(30); make.size.equalTo($size(200, 45)); } } ] }); $thread.main(() => { const rootView = $ui.controller.view.ocValue(); const nativeLabel = $objc("UILabel").$new(); nativeLabel.$setText("这是由 Runtime 创建的原生标签"); nativeLabel.$setTextColor($objc("UIColor").$systemBlueColor()); nativeLabel.$setTextAlignment(1); nativeLabel.$setFont($objc("UIFont").$systemFontOfSize(18)); const nativeLabelJSView = nativeLabel.jsValue(); nativeLabelJSView.layout = (make) => { make.centerX.equalTo(make.super); make.top.equalTo($("runtimeTargetButton").bottom).offset(30); make.width.equalTo(300); make.height.equalTo(30); }; rootView.$addSubview(nativeLabel); }); ``` --- ## 综合应用案例:提醒应用小程序 (增强版) 结构 ``` MyReminderApp/ ├── main.js ├── config.json ├── strings/ │ ├── en.strings │ └── zh-Hans.strings ├── scripts/ │ ├── reminder_model.js │ ├── reminder_view.js │ └── util.js └── assets/ └── icon.png ``` 1) config.json ```json { "info": { "name": "MyReminderApp", "version": "1.0.0", "author": "YourName", "icon": "icon.png", "category": "工具" }, "settings": { "theme": "auto", "minSDKVer": "2.12.0" } } ``` 2) strings/en.strings ``` "APP_TITLE" = "My Reminders"; "ADD_REMINDER" = "Add Reminder"; "EDIT_REMINDER" = "Edit Reminder"; "NEW_REMINDER" = "New Reminder"; "REMINDER_TEXT_PLACEHOLDER" = "Reminder Text"; "REMINDER_DATE" = "Reminder Date"; "SAVE" = "Save"; "DELETE" = "Delete"; "CANCEL" = "Cancel"; "REMINDER_SAVED_SUCCESS" = "Reminder saved!"; "REMINDER_DELETED_SUCCESS" = "Reminder deleted!"; "REMINDER_FETCH_FAILED" = "Failed to load reminders."; "NO_REMINDERS" = "No reminders yet. Add one!"; "OK" = "OK"; "CONFIRM_DELETE_TITLE" = "Confirm Delete?"; "CONFIRM_DELETE_MESSAGE" = "Are you sure you want to delete this reminder?"; "NOTIFICATION_PERMISSION_DENIED" = "Notification permission denied. Cannot schedule reminder."; "CALENDAR_PERMISSION_DENIED" = "Calendar permission denied. Cannot access calendar."; "REMINDER_PERMISSION_DENIED" = "Reminders permission denied. Cannot access reminders."; ``` 3) strings/zh-Hans.strings ``` "APP_TITLE" = "我的提醒"; "ADD_REMINDER" = "添加提醒"; "EDIT_REMINDER" = "编辑提醒"; "NEW_REMINDER" = "新提醒"; "REMINDER_TEXT_PLACEHOLDER" = "提醒内容"; "REMINDER_DATE" = "提醒日期"; "SAVE" = "保存"; "DELETE" = "删除"; "CANCEL" = "取消"; "REMINDER_SAVED_SUCCESS" = "提醒已保存!"; "REMINDER_DELETED_SUCCESS" = "提醒已删除!"; "REMINDER_FETCH_FAILED" = "加载提醒失败。"; "NO_REMINDERS" = "暂无提醒,点击添加!"; "OK" = "好的"; "CONFIRM_DELETE_TITLE" = "确认删除?"; "CONFIRM_DELETE_MESSAGE" = "你确定要删除这条提醒吗?"; "NOTIFICATION_PERMISSION_DENIED" = "通知权限被拒绝。无法调度提醒。"; "CALENDAR_PERMISSION_DENIED" = "日历权限被拒绝。无法访问日历。"; "REMINDER_PERMISSION_DENIED" = "提醒事项权限被拒绝。无法访问提醒。"; ``` 4) scripts/util.js(EventKit 权限包装) ```javascript // scripts/util.js function handleError(message, error) { $ui.loading(false); $ui.alert(message); if (error) console.error("Error:", message, error); else console.error("Error:", message); } /** * 请求日历或提醒事项权限(EventKit) * @param {"calendar"|"reminder"} entity * @returns {Promise<boolean>} */ async function requestEventKitAccess(entity) { const EventStoreClass = $objc("EKEventStore"); const store = EventStoreClass.$new(); const type = entity === "reminder" ? 1 : 0; // 0: Event, 1: Reminder const status = EventStoreClass.$authorizationStatusForEntityType(type); if (status === 3) return true; // Authorized if (status === 2) { // Denied handleError(entity === "calendar" ? $l10n("CALENDAR_PERMISSION_DENIED") : $l10n("REMINDER_PERMISSION_DENIED")); return false; } if (status === 0 || status === 1) { return await new Promise((resolve) => { const completion = $block("void, BOOL, id", (granted, error) => { if (error) { console.error("EventKit permission error:", error); resolve(false); } else { resolve(!!granted); } }); store.$requestAccessToEntityType_completion_(type, completion); }); } return false; } module.exports = { handleError, requestEventKitAccess }; ``` 5) scripts/reminder_model.js ```javascript // scripts/reminder_model.js const util = require('./util'); const DATA_FILE = "reminders.json"; let remindersInMemory = []; async function loadReminders() { if (!$file.exists(DATA_FILE)) { remindersInMemory = []; return []; } try { const data = $file.read(DATA_FILE); if (data && data.string) { remindersInMemory = JSON.parse(data.string); return remindersInMemory; } } catch (e) { console.error("Error parsing reminders.json:", e); util.handleError($l10n("REMINDER_FETCH_FAILED"), e); } remindersInMemory = []; return []; } async function saveReminders() { try { const dataString = JSON.stringify(remindersInMemory, null, 2); const success = $file.write({ data: $data({ string: dataString, encoding: 4 }), path: DATA_FILE }); return success; } catch (e) { console.error("Error saving reminders.json:", e); util.handleError("保存提醒失败!", e); return false; } } async function addOrUpdateReminder(reminder, isEditing, index) { if (isEditing) remindersInMemory[index] = reminder; else remindersInMemory.push(reminder); return await saveReminders(); } async function deleteReminder(index) { remindersInMemory.splice(index, 1); return await saveReminders(); } function getRemindersInMemory() { return remindersInMemory; } // 调度通知:无需显式请求通知权限,iOS 首次调度会弹出授权弹窗。 // 若用户拒绝,后续调度可能静默失败,应引导去系统设置开启。 function scheduleNotification(reminder) { const when = new Date(reminder.date); if (when > new Date()) { $push.schedule({ id: reminder.id, title: $l10n("APP_TITLE"), body: reminder.text, date: when, renew: true }); console.log("Notification scheduled:", reminder.text); } } function cancelNotification(reminderId) { $push.cancel({ id: reminderId }); console.log("Notification cancelled:", reminderId); } module.exports = { loadReminders, saveReminders, getRemindersInMemory, addOrUpdateReminder, deleteReminder, scheduleNotification, cancelNotification }; ``` 6) scripts/reminder_view.js ```javascript // scripts/reminder_view.js const reminderModel = require('./reminder_model'); const util = require('./util'); async function renderMainView() { $ui.render({ props: { title: $l10n("APP_TITLE"), bgcolor: $color("systemBackground"), theme: "auto", navButtons: [ { symbol: "plus.circle", tintColor: $color("tintColor"), handler: () => pushEditView() } ] }, views: [ { type: "list", props: { id: "reminderList", rowHeight: 70, template: { views: [ { type: "label", props: { id: "reminderText", font: $font(18), lines: 0, textColor: $color("label") }, layout: (make) => { make.left.right.inset(15); make.top.inset(10); make.height.lessThanOrEqualTo(40); } }, { type: "label", props: { id: "reminderDate", font: $font(13), textColor: $color("secondaryLabel") }, layout: (make) => { make.left.right.inset(15); make.top.equalTo($("reminderText").bottom).offset(5); make.height.equalTo(20); } }, { type: "switch", props: { id: "completedSwitch", onColor: $color("systemGreen") }, layout: (make) => { make.right.inset(15); make.centerY.equalTo(make.super); }, events: { changed: async (sender) => { $ui.loading(true); try { const index = sender.info.index; const reminders = reminderModel.getRemindersInMemory(); const reminder = reminders[index]; reminder.completed = sender.on; const ok = await reminderModel.addOrUpdateReminder(reminder, true, index); $ui.loading(false); if (ok) { $ui.toast(sender.on ? "已完成" : "未完成"); await loadRemindersToList(); } else { $ui.alert("更新提醒失败。"); sender.on = !sender.on; } } catch (e) { util.handleError("更新提醒状态失败", e); sender.on = !sender.on; } } } } ] }, actions: [ { title: $l10n("DELETE"), color: $color("red"), handler: (sender, indexPath) => confirmDeleteReminder(indexPath.row) } ] }, layout: $layout.fill, events: { didSelect: (sender, indexPath) => pushEditView(indexPath.row), pulled: async (sender) => { await loadRemindersToList(); sender.endRefreshing(); } } } ] }); await loadRemindersToList(); // 可选:在进入后台/退出时自动保存(需确认版本支持 $app.listen) // $app.listen({ pause: async () => await reminderModel.saveReminders(), exit: async () => await reminderModel.saveReminders() }); } async function loadRemindersToList() { $ui.loading(true); await reminderModel.loadReminders(); const reminders = reminderModel.getRemindersInMemory(); $ui.loading(false); if (reminders.length === 0) { $("reminderList").data = [{ rows: [{ reminderText: { text: $l10n("NO_REMINDERS") }, completedSwitch: { hidden: true }, reminderDate: { hidden: true } }] }]; $("reminderList").rowHeight = 100; $("reminderList").selectable = false; } else { const rows = reminders.map((r, i) => ({ reminderText: { text: r.text, textColor: r.completed ? $color("secondaryLabel") : $color("label") }, reminderDate: { text: new Date(r.date).toLocaleString(), textColor: r.completed ? $color("secondaryLabel") : $color("secondaryLabel") }, completedSwitch: { on: r.completed, info: { index: i } }, info: { id: r.id, index: i } })); $("reminderList").data = [{ rows }]; $("reminderList").rowHeight = 70; $("reminderList").selectable = true; } } async function pushEditView(index) { let currentReminder, isEditing = false; const reminders = reminderModel.getRemindersInMemory(); if (index !== undefined) { isEditing = true; currentReminder = { ...reminders[index] }; } else { currentReminder = { id: Date.now().toString(), text: "", date: new Date().toISOString(), completed: false }; } $ui.push({ props: { title: isEditing ? $l10n("EDIT_REMINDER") : $l10n("NEW_REMINDER"), bgcolor: $color("systemBackground"), theme: "auto" }, views: [ { type: "input", props: { id: "reminderTextInput", placeholder: $l10n("REMINDER_TEXT_PLACEHOLDER"), text: currentReminder.text, font: $font(17), textColor: $color("label"), cornerRadius: 5, bgcolor: $color("secondarySystemBackground") }, layout: (make) => { make.left.right.inset(15); make.top.inset(20); make.height.equalTo(40); } }, { type: "label", props: { id: "reminderDate", text: $l10n("REMINDER_DATE"), textColor: $color("label"), font: $font(15) }, layout: (make) => { make.left.inset(15); make.top.equalTo($("reminderTextInput").bottom).offset(20); } }, { type: "date-picker", props: { id: "reminderDatePicker", mode: 0, date: new Date(currentReminder.date) }, layout: (make) => { make.left.right.inset(15); make.top.equalTo($("reminderDate").bottom).offset(5); make.height.equalTo(180); } }, { type: "button", props: { title: $l10n("SAVE"), bgcolor: $color("systemBlue"), titleColor: $color("white"), cornerRadius: 8 }, layout: (make) => { make.left.right.inset(15); make.top.equalTo($("reminderDatePicker").bottom).offset(30); make.height.equalTo(45); }, events: { tapped: () => saveReminder(currentReminder, isEditing, index) } }, ...(isEditing ? [{ type: "button", props: { title: $l10n("DELETE"), bgcolor: $color("systemRed"), titleColor: $color("white"), cornerRadius: 8 }, layout: (make) => { make.left.right.inset(15); make.top.equalTo($("button").bottom).offset(15); make.height.equalTo(45); }, events: { tapped: () => confirmDeleteReminder(index, true) } }] : []) ] }); } async function saveReminder(reminder, isEditing, index) { const textInput = $("reminderTextInput").text; const datePicker = $("reminderDatePicker").date; if (!textInput.trim()) return $ui.alert($l10n("REMINDER_TEXT_PLACEHOLDER")); if (Number.isNaN(datePicker.getTime())) return $ui.alert("无效日期"); reminder.text = textInput.trim(); reminder.date = datePicker.toISOString(); $ui.loading(true); try { const ok = await reminderModel.addOrUpdateReminder(reminder, isEditing, index); if (ok) { $ui.toast($l10n("REMINDER_SAVED_SUCCESS")); // 可选:调度本地通知 // reminderModel.scheduleNotification(reminder); $ui.pop(); await loadRemindersToList(); } else { util.handleError("保存提醒失败!"); } } catch (e) { util.handleError("保存提醒失败!", e); } finally { $ui.loading(false); } } async function confirmDeleteReminder(index, fromEditPage = false) { const confirm = await $ui.alert({ title: $l10n("CONFIRM_DELETE_TITLE"), message: $l10n("CONFIRM_DELETE_MESSAGE"), actions: [{ title: $l10n("DELETE"), style: $alertActionType.destructive }, { title: $l10n("CANCEL") }] }); if (confirm.index !== 0) return; $ui.loading(true); try { const ok = await reminderModel.deleteReminder(index); if (ok) { $ui.toast($l10n("REMINDER_DELETED_SUCCESS")); if (fromEditPage) $ui.pop(); await loadRemindersToList(); } else { util.handleError("删除提醒失败!"); } } catch (e) { util.handleError("删除提醒失败!", e); } finally { $ui.loading(false); } } module.exports = { renderMainView }; ``` 7) main.js ```javascript // main.js const reminderView = require('./scripts/reminder_view'); reminderView.renderMainView(); ``` ### 综合实践 - 打包并安装到 JSBox:将 MyReminderApp 压缩为 zip,分享到 iOS 用 JSBox 打开安装。 - 运行后测试:添加/编辑/删除/完成状态切换/下拉刷新/本地化/暗黑模式适配。 --- ## 学习总结与展望 - 你已掌握:JSBox 核心 API、原生 UI 构建、模块化组织、异步编程、Runtime 交互。 - 后续优化建议: - 权限管理:日历/提醒/照片/通知等,统一处理拒绝与异常。 - 错误处理:统一 try-catch 与提示策略。 - 状态管理:内存态 + 关键路径落盘,必要时生命周期补充。 - 体验优化:loading 仅用于阻塞式等待,toast 用于结果提示。 - 进阶功能:搜索过滤、重复提醒、本地通知、数据导入导出、快捷指令与 URL Scheme 集成。 - 调试:充分利用控制台与 Safari 调试工具。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章