跳转到内容
返回

记一次跨平台开发

封面图作者:镜子222333

很早就听过electronreactnaive,可以让前端的同学来使用他们熟悉的web前端技术栈来分别开发pc客户端和移动客户端;再后来小程序火了起来后,也有不少团队开始做起了多端共用一套代码这样的理想化框架,比如滴滴的chameleon和京东的Taro,但是对于这些新’玩具’一直是停留在知道层面,并没有接触,趁着大四最后的一个假期, 想着接触一下;然后发现了一款叫scriptable的ios/macos上的的app;可以用js来实现对该应用在ios桌面组件的自定义;有点类似小程序那样,微信封装一些底层设备的操作暴露给上层,然后由我们来利用这些api来做二次开发,所以最近以这个为头,尝试了第一次的’跨端’开发,并记录一下第一次尝试。

写在前面

关于scriptable

软件说明

  • 这个软件做的事就是封装了ios的底层一些api
  • 然后我们用软件提供的api来定制该软件创建的组件所显示的内容
  • 需要注意的是使用的是apple自己的js引擎,但支持ES6
  • 其次因为只是内嵌了js引擎,所有没有浏览器的那些api

开发

  • 打开app,点击右上角加号创建一个新脚本
  • 在创建的脚本文件中,直接开始书写即可

查看效果

  • 在桌面添加该软件的小组件
  • 编辑该小组件,在script中选择我们的脚本
  • 回到桌面就可以查看效果了

代码演示

// 创建一个小组件
let w = new ListWidget();
// 设置组件背景颜色
w.backgroundColor = new Color("#fff");
// 添加组件内显示的文本
let textNode = w.addText("Hello World");
// 在组件内部居中显示文本
textNode.centerAlignText();
// 设置文本的颜色
textNode.textColor = new Color("#000");
// 渲染组件
Script.setWidget(w);
// 通知系统脚本执行完成
Script.complete();

关于pc开发小组件

食用指南

最后

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: comments;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//

// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能
if (typeof require === "undefined") require = importModule;

const { Base } = require("./「小件件」开发环境");
// const { DmYY: Base } = require("./DmYY.js")

// @组件代码开始
class Widget extends Base {
  /**
   * 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
   * @param {string} arg 自定义参数
   */
  constructor(arg) {
    super(arg);
    this.name = "示例小组件";
    this.desc = "「小件件」—— 原创精美实用小组件";
    // 注册操作菜单
    if (config.runsInApp) {
      this.registerAction("设置文本", this.actionSetText);
      this.registerAction("设置时间", this.actionSetDate);
    }
  }
  // 设置文本
  async actionSetText() {
    let getText = new Alert();
    getText.title = "设置组件显示文本";
    getText.message = "请输入组件要显示的文本内容";
    getText.addTextField("输入显示文本", this.settings["text"]);
    // 增加按钮
    getText.addAction("确定");
    getText.addCancelAction("取消");
    await getText.presentAlert();
    let inputText = await getText.textFieldValue(0);
    this.settings["text"] = inputText;
    // 保存设置
    this.saveSettings();
  }
  // 设置时间
  async actionSetDate() {
    console.log("设置时间");
    try {
      let dp = await new DatePicker();
      let selectDate = await dp.pickDate();
      // ios只能解析 xxxx/xx/xx格式的日期
      let day = `${selectDate.getFullYear()}/${selectDate.getMonth() + 1}/${selectDate.getDate()}`;
      this.settings["day"] = day;
      this.saveSettings();
    } catch (error) {
      console.log("请选择时间");
    }
  }
  /**
   * 渲染函数,函数名固定
   * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
   */
  async render() {
    // 请求接口
    const data = await this.getData();
    switch (this.widgetFamily) {
      case "large":
        return await this.renderLarge(data);
      case "medium":
        return await this.renderMedium(data);
      default:
        return await this.renderSmall(data);
    }
  }
  // 渲染背景颜色
  renderBackColor(w) {
    const gradient = new LinearGradient();
    gradient.locations = [0, 1];
    gradient.colors = [new Color("#eec3ee", 1), new Color("#b2c0ed", 1)];
    w.backgroundGradient = gradient;
  }
  // 渲染字体
  renderFontStyle(t, fontSize, fontColor, position) {
    switch (position) {
      case "center":
        t.centerAlignText();
        break;
      case "right":
        t.rightAlignText();
        break;
      case "left":
        t.leftAlignText();
        break;
      default:
        break;
    }
    t.font = Font.lightSystemFont(fontSize);
    t.textColor = new Color(fontColor, 1);
  }
  genTime() {
    var date = new Date(); //1. js获取当前时间
    var min = date.getMinutes(); //2. 获取当前分钟
    date.setMinutes(min + 1); //3. 设置当前时间+10分钟:把当前分钟数+10后的值重新设置为date对象的分钟数
    var y = date.getFullYear();
    var m =
      date.getMonth() + 1 < 10
        ? "0" + (date.getMonth() + 1)
        : date.getMonth() + 1;
    var d = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
    var h = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
    var f =
      date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
    var s =
      date.getSeconds() < 10 ? "0" + date.getseconds() : date.getSeconds();
    var formatdate = y + "/" + m + "/" + d + " " + h + ":" + f + ":" + s;
    console.log(formatdate); // 获取10分钟后的时间,格式为yyyy-mm-dd h:f:s
    console.log(new Date(formatdate));
    return formatdate;
  }
  /**
   * 渲染小尺寸组件
   */
  async renderSmall(data) {
    console.log("刷新");
    let w = new ListWidget();
    w.refreshAfterDate = new Date(this.genTime());
    let headerT = w.addText(
      this.settings["text"] ? this.settings["text"] : "默认文本"
    );
    this.renderFontStyle(headerT, 15, "#fff", "center");
    let start = await this.settings["day"];
    let day = start
      ? Math.ceil((new Date() - new Date(start)) / (1000 * 60 * 60 * 24))
      : 1;
    const t = w.addText(day.toString());
    t.centerAlignText();
    this.renderFontStyle(t, 60, "#fff", "center");
    let today = w.addDate(new Date());
    this.renderFontStyle(today, 15, "#fff", "center");
    this.renderBackColor(w);
    return w;
  }
  /**
   * 渲染中尺寸组件
   */
  async renderMedium(data, num = 3) {
    let w = new ListWidget();
    let text = w.addText(
      this.settings["text"] ? this.settings["text"] : "默认文本"
    );
    this.renderFontStyle(text, 36, "#fff", "center");
    let day = Math.ceil(
      parseInt(new Date() - new Date(this.settings["day"])) /
        (1000 * 60 * 60 * 24)
    );
    // 创建中部布局
    let footerT = w.addText(
      `${String(day) === "NaN" || day <= 0 ? "请设置今天之前的时间" : day}`
    );
    footerT.centerAlignText();
    if (String(day) === "NaN" || day <= 0) {
      this.renderFontStyle(footerT, 27, "#fff", "center");
    } else {
      this.renderFontStyle(footerT, 40, "#fff", "center");
    }
    let today = w.addDate(new Date());
    this.renderFontStyle(today, 15, "#fff", "center");
    this.renderBackColor(w);
    // w.addSpacer(2)
    return w;
  }
  /**
   * 渲染大尺寸组件
   */
  async renderLarge(data) {
    return await this.renderMedium(data, 10);
  }

  /**
   * 获取数据函数,函数名可不固定
   */
  async getData() {
    const api = "https://x.im3x.cn/v1/test-api.json";
    return await this.httpGet(api, true, false);
  }

  /**
   * 自定义注册点击事件,用 actionUrl 生成一个触发链接,点击后会执行下方对应的 action
   * @param {string} url 打开的链接
   */
  async actionOpenUrl(url) {
    Safari.openInApp(url, false);
  }
}
// @组件代码结束

const { Testing } = require("./「小件件」开发环境");
await Testing(Widget);


上一篇
ios-date构造函数
下一篇
textarea中复制光标所在行文本