minororange

minororange 查看完整档案

深圳编辑湖北大学  |  电子信息工程 编辑  |  填写所在公司/组织 learnku.com/blog/minororange 编辑
编辑

Q是盖E是轮,一个R教做人儿。

个人动态

minororange 回答了问题 · 2020-12-21

解决nginx 反向代理中路径含有http://,转发后获取路径为http:/ 缺少一个斜杠

把 URL encode 一下,然后在接收的地方 decode吧,应该是 nginx 转义了双斜杠

关注 2 回答 1

minororange 赞了文章 · 2020-12-07

将 Vue 渲染到嵌入式液晶屏

image

前言

之前看了雪碧大佬的将 React 渲染到嵌入式液晶屏觉得很有意思,React能被渲染到嵌入式液晶屏,那Vue是不是也可以呢?所以本文我们要做的就是:

如标题所示,就是将Vue渲染到嵌入式液晶屏。这里使用的液晶屏是0.96 寸大128x64分辨率的SSD1306。要将Vue渲染到液晶屏,我们还需要一个桥梁,它必须具备控制液晶屏及运行代码的能力。而树莓派的硬件对接能力和可编程性天然就具备这个条件。最后一个问题来了,我们用什么技术来实现呢?

vue-ssd1306

这里我选择了 Node.js。原因:

  • Atwood 定律:“任何可以使用 JavaScript 来编写的应用,最终会由 JavaScript 编写。” 🐶
  • 驱动硬件我大 Node.js 一行npm install走天下。 🐶

vue-ssd1306

这个有趣的实践可拆分为这几个步骤:

  • 在 Node.js 运行 Vue
  • 树莓派连接屏幕芯片
  • Node.js 驱动硬件

Talk is cheap,Let's Go!!!

跨端渲染

无论是 基于 React 的 React Native 宣称的「Learn Once, Write Anywhere」,还是基于 Vue 的 Weex 宣称的「Write Once, Run Everywhere」口号,本质上强调的都是它们跨端渲染的能力。那什么是跨端渲染呢?

React: ReactNative Taro ...

Vue: Weex UniApp ...

各种五花八门的前端框架纷纷袭来,前端工程师们纷纷抱怨学不动了~

老板们看到纷纷笑嘻嘻, App 单,前端分,小程序单,前端吞,PC/H5,前端昏。skr~

这些跨平台框架原理其实都大同小异,选定 Vue/React 作为 DSL,以这个 DSL 框架为标准在各端分别编译,在运行时,各端使用各自的渲染引擎(Render Engines)进行渲染,底层渲染引擎中不必关心上层 DSL 的语法和更新策略,只需要处理 JS Framework 中统一定义的节点结构和渲染指令。也正是因为这一渲染层的抽象,使得跨平台/框架成为了可能。

vue-ssd1306

Vue 和 React 现在都实现了自定义渲染器,下面我们简单介绍一下:

React Reconciler

React16 采用新的 Reconciler,内部采用了 Fiber 的架构。react-reconciler模块正是基于 v16 的新 Reconciler 实现,它提供了创建 React 自定义渲染器的能力.

const Reconciler = require("react-reconciler");

const HostConfig = {
  // You'll need to implement some methods here.
  // See below for more information and examples.
};

const MyRenderer = Reconciler(HostConfig);

const RendererPublicAPI = {
  render(element, container, callback) {
    // Call MyRenderer.updateContainer() to schedule changes on the roots.
    // See ReactDOM, React Native, or React ART for practical examples.
  },
};

module.exports = RendererPublicAPI;

Vue createRenderer

vue3 提供了createRender API,让我们创建自定义渲染器。

createRenderer 函数接受两个泛型参数: HostNode 和 HostElement,对应于宿主环境中的 节点 和 元素 类型。
自定义渲染器可以传入特定于平台的类型,如下所示:

import { createRenderer } from 'vue'
const { render, createApp } = createRenderer<Node, Element>({
  patchProp,
  ...nodeOps
})

vue-ssd1306

在 Node.js 上运行 Vue

SFC To JS

<template>
  <text x="0" y="0">Hello Vue</text>
  <text x="0" y="20">{{ time }}</text>
  <text x="0" y="40">Hi SSD3306</text>
</template>
<script>
import { defineComponent, ref, toRefs, onMounted } from "vue";
import dayjs from "dayjs";
export default defineComponent({
  setup() {
    const time = ref(dayjs().format("hh:mm:ss"));
    onMounted(() => {
      setInterval(() => {
        time.value = dayjs().format("hh:mm:ss");
      }, 800);
    });
    return {
      ...toRefs({
        time,
      }),
    };
  },
});
</script>

要将 Vue 渲染到液晶屏,我们首先需要让 Vue 能运行在 Node.js 上,但是上面这个 SFC 是没办法被 Node.js 识别的,它只是 vue 的编程规范,是一种方言。所以我们需要做的是先将 SFC 转为 js。这里我使用 Rollup 打包将 SFC 转为 JS(相关配置这里就不啰嗦了,贴个传送门)。到了这一步,Node.js 就能成功运行打包后的 js 代码了,这还不够,这时候 Vue 组件的状态更新是没办法同步到 Node.js 的。

Create Custom Renderer

组件状态更新我们需要通知 Node.js 更新并渲染液晶屏内容,我们需要创建自定义的"更新策略"。这里就需要用到了我们前面提到的自定义渲染器:createRenderer API。下面我们简单介绍下我们相关使用:

// index.js
// 自定义渲染器
import { createApp } from "./renderer.js";
// 组件
import App from "./App.vue";
// 容器
function getContainer() {
  // ...
}
// 创建渲染器,将组件挂载到容器上
createApp(App).mount(getContainer());
// renderer.js

import { createRenderer } from "vue";
// 定义渲染器,传入自定义nodeOps
const render = createRenderer({
  // 创建元素
  createElement(type) {},
  // 插入元素
  insert(el, parent) {},
  // props更新
  patchProp(el, key, preValue, nextValue) {},
  // 设置元素文本
  setElementText(node, text) {},
  // 以下忽略,有兴趣的童鞋可自行了解
  remove(el) {},
  createText(type) {},
  parentNode(node) {},
  nextSibling(nide) {},
});

export function createApp(root) {
  return render.createApp(root);
}

vue 渲染器默认实现了 Web 平台 DOM 编程接口,将 Virtual DOM 渲染为真实 DOM。但是这个渲染器只能运行在浏览器中,不具备跨平台能力。所以我们必须重写 nodeOps 相关钩子函数,实现对应宿主环境元素的增删改查操作。接下来我们定义一个适配器,来实现相关逻辑。

Adapter

在实现前,我们先来理一下我们要实现的逻辑:

  • 创建元素实例 (create)
  • 将元素实例插入容器,由容器进行管理 (insert)
  • 状态改变时,通知容器进行更新 (update)
// adapter.js

// 文本元素
export class Text {
  constructor(parent) {
    // 提供一个父节点用于寻址调用更新 (前面提到状态更新由容器进行)
    this.parent = parent;
  }
  // 元素绘制,这里需要实现文本元素渲染逻辑
  draw(text) {
    console.log(text);
  }
}
// 适配器
export class Adapter {
  constructor() {
    // 装载容器
    this.children = [];
  }
  // 装载子元素
  append(child) {
    this.children.push(child);
  }
  // 元素状态更新
  update(node, text) {
    // 找到目标渲染进行绘制
    const target = this.children.find((child) => child === node);
    target.draw(text);
  }
  clear() {}
}
// 容器 === 适配器实例
export function getContainer() {
  return new Adapter();
}

好了,基本的适配器已经完成了,接下来我们来实现渲染器。

Renderer Abstract

import { createRenderer } from "vue";

import { Text } from "./adapter";
let uninitialized = [];
const render = createRenderer({
  // 创建元素,实例化Text
  createElement(type) {
    switch (type) {
      case "text":
        return new Text();
    }
  },
  // 插入元素,调用适配器方法进行装载统一管理
  insert(el, parent) {
    if (el instanceof Text) {
      el.parent = parent;
      parent.append(el);
      uninitialized.map(({ node, text }) => el.parent.update(node, text));
    }
    return el;
  },
  // props更新
  patchProp(el, key, preValue, nextValue) {
    el[key] = nextValue;
  },
  // 文本更新,重新绘制
  setElementText(node, text) {
    if (node.parent) {
      console.log(text);
      node.parent.clear(node);
      node.parent.update(node, text);
    } else {
      uninitialized.push({ node, text });
    }
  },
  remove(el) {},
  createText(type) {},
  parentNode(node) {},
  nextSibling(nide) {},
});

export function createApp(root) {
  return render.createApp(root);
}

树莓派连接屏幕芯片

SSD1306 OLED

OLED,即有机发光二极管( Organic Light Emitting Diode)。是一种液晶显示屏。而 SSD1306 就是一种 OLED 驱动芯片。ssd1306 本身支持多种总线驱动方式:6800/8080 并口、SPI 及 IIC 接口方式。这里我们选择 IIC 接口方式进行通信,理由很简单: 1. 接线简单方便(两根线就可以驱动 OLED) 2.轮子好找...缺点就是 IIC 传输数据效率太慢了,刷新率只有 10FPS 不到。而 SPI 刷新率最大能达到 2200FPS。

硬件接线

IIC 仅需要 4 根线就可以,其中 2 根是电源,另外 2 根是 SDA 和 SCL。我们使用 IIC-1 接口。下面是树莓派的 GPIO 引脚图。

vue-ssd1306

注意:请一定以屏幕的实际引脚编号为准。

  • 屏幕 VCC 接树莓派 1 号引脚。- 3.3v 电源
  • 屏幕 GND 接树莓派 9 号引脚。- 地线
  • 屏幕 SDA 接树莓派 3 号引脚。- IIC 通信中为数据管脚
  • 屏幕 SCL 接树莓派 5 号引脚。- IIC 通信中为时钟管脚

树莓派启用 I2C

1.安装工具包

sudo apt-get install -y i2c-tools

2.启用 I2C

  • sudo raspi-config
  • 选择 Interfacing Options
  • Enable I2C

3.检查设备挂载状态

i2c-tools 提供的 i2cdetect 命令可以查看挂载设备

sudo i2cdetect -y 1

Node.js 驱动硬件

Node.js Lib

我们先来看几个 Node.js 库,看完你会不得不感叹~任何可以使用 JavaScript 来编写的应用,最....

johnny-five

Johnnt-Five 是一个支持 JavaScript 语言编程的机器人和 IOT 开发平台,基于 Firmata 协议。Firmata 是计算机软件和微控制器之间的一种通信协议。使用它,我们可以很简单的架起树莓派和屏幕芯片之间的桥梁。

vue-ssd1306

raspi-io

Raspi IO 是一个为 Johnny-Five Node.js 机器人平台提供的 I/O 插件,该插件使 Johnny-Five 能够控制一个 Raspberry Pi 上的硬件。

oled-font-5x7

5x7 oled 字体库,将字符转为 16 进制编码,让 oled 程序能够识别。用于绘制文字。

oled-js

📺 兼容 johnny-five 的 oled 支持库 (johnny-five 本身并不支持 oled),提供了操作 oled 的 API。

驱动程序实现

// oled.js
const five = require("johnny-five");
const Raspi = require("raspi-io").RaspiIO;
const font = require("oled-font-5x7");
const Oled = require("oled-js");
const OPTS = {
  width: 128, // 分辨率  0.96寸 ssd1306 128*64
  height: 64, // 分辨率
  address: 0x3c, // 控制输入地址,ssd1306 默认为0x3c
};
class OledService {
  constructor() {
    this.oled = null;
  }
  /**
   * 初始化: 创建一个Oled实例
   * 创建后,我们就可以通过操作Oled实例来控制屏幕了
   */
  init() {
    const board = new five.Board({
      io: new Raspi(),
    });
    // 监听程序退出,关闭屏幕
    board.on("exit", () => {
      this.oled && this.remove();
    });
    return new Promise((resolve, reject) => {
      board.on("ready", () => {
        // Raspberry Pi connect SSD 1306
        this.oled = new Oled(board, five, OPTS);
        // 打开屏幕显示
        this.oled.turnOnDisplay();
        resolve();
      });
    });
  }
  // 绘制文字
  drawText({ text, x, y }) {
    // 重置光标位置
    this.oled.setCursor(+x, +y);
    // 绘制文字
    this.oled.writeString(font, 2, text, 1, true, 2);
  }
  clear({ x, y }) {
    this.oled.setCursor(+x, +y);
  }
  // 刷新屏幕
  update() {
    this.oled.update();
  }
  remove() {
    // 关闭显示
    this.oled.turnOffDisplay();
    this.oled = null;
  }
}
export function oledService() {
  return new OledService();
}

接下来,我们就可以在适配器中调用 oled 程序渲染屏幕了~

// index.js
import { createApp } from "./renderer.js";
import { getContainer } from "./adapter";
import { oledService } from "./oled";
import App from "./App.vue";
const oledIns = oledService();
oledIns.init().then(() => {
  createApp(App).mount(getContainer(oledIns));
});

// adapter.js
export class Text {
  constructor(parent) {
    this.parent = parent;
  }
  draw(ints, opts) {
    ints.drawText(opts);
    ints.update();
  }
}

export class Adapter {
  constructor(oledIns) {
    this.children = [];
    this.oled = oledIns;
  }
  append(child) {
    this.children.push(child);
  }
  update(node, text) {
    const target = this.children.find((child) => child === node);
    target.draw(this.oled, {
      text,
      x: node.x,
      y: node.y,
    });
  }
  clear(opts) {
    this.oled.clear(opts);
  }
}
export function getContainer(oledIns) {
  return new Adapter(oledIns);
}

到这一步,就可以成功点亮屏幕啦,来看看效果~

效果展示

vue-ssd1306

参考

将 React 渲染到嵌入式液晶屏

在树莓派上使用 SSD1306 OLED 屏幕

结语

完整代码已上传到 Github,如果你觉得这个实践对你有启发/帮助,点个 star 吧~

Vue 已经成功渲染到嵌入式液晶屏了,那下一步是不是可以考虑接个摇杆写个贪吃蛇游戏了~哈哈哈,这很"Javascript"。

"阅读式"的学习使我犯困,所以我更倾向通过一些有趣的实践吸收知识。如果你和我一样爱折腾,欢迎关注

查看原文

赞 14 收藏 7 评论 3

minororange 赞了文章 · 2020-10-27

快使用Scriptable自己开发一个iPhone小组件吧

最近苹果的 iOS 系统升级到了 iOS 14,这次的更新我比较关注的就是升级的小组件功能,这次更新我们可以将小组件放置在主屏幕中的任何位置,可以让我们更加便捷的查看一些信息,从而省去了还需要打开APP去查看消息的步骤,感觉很方便。

看到这里一些同学可能会说,功能是挺不错的,如果我自己也能开发一个小组件展示自己想看的内容就好了。是呀,哪一个小男孩不想拥有一个专属于自己的 iOS 小组件

别慌,最近发现了一个APP可以让我们通过使用JavaScript来创建我们自己想要的小组件。这个APP的名字就是Scriptable,还是验证了Jeff Atwood那句话,任何可以使用JavaScript来编写的应用,最终会由JavaScript编写。作为一个前端有没有感觉很开心,又有一个地方可以让你来大展身手了,那就趁热赶紧来了解一下吧。

Scriptable的简单介绍

Scriptable

工欲善其事,必先利其器,我们先来了解一下Scriptable有哪些作用吧,从上面官网上的介绍我们可以知道,这个APP可以让我们使用JavaScript来自动化iOS。这句话是什么意思呢?就是我们可以提前编写好一些代码去执行一些特定的任务,比如:获取GitHub上面的Trending项目的名字和介绍、了解Hacker News的最新资讯、获取自己的最近日程、以及自己的TODO列表等等。当然这都只是一些最基本的使用场景,你肯定有自己的想法,看完这篇文章之后就去自己去实现一个独一无二的小应用吧。

Scriptable

上面列举的是一些Scriptable的特性,这些特性包括:

  • 支持ES6语法
  • 可以使用JavaScript调用一些原生的API
  • Siri 快捷方式
  • 完善的文档支持
  • 共享表格扩展
  • 文件系统继承
  • 编辑器的自定义
  • 代码样例
  • 以及通过x-callback-url和其它APP交互

是不是感觉还是挺不错的,这些特性已经可以让我们去做很多可以自动化的事情了。

开始前的准备工作

  • 一台升级到 iOS 14iPhone 手机
  • 安装 Scriptable 应用程序

下载完成之后打开应用,我们可以看到一些已经写好的例子:

image

点击小卡片会直接运行相应的程序,点击小卡片右上角的更多按钮进入相应程序的代码编辑模式。

image

底部有一个悬浮的操作栏,左边第一个按钮是一个设置按钮,你可以为当前的小程序设置图标,颜色,等等:

image

左边第二个按钮是一个文档提示按钮,点击可以搜索想要使用的相关的API:

image

最右边是一个运行按钮,点击会直接在手机上运行你编写的应用程序。这个大家应该一看就知道了:

image

我以为通过在手机上的编辑器进行代码的编写会比较费劲和吃力,但是试了一下发现还好。因为手机上的编辑器也有比较完善的语法提示功能,编写代码的体验虽然不如在电脑上那般舒服,但也是在可以接受的范围之内。

image

上面是一些关于 Scriptable APP的简单的介绍,你可以自己在手机上好好体验一下。我觉得整个APP很简约,但是功能还是很强大的。

第一个 Hello World 小组件

我们学习编程语言的第一步就是输出Hello World,所以我们使用 Scriptable 的第一个小应用就是在主屏幕上展示Hello Wolrd

我个人觉得在你开始真正的开发自己想要的小组件之前,开发一个Hello World的小组件还是很有必要的,因为这个过程相对容易一点,可以增加我们的自信心。我们可以通过这个小程序来建立起我们开发小组件的手感,并且我们是可以直接在手机的屏幕上看到这个结果的,是不是还蛮有成就感的。

image

在编码的过程中有几个选择,你可以选择直接在手机的编辑器上进行编码,也可以通过 Mac 的 iCloud 云盘 的同步功能,在 Mac 电脑上用自己熟悉的编辑器开发。如果你有蓝牙的键盘,可以直接使用蓝牙键盘连接到手机上使用自己的键盘进行编码。根据自己的条件选择一个自己舒服的方式进行编码。

接下来就是Hello World小组件的代码了:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: cyan; icon-glyph: greater-than-equal;

// 以下代码仅供学习交流使用

// 判断是否是运行在桌面的组件中
if (config.runsInWidget) {
  // 创建小部件
  const widget = new ListWidget();
  // 添加文本
  const text = widget.addText("Hello, World!");
  text.textColor = new Color("#000000");
  text.font = Font.boldSystemFont(36);
  text.centerAlignText();
  // 添加渐变色背景
  const gradient = new LinearGradient();
  gradient.locations = [0, 1];
  gradient.colors = [new Color("#F5DB1A"), new Color("#F3B626")];
  widget.backgroundGradient = gradient;
  // 设置部件
  Script.setWidget(widget);
}

上面的代码还是比较简单的,相信大家看一下就明白了。我再简单介绍一下,最开头的注释是 Scriptable 自己生成的,用来设置小卡片的图标和图标颜色;接下来的一个if判断表明我们希望接下来的代码是在小组件的条件下运行的,用来生成我们的小组件。

然后接下来的代码就是创建小组件,添加文本,设置本文的颜色、字体以及对齐方式。然后添加了一个渐变的背景,最后把上面生成的小组件通过Script.setWidget()进行设置。这样我们的Hello World小组件就算完成啦。

“今天吃点啥”小组件

也许5分钟过后你就开始不满足一个简单的 Hello World 小组件了,你知道你的征途是星辰大海,所以你要做出一些有实际应用价值的小组件。但此时的你已经工作到晚上十点多了,十分想给自己点一个夜宵来犒劳一下自己。但是你特别纠结吃啥?

看了看楼下的炒粉干和山东杂粮煎饼以及烤冷面,你十分纠结要吃啥。所以为了节省时间我决定开发一个帮你选择吃什么的小组件。就叫它:“今天吃点啥”吧。

image

看看这个组件的图标是不是就很有食欲?当你不确定要吃啥的时候就点击这个小组件,然后我们编写的程序就会运行,它会在面板上列出你这次要吃的选项,你点击选择,马上就知道自己要吃啥了,是不是解决了你迟迟下不定决心要吃啥的纠结状态。

image

image

为了明确告诉你这次的选择是什么,我特意在选择之后给你发送一个选择结果的通知,是不是很人性化😂,你肯定还发现了为什么食物的名字与图片不符合。是的,我是故意这样做的,为了营造一种你即将吃大餐的假象😁。

下面是一张动图,可以提前感受一下这个过程:

image

因为我之前有帮助过同事使用Swift开发原生 iOS 的一些经验,所以这里面跟原生相关的一些API我看着还算熟悉的,也好上手。就算没有相关的开发经验关系也不大,毕竟文档对于相关API的解释都还算清楚的,相信你看一看就可以很快上手的。

因为这个小组件的代码量稍微多一点,就不在这里展示了;大家如果有兴趣的话可以在scriptable-scripts看到这个小组件的源码部分,写的时候比较仓促,还有很多可以完善的地方。如果你有什么好的意见也可以提出来,我们一起学习进步。

对小组件的一些思考

更新了 iOS 14 之后,发现手机上的很多APP都新增了相关的小组件,这让用户可以快速方便的浏览一些关键的信息,也可以快速直达具体的服务。对用户来说还是很有帮助的。

对于开发者来说,这里面也存在一些新的机遇。就算不会原生的 iOS 开发,我们也可以借助像Scriptable这样的小组件平台,来创造出一些有趣,有价值,有意义的小组件。

有没有发现小组件是不是跟小程序在某些方面很相似?感觉以后应该会出现系统级别的“小程序”平台,如果AndroidiOS再搞一个统一的开发平台,前端开发者又可以扬帆远航了,想想是不是有点小激动呢。。。

学习与交流

如果你对使用 Scriptable 开发小组件很有兴趣,也欢迎大家进Scriptable小组件交流群进行交流讨论。

文章到这里就结束了,如果你有什么意见和建议欢迎给我留言。你还可以关注我的公众号关山不难越,可以及时获取最新好玩有趣文章的更新。

注:“今天吃点啥”小组件的图标使用的是https://undraw.co/网站上的,相关食物的图片来自https://unsplash.com/,每张图片来自哪个创作者在代码的注释中有说明。

查看原文

赞 10 收藏 5 评论 1

minororange 回答了问题 · 2020-10-27

ubuntu开发环境的可行性

win10 自带 ubuntu 啊,跟主机共享文件夹,共享端口,推荐试试:https://learnku.com/articles/...

关注 4 回答 5

minororange 赞了回答 · 2020-08-07

如何在微博中书写井号“#”而不被解析为话题?

可以用全角井号 #

关注 1 回答 1

minororange 赞了回答 · 2020-07-30

解决sql怎么按日期统计客户信息性别年龄段

可以实现,但是,这种查询几乎无法优化啊.

SELECT
    DATE_FORMAT( create_time, "%Y-%m-%d" ) AS d,
    sum(
    IF
    ( sex = 1, 1, 0 )) AS male,
    sum(
    IF
    ( sex = 2, 1, 0 )) AS female,
    sum( is_signed ) as signed,
    sum(
    IF
    ( DATEDIFF( NOW(), birth_day )/ 365 < 20, 1, 0 )) AS under20,
    sum(
    IF
    ( DATEDIFF( NOW(), birth_day )/ 365 >= 20 AND DATEDIFF( NOW(), birth_day )/ 365 < 31, 1, 0 )) AS 20to30,
    sum(
    IF
    ( DATEDIFF( NOW(), birth_day )/ 365 >= 31, 1, 0 )) AS 30more 
FROM
    `user` 
GROUP BY
    d;

子查询版

SELECT
    u.d,
    sum(
    IF
    ( u.sex = 1, 1, 0 )) AS male,
    sum(
    IF
    ( u.sex = 2, 1, 0 )) AS female,
    sum( u.is_signed ) AS signed,
    sum(
    IF
    ( u.age < 20, 1, 0 )) AS under20,
    sum(
    IF
    ( u.age >= 20 AND u.age < 31, 1, 0 )) AS 20to30,
    sum(
    IF
    ( u.age >= 31, 1, 0 )) AS 30more 
FROM
    ( SELECT DATE_FORMAT( create_time, "%Y-%m-%d" ) AS d, sex, is_signed, DATEDIFF( NOW(), birth_day )/ 365 AS age FROM USER ) AS u 
GROUP BY
    u.d

关注 3 回答 2

minororange 回答了问题 · 2020-07-10

解决mac上如何安装PHPRedis 扩展?

pecl install redis

关注 2 回答 2

minororange 回答了问题 · 2020-07-10

Nginx server_name 未命中依旧能够访问进来的原因

关注 2 回答 1

minororange 回答了问题 · 2019-11-21

解决如何检查网站是否引用了某个字体?

clipboard.png

关注 2 回答 1

minororange 回答了问题 · 2019-11-16

vue 中el-input 为number 如何控制不允许输入负号

<el-input-number :min="0"></el-input-number>

关注 2 回答 2

认证与成就

  • 获得 102 次点赞
  • 获得 22 枚徽章 获得 1 枚金徽章, 获得 6 枚银徽章, 获得 15 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-10
个人主页被 1.9k 人浏览