首页

新版vue-router的hooks用法

seo达人

虽然Vue 3还没有正式发布,但是热爱新技术的我早已按捺不住自己的内心,开始尝试在小项目中使用它了。


根据这篇《今日凌晨Vue3 beta版震撼发布,竟然公开支持脚手架项目!》我搭建了一个Vue 3的脚手架项目,用这种方式搭建的脚手架项目不仅仅只有vue是新版的,就连vue-router、vuex都是的。


给大家截一下package.json的图:




可以看到vue-router和vuex都已经开启4.0时代啦!


不过其实我并没有去了解过vue-router 4.0的新用法什么的,因为我觉得它不像vue 3.0都已经进行到beta的版本不会有特别大的变动。


而vue-router 4.0还是alpha的阶段,所以我认为现在去学习它有些为时尚早。但却就是它!差点酿成了一场惨剧。


旧版vue + vue-router的使用方式

假如你在路由里面定义了一个动态参数通常都会这么写:


{

   path: '/:id'

}

然后用编程式导航的时候通常会这样去写:


this.$router.push('/123')

在组件中是这样获取这个参数的:


this.$route.params.id

我以为的新版vue + vue-router的使用方式

由于vue 3.0的Composition API中没有this了,所以我想到了通过获取组件实例的方式来获取$route:


import { defineComponent, getCurrentInstance } from 'vue'


export default defineComponent((props, context) => {

   const { ctx } = getCurrentInstance()

   

   console.log(ctx.$route)

})

没想到打印出来的居然是undefined!

这是咋回事呢?

于是我又打印了一遍ctx(ctx是当前组件上下文):




没有$的那些字段是我在组件中自己定义的变量,带$的这些就是vue内置的了,找了半天发现没有$route了,只剩下了一个$router,估计vue-router 4.0把当前路由信息都转移到$router里面去了。


带着猜想,我点开了$router:




currentRoute! 看名字的话感觉应该就是它了!于是乎我:


import { defineComponent, getCurrentInstance } from 'vue'


export default defineComponent((props, context) => {

   const { ctx } = getCurrentInstance()

   

   console.log(ctx.$router.currentRoute.value.params.id)

})

果然获取到了!好开心!


实际的新版vue + vue-router用法

在接下来的过程中我用ctx.$router代替了原来的this.$router、用ctx.$router.currentRoute.value代替了原先的this.$route。


尽管在接下来的进度中并没有出现任何的bug,程序一直都是按照我所设想的那样去运行的。


但在项目打包后却出现了意想不到的bug:在跳转路由的时候报了一个在undefined上面没有push的错误。


奇了怪了,在开发阶段程序都没有任何的报错怎么一打包就不行了呢?根据我多年的开发经验,我很快就定位到了是vue-router的错误。


难道这样写是错的吗?可是我打印了ctx,它里面明明有一个$router、$router里面明明就有currentRoute、currentRoute里面明明就有一个value、value里面明明就有params、params里面我一点开明明就看到了传过来的参数啊:




估计可能是vue-router的bug,果然alpha阶段的产物不靠谱,我开始后悔使用新版的vue脚手架项目了。


vue-router里的hooks

不过这时我突然灵光一现,vue 3不是受到了react hooks的启发才产生了Composition API的吗?


那么估计vue-router肯定也会受到react-router的启发了!


还好我学过react,果然技多不压身啊!估计里面肯定是有一个useXxx,就像这样:


import { useXxx } from 'vue-router'

那么应该是use什么呢?按理来说应该会尽量的和以前的API保持一定的联系,我猜应该是useRoute和useRouter吧!


为了验证我的想法,我打开了node_modules找到了vue-router的源码:




果不其然,在第2454和第2455行我发现它导出了useRoute和useRouter,那么就是它了:


import { defineComponent } from 'vue'

import { useRoute, useRouter } from 'vue-router'


export default defineComponent(_ => {

   const route = useRoute()

   const router = useRouter()


   console.log(route.params.id)

   router.push('/xxx/xxx')

})

使用这种方式不但可以成功跳转路由,也同样可以获取到路由传过来的参数,这次再打包试了一下,果然就没有之前的那个报错了。


结语

估计以后的vue全家桶要开启全民hooks的时代了,在翻看源码的同时我发现他们把一些示例都写在了vue-router/playground文件夹下了,在里面我发现了一些有趣的用法。


如果有时间的话我会仔细研究一下然后出一篇更加深入的文章给大家,当然如果已经有小伙伴等不及我出新文章的话可以直接进入vue-router-next的github地址:


https://github.com/vuejs/vue-router-next

它的示例都放在了playground这个文件夹下,期待你们研究明白后出一篇更加深入的文章!

B 端设计师如何做竞品分析?

雪涛

将要分析的竞品排了个期,从最难最不熟悉的开始。为什么从最难的开始,可能是个人习惯吧,吃掉最难的那个,后面就会更上手。突然想起之前读的一本书「吃掉那只青蛙」,很不错的一本书,有时间去温习下。

一个产品,其实会有很多功能点,有核心的主要功能,也有一些辅助功能,也会有一些让你忽略,但关键时刻很需要的应急功能,而这些点都需要去整理出来。

分析前-熟悉产品

这一点很重要,要先熟悉产品。如果对产品都不熟悉,那还是先不要做竞品分析。因为很难判断竞品的功能和风格是否也适合当前产品,因为对产品的不熟悉,会产生误判。

当然,产品的目标人群,产品定位,适用范围等等,都会影响产品分析。

所以,花时间熟悉自己负责的产品,是不能跳过的。

开始前的准备

1. 制定时间规划

最好事先做好时间规划,可以有一整块的时间,这样分析产品时,思绪也会比较完整和连续,可以更专注。计算大概分析一个产品需要花费的时间,最好不要用零碎时间来做,这样只会增加时间上的代价,也会增加挫折感;

2. 确定分析的目的

在「竞品分析」中,想要得到的结论和重点是什么。比如重点可能是产品的报表功能、产品的代码审核功能等等,目的的确定能让分析更有针对性,减少干扰。无目的随意分析,得到的结果也会是零乱不堪,最后只是在浪费时间。

3. 寻找帮助者

每个产品,都有其不一样的特性和产品逻辑,你不一定能够完全 cover 到,甚至有些点就是比较难理解的,特别是偏技术性的名词,这时若有技术同学的帮助,就会如虎添翼。所以最好可以事先找一位产品相关的技术同学,询问这段时间是否有空,帮助你解答一些问题。

个人建议:能够在网上查到的资料,就不要先问人,除非时间成本特别高。一方面也是提升自己解决问题的能力,另一方面,也是节省彼此的时间。对方愿意帮你解决问题,不代表你要把所有问题一股脑倒给他,自己了解后再问,也是对对方的尊重,大家的时间都同样宝贵。

4. 其他tips

如果是内部公司产品,提前确认是否需要权限,提前申请好,减少正式开始后,还要等待审批时间。外部产品可以提前找好网站,可以咨询的客服入口,如果是付费竞品,咨询是否可以向财务申请报销等等。

好,现在正式开始吧!

1. 像个用户一样去使用产品

很多时候,设计师的职业病,会让我们过多注重视觉享受,而忽略作为用户,想要的有时候只是功能可用。今天不管你把「扫一扫」功能做得多美,美得像个艺术品一样,可是当扫码付款的时候,怎么也扫不出来,那种站在店家前面忐忑不安,怎么也无法完成付款,后面一堆人等你,你仿佛听见后面其他顾客窃窃私语地讨论着发生什么事情。那种场景我相信你不想经历,同样我们也不应该让用户来经历。

我的项目主管,一直都有提醒我,要像个小白来使用和设计我们的产品。这句建议,也一直在提醒着我。如果站在高姿态来俯视用户,我们就很难真正的「懂」用户,进而很难设计出真正满足用户需要的产品。

这是竞品分析,但是我们也需要转换自己的角色,变成用户。这样能更明白究竟竞品带给用户是便利,还是麻烦。有时适时抽离「设计师」的角色,会让你更能去体会用户的感受。

所以,先去用这个产品吧,然后才会有然后。

2. 如何去使用竞品

一个产品的使用,总是有它的使用场景,手机端的就更多样了,简直无所不在。B 端产品可能会相对少,一般是在办公场景或是特定场景。

可以像个编剧一样,给自己写点剧本,加点情节,塑造一个角色,假设竞品是电商方向,你可以想像,自己是一个刚毕业的社会新人,你可能没多少钱,你可能刚拿到你人生第一桶金,你想买件衣服犒劳自己,或许你会是数码控,你关注已久的佳能单反在双 11 中有优惠等等,然后再去预想接下去的情节,在购物方面会考虑的问题,或许是好用,或许是有趣等等。

也可以做任务式去使用产品,比如以电商为例,任务可以是买件喜欢的衣服,从搜索产品,到找到喜欢的衣服,添加购物车,提交订单,等待发货,收货,确认收货。这一个完整的流程走下来,就会体验产品功能是否好用,搜索结果是否符合预期等等。

3. 记录

使用产品的过程中,会遇到很多情况,有些是可预期的,有些是不可预期的。有些让人觉得很好用,有些却会让人受挫。将这些情况都记录下来,有助于分析产品的可用性程度和满意度。

  • 愉快的:可能是一个友好的提示,减轻你的认知负担,也可以是一个贴心小 loading 动画等等
  • 受挫的:点击没有反馈,提交后没反馈,不知道执行成功与否等等
  • 难以理解:产品中专业名词太多,没有附带解释和帮助文档,完全不知其所以然
  • 产生误解:以为是 A,结果是 B
  • 一脸懵:页面太乱,不知从哪里下手

上面这些只是举例说明,在竞品当中可能遇到的一些问题,也可以去反思自己的产品是否也会这样让用户感到困惑。有时候,太熟悉自己的产品,会自认为产品很完美,会理所当然认为「大家都这么认为」……

记录问题、原因,感受并截图为证(有必要可录屏),后期可追溯。写得越详细越好,后面整理的时候会更清晰。

4. 各个击破-功能了解

在熟悉整个产品后,就需要对产品的各个功能进行分析了解、梳理。了解竞品的核心功能是什么,核心功能在解决用户什么问题,是否真的解决了用户的痛点,其他功能又在整个产品当中充当什么样的角色。

将竞品的功能与本产品功能对比,不只是对比有无,更进一步地去想,为什么有这个功能,为什么没有这个功能,有或没有是否会提高用户的使用效率,用户的留存,用户的体验等等。

功能多不代表好,如果功能不能给用户带来益处,其实它的存在只是增加开发成本而已。

整体总结

其实竞品分析中,最难的是总结归纳。做了一堆的分析后,结论是什么呢,这个结论如何写呢?

可以先从设立分析目的开始,找到中心轴线,然后再慢慢延展开来。在要做总结报告时,你会欣喜地发现最初设立目标是多么的重要。

文章来源:优设    作者:箴盐设计

这10个设计原则,是确保金融类产品体验优秀的核心要义

雪涛

1、

如何让邮件体验设计更加吸引人?

雪涛

互联网时代的人们早就受够了信息爆炸,我们每天都会经系统推送、应用通知、微信、电话、短信等各类渠道收到大量消息。有多久你没有查收自己的邮箱?就算打开邮件,又有多少推荐内容让你有兴趣进一步了解?是 EDM 老了没用了?真正的原因,可能是我们一开始就错误地忽视了 EDM 设计。

对于 95 后以及更年轻的群体来说,EDM 确实是个上了年纪的概念。EDM(Email Direct Marketing)也叫 Email 营销、电子邮件营销。企业向目标客户发送 EDM 邮件,建立同目标客户的沟通渠道,向其直接传达相关信息,用来促进销售转化。

这个起源于上世纪 80 年代中期,正式诞生于 90 年代的早期互联网产物现在已经三十多岁了。时至今日,EDM 早已成为了全球公认的网络营销重要方法之一,其卓越效果为互联网人数十年的实践所证实。但 EDM 在我国的应用还处于非常低级的水平,不仅没有系统的理论,在实践中也存在许多误区。

在这样一个重视审美与强调更新及时的时代,EDM 邮件朴实无华的外表与「一旦发出就固定呈现」的内容特质显得有些格格不入。作为用户体验设计师,我们可以做什么让 EDM 不落伍呢?

避免成为垃圾邮件

首先,我们可以在设计层面上避免 EDM 邮件被邮箱软件识别为垃圾邮件,不带敏感词语或内容、淡化商业广告色彩、减少数字与附件使用都有助于降低被邮箱系统屏蔽的风险。我们更可以在全量发送前,对指定邮箱进行小范围测试以确保邮件发送成功率。

其次,从其历史来源来看,早期的 EDM 来源于垃圾邮件,这使人们对其本能地缺乏好感,存在排斥心理。因此 EDM 的节奏和时机必须做好控制,对邮件发送的各类数据做好统计,掌握用户的阅读习惯,能更好地提升邮件的打开率。

保持最佳邮件格式

邮件内容需要设计为一定的格式来发送,常用的邮件格式包括纯文本格式、HTML 格式和 Rich Media 格式,或者是这些格式的组合。一般来说,HTML 格式和 Rich Media 格式的电子邮件比纯文本格式具有更好的体验效果。但 Rich Media 格式的电子邮件易造成邮件过大,并且无法确保用户在客户端均能够正常显示,所以在设计时我们优先选择 HTML 格式邮件。

确保跨端体验

与网页不同,我们无法针对不同设备做邮件内容相应的适配设计,兼顾设备特性的通用模版也就成为了设计时的必要关注点。对用户来说,一封邮件阅读体验很差,那么无论邮件的内容多么精彩、多么吸引人,最终的结果也可能只会被丢弃在一边。因此,我们通常会按照移动端尺寸对邮件界面进行设计,注意字体大小、最佳尺寸以及链接按钮的大小等。

除此以外,邮件中链接的定义也应得到我们充分的重视。由于邮件中的链接我们同样无法预先针对不同打开设备进行单独编辑,在有条件的情况下我们可以对链接所跳转的页面进行响应式设计以确保高质量的跨端浏览体验,或者我们也可以采用默认跳转路径而后重定向的传统方式。

与「我」紧密相关

EDM 营销与一般的营销方式最大的区别是:EDM 是一对一的沟通,让用户感觉到尊重,让他感觉到这是为他所建立并且是他所独享的沟通方式。在标题、正文的文案上强调「我」,在内容上也应如此。用户在意什么,我们就发送什么。把握住用户关注的信息,帮助用户收集支持 TA 做决策所需的信息。当我们发送邮件给用户,给予其操作行为的反馈或提醒时,不要浪费这最好的营销机会。优先提供给用户与之行为或特征相关的服务与帮助,其次通过个性化服务或产品推荐促进购买或注册转化,有助于我们将营销机会转化为实际销售成果。

兼顾质量和效率

做好个性化对 EDM 内容模型要求颇高,但从设计角度讲,我们完全可以以原子设计思维实现邮件内容模块的低成本创建与复用。以通用设计模块为「壳」,内容与组合规则为「核」,快速响应 EDM 的运营需求。

以上 5 点就是我结合近期项目经验所得。EDM 虽老,但设计可以让 EDM 老而弥新。祝经你精心设计的 EDM 邮件,一经发出,封封有回应

文章来源:优设    作者:鱼子酱聊设计

用4个经典的重量级产品案例,告诉你什么是标杆式体验设计

雪涛

今天和大家聊一个很多朋友常年卡在 P5/P6 需要关心的命题——如何从业务出发打造具有商业价值还能兼顾用户体验的设计,此篇不谈理论,就通过 4 个经典的重量级产品案例就给大家安排明白啥是「一拳超人」式体验设计——就一个字「强」。

滴滴出行-xpanel

滴滴出行应该属于大家的高频使用 app,但是使用的功能一般还是集中在叫车流程,所以大家可能不太会关注到 CDX 设计团队一个非常核心的设计成果——xpanel。

简单来说 xpanel 就是一个附着于第一信息架构层级上,垂直 Y 轴且支持 X 轴拓展滑动的 Feed 卡片位。内容上分为「消息卡片」「主体卡片」「拓展卡片」三个维度,首屏保障除了「消息」与「主体」外三分之一「拓展卡片1」的露出。

但在简单的交互背后蕴藏的是基于业务的 UGD(用户增长设计)设计思考,这里引用 2018IXDC 会上滴滴主讲人的原话来说就是:

对特定场景垂直领域的深耕和挖掘,寻找「接触点」,帮助获取更多的功能、内容、服务、特性、品牌、运营甚至是喜好……进而实现业务的「有效增长」(转化、变现、留存)。

通俗一点解释就是 xpanel 利用主卡与拓展卡之间的信息架构关系,把拓展卡平衡的分为几类,比如「与产品功能相关的卡片」「与运营相关的卡片」等。

把本来被 LBS 地图一屏内抢占的空间通过简易的交互模式补偿回来了,这样既不打破用户的核心体验 focus 在地图与主卡上,同时又增强了运营、功能的玩法与拓展,可谓双赢。

根据这几年滴滴 xpanel 的线上应用,拓展卡片基本挖掘涵盖了以下场景的露出:优惠福利、出现卡券、会员体系、安全相关、出行提醒、拉新导流、运营活动等,未来可拓展的价值内容会更多。看着各路出行类 app 又纷纷长期沿用 xpanel 的设计,想必线上的数据反馈应该也是很正向的。

抖音-TopView

在上篇文章《多维度解析 | 抖音vs快手的产品设计策略差异》中的商业化模块里简要提及过抖音的 Topiew 超级广告位,这里单独拿出来和大家解析一下它究竟有多6。

从功能角度看,它是一个从开屏延续到端内视频信息流的广告位,占据了用户从进入抖音的第一视觉。

从交互角度看,topview 主要展现以开屏沉浸式视频 3s 播放→淡出互动转化组件 3s(完美融入原生视频信息流),剩余操作手势与功能等同原生视频信息流。

在这样一个有着 1 亿+第一曝光的产品位置,单纯只做常规静态开屏稳当入账不香吗?事实是抖音确实让它不香了,没有创新就没有新的收获。基于业务和当前产品形态下的交互模式使抖音有一个天时地利的优势——沉浸式体验,在这样的交互模式下给视频化的开屏提供了很好的承接入口。从开屏开启到融入信息流,在交互形态的切换中又为广告内容的播放时长赢得了更多时间。

更可怕的一点是 3s 播放后融入原生视频信息流中的 TopView 除了正常收割广告转化带来的单量,还可以通过右侧的主页链接轻松引流进行粉丝沉淀(今天就算你不买,先关注我,成为我的潜在用户,来日我再推一个新商品视频,你可以第一时间看见也许感兴趣就买单了)。

说完这些大家仔细回忆一下平常我们接触的有视频广告的视频平台,别说 60s、30s,15s 我们都嫌长,但为啥 TopView 显得相对没那么惹人烦呢(上次留的思考题)?个人认为除了抖音在选择合作品牌时会倾向符合平台气质的品牌合作(细数它合作过的品牌:Mac、宝马、林肯、vivo 等)保障广告质量和提供「跳过」外,直接融入信息淡出的互动组件会不仅会给用户新奇感,还会激发用户的互动欲望。

最后看一组数据(与宝马合作数据),曝光数:1.1 亿+;有效播放率:53.82%;点击率:13.26%。所以你猜一个最长可以展示 60s 的品牌视频内容、同时进行品牌粉丝沉淀、良好体验带来更高有效播放的亿级曝光广告位能值多少钱?

淘宝-二楼

2016 年淘宝启动了一个项目要做一款内容化栏目——以视频为主,每晚更新一期,类比「一千零一夜」的故事。

那么在满满当当的淘宝运营区里该选择哪一个来试玩这个有趣的「新栏目」呢?是在头部的 10 宫格里再挤进去一个图标呢?还是在热门推荐里挤出一个 tab 呢?还是做一个悬浮的右下角的运营位?显然都不太合适。

根据这款产品每晚 6 点钟才可以使用,早上 7 点就会消失的游戏规则,最适配它的入口是一个不占界面原生空间,同时又有一定仪式感的位置。于是下拉 loading 的大空区成为了设计师们考虑的阵地。

△ 不知道这个banner为什么要排挤我

但地方选好了,又有了新顾虑。因为 iOS 的用户基本被系统洗脑了下拉手势,对于他们来说下拉=刷新,贸然在下拉刷新的手势基础上再叠加一个无关联的结果显然是有风险的。因此从交互上需要界定 2 个维度的指标来保障新栏目的体验。

  • 下拉速度(速度临界值:速度多快?→刷新,多慢?→新栏目)——以速度为优先衡量指标(只要速度快,拉的距离再大也是→刷新)
  • 下拉距离(距离临界值:拉到多少距离进入新栏目?)——兼顾单手用户操作难度

反复试错 2 个指标数据的实际体验之后,新栏目有了安身之所,赐名「二楼」。进入「二楼」的整体交互和现在的短视频产品玩法基本雷同,全屏竖滑切换,小图标带货。下拉加载位的开发,从普通 loading 动效到运营位的植入基本被各类电商平台轻松复刻了,因此这一切看上去更没什么了得,但对于原创来说那毕竟是 4 年前。

豆瓣-叠加上滑板

谈到豆瓣我算是半个老用户了,豆瓣自身是个比较复杂的集合多条业务线分支(「小组」「同城」「阅读」「音影」……)的多生态产品,这里我们主要拿它 18 年 6.0 大改版中影音模块的详情页大改造来说事儿。

△ 可能有很多人已经忘记6.0前的豆瓣电影详情页长啥样了,带你回顾一下。

看完对比图,视力正常的朋友乍一看都能看出 6.0 版详情页整容得有多成功。但具体成功在哪里,可能不仅仅是好看这么简单。

大背景从海报上智能取色虽然不算是什么稀奇的做法,但是加了适度的渐变应用在这里也可以说是非常的恰到好处了。另外深底色和视觉比重加大的外链区都突显了「第三方播放」与「购票选座」的视觉感知。让用户沉浸在电影详情中并引导他们走向「豆瓣的主要收入来源之一——电影票分销与第三方视频播放产品引流」正好是 6.0 豆瓣改版一个「小小的目标」——更务实(商业化)。

从交互层面看,且不说评论头部吸底这个事情是不是也是因为 6.0 商业化的影响(评论区增加「话题」进行重点运营),这个交互本身我觉得还是很强大的。强大的体现在于良好的空间收纳能力与信息拓展能力。我给它起了个好听的名字叫-叠加上滑板(不好听也认了吧,毕竟也没有内部人员告诉我他们是不是起名字了)

这里可能又会有很多人质疑它与用户已洗脑的上滑手势之间的冲突,这点解释起来和上文淘宝「二楼」有些类似,区别是豆瓣并没有做上滑速度 or 距离的临界值,只是把滑动区域做了隔离。而对比它的效仿者 boss 直聘,人家倒是在交互上做了进一步优化,适配自己的产品情况做了上滑叠层卡隐藏和上滑距离临界值。

这个故事告诉我们,要抄也要抄得比人家的交互更优秀才不丢人昂。

文章来源:优设    作者:Nana的设计锦囊

前端实现生成带有样式的excel表格 Node和浏览器读写Excel文件探究实践

seo达人

最近碰到个需要自动生成表格的任务,作为前端的我,就想在 node 和浏览器中生成强大的表格,所以特此研究了很多关于表格的 npm 库

支持读写 Excel 的 node.js 模块

node-xlsx: 基于 Node.js 解析 excel 文件数据及生成 excel 文件,仅支持 xlsx 格式文件

js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格 XLSX / XLSM / XLSB / XLS / CSV,解析采用纯 js 实现,写入需要依赖 nodejs 或者 FileSaver.js 实现生成写入 Excel,可以生成子表 Excel,功能强大,但上手难度稍大。不提供基础设置 Excel 表格 api 例单元格宽度,文档有些乱,不适合快速上手;普通版本不支持定义字体、颜色、背景色等,有这个功能需要的可以使用 pro 版,是要联系客服收费的,害我照着 API 设置调试了好多次都失败。好在样式设置问题有一些教程,通过研究本人已解决,可设置宽度颜色等等,见根目录本人修改的 xlsx.js

xlsx-style 基于 xlsx 封装的样式库,可以在 xlsx 的基础上设置样式。样式不全,宽度都设置不了,好多年前作者就不维护了.宽度设置问题本人已解决了,见修改的 xlsx-style.js 文件

exceljs 在使用此库之前,本人已花费了很大的精力,用以上库做好了表格,但是发现不能设置页眉页脚,添加图片,打印选项设置等等,直到发现了这个库,文档齐全,功能强大,并且还免费.但是star较少,差一点就错过了。本教程主要针对这个库

代码库地址

https://github.com/lingxiaoyi/excel

安装

npm install


npm install -g nodemon


调试使用,替代 node 命令,实现保存文件,node 自动重新启动执行,必须全局安装才能运行


使用

nodemon app.js


js-xlsx 具体 api 使用方法请参考 main.js demo 使用,app.js 中修改为 require('./src/main.js');

exceljs 具体 api 使用方法请参考 main-exceljs.js demo 使用,app.js 中修改为 require('./src/main-exceljs.js');

因为每次生成完表格,每次都需要打开表格查看样式,在 windows 电脑中,打开表格之后就锁定不能生成新文件了,本来想着能导出一个 html 文件对应表格的样式


node 调试

vscode 中打开调试右侧设置编辑,将下方代码复制进去,点 nodemon 启动就可以进行 debug 调试了


{

     "type": "node",

     "request": "launch",

     "name": "nodemon",

     "runtimeExecutable": "nodemon",

     "program": "${workspaceFolder}/app.js",

     "restart": true,

     "console": "integratedTerminal",

     "internalConsoleOptions": "neverOpen",

     "skipFiles": ["<node_internals>/**"]

   },

webpack 目录的作用

每次生成完新表格,都需要重新打开表格查看样式,在 windows 电脑中,打开表格之后就锁定了,再次生成新表格就会报错,文件已锁定,不能写入,对于想偷懒的我,能不能实现像 webpack 热更新功能那种,修改样式 js 页面自动更新呢?


wps 自带另存 html 文件功能,但是没有提供生成的 api ,网上也搜索不到对应的转换功能,

本来以为自己要实现一套表格转 html 的功能。通过不断尝试,偶然间发现手机浏览器可以直接打开预览 xlsx 文件,内心狂喜啊


使用方法

进入 webpack 目录安装依赖包,安装好之后执行


npm run dev


启动成功之后,会自动打开带有 ip 地址的预览地址,此时在电脑浏览器会自动下载 xlsx 文件,忽略不管,用手机直接打开此地址,就能看到 xlsx 表格的内容了,并且每次新修改内容和样式,都会自动刷新页面显示新表格.


小技巧

谷歌浏览器插件:


生成二维码的插件生成二维码方便手机扫描

划词翻译 用来翻译一些看不懂的英文文档

browser 目录

浏览器中实现生成 xlsx 表格方法


进入 browser 目录安装依赖包,安装好之后执行


npm run dev


启动成功之后,拖动根目录 src 下的李四表格到页面上的输入框里,成功生成表格之后会生成一个下载链接地址,右键在新标签页打开链接,即会生成一个新的表格文件出来,完整 api 使用和 demo 文件请参考 index.js


vue 和 react 用法可以参考此例子,如果有必要也可以此版本库的例子


一些概念

在使用这个库之前,先介绍库中的一些概念。


workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。

worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。

cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。

xlsx 使用注意事项

constXLSX = require('xlsx');

let html = XLSX.utils.sheet_to_html(workbook.Sheets.Sheet1)

生成 html 的用法,并且不会有任何样式


exceljs 使用注意

读取文件问题

因为 exceljs 读取文件不支持 sync 同步读取,给的实例也是 await 例子.导致我读取完遇到一个问题,就是老是生成不成功,最后发现必须要把所有逻辑全部放入函数中,像下方这样


(async function (params) {

 let res = await workbook.xlsx.readFile(`${__dirname}/赵六.xlsx`);

 //执行所有数据处理逻辑

 //执行写的逻辑

 workbook.xlsx.writeFile(path.resolve(__dirname, '../webpack/test222.xlsx'));

});

所有逻辑全部要写入这个函数中,这样本来是可以的,但是出错调试几率较大,并且读取到的数据庞大还需要额外处理,所以我读取数据逻辑就用的 node-xlsx,十分简单方便,如果你用的 exceljs 读取文件数据出现问题,大概率是异步同步逻辑搞错了,多加注意即可

宽度设置

列宽不知道是以什么为单位,反正不是像素(已测量),例子中是以厘米为单位再乘以 4.7 的结果设置的,4.7 是不断测试的结果.

快捷查看列宽的方法,打开 wps 表格,长按列与列字母间的竖线,就能看到列宽,取厘米的单位即可.见下图




前景色

前景色设置必须右键单元格选择设置单元格格式,然后选择图案样式选择颜色,就可以前景色填充

worksheet.getCell('A2').fill = { type: 'pattern', pattern:'darkTrellis', fgColor:{argb:'FFFFFF00'}, bgColor:{argb:'FF0000FF'} };


背景色

worksheet.getCell('A2').fill = { type: "pattern", pattern: "solid", fgColor: { argb: next.bgColor }, }


排版不一致的问题

解决 Mac 下编辑 Microsoft Office Word 文档与 Windows 排版不一致的问题,,不同的系统用 wps 打开相同的表格,打印预览的时候,表格宽度显示不一样

问题详细说明地址


我的解决办法就是 mac 下显示正常,按 mac 下的宽度来设置就可以了


参考资料

exceljs

node-xlsx

js-xlsx

函数节流与函数防抖的区别

seo达人

函数节流与函数防抖是我们解决频繁触发DOM事件的两种常用解决方案,但是经常傻傻分不清楚。。。这不,在项目中又用遇到了,在此处记录一下



函数防抖 debounce

原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次!!!!!!!!!!)。



举个栗子:滚动scroll事件,不停滑动滚轮会连续触发多次滚动事件,从而调用绑定的回调函数,我们希望当我们停止滚动的时,才触发一次回调,这时可以使用函数防抖。



原理性代码及测试:



// 给盒子较大的height,容易看到效果

<style>

    * {

        padding: 0;

        margin: 0;

    }



    .box {

        width: 800px;

        height: 1200px;

    }

</style>

<body>

    <div class="container">

        <div class="box" style="background: tomato"></div>

        <div class="box" style="background: skyblue"></div>

        <div class="box" style="background: red"></div>

        <div class="box" style="background: yellow"></div>

    </div>

    <script>

        window.onload = function() {

            const decounce = function(fn, delay) {

                let timer = null



                return function() {

                    const context = this

                    let args = arguments

                    clearTimeout(timer) // 每次调用debounce函数都会将前一次的timer清空,确保只执行一次

                    timer = setTimeout(() => {

                        fn.apply(context, args)

                    }, delay)

                }

            }



            let num = 0



            function scrollTap() {

                num++

                console.log(看看num吧 ${num})

            }

            // 此处的触发时间间隔设置的很小

            document.addEventListener('scroll', decounce(scrollTap, 500))

            // document.addEventListener('scroll', scrollTap)

        }

    </script>

</body>



此处的触发时间间隔设置的很小,如果匀速不间断的滚动,不断触发scroll事件,如果不用debounce处理,可以发现num改变了很多次,用了debounce函数防抖,num在一次上时间的滚动中只改变了一次。



调用debouce使scrollTap防抖之后的结果:



直接调用scrollTap的结果:





补充:浏览器在处理setTimeout和setInterval时,有最小时间间隔。

setTimeout的最短时间间隔是4毫秒;

setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒。

事实上,未优化时,scroll事件频繁触发的时间间隔也是这个最小时间间隔。

也就是说,当我们在debounce函数中的间隔事件设置不恰当(小于这个最小时间间隔),会使debounce无效。



函数节流 throttle

原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率



举个栗子:还是以scroll滚动事件来说吧,滚动事件是及其消耗浏览器性能的,不停触发。以我在项目中碰到的问题,移动端通过scroll实现分页,不断滚动,我们不希望不断发送请求,只有当达到某个条件,比如,距离手机窗口底部150px才发送一个请求,接下来就是展示新页面的请求,不停滚动,如此反复;这个时候就得用到函数节流。



原理性代码及实现



// 函数节流 throttle

// 方法一:定时器实现

const throttle = function(fn,delay) {

  let timer = null



  return function() {

    const context = this

    let args = arguments

    if(!timer) {

      timer = setTimeout(() => {

        fn.apply(context,args) 

        clearTimeout(timer) 

      },delay)

    }

  }

}



// 方法二:时间戳

const throttle2 = function(fn, delay) {

  let preTime = Date.now()



  return function() {

      const context = this

      let args = arguments

      let doTime = Date.now()

      if (doTime - preTime >= delay) {

          fn.apply(context, args)

          preTime = Date.now()

      }

  }

}



需要注意的是定时器方法实现throttle方法和debounce方法的不同:



在debounce中:在执行setTimeout函数之前总会将timer用setTimeout清除,取消延迟代码块,确保只执行一次

在throttle中:只要timer存在就会执行setTimeout,在setTimeout内部每次清空这个timer,但是延迟代码块已经执行啦,确保一定频率执行一次




我们依旧可以在html页面中进行测试scroll事件,html和css代码同debounce,此处不赘述,运行结果是(可以说是一场漫长的滚轮滚动了):





最后再来瞅瞅项目中封装好的debounce和throttle函数,可以说是很优秀了,考虑的特别全面,希望自己以后封装的函数也能考虑的这么全面吧,加油!



/*

 
空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行

 

 
@param  {function} func        传入函数,最后一个参数是额外增加的this对象,.apply(this, args) 这种方式,this无法传递进函数

  @param  {number}   wait        表示时间窗口的间隔

 
@param  {boolean}  immediate   设置为ture时,调用触发于开始边界而不是结束边界

  @return {function}             返回客户调用函数

 
/

const debounce = function(func, wait, immediate) {

    let timeout, args, context, timestamp, result;



    const later = function() {

        // 据上一次触发时间间隔

        let last = Number(new Date()) - timestamp;



        // 上次被包装函数被调用时间间隔last小于设定时间间隔wait

        if (last < wait && last > 0) {

            timeout = setTimeout(later, wait - last);

        } else {

            timeout = null;

            // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用

            if (!immediate) {

                result = func.call(context, ...args, context);

                if (!timeout) {

                    context = args = null;

                }

            }

        }

    };



    return function(..._args) {

        context = this;

        args = _args;

        timestamp = Number(new Date());

        const callNow = immediate && !timeout;

        // 如果延时不存在,重新设定延时

        if (!timeout) {

            timeout = setTimeout(later, wait);

        }

        if (callNow) {

            result = func.call(context, ...args, context);

            context = args = null;

        }



        return result;

    };

};



/*

 
频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait

 

 
@param  {function}   func      传入函数

  @param  {number}     wait      表示时间窗口的间隔

 
@param  {object}     options   如果想忽略开始边界上的调用,传入{leading: false}。

                                 如果想忽略结尾边界上的调用,传入{trailing: false}

 
@return {function}             返回客户调用函数

 */

const throttle = function(func, wait, options) {

    let context, args, result;

    let timeout = null;

    // 上次执行时间点

    let previous = 0;

    if (!options) options = {};

    // 延迟执行函数

    let later = function() {

        // 若设定了开始边界不执行选项,上次执行时间始终为0

        previous = options.leading === false ? 0 : Number(new Date());

        timeout = null;

        result = func.apply(context, args);

        if (!timeout) context = args = null;

    };

    return function(..._args) {

        let now = Number(new Date());

        // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。

        if (!previous && options.leading === false) previous = now;

        // 延迟执行时间间隔

        let remaining = wait - (now - previous);

        context = this;

        args = _args;

        // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口

        // remaining大于时间窗口wait,表示客户端系统时间被调整过

        if (remaining <= 0 || remaining > wait) {

            clearTimeout(timeout);

            timeout = null;

            previous = now;

            result = func.apply(context, args);

            if (!timeout) context = args = null;

            //如果延迟执行不存在,且没有设定结尾边界不执行选项

        } else if (!timeout && options.trailing !== false) {

            timeout = setTimeout(later, remaining);

        }

        return result;

    };

};


超简单入门Vuex小示例

seo达人

写在前面

本文旨在通过一个简单的例子,练习vuex的几个常用方法,使初学者以最快的速度跑起来一个vue + vuex的示例。

学习vuex需要你知道vue的一些基础知识和用法。相信点开本文的同学都具备这个基础。

另外对vuex已经比较熟悉的大佬可以忽略本文。

生成基于vue的项目

基于vue-cli脚手架生成一个vue项目
常用npm命令:

npm i vue-vli -g vue --version vue init webpack 项目名 

进入项目目录,使用npm run dev先试着跑一下。

一般不会出现问题,试跑成功后,就可以写我们的vuex程序了。

使用vue完成的示例

使用vuex首先得安装vuex,命令:

npm i vuex --save

介绍一下我们的超简单Demo,一个父组件,一个子组件,父组件有一个数据,子组件有一个数据,想要将这两个数据都放置到vuex的state中,然后父组件可以修改自己的和子组件的数据。子组件可以修改父组件和自己的数据。

先放效果图,初始化效果如下:

如果想通过父组件触发子组件的数据,就点“改变子组件文本”按钮,点击后效果如下:

如果想通过子组件修改父组件的数据,就在子组件点击“修改父组件文本”按钮,点击后效果如下:

代码文件介绍

首先是Parent.vue组件

<template> <div class="parent"> <h3>这里是父组件</h3> <button type="button" @click="clickHandler">修改自己文本</button> <button type="button" @click="clickHandler2">修改子组件文本</button> <div>Test: {{msg}}</div> <child></child> </div> </template> <script> import store from '../vuex' import Child from './Child.vue' export default { computed: {
            msg(){ return store.state.testMsg;
            }
        }, methods:{
            clickHandler(){
                store.commit('changeTestMsg', '父组件修改自己后的文本')
            },
            clickHandler2(){
                store.commit('changeChildText', '父组件修改子组件后的文本')
            }
        }, components:{ 'child': Child
        },
        store,
    } </script> <style scoped> .parent{ background-color: #00BBFF; height: 400px;
    } </style> 

下面是Child.vue子组件

<template> <div class="child"> <h3>这里是子组件</h3> <div>childText: {{msg}}</div> <button type="button" @click="clickHandler">修改父组件文本</button> <button type="button" @click="clickHandler2">修改自己文本</button> </div> </template> <script> import store from '../vuex' export default { name: "Child", computed:{
            msg(){ return store.state.childText;
            }
        }, methods: {
            clickHandler(){
                store.commit("changeTestMsg", "子组件修改父组件后的文本");
            },
            clickHandler2(){
                store.commit("changeChildText", "子组件修改自己后的文本");
            }
        },
        store
    } </script> <style scoped> .child{ background-color: palegreen; border:1px solid black; height:200px; margin:10px;
    } </style> 

最后是vuex的配置文件

 import Vue from 'vue' import Vuex from 'vuex';

Vue.use(Vuex) const state = { testMsg: '原始文本', childText:"子组件原始文本" } const mutations = {
    changeTestMsg(state, str){
        state.testMsg = str;
    },
    changeChildText(state, str){
        state.childText = str;
    }

} const store = new Vuex.Store({ state: state, mutations: mutations
}) export default store;

后记

通过该vuex示例,了解vuex的常用配置及方法调用。希望对不怎么熟悉vuex的同学快速上手vuex项目有点帮助。

因为没太多东西,我自己也是刚接触,本例就不往GitHub扔了,如果尝试了本例,但是没有跑起来的同学,可以一起交流下。

为何深色模式看起来不自然?

分享达人

为何深色模式看起来不自然?它的实用性到底怎么样?深色模式是不是对眼睛更健康?结合文献,我们一起来探究深色模式的种种!


在过去的几年中,深色模式一直是用户最期待的一个功能。你可以自由切换你喜欢的模式来适应当前的场景,iOS和Android也都在2019年布局了系统级的深色模式,深色模式可获得出色的视觉体验,尤其是在弱光环境中……有助于你专注地开展工作,因为内容会较为显眼,而颜色加深的控制项和窗口则会隐入背景之中。但真是这样吗,或者只是一种实际上弊大于利的操作? 


文章内容:


1、什么是深色模式

2、从可用性角度看深色模式

3、为什么深色模式看起来不自然

4、从设计角度看深色模式

5、结论


什么是深色模式?


虽然各种软件界面的色调和颜色会有所不同,但是对深色模式的处理方式都是在深色背景上显示明亮文本和界面元素的配色方案。相比之下,在浅色背景上显示深色文本和界面元素的配色方案,在这里我们称之为浅色模式。


而事实上,深色模式比浅色模式早了几十年。在其最初的阶段,深色模式并不是一种有意的解决方案,而仅仅是当时计算显示设备是阴极射线管(CRT),且只能显示单色的显示,屏幕看上去就是黑的,黑底白字或者黑底绿字就是计算机行业的早期状态。直到1980年代彩色显示器发明后,微软视窗系统上线之后,黑底显示才退出主流。苹果在1984年推出“麦金塔”个人电脑,第一次发布采用图形用户界面,由此开启了计算机屏幕白底显示的主流之路。


           

          

 ibm 5151单色监视器


浅色模式出现的确切时间很难确定,但可以追溯到施乐Parc图形用户界面,它也极大地影响了早期苹果的“麦金塔”和其他操作系统,该界面使用了以白色背景为主的深色文本和界面元素。它与显示技术的进步和现代图形用户界面的出现密切相关。这种能够显示色彩的且更先进的RGB CRT显示器拉开了浅色模式的序幕。


                       

1973年的施乐Alto是最早使用轻型接口模式的计算机之一


从可用性角度看深色模式


深色模式的实用性有多少,每个人看法也不一样。尽管深色模式的界面可能不像浅色模式那样观感清晰,但深色模式的界面却具有一定的魅力,可以让你联想到新颖,高端,精致,神秘,力量,奢华这些词。但是,黑色也是一种会引起人们强烈的情感一个颜色,并且在过度使用时很容易使人无法承受。


2019年9月,苹果公司上线深色模式(Dark Model)时,在官网上如是宣传道。从iPhone到Mac,当库克决定在苹果公司几乎所有产品上线深色模式、甚至要求所有在AppStore上架的应用都必须兼容深色模式时,安卓阵营的谷歌、华为、三星等头部公司也纷纷在其手机中推出了深色模式,相应地从WhatsApp到微信等全球主流的应用也都推出了深色模式。



            


但是,想要获得良好的深色模式是相对比较难的。一方面,深色模式迫使放大瞳孔来捕获必要的视觉信息,从而导致整体清晰度的降低。而同时界面中高亮的部分又迫使我们的瞳孔缩小去适应亮度,以便提高清晰度。这也就能理解为什么白色背景上的黑色文本在你的眼睛中通常会显得更清晰的原因。


             

扩大的瞳孔让光线更多,但感觉到的锐度受到损害



行业对深色模式追逐的群羊效应,也进一步加剧、放大了一个见解:深色模式对眼睛更友好更健康!


但是,事实并非如此。对于一些有散光的人来说,在某种程度上深色模式对他们的眼睛来说更糟糕,深色模式比浅色模式更友好更健康的科学仍有待讨论。斯坦福·拜尔斯眼科研究所的眼科专家对此表示:“鉴于文献中的数据,我认为深色模式对眼睛没有任何的友好和健康。” 这种深色模式对眼睛疲劳和潜在的眼睛健康的影响时,使用时间可能比设备的亮度或亮度更重要。


根据美国验光协会的说法称,大多数人的眼睛在某种程度上都患有散光,但通常不会引起注意。据美国眼科学院统计,每三个美国人中就一个存在散光的情况,1.5亿美国人需要佩戴眼镜来矫正视力;香港理工大学针对2700多人的临床检测发现,在21岁的30岁的香港人中,近40%以上患有100度以上散光。


一起看下面的说明性图像。即使你有完美的视觉,你也很可能在黑色背景上看到白色文字周围的光晕。



            

浅色模式与深色模式


如果增加文字并降低文字的大小,这种效果可能会更强:


             


右边的图像你应该会看到更多的光晕,如果你有散光症状,深色模式可能会让你看屏幕更费劲。在深色模式下,虹膜打开以接收更多的光,并且瞳孔的变形在眼睛上产生了更加模糊的焦点,因此,当你在深色屏幕上看到浅色文本时,其边缘似乎渗入黑色背景,也就是所谓的“光晕效果”。虽然深色模式可能更适合夜晚等弱光环境下使用,但不一定能帮助更好地阅读,对于散光患者来说,可能还会加剧视疲劳。


当然,从实用性上讲,深色模式也有它的好处,深色模式利用OLED屏幕在纯黑的背景下不发光的特性,确实能有效的省电,让你的电子设备续航上提升很大,这也是很多人一直在追求深色模式的一个重要原因,只要手机续航强比啥都重要!这也可能是苹果公司决定在几乎所有产品上线深色模式的一个原因。


另外深色模式更有利于给用户营造一种沉浸体验,对于视觉娱乐应用尤为如此。当你想突出显示特定类型的内容时,深色模式会特别有用。豆瓣、数字尾巴、网易云音乐是我常用的几个软件,它们都已经适配了深色模式。在这种模式下你的目光会更加注意到电影的海报、数码产品和充满活力的音乐专辑上。



             

豆瓣、数字尾巴、网易云音乐的深色模式


为什么深色模式看起来不自然


一是由于我们人脑的组织结构造成的,从多年来的多项科学研究和调查得出的结论是,从物种进化来看,人类99%的时间都是在白天中活动,人脑更倾向于在浅的背景上显示深色的图像。所以无论白天还是黑夜,浅色的背景都可以让你更快地专注于显示的元素,而深色的背景则使其难以辨别文字和视觉界面元素,从而影响你的阅读效果并最终使你的眼睛疲劳。 其实从世界各地多个洞穴中发现的史前壁画也能说明为什么我们倾向于喜欢浅色模式。



            

追逐猎物的狮子,法国Chauvet Cave,约公元前30,000-28,000


德国帕绍大学曾经做过一次测试。在该研究要求参与者阅读屏幕上的正极性(白色背景上的黑色文本)或负极性(黑色背景上的白色文本)的文本。随后,参与测试的人员会执行基本的校对任务,例如查找拼写或语法错误。研究人员还测量了每种模式下参与者的阅读速度。结果是所有参与者在正极性条件下的表现都会更好,他们检查出更多的错误以及阅读的速度更快。


                       

可读性差异


另一个学习发现正极性对于在显示器上读取小文本特别有利。人脑具有更快的可读性,更喜欢在光线背景下显示的深色文本和物体。


二是由于含有大量蓝光的光源会使我们眼睛不舒服,当我们谈论屏幕对眼睛的潜在破坏性影响时,我们通常是在谈论“蓝光”,这是由短、高能量波长构成的光谱的一部分。研究发现蓝光可能是导致眼睛疲劳的一个因素,但指出长时间不眨眼的干眼也是导致眼睛疲劳的一个更严重的原因,当然也有是因为字体太小,以及散光这样的原因。


当我们身处暗室或是在黄昏或夜晚时,眼睛会切换成不同的视觉模式;在弱光环境下,人眼会从对绿色敏感变成对高能量蓝光敏感,这代表我们在大量的接收蓝光,因此对刺眼强光的敏感度会增强。这类情形对驾驶人而言并不陌生,当他们被来车车头灯的强光照射时,特别是使用现代化氙气灯或LED头灯的车辆,可能会暂时丧失视力。


           

平板电脑、智能手机和其他电子显示屏,不仅改变了我们所接触的光谱,也使我们的视觉行为发生转变。我们必须意识到,我们现在用于“近距离”视物的时间比以往多得多,这通常是因为背景亮度太暗所致。


在德国光学公司蔡司官方网站上,对于蓝光也作一分为二的评价:“好处是当外界环境变亮也就是蓝光较多时,身体便释放出血清素—它是其中一种快乐荷尔蒙以及皮质醇—这是一种压力荷尔蒙。这两种荷尔蒙能让我们保持清醒,富有活力,同时也应用于冬季抑郁和失眠的治疗中。但过多的紫外光和蓝紫光可能会对肉眼造成损伤,除了可能导致令人难受的结膜和角膜发炎,也可能会破坏眼睛的晶状体(例如白内障),尤其是伤害我们的视网膜(黄斑病变)。”


从设计角度看深色模式


在WWDC 2019大会上,苹果宣布了iOS13的深色模式功能,在令人兴奋之余,作为设计师和开发人员,我们应该考虑的该如何去实现它。苹果和安卓已经发布了为应用程序设计深色模式的设计指南。当然,没有硬性规定要求遵循他们提供的设计指南,这些只是指导原则。


             

由于Material Design设计语言的原因,投影的占比是非常大的。在浅色模式下还好,但这不太适用于深色模式,因为深色背景上的黑色阴影在视觉上不容易察觉,为此安卓还提供了在深色模式下不同层级的卡片与投影上的参考。


            

根据设计文档来看,iOS背景为纯黑色,色值为#000000,Google 则更喜欢深灰色,色值为#121212。


            

通过提供的设计指南,我们可以轻易上手来设计和开发我们的软件,但要注意的是深色模式并不是简单的与浅色模式颜色对调,必须为所有的元素进行单独配色。


            

浅色模式下的白色不会在深色模式下转换成纯黑色


这样也就能理解为什么很多软件并没有全部去适配新的深色模式,一方面使用场景决定的,另一方面就是深色模式并不是简单地换个换个颜色就行,很多元素需要重新设计和开发。


结论:该选择哪种模式


在去年的 WWDC 大会上,苹果人机交互团队的设计师曾对 macOS 的深色模式使用场景做了进一步的解释。

他指出,只有阅读浏览或是内容创作型 App 才需要长期启用深色模式,比如文字或代码编辑。它们会借助黑底白字的高对比度特性来让用户视线保持集中,其它大部分软件对于深色模式的需求反而并不强烈。


或则你可以通过使用场景去选择,在明亮的环境中使用浅色模式,在昏暗的环境中使用深色模式。



            

但是在大多数情况下,真正帮助你避免眼睛疲劳的是不要整天盯着屏幕,而不是纠结到底用深色还是浅色模式。

转自:站酷-


移动端列表查询最佳实践

seo达人

无论是 pc 端还是移动端,无可避免都会涉及到列表查询有关的操作,但对于这两种不同的设备,其列表查询的最佳处理方式也是完全不同。

对于 pc 端列表查询来说,前端通常是给与服务端当前需要获取的数据量(如 pageCount,limit 等参数)以及所需要获取数据的位置(如 pageSize,offset 等参数)作为查询条件。然后服务端然后返回数据总数,以及当前数据,前端再结合这些数据显示页面总数等信息。这里我称为相对位置取数。

对于移动端而言,没有pc 端那么大的空间展示以及操作,所以基本上都会采用下拉取数这种方案。

那么我们在处理移动端列表查询时候使用这种相对位置取数会有什么问题呢?

相对位置取数存在的问题

性能劣势

通过相对位置取数会具有性能问题,因为一旦使用 offset 信息来获取数据,随着页数的增加,响应速度也会变的越来越慢。因为在数据库层面,我们每次所获取的数据都是“从头开始第几条”,每次我们都需要从第一条开始计算,计算后舍弃前面的数据,只取最后多条数据返回前端。

当然了,对于相对位置取数来说,数据库优化是必然的,这里我就不多做赘述了。对于前端开发来说,优秀的的查询条件设计可以在一定方面解决此问题。

数据显示重复

事实上,对于一个实际运行的项目而言,数据更新才是常态,如果数据更新的频率很高或者你在当前页停留的时间过久的话,会导致当前获取的数据出现一定的偏差。

例如:当你在获取最开始的 20 条数据后,正准备获取紧接着的后 20 条数据时,在这段时间内 ,发生了数据增加,此时移动端列表就可能会出现重复数据。虽然这个问题在 pc 端也存在,但是 pc 端只会展示当前页的信息,这样就避免了该问题所带来的负面影响。

结合列表 key 维持渲染正确

我们在上面的问题中说明了,移动端下拉加载中使用相对位置查询取数是有问题的。

那么,如果当前不能迅速结合前后端进行修改 api 的情况下,当服务端传递过来的数据与用户想要得的数据不一致,我们必须在前端进行处理,至少处理数据重复问题所带来的负面影响。

因为当前分页请求时无状态的。在分页取到数据之后前端可以对取得的数据进行过滤,过滤掉当前页面已经存在的 key(例如 id 等能够确定的唯一键)。

通过这种处理方式,我们至少可以保证当前用户看到的数据不会出现重复。同时当列表数据可以编辑修改的时候,也不会出现因为 key 值相同而导致数据错乱。

通过绝对位置获取数据

如果不使用相对位置获取数据,前端可以利用当前列表中的最后一条数据作为请求源参数。前端事先记录最后一条数据的信息。例如当前的排序条件为创建时间,那么记录最后一条数据的创建时间为主查询条件(如果列表对应的数据不属于个人,可能创建时间不能唯一决定当前数据位置,同时还需要添加 ID 等信息作为次要查询条件)。

当我们使用绝对位置获取数据时候,虽然我们无法提供类似于从第 1 页直接跳转 100 页的查询请求,但对于下拉加载这种类型的请求,我们不必担心性能以及数据重复显示的问题。

对于相对位置取数来说,前端可以根据返回数据的总数来判断。但当使用绝对位置取数时,即使获取数据总数,也无法判断当前查询是否存在后续数据。

从服务器端实现的角度来说,当用户想要得到 20 条数据时候,服务端如果仅仅只向数据库请求 20 条数据,是无法得知是否有后续数据的。服务端可以尝试获取当前请求的数据条数 + 1, 如向数据库请求 21 条数据,如果成功获得 21 条数据,则说明至少存在着 1 条后续数据,这时候,我们就可以返回 20 条数据以及具有后续数据的信息。但如果我们请求 21 条数据却仅仅只能获取 20 条数据(及以下),则说明没有后续数据。

如可以通过 “hasMore” 字段来表示是否能够继续下拉加载的信息。

{ data: [], hasMore: true }

结合 HATEOAS 设计优化

事实上,前面我们已经解决了移动端处理列表查询的问题。但是我们做的还不够好,前端还需要结合排序条件来处理并提供请求参数,这个操作对于前端来说也是一种负担。那么我们就聊一下 HATEOAS 。

HATEOAS (Hypermedia As The Engine Of Application State, 超媒体即应用状态引起) 这个概念最早出现在 Roy Fielding 的论文中。REST 设计级别如下所示:

  • REST LEVEL 0: 使用 HTTP 作为传输方式
  • REST LEVEL 1: 引入资源的概念(每一个资源都有对应的标识符和表达)
  • REST LEVEL 2: 引入 HTTP 动词(GET 获取资源/POST 创建资源/PUT 更新或者创建字样/DELETE 删除资源 等)
  • REST LEVEL 3: 引入 HATEOAS (在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作)

HATEOAS 会在 API 返回的数据中添加下一步要执行的行为,要获取的数据等 URI 的链接信息。客户端只要获取这些信息以及行为链接,就可以根据这些信息进行接下来的操作。

对于当前的请求来说,服务端可以直接返回下一页的信息,如

{ data: [], hasMore: true, nextPageParams: {}    
}

服务端如此传递数据,前端就不需要对其进行多余的请求处理,如果当前没有修改之前的查询以及排序条件,则只需要直接返回 “nextPageParams” 作为下一页的查询条件即可。

这样做的好处不但符合 REST LEVEL 3,同时也减轻了前端的心智模型。前端无需配置下一页请求参数。只需要在最开始查询的时候提供查询条件即可。

当然,如果前端已经实现了所有排序添加以及查询条件由服务端提供,前端仅仅提供组件,那么该方案更能体现优势。 前端是不需要知道当前业务究竟需要什么查询条件,自然也不需要根据查询条件来组织下一页的条件。同时,该方案的输入和输出都由后端提供,当涉及到业务替换( 查询条件,排序条件修改)时候,前端无需任何修改便可以直接替换和使用。

其他注意事项

一旦涉及到移动端请求,不可避免的会有网络问题,当用户在火车或者偏远地区时候,一旦下拉就会涉及取数,但是当前数据没有返回之前,用户多次下拉可能会有多次取数请求,虽然前端可以结合 key 使得渲染不出错,但是还是会在缓慢的网络下请求多次,无疑雪上加霜。这时候我们需要增加条件变量 loading。

伪代码如下所示:

// 查询 function search(cond) {
  loading = true api.then(res => {
      loading = false }).catch(err => {
      loading = false })
} // 获取下一页数据 function queryNextPage() { if (!nextPageParams) return if (!loading) return search(nextPageParams)
}

日历

链接

个人资料

蓝蓝 http://www.lanlanwork.com

存档