图片 19

的性能优化,快速入门

XCel 项目计算:Electron 与 Vue 的品质优化

2017/03/01 · 功底手艺 ·
Javascript,
算法

正文小编: 伯乐在线 –
刘健超-J.c
。未经笔者许可,防止转发!
应接到场伯乐在线 专栏编辑者。

XCEL 是由京东顾客体验设计部凹凸实验室推出的一个 Excel
数据洗涤工具,其通过可视化的法子让客商轻巧地对 Excel 数据开展挑选。

XCEL 基于 Electron 和 Vue 2.x,它不只跨平台(windows 7+、Mac 和
Linux),並且足够利用 Electron 多进程职分管理等效果,使其性子卓绝。

落地页: ✨✨✨
项目地址: ✨✨✨

急迅入门

Electron 能够令你接收纯 JavaScript 调用丰硕的原生 APIs
来创建桌面应用。你可以把它看成三个介意于桌面应用的 Node.js
的变体,并不是 Web 服务器。

这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反,Electron 使用
web 页面作为它的 GUI,所以您能把它看做成三个被 JavaScript
调整的,精练版的 Chromium 浏览器。

原文:https://github.com/electron/electron/blob/master/docs/glossary.md
译者:Lin

类型背景

客户研究的定量研商和轻量级数据管理中,均需对数码举行洗刷管理,以剔除相当数据,保险数据结果的信度和效度。前段时间因调研数据和轻量级数据的多变性,对轻量级数据洗濯往往选用人工洗濯,贫乏统风华正茂、典型的洗濯流程,但对于调查研讨和轻量级的多寡往往是索要保障数据稳固性的,由此,在对数码进行清洗时最佳有原则的保洁格局。

主进程

在 Electron 里,运行 package.jsonmain
脚本的历程被称作主进程。在主进度运营的脚本能够以创制 web
页面包车型地铁方式显示 GUI。

以此页面定义了一些在Electron中平日采纳的专有名词。

特点一览

  • 轶闻 Electron 研发并封装成为原生应用,客户体验优秀;
  • 可视化操作 Excel 数据,支持文件的导入导出;
  • 怀有单列运算逻辑、多列运算逻辑和双列范围逻辑二种筛选格局,而且可经过“且”、“或”和“编组”的措施自由组合。

渲染进度

由于 Electron 使用 Chromium 来展现页面,所以 Chromium
的多进度协会也被足够利用。每一种 Electron
的页面都在运行着和睦的历程,那样的长河我们誉为渲染进度

在相近浏览器中,网页经常会在沙盒景况下运营,并且不一样意访谈原生产资料源。可是,Electron
客商全数在网页中调用 Node.js 的 APIs
的技巧,能够与底层操作系统间接相互影响。

ASAR

ASAR是Atom Shell Archive
Format的简称。一个asar文档是三个把公文都放在四个独门的文本中的简单的tar-like类型文件。Electron能够从中读取全部的公文而不用解压整个文件。

创制ASA智跑类型主假设为了在Windows下抓实品质… TODO

思路与完毕

依赖用研组的需要,利用 Electron 和 Vue 的特征对该工具进行付出。

主进度与渲染进度的不一样

主进度使用 BrowserWindow 实例创制页面。每一个 BrowserWindow
实例都在自个儿的渲染进程里运维页面。当一个 BrowserWindow
实例被销毁后,相应的渲染进度也会被结束。

主进程管理全体页面和与之相应的渲染进度。各个渲染进度都以互相独立的,况且只关心他们和睦的页面。

是因为在页面里管理原生 GUI
能源是特别危殆何况轻易招致能源走漏,所以在页面调用 GUI 相关的 APIs
是不被允许的。假如你想在网页里使用 GUI
操作,其对应的渲染进度必需与主进度实行电视发表,央浼主进度打开连锁的 GUI
操作。

在 Electron,我们提供二种艺术用于主进度和渲染进度之间的电视发表。像
ipcRenderer
ipcMain
模块用于发送消息, remote
模块用于 RPC 方式通信。这一个剧情都足以在三个 FAQ 中查阅 how to share
data between web
pages。

Brightray

Brightray是八个使libchromiumcontent更便于采用应用的静态库。它是特意为了Electron而创设的,但是也得以允许尚未基于Electron的原生应用使用Chromium的渲染引擎。

Brightray是Electron的叁个平底的重视,大多数Electron的使用者并不用顾忌它。

技巧选型

  • Electron:桌面端跨平台框架,为 Web
    提供了原生接口的权力。打包后的次第宽容 Windows 7 及以上、Mac、Linux
    的 32 / 64 位系统。详情>>
  • Vue 全家桶:Vue
    拥有数量驱动视图的表征,相符重数量交互作用的选择。详情>>
  • js-xlsx:包容种种石英钟格格式的剖判器和生成器。纯 JavaScript
    完成,适用于 Node.js 和 Web
    前端。详情>>

创设你首先个 Electron 应用

粗粗上,三个 Electron 应用的目录结构如下:

your-app/
├── package.json
├── main.js
└── index.html

package.json 的格式和 Node 的完全生机勃勃致,并且特别被 main
字段注脚的剧本文件是您的应用的起步脚本,它运行在主进度上。你选拔里的
package.json 看起来应当像:

{
  "name"    : "your-app",
  "version" : "0.1.0",
  "main"    : "main.js"
}

注意:如果 main 字段未有在 package.json 表明,Electron会优先加载
index.js

main.js 应该用于创制窗口和拍卖系统事件,三个独立的事举个例子下:

const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')

// 保持一个对于 window 对象的全局引用,如果你不这样做,
// 当 JavaScript 对象被垃圾回收, window 会被自动地关闭
let win

function createWindow () {
  // 创建浏览器窗口。
  win = new BrowserWindow({width: 800, height: 600})

  // 加载应用的 index.html。
  win.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))

  // 打开开发者工具。
  win.webContents.openDevTools()

  // 当 window 被关闭,这个事件会被触发。
  win.on('closed', () => {
    // 取消引用 window 对象,如果你的应用支持多窗口的话,
    // 通常会把多个 window 对象存放在一个数组里面,
    // 与此同时,你应该删除相应的元素。
    win = null
  })
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在这文件,你可以续写应用剩下主进程代码。
  // 也可以拆分成几个文件,然后用 require 导入。
  if (win === null) {
    createWindow()
  }
})

// 在这文件,你可以续写应用剩下主进程代码。
// 也可以拆分成几个文件,然后用 require 导入。

最后,你想浮现的 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

DMG

Apple Disk
Image是叁个在MacOS上接纳的打包类型。DMG文件平日用来散发应用的“安装文件”。electron-builder支撑dmg作为一个装进目的。

落到实处思路

  1. 因此 js-xlsx 将 Excel 文件解析为 JSON 数据
  2. 依赖挑选标准对 JSON 数据进行筛选过滤
  3. 将过滤后的 JSON 数据转变来 js-xlsx 钦命的数据结构
  4. 采纳 js-xlsx 对转移后的数额生成 Excel 文件

轻描淡写,绝知这事要躬行

运营你的应用

就算您创立了最早的 main.jsindex.htmlpackage.json
这多少个文本,你或者会想尝尝在当地运营并测验,看看是或不是和希望的那样符合规律运维。

IPC

IPC是Inter-Process
Communication的简称。Electron使用IPC在主进程和渲染进度以内发送体系化的JSON消息。

连锁技术

假若对某项本事相比熟知,则可略读/跳过。

electron-prebuilt

electron
是一个 npm 模块,饱含所选拔的 Electron 预编译版本。
风度翩翩经您早已用 npm 全局安装了它,你只须求遵照如下方式平素运营你的选取:

electron .

设若你是风度翩翩对安装,那运转:

libchromiumcontent

叁个包罗了Chromium内容模块和富有重视(比方,Blink,V8等卡塔 尔(英语:State of Qatar)的大约的分享库。

Electron

macOS / Linux

$ ./node_modules/.bin/electron .

main process

主进程,常常是三个叫作main.js的公文,是指向每贰个Electron应用的输入。它调整着应用从张开到关门的生命周期。它也管理着原生控件,比如MenuMenu BarDockTray等。主进程在利用中担任着创制每一个新的渲染进程的职责。全体的Node接口都在它此中。

每三个用到的主线程文件是在package.json文件中的main品质中被钦赐的。那是electron .什么样掌握运营时要实施哪个文件的原故。

参见:process,renderer
process

Electron 是什么?

Electron 是三个足以用 JavaScript、HTML 和 CSS
创设桌面应用程序的。这个应用程序能打包到 Mac、Windows 和 Linux
系统上运转,也能上架到 Mac 和 Windows 的 App Store。

  • JavaScript、HTML 和 CSS 都以 Web
    语言,它们是整合网址的生龙活虎有些,浏览器(如
    Chrome卡塔 尔(英语:State of Qatar)掌握如何将那一个代码转为可视化图像。
  • Electron 是一个库:Electron
    对底层代码实行抽象和包裹,让开拓者能在这里之上营造项目。

Windows

$ .node_modules.binelectron .

MAS

Apple’s Mac App
Store的缩写。关于提交你的应用程序到MAS的详细新闻,请看Mac App Store
Submission
Guide。

干什么它如此重大?

万般来讲,每一种操作系统的桌面应用都由个别的原生语言进行编辑,那意味须求3 个协会分别为该利用编写相应版本。而 Electron 则允许你用 Web
语言编写三回就能够。

  • 原生(操作系统卡塔 尔(阿拉伯语:قطر‎语言:用于支付主流操作系统应用的原生语言的对应关系(大多数景观下卡塔 尔(英语:State of Qatar):Mac
    对应 Objective C、Linux 对应 C、Windows 对应 C++。

手工业下载 Electron 二进制文件

借使你手工业下载了 Electron
的二进制文件,你也得以一贯利用在这之中的二进制文件直接运营你的行使。

native modules

Native
modules(在Node.js中也叫插件卡塔尔是C或C++写的模块,使用require()函数能够被加载到Node.js或Electron中,然后就可以像叁个见惯不惊Node.js模块同样选拔了。它们首要用以提供多少个把js运营在Node.js和C/C++库上的接口。

Electron支持Native Node
modules,可是出于Electron非常有望使用安装在你Computer上的Node二进制文件中的差别版本的V8,你在编写翻译native
modules的时候供给手动钦定Electron的头顶地点。

参考Using Native Node
Modules。

它由什么组成?

Electron 结合了 ChromiumNode.js 和用于调用操作系统本地功用的
API(如展开文件窗口、公告、Logo等卡塔 尔(阿拉伯语:قطر‎。

  • Chromium:谷歌 创立的几个开源库,并用以 Google 的浏览器
    Chrome。
  • Node.js(Node):三个在服务器运维 JavaScript
    的周转时(runtime卡塔 尔(阿拉伯语:قطر‎,它具备访谈文件系统和互联网权限(你的微型机也得以是意气风发台服务器!卡塔 尔(阿拉伯语:قطر‎。

图片 1

Windows

$ .electronelectron.exe your-app

NSIS

Nullsoft Scriptable Install System是三个Microsoft
Windows下的脚本驱动的安装制作工具。它拆穿在无偿软件执照下,是三个像样于InstallShield的大范围的被用来代替商业专有产物的工具。electron-builder支持NSIS作为多个编写翻译指标。

开拓体验怎么着?

依据 Electron 的开销就疑似在支付网页,并且能够无缝地 使用
Node
。恐怕说:在构建三个 Node 应用的同不经常候,通过 HTML 和 CSS
构建分界面。此外,你只需为二个浏览器(最新的
Chrome
卡塔 尔(英语:State of Qatar)举行规划(即没有必要思忖宽容性等卡塔 尔(阿拉伯语:قطر‎。

  • 使用 Node:这还不是全部!除了全部的 Node API,你仍然是能够使用托管在
    npm 上超过 350,000 个的模块。
  • 五个浏览器:而不是全部浏览器都提供平等的体裁,Web
    设计员和开拓者常常由此而只好源消花费越来越多的肥力,让网址在区别浏览器上显现同样。
  • 最新的 Chrome:可采用超越 七成 的 ES二〇一六 本性和别的超帅的个性(如
    CSS 变量卡塔 尔(英语:State of Qatar)。

Linux

$ ./electron/electron your-app/

process

贰个经过是三个正在运作的微处理机程序的实例。Electron应用实际上是使用主进程和三个或多少个渲染进度並且运转多少个程序。

Node.js和Electron中,每三个运行着的长河都以二个process对象。这么些目的是叁个大局的并提供有关当前经过的新闻和决定。作为二个大局的,它在选取中不应用require()也是行之有效的。

参见:main
process, renderer
process

五个经过(注重卡塔尔国

Electron
有两种进度:『主进度』和『渲染进度』。部分模块只可以在二者之后生可畏上运转,而略带则无界定。主进度愈来愈多地负责幕后剧中人物,而渲染进度则是应用程序的逐一窗口。

注:可由此职分微电脑(PC卡塔尔国/活动监视器(Mac卡塔尔查看进程的连带消息。

  • 模块:Electron 的 API 是依附它们的用项进行分组。比方:dialog
    模块具备有着原生 dialog 的 API,如张开文件、保存文件和警报等弹窗。

macOS

$ ./Electron.app/Contents/MacOS/Electron your-app/

Electron.app 里面是 Electron 发表包,你能够在
这里
下载到。

renderer process

在你的施用中,渲染进程正是三个浏览器窗口。不一致于主进度,能够有四个渲染进程并且每三个渲染进度都作为四个相间的经过来运维。它们也得以被埋伏。

相同的浏览器中,网页平日运维在一个沙盒情状中,并且不容许调用本地财富。Electron的使用者有职责使用Node.js接口来与底层的操作系统哦人机联作。

参考:process,main
process

主进程

主进程,日常是三个命名字为 main.js 的文本,该文件是种种 Electron
应用的入口。它调节了动用的生命周期(从展开到关闭卡塔 尔(英语:State of Qatar)。它不只能调用原生成分,也能创立新的(八个卡塔 尔(阿拉伯语:قطر‎渲染进度。其它,Node
API 是停放在那之中的。

  • 调用原生成分:展开 diglog
    和别的操作系统的竞相均是能源密集型操作(注:出于安全着想,渲染进度是无法一直访谈本地能源的卡塔尔,由此都要求在主进度完成。

图片 2

以批发版本运转

在您成功了你的行使后,你能够遵从
利用铺排
教导发表二个本子,并且以已经打包好的花样运转应用。

Squirrel

Squirrel是二个开源的框架,可以允许Electron应用自动晋级到已经发布的流行版本。查看autoUpdater接口的运用Squirrel运营的信息。

渲染进程

渲染进程是运用的三个浏览器窗口。与主进度差别,它能存在七个(注:三个Electron
应用只好存在一个主进度卡塔 尔(英语:State of Qatar)况兼交互作用独立(它也能是隐藏的卡塔尔。主窗口平时被取名字为
index.html。它们就像是超人的 HTML 文件,但 Electron 授予了它们完整的
Node API。由此,那也是它与浏览器的分别。

  • 互相独立:每一个渲染进程都以独立的,那表示有个别渲染进度的垮台,也不会耳熏目染其它渲染进程。
  • 隐藏:可隐瞒窗口,然后让其在私自运营代码(卡塔 尔(阿拉伯语:قطر‎。

图片 3

仿效上边例子

复制并且运行那么些库
electron/electron-quick-start

注意:运转时索要您的种类已经安装了
Git 和
Node.js(包含
npm)。

# 克隆这仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入仓库
$ cd electron-quick-start
# 安装依赖库并运行应用
$ npm install && npm start

越来越多 apps 例子,查看 electron 社区成立的 list of
boilerplates。

userland

其风华正茂术语来自于Unix社区,”userland”或”userspace”在运营在操作系统内核之外的顺序中被谈到。方今,那么些术语已经在Node和npm社区中普遍,用于区分”Node
core”和npm上记下的通过更加大的”user”社区发表的包。

像Node,Electron是二个在意于有八个小的接口集结,并且那些集结提供全部的必得的为了开拓多平台桌面程序的原生接口。这么些企划观念使得Electron保持为三个灵活的工具,并不是过多的鲜明何以来行使它。Userland使得客户能够创立并分享工具,而那个工具提供依赖“core”中立见成效内容之上的叠合效能。

把它们想象成那样

Chrome(或其余浏览器卡塔尔国的各种标签页(tab卡塔尔国及其页面,就好比 Electron
中的一个独自渲染进程。尽管关闭全部标签页,Chrome 依然留存。这好比
Electron 的主进程,能开荒新的窗口或关闭那么些动用。

注:在 Chrome
浏览器中,三个标签页(tab卡塔 尔(阿拉伯语:قطر‎中的页面(即除去浏览器自身部分,如找寻框、工具栏等卡塔尔就是三个渲染进度。

图片 4

V8

V8是谷歌(Google卡塔尔的开源JavaScrip引擎。它是用C++编写的还要被用在GoogleChrome中,Chrome是Google的开源浏览器。V8能够独立运转,恐怕被放置到此外C++应用中。

相互近讯

鉴于主进度和渲染进程各自承当不相同的义务,而对此急需一块完结的职务,它们要求相互通信。IPC就为此而生,它提供了经过间的通信。但它只好在主进度与渲染进度之间传递消息(即渲染进度之间不能够实行直接通信卡塔尔国。

  • IPC:主进程和渲染进度各谦和有贰个 IPC 模块。

图片 5

webview

webview标签是被用来在你的Electron应用中放到“guest”(举个例子二个外界网页卡塔尔国内容。他们是丰盛相同的内嵌框架,然而不一样之处在于每三个webview运营在二个点名的进度中。它并从未和你的网页具备相近的权柄,况兼在您的行使和停放内容之间相互都以异步的。那将维持你的使用对于嵌入内容的安全性。

汇成一句话

Electron 应用就如 Node 应用,它也依附二个 package.json
文件。该文件定义了哪些文件作为主进度,并因此让 Electron
知道从何运营应用。然后主进度能成立渲染进度,并能使用 IPC
让两个间开展消息传递。

图片 6

时至明天,Electron
的底子部分介绍达成。该片段是基于小编从前翻译的生龙活虎篇文章《Essential
Electron》,译文可点击
这里。


Vue 全家桶

该工具使用了 Vue、Vuex、Vuex-router。在工具基本定型阶段,由 1.x 进级到了
2.x。

何以选取 Vue

对此作者来讲:

  • 大约易用,日常采用只需看官方文书档案。
  • 数量驱动视图,所以基本不用操作 DOM 了。
  • 框架的存在是为了帮扶我们应对复杂度。
  • 全家桶的低价是:对于日常景色,大家就无需思虑用什么样个库(插件卡塔 尔(阿拉伯语:قطر‎。

Vue 1.x -> Vue 2.0 的本子迁移用
vue-migration-helper
就可以深入分析出抢先一半供给改换的地方。

网暮春有相当多关于 Vue 的科目,故在这里不再赘言。至此,Vue 部分介绍完结。


js-xlsx

该库支持各个石英表格格式的剖析与变化。它由 JavaScript 完成,适用于前面一个和
Node。详情>>

方今支撑读入的格式有(不断更新卡塔尔国:

  • Excel 2007+ XML Formats (XLSX/XLSM)
  • Excel 2007+ Binary Format (XLSB)
  • Excel 2003-2004 XML Format (XML “SpreadsheetML”)
  • Excel 97-2004 (XLS BIFF8)
  • Excel 5.0/95 (XLS BIFF5)
  • OpenDocument Spreadsheet (ODS)

支撑写出的格式有:

  • XLSX
  • CSV (and general DSV)
  • JSON and JS objects (various styles)

当前该库提供的 sheet_to_json 方法能将读入的 Excel 数据转为 JSON
格式。而对此导出操作,大家必要为 js-xlsx 提供钦赐的 JSON 格式。

越多关于 Excel 在 JavaScript
中拍卖的知识可查阅凹凸实验室的《Node读写Excel文件斟酌实行》。但该文章存在两处难点(均在
js-xlsx 实战的导出表格部分卡塔 尔(阿拉伯语:قطر‎:

  1. 转移尾部时,Excel 的列音信轻易地因此 String.fromCharCode(65+j)
    生成。当列大于 26 时会出现难题。这么些难题会在后面章节中提交实施方案;
  2. 转换来 worksheet
    要求的组织处,现身逻辑性错误,而且会形成严重的质量难点。逻辑难点在这不汇报,大家看看品质难点:
    随着 ECMAScript 的不断更新,JavaScript
    变得尤其有力和易用。纵然如此,大家依然要做到『物尽所用』,而不要『大材小用』,不然大概会拿到“反效果”。这里引致品质难题的正是
    Object.assign()
    方法,该格局能够把自由多少个源对象的可枚举属性拷贝至目的对象,并回到目的对象。由于该办法本人的得以完成机制,会在这里案例中生出大批量的冗余操作。在该案例中,单元格音信是唯黄金年代的,所以直接通过
    forEach 为三个空对象赋值就可以。提高 N
    倍品质的同不平时间,也把逻辑性错误解决了。

原来的:

JavaScript

var result = 某数组.reduce((prev, next) => Object.assign({}, prev,
{[next.position]: {v: next.v}}), {});

1
2
var result = 某数组.reduce((prev, next) =&gt; Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
 

改为:

JavaScript

var result = 某数组.forEach((v, i) => data[v.position]= {v: v.v})

1
2
var result = 某数组.forEach((v, i) =&gt; data[v.position]= {v: v.v})
 

实行是验证真理的唯大器晚成典型

在领略上述知识后,上面就研讨在该品种进行中总结出来的手艺、难题和重大

CSS、JavaScript 和 Electron 相关的学识和技能

高亮 table 的列

Excel 单元格选拔 table 标签突显。在 Excel
中,被入选的单元格会高展示公布应的『行』和『列』,以提示顾客。在该使用中也可能有做相应的管理,横向高亮采纳
tr:hover 达成,而纵向呢?这里所使用的贰个才能是:

假如 HTML 结构如下:

JavaScript

div.container table tr td

1
2
3
4
5
div.container
  table
    tr
      td
 

CSS 代码如下:

JavaScript

.container { overflow:hidden; } td { position: relative; }
td:hover::after { position: absolute; left: 0; right: 0; top: -1个亿px;
// 小指标完毕,可是是负的😭 bottom: -1个亿px; z-index: -1; //
幸免遮住自家和同列 td 的剧情、border 等 }

1
2
3
4
5
6
7
8
9
10
11
.container { overflow:hidden; }
td { position: relative; }
td:hover::after {
  position: absolute;
  left: 0;
  right: 0;
  top: -1个亿px; // 小目标达成,不过是负的&#x1f62d;
  bottom: -1个亿px;
  z-index: -1; // 避免遮住自身和同列 td 的内容、border 等
}
 

斜分水线

如图:图片 7

分水线能够由此 ::after/::before 伪类成分完成一条直线,然后通过
transform:rotate();
旋转特定角度实现。但这种达成的多个标题是:由于宽度是不定的,由此需求通过
JavaScript 运算技巧赢得确切的对角分水岭。

就此,这里能够经过 CSS 线性渐变
linear-gradient(to top right, transparent, transparent calc(50% - .5px), #d3d6db calc(50% - .5px), #d3d6db calc(50% + .5px), transparent calc(50% + .5px))
达成。无论宽高怎么样变,依然妥妥地自适应。

Excel 的列转变

  • Excel 的列供给用『字母』表示,但无法轻巧地因而
    String.fromCharCode()
    达成,因为当不止 26 列 时就能时有爆发难题(如:第 27
    列,String.fromCharCode(65+26) 获得的是 [,而不是
    AA卡塔尔国。因而,那亟需经过『十进制和 26 进制调换』算法来完毕。

JavaScript

// 将盛传的自然数转变为26进制表示。映射关系:[0-25] -> [A-Z]。
function getCharCol(n) { let temCol = ”, s = ”, m = 0 while (n >=
0) { m = n % 26 + 1 s = String.fromCharCode(m + 64) + s n = (n – m) / 26
} return s }

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将传入的自然数转换为26进制表示。映射关系:[0-25] -&gt; [A-Z]。
function getCharCol(n) {
  let temCol = ”,
    s = ”,
    m = 0
  while (n &gt;= 0) {
    m = n % 26 + 1
    s = String.fromCharCode(m + 64) + s
    n = (n – m) / 26
  }
  return s
}
 

JavaScript

// 将盛传的26进制转变为自然数。映射关系:[A-Z] ->[0-25]。
function getNumCol(s) { if (!s) return 0 let n = 0 for (let i = s.length

  • 1, j = 1; i >= 0; i–, j *= 26) { let c = s[i].toUpperCase() if
    (c < ‘A’ || c > ‘Z’) return 0 n += (c.charCodeAt() – 64) * j }
    return n – 1 }
1
2
3
4
5
6
7
8
9
10
11
12
// 将传入的26进制转换为自然数。映射关系:[A-Z] -&gt;[0-25]。
function getNumCol(s) {
  if (!s) return 0
  let n = 0
  for (let i = s.length – 1, j = 1; i &gt;= 0; i–, j *= 26) {
    let c = s[i].toUpperCase()
    if (c &lt; ‘A’ || c &gt; ‘Z’) return 0
    n += (c.charCodeAt() – 64) * j
  }
  return n – 1
}
 

为 DOM 的 File 对象增添了 path 属性

Electron 为 File 对象额外增了 path
属性,该属性可拿到文件在文件系统上的诚实路线。因而,你能够动用 Node
横行霸道。应用处景有:拖拽文件后,通过 Node 提供的 File API
读取文件等。

扶植广大的编纂成效,如粘贴和复制

Electron 应用在 MacOS
中默许不匡助『复制』『粘贴』等大范围编辑成效,因而要求为 MacOS
显式地设置复制粘贴等编写制定效率的菜单栏,并为此设置相应的快捷键。

JavaScript

// darwin 就是 MacOS if (process.platform === ‘darwin’) { var template =
[{ label: ‘FromScratch’, submenu: [{ label: ‘Quit’, accelerator:
‘CmdOrCtrl+Q’, click: function() { app.quit(); } }] }, { label: ‘Edit’,
submenu: [{ label: ‘Undo’, accelerator: ‘CmdOrCtrl+Z’, selector:
‘undo:’ }, { label: ‘Redo’, accelerator: ‘Shift+CmdOrCtrl+Z’, selector:
‘redo:’ }, { type: ‘separator’ }, { label: ‘Cut’, accelerator:
‘CmdOrCtrl+X’, selector: ‘cut:’ }, { label: ‘Copy’, accelerator:
‘CmdOrCtrl+C’, selector: ‘copy:’ }, { label: ‘Paste’, accelerator:
‘CmdOrCtrl+V’, selector: ‘paste:’ }, { label: ‘Select All’, accelerator:
‘CmdOrCtrl+A’, selector: ‘selectAll:’ }] }]; var osxMenu =
menu.buildFromTemplate(template); menu.setApplicationMenu(osxMenu); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// darwin 就是 MacOS
if (process.platform === ‘darwin’) {
    var template = [{
      label: ‘FromScratch’,
      submenu: [{
        label: ‘Quit’,
        accelerator: ‘CmdOrCtrl+Q’,
        click: function() { app.quit(); }
      }]
    }, {
      label: ‘Edit’,
      submenu: [{
        label: ‘Undo’,
        accelerator: ‘CmdOrCtrl+Z’,
        selector: ‘undo:’
      }, {
        label: ‘Redo’,
        accelerator: ‘Shift+CmdOrCtrl+Z’,
        selector: ‘redo:’
      }, {
        type: ‘separator’
      }, {
        label: ‘Cut’,
        accelerator: ‘CmdOrCtrl+X’,
        selector: ‘cut:’
      }, {
        label: ‘Copy’,
        accelerator: ‘CmdOrCtrl+C’,
        selector: ‘copy:’
      }, {
        label: ‘Paste’,
        accelerator: ‘CmdOrCtrl+V’,
        selector: ‘paste:’
      }, {
        label: ‘Select All’,
        accelerator: ‘CmdOrCtrl+A’,
        selector: ‘selectAll:’
      }]
    }];
    var osxMenu = menu.buildFromTemplate(template);
    menu.setApplicationMenu(osxMenu);
}
 

更临近原生应用

Electron
的三个缺欠是:纵然你的采纳是二个差不离的机械手表,但它也只可以包罗完整的根底设备(如
Chromium、Node
等卡塔 尔(阿拉伯语:قطر‎。因而,经常境况下,打包后的程序起码会落得几十兆(依照系统项目举办调换卡塔尔。当您的使用越复杂,就越能够忽视文件容积难点。

赫赫有名,页面包车型地铁渲染难免会导致『白屏』,并且这里运用了 Vue
这类框架,情形就特别糟糕了。其余,Electron
应用也防止不了『先开发浏览器,再渲染页面』的步子。下边提供二种格局来缓解这种状态,以让程序更周围原生应用。

  1. 钦赐 BrowserWindow 的背景颜色;
  2. 先隐蔽窗口,直到页面加载后再呈现;
  3. 保留窗口的尺码和岗位,以让程序后一次被展开时,依旧保留的相通大小和出今后平等的职位上。

对此第一点,若使用的背景不是黑灰(#fff卡塔尔国的,那么可钦赐窗口的背景颜色与其肖似,以制止渲染后的剧变。

JavaScript

mainWindow = new BrowserWindow({ title: ‘XCel’, backgroundColor:
‘#f5f5f5’, };

1
2
3
4
5
mainWindow = new BrowserWindow({
    title: ‘XCel’,
    backgroundColor: ‘#f5f5f5’,
};
 

对于第二点,由于 Electron
本质是二个浏览器,要求加载非网页部分的能源。因而,大家能够先隐讳窗口。

JavaScript

var mainWindow = new BrowserWindow({ title: ‘ElectronApp’, show: false,
};

1
2
3
4
5
var mainWindow = new BrowserWindow({
    title: ‘ElectronApp’,
    show: false,
};
 

等到渲染进度始起渲染页面包车型大巴那一刻,在 ready-to-show
的回调函数中突显窗口。

JavaScript

mainWindow.on(‘ready-to-show’, function() { mainWindow.show();
mainWindow.focus(); });

1
2
3
4
5
mainWindow.on(‘ready-to-show’, function() {
    mainWindow.show();
    mainWindow.focus();
});
 

对此第三点,作者并不曾兑现,原因如下:

  1. 客户平常是基于当下的动静对程序的尺寸和地方张开调治,即视景况而定。
  2. 以上是本身个人臆测,首借使自家懒。

其贯彻情势,可参照《4 must-know tips for building cross platform
Electron
apps》。

哪些在渲染进度调用原生弹框?

在渲染进度中调用原来专项于主进程中的 API (如弹框卡塔尔国的艺术有二种:

  1. IPC 通信模块:先在主进度通过 ipcMain 进行监听,然后在渲染进度经过
    ipcRenderer 进行接触;
  2. remote 模块:该模块为渲染进程和主进度之间提供了快速的简报方式。

对此第三种办法,在渲染进度中,运营以下代码就能够:

JavaScript

const remote = require(‘electron’).remote remote.dialog.showMessageBox({
type: ‘question’, buttons: [‘不告知你’, ‘未有梦想’], defaultId: 0,
title: ‘XCel’, message: ‘你的企盼是什么样?’ }

1
2
3
4
5
6
7
8
9
10
const remote = require(‘electron’).remote
 
remote.dialog.showMessageBox({
  type: ‘question’,
  buttons: [‘不告诉你’, ‘没有梦想’],
  defaultId: 0,
  title: ‘XCel’,
  message: ‘你的梦想是什么?’
}
 

自动更新

假定 Electron
应用尚未提供自动更新成效,那么就意味着顾客想体会新开荒的功力或用上修复
Bug
后的新本子,只好靠顾客自个儿主动地去官方网站下载,那活脱脱是倒霉的经历。Electron
提供的 autoUpdater
模块可完结自动更新效能,该模块提供了第三方框架
Squirrel 的接口,但 Electron 近期只内置了
Squirrel.Mac,且它与
Squirrel.Windows(须求额外引进卡塔 尔(英语:State of Qatar)的管理方式也不平等(在顾客端与劳动器端两上边卡塔 尔(英语:State of Qatar)。因而风流倜傥旦对该模块目生,管理起来会相对相比麻烦。具体能够参见笔者的另豆蔻年华篇译文《Electron
自动更新的欧洲经济共同体教程(Windows 和
OSX卡塔 尔(英语:State of Qatar)》。

当下 Electron 的 autoUpdater 模块不扶持 Linux 系统。

其余,XCel 如今并从未运用 autoUpdater 模块实现自动更新功效,而是利用
Electron 的
DownloadItem
模块实现,而服务器端则利用了 Nuts。

为 Electron 应用生成 Windows 安装包

通过 electron-builder 可一贯扭转见惯司空的
MacOS 安装包,但它生成的 Windows 的安装包却略显简洁(暗中认可选项时卡塔 尔(英语:State of Qatar)。

图片 8
Mac 平淡无奇的设置形式,将“侧边包车型大巴施用图标”拖拽到“侧面的 Applications”就可以

透过 electron-builder 生成的 Windows 安装包与大家在 Windows
上大范围的软件设置分界面不太相近,它未有安装向导和点击“下一步”的开关,只有叁个安装时的
gif 动漫(暗中同意的 gif 动漫如下图,当然你也足以钦定特定的 gif
动漫卡塔尔国,因而也就停业了客商筛选安装路线等义务。

图片 9
Windows 安装时 私下认可突显的 gif
动漫

若是您想为打包后的 Electron 应用(即透过
electron-packager/electron-builder
生成的,可径直运转的主次目录卡塔尔生成拥有一点击“下一步”开关和可让客户指定安装路线的家常便饭安装包,能够品味
NSIS 程序,具体可看那篇教程 《[教學]只要10分鐘學會使用 NSIS
包裝您的桌面軟體–安裝程式打包。完全免費。》。

注:electron-builder
也提供了转移安装包的配备项,具体查看>>。

NSIS(Nullsoft Scriptable Install System卡塔 尔(阿拉伯语:قطر‎是三个开源的 Windows
系统下安装程序制作程序。它提供了设置、卸载、系统设置、文件解压缩等效能。正如其名字所描述的那么,NSIS
是透过它的脚本语言来陈诉安装程序的一举一动和逻辑的。NSIS
的脚本语言和大面积的编制程序语言有相似的结议和语法,但它是为安装程序这类应用所设计的。

时至明天,CSS、JavaScript 和 Electron 相关的学识和手艺部分演讲完成。


性子优化

上边谈谈『质量优化』,那部分涉嫌到运作功能内部存款和储蓄器占用量
注:以下内容均基于 Excel 样例文件(数据量为:1915 行 x 180
列卡塔 尔(英语:State of Qatar)得出的下结论。

进行功用和渲染的优化

Vue 质量真的好?

Vue 一向标榜着自个儿品质优质,但当数据量上升到自然量级时(如 一九一二 x 180 ≈
34 万个数据单元卡塔尔,会冒出严重的本性难点(未做相应优化的前提下卡塔 尔(阿拉伯语:قطر‎。

如直接通过列表渲染 v-for 渲染数据时,会促成程序卡死。
答:通过翻看有关材质可得, v-for
在第意气风发渲染时,须求对各种子项进行起头化(如数据绑定等操作,以便具有越来越快的换代速度卡塔 尔(阿拉伯语:قطر‎,那对于数据量十分的大时,无疑会引致惨痛的品质难点。

当即,作者想到了三种缓慢解决思路:

  1. Vue 是数码驱动视图的,对数据分段 push,将要一个特大的职分分割为 N
    份。
  2. 自身拼接 HTML 字符串,再经过 innerHTML 一回性插入。

末尾,笔者选拔了第二条,理由是:

  1. 属性最好,因为每回履行多少过滤时,Vue 都要举行 diff,品质不好。
  2. 更相符当下利用的须要:纯展现且没有必要动漫过渡等。
  3. 实现更简明

将原本勤奋的 DOM 操作(Vue卡塔尔国调换为 JavaScript
的拼接字符串后,质量获得了比非常的大提高(不会变成程序卡死而渲染不出视图卡塔尔国。这种优化措施难道不便是Vue、React
等框架消释的主题材料之大器晚成吧?只然而框架思考的情景更广,有个别地点须求我们友好依据实际景况实行优化而已。

在浏览器此中,JavaScript 的运算在今世的外燃机中拾壹分快,但 DOM
自己是那些缓慢的东西。当你调用原生 DOM API 的时候,浏览器要求在
JavaScript 引擎的语境下去接触原生的 DOM
的完成,这几个进度有非常的性质损耗。所以,本质的考虑衡量是,要把耗时的操作尽量放在纯粹的乘除中去做,保险最终总计出来的内需实际接触实际
DOM 的操作是最少的。 —— 《Vue
2.0——渐进式前端施工方案》

自然,由于 JavaScript
天生单线程,固然进行数速度再快,也难免会引致页面有短暂的日子不容客户的输入。那个时候可通过
Web Worker 或任何方式消释,那也将是我们延续讲到的标题。

也会有网络基友提供了优化多量列表的不二秘诀:。但在该案例中小编并从未应用此办法。

强大的 GPU 加速

将拼接的字符串插入 DOM
后,现身了其余一个主题材料:滚动会很卡。推测那是渲染难点,究竟 34
万个单元格同不平时候设有于分界面中。

添加 transform: translate3d(0, 0, 0) / translateZ(0) 属性运转 GPU
渲染,就能够缓慢解决这么些渲染质量难题。再度咋舌该属性的强硬。

后来,构思到客户并没有供给查看全数数额,只需出示部分数据让顾客实行参考就能够。大家对此只渲染前
30/50 行数据。那样就可以升高客户体验,也能尤其优化质量。

纪念关闭 Vuex 的严格格局

此外,由于投机学艺不精和大意,忘记在生养条件关闭 Vuex
的『严刻方式』。

Vuex 的严苛方式要在分娩遭受中关闭,不然会对 state 树实行一个深观察(deep
watch),发生不须要的性质损耗。可能在数据量少时,不会当心到那些题目。

余烬复起那个时候的景色:导入 Excel 数据后,再举行交互作用(涉及 Vuex
的读写操作卡塔尔国,要求等几秒才会响应,而直白通过纯 DOM
监听的事件则无此主题素材。由此,决断出是 Vuex 难点。

JavaScript

const store = new Vuex.Store({ // … strict: process.env.NODE_ENV !==
‘production’ })

1
2
3
4
5
const store = new Vuex.Store({
  // …
  strict: process.env.NODE_ENV !== ‘production’
})
 

多进程!!!

前面说道,JavaScript
天生单线程,固然再快,对于数据量一点都不小时,也相会世拒却响应的题目。由此须要Web Worker 或近乎的方案去解决。

在此自个儿不选拔 Web worker 的由来有如下几点:

  1. 有别的越来越好的代表方案:贰个主进度能创立五个渲染进度,通过 IPC
    就能够实行数量交互;
  2. Electron 不协助 Web
    Worker!(当然,或许会在新本子帮衬,最新新闻请关心官方)

Electron 小编在 二零一五.11.7 在《state of web worker support?》 issue
中还原了以下那意气风发段:

Node integration doesn’t work in web workers, and there is no plan to
do. Workers in Chromium are implemented by starting a new thread, and
Node is not thread safe. Back in past we had tried to add node
integration to web workers in Atom, but it crashed too easily so we
gave up on it.

进而,我们最后利用了创建叁个新的渲染进程 background process
实行拍卖数量。由 Electron 章节可见,各个 Electron
渲染进度是单身的,由此它们不会相互影响。但那也拉动了一个主题素材:它们不能够互相通信?

错!下边有 3 种办法开展广播发表:

  1. Storage
    API:对有个别标签页的
    localStorage/sessionStorage 对象举办增加和删除改时,别的标签页能经过
    window.storage 事件监听到。
  2. IndexedDB:IndexedDB
    是二个为了能够在顾客端存款和储蓄可观数额的结构化数据,何况在此些数据上利用索引实行高质量检索的
    API。
  3. 因此主进度作为中转站:设主分界面包车型客车渲染进度是 A,background process
    是 B,那么 A 先将 Excel 数据传递到主进程,然后主进度再转车到 B。B
    处理完后再原路重临,具体如下图。当然,也足以将数据存款和储蓄在主进度中,然后在多少个渲染进程中运用
    remote 模块来拜见它。

该工具接收了第两种方法的第生龙活虎种意况:
图片 10

1、主页面渲染进程 A 的代码如下:

JavaScript

//① ipcRenderer.send(‘filter-start’, { filterTagList:
this.filterTagList, filterWay: this.filterWay, curActiveSheetName:
this.activeSheet.name }) // ⑥ 在某处采用 filter-response 事件
ipcRenderer.on(“filter-response”, (arg) => { // 得随地理数据 })

1
2
3
4
5
6
7
8
9
10
11
12
//①
ipcRenderer.send(‘filter-start’, {
    filterTagList: this.filterTagList,
    filterWay: this.filterWay,
    curActiveSheetName: this.activeSheet.name
})
 
// ⑥ 在某处接收 filter-response 事件
ipcRenderer.on("filter-response", (arg) =&gt; {
    // 得到处理数据
})
 

2、作为中转站的主进度的代码如下:

JavaScript

//② ipcMain.on(“filter-start”, (event, arg) => { // webContents
用于渲染和调控 web page
backgroundWindow.webContents.send(“filter-start”, arg) }) // ⑤
用于收纳再次来到事件 ipcMain.on(“filter-response”, (event, arg) => {
mainWindow.webContents.send(“filter-response”, arg) })

1
2
3
4
5
6
7
8
9
10
11
//②
ipcMain.on("filter-start", (event, arg) =&gt; {
    // webContents 用于渲染和控制 web page
    backgroundWindow.webContents.send("filter-start", arg)
})
 
// ⑤ 用于接收返回事件
ipcMain.on("filter-response", (event, arg) =&gt; {
    mainWindow.webContents.send("filter-response", arg)
})
 

3、处理费劲数据的 background process 渲染进度 B 的代码如下:

JavaScript

// ③ ipcRenderer.on(‘filter-start’, (event, arg) => { // 进行演算 …
// ④ 运算完结后,再通过 IPC 原路再次回到。主进度和渲染进度 A
也要构建相应的监听事件 ipcRenderer.send(‘filter-response’, { filRow:
tempFilRow }) })

1
2
3
4
5
6
7
8
9
10
11
// ③
ipcRenderer.on(‘filter-start’, (event, arg) =&gt; {
    // 进行运算
    …
 
    // ④ 运算完毕后,再通过 IPC 原路返回。主进程和渲染进程 A 也要建立相应的监听事件
    ipcRenderer.send(‘filter-response’, {
        filRow: tempFilRow
    })
})
 

至今甘休,大家将『读取文件』、『过滤数据』和『导出文件』三大耗费时间的数量操作均转移到了
background process 中处理。

那边,大家只开创了二个
background process,假若想要做得更可是,我们得以新建『CPU 线程数- 1 』
个的 background process
同期对数据开展拍卖,然后在主进度对拍卖后数据实行拼接,最终再将拼接后的数目重回到主页面包车型客车渲染进度。那样就能够充足榨干
CPU 了。当然,在那作者不会进展这一个优化。

决不为了优化而优化,不然劳民伤财。 —— 某网上好朋友

内部存款和储蓄器占领量过大

解决了实施效能和渲染难点后,发掘也设有内部存款和储蓄器占用量过大的难题。那时推断是以下多少个原因:

  1. 三大耗时操作均放置在 background process
    管理。在通信传递数据的历程中,由于不是分享内部存款和储蓄器(因为 IPC 是基于
    Socket
    的卡塔尔国,导致现身多份数据别本(在写那篇小说时才有了那相对方便的答案卡塔尔。
  2. Vuex
    是以一个大局单例的方式实行保管,但它会是否对数据做了一点封装,而导致质量的损耗呢?
  3. 鉴于 JavaScript
    近年来不享有积极回笼能源的才具,所以只可以积极对闲置对象设置为
    null,然后等待 GC 回笼。

是因为 Chromium 采纳多进度框架结构,因而会涉及到进度间通讯难题。Browser
进度在运维 Render 过程的历程中会创立一个以 UNIX Socket 为根底的 IPC
通道。有了 IPC 通道之后,接下去 Browser 进程与 Render
进度就以新闻的情势打开通讯。大家将这种消息称为 IPC
信息,以界别于线程音信循环中的音讯。
——《Chromium的IPC音信发送、接收和散发机制解析》

概念:为了轻易通晓,以下『Excel 数据』均指 Excel 的整套使得单元格转为
JSON 格式后的数额。

最轻便管理的实实在在是第三点,手动将不再必要的变量及时安装为
null,但成效并不鲜明。

新兴,通过操作系统的『活动监视器』(Windows
上是任务微处理器卡塔 尔(英语:State of Qatar)对该工具的每阶段(展开时、导入文本时、筛选时和导出时卡塔尔国举行简易的内部存款和储蓄器解析,获得以下报告:

—————- S:报告分水线 —————-
经观望,主要耗内部存款和储蓄器的是页面渲染进度。上边通过截图注解:
PID 15243 是主进度
PID 15246 是页面渲染进度
PID 15248 是 background 渲染进度

a、第叁遍运转程序时(第 4 行是主进度;第 1 行是页面渲染进度;第 3 行是
background 渲染进程 卡塔尔国

图片 11

b、导入文本(第 5 行是主进度;第 2 行是页面渲染进程;第 4 行是
background 渲染进程 卡塔尔国
图片 12

c、筛选数据(第 4 行是主进程;第 1 行是页面渲染进度;第 3 行是
background 渲染进度 卡塔尔
图片 13

由于 JavaScript 前段时间不具备积极回笼财富的功效,所以只可以积极将对象设置为
null,然后等待 GC 回笼。

就此,经过大器晚成段时间等待后,内部存储器占用如下:
d、大器晚成段时间后(第 4 行是主进程;第 1 行是页面渲染进度;第 3 行是
background 渲染进程 卡塔尔
图片 14

由上述可得,页面渲染进度由于页面成分和 Vue 等 UI
相关能源是稳定的,占用内部存储器非常大且无法回笼。主进度占用能源也无法赢得很好释放,临时不知底自始自终的经过,而
background 渲染进度则较好地放走能源。

—————- E:报告分水线 —————-

依照报告,最初得出的结论是 Vue 和报导时占用能源不小。

基于该工具的实际采取场景:Excel
数据只在『导入』和『过滤后』多个级次须要展示,並且显示的是经过
JavaScript 拼接的 HTML 字符串所结合的 DOM 而已。由此将表格数据放置在
Vuex 中,有一些滥用能源的存疑。

另外,在 background process 中也许有存有后生可畏份 Excel
数据别本。因而,索性只在 background process 存储生机勃勃份 Excel
数据,然后每当数据变动时,通过 IPC 让 background process 再次来到拼接好的
HTML
字符串就可以。这样一来,内部存储器据有量马上跌落相当多。其余,那也是一个一举多得的优化:

  1. 字符串拼接操作也转移到了
    background process,页面渲染进度进一层裁减耗费时间的操作;
  2. 内部存储器据有量大大减小,响应速度也得到了提高。

实际上,那也是有一点像 Vuex 的『全局单例形式管理』,生机勃勃份数据就好。

当然,对于 Excel 的着力音信,如行列数、SheetName、标题组等均依旧保存在
Vuex。

优化后的内部存款和储蓄器据有量如下图。与上述报告的第三张图对比(同一等第卡塔 尔(阿拉伯语:قطر‎,内部存款和储蓄器占领量下降了
44.419%: 图片 15
除此以外,对于没有须求响应的多少,可由此 Object.freeze()
冻结起来。那也是后生可畏种优化花招。但该工具最近并不曾接收到。

时现今天,优化部分也演说罢成了!


该工具目前是开源的,接待我们使用或引入给用研组等有亟待的人。

你们的举报(可提交 issues /
pull
request卡塔 尔(英语:State of Qatar)能让那么些工具在运用和职能上不断完备。

最后,感谢 LV
在产物布置、分界面设计和优化上的武力支持。全文完!

打赏帮助小编写出越多好小说,感谢!

打赏作者

打赏帮助本人写出更多好文章,多谢!

任选豆蔻年华种支付形式

图片 16
图片 17

1 赞 2 收藏
评论

有关笔者:刘健超-J.c

图片 18

前端,在路上…
个人主页 ·
笔者的篇章 ·
19 ·
    

图片 19

发表评论

电子邮件地址不会被公开。 必填项已用*标注