马士基前端参考标准
前端安全注意事项
对于内部应用(在安全网络环境,为马士基内部提供服务),我们仅需要遵循前三条安全要求。
对于面向外部客户的应用尤其涉及订票与支付的高风险场景,请务必仔细阅读与遵循安全注意事项。
基本安全要求
用户信息传输时应该进行加密,避免明文传输。
如下图场景,传输用户名和密码时应该加密后再加入请求头,直接明文传输很容易被劫持盗取用户信息。
在大多数场景下使用与后台协商一致的对称加密即可。
对可输入组件进行长度限制,前端先进行校验(输入过滤)后再发送请求。
比如密码和用户名输入框最大长度为 50 一般就足够了,电话号码输入框仅可以输入数字,邮件输入框要先进行格式校验,在其他 input/search 或者 textArea 也应当过滤掉无意义的非法字符,避免 XSS 注入攻击即黑客使用注入脚本攻击马士基后端。
谨慎使用 cookie,不需要与后台通信时使用 local storage。
cookie 在每次 HTTP 请求时都会被带上,在这里存储用户敏感信息是非常不安全的,当用户访问其他网站时也可能被劫持该用户在马士基网站的 cookie 信息。一般情况下 cookie 仅用于前后台通信场景,存储前台需要的数据时 local storage 是更安全高效的方式,仅单次会话需要通信的信息请存储在 session storage。
自定义找不到页面(404 页)
在外部应用中自定义一个 404 页面是十分必要的,不应该直接使用浏览器自带的找不到页面。当用户输入不正确的 URL 或者查询的资源过期时我们应该提供一个页面不存在的模糊提示,如下图马士基官网的 404 页。
直接使用浏览器错误页面并进行详细错误提示会导致黑客可以根据报错信息找到我们的文件组织结构并进行攻击,尤其是直接暴露服务器报错会为攻击者提供更多信息。
在代码打包时应该压缩并加入混淆
压缩后的代码占用内存更少,运行速度更快,而混淆代码是为了防止黑客窃取我们的前端代码。需要注意的是常用的压缩工具比如 Uglify.js 默认设置下不会进行代码混淆需要手动设置 mangle 选项。另外请在产品环境下关闭 warning 和 sorceMap。
在制作表单时需要防范跨站请求仿造攻击(CSRF),在请求时使用 toeken 并加入 original/referer header。黑客可以窃取 cookie 仿造用户行为修改用户密码进而盗取账号,因此我们需要校验请求发送的信息,但由于 header 也可以被篡改,前后台验证 toeken 是更安全的防范方式。
外部应用 API 请求时尽量使用 Wei Fu Lei 创建维护的阿里云后台进行中转,加入白名单黑名单,访问端口号限制等常用安全策略,进一步提升安全性。
在使用 CDN 时,请使用内容安全策略 content-security-policy 的 HTTP 请求头。
在设计外部应用表单等需要用户输入的功能时,应该设计错误提交上限和重复提交上限。让用户等待几分钟后再试或者进行人机校验即加入谷歌常见的选择图片等验证你不是在使用脚本的模块,以避免恶意提交 DOS 攻击。对于同一 IP 的页面请求与刷新也应该进行后台安全限制。
错误与异常处理
我们在设计一个网页或者应用程序时错误的处理是十分重要的,要根据不同情况给用户清晰的错误提示,并且确保覆盖了所有可能性。
我们在设计前台错误处理时至少要考虑如下情况
- 是否在设计时考虑到了所有异常的情况,前台是否覆盖了后台所有错误码
- 如果在用户操作过程中发生异常,如 API 请求超时如何处理
- 如果后台 API 崩溃或者发生了未知异常通用的错误处理是什么样的
- 是否需要自定义找不到页面,使用浏览器自带的话提供什么样错误提示
- [ ] 下面的一系列图片为马士基应用程序的部分错误处理,可以从中选取实际项目需要的部分。
左图为通用报错,API 崩溃或者发生不在前端处理列表的错误跳转到这一页面。(几乎所用应用都需要设计一个通用报错页面来处理未知异常)
已经进入列表 overview 页面点击 container 时或者刷新时后台 API 超时或报错,提示如右图。


如果运单号不符合规范(集装箱编号由 4 个字母和 7 个数字组成。运输单证编号(B/L)由 9 个字符组成),前端校验不通过,不发送请求,返回左图弹出框 Please enter a valid B/L or Container number。
如果用户查询的编号没有记录即{"error": "No tp doc found"}或者这个编号已经过期无法显示即 {"error": "No containers available within the display window dates"}则返回右边弹出框 No results found。


下面左图为用户提交太多次之后的错误提示,详见基本安全要求第九条。
右图为表单正常的错误提示,从用户体验出发建议前台校验的错误提示在用户输入后就显示,而不需要等到提交后一起提示。


注意左图的网络报错,尤其在移动端应用中应该加入用户网络问题的报错,用户无信号或者网速过慢打不开我们的应用可以做出提示。
右图为提交搜索表单的两种报错,第一个为 From 和 To 没填写被标注成红色,第二在搜索框下方提前提示用户需要至少输入两个字母(当我们设计有特殊规则的搜索框和密码输入框时最好把规则提前写在下面而不是提交后再报错显示)


- 下图是为用户设计的问题提交模块,如果用户在使用应用中遇到了任何技术问题或者有建议都可以通过这个模块反馈提交给我们。此外对于已经登陆的用户可以在应用中留下技术和业务支持的联系方式。
[ ] 此外前端取 API 字段时应该先判断前置字段是否存在,以及在外层和通用函数多使用 try catch,避免崩溃。
比如 shipment:{today:{number:012345}}这一数据结构,我们取 number 时,应该先判断有没有 shipment 和 today 这两个对象,避免后台数据问题导致前台直接报错崩溃。
下图为在处理 URL 时遇到错误 catch 进行打印仍旧返回未处理的 URL 保证页面正常运行。

基础样式
间隔和 Breakpoints
我们建议您使用 4px 作为最小单位,在组件长宽和间距上尽量使用四的倍数。
小于 768px 采用手机端布局,768px-992px 平板竖屏和手机横屏,992px-1200px 平板横屏,1200px-1920px 为电脑端。
在手机端字体建议使用 16px,其他端为 24px。
由于 web 应用程序可以在各个端访问,即使是主要提供给电脑端客户的应用也请尽量保证在小屏布局不混乱,或者在手机端提示用户用电脑访问页面。

颜色部分
马士基网站主体颜色为蓝白色,请参阅网站https://www.maersk.com/进行颜色设计。
配色参考如下:
按钮 Button:
普通状态 Blue-900-Dark
被划过 hover 状态 Blue-800
被选中 Active 状态 Blue-900-Dark
被禁用 disable 状态 rgba(0, 36, 61, .5) 半透明 Blue-900-Dark
输入框 Input:
普通状态 Grey-400
被选中 focus 和 active 状态 border-color: Blue-400; box-shadow: 0 0 0 3px #b5e0f5;(Info-Light)
被划过 hover 状态 border-color: Grey-600;
错误 error 状态 border-color: Danger;
被禁用 disable 状态 Grey-500
原形和方形 radio 勾选框
选中颜色为 box-shadow: 0 0 0 3px #b5e0f5;(Info-Light) border-color: Blue-400;
请参阅下面列出的颜色进行设计和开发,并参照马士基各个组件的配色











字体
请使用文件夹内的马士基字体和图标,当字体不能被访问,备选字体使用 Arial。
在各个端标题都适用 Maersk Headline,内容则使用 Maersk Text。
电脑端推荐标题大小:
Size: 80px;Line height: 80px;Weight: 300;Style: Normal
Size: 50px;Line height: 56px;Weight: 300;Style: Normal
Size: 40px;Line height: 40px;Weight: 300;Style: Normal
Size: 26px;Line height: 32px;Weight: 400;Style: Normal
Size: 20px;Line height: 24px;Weight: 400;Style: Normal
手机端推荐标题大小:
Size: 56px;Line height: 56px;Weight: 300;Style: Normal
Size: 38px;Line height: 38px;Weight: 300;Style: Normal
Size: 26px;Line height: 32px;Weight: 300;Style: Normal
Size: 22px;Line height: 26px;Weight: 400;Style: Normal
Size: 20px;Line height: 24px;Weight: 400;Style: Normal
推荐的文字大小:
普通: Size: 16px;Line height: 24px;Weight: 400;Style: Normal
小: Size: 14px;Line height: 20px;Weight: 400;Style: Normal
技校: Size: 12px;Line height: 16px;Weight: 400;Style: Normal
推荐加粗 Weight 为 700
日期和时间
以下是几种常见中文日期格式的代码示范,需要多语言的情况下请使用 moments(https://momentjs.com/)
const event = new Date(Date.UTC(2012, 11, 20, 3, 0, 0))
const options = { year: 'numeric', month: 'long', day: 'numeric' }
console.log(event.toLocaleDateString('zh-CN', options))
// 2012年12月20日
const mediumTime = new Intl.DateTimeFormat('zh', {
timeStyle: 'short',
dateStyle: 'long',
})
console.log(mediumTime.format(Date.now()))
// 2022年11月21日 17:48
const longTime = new Intl.DateTimeFormat('zh', {
timeStyle: 'long',
dateStyle: 'long',
timeZone: 'UTC',
})
console.log(longTime.format(Date.now()))
// 2022年11月21日 UTC 09:53:03
绝对时间 | 相对时间 | 翻译 |
---|---|---|
Published 11 November 2021 15:19 | Published 30 minutes ago | 30分钟前发布 |
内容 | 显示格式 | 翻译 |
---|---|---|
Date only | 24 February 2022 | 2022年2月24日 |
Date + time | 24 February 2022 13:45 | 2022年2月24日 13:45 |
Date + time + timezone (UTC) | 24 February 2022 14:45 UTC+01 | 2022年2月24日 UTC 14:45:03 |
分类 | 缩写 |
---|---|
Months (January to December) | Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec |
Days of the week (Monday to Sunday) | Mon, Tue, Wed, Thu, Fri, Sat, Sun |
过去事件发生时间 | 显示格式 | 翻译 |
---|---|---|
少于一分钟以前 | Now | 现在 |
大于一分钟小于一小时 | x minutes ago | x分钟前 |
六十分钟以前 | 1 hour ago | 1小时前 |
X小时以前 | x hours ago | x小时前 |
日历里昨天 | Yesterday | 昨天 |
2-6天前 | x days ago | x天前 |
7天前 | 1 week ago | 1周前 |
大于7天前 | Datestamp: i.e. 20 Dec 2021 | 2021年12月20日 |
未来发生事件时间 | 显示格式 | 翻译 |
---|---|---|
下一分钟以内 | Now | 现在 |
下一分钟内 | In 1 minute | 1分钟内 |
下60分钟内 | In x minutes | x分钟内 |
下60分钟 | In 1 hour | 1小时内 |
x小时以内 | In x hours | x小时内 |
一天后 | Tomorrow | 明天 |
在2到6天内 | In x days | x天内 |
7天内 | In 1 week | 一周内 |
大于7天以后 | Datestamp: i.e. 12 Apr 2023 | 2023年4月12日 |
表单
尽量减少表单长度,只加入必要字段:
要知道表单越长,用户填完的概率越小,我们应该尽量保证表单的简洁
确定你需要这个字段才能进行服务
想好这是一个必须还是可选字段
确定哪个类型的用户需要填写这个字段
你怎么校验这个字段的准确性
你怎么存储这个字段以及保障它的安全性
如左图,一般情况下我们只标注可选字段,其他的则为必须字段,除非表单大部分字段都是可选的,一般情况下我们使用左上角的 label 格式,而不是用左右布局。
在代码中我们使用 <fieldset>
tag 去组织所有 radio 按钮,checkboxes 和 switches 使用 <legend>
添加 label。常见的地址填写输入框组合和信用卡银行卡填写 input 组合也可以使用这个 HTML 格式。格式示范如下:
<fieldset class="mc-multi-choice-fieldset-host mc-multi-choice--medium ">
<legend>Container types</legend>
<div
id="errormessage"
class="mc-input__error-message mc-input__error-message--empty"
>
<slot name="errormessage"></slot>
</div>
<div id="hint" class="mc__hint mc__hint--empty">
<slot name="hint"></slot>
</div>
<div class="slot">
<slot></slot>
</div>
</fieldset>
在间隔上,我们也采用 4px 为最小单位的原则,推荐间隔大小如下。此外,应该尽量保证输入框长度一致,上下对齐。

表单按钮一般放置在左下角和输入框对齐。一般情况下表单都会包含条款行,以下两种是推荐的添加方式。第一种是可点击展开的勾选框,第二种是点击默认同意的方式。


表单错误提示如下图,建议单行错误提示在左下角和表单输入框对齐,表单整体错误提示在表单最上或者最下,整体对齐。成功提交消息应该在表单页面上方,或制作单独的成功跳转页面。

组合输入情况建议 2-3 个选项时使用 radio 按钮,大于三个使用下拉选择框,排版对齐方式如图。


过滤器,排序和搜索
过滤器最佳实践
- 如果你需要很多过滤器可以按照类型将他们分组
- 请确保过滤器的顺序和名称符合用户逻辑
- 如果过滤器数量过多可以仅展开常用的那些其他的折叠起来
- 已经被选择的过滤器应该清晰的显示在已选择区域
- 用户应该可以在已选择区域清除部分和全部过滤器
下图展示了如果把不常用的过滤器折叠起来,以及被折叠过滤器展开后的 UI(可自定义)

如果过滤器被选择后需要较长时间才能更新结果(2-3 秒以上),就要考虑使用过滤按钮,在用户等待期间,按钮变为 loading 状态,已改善用户体验。

排序最佳实践
- 对于数据表格来说如果需要排序,应该在表头直接提供排序选项
- 当我们使用向上向下箭头,向上表示上升/增长,向下表示下降/减少
- 用户可以点击表头文字和箭头进行排序和切换排序顺序
- 当排序顺序改变,箭头顺序也应该改变

搜索最佳实践
- 根据你要搜索的字段长度决定搜索框长度
- 在 Maersk Icons 文件夹选择 magnifying-glass 用于搜索,一般情况使用浅灰色(light gray)
- 在点击搜索按键或者回车时开始搜索
- 使用 placeholder 提示用户输入
- 如果支持逗号间隔多个关键词搜索,请在搜索界面说明
- 在开始搜索后不要清除输入框的文字
- 在搜索结果不能返回时使用 loading 或者浅灰色占位符
- 请确保做了没有搜索结果的处理
- 搜索需要请求 API 时请在前台做缓存或者加入防抖/节流已减少对后台的压力


标签(按钮,链接和表单字段)
我们在设计按钮文字时应该使用操作动词,比如保存不保存,下载,编辑等而不是简单的 yes no。按钮内容应该和选择内容相关,便于用户的理解。
应该使用正常的大小写,全部大写和小写都是不合适的。
链接的标签不要使用“Click here” 和“Read more”这一类不明确的指引,应该直接的写出链接内容。在链接范围内不要包含介词(a,an,the,etc.),也不要包含标点符号。
表单的标签需要注意,不要使用 enter, add, input 这样的动词作为起始,也不要使用:作为结尾,直接写明这个字段是什么就可以
Placeholder(提示框内的提示)和提示文字(提示框下的说明文字)布局如图,在错误提示出现的时候应该下移提示文字,把错误提示放在正下方。


弹出框
我们一共有四种类型的弹出,一般推荐使用在页面最下方或者最上方中心的提示条,可以采用固定在页面上或者跟随鼠标滚动的 sticky 格式,也可以使用在页面内部某个元素下的提示。可交互的提示框,比如提交表单显示以及点击详情后显示,应该在页面中央,使用浅灰透明遮罩。
遮挡性的弹出框应该只用于重要的阻塞事件,比如判断用户身份,条款与合法性确认,一旦打开只可以通过点击按钮关闭,不可以点击页面其他部分取消。像用户 cookie 收集,活动推送等次要事件应该在页面设计中使用提示条,固定在页面的上方或下方,也可以学习马士基官网的方式当用户下拉到页面下方再弹出 cookie 提示框。
尽可能不要在首屏或者活动页面使用进入页面强制弹出的提示框,这会影响页面分享,谷歌搜索抓取等功能。比如用户想在脸书或者推特上分享这一页面,会因为弹出框抓取不到正确的页面截图。如果一定需要的情况下,请使用 crawler 方案。
以下为实际提示和弹出框的参照样式



数字格式和省略格式
数字国际化请参阅 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
//常用代码
console.log(
new Intl.NumberFormat('zh-CN', { notation: 'compact' }).format(987654321)
)
// → 9.9亿 数字中文缩写
console.log(
new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(
number
)
)
//人民币"¥123,456.79"
console.log(new Intl.NumberFormat('zh-CN').format(12314000))
//中国数字格式转换 12,314,000
console.log(new Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec').format(number))
// → 一二三,四五六.七八九 通过编号系统中的 nu 扩展键请求,例如:中文十进制数字
推荐在数字单位和数字之间使用一个空格并且使用小写的数字单位
国内电话号码推荐格式
+(86)18624367620 +(86)(010)12345
关于省略:
像邮箱和电话号码等需要保密的个人信息,或者带有格式的文件推荐中间省略,其他情况推荐结尾省略。


翻页菜单推荐如下配色的两头省略
数据可视化
图表颜色与样式参考(实际颜色和样式可根据使用的图表库以及设计修改)
曲线图样式 | |
---|---|
线段宽度: | 2px |
线段样式: | Solid |
原点样式: | 4px solid circle |
图表参考对比配色 | |||
---|---|---|---|
Data 1: Blue-300-Maersk | #42B0D5 | rgba(66,176,213,1) | |
Data 2: Blue-900-Dark | #00243D | rgba(0,36,61,1) | |
Data 3: Grey-400 | #CFCFCF | rgba(207,207,207,1) | |
Data 4: Red-300-Accent | #FA6A55 | rgba(250,106,85,1) | |
Data 5: Teal-300-Accent | #4DB5AB | rgba(77,181,171,1) | |
Data 6: Grey-300 | #EDEDED | rgba(237,237,237,1) | |
Positive: Success | #40AB35 | rgba(64,171,53,1) | |
Neutral: Warning | #FFD029 | rgba(255,208,41,1) | |
Negative: Danger | #B80012 | rgba(184,0,18,1) |
图表参考渐变配色 | |||
---|---|---|---|
Data 1: Blue-900-Dark | #00243D | rgba(0,36,61,1) | |
Data 2: Blue-800 | #003D6D | rgba(0,61,109,1) | |
Data 3: Blue-700 | #005487 | rgba(0,84,135,1) | |
Data 4: Blue-600-Dark | #0073AB | rgba(0,115,171,1) | |
Data 5: Blue-500 | #058EC6 | rgba(5,142,198,1) | |
Data 6: Blue-400 | #0CA0CE | rgba(12,160,206,1) | |
Data 7: Blue-300-Maersk | #42B0D5 | rgba(66,176,213,1) | |
Data 8: Blue-200 | #77C6E0 | rgba(119,198,224,1) | |
Data 9: Blue-100 | #91D8F2 | rgba(145,216,242,1) | |
Data 10: Blue-50 | #B5E0F5 | rgba(181,224,245,1) |
主标题样式 | |
---|---|
Font face: | Maersk Headline (alternatively Helvetica, Arial) |
Font size: | 20px |
Font weight: | Regular |
Font colour: | Grey-900-Black | #141414 | rgba(20,20,20,1) |
副标题样式 | |
---|---|
Font face: | Maersk Text (alternatively Helvetica, Arial) |
Font size: | 26px |
Font weight: | Regular |
Font colour: | Grey-600 | #878787 | rgba(135,135,135,1) |
我们推荐在图表上使用图表轴线以便于用户做对比:
图表轴线推荐使用 Grey-500 |#B2B2B2 | rgba(178,178,178,1) 1px solid
图表左侧与下方标题推荐居中使用 Maersk Text (alternatively Helvetica, Arial) Grey-900-Black
标注说明区域即上图 4,推荐使用 Maersk Text (alternatively Helvetica, Arial) Grey-700
我们推荐在每个图表下方注明数据来源,且图表字段的排序应该有业务上的意义而非随机
在图表内容较多时,设计应该能让用户专注在核心内容上,比如使用重点色,添加红色辅助线
在图表颜色设计上遵循简单与统一的原则,避免使用无意义的对比配色,并且在整个应用的图表中应该保持一致的配色,为用户提供更好的视觉体验。
组件补充说明
下拉菜单推荐设计:左图为官网电脑端语言切换菜单,当鼠标滑动到语言切换按钮时菜单显示,移开则收回,菜单无下拉动画。在平板和移动端收纳到折叠菜单栏内。在设计搜索功能的结果下拉菜单时我们也应该加入点击空白处收回的功能。


导航栏推荐设计:下图一为电脑端导航栏设计,图二图三在平板和手机端导航列表和语言列表都被折叠到菜单中,点击展开可以查看。应该避免在小屏设备导航栏过长导致挤压重叠。



表单表格设计额外附加:如果需要用户提交长表单一定要在前端做缓存,当用户刷新页面切换 tab 页甚至切换语言时根据需求为用户保存已经填写的内容。同样的,在 table 中设计了多条筛选之类的编辑条件也尽量做缓存避免用户重复勾选。

对于长表单,在用户提交时如果有错误应该导航到表单最上面的错误项,避免用户找不到,另外不要在提交失败时清除用户输入的内容。

卡片推荐设计:当我们设计卡片时应该加入可交互性的样式
第一种为鼠标划到卡片上时卡片加阴影。
box-shadow: 0 8px 10px 1px rgb(0 0 0 / 3%), 0 3px 14px 2px rgb(0 0 0 / 3%), 0 5px 5px -3px rgb(0 0 0 / 4%);
第二种为图片放大(background-size)文字加下划线。


此外,不要在网页中加入任何直接播放的音频视频资源,应该使用播放按钮并且加入进度条和视频静音按键。