前言
校猩猩一直注重简约而不简单,因此在体验上会额外关注,每一个功能都会反复讨论怎么做用户才会觉得好用,不光是用户体验,在校猩猩开发过程中,我也时刻致力于提升开发体验。
我们是怎么从excel导入数据的
常规实现方式
- 让用户下载excel模板
- 用户将数据先填充到模板中
- 然后导入模版
这种方式不够简约,既然用户需要将数据复制到模板中, 那为什么不直接复制到我们的表格中呢?
直接复制到表格中,乍一看实现起来有点复杂,经过一番摸索,其实也相当简单,经过以下三步即可实现。
读取复制的字符卷,转换二维数组
浏览器中,原生提供了读取粘贴板内容的接口,异步返回字符卷。
// 获取复制的字符卷
const text = await navigator.clipboard.readText()
从excel复制的字符卷有3个特点:
- 行与行之间
\n
分割 - 单元格之间
\t
分割 - 若单元格中有换行,该字符卷有双引号,以
\n
换行
根据这3个特点,很容易将其转换为二维数组
const rows = text
.replace(/("[^"]*")/g, function (match, captured) {
// excel单元格中若换行,则字符卷包含双引号且中间有\n
return captured.replace(/\n/g, '🦪')
})
.split(/\n/)
const gridDatas = []
rows.forEach((it, i) => {
const cols = it.replace(/🦪/g, '\n').replace(/"|\r/g, '').split(/\t/)
// 省略了去掉空行的代码
gridDatas.push(cols)
})
将二维数组转换成对象数组
得到gridDatas
之后,接下来的就是应用到我们的表格中,该过程有个关键点是,这个二维数组的数据应该赋值表格的哪些行和哪些列
这个时候就需要记录用户选中了表格的哪个单元格,在sessionStorage
中存储单元格的索引,我们在执行转换时,就以索引为准
const dataLen = data.value.length
// 获取索引
const [row = dataLen, col = 1] = JSON.parse(sessionStorage.getItem('poniter')) || []
// 表格的字段
const keys = ['name', 'phone', 'relation', 'sex', 'birthday', ...customKeys, 'remark']
// 转换
const newArr = []
gridDatas.forEach((item) => {
const newObj = {}
const newKeys = keys.slice(col - 1)
item.forEach((it, index) => {
const key = newKeys[index]
switch (key) {
case 'phone':
if (/^\d+$/.test(it)) {
newObj[key] = it.substring(0, 11)
} else {
newObj[key] = ''
}
break
// .....省略其他
}
})
newArr.push(newObj)
})
至此,newArr
就可以更新表格的数据了
更新表格数据
// 如果选中的行是最后一行,则全部新增
if (row === dataLen) {
data.value = data.value.slice(0, dataLen - 1).concat(newArr)
} else { // 更新或新增
const updateNum = dataLen - row
const updateArr = newArr.slice(0, updateNum) // 要更新的数据
const restArr = newArr.slice(updateNum) // 要新增的数据
// ....省略其他代码
}
我们是怎么写小程序页面的
新增页面
当我们新增页面时,需要新增一个文件夹和4个文件,还要在app.json
中新增页面path
,我们觉得有点麻烦。
因此写了一个命令行工具,只要执行如下命令即可完成上面的操作:
// 新增页面
npm run cpa
// 或者新增组件
npm run cpc
此命令主要用nodejs来读写文件
- 读取页面模板
- 用模板内容生成目录和文件
- 修改
app.json
文件以添加path
html转wxml实时监听程序
开发小程序页面,我们习惯在vscode中开发,然而在vscode写wxml文件emmt不友好,因此,我们还是用html
写页面
我们开发了一个html
转wxml
的程序,实时监听文件变化生成wxml文件。
这个实时监听程序将html
转换成wxml
,还可以帮助我们做以下事情:
- 将
div
标签转换成view
标签 - 将
span
标签转换成text
标签 - 自动引入全局的
wxs
文件 - 自动引入自定义navbar
- 自动引入全局的dialog组件和toast组件
图标
以往,我们使用图标都是引入iconfont
链接,但这种方式缺点明显:
- 添加了新图标,链接地址要更新
- 需要网络加载,图标会出现延迟显示的情况
- 考虑将来,将来iconfont网站不知道会发生什么
- iconfont项目管理员可能离职
因此,我们采用将图标的svg代码复制到项目仓库,跟随代码一起管理的方式
在项目中,用一个专用文件放置图标的svg文件
然后用一个.wxs
文件管理svg文件的路径,类似下面这样
var baseSvgUrl = '/assets/svg-icons/'
module.exports = {
logo: baseUrl + 'logo.png'
}
使用图标时,我们只需这样
<wxs src="/config/img-urls.wxs" module="imgUrls" />
<image class="w16 h12 mr3" src="{{imgUrls.logo}}"></image>
弹框组件应用
弹框是最频繁使用的组件之一,以声明式使用vant
的组件,每次都需要定义如下变量和方法
<van-popup show="{{ show }}" bind:close="onClose">内容</van-popup>
Page({
data: {
show: false,
},
showPopup() {
this.setData({ show: true });
},
onClose() {
this.setData({ show: false });
},
});
若一个页面有多个弹框,需要定义多个更多这样的变量,略显繁琐,所以,我们希望以函数式调用使用弹框,例如:
const res = await popup({
content: 'calendar', // 弹框类型
title: _t('请选择日期'),
param: {}, // 传输参数
})
if(res) {
// 其他逻辑
}
实现方式, 先在app
对象上定义一个getPopup
方法,调用后返回popup
方法
export default function (params) {
return this.selectComponent('#custom-popup').open(params)
}
// app.js中引入上面的方法
// 在需要popup的页面调用
const popup = app.getPopup.bind(ctx)
// 调用popup
await popup({
....
})
我们是怎么支持国际化的
常规国际化方案
定义语言配置文件,例如:
module.exports = {
zh_cn: {
pageTitle: '页面标题'
},
en: {
pageTitle: 'page title'
},
...
}
然后定义一个方法,根据i18n
变量读取相应的语言配置
function _t(i18n, key) {
if(i18n === 'zh_cn') {
return obj[key]
}
...
}
这样做,不利于代码阅读,因为页面中没有实际的中文,而且如果要通过搜索某个中文定位代码也不是很方便。
为解决上面的问题,我们直接在代码中写中文实现国际化,例如:
<wxs src="./i18n_html/t.wxs" module="_t" />
<div>
{{_t('新增学员', III)}}
{{_t('金额{0}', III, [1000])}}
</div>
很明显,上面代码的_t
函数是用于切换语言的,那么是怎么切换的呢?同样也是根据语言配置文件
var map = require('./keymap.wxs') // 引入语言配置文件
module.exports = function(text, i18n, params) {
var ret = ''
if (i18n === 'zh_CN') {
ret = text
} else {
var obj = map[text]
if (obj && obj[i18n]) {
ret = obj[i18n]
} else {
ret = text
}
}
// ...
return ret
}
上面的语言配置文件是怎么来的呢?这里我们又用到了实时监听程序了,程序自动为我们生成了配置文件
const map = {}
const reg = /_t\(([^\)]+)\)/gim
const m = htmlStr.match(reg) // 代码中是否有_t()调用
if(!m) return
m.forEach((code, i) => {
const m = code.match(/[\'\"]([^\'\"]+)[\'\"]/)
if (m && m[1]) {
map[m[1]] = null
}
})
fs.writeFile(rootPath + 'keymap.js', `export default ${JSON.stringify(map, null, 2)}`, (err) => {})
配置文件结果如下
module.exports = {
"新增学员": {
"en": "Add student",
"zh_HK": "新增學員",
"zh_TW": "新增學員"
},
}
配置文件生成之后,接下来就只要翻译了~