在過去幾年的時間里,數字座艙系統已經陪伴車主朋友們經歷了兩個大的版本迭代。這期間每一次的優化、每一個新功能的推出,伴隨的都是大家對座艙體驗設計更高的期待。為此,NIO數字座艙設計團隊不斷總結經驗教訓,努力探索著最適合,最貼近用戶使用習慣的座艙體驗。我們也非常愿意結合以往的經驗總結,把我們的思考分享給大家:
兩年來,設計趨勢在更新,座艙駕駛體驗也發生了很多變化。較早的版本一方面設計風格漸漸遠離趨勢,結構布局不能滿足國際化的需求;另一方面,隨著技術的更新,一些交互方式也需要升級和優化。
這里要感謝車主朋友們的參與和反饋,使我們得以通過用戶沙龍,一對一訪談,線上調研,了解大家在駕駛體驗中的痛點,挖掘真實的需求。我們通過創建用戶畫像,體驗地圖,原型設計,驗證我們的假設,并用批判思維方法討論這些假設,逐步將有價值的概念打磨成具體落地方案。
在過去的兩個版本中,我們從建立基礎交互架構,滿足基本功能需求起步,逐步覆蓋主要的使用場景和功能定義,優化用戶體驗結構。
今天的3.0版本,我們希望能結合技術創新展示更自然的交互,在不同的場景下滿足多樣性的需求。在提升易用性的同時提高品質感。簡單來說就是“更易用,更高級”。
區別于移動設備,座艙體驗設計最大的挑戰來自用戶有限的操作時間和視覺注意力的管理。在安全至上的原則指導下,我們想要保證用戶高效率的操作,就必須確保交互架構更扁平、更淺層。在各種場景下提供必要的信息,保證用戶高效的理解,幫助用戶快速的決策。為此,我們提出了幾個體驗原則:
人類對于視覺的感知是不均勻的,眼睛在短時間內獲取大量信息,大腦無法快速、即時的作出反饋。所以,信息的呈現方式及其出現的時機顯得尤為重要。新版本的儀表盤,我們梳理了信息層級,讓重要的信息在關鍵的時間“恰如其分”的展現,用戶通過“瞥一眼”就能掌握盡可能多的有效內容,了解狀態的變化。
2.0 儀表盤界面
3.0 儀表盤界面
根據NHTSA指南,駕駛員的每次操作應該在1.5秒的掃視中完成,并且視線花在離開道路上的累積時間不超過12秒。如果希望用戶不用花太多注意力“瞥一眼”就可以看清時速變化,我們需要將2.0版本“窄而細”的時速字重加大;這樣視線從路面到時速信息盡可能的保持簡短和垂直。
我們常說“如無必要,勿擾用戶”。新版本的儀表盤,去掉了一些冗余的圖標,界面盡可能保持簡潔;區別了實線和虛線,并弱化了NOP時圍繞在“車”周圍的視覺表現,因為我們認為視覺的重心應該是路面動態的信息,而不是NOP本身的特效。
其次,在相關人因測試結果的支持下,我們將時速等重要信息移到視覺優勢區域內,避免了因角度問題造成的方向盤遮擋,同時也是希望到視線焦點盡量保持與路面垂直,縮短視線偏移帶來的安全隱患;天際線抬高以后,中間輔助駕駛顯示區域更大,提高了縱深感,可以看到更遠更多的內容。
我們重新劃定了信息區域:左邊為駕駛區域,將功率挪到時速下面;中間為ADAS區域,右側則是拓展信息區域。頂部區域我們留給了常出現的警報和提醒,左右兩側的邊緣留給非常見的提醒。
通過車速的快慢,調整視角高低,視角的變化更符合用戶實際駕駛時的感受,時速增加需要看到更遠的信息。
HUD之前的布局比較分散,路口道路指示和NP/NOP并不明顯,路口距離的豎狀進度條也比較難理解。所以新版本我們除了增加限速提醒,攝像頭距離提示以外,重新調整了布局:選擇字面率寬的字體,加粗路口方向圖標,進度條也改成便于理解的橫向擺放,同時增強了NOP變道時的提示效果。
2.0 vs 3.0 HUD
HUD 信息結構
自然的交互方式符合人們思想的延伸。用戶心理模型和行為認知越接近,實際使用過程中的認知負荷就越低,就會覺得操作簡單方便。我們希望用更自然更符合用戶預期的交互方式,喚起用戶的本能的意識,用最小的輸入獲得最高效的結果。
主頁右側的卡片,在2.0版本的實用價值并不高,僅在左下角展示了“能耗”,用戶通常只把它作為一個“我的車”的入口。新版本里,我們將用戶關注的信息直觀呈現在卡片上,用戶不需要進入詳細頁面就能掌握能耗信息;同時,我們把其他的內容也添加到不同的場景卡片中,用戶可以根據自己的習慣選擇常用的信息??旖輬鼍翱ㄆo了我們更多種擴展的可能性,比如未來可以加入組隊動態,換電下單,K歌歌詞等。
橫向滑動右側卡片切換內容
我們發現用戶在點擊app列表以后,75%以上的人是打開“設置”。新版本我們將“我的車”和“設置”合并,把入口提到了Dock欄里,這樣可以更快速的打開“車輛設置”
車輛設置放入Dock欄
2.0版本的空調調節,需要先喚起空調面板,再上下點擊或滑動溫度,這樣操作一是路徑長,二是上下滑動并不順手。啟動和調節空調作為常用操作,應該更自然,為此我們把它直接放在了Dock欄上,用戶可以點擊溫度旁邊的箭頭,也可以直接在數字上滑動開啟并調節空調。
點擊溫度旁邊的箭頭或直接滑動溫度調節空調
長按喚起風量圖標,左右滑動調節風量參數
新版本里我們還增加了一個操作,用戶可以將卡片下拉進入mini狀態,滿足在某些場景下用戶希望看到更大地圖的需要。
下拉桌面卡片變成mini卡
UI設計中的一個重點是協調和管理各個元素之間的關系,而非只是設計“元素”本身。布局的變化能引導人們視覺的焦點,幫助用戶理解外觀和功能之間的關系。
新版本的布局更加舒展,元素之間更講究“透氣”的留白,這使得界面有了視覺暫停,布局顯得松弛有度,同時也是暗示用戶何時專注和放松。
我們收窄了2.0布局兩邊的邊距,減少不必要的層級羅列,重新梳理網格系統,加大調節Bar,用戶在使用的時候更順暢。我們也更新了一些控件,比如2.0版本里按鈕,Tab,標簽樣式過于相近,新版本中我們做了區別。
新版本布局變化
視覺層級是UI設計中的核心要素,字體字重能幫助用戶辨別信息的主次,平衡內容,減少信息密度。較早版本只有常規和細體兩種字重,對于一個數字系統OS來說,僅用兩種字重難以滿足展示多重層級信息的需求。新版本不再使用2.0時細體的字重,增加了易讀性。
新版本有5種字重
新版本中對比度的優化提升了易讀性。按照WCAG的標準,常規閱讀文字最低的對比度應在4.5:1以上,而在駕駛場景下的理想對比度要求更高,至少應為7:1。高度清晰的文本有助于駕駛員縮短瀏覽和決策時間,減少分心,提高駕駛安全。
易讀性對比度規則
一直以來我們都在追求高級感的UI效果,讓內外飾都具備很精致的品質。之前的版本控件樣式比較厚而軟,過渡色有些模糊,藍黑色雖然取自品牌色系統,但是作為背景在數字界面里色相不明確,整體頁面氣氛有些“悶”,加上對比度不夠,調性顯得不夠“精神”。
3.0深色模式里我們用了黑色作為主色調,輔助色也更純粹,色相更清晰,飽和度更高,點綴蔚來藍為主的高亮色,使界面保證足夠的生動性。
新版本里我們重建了ADAS視覺識別模型,增加了細節,區分的車型更多;飽受用戶詬病的模型也做了調整。在儀表盤上,我們會逐漸優化自車模型,實現開門、充電等功能的動態展示;在中控屏,我們也通過渲染引擎的搭建,實現車輛模型的實時展示和交互,提升大家的專屬感體驗。
3D UI能夠更好的還原真實的場景,形成所見即所得的效果,避免抽象圖形給用戶帶來的認知負擔。這樣的處理方式也被運用在其他重要的場景里。比如座椅調節,香氛,氛圍燈,音場模式等。
氛圍燈是很有專屬感的界面。3.0版本的氛圍燈場景增加了預覽圖,用戶在切換顏色的時候,可以看到預覽效果。
這里要再次提到空調操作這個高頻場景,3.0版本用更加立體的界面取代了2.0版本扁平的圖標形式。前后排的切換的動效也更直觀,動效平滑。
空調前后排切換動效
感謝在3.0版本設計過程中,參與設計測試,并給予我們提供寶貴建議和反饋的所有車主朋友們。
以用戶為中心,關心每個用戶的使用體驗,設身處地站在用戶的角度和場景思考設計和體驗是數字座艙團隊一直以來的宗旨。這次升級,我們用時一年多,從早期的需求挖掘,到創新探索,設計驗證,再到最后交付走查。我們打磨細節反復驗證,與研發兄弟團隊協作聯調。大家既有激烈的爭執,也有無需言說的默契;會為了一個好想法的產生而興奮,也會為了沒有來得及實現的功能而遺憾不已。每個人都表現出對完美的極致追求,團隊成員雖壓力山大,卻也充滿激情!
3.0版本是一個新的開始,我們會繼續以用戶為中心,收集用戶訴求和反饋,努力帶給用戶超出預期的全程體驗!
就在本文發表的前一周,NIO OS Aspen 3.0 榮獲德國 iF 設計獎,感謝一路以來車主朋友們的陪伴與反饋以及設計團隊每一位小伙伴的努力與付出。
作者:蔚來數字駕艙設計 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
作者:Muzri 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
作者:少源_Seybye 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
作者:九色鹿紫 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
作者:ZDDONG 來源:站酷
藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加微信ban_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~
希望得到建議咨詢、商務合作,也請與我們聯系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者. 免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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 的有效期
-
// 記?。呵f不要把密碼加密到 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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
數據大屏的設計,并非是傳統意義上的設計師或產品經理就能完成的。它需要將藝術家、科學家與企業家的能力集于一身,需要擁有對動態數據的把握能力、對產業經濟與供應鏈的結構方法、對社會議題的捕捉與構造,以及宏觀的視野和細致入微的匠人用心??梢暬尡涞臄祿a生溫度。
數據大屏是一個凝聚情緒的超級機器。
數據大屏不講述傳奇,它就是傳奇本身。
在這塊巨幕上,數據是公開透明的,它的變化在實時的體現著每一筆消費的數字。每個人都能看到,也會被傳遞到全世界每個角落。雙11所帶來的巨大能量與共振,我們需要一塊巨大的屏幕來承載這份共情——這并不是一則新聞播報、一條統計數據,抑或一張圖表就可以完成的。在這樣一個狂歡的日子里,手機、個人電腦、電視機這些面向個人的設備,全都需要融入到這個巨型的超級情緒機器之中。
從宣傳與商業作戰的角度講,數據大屏需要兼顧故事性和震撼性兩重特點。通過故事腳本與內容框架的設計,讓觀眾層層抽絲撥繭,從表層的情緒,看到內核的戰略。
2019數據大屏的內容框架大致分為三個層次。
情緒層:GMV的節節攀升滿足了媒體不斷推升的情緒高潮。在日益蕭條的國際環境中,中國的經濟仍能屢創新高,每一位在雙11買買買的中國人背后是一種愛國主義與中國信心的體現。
業務層:阿里的自我表達。阿里經濟體在城市中繼續深化的服務我們的消費者,數據成為城市可持續發展的新資源;而商業操作系統隨著數字經濟時代的到來,開始系統的服務我們的品牌與商家,在新的時代續寫“讓天下沒有難做的生意”。
戰略層:企業與國家發展同行。阿里的改變,反射了社會關系和社會結構。點擊購物車就能買到全世界的東西,而對于國內市場,精準的人群定位、產業帶的建設都讓拉動內需變成一個大眾都能參與的事情。
依據數據表現,雙11當天的情緒高潮會集中0點和24點前后。24小時內,情緒的跌宕起伏,媒體向世界專遞著這種情緒?,F場,根據數據和情緒的變化,我們開始導演數據大屏在不同的時間段出現的鏡頭: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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的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.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司
藍藍設計的小編 http://www.syprn.cn