作者:九色鹿紫 来源:站酷
蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请加微信ban_lanlan,报下信息,蓝小助会请您入群。欢迎您加入噢~~
希望得到建议咨询、商务合作,也请与我们联系01063334945。
分享此文一切功德,皆悉回向给文章原作者及众读者. 免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
作者:ZDDONG 来源:站酷
蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请加微信ban_lanlan,报下信息,蓝小助会请您入群。欢迎您加入噢~~
希望得到建议咨询、商务合作,也请与我们联系01063334945。
分享此文一切功德,皆悉回向给文章原作者及众读者. 免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
目前主流的 Web 开发模式有两种,分别是:
服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据。代码示例如下:
优点:
缺点:
前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式。
优点:
缺点:
不利于 SEO。因为完整的 HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用 Vue、React 等前端框架的 SSR (server side render)技术能够很好的解决 SEO 问题!)
不谈业务场景而盲目选择使用何种开发模式都是耍流氓。
另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染 + 其他页面前后端分离的开发模式。
身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。
在互联网项目开发中,如何对用户的身份进行认证,是一个值得深入探讨的问题。例如,如何才能保证网站不会错误的将“马云的存款数额”显示到“马化腾的账户”上。
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
对于超市来说,为了方便收银员在进行结算时给 VIP 用户打折,超市可以为每个 VIP 用户发放会员卡。
注意:现实生活中的会员卡身份认证方式,在 Web 开发中的专业术语叫做 Cookie
Cookie的几大特性:
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。
注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。
3.6、提高身份认证的安全性
为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。
这种“会员卡 + 刷卡认证”的设计理念,就是 Session 认证机制的精髓。
在 Express 项目中,只需要安装 express-session 中间件,即可在项目中使用 Session 认证:
npm install express-session
express-session 中间件安装成功后,需要通过 app.use() 来注册 session 中间件,示例代码如下:
-
// 导入 session 中间件
-
const session = require('express-session')
-
-
// 配置 session 中间件
-
app.use(
-
session({
-
secret: 'itheima', // secret 属性的值可以为任意字符串
-
resave: false, // 固定写法
-
saveUninitialized: true, // 固定写法
-
})
-
)
当 express-session 中间件配置成功后,即可通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息:
-
// 登录的 API 接口
-
app.post('/api/login', (req, res) => {
-
// 判断用户提交的登录信息是否正确
-
if (req.body.username !== 'admin' || req.body.password !== '000000') {
-
return res.send({ status: 1, msg: '登录失败' })
-
}
-
-
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
-
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
-
req.session.user = req.body // 用户的信息
-
console.log(req.body)
-
req.session.islogin = true // 用户的登录状态
-
-
res.send({ status: 0, msg: '登录成功' })
-
})
可以直接从 req.session 对象上获取之前存储的数据,示例代码如下:
-
// 获取用户姓名的接口
-
app.get('/api/username', (req, res) => {
-
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
-
if (!req.session.islogin) {
-
return res.send({ status: 1, msg: 'fail' })
-
}
-
res.send({
-
status: 0,
-
msg: 'success',
-
username: req.session.user.username,
-
})
-
})
调用 req.session.destroy() 函数,即可清空服务器保存的 session 信息。
-
// 退出登录的接口
-
app.post('/api/logout', (req, res) => {
-
// TODO_04:清空 Session 信息
-
req.session.destroy()
-
res.send({
-
status: 0,
-
msg: '退出登录成功',
-
})
-
})
index.html
-
<!DOCTYPE html>
-
<html lang="en">
-
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>后台主页</title>
-
<script src="./jquery.js"></script>
-
</head>
-
-
<body>
-
<h1>首页</h1>
-
-
<button id="btnLogout">退出登录</button>
-
-
<script>
-
$(function () {
-
-
// 页面加载完成后,自动发起请求,获取用户姓名
-
$.get('/api/username', function (res) {
-
// status 为 0 表示获取用户名称成功;否则表示获取用户名称失败!
-
if (res.status !== 0) {
-
alert('您尚未登录,请登录后再执行此操作!')
-
location.href = './login.html'
-
} else {
-
alert('欢迎您:' + res.username)
-
}
-
})
-
-
// 点击按钮退出登录
-
$('#btnLogout').on('click', function () {
-
// 发起 POST 请求,退出登录
-
$.post('/api/logout', function (res) {
-
if (res.status === 0) {
-
// 如果 status 为 0,则表示退出成功,重新跳转到登录页面
-
location.href = './login.html'
-
}
-
})
-
})
-
})
-
</script>
-
</body>
-
-
</html>
login.html
-
<!DOCTYPE html>
-
<html lang="en">
-
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>登录页面</title>
-
<script src="./jquery.js"></script>
-
</head>
-
-
<body>
-
<!-- 登录表单 -->
-
<form id="form1">
-
<div>账号:<input type="text" name="username" autocomplete="off" /></div>
-
<div>密码:<input type="password" name="password" /></div>
-
<button>登录</button>
-
</form>
-
-
<script>
-
$(function () {
-
// 监听表单的提交事件
-
$('#form1').on('submit', function (e) {
-
// 阻止默认提交行为
-
e.preventDefault()
-
// 发起 POST 登录请求
-
$.post('/api/login', $(this).serialize(), function (res) {
-
// status 为 0 表示登录成功;否则表示登录失败!
-
if (res.status === 0) {
-
location.href = './index.html'
-
} else {
-
alert('登录失败!')
-
}
-
})
-
})
-
})
-
</script>
-
</body>
-
-
</html>
app.js
-
// 导入 express 模块
-
const express = require('express')
-
// 创建 express 的服务器实例
-
const app = express()
-
-
// TODO_01:请配置 Session 中间件
-
const session = require('express-session')
-
app.use(
-
session({
-
secret: 'itheima',
-
resave: false,
-
saveUninitialized: true,
-
})
-
)
-
-
// 托管静态页面
-
app.use(express.static('./pages'))
-
// 解析 POST 提交过来的表单数据
-
app.use(express.urlencoded({ extended: false }))
-
-
// 登录的 API 接口
-
app.post('/api/login', (req, res) => {
-
// 判断用户提交的登录信息是否正确
-
if (req.body.username !== 'admin' || req.body.password !== '000000') {
-
return res.send({ status: 1, msg: '登录失败' })
-
}
-
-
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
-
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
-
req.session.user = req.body // 用户的信息
-
console.log(req.body)
-
req.session.islogin = true // 用户的登录状态
-
-
res.send({ status: 0, msg: '登录成功' })
-
})
-
-
// 获取用户姓名的接口
-
app.get('/api/username', (req, res) => {
-
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
-
if (!req.session.islogin) {
-
return res.send({ status: 1, msg: 'fail' })
-
}
-
res.send({
-
status: 0,
-
msg: 'success',
-
username: req.session.user.username,
-
})
-
})
-
-
// 退出登录的接口
-
app.post('/api/logout', (req, res) => {
-
// TODO_04:清空 Session 信息
-
req.session.destroy()
-
res.send({
-
status: 0,
-
msg: '退出登录成功',
-
})
-
})
-
-
// 调用 app.listen 方法,指定端口号并启动web服务器
-
app.listen(80, function () {
-
console.log('Express server running at http://127.0.0.1:80')
-
})
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
注意:
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
总结:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
下面是 JWT 字符串的示例:
JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。
其中:
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
运行如下命令,安装如下两个 JWT 相关的包:
npm install jsonwebtoken express-jwt
其中:
使用 require() 函数,分别导入 JWT 相关的两个包:
-
// 安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
-
const jwt = require('jsonwebtoken')
-
const expressJWT = require('express-jwt')
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥:
-
// 定义 secret 密钥,建议将密钥命名为 secretKey,本质上就是一个字符串
-
const secretKey = 'itheima No1 ^_^'
调用 jsonwebtoken 包提供的 sign() 方法,将用户的信息加密成 JWT 字符串,响应给客户端:
-
// 登录接口
-
app.post('/api/login', function (req, res) {
-
// 将 req.body 请求体中的数据,转存为 userinfo 常量
-
const userinfo = req.body
-
// 登录失败
-
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
-
return res.send({
-
status: 400,
-
message: '登录失败!',
-
})
-
}
-
// 登录成功
-
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
-
// 参数1:用户的信息对象
-
// 参数2:加密的秘钥
-
// 参数3:配置对象,可以配置当前 token 的有效期
-
// 记住:千万不要把密码加密到 token 字符中
-
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
-
res.send({
-
status: 200,
-
message: '登录成功!',
-
token: tokenStr, // 要发送给客户端的 token 字符串
-
})
-
})
-
// 使用 app.use() 来注册中间件
-
// expressJWT({ secret: secretKey }) 就是用来解析 Token 的中间件
-
// .unless({ path: [/^\/api\//] }) 用来指定哪些接口不需要访问权限
-
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
6.6、使用 req.user 获取用户信息
当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息了,示例代码如下:
-
// 这是一个有权限的 API 接口
-
app.get('/admin/getinfo', function (req, res) {
-
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
-
console.log(req.user)
-
res.send({
-
status: 200,
-
message: '获取用户信息成功!',
-
data: req.user, // 要发送给客户端的用户信息
-
})
-
})
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
//main.js //引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数 import {createApp} from 'vue import App from './App.vue //创建应用实例对象-app(类似于之前vue2中的vm实例,但是app比vm更轻) createApp(APP).mount('#app') //卸载就是unmount,卸载就没了 //createApp(APP).unmount('#app') //之前我们是这么写的,在vue3里面这一块就不支持了,会报错的,引入不到 import vue from 'vue'; new Vue({ render:(h) => h(App) }).$mount('#app') //多个应用实例 const app1 = createApp({ /* ... */ }) app1.mount('#container-1') const app2 = createApp({ /* ... */ }) app2.mount('#container-2')
理解:Vue3.0中一个新的额配置项,值为一个函数
2.setup是所有Composition API(组合api) “表演的舞台”
组件中所用到的:数据、方法等等,均要配置在setup中
setup函数的两种返回值:
注意点:
import {h} from 'vue' //向下兼容,可以写入vue2中的data配置项 module default { name: 'App', setup(){ //数据 let name = '张三', let age = 18, //方法 function sayHello(){ console.log(name) }, //f返回一个对象(常用) return { name, age, sayHello } //返回一个函数(渲染函数) //return () => {return h('h1','学习')} return () => h('h1','学习') } }
<script setup></script >
<script setup>。(不包括一般的 <script>)
<script setup> 中的顶层绑定都将自动暴露给模板。
<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:
/* 里面的代码会被编译成组件 setup() 函数的内容。
这意味着与普通的 `<script>` 只在组件被首次引入的时候执行一次不同,
`<script setup>` 中的代码会在每次组件实例被创建的时候执行。*/ <script setup> console.log('hello script setup') </script>
当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:
<script setup> // 变量 const msg = '王二麻子' // 函数 function log() { console.log(msg) } </script> <template> <button @click="log">{{ msg }}</button> </template>
import 导入的内容也会以同样的方式暴露。这意味着我们可以在模板表达式中直接使用导入的 action 函数,而不需要通过 methods 选项来暴露它:
<script setup> import { say } from './action' </script> <template> <div>{{ say ('hello') }}</div> </template>
响应式状态需要明确使用响应式 API 来创建。和 setup() 函数的返回值一样,ref 在模板中使用的时候会自动解包:
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">{{ count }}</button> </template>
<script setup> 范围里的值也能被直接作为自定义组件的标签名使用:
/**
*这里 MyComponent 应当被理解为像是在引用一个变量。
*如果你使用过 JSX,此处的心智模型是类似的。
*其 kebab-case 格式的 <my-component> 同样能在模板中使用——不过,
*强烈建议使用 PascalCase 格式以保持一致性。同时这也有助于区分原生的自定义元素。
*/ <script setup> import MyComponent from './MyComponent.vue' </script> <template> <MyComponent /> </template>
/**
*由于组件是通过变量引用而不是基于字符串组件名注册的,
*在 <script setup> 中要使用动态组件的时候,应该使用*动态的 :is 来绑定:
*/ <script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template>
<FooBar/> 引用它自己。
import { FooBar as FooBarChild } from './components'
<Foo.Bar> 来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用:
<script setup> import * as Form from './form-components' </script> <template> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template>
<script setup> 中不需要显式注册,但他们必须遵循 vNameOfDirective 这样的命名规范:
<script setup> const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } </script> <template> <h1 v-my-directive>This is a Heading</h1> </template>
<script setup> import { myDirective as vMyDirective } from './MyDirective.js' </script>
<script setup> 中可用:
<script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup 代码 </script>
<script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
<script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
//可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性: <script setup> import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b }) </script> //当父组件通过模板引用的方式获取到当前组件的实例, //获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)
<script setup> 使用 slots 和 attrs 的情况应该是相对来说较为罕见的,因为可以在模板中直接通过 $slots 和 $attrs 来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlots 和 useAttrs 两个辅助函数:
<script setup> import { useSlots, useAttrs } from 'vue' const slots = useSlots() const attrs = useAttrs() </script> //useSlots 和 useAttrs 是真实的运行时函数,它的返回与 setupContext.slots 和 setupContext.attrs 等价。 //它们同样也能在普通的组合式 API 中使用。
<script> 一起使用:
<script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有这些需要的情况下或许会被使用到:
<script> // 普通 <script>, 在模块作用域下执行 (仅一次) runSideEffectOnce() // 声明额外的选项 export default { inheritAttrs: false, customOptions: {} } </script> <script setup> // 在 setup() 作用域中执行 (对每个实例皆如此) </script>
<script setup> 中可以使用顶层 await。结果代码会被编译成 async setup():
<script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script> // 另外,await 的表达式会自动编译成在 await 之后保留当前组件实例上下文的格式。
Object.defineProperty( data, 'count', { get(){}, set(){} }) //模拟实现一下 let person = { name: '张三', age: 15, } let p = {} Object.defineProperty( p, 'name', { configurable: true, //配置这个属性表示可删除的,否则delete p.name 是删除不了的 false get(){ //有人读取name属性时调用 return person.name }, set(value){ //有人修改时调用 person.name = value } })
- 存在问题:
1. 新增属性。删除属性。界面不会更新
2. 直接通过下表修改数组,界面不会自动更新
//模拟vue3中实现响应式 let person = { name: '张三', age: 15, } //我们管p叫做代理数据,管person叫源数据 const p = new Proxy(person,{ //target代表的是person这个源对象,propName代表读取或者写入的属性名 get(target,propName){ console.log('有人读取了p上面的propName属性') return target[propName] }, //不仅仅是修改调用,增加的时候也会调用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`) target[propName] = value }, deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`) return delete target[propName] } }) //映射到person上了,捕捉到修改,那就是响应式啊
//vue3底层源码不是我们上面写的那么low,实现原理一样,但是用了一个新的方式 window.Reflect  let obj = { a: 1, b:2, } //传统的只能通过try catch去捕获异常,如果使用这种那么底层源码将会有一堆try catch try{ Object.defineProperty( obj, 'c', { get(){ return 3 }, }) Object.defineProperty( obj, 'c', { get(){ return 4 }, }) } catch(error) { console.log(error) } //新的方式: 通过Reflect反射对象去操作,相对来说要舒服一点,不会要那么多的try catch const x1 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) const x2 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) //x1,和x2是有返回布尔值的 if(x2){ console.log('某某操作成功了') }else { console.log('某某操作失败了') }
let person = { name: '张三', age: 15, } //我们管p叫做代理数据,管person叫源数据 const p = new Proxy(person,{ //target代表的是person这个源对象,propName代表读取或者写入的属性名 get(target,propName){ console.log('有人读取了p上面的propName属性') return Reflect.get(target, propName) }, //不仅仅是修改调用,增加的时候也会调用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`) Reflect.set(target, propName, value) }, deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`) return Reflect.deleteProperty(target,propName) } })
从定义数据角度对比:
从原理角度对比:
从使用角度对比:
//父组件 <script setup> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from './components/test3.vue'; const hello = (val) =>{ console.log('传递的参数是:'+ val); } </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="传递吧" @hello="hello"> <template v-slot:cacao> <span>是插槽吗</span> </template> <template v-slot:qwe> <span>meiyou</span> </template> </HelloWorld> </template>
//子组件 export default { name: 'test3', props: ['msg'], emits:['hello'], //这里setup接收两个参数,一个是props,一个是上下文context setup(props,context){ /**
* props就是父组件传来的值,但是他是Porxy类型的对象
* >Proxy:{msg:'传递吧'}
* 可以当作我们自定义的reactive定义的数据
*/ /**
* context是一个对象 包含以下内容:
* 1.emit触发自定义事件的
* 2.attrs 相当于vue2里面的 $attrs 包含:组件外部传递过来,但没有在props配置中声明的属性
* 3.slots 相当于vue2里面的 $slots
* 3.expose 是一个回调函数
*/ console.log(context.slots); let person = reactive({ name: '张三', age: 17, }) function changeInfo(){ context.emit('hello', 666) } //返回对象 return { person, changeInfo } //返回渲染函数(了解) 这个h是个函数 //return () => h('name','age') } } </script>
<template> <h1>一个人的信息</h1> <div> 姓: <input type="text" v-model="person.firstName"> 名:<input type="text" v-model="person.lastName"> <div> <span>简名:{{person.smallName}}</span> <br> <span>全名:{{person.fullName}}</span> </div> </div> </template> <script> import { computed,reactive } from 'vue' export default { name: 'test4', props: ['msg'], emits:['hello'], setup(){ let person = reactive({ firstName: '张', lastName: '三' }) //简写形式 person.smallName = computed(()=>{ return person.firstName + '-' + person.lastName }) //完全形态 person.fullName = computed({ get(){ console.log('调用get'); return person.firstName + '*' + person.lastName }, set(value){ console.log('调用set'); const nameArr = value.split('*') person.firstName = nameArr[0] person.firstName = nameArr[1] }, }) return { person, } }, } </script>
1.监视reactive定义的响应式数据的时候:oldValue无法获取到正确的值,强制开启了深度监视(deep配置无效)
2.监视reactive定义的响应式数据中某个属性的时候:deep配置有效
具体请看下面代码以及注释
<template> <h1>当前求和为: {{sum}}</h1> <button @click="sum++">点我+1</button> <hr> <h1>当前信息为: {{msg}}</h1> <button @click="msg+='!' ">修改信息</button> <hr> <h2>姓名: {{person.name}}</h2> <h2>年龄: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增长年龄</button> </template> <script> //使用setup的注意事项 import { watch,ref,reactive } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '张三', age: 18, job:{ salary: '15k' }, }) //由于这里的this是指的是undefined,所以使用箭头函数 //情况一:监视ref所定义的一个响应式数据 // watch(sum, (newValue,oldValue)=>{ // console.log('新的值',newValue); // console.log('旧的值',oldValue); // }) //情况二:监视ref所定义的多个响应式数据 watch([sum,msg], (newValue,oldValue)=>{ console.log('新的值',newValue); //['sum的newValue', 'msg的newValue'] console.log('旧的值',oldValue); //['sum的oldValue', 'msg的oldValue'] },{immediate: true,deep:true}) //这里vue3的deep是有点小问题的,可以不用deep,(隐式强制deep) //情况三:监视reactive定义的所有响应式数据, //1.此处无法获取正确的oldValue(newValue与oldValue是一致值),且目前无法解决 //2.强制开启了深度监视(deep配置无效) /**
* 受到码友热心评论解释: 此处附上码友的解释供大家参考:
* 1. 当你监听一个响应式对象的时候,这里的newVal和oldVal是一样的,因为他们是同一个对象【引用地址一样】,
* 即使里面的属性值会发生变化,但主体对象引用地址不变。这不是一个bug。要想不一样除非这里把对象都换了
*
* 2. 当你监听一个响应式对象的时候,vue3会隐式的创建一个深层监听,即对象里只要有变化就会被调用。
* 这也解释了你说的deep配置无效,这里是强制的。
*/ watch(person, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('旧的值',oldValue); }) //情况四:监视reactive对象中某一个属性的值, //注意: 这里监视某一个属性的时候可以监听到oldValue watch(()=>person.name, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('旧的值',oldValue); }) //情况五:监视reactive对象中某一些属性的值 watch([()=>person.name,()=>person.age], (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('旧的值',oldValue); }) //特殊情况: 监视reactive响应式数据中深层次的对象,此时deep的配置奏效了 watch(()=>person.job, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('旧的值',oldValue); },{deep:true}) //此时deep有用 return { sum, msg, person, } }, } </script>
<script> //使用setup的注意事项 import { ref,reactive,watchEffect } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '张三', age: 18, job:{ salary: '15k' }, }) //用处: 如果是比较复杂的业务,发票报销等,那就不许需要去监听其他依赖,只要发生变化,立马重新回调 //注重逻辑过程,你发生改变了我就重新执行回调,不用就不执行,只执行一次 watchEffect(()=>{ //这里面你用到了谁就监视谁,里面就发生回调 const x1 = sum.value
console.log('我调用了'); }) return { sum, msg, person, } }, } </script>
<template> <h1>生命周期</h1> <p>当前求和为: {{sum}}</p> <button @click="sum++">加一</button> </template> <script> //使用setup的注意事项 import { ref,reactive,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue' export default { name: 'test7', setup(){ let sum = ref(0) //通过组合式API的形式去使用生命周期钩子 /**
* beforeCreate 和 created 这两个生命周期钩子就相当于 setup 所以,不需要这两个
*
* beforeMount ===> onBeforeMount
* mounted ===> onMounted
* beforeUpdate ===> onBeforeUpdate
* updated ===> onUpdated
* beforeUnmount ===> onBeforeUnmount
* unmounted ===> onUnmounted
*/ console.log('---setup---'); onBeforeMount(()=>{ console.log('---onBeforeMount---'); }) onMounted(()=>{ console.log('---onMounted---'); }) onBeforeUpdate(()=>{ console.log('---onBeforeUpdate---'); }) onUpdated(()=>{ console.log('---onUpdated---'); }) onBeforeUnmount(()=>{ console.log('---onBeforeUnmount---'); }) onUnmounted(()=>{ console.log('---onUnmounted---'); }) return { sum } }, //这种是外层的写法,如果想要使用组合式api的话需要放在setup中 beforeCreate(){ console.log('---beforeCreate---'); }, created(){ console.log('---created---'); }, beforeMount(){ console.log('---beforeMount---'); }, mounted(){ console.log('---mounted---'); }, beforeUpdate(){ console.log('---beforeUpdate---'); }, updated(){ console.log('---updated---'); }, //卸载之前 beforeUnmount(){ console.log('---beforeUnmount---'); }, //卸载之后 unmounted(){ console.log('---unmounted---'); } } </script>
//usePoint.js import {reactive,onMounted,onBeforeUnmount } from 'vue' function savePoint(){ //实现鼠标打点的数据 let point = reactive({ x: null, y: null }) //实现鼠标点的方法 const savePoint = (e)=>{ point.x = e.pageX
point.y = e.pageY } //实现鼠标打点的生命周期钩子 onMounted(()=>{ window.addEventListener('click',savePoint) }) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) }) return point } export default savePoint
//组件test.vue <template> <p>当前求和为: {{sum}} </p> <button @click="sum++">加一</button> <hr> <h2>当前点击时候的坐标: x: {{point.x}} y:{{point.y}}</h2> </template> <script> import { ref } from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'test8', setup(props,context){ let sum = ref(0) let point = usePoint() return { sum, point } } } </script>
<template> <h2>姓名: {{name2}}</h2> <h2>年龄: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增长年龄</button> </template> <script> //使用setup的注意事项 import { reactive, toRef, toRefs } from 'vue' export default { name: 'test9', setup(){ let person = reactive({ name: '张三', age: 18, job:{ salary: '15k' }, }) //toRef const name2 = toRef(person,'name') //第一个参数是对象,第二个参数是键名 console.log('toRef转变的是',name2); //ref定义的对象 //toRefs,批量处理对象的所有属性 //const x = toRefs(person) //console.log('toRefs转变的是',x); //是一个对象 return { person, name2, ...toRefs(person) } }, } </script>
//场景一: 使用<script setup> <script setup lang="ts"> const props = defineProps({ foo: { type: String, required: true }, bar: Number }) props.foo // string props.bar // number | undefined </script> //也可以将 props 的类型移入一个单独的接口中 <script setup lang="ts"> interface Props { foo: string
bar?: number } const props = defineProps<Props>() </script> //场景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ props: { message: String }, setup(props) { props.message // <-- 类型:string } })
//1.一个类型字面量: defineProps<{ /*... */ }>() //2.对同一个文件中的一个接口或对象类型字面量的引用 interface Props {/* ... */} defineProps<Props>() //3.接口或对象字面类型可以包含从其他文件导入的类型引用,但是,传递给 defineProps 的泛型参数本身不能是一个导入的类型: import { Props } from './other-file' // 不支持! defineProps<Props>()
//当使用基于类型的声明时,失去了对 props 定义默认值的能力。通过目前实验性的响应性语法糖来解决: <script setup lang="ts"> interface Props { foo: string
bar?: number } // 对 defineProps() 的响应性解构 // 默认值会被编译为等价的运行时选项 const { foo, bar = 100 } = defineProps<Props>() </script>
//场景一: 使用<script setup> <script setup lang="ts"> const emit = defineEmits(['change', 'update']) // 基于类型 const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>() </script> //场景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ emits: ['change'], setup(props, { emit }) { emit('change') // <-- 类型检查 / 自动补全 } })
import { ref } from 'vue' import type { Ref } from 'vue' //1.ref 会根据初始化时的值推导其类型: // 推导出的类型:Ref<number> const year = ref(2020) // => TS Error: Type 'string' is not assignable to type 'number'. year.value = '2020' //2.指定一个更复杂的类型,可以通过使用 Ref 这个类型: const year: Ref<string | number> = ref('2020') year.value = 2020 // 成功! //3.在调用 ref() 时传入一个泛型参数,来覆盖默认的推导行为: // 得到的类型:Ref<string | number> const year = ref<string | number>('2020') year.value = 2020 // 成功! //4.如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含 undefined 的联合类型: // 推导得到的类型:Ref<number | undefined> const n = ref<number>()
import { reactive } from 'vue' //1.reactive() 也会隐式地从它的参数中推导类型: // 推导得到的类型:{ title: string } const book = reactive({ title: 'Vue 3 指引' }) //2.要显式地标注一个 reactive 变量的类型,我们可以使用接口: interface Book { title: string
year?: number } const book: Book = reactive({ title: 'Vue 3 指引' })
import { ref, computed } from 'vue' //1.computed() 会自动从其计算函数的返回值上推导出类型: const count = ref(0) // 推导得到的类型:ComputedRef<number> const double = computed(() => count.value * 2) // => TS Error: Property 'split' does not exist on type 'number' const result = double.value.split('') //2.通过泛型参数显式指定类型: const double = computed<number>(() => { // 若返回值不是 number 类型则会报错 })
//在处理原生 DOM 事件时,应该为我们传递给事件处理函数的参数正确地标注类型 <script setup lang="ts"> function handleChange(event) { // 没有类型标注时 `event` 隐式地标注为 `any` 类型, // 这也会在 tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 时报出一个 TS 错误。 console.log(event.target.value) } </script> <template> <input type="text" @change="handleChange" /> </template> //因此,建议显式地为事件处理函数的参数标注类型,需要显式地强制转换 event 上的属性: function handleChange(event: Event) { console.log((event.target as HTMLInputElement).value) }
/*
provide 和 inject 通常会在不同的组件中运行。要正确地为注入的值标记类型,
Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,
可以用来在提供者和消费者之间同步注入值的类型:
*/ import { provide, inject } from 'vue' import type { InjectionKey } from 'vue' const key = Symbol() as InjectionKey<string> provide(key, 'foo') // 若提供的是非字符串值会导致错误 const foo = inject(key) // foo 的类型:string | undefined //建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入。 //当使用字符串注入 key 时,注入值的类型是 unknown,需要通过泛型参数显式声明: const foo = inject<string>('foo') // 类型:string | undefined //注意注入的值仍然可以是 undefined,因为无法保证提供者一定会在运行时 provide 这个值。 //当提供了一个默认值后,这个 undefined 类型就可以被移除: const foo = inject<string>('foo', 'bar') // 类型:string //如果你确定该值将始终被提供,则还可以强制转换该值: const foo = inject('foo') as string
//模板引用需要通过一个显式指定的泛型参数和一个初始值 null 来创建: <script setup lang="ts"> import { ref, onMounted } from 'vue' const el = ref<HTMLInputElement | null>(null) onMounted(() => { el.value?.focus() }) </script> /**
注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。这是因为直到组件被挂载前,
这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null。
*/ <template> <input ref="el" /> </template>
//有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 MyModal 子组件,它有一个打开模态框的方法 <!-- MyModal.vue --> <script setup lang="ts"> import { ref } from 'vue' const isContentShown = ref(false) const open = () => (isContentShown.value = true) defineExpose({ open }) </script> //为了获取 MyModal 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型来获取其实例类型: <!-- App.vue --> <script setup lang="ts"> import MyModal from './MyModal.vue' const modal = ref<InstanceType<typeof MyModal> | null>(null) const openModal = () => { modal.value?.open() } </script> //注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用这种技巧,需要开启 Volar 的Takeover 模式。
import { useStore } from 'vuex' export default { setup () { const store = useStore() } }
import { computed } from 'vue' import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 在 computed 函数中访问 state count: computed(() => store.state.count), // 在 computed 函数中访问 getter double: computed(() => store.getters.double) } } }
import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 使用 mutation increment: () => store.commit('increment'), // 使用 action asyncIncrement: () => store.dispatch('asyncIncrement') } } }
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
一年一度,天猫双十一全球狂欢节,如约而至!
从2015年开始,我们每年都会在双十一期间,将双十一品牌设计的完整思路分享给大家,这已经成为双十一设计团队的传统。不为别的,各位同仁辛苦一年,想跟大家就着新鲜出炉的设计唠唠嗑。
每逢双十一logo出街,都会有热心的朋友帮我们解读,也有人问我们为啥不搞个官方发布?各位朋友,您现在看到的就是官方发布的内容,它不只有logo,而是从头到尾一个完整的故事。
2019天猫双十一主logo
2019天猫双十一主logo多语言版本
今年是双十一的第十一年,当我们接到这个任务的时候,就有机灵的同学提议:“我们用6个1吧,111111,61儿童节!”、“让我们回归购物的纯真快乐!”。
“哈哈哈哈哈哈…”魔性的笑声在整个会议室回荡,看来往年挠破头也解不开的难题,就这么解开了?故事当然不会这么简单,我们还没有往这个方向尝试就被否了。
其一,双十一是一个深入人心的认知,这四个一已经成为了超级符号,是我们宝贵的品牌资产,而六个一不但不能帮我们强化认知价值,反而会增加认知成本。
其二,六个一是一个纯视觉的创意,他很难支撑起我们要传达的消费者价值,也很难建立起情感连接。
我们应该从哪儿入手?
回归到设计的本质来思考,我们认为,设计的本质是将一个想法或者观点巧妙的表达给目标对象,表达的过程中,形式只是手段,重点在于我们要表达什么。
我们集合了阿里各事业部的设计师代表,让大家回归到一个普通消费者的状态,一起聊一聊各自的双十一故事,把这些故事提炼出来,就是消费者对于双十一普遍真实的认知。在全年最便宜的一天,无论凑热闹也好,跟风也好,贪便宜也好,好像不买点什么总感觉错过了什么。在这一天,“购物”毫无疑问成为头等重要的事情。
阿里巴巴经济体设计师共创
那么我们要对消费者表达“购物”吗,讲我们多么便宜,货品多么丰富,多么物美价廉?这些是消费者早就形成的认知,是我们不用表达大家都知道的事,它看起来并不是一个想法和观点。
还是购物,但肯定不是教大家怎么购物,作为消费者,购物能给我们带来什么?
有人说,购物能让我们吃饱穿暖,让我们出行方便,让我们安居无忧。
如果这些你都有,你为什么还要购物?
因为每个人都向往更好的生活!
为了更好的生活,我们需要通过物品的改善带来心理的满足感。当然也有人会会说,满足感也可以通过其他的方式获取,比如关爱他人、亲近自然、学习、修行、冥想等等,我们非常认同,更好的生活当然不仅仅只有购物。但我们当下探讨的范畴仅仅只是“购物”以及“购物”能带来的满足感,对这种满足感的期待,是每一个消费行为的动因。比如你想要买一件新衣服的时候,其实你已经在期待穿上这件新衣服的样子,你在挑选一件礼物的时候,已经在期待他人收到这件礼物时的反应……
双十一,全年最便宜的一天,无疑让你的期待,变得“更值得”期待,所以“更值得”让大家买得更多。
但,这些洞察还只是帮我们理清了消费行为背后的共性规律,实际上,细分到每个消费者,因为身份角色生活方式的不同,动因各自不同,还不能简单的用向往更好的生活来概括,因为它太抽象,听上去对,但每个消费者更关心的是我的需求是不是被满足,而对于双十一来讲,我们就是要打造属于每一人的双十一,而不仅仅只是购物,这样它才具有节日的文化属性。
所以,我们开始探寻真实的消费者故事,寻找那些通过物品让生活变得更好的故事,这些真实的故事,给了我们很大的感触。我们发现,购物行为下,其实还隐藏了每一个消费者内心更深层的需求,它是一个个藏在心底的愿望,正是这些不同人的愿望,成就了每一个平凡人鲜活的人生。我们想要帮助他们实现自己的愿望。在双十一当天,帮助每个消费者“愿望11实现”!这才是双十一更应该满足的消费者需求,它不仅仅是购物,而是通过物品价值上升到情感价值,这样的品牌,才真正能够让人感受到温度。在倾听这些故事的时候,我们的阿里女神被感动了,她主动要求帮我们写一首歌,她想把她的感动通过音乐的方式记录下来,配合我们精选出来的11个故事,讲给大家听。
“logo出来了?”低沉而沙哑的声音,把我们从自我陶醉中唤醒,我们找到了想要表达什么,但和怎么表达之间还隔着上百个logo方案。
于是,我们开始了一轮又一轮的打磨,打磨的的重点放在了如何表达“愿望11实现”这一主题,这个过程中,有两个大方向上的分歧:
一个大方向是表现“愿望”,因为它比较有画面感,也比较容易表达。
另一个大方向是表现“实现”,因为它是对结果的描述,更符合消费者对结果的预期。
在纠结挣扎过后,我们选择了把两个方向融合,剧情貌似又回到了熟悉的设计故事,“把这两个方案融合一下!”我相信做设计的朋友,一定反复听过这句话,没听过的朋友,那说明你做设计还不久,我保证在你今后的职业生涯里,这句话一定会反复出现。(一个会心的微笑)
融合说起来容易,这么抽象的文字怎么转换成图形表达,同时还要和猫头+11.11融合,为什么要和猫头+11.11融合呢,因为这是我们重要的品牌形象资产,从2015年开始,猫头+11.11的组合就固定下来了,这意味着logo的80%的主体已经固定,我们的难点就在于在这20%的区域里,如何既要表达主题,还能做出和往年不一样的感觉。我敢向你保证,双十一的logo是所有logo里最难的,没有之一,至少是我十几年职业生涯里最硬的茬。
“愿望、实现、猫头、11.11”这几个词反复在脑海里萦绕,经验告诉我们,当面对如此复杂的局面,我们应该从里面跳出来,换个视角看问题,换什么视角?当然还是再次回到消费者视角,消费者愿望实现时是一种什么样的状态?是愿望实现时的满足?好像还差点意思,愿望平时也能实现,和在双十一实现愿望有什么不同?
我们认为,它应该是超越你期待的表达,从愿望实现时的满足,升级到愿望实现时的惊喜!这才是狂欢节该有的味道。当然,惊喜也有很多种它还不够有体感,如何找准惊喜体感?
答案是感同身受。于是我们开始了场景模拟,模拟消费者逛双十一的场景。
当我们来到双十一的时候:“咦!今年好像真的不一样!”
继续探索的时候:“呀!找了好久的idou同款原来在这里!”
准备下单的时候:“喔!真的很便宜!”
收到快递的时候:“哇!!!!”
听上去有点夸张,但这确实是我们想要营造给消费者的惊喜,当人感到超越期待的惊喜时,会不自觉的放大瞳孔、张开嘴巴脱口而出。这是人类共通的体感,是不用解释就有的共鸣。这让我们瞬间被点亮了,“惊喜到脱口而出!”我们一下子找到了核心创意。
通过反复尝试,我们发现气泡形的表达,不仅能成为承载所有消费者愿望的想法框,同时也能成为表达愿望实现时惊喜到脱口而出的对话框,把这个气泡形和猫头+11.11结合,这就是我们今年双十一主logo的原由,这个logo和以往双十一的logo最大的不同在于,它更像是一个容器,容纳不同人不同的个性化表达。它一改之前一直端着的状态,以一种更加亲民,更加个人化的方式呈现给大家。
当然,作为容器,我们还要把核心创意延展到线上线下各个场景。
双十一定制礼盒
走向全球的双十一
过去几年,我们向大家介绍过天猫双11的主风格的来龙去脉,一定会在创新的基础上,保持一贯的传承。所以今年波普艺术的主基调还是会延续下去,问题又回到了我们如何在波普艺术这个大的基调下面,通过老元素的新组合,创造出全新的视觉感受。相比符号,视觉风格更容易表现“惊喜到脱口而出!”这个idea,它可以通过形色质构全方位的表达。当一个人“惊喜到脱口而出!”的时候,快乐的气场围绕在他周围,这些无形的从中心向四周放散的表现,看上去很像是圆形声波,同时它还能根据不同人的状态做动态变化,这就形成了一种设计语言,一种能用固定的形式做出千变万化的效果的语言。
当我们把它和波普艺术的主基调结合的时候,就形成了今年双十一独特的视觉语言,再通过形色质构的拆解,应用到各个场景。
装置应用
天猫双十一发布会现场应用
天猫双十一开幕盛典现场应用
天猫双十一许愿猫
天猫双十一,助你愿望11实现!
现在参与阿里巴巴设计官方微博@AlibabaDesign 的双十一话题互动,就有阿里设计限量周边好礼相赠!这个双十一,我们一起让愿望11实现~
作者:阿里巴巴设计
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
数据大屏的设计,并非是传统意义上的设计师或产品经理就能完成的。它需要将艺术家、科学家与企业家的能力集于一身,需要拥有对动态数据的把握能力、对产业经济与供应链的结构方法、对社会议题的捕捉与构造,以及宏观的视野和细致入微的匠人用心。可视化让冰冷的数据产生温度。
数据大屏是一个凝聚情绪的超级机器。
数据大屏不讲述传奇,它就是传奇本身。
在这块巨幕上,数据是公开透明的,它的变化在实时的体现着每一笔消费的数字。每个人都能看到,也会被传递到全世界每个角落。双11所带来的巨大能量与共振,我们需要一块巨大的屏幕来承载这份共情——这并不是一则新闻播报、一条统计数据,抑或一张图表就可以完成的。在这样一个狂欢的日子里,手机、个人电脑、电视机这些面向个人的设备,全都需要融入到这个巨型的超级情绪机器之中。
从宣传与商业作战的角度讲,数据大屏需要兼顾故事性和震撼性两重特点。通过故事脚本与内容框架的设计,让观众层层抽丝拨茧,从表层的情绪,看到内核的战略。
2019数据大屏的内容框架大致分为三个层次。
情绪层:GMV的节节攀升满足了媒体不断推升的情绪高潮。在日益萧条的国际环境中,中国的经济仍能屡创新高,每一位在双11买买买的中国人背后是一种爱国主义与中国信心的体现。
业务层:阿里的自我表达。阿里经济体在城市中继续深化的服务我们的消费者,数据成为城市可持续发展的新资源;而商业操作系统随着数字经济时代的到来,开始系统的服务我们的品牌与商家,在新的时代续写“让天下没有难做的生意”。
战略层:企业与国家发展同行。阿里的改变,反射了社会关系和社会结构。点击购物车就能买到全世界的东西,而对于国内市场,精准的人群定位、产业带的建设都让拉动内需变成一个大众都能参与的事情。
依据数据表现,双11当天的情绪高潮会集中0点和24点前后。24小时内,情绪的跌宕起伏,媒体向世界专递着这种情绪。现场,根据数据和情绪的变化,我们开始导演数据大屏在不同的时间段出现的镜头:GMV的弯道超车紧张窒息,晚饭过后是观看城市夜经济的最佳时机,还有“买遍全球的购物车”、“小镇青年”等进20个镜头。
为什么是弯道超车?
大航海时代是贸易全球化的开端,也是当代中国继续扩大开放,用一带一路、进博会等等新模式,承接人类当今世界发展的新格局所在。互联网与移动互联时代的到来,让中国得以弯道超车占据世界领先地位,而随之到来的数字经济时代正式开始了人类历史上的新商业文明。马老师说:打造新商业文明的时机已经到来。数字时代是我们面临的最大机遇,这个新时代最大的风险就是错失机会。
我们将这个核心理念融入GMV大屏的设计,正如逍遥子所说的那样“消费不是商业的终点,通过消费者来提升生产端生产契机,优化生产决策。”为此,我们导演了新商业文明的数据大戏:GMV屏中的赛道,3个镜头穿越了大航海时代、互联网时代,数字经济时代弯道超车的新商业文明,快进了商业文明的发展。
11.11当天的数据也被融入其中,赛道上奔跑着饿了么、盒马配送线和菜鸟的物流线,空中飘散的气泡是实时产生的交易热力。
△2019双11数据大屏-GMV弯道超车&3个视角切换
2019年,即使是在国际经济大环境衰退的今天,阿里的双11仍旧创造了新的商业奇迹:2684亿人民币的GMV的背后,是中国人为了家庭与自己而欢乐剁手,也是中国消费者面对全球大环境下对中国的强大信心。从2009年的电商大促,到11年后的全球狂欢节,阿里伴随着中国经济海洋的形成而不断掀起巨浪。李克强总理就曾经用双11的销售数据,来解答那些对中国经济感到不解的人们,让他们瞬间懂得中国经济是汪洋大海。
△2019双11数据大屏-历年GMV增速
2. 全球化:买遍全球的购物车
中国经济与中国消费者的贡献,是对全球经济的贡献。消费者购物车里藏着美好生活的愿景,打开了世界消费的新空间。天猫国际把来自全球78个国家和地区的品牌和商品带进中国,满足消费者的品质消费需求。买遍全球的购物车,更为世界经济增长贡献拉动力。越来越多国际品牌青睐中国市场,通过天猫国际满足中国消费者的需求。
△2019双11数据大屏-全球化
随着政策的推动,全国经济进入夜生活消费时代,大量的城市开始准备成为一座座不夜城。在这个新的消费增长领域,新商品、新商机、新消费模式、新空间与新玩法都层出不穷。在未来,理解夜晚的中国,或许比理解白天的中国更为重要。
△2019双11数据大屏-杭州经济体服务网络
△2019双11数据大屏-天猫商超网络
△2019双11数据大屏-杭州城市夜生活
14亿的中国人口、巨大的地域差异与文化差异意味着,每一种类型的消费人群都是海量的,都拥有现有经济理论所无法囊括的巨大潜力。小镇青年、银发一族、95后作为新消费崛起的代表族群,正悄悄改变着社会的消费结构。通过数据我们清晰看到:族群的喜好千差万别,数字化的新消费使得商家能针对消费者需求创造新供给。
△2019双11数据大屏-新人群,新消费
天猫创造的价值是真正支持品牌的数字化转型,不仅仅赢得今天的业务,更在于决胜未来。国潮席卷而来,智能商业魅力无限,全球供应链在动荡与智能化中全面转身。全方位重构产品创新、品牌建设,强化天猫与品牌之间的合作,这便是我们想在双11这天展现的万里品牌江山画卷。
△2019双11数据大屏-品牌榜
当GMV越来越逼近2684亿人民币时,炸裂感给每个人的冲击是:中国又诞生了一个新的奇迹!即使在全球经济放缓的今天,中国人民对于天猫双11全球狂欢节的热情丝毫不减。在这背后,是数字经济时代下的阿里巴巴,向新商业文明迈进了一大步。
作者:阿里巴巴设计
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
前言
互联网瞬息万变,在产品不断更迭的过程中,我们经常说要保证产品设计的一致性和质量,提升产研链路的效率。但现实情况是:产研团队长期面对的是产品越来越复杂,体量越来越大,一个个复杂的产品下包含N个业务线,N个业务团队,甚至还有外部合作的业务,每个迭代都要面对数以百计的功能上线,经常容易出现各种相同但不一致的功能,上线质量参差不齐,执行者也容易陷入日复一日的需求海洋而没有更多精力去挖掘更有价值的事情。
所以如何解决团队效率和产品质量问题?我们的解法是抽象体系化的解决方案:设计模式化和代码化,设计从原子到全局进行统一和优化,并形成系统化的设计指导,由开发进行模式代码化,提供灵活可配置的规则。以此,设计有更系统化的设计原则,整体的统一性和体验有保障,设计和开发周期也可以缩减,甚至大部分日常需求可直接由产品对接开发直接上线。
1.1 什么是系统化解决方案?
大多数日常需求大多是从单点出发,当点变多变复杂了,就容易出现上述说到的现状问题。所以解决方案需要基于业务全盘进行设计抽象:从元素——组件——区块——页面——功能流程沉淀设计规则并代码化,来灵活提供拼装N个不同页面的机制,帮助团队更系统化的进行产品设计。从组成内容不难看出,解决方案是需要建立在基础组件基础上,与基础组件、复杂组件、行为模式共同组成设计系统的【功能模式】部分。
解决方案是一套相对稳定的设计机制,所以在产品初期或团队建立初期,产品可能经常会调整的情况下,并不适合做。初期可以借助成熟的设计系统来减少投入成本。而到成长期可以根据业务的发展梳理基础元素、组件,选择性的建立部分稳定且利用率高的解决方案,并持续发展,保证解决方案可以起到指导和提效的作用。随着产品或团队逐渐成熟,解决方案也应该随着一起成长,相互影响相互作用。
1)对产品页面(尤其是重点功能)进行盘点,划分页面类型:比如列表、表单、详情、dashboard;
2)对页面中的内容进行区块归类
3)对区块中的信息进行拆解
这三个过程下来,对于问题、规则、规律都会有一定的概念。以一个后台系统为例
1、页面大类主要是:列表、表单、详情。
2、其中列表的内容大致区块分为:页面标题区、列表操作、列表筛选、列表内容,到这个阶段已经可以发现,相同区块位置就存在不稳定,在后台系统中可能影响面不会非常大,但对于内容复杂繁多的工具或C端界面就会容易出现找不到的情况。
3、不同区块的内容拆解,同样也会发现一些细节问题,比如筛选的样式、规则不一致,列表操作的方式、位置、样式、交互不一致等等
2.2、抽象、重组:从布局——区块——组件——设计规则
从第一步全盘的信息拆解和归纳, 已经发现问题, 这一阶段主要2点:第一是如何通过设计规则来避免同样的问题产生,第二是如何通过简单的规则重组减少多人合作记忆复杂度。思路类似于设计一个界面,首先得有一个布局划分,不同的区块要放哪些内容,再到区块里的细节内容规则,从而抽象出由布局到区块的设计规则和可复用的组件。
以前面说的列表为例
1) 区块主要是4类,明显的问题是区块位置不稳定,所以在布局结构上,需要定义1-2个稳定的可配置的布局框架来适应不同的内容
2)不同区块梳理组成内容,内容细则
3)标记出可组件化的内容及规则
4)提炼整个过程中通用的设计规则,作为全局的指导。如:国际化、排版规则、超限规则、适配规则、文案规则等等。
通过布局——区块——组件——设计规则,可以灵活的进行页面拼搭
区分通用层和业务层,通用层落地到通用模板市场,利用脚手架生产新页面。业务层面的落地则是基于通用库封装具备业务属性(如:业务主题、业务数据、业务拓展方案)的业务库来生产新页面。
目前群核设计团队建立了一套平台通用的解决方案,适用于所有中后台产品。业务属性比较强的产品也基于通用解决方案封装业务层面的解决方案,同样的思路也应用在不同体系的工具场景中。整体实践下来,产研效率提升近50%,甚至完全解放了一条业务线的设计资源。产品体验的一致性、上线质量也有明显的提升
三、解决方案的管理和发展
解决方案作为设计系统的一部分,与设计系统一同管理,业务设计师使用系统来输出,反馈问题或需求给系统,有系统设计师判断可行性,周期性的管理,及时更新并在内部互通,促进互相成长和发展。
解决方案与设计系统的发展有一点不同的是解决方案有更多业务化的内容,业务团队根据业务迭代维护解决方案。当业务的方案达到通用级别,则列入到通用库。
这些方法和思路也并不限制行业或产品类型,仅是在我们当前服务的产品体系下总结的方法。当然解决方案并不能解决所有问题,只是希望在提供更系统化的设计方法和模式的同时能减少重复工作提升效率,让产研团队有更多的精力和时间投入更有价值的事情。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
前言:
对于B端而言他们使用导航菜单目的性很强,到后台主要是对具体功能进行操作。因此,其主要的功能就是对B端产品进行分发、引导,帮助用户找到自己想要的功能。
分享内容:
1. 导航是什么,存在的意义
2. 导航的设计目标
3. 其设计原则
4. 设计建议
5. 几种常见的导航类型
导航用来展示当前产品中,用户在哪儿,可以去哪儿。在广义上,任何告知用户他在哪里,他能去什么地方以及如何到达那里的方式,都可以称之为导航。当设计者使用导航或者自定义一些导航结构时,请注意:
a. 尽可能提供标识、上下文线索,避免用户迷路。
b. 保持导航样式和行为一致或者减少导航数量,降低用户学习成本。
c. 尽可能减少页面间的跳转(例如:一个常见任务需要多个页面跳转时,请减少至一到两次),让用户移动距离保持简短。
导航菜单是将内容信息友好地展示给用户的有效方式。在确定好网站的信息架构后,应当按需选取适当的导航菜单样式。
导航菜单是让用户明确知晓当前所处产品中的位置,并方便快捷地带用户到他想去的地方。
用户可定位到他们想要的信息。
a. 多接入点:对同一目的地提供多个链接。
b. 捷径:提供访问内容的捷径,如相关链接。
c. 逃生舱:点击 logo 回到首页重新启动信息搜寻。
• 设计时应尽量保持浅平宽的信息架构层级;
• 从用户的使用路径考虑导航,而非仅基于层级结构;
• 常见的组织方式有:
a. 按主题,例如产品提供的服务或内容分类,好处是直接呈现站点的内容范围。
b. 按受众群体,例如管理员、运营、操作员。
c. 按任务,例如了解合作模式、联系合作专员、签约流程、合作联调、业务运营、客户服务。
完善的导航应该允许用户沿多种路径移动:
a-平移:同层级跳转
b-下钻:进入低层级的内容
c-返回:返向浏览历史或高层级内容
d-联想导航:根据相关性导航至内容
正确理解和使用导航组件对产品全局体验至关重要。
我们将导航划分为以下 6 种类型:
a. 全局导航(侧边导航、顶部导航、弹出式导航)
b. 子站点导航(沉浸式导航、多级站点导航)
c. 页内导航
d. 下钻类导航
e. 返回类导航
f. 联想类导航
全局导航体现网站的核心组织结构。
a. 各菜单权重常常与排列顺序呈正相关,即排列顺序影响用户使用频次。
b. 建议 2~7 项内容使用。
c. 建议 1-2 个层级;超出 2 个层级时,建议采用弹出式导航。
垂直导航较横向的导航更灵活,易于向下扩展, 且允许的标签长度较长。类目数量不限,可配合滚动条使用,适合信息层级多、操作切换频率高的管理性质的应用。
a. 很多菜单时使用,建议菜单多于 6 项时使用。
b. 可以承载多个层级,但建议 1-3 个层级。
c. 企业级产品推荐使用侧栏导航,其可见性更好易于扫读,各菜单重要性受菜单排列顺序影响较小。
用于拓展导航承载层级,适用于大型网站。站点地图式导航可以让用户对整个网站的可用功能一目了然。
a. 不要让用户延着狭窄的悬停路径获取导航菜单。
b. 不要让用户逐层打开每层菜单去查找,低效又困难。(此建议仅针对导航类菜单,不适用于操作类菜单。)
子站点导航(沉浸式、多级站点)
企业级产品常采用层级+数据库混合结构的信息架构,这种信息架构通常层级较深,为了实现用户感知层面的浅平宽,将较深几个层级组织为一个子站点,降低单个站点层级数量,减轻用户认知负担。
另一种子站点场景是,面对一些任务复杂,需要较大的工作空间,以子站点的方式沉浸式处理任务。最常见的是编辑器。子站点模式下,对全站导航功能需求低,通常只需提供一个返回上级或回到首页的出口。
(此处的数据库是一种信息架构形式,各页面内容独立,但都遵循一致的形式/格式。)
a. 菜单数量较多的子站点使用。
b. 子站点设计上,应明显区别于全站导航,使得进入子站点需要成较大的过渡波动,提示用户进入了新的空间。
点击进入信息架构下层内容,默认站内跳转,站外新开标签页,典型场景为列表下钻至详情。
反映当前页面在网站结构中的位置,在少于三个层级时无需展示,此时的全局导航能直接呈现位置。用户可通过面包屑返回上级页面。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
你一定也发现了,在这个时代下只要处在同一赛道的产品,大多数免不了日趋同质化,不论是功能还是形式、内容还是算法。就拿内容型产品来举例:去掉颜色,以下四个产品的首页,你能分得清谁是谁吗?
虽然可以做品牌差异化,但很多产品在招牌打响前,也会经历相当漫长的一段成长期。而在大家都卷的分不清谁是谁的时候,通过趣味性来建立情感链接倒是一个独辟蹊径的狠招。
之前在团队有幸负责过一次产品趣味性的探索,虽未全部落地,但沉淀了不少经验。今天,我将这些经验以文章的方式分享给你,希望对你有所启发。
为什么产品需要趣味性
1.符合本能需求
马斯洛的“需求层次理论”(Hierarchy of Needs)你一定听过。这个理论提出人类的需求状态是持续不断的,一段时间内,个人的动机或许可以获得暂时性的满足。但从人的成长角度来看,人类的需求永远不会止步于一个阶段。类似的还有亚伦·瓦尔特基于此提出的需求层次理论——有用的、可靠的、可用的、愉悦的。在满足了温饱阶段后,自然会需要情感的满足。
尤其现在这个相对严峻、内卷成风的时代下,大家都渴望在上了一天班之后,自己用的产品可以给自己解压,而不只是个单纯的冷血工具。
2.影响用户行为
产品传达趣味性的信息,那么这些信息便更容易被用户接受、甚至激发兴趣。正如用户关系和精准化运营的先驱——史蒂文·贝莱格姆的那句话:“趣味是设计产品中最人类化,最直接,最能引起人们兴趣的因素。”
根据艾达模型这个经典理论,一旦用户对某件事产生兴趣后,则更容易促成他的决策、乃至最后行为的发生。毕竟我们绝大多数的决策并非源自理性。
另外,《疯传》这本书提及过一个重要的概念:情绪唤醒。当情绪唤醒度较高时,往往会更容易激发用户的分享行为(比如愉悦、惊喜这样的正面情绪,当然也会包括愤怒、恐惧这样的负面情绪)。而产品所呈现的趣味性,很大程度上更容易引发用户的高唤醒情绪,从而主动采取一些产品希望他实施的行为。
3.缓解负面情绪
使用一款产品的过程中,必然会出现不够正向的反馈,比如404、断网、填写格式错误、输入隐私信息等等。一旦超出了用户的容忍能力,很容易造成行为中断甚至流失。
但此时如果通过一种趣味性的方式呈现,很大程度上可以缓解用户的负面情绪。
比如readme的登录页面,当你输入密码时,猫头鹰捂住双眼,很好得缓解了用户的不安全感。再比如饿了么,恶劣天气下会实时记录当前的天气状态,通过同理心的触发也可以缓解用户等待时产生的焦虑。
4.实现情感链接
对应诺曼在《情感化设计》中的「反思层」,通过品牌差异、独有内涵等方式来唤起用户深层的记忆和情感,产生情感共鸣,从而形成认知,建立起深厚的情感链接,以让用户越来越忠实于产品。此时产品作用更多的是作为一个承担情感的载体。
我们旅游时经常会购买一些貌似没什么实用性的纪念品,明信片、木雕、徽章等等,可能最多只能放在房间当装饰。但是纪念品的关键意义,在于承载着旅游时的记忆。类似的还有单机游戏发售时,除了标准版外,往往会同步发售包含手办、美术设定集等物品的豪华版,同样用来承载玩家的情怀。
最典型的例子,就是Google的Doodle设计,在不同的节日、纪念日、或者缅怀一些先驱、艺术家时,会投入很高的成本起设计各种趣味性的插画logo,丰富有趣,而且很多时候可以支持互动游戏。比如18年的万圣节,就做了一个有趣的多人对战的小游戏。
Doodle不仅是Google文化、价值观的象征,也通过这种趣味性的创意让用户实现更深层次的情感交流,建立情感纽带并增加粘性,不会轻易得流失到其他替代品中。
趣味性是什么
关于这个概念,有三条相对客观专业的说法。
德国古典哲学创始人康德《实用人类学》中,指出趣味性是人类由于受到外部环境的影响而对客观事物产生的直接的感性反应,不具有普遍性和必然性。通俗来讲是人们对身边的事物感到新奇、有趣、有意思,从而心理产生愉悦的变化。
另外,康德在《判断力批判》也指出,趣味是指主体以积极主动的姿态,在超功利的层次上对某一对象表现出喜爱和愉悦的情绪状态。
《汉语词典》将趣味释义为:使人感到愉快,能引起兴趣的特性。
看似各不相同,但根据这三个概念提炼下,可以发现他们都是在融入一种正向的情绪加以解释。
那么,趣味性背后的情绪到底是什么?
趣味性背后的情绪
情绪这东西千变万化,为了避免主观臆断的不准确,我搜罗了关于情绪的研究资料,目前相对权威的有三个:plutchik情绪轮、日本感性工学、诺曼的三层理论。
后两者未对情绪进行指标的定义,所以这里只聚焦在情绪轮上。
情绪轮由罗伯特·普洛特契克开创,这个理论指出人类的基本情绪一共由8种,生气、厌恶、恐惧、悲伤、期待、愉悦、意外和信任。撇去那些负面情绪,能够符合趣味性带来的情绪基本就三种:愉悦、期待和意外。
为了为产品所用,就需要提炼出更具象的特征。下面,我通过脑暴的方式对每类情绪进行发散联想。
联想了大量可以引发愉悦情绪的事物,比如可爱的猫猫、各类好玩的表情包、毛绒玩具、影视作品里那些会突然说话的动物、解压神器等等。
我发现这些事物,一般具备了以下特征:
1.会给予我们即时反馈;
2.拟人化,比如带有人类才有的表情,或者是人类的动作语言;
3.柔软的、毛茸茸的、圆润的、有温度的;
4.会动的、解压的
联想了大量可以引发期待情绪的事物,比如被买爆的泡泡玛特盲盒,商家售卖的福袋,各种诸如掷骰子、老虎机的随机性游戏,各类抽奖活动,一些游戏开局roll属性点的环节(比如金2、河洛等游戏,当年玩的时候我惊讶得发现我居然能roll上一整天)
这些事物大致会带有这些特征:
1.未知的、神秘的;
2.满足人类的好奇心的;
3.对结果能满怀希望的;
4.随机的正向奖赏;
5.只需要一步简单到发指的操作(打开容器、拉动拉杆、点击按钮等等),不需要任何的行动门槛,也没有等待时间
比如《设计中的设计》这本书中提到的出入境的印章案例、一兰拉面碗底的细节、电影或者游戏中突然出现的彩蛋、iPhone第一代的发布现场、突然发现的世外桃源、突袭的生日惊喜等等。
这些事物大致会带有这些特征:
1.超出原有预期的;
2.未提前告知的;
3.正向结果的;
4.产生共鸣的
那么,这些特征又该如何更具象得落地成策略,产品中又是如何利用这些策略,引导用户产生这些情绪的呢?
下面,我将基于这三类维度,并结合大量的大厂案例来为一步步你抽丝剥茧。
如何制造愉悦感
基于上面的挖掘,我发现能引发愉悦感的一个明显特征,就是拟人化,这个非常有意思。
克利福德·纳斯在《the man who lied to his laptop》这本书中提出过一个观点:人们对待电脑的方式,与我们在与他人交流时使用的社会规范是一致的。我们对电脑的反应,就好像它们是人类一样。
也正因此,很多原本没有生命体征的物体,一旦被赋予了拟人形象,就容易被大家所接纳、引发愉悦。
做一下细分的话,拟人化基本涵盖了三个方面:语言、形象和表情。
1.1拟人化的语言
人对于具体的拟人形象更容易产生情感,其中便包含拟人化的口吻。使用这种口吻与用户交流,而非冷冰冰的设备语言,就可以提升产品温度,引导用户对产品产生正向情感,关于这块我总结了两方面。
一方面,文案尽量得接地气、轻松。
拿京东这款产品举例,文案从口吻上都会采用一种很轻松、接地气的拟人语气,来拉近和用户间的距离。比如在引导词后加入“哦~”“~”这种轻松的语气词。
另外,「京东问答」版块的引导词:传统产品是“添加问答”,是不是平平无奇。但京东使用了“我来助你一臂之力~”
QQ浏览器的评论暗提示,也不是传统的“写评论”“输入评论”,而是像日常聊天那样“我来说两句”,非常接地气、生活化。知乎直接套用了流行语,诙谐的同时引导用户注意言论。
另一方面,人类富有情绪变化,所以一旦用户触发了某个正面/负面行为,就可以给与情绪反馈。
在用户完成商品评价后,京东不仅仅是很传统得反馈“评价成功”,而是又加入了“感谢您!”的感谢词。看似加了三个字,但行动过后的正向感激反馈,和我们日常生活中受到正向反馈一样,被唤起正向的愉悦情绪。并愿意继续这个行为。
知乎的圈子业务,在用户完成签到后,反馈完成的基础上,使用了正向的情感反馈“ 我就知道你会再来看我”,仿佛是一位关系不错的老朋友,来唤起用户正向情绪。
另外,知乎在改版后,回答收到赞也不仅仅是“点赞成功”这类冷冰冰的机器语言,而是“已收到你的喜欢,谢谢!”并且配以作者头像,非常形象得传出对对方对我们的感谢反馈。这种正向反馈,相比”点赞成功“更有效得唤起正向情绪,形成正向激励。
当然,用户的消极行为也可以产生情绪反馈。
比如现在很多产品的取关反馈,不是传统的“确认”“取消”,而是用“残忍取关”和“给个机会”这种融入情感的文案来做挽留,就好像真的有个活生生的人,在被你否定后反馈给你的情绪,请求你再给个机会。有趣的同时一定程度上也可以唤醒用户的同情。(虽然这种做法过于业务导向,且有绑架用户的嫌疑)
人对于具体的拟人形象更容易产生情感。因此,树立一个拟人化的品牌形象(也就是我们常说的IP),可以使用户对产品快速建立情感联系,也可以强化品牌感知。比如优酷的猴子、高德地图的老鹰、钉钉的燕子、linefriends等等。
世界杯的吉祥物也是同理,每一届的吉祥物必定会融入拟人化的特征,凭着亲切、可爱的形象和全世界的观众一起热情互动。
说到产品,以京东的这只狗子为例,兜底页、缺省页、启动页,都做了狗这个拟人形象的贯穿。
如果你记得没错的话,形象改版之前这只狗还是四脚坐地上的,但改版后就两脚着地了。这同样是通过更拟人的形象来赋予「人格」,以此提升愉悦感,拉近距离。
再比如夸克,它甚至单独新增了底tab入口,通过3d品牌形象,实现与用户的智能交流。另外,卡片和语音搜索都做了形象贯穿,后者还使用了形象的眼睛,来通过眼睛的变化进行情感交流。
一段文字在加入了表情后,我们就能迅速了解到对方的情绪(比如我们日常的聊天)。因此表情这一招,也可以用以加深用户与产品的情感交流。
它可以和语音、形象灵活结合使用。通过全面的拟人化,来大幅提升用户对产品的形象感知,促进用户情感的产生、加深用户和产品的情感链接。
比如用语言输入,当系统未识别清楚时,传统的方案可能是一个错误图标加上「未识别声音,请再试一遍」的文案,但夸克中则使用了品牌形象的双眼来传达失落的表情,并结合了拟人化的语言。
同样做法的还有百度的未开麦提示、段子头图,快速传达产品的情绪,和用户情感交流。
另外,闲鱼设置昵称时,正在输入时、以及超出了限定字数,右侧的形象表情也会发生变化。
除了拟人化能引发愉悦外,还有一个很容易被忽略的要素同样能起到作用——动画反馈。
提起动画的作用,我们更多人首先想到的可能是强引导、注意力吸引,但动画同样能触发用户愉悦。比如当动画更加贴近现实,或者结合拟人化的特征时。
最典型的例子,就是苹果appstore从卡片到详情页的转场过渡,点按的预备动作、卡片的放大展开、缓出的曲线、转场完成的惯性等等,都很贴近现实物理世界的运动规律。而这种自然的丝滑也更能引发用户的愉悦。
比如知乎的加载动画,结合了拟人化的特征,让这个过程变得格外有趣。刘看山奋笔疾书的动作也很符合知乎这款产品的定位。
如何制造期待感
根据我们对期待感的脑暴,发现能触发这种情绪最典型的特征,就是随机性。
在产品中,则可以通过随机给予用户有价值的内容来引发用户的关注。结果的不确定使过程充满神秘,这种状态让用户兴奋和满怀期待,不会轻易离开。
比如网易新闻在进行新闻加载的操作时,会在加载动画上加入一个随机的小贴士。内容大致有三类:黄历、名言和一句话新闻。而且这些内容完全是随机出现,这种刷新过程好像在开盲盒,你无法预知会给你什么内容,非常有趣。而且它们入口深,不干扰;内容对用户也具有价值。
同样的还有丁香医生。在首页每次下来刷新后,都会出现不一样的健康类的小贴士。随机性很强,富有趣味。而且可以很好得满足求知欲。
雪球的个人页上,上划时也会随机给一条关于投资、市场的名言和网友的热门观点。
把现实生活中的游戏搬到了手机,同样能激发用户的好奇和期待。比如微信读书的翻牌子、摇一摇、老虎机。还有天猫的翻牌子。
如何制造惊喜感
格雷戈里·伯恩斯曾做个关于脑部区域的研究,发现大脑不仅探寻未知,而且实际上还渴望未知。相比那些已知的日常惯例,新鲜新颖的事物更容易引人注意。所以提供一些以往没有过的、出乎意料的事物或互动,不但能引起注意力,而且也会带来惊喜感。
产品中的惊喜感设计,可以说是使用瞬间能够触达我们内心的一种短暂性愉悦,一个体验的峰值。
根据对惊喜感的挖掘,一个典型的特征就是超出预期,并且未提前预料。
比如Figma使用过程中,团队成员和你同时长按鼠标,并且靠在一起后,就可与触发击掌的动画彩蛋,非常有意思。
比如京东在五星好评后,会放烟花庆祝。
比如很多内容型产品,在详情页长按点赞按钮后,会触发全屏的「爆赞」动画。
再比如用户生日当天,通过启动页、弹窗等场景送上生日祝福等等。
这些能够超出用户预期的细节,即便多么细枝末节、出现频率多低,但只要能够被触发,都可能因为惊喜感而生成情绪峰值,加深用户和产品的情感链接。
最后
以上,便是趣味性背后的情绪挖掘和案例讲解。
作为设计师,我们在工作中总是强调以用户为中心,通过不断的调研、跟访、可用性测试来了解、分类用户,但更多的结果可能只是让产品不断得贴合用户的心智、符合预期。但如何增加粘度、建立深层链接,就需要通过趣味性这种情感化表达方式,来为用户制造愉悦、期待甚至惊喜。
正像诺曼在「情感与设计」中说的那样,每个产品都需要令人更放松、更愉悦的设计,以此来增强产品的适用性。
希望这篇文章能对你有所启发。
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
在上一篇《如何设计高质量B端调研问卷?用户研究方法(一)》一文中,详细介绍了如何通过调研问卷的方式,整理发现用户的浅层需求。
本文将分享另一个更为深层全面的B端用户研究方法——用户访谈。通过面对面地沟通,以及观察用户的表情、行为去挖掘更深层的需求。(文末附模板下载链接)
下面是本次分享的文章结构,标⭐️的为重点部分。
01
用户访谈的两种路径
用户访谈通常可以采用线上会议、电话或者线下面对面交流两种形式。
线上会议和电话访谈的优势显而易见,可以不受地域限制展开调研访谈,整体的成本也比较低,设计师可以自己找领导或者协调资源去推动。
缺点也是比较明显,沟通起来效率低下,比如我们一定有过“电话里说不清,我们见面详谈”的经历。
所以,相较而言线下访谈无疑是最佳的调研形式。首先当面沟通更加高效,其次对于搭载硬件设备的产品来说,让受访者在真实场景里操作演示,可以发现更多隐性问题。
02
常见的3种受访者类型
在访谈过程中通常会接触到以下3种类型的受访用户,不同类型的用户我们应该怎么接触交流呢?
话痨型的受访者占大多数,通常就是想法、意见比较多。他们不仅有一大堆不满意的点要诉说,甚至连对应的解决方案都想好了。
整体接触下来,我觉得该类型的受访者,可提供的有价值信息会更多一点。只不过我们要学会过滤信息。因为他们的修饰用词通常比较多,例如:“太难用”、“超级麻烦”、“哎 我真的是受不了啊”...
访谈话痨型的受访者,我们需要注意无论怎么聊都要紧扣主题,防止变成了吐槽专场。
其次也要表达肯定,安抚情绪,并对问题进行进一步提问:“您的这些建议真的很棒,也给我们提供新的思路,我都记下来了,我还想确认下,刚才您说的3个关于结算环节的问题,哪一个给您造成的困扰最大?”
顾名思义,受访者可能是因为性格内向或害怕说错了不好意思等缘故,在受访过程中问一句答一句,比较容易冷场。答案的价值也比较低,例如:“对,是的”、“还行吧”、“没啥感觉啊”、“反正就这样用用吧”、“说不上来”。
这种情况下要尝试缓解下氛围压力,换个形式沟通:“就是随便聊聊,公司派我们来呢,就是因为非常关注用户的使用感受,想收集一波用户反馈与建议,您有啥不满意的地方都可以跟我讲讲,越多越好。”
其次我们在提问的时候也要带有引导性,例如:“还有呢、然后呢、具体说说呢”,如果对方实在说不出所以然,最有效的办法就是进入上机操作环节,通常操作过程中那些问题也会随之暴露出来。
专业型的受访者一般是老板或者店长、经理岗位的员工,他们对于产品或整个门店乃至行业都了解的比较透彻。
和这样的受访者沟通,不仅是局限于产品的一些问题挖掘,他们会从这个行业的角度阐述一些个人的见解观点,给我们提供一些有价值的优化方案或者改进方向。
例如餐饮的老板其实并不是很关心点餐、结算的流程有多么的顺畅高效,毕竟他们不需要亲自去做这些执行,而且坦率的说,对于绝大部分产品而言,好用并不能成为其核心竞争力。
他们关心的是如何带来更大的商业价值,类似于如何提升坪效,如何提升门店会员的储值能力等等。而这些老板的关注点,也会给我们未来的产品优化方向打开新的思路,去思考如何给我们的用户创造更高的商业价值。
03
我们在做访谈之前,首先一定要熟悉产品业务的相关背景、受访商户的基本情况。
访谈过程中可能会提及一些专业名词,例如餐前餐后模式、明档、一鱼多吃、坪效、估清等等。我们具备了这些基础相关知识以后,省去了不必要的解释环节,可以使访谈推进地更顺利。
其次建议要了解下行业相关的基础知识。尤其是餐饮业态,华南、北方、四川等地都因为饮食习惯的不同,在产品的功能和使用上的需求也是相差很大。具备了这些知识点以后,在提问过程中,也更利于提出一些深刻的问题,而不是浮于表面的提问。
行业相关的分析报告,可以去艾瑞、36氪、发现报告等网站进行查询收集,在此不做赘述。
在访谈初期,需要准备一份访谈框架,但并不意味着我们整个过程只能按提纲来提问。尤其是B端这种千载难逢的访谈机会,只按规定的框架提问属实是太吃亏了。所以在时间允许的情况下,除了框架以内的问题,尽可能多发散的去提问。
例如餐饮业态,会有各种不同载体的终端设备联动使用,一体机POS、手持的POS、厨房KDS、厨显大屏、各类打印机等等都可以顺便了解、调研下,让我们对于全链路的协作流程也会有更深刻的认知。
再例如后厨会涉及到KDS大屏显示,可以问问目前的大屏显示是否够清晰(字够大),也可以和负责海鲜称重的工作人员聊聊,海鲜的售卖、计价流程,以及菜品双单位(例如:1【条】鱼,重量1【公斤】)的使用等等。
在沟通过程中,一定会有一些触类旁通的收获与问题被发现。这些问题也许来自一个模块、或者某个特定角色,又或者是主产品关联的其他后台产品。
在提问的时候要考虑到受访者的年纪和理解能力,如果措辞过于专业,可能会导致受访者理解不到位,因此沟通的过程中要尽可能的说大白话。
我们都知道没有经历过的事,很难感同身受。有时候看到客户群里,因为产品的各种原因导致商户情绪激动,我们理智上非常理解,但是情感上很难共鸣。
因此每次的门店调研,我都会抓住机会观察整个门店的运营情况,去感受那种忙碌的氛围。有时候开始进入营业高峰期,机器出现卡顿或者外卖不接单等情况时,自己的情绪都会一下子紧张起来,也能够深刻感受到产品给客户带来的困扰。
当再次有产品迭代优化时,这些体验总能让自己能更容易代入用户的角度思考问题。
除了去体会产品对情绪的直接影响,还可以关注下整个门店的布局、收银产品的数量与摆放位置、不同产品的协同使用等等。
这些都会帮助我们发现,到底什么是门店运营环节里最重要的模块。
在访谈过程中,不排除受访客户会反馈一些暂时无法解决的问题。这时候一定要告诉受访商户:“您的问题我已经记录下来,回去会针对这个问题反馈上报,最迟X天我会让顾问给您回复的”。
这么做一方面也是细节处维护公司品牌的整体售后体验,其次也有助于我们再次回访时,受访商户乐意花时间跟我们聊。
04
了解了用户访谈的一些基本信息和注意点以后,到了本文核心部分,关于整个访谈的推进过程,一共分为3个阶段。
首先明确访谈的类型,用户访谈的类型主要分为以下三种,最常见的就是第三种类产品使用回访。
新品调研的访谈,一般是由于业务的发展,可能需要升级或者打磨一款新产品来满足市场的需求。
在访谈的过程中,我们需要关注的点就是用户画像、商户诉求、使用场景、终端载体等一系列因素。
当去往一个城市进行批量客户调研的时候,偶尔会有拜访潜在客户的调研机会。这种类型访问的关注点集中在商户的痛点与需求上。
由于是潜在客户,我们势必是要了解他们想要购买或者替换产品的前因后果。也是借此机会了解到竞对的优劣势,他们放弃竞对的原因,以及我们当前产品的功能是否满足客户的需求,还有哪些点不满足,客户重点关注的是什么。
使用回访是最常见的访问类型,主要目的是对商户进行售后维护、提升使用体验。
且由于B端产品的复杂性和远距离特点,以及新功能同步给商户存在的滞后性,公司会安排定期的上门回访。如果有这样的机会,UED一定要尽量申请跟着去门店调研。
到了最关键的一步,就是关于访谈的问题设计。
总结一下,问题的设计渠道来源主要有3种。主要还是根据调研目标进行问题设计,另外两种方式,作为辅助。
那具体问题应该怎么设计,这边我们分为3个步骤,从面到点依次拆解进行问题设计。
B端产品的特点可以借用《U一点料》的9个字概括,“多场景、全链路、多角色”,所以设计问题前,我们可以从场景+链路+角色/节点功能的维度来设计问题。
以餐饮行业的产品举例,首先建议了解自家产品的全场景业务能力。即从商家端到消费端会经历的产品模块和具备的现有功能,做到心中有数即可,毕竟不会一下子调研这么多模块和功能。
其他ToB产品同理,可根据某个操作链路为主线,确定主流程后进行问题设计。其实就可以理解为确定调研的目标。例如下图要调研的主流程,就是提取点餐>下单>结算为主线。
根据刚才确定的主流程 我们找到所有关键节点进行问题框架设计。
以上3个步骤可以理解为,如何在功能极其复杂的B类产品当中,筛选出与访谈目标紧密相连的功能链路。避免我们的访谈提纲做的过于冗余没有核心。
这点也非常重要,每一次去门店调研之前,先由当地负责的顾问与受访商户提前沟通。
一方面是需要与对方预约时间,另外一方面这种访谈对商户而言就相当于优质的售后服务,会有受访商户提前列框架,准备问题。
那么这种情况更有利于调研,因为受访者明确自己的问题点在哪里,就等着调研团队(售后团队)来门店后,好好拉扯一番。
万事具备,只欠东风。我们再盘点确认下本次访谈的各类工具是否备齐,准备进入访谈阶段,大致需要准备的东西是以下4种材料工具。
在了解了受访者类型、采访者需要注意的点,以及带着我们设计好的问题,下面正式进入访谈阶段。
到达门店后,我们的顾问会给受访客户做一下来访成员介绍,并讲明此次到店的目的。一般都是产品使用回访,或者新功能推荐培训。
通常来说,受访商户对来访团队总是有很多”心里话“想唠一唠,因此暖场氛围比较容易起来,也有助于我们接下来的沟通
进入正题以后,我们会先大致了解下受访者最近使用的体验以及遇到的问题,而后会根据问题框架进行提问。在整个过程中也需要注意观察用户在描述问题时候的表情和肢体语言,搜集用户对于产品的真实态度。
其实整个访谈流程我们归纳一下,需要关注的就是四个关键点,手+口+心+颜。
关于实操演示放在第一个讲,是因为这个环节非常重要。线下访谈时强烈不建议“脱机访问”,这种形式对受访者而言需要花更多的时间去思考问题的答案。
会遗忘甚至想不起来当时的具体情况,随便的敷衍回答“嗯,还行”、“挺好的”,或者干脆彼此都不在一个频道上。
其次,在操作过程中,建议用手机进行拍摄记录。因为每个人对产品的理解方式与程度不同,在用户操作的过程中,会发现一些有意思的代偿方案。这些也是思考的切入点。代偿方案是否比原先设定的实现方法更便捷。
回去通过视频仔细分析受访者的操作路径,以及每个操作之前是否有迟疑等等。并且将问题点一一记录下来。
这个就是根据问题框架进行提问。在这个过程中,通常会穿插着上机操作演示,在现场记录的时候可以先记录个大概,等结束后再仔细整理。
问受访者的心理感受,其实就比较偏主观了,我们可以从“我们家的产品”和“人家的产品”两个维度去对比询问心里感受。值得注意的是,即使是这样开放式的提问,也有提问技巧。
比如“你觉得现在还有什么不好用的地方”就比“你感觉现在的产品好用么”这样的提问方式更有效。
因为产品一定是有可有优化的空间,“有什么不好用的地方”这样提问的方式就是具体到了某个点上面,具体什么点不好用。
而后者的提问方式是基于整个产品,受访者可能会出于不好意思等原因直接说“你们的东西还行吧,还可以”。
当我们提问产品优缺点的时候,受访者大部分都会带上表情和肢体语言,并且情绪表现和性格有比较大的关系。
性格比较雷厉风行、急躁一点的,通常会像连珠炮一样疯狂输出,并且措辞会比较极端。例如:“真的太难用了”、“太麻烦了”、“严重影响门店营业了啊”。
接受到这种信息我们首先要做的就是安抚对方的情绪,其次对于这些信息要学会剔除一些夸张描述。
而性格温和一点的受访者,在阐述问题的时候也会比较婉转。“不是太方便”、“这个改动没啥感觉”、“也还行、都可以”,如果某个高频操作真的很影响日常工作效率,往往会表现得很无奈,甚至还有点委屈。
那么无论是哪种表现,其实都要考验采访者的经验,结合产品的功能去考虑,找到反馈中真实有效的部分。
通过以上4个环节,其实就可以收集到很多有用的信息,语言信息、视频信息、动作表情信息等等。接下来的任务就是信息的梳理归纳。
访谈结束后,我们需要做个简单的总结回顾。将关键问题再次复述确认,进行查漏补缺并且再次感谢受访者,表达他们今天提的建议价值很大,后续会梳理出可落地的点优化到产品当中。
如果说聊得比较开心,大多数的受访商户都会邀请来访团队吃个饭再走,也算是额外的福利。在就餐过程中的非正式场合交流,也可以聊聊门店的一些运营情况等等,帮助我们更深入的了解这个行业。
我们在访谈过程中会有大量未整理的一手记录,结束后需尽快的梳理,尽可能详细的记录下用户描述的细节、肢体动作表情语言等等。建议使用石墨、语雀、腾讯文档等在线编辑工具,方便分享修改。(本文提供模板可下载)如果是连续访谈几位商户,我会在访谈结束以后,迅速地将刚才的访谈内容整理出一份原始资料。并罗列出一些受访者反馈但访谈提纲里没有的问题,去下一家受访商户的时候可以验证下该问题的普遍性。
② 整理落地
输出后我们需要再次进行提炼,将有价值可落地的问题点提取出来,进行汇报分享,并找到相对应的产品研发进行探讨排期,这样就形成了一个完整的闭环,真正做到了发现问题、解决问题.
③ 流程概括
前文絮絮叨叨说了很多,其实关于用户访谈这事用6个字就可以概括,简单理解:
问谁?问啥?答啥?改啥?
能够回答清楚这4个问题,那么这就是一次有价值的访谈经历。
ToB业务的特点就是会有一定的行业壁垒,设计师在刚接触的时候一定会有很多茫然时刻。对于各种专业词汇的一脸懵,对于行业的不了解。
或者很多人对于B端的认知还停留在,B端好像没啥好设计的,都是现成的组件库拖一拖,成就感比C端差远了。
那么做访谈、体验优化的意义是什么呢?
分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
蓝蓝设计的小编 http://www.lanlanwork.com