作者:Muzri 来源:站酷
蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请加微信ban_lanlan,报下信息,蓝小助会请您入群。欢迎您加入噢~~
希望得到建议咨询、商务合作,也请与我们联系01063334945。
分享此文一切功德,皆悉回向给文章原作者及众读者. 免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
作者:少源_Seybye 来源:站酷
蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请加微信ban_lanlan,报下信息,蓝小助会请您入群。欢迎您加入噢~~
希望得到建议咨询、商务合作,也请与我们联系01063334945。
分享此文一切功德,皆悉回向给文章原作者及众读者. 免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务、UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司
作者:九色鹿紫 来源:站酷
蓝蓝设计建立了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咨询、用户体验公司、软件界面设计公司
蓝蓝设计的小编 http://www.lanlanwork.com