<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          vue-router 導航守衛中 next 控制實現

          seo達人

          使用 vue-router 的導航守衛鉤子函數,某些鉤子函數可以讓開發者根據業務邏輯,控制是否進行下一步,或者進入到指定的路由。


          例如,后臺管理頁面,會在進入路由前,進行必要登錄、權限判斷,來決定去往哪個路由,以下是偽代碼:


          // 全局導航守衛

          router.beforEach((to, from, next) => {

           if('no login'){

             next('/login')

           }else if('admin') {

             next('/admin')

           }else {

             next()

           }

          })


          // 路由配置鉤子函數

          {

           path: '',

           component: component,

           beforeEnter: (to, from, next) => {

             next()

           }

          }


          // 組件中配置鉤子函數

          {

           template: '',

           beforeRouteEnter(to, from, next) {

             next()

           }

          }

          調用 next,意味著繼續進行下面的流程;不調用,則直接終止,導致路由中設置的組件無法渲染,會出現頁面一片空白的現象。


          鉤子函數有不同的作用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,針對這些注冊的鉤子函數,要依次進行執行,并且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。


          以下分析下源碼中實現的方式,而源碼中處理的邊界情況比較多,需要抓住核心點,去掉冗余代碼,精簡出便于理解的實現。


          精簡源碼核心功能

          總結下核心點:鉤子函數注冊的回調函數,能順序執行,同時會將控制權交給開發者。


          先來一個能夠注冊回調函數的類:


          class VueRouter {

           constructor(){

             this.beforeHooks = []

             this.beforeEnterHooks = []


             this.afterHooks = []

           }


           beforEach(callback){

             return registerHook(this.beforeHooks, callback)

           }

           beforeEnter(callback){

             return registerHook(this.beforeEnterHooks, callback)

           }

           afterEach(callback){

             return registerHook(this.afterHooks, callback)

           }

          }

          function registerHook (list, fn) {

           list.push(fn)

           return () => {

             const i = list.indexOf(fn)

             if (i > -1) list.splice(i, 1)

           }

          }

          聲明的類,提供了 beforEach 、beforeEnter 和 afterEach 來注冊必要的回調函數。


          抽象出一個 registerHook 公共方法,作用:


          注冊回調函數

          返回的函數,可以取消注冊的回調函數

          使用一下:


          const router = new VueRouter()


          const beforEach = router.beforEach((to, from, next) => {

           console.log('beforEach');

           next()

          })

          // 取消注冊的函數

          beforEach()

          以上的回調函數會被取消,意味著不會執行了。



          router.beforEach((to, from, next) => {

           console.log('beforEach');

           next()

          })


          router.beforeEnter((to, from, next) => {

           console.log('beforeEnter');

           next()

          })


          router.afterEach(() => {

           console.log('afterEach');

          })

          以上注冊的鉤子函數會依次執行。beforEach 和 beforeEnter 的回調接收內部傳來的參數,同時通過調用 next 可繼續走下面的回調函數,如果不調用,則直接被終止了。

          最后一個 afterEach 在上面的回調函數都執行后,才被執行,且不接收任何參數。


          先來實現依次執行,這是最簡單的方式,在類中增加 run 方法,手動調用:



          class VueRouter {

           // ... 其他省略,增加 run 函數


           run(){

             // 把需要依次執行的回調存放在一個隊列中

             let queue = [].concat(

               this.beforeHooks,

               this.afterHooks

             )

             

             for(let i = 0; i < queue.length; i++){

               if(queue(i)) {

                 queue(i)('to', 'from', () => {})

               }

             }

           }

          }


          // 手動調用


          router.run()

          打?。?


          'beforEach'

          'beforeEnter'

          上面把要依次執行的回調函數聚合在一個隊列中執行,并傳入必要的參數,但這樣開發者不能控制是否進行下一步,即便不執行 next 函數,依然會依次執行完隊列的函數。


          改進一下:


          class VueRouter {

           // ... 其他省略,增加 run 函數


           run(){

             // 把需要依次執行的回調存放在一個隊列中

             let queue = [].concat(

               this.beforeHooks,

               this.afterHooks

             )

             queue[0]('to', 'from', () => {

               queue[1]('to', 'from', () => {

                console.log('調用結束');

               })

             })

           }

          }


          router.beforEach((to, from, next) => {

           console.log('beforEach');

           // next()

          })


          router.beforeEnter((to, from, next) => {

           console.log('beforeEnter');

           next()

          })

          傳入的 next 函數會有調用下一個回調函數的行為,把控制權交給了開發者,調用了 next 函數會繼續執行下一個回調函數;不調用 next 函數,則終止了隊列的執行,所以打印結果是:


          'beforEach'

          上面實現有個弊端,代碼不夠靈活,手動一個個調用,在真實場景中無法確定注冊了多少個回調函數,所以需要繼續抽象成一個功能更強的方法:


          function runQueue (queue, fn, cb) {

           const step = index => {

             // 隊列執行結束了

             if (index >= queue.length) {

               cb()

             } else {

               // 隊列有值

               if (queue[index]) {

                 // 傳入隊列中回調,做一些必要的操作,第二個參數是為了進行下一個回調函數

                 fn(queue[index], () => {

                   step(index + 1)

                 })

               } else {

                 step(index + 1)

               }

             }

           }

           // 初次調用,從第一個開始

           step(0)

          }

          runQueue 就是執行隊列的通用方法。


          第一個參數為回調函數隊列, 會依次取出來;

          第二個參數是函數,它接受隊列中的函數,進行一些其他處理;并能進行下個回調函數的執行;

          第三個參數是隊列執行結束后調用。

          知道了這個函數的含義,來使用一下:



          class VueRouter {

           // ... 其他省略,增加 run 函數


           run(){

             // 把需要依次執行的回調存放在一個隊列中

             let queue = [].concat(

               this.beforeHooks,

               this.beforeEnterHooks

             )


             // 接收回到函數,和進行下一個的執行函數

             const iterator = (hook, next) => {

               // 傳給回調函數的參數,第三個參數是函數,交給開發者調用,調用后進行下一個

               hook('to', 'from', () => {

                 console.log('執行下一個回調時,處理一些相關信息');

                 next()

               })

             }


             runQueue(queue, iterator, () => {


               console.log('執行結束');

               // 執行 afterEach 中的回調函數

               this.afterHooks.forEach((fn) => {

                 fn()

               })

             })

           }

          }

          // 注冊

          router.beforEach((to, from, next) => {

           console.log('beforEach');

           next()

          })


          router.beforeEnter((to, from, next) => {

           console.log('beforeEnter');

           next()

          })


          router.afterEach(() => {

           console.log('afterEach');

          })


          router.run();

          從上面代碼可以看出來,每次把隊列 queue 中的回調函數傳給 iterator , 用 hook 接收,并調用。

          傳給 hook 必要的參數,尤其是第三個參數,開發者在注冊的回調函數中調用,來控制進行下一步。

          在隊列執行完畢后,依次執行 afterHooks 的回調函數,不傳入任何參數。


          所以打印結果為:


          beforEach

          執行下一個回調時,處理一些相關信息

          beforeEnter

          執行下一個回調時,處理一些相關信息

          執行結束

          afterEach

          以上實現的非常巧妙,再看 Vue-router 源碼這塊的實現方式,相信你會豁然開朗。

          小白學VUE——快速入門

          前端達人

          文章目錄

          小白學VUE——快速入門

          前言:什么是VUE?

          環境準備:

          vue的js文件

          vscode

          Vue入門程序

          抽取代碼片段

          vue標準語法:

          什么是vue指令?

          v-bind指令

          事件單向綁定

          v-model:事件雙向綁定

          v-on事件監聽指令

          v: on:submit.prevent指令

          v-if 判斷指令

          v-for 循環渲染指令

          前言:什么是VUE?

          Vue.js(讀音 /vju?/, 類似于 view) 是一套構建用戶界面的漸進式框架。 Vue 只關注視圖層, 采用自底向上增量開發的設計。 Vue 的目標是通過盡可能簡單的 API 實現響應的數據綁定和組合的視圖組件。

          點擊查看原圖

          環境準備:
          vue的js文件
          使用CDN外部導入方法
          以下推薦國外比較穩定的兩個 CDN,把這些網址放進script標簽的src屬性下即可,國內還沒發現哪一家比較好,目前還是建議下載到本地。

          Staticfile CDN(國內) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js
          unpkg:https://unpkg.com/vue/dist/vue.js, 會保持和 npm 發布的的版本一致。
          cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js
          2.VSCODE軟件
          (2).使用內部導入方法(自行下載js文件放進工作區js文件夾即可)

          2.png

          vscode

          前往vscode官網下載對應版本的vscode

          點擊查看原圖

          Vue入門程序
          首先了解一下什么是插值
          插值:數據綁定最常見的形式就是使用 **{{…}}(雙大括號)**的文本插值:

          單獨抽出這段來看一下:
          Vue即是vue內置的對象,el(element)指的是綁定的元素,可以用#id綁定元素,data指的是定義頁面中顯示的模型數據,還有未展示的methods,指的是方法

          var app = new Vue({
                      el: "#app",//綁定VUE作用的范圍
                      data: {//定義頁面中顯示的模型數據
                          message: 'hello vue'
                      }
           });

          代碼如下:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>


              <script src="js/vue.min.js"></script>

          </head>
          <body>
              

              <!-- 插值表達式 獲取data里面定義的值 {{message}} -->
              <div id="app">{{ message }}</div>

              <script>
                  //創建一個VUE對象
                  var app = new Vue({
                      el: "#app",//綁定VUE作用的范圍
                      data: {//定義頁面中顯示的模型數據
                          message: 'hello vue'
                      }
                  });

              </script>

          </body>
          </html>

          抽取代碼片段

          步驟:文件-首選項-用戶片段
          輸入片段名稱回車

          復制以下片段覆蓋之前的注釋內容

          {
          "vh": {
          "prefix": "vh", // 觸發的關鍵字 輸入vh按下tab鍵
          "body": [
          "<!DOCTYPE html>",
          "<html lang=\"en\">",
          "",
          "<head>",
          "    <meta charset=\"UTF-8\">",
          "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
          "    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
          "    <title>Document</title>",
          "    <script src=\"js/vue.min.js\"></script>",
          "</head>",
          "",
          "<body>",
          "    <div id=\"app\"></div>",
          "    <script>",
          "        var vm=new Vue({",
          "           el:'#app',",
          "           data:{},",
          "           methods:{}",
          "        });",
          "    </script>",
          "</body>",
          "",
          "</html>",
          ],
          "description": "vh components"
          }
          }

          此時,新建一個html文件,輸入vh在按下tab鍵即可快速填充內容

          vue標準語法:
          什么是vue指令?
          在vue中提供了一些對于頁面 + 數據的更為方便的輸出,這些操作就叫做指令, 以v-xxx表示
          類似于html頁面中的屬性 `

          比如在angular中 以ng-xxx開頭的就叫做指令
          在vue中 以v-xxx開頭的就叫做指令
          指令中封裝了一些DOM行為, 結合屬性作為一個暗號, 暗號有對應的值,根據不同的值,框架會進行相關DOM操作的綁定

          下面簡單介紹一下vue的幾個基礎指令: v-bind v-if v-for v-on等

          v-bind指令
          作用:

          給元素的屬性賦值
          可以給已經存在的屬性賦值 input value
          也可以給自定義屬性賦值 mydata
          語法
          在元素上 v-bind:屬性名="常量||變量名"
          簡寫形式 :屬性名="變量名"
          例:
          <div v-bind:原屬性名="變量"></div> <div :屬性名="變量"></div>

          事件單向綁定

          事件單向綁定,可以用 v-bind:屬性名="常量||變量名,綁定事件,用插值表達式取出值

          <body>
              <div id="app">
              
                  <h1 v-bind:title="message">
                      {{content}}
                  </h1>

                  <!-- 簡寫方式 -->
                  <h2 :title="content">{{message}}</h2>


              </div>
              <script>
                  var vm=new Vue({
                     el:'#app',
                     data:{
                         content: '我是標題',
                         message: '頁面加載于' + new Date().toDateString()
                     }
                     
                  });
              </script>
          </body>

          效果:
          20200511203222885.png


          ————————————————
          版權聲明:本文為CSDN博主「熱愛旅行的小李同學」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
          原文鏈接:https://blog.csdn.net/m0_46275020/java/article/details/106055312


          JavaScript 對象可以做到的三件事

          seo達人

          1. 訪問內部屬性

          JavaScript 對象無法以常規方式訪問的內部屬性。內部屬性名由雙方括號[[]]包圍,在創建對象時可用。


          內部屬性不能動態地添加到現有對象。


          內部屬性可以在某些內置 JavaScript 對象中使用,它們存儲ECMAScript規范指定的內部狀態。


          有兩種內部屬性,一種操作對象的方法,另一種是存儲數據的方法。例如:


          [[Prototype]] — 對象的原型,可以為null或對象

          [[Extensible]] — 表示是否允許在對象中動態添加新的屬性

          [[PrivateFieldValues]] — 用于管理私有類字段

          2. 屬性描述符對象

          數據屬性包含了一個數據值的位置,在這個位置可以讀取和寫入值。也就是說,數據屬性可以通過 對象.屬性 訪問,就是我么平常接觸的用戶賦什么值,它們就返回什么,不會做額外的事情。


          數據屬性有4個描述其行為的特性(為了表示內部值,把屬性放在兩對方括號中),稱為描述符對象。


          屬性 解釋 默認值

          [[Configurable]] 能否通過delete刪除屬性從而重新定義屬性;

          能否修改屬性的特性;

          能否把屬性修改為訪問器屬性 true

          [[Enumerable]] 能否通過for-in循環返回屬性 true

          [[Writable]] 能否修改屬性的值 true

          [[Value]] 包含這個屬性的數據值 undefined

          value 描述符是屬性的數據值,例如,我們有以下對象 :


          let foo = {

           a: 1

          }

          那么,a 的value屬性描述符為1。


          writable是指該屬性的值是否可以更改。 默認值為true,表示屬性是可寫的。 但是,我們可以通過多種方式將其設置為不可寫。


          configurable 的意思是可以刪除對象的屬性還是可以更改其屬性描述符。 默認值為true,這意味著它是可配置的。


          enumerable 意味著它可以被for ... in循環遍歷。 默認值為true,說明能通過for-in循環返回屬性


          將屬性鍵添加到返回的數組之前,Object.keys方法還檢查enumerable 描述符。 但是,Reflect.ownKeys方法不會檢查此屬性描述符,而是返回所有自己的屬性鍵。


          Prototype描述符有其他方法,get和set分別用于獲取和設置值。


          在創建新對象, 我們可以使用Object.defineProperty方法設置的描述符,如下所示:


          let foo = {

           a: 1

          }

          Object.defineProperty(foo, 'b', {

           value: 2,

           writable: true,

           enumerable: true,

           configurable: true,

          });

          這樣得到foo的新值是{a: 1, b: 2}。


          我們還可以使用defineProperty更改現有屬性的描述符。 例如:


          let foo = {

           a: 1

          }

          Object.defineProperty(foo, 'a', {

           value: 2,

           writable: false,

           enumerable: true,

           configurable: true,

          });

          這樣當我們嘗試給 foo.a 賦值時,如:


          foo.a = 2;

          如果關閉了嚴格模式,瀏覽器將忽略,否則將拋出一個錯誤,因為我們將 writable 設置為 false, 表示該屬性不可寫。


          我們還可以使用defineProperty將屬性轉換為getter,如下所示:


          'use strict'

          let foo = {

           a: 1

          }


          Object.defineProperty(foo, 'b', {

           get() {

             return 1;

           }

          })

          當我們這樣寫的時候:


          foo.b = 2;

          因為b屬性是getter屬性,所以當使用嚴格模式時,我們會得到一個錯誤:Getter 屬性不能重新賦值。


          3.無法分配繼承的只讀屬性

          繼承的只讀屬性不能再賦值。這是有道理的,因為我們這樣設置它,它是繼承的,所以它應該傳播到繼承屬性的對象。


          我們可以使用Object.create創建一個從原型對象繼承屬性的對象,如下所示:


          const proto = Object.defineProperties({}, {

           a: {

             value: 1,

             writable: false

           }

          })


          const foo = Object.create(proto)

          在上面的代碼中,我們將proto.a的 writable 描述符設置為false,因此我們無法為其分配其他值。


          如果我們這樣寫:


          foo.a = 2;

          在嚴格模式下,我們會收到錯誤消息。


          總結

          我們可以用 JavaScript 對象做很多我們可能不知道的事情。


          首先,某些 JavaScript 對象(例如內置瀏覽器對象)具有內部屬性,這些屬性由雙方括號包圍,它們具有內部狀態,對象創建無法動態添加。


          JavaScript對象屬性還具有屬性描述符,該屬性描述符使我們可以控制其值以及可以設置它們的值,還是可以更改其屬性描述符等。


          我們可以使用defineProperty更改屬性的屬性描述符,它還用于添加新屬性及其屬性描述符。


          最后,繼承的只讀屬性保持只讀狀態,這是有道理的,因為它是從父原型對象繼承而來的。

          最簡單理解web前端

          前端達人

          web前端

          web中開發的三個基本技術(html5,css3,JavaScript)

          html簡介:html語言是純文本類型的語言,是internet上用來編寫網頁的主要語言,使用HTML語言編寫的網頁文件也是標準的純文本文件(簡單說告訴瀏覽器顯示什么)
          .
          css簡介:css是一種網頁控制技術,采用css技術,可以有效地對頁面、字體、顏色、背景和其他效果實現更加精準的控制
          (簡單的說告訴瀏覽器如何顯示)
          .
          JavaScript:JavaScript是web頁面中的一種腳本編程語言,也是一種通用的、跨平臺的、基于對象和事件驅動并具有安全性的腳本語言。它不需要進行編譯,而是直接嵌入HTML頁面中,把靜態頁面變成動態頁面。(簡單的來說告訴瀏覽器如何交互)

          簡單HTML文件結構

          <html>/*文件開始*/ <head>/*文件頭*/ <title>標題</title>/*文件標題*/ </head> <body>內容</body> </html>/*文件結束*/

          HTML常用的標記

          <br>換行 <p></p>段落 <s></s>刪除線 <b></b>字體粗體 <u></u>下劃線 <em></em>斜體內容 <sub></sub> 下標 <sup></sup>上標 <hr></hr>水平線 <a></a>超鏈接 .....





          bool查詢簡介

          Elasticsearch(下面簡稱ES)中的bool查詢在業務中使用也是比較多的。在一些非實時的分頁查詢,導出的場景,我們經常使用bool查詢組合各種查詢條件。



          Bool查詢包括四種子句,



          must

          filter

          should

          must_not

          我這里只介紹下must和filter兩種子句,因為是我們今天要講的重點。其它的可以自行查詢官方文檔。



          must, 返回的文檔必須滿足must子句的條件,并且參與計算分值

          filter, 返回的文檔必須滿足filter子句的條件。但是跟Must不一樣的是,不會計算分值, 并且可以使用緩存

          從上面的描述來看,你應該已經知道,如果只看查詢的結果,must和filter是一樣的。區別是場景不一樣。如果結果需要算分就使用must,否則可以考慮使用filter。



          光說比較抽象,看個例子,下面兩個語句,查詢的結果是一樣的。



          使用filter過濾時間范圍,

          GET kibana_sample_data_ecommerce/_search
          {
            "size": 1000, 
            "query": {
              "bool": {
                "must": [
                  {"term": {
                    "currency": "EUR"
                  }}
                ],
                "filter": {
                  "range": {
                    "order_date": {
                      "gte": "2020-01-25T23:45:36.000+00:00",
                      "lte": "2020-02-01T23:45:36.000+00:00"
                    }
                  }
                }
              }
            }
          }
          


          filter比較的原理

          上一節你已經知道了must和filter的基本用法和區別。簡單來講,如果你的業務場景不需要算分,使用filter可以真的讓你的查詢效率飛起來。



          為了說明filter查詢的原因,我們需要引入ES的一個概念 query context和 filter context。



          query context



          query context關注的是,文檔到底有多匹配查詢的條件,這個匹配的程度是由相關性分數決定的,分數越高自然就越匹配。所以這種查詢除了關注文檔是否滿足查詢條件,還需要額外的計算相關性分數.



          filter context



          filter context關注的是,文檔是否匹配查詢條件,結果只有兩個,是和否。沒有其它額外的計算。它常用的一個場景就是過濾時間范圍。



          并且filter context會自動被ES緩存結果,效率進一步提高。



          對于bool查詢,must使用的就是query context,而filter使用的就是filter context。



          我們可以通過一個示例驗證下。繼續使用第一節的例子,我們通過kibana自帶的search profiler來看看ES的查詢的詳細過程。



          使用must查詢的執行過程是這樣的:



          可以明顯看到,此次查詢計算了相關性分數,而且score的部分占據了查詢時間的10分之一左右。



          filter的查詢我就不截圖了,區別就是score這部分是0,也就是不計算相關性分數。



          除了是否計算相關性算分的差別,經常使用的過濾器將被Elasticsearch自動緩存,以提高性能。



          我自己曾經在一個項目中,對一個業務查詢場景做了這種優化,當時線上的索引文檔數量大概是3000萬左右,改成filter之后,查詢的速度幾乎快了一倍。


          總結

          我們應該根據自己的實際業務場景選擇合適的查詢語句,在某些不需要相關性算分的查詢場景,盡量使用filter context可以讓你的查詢更加。


          Web安全之CSRF實例解析

          seo達人

          前言

          文章首次發表在 個人博客


          之前寫過一篇 web安全之XSS實例解析,是通過舉的幾個簡單例子講解的,同樣通過簡單得例子來理解和學習CSRF,有小伙伴問實際開發中有沒有遇到過XSS和CSRF,答案是有遇到過,不過被測試同學發現了,還有安全掃描發現了可能的問題,這兩篇文章就是簡化了一下當時實際遇到的問題。


          CSRF

          跨站請求偽造(Cross Site Request Forgery),是指黑客誘導用戶打開黑客的網站,在黑客的網站中,利用用戶的登陸狀態發起的跨站請求。CSRF攻擊就是利用了用戶的登陸狀態,并通過第三方的站點來做一個壞事。


          要完成一次CSRF攻擊,受害者依次完成兩個步驟:


          登錄受信任網站A,并在本地生成Cookie

          在不登出A的情況,訪問危險網站B

          CSRF攻擊


          在a.com登陸后種下cookie, 然后有個支付的頁面,支付頁面有個誘導點擊的按鈕或者圖片,第三方網站域名為 b.com,中的頁面請求 a.com的接口,b.com 其實拿不到cookie,請求 a.com會把Cookie自動帶上(因為Cookie種在 a.com域下)。這就是為什么在服務端要判斷請求的來源,及限制跨域(只允許信任的域名訪問),然后除了這些還有一些方法來防止 CSRF 攻擊,下面會通過幾個簡單的例子來詳細介紹 CSRF 攻擊的表現及如何防御。


          下面會通過一個例子來講解 CSRF 攻擊的表現是什么樣子的。

          實現的例子:

          在前后端同域的情況下,前后端的域名都為 http://127.0.0.1:3200, 第三方網站的域名為 http://127.0.0.1:3100,釣魚網站頁面為 http://127.0.0.1:3100/bad.html。


          平時自己寫例子中會用到下面這兩個工具,非常方便好用:

          http-server: 是基于node.js的HTTP 服務器,它最大的好處就是:可以使用任意一個目錄成為服務器的目錄,完全拋開后端的沉重工程,直接運行想要的js代碼;

          nodemon: nodemon是一種工具,通過在檢測到目錄中的文件更改時自動重新啟動節點應用程序來幫助開發基于node.js的應用程序

          前端頁面: client.html


          <!DOCTYPE html>

          <html lang="en">


          <head>

             <meta charset="UTF-8">

             <meta name="viewport" content="width=device-width, initial-scale=1.0">

             <meta http-equiv="X-UA-Compatible" content="ie=edge">

             <title>CSRF-demo</title>

             <style>

                 .wrap {

                     height: 500px;

                     width: 300px;

                     border: 1px solid #ccc;

                     padding: 20px;

                     margin-bottom: 20px;

                 }

                 input {

                     width: 300px;

                 }

                 .payInfo {

                     display: none;

                 }

                 .money {

                     font-size: 16px;

                 }

             </style>

          </head>


          <body>

             <div class="wrap">

                 <div class="loginInfo">

                     <h3>登陸</h3>

                     <input type="text" placeholder="用戶名" class="userName">

                     <br>

                     <input type="password" placeholder="密碼" class="password">

                     <br>

                     <br>

                     <button class="btn">登陸</button>

                 </div>

                 

                 

                 <div class="payInfo">

                     <h3>轉賬信息</h3>

                     <p >當前賬戶余額為 <span class="money">0</span>元</p>

                     <!-- <input type="text" placeholder="收款方" class="account"> -->

                     <button class="pay">支付10元</button>

                     <br>

                     <br>

                     <a target="_blank">

                         聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么

                     </a>

                 </div>

             </div>

          </body>

          <script>

             const btn = document.querySelector('.btn');

             const loginInfo = document.querySelector('.loginInfo');

             const payInfo = document.querySelector('.payInfo');

             const money = document.querySelector('.money');

             let currentName = '';

             // 第一次進入判斷是否已經登陸

             Fetch('http://127.0.0.1:3200/isLogin', 'POST', {})

             .then((res) => {

                 if(res.data) {

                     payInfo.style.display = "block"

                     loginInfo.style.display = 'none';

                     Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 0})

                     .then((res) => {

                         money.innerHTML = res.data.money;

                     })

                 } else {

                     payInfo.style.display = "none"

                     loginInfo.style.display = 'block';

                 }

                 

             })

             // 點擊登陸

             btn.onclick = function () {

                 var userName = document.querySelector('.userName').value;

                 currentName = userName;

                 var password = document.querySelector('.password').value;

                 Fetch('http://127.0.0.1:3200/login', 'POST', {userName, password})

                 .then((res) => {

                     payInfo.style.display = "block";

                     loginInfo.style.display = 'none';

                     money.innerHTML = res.data.money;

                 })

             }

             // 點擊支付10元

             const pay = document.querySelector('.pay');

             pay.onclick = function () {

                 Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 10})

                 .then((res) => {

                     console.log(res);

                     money.innerHTML = res.data.money;

                 })

             }

             // 封裝的請求方法

             function Fetch(url, method = 'POST', data) {

                 return new Promise((resolve, reject) => {

                     let options = {};

                     if (method !== 'GET') {

                         options = {

                             headers: {

                                 'Content-Type': 'application/json',

                             },

                             body: JSON.stringify(data),

                         }

                     }

                     fetch(url, {

                         mode: 'cors', // no-cors, cors, *same-origin

                         method,

                         ...options,

                         credentials: 'include',

                     }).then((res) => {

                         return res.json();

                     }).then(res => {

                         resolve(res);

                     }).catch(err => {

                         reject(err);

                     });

                 })

             }

             

          </script>


          </html>

          實現一個簡單的支付功能:


          會首先判斷有沒有登錄,如果已經登陸過,就直接展示轉賬信息,未登錄,展示登陸信息

          登陸完成之后,會展示轉賬信息,點擊支付,可以實現金額的扣減

          后端服務: server.js


          const Koa = require("koa");

          const app = new Koa();

          const route = require('koa-route');

          const bodyParser = require('koa-bodyparser');

          const cors = require('@koa/cors');

          const KoaStatic = require('koa-static');


          let currentUserName = '';


          // 使用  koa-static  使得前后端都在同一個服務下

          app.use(KoaStatic(__dirname));


          app.use(bodyParser()); // 處理post請求的參數


          // 初始金額為 1000

          let money = 1000;


          // 調用登陸的接口

          const login = ctx => {

             const req = ctx.request.body;

             const userName = req.userName;

             currentUserName = userName;

             // 簡單設置一個cookie

             ctx.cookies.set(

                 'name',

                 userName,

                 {

                   domain: '127.0.0.1', // 寫cookie所在的域名

                   path: '/',       // 寫cookie所在的路徑

                   maxAge: 10 * 60 * 1000, // cookie有效時長

                   expires: new Date('2021-02-15'),  // cookie失效時間

                   overwrite: false,  // 是否允許重寫

                   SameSite: 'None',

                 }

               )

             ctx.response.body = {

                 data: {

                     money,

                 },

                 msg: '登陸成功'

             };

          }

          // 調用支付的接口

          const pay = ctx => {

             if(ctx.method === 'GET') {

                 money = money - Number(ctx.request.query.money);

             } else {

                 money = money - Number(ctx.request.body.money);

             }

             ctx.set('Access-Control-Allow-Credentials', 'true');

             // 根據有沒有 cookie 來簡單判斷是否登錄

             if(ctx.cookies.get('name')){

                 ctx.response.body = {

                     data: {

                         money: money,

                     },

                     msg: '支付成功'

                 };

             }else{

                 ctx.body = '未登錄';

             }

          }


          // 判斷是否登陸

          const isLogin = ctx => {

             ctx.set('Access-Control-Allow-Credentials', 'true');


             if(ctx.cookies.get('name')){

                 ctx.response.body = {

                     data: true,

                     msg: '登陸成功'

                 };


             }else{

                 ctx.response.body = {

                     data: false,

                     msg: '未登錄'

                 };

             }

          }

          // 處理 options 請求

          app.use((ctx, next)=> {

             const headers = ctx.request.headers;

             if(ctx.method === 'OPTIONS') {

                 ctx.set('Access-Control-Allow-Origin', headers.origin);

                 ctx.set('Access-Control-Allow-Headers', 'Content-Type');

                 ctx.set('Access-Control-Allow-Credentials', 'true');

                 ctx.status = 204;

             } else {

                 next();

             }

          })


          app.use(cors());

          app.use(route.post('/login', login));

          app.use(route.post('/pay', pay));

          app.use(route.get('/pay', pay));

          app.use(route.post('/isLogin', isLogin));


          app.listen(3200, () => {

             console.log('啟動成功');

          });

          執行 nodemon server.js,訪問頁面 http://127.0.0.1:3200/client.html


          CSRF-demo


          登陸完成之后,可以看到Cookie是種到 http://127.0.0.1:3200 這個域下面的。


          第三方頁面 bad.html


          <!DOCTYPE html>

          <html lang="en">

          <head>

             <meta charset="UTF-8">

             <meta name="viewport" content="width=device-width, initial-scale=1.0">

             <title>第三方網站</title>

          </head>

          <body>

             <div>

                 哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

                 <!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->

                 <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr" style="display: none">

                     <input type="text" name="userName" value="xiaoming">

                     <input type="text" name="money" value="100">

                 </form>

                 <iframe name="targetIfr" style="display:none"></iframe>

             </div>

          </body>

          <script>

             document.querySelector('.form').submit();

          </script>

          </html>

          使用 HTTP-server 起一個 本地端口為 3100的服務,就可以通過 http://127.0.0.1:3100/bad.html 這個鏈接來訪問,CSRF攻擊需要做的就是在正常的頁面上誘導用戶點擊鏈接進入這個頁面

          CSRF-DEMO


          點擊誘導鏈接,跳轉到第三方的頁面,第三方頁面自動發了一個扣款的請求,所以在回到正常頁面的時候,刷新,發現錢變少了。

          我們可以看到在第三方頁面調用 http://127.0.0.1:3200/pay 這個接口的時候,Cookie自動加在了請求頭上,這就是為什么 http://127.0.0.1:3100/bad.html 這個頁面拿不到 Cookie,但是卻能正常請求 http://127.0.0.1:3200/pay 這個接口的原因。


          CSRF攻擊大致可以分為三種情況,自動發起Get請求, 自動發起POST請求,引導用戶點擊鏈接。下面會分別對上面例子進行簡單的改造來說明這三種情況


          自動發起Get請求

          在上面的 bad.html中,我們把代碼改成下面這樣


          <!DOCTYPE html>

          <html>

           <body>

             <img src="http://127.0.0.1:3200/payMoney?money=1000">

           </body>

          </html>

          當用戶訪問含有這個img的頁面后,瀏覽器會自動向自動發起 img 的資源請求,如果服務器沒有對該請求做判斷的話,那么會認為這是一個正常的鏈接。


          自動發起POST請求

          上面例子中演示的就是這種情況。


          <body>

             <div>

                 哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

                 <!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->

                 <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr">

                     <input type="text" name="userName" value="xiaoming">

                     <input type="text" name="money" value="100">

                 </form>

                 <iframe name="targetIfr" style="display:none"></iframe>

             </div>

          </body>

          <script>

             document.querySelector('.form').submit();

          </script>

          上面這段代碼中構建了一個隱藏的表單,表單的內容就是自動發起支付的接口請求。當用戶打開該頁面時,這個表單會被自動執行提交。當表單被提交之后,服務器就會執行轉賬操作。因此使用構建自動提交表單這種方式,就可以自動實現跨站點 POST 數據提交。


          引導用戶點擊鏈接

          誘惑用戶點擊鏈接跳轉到黑客自己的網站,示例代碼如圖所示


          <a >聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么</a>

          用戶點擊這個地址就會跳到黑客的網站,黑客的網站可能會自動發送一些請求,比如上面提到的自動發起Get或Post請求。


          如何防御CSRF

          利用cookie的SameSite

          SameSite有3個值: Strict, Lax和None


          Strict。瀏覽器會完全禁止第三方cookie。比如a.com的頁面中訪問 b.com 的資源,那么a.com中的cookie不會被發送到 b.com服務器,只有從b.com的站點去請求b.com的資源,才會帶上這些Cookie

          Lax。相對寬松一些,在跨站點的情況下,從第三方站點鏈接打開和從第三方站點提交 Get方式的表單這兩種方式都會攜帶Cookie。但如果在第三方站點中使用POST方法或者通過 img、Iframe等標簽加載的URL,這些場景都不會攜帶Cookie。

          None。任何情況下都會發送 Cookie數據

          我們可以根據實際情況將一些關鍵的Cookie設置 Stirct或者 Lax模式,這樣在跨站點請求的時候,這些關鍵的Cookie就不會被發送到服務器,從而使得CSRF攻擊失敗。


          驗證請求的來源點

          由于CSRF攻擊大多來自第三方站點,可以在服務器端驗證請求來源的站點,禁止第三方站點的請求。

          可以通過HTTP請求頭中的 Referer和Origin屬性。


          HTTP請求頭


          但是這種 Referer和Origin屬性是可以被偽造的,碰上黑客高手,這種判斷就是不安全的了。


          CSRF Token

          最開始瀏覽器向服務器發起請求時,服務器生成一個CSRF Token。CSRF Token其實就是服務器生成的字符串,然后將該字符串種植到返回的頁面中(可以通過Cookie)

          瀏覽器之后再發起請求的時候,需要帶上頁面中的 CSRF Token(在request中要帶上之前獲取到的Token,比如 x-csrf-token:xxxx), 然后服務器會驗證該Token是否合法。第三方網站發出去的請求是無法獲取到 CSRF Token的值的。

          其他知識點補充

          1. 第三方cookie

          Cookie是種在服務端的域名下的,比如客戶端域名是 a.com,服務端的域名是 b.com, Cookie是種在 b.com域名下的,在 Chrome的 Application下是看到的是 a.com下面的Cookie,是沒有的,之后,在a.com下發送b.com的接口請求會自動帶上Cookie(因為Cookie是種在b.com下的)


          2. 簡單請求和復雜請求

          復雜請求需要處理option請求。


          之前寫過一篇特別詳細的文章 CORS原理及@koa/cors源碼解析,有空可以看一下。


          3. Fetch的 credentials 參數

          如果沒有配置credential 這個參數,fetch是不會發送Cookie的


          credential的參數如下


          include:不論是不是跨域的請求,總是發送請求資源域在本地的Cookies、HTTP Basic anthentication等驗證信息

          same-origin:只有當URL與響應腳本同源才發送 cookies、 HTTP Basic authentication 等驗證信息

          omit: 從不發送cookies.

          平常寫一些簡單的例子,從很多細節問題上也能補充自己的一些知識盲點。

          后來居上的抖音,做對了什么?

          濤濤

          當下“兩微一抖”已成為企業品牌傳播必備的渠道策略,可見抖音的江湖地位不容小覷。

          抖音,誕生于2016年9月,是一款“音樂創意”短視頻社交軟件。截至2020年1月,其日活躍用戶數突破4億,而同屬短視頻行業的快手的日活躍用戶數也才剛過3億。要知道,在2016年6月,當時抖音還沒上線,快手就坐擁3億注冊用戶數。顯而易見,抖音不過是一個后起之秀,卻能傲視群雄。

          短視頻行業的數據統計

          也有數據表明,抖音用戶的粘性很大,轉去玩快手的用戶只有55.6%,反而快手用戶轉去玩抖音的高達68.2%??焓指呒壐笨偛民R宏斌也曾透露,快手前100名的大V有70個是抖音用戶,抖音前100名的大V只有50個是快手用戶。

          想知道抖音是如何后來居上,必先了解冰山模型

          一般認為,一個人80%的成功幾率是由冰山模型水平面以下要素:動機、價值觀/性格和綜合能力決定的,另外20%是由冰山模型水平面以上要素:經驗和技能決定的。

          而且,上層要素的生長受到底層要素的制約:一個人有什么樣的動機,就會促使他/她著重培養哪方面的綜合能力;有什么樣的價值觀/性格,對他/她的行為舉止也會產生一定約束力,什么該做,什么不該做。

          互聯網產品的冰山模型

          如果把一家互聯網公司的品牌視為一個人,那么動機就是品牌價值,價值觀/性格就是品牌人格,綜合能力就是營銷4C中的用戶需求(Customer)和學習成本(Cost),經驗和技能就是營銷4C中的消除障礙(Convenience)和用戶運營(Communication)。

          抖音的成功可從冰山模型自下而上進行總結:

          1. 底層構建了一個讓人為之驚嘆的品牌人格

          從最初的品牌Slogan——讓崇拜從這里開始,以及有一頭條的員工在朋友圈曬出公司的背景墻——讓更多人的美好生活被看見,可以看出抖音成立的動機——幫助更多人提升個人知名度。

          字節跳動的公司背景墻

          這個動機是基于對社會沖突的深刻洞察:近年來各類選秀節目(如:中國好聲音、超級女聲等)的熱播透露出許多年輕人心藏一個明星夢,但由于家庭出身、人際資源、社會財富等先天條件限制不得不放棄追逐夢想。城市已經發展到需要一個品牌來給這批年輕人創造機會的時候,抖音應運而生。

          作為一個創造者,抖音還找到了提升個人知名度應該具備的人格特質——精致(Sophistication),并以這樣的人格特質與城市年輕群體溝通,包括借助明星光環來詮釋何為精致、擲重金在各大城市舉辦美好生活節……這些事件在目標群體里迅速傳開,并取得目標人群的情感認同和品牌信任。

          在抖音里明星云集

          注:精致(Sophistication)、稱職(Competence)、刺激(Excitement)、堅韌(Ruggedness)和真誠(Sincerity)是J.Aaker通過問卷調查得出的五大最容易被消費者感知的人格特質,而且這五大特質也能概括許多流行品牌在消費者心目中的人格形象。

          2. 淺層做到了品牌價值穩定輸出及承諾兌現

          在站內,抖音致力于幫助更多人提升個人知名度,加強產品功能和用戶體驗,讓更多人得到同等機會展示自我。

          在用戶需求(Customer)方面,采用了去中心化內容分發機制,當內容創作者發布一條短視頻后,抖音并不是把這條短視頻發給創作者的所有粉絲用戶,而是小范圍分發測試這條短視頻的潛力,讓可能喜歡這類內容的標簽用戶通過行動來評價。只有獲得更多用戶認可的優質短視頻才能得到二次流量推薦,進而成為爆款,創作者就有機會爆紅。同時,采用了全屏自動播放模式,讓每一條短視頻都能被觀看,不失為一種公平競爭。

          抖音的產品功能

          在學習成本(Cost)方面,采用了中心化內容生產機制,組建了服務達人的MCN機構,由平臺簽約明星、網紅和大V推出一個個可復制、易模仿的有趣短視頻,降低用戶學習門檻。同時,運營團隊也會不定期發起各類短視頻挑戰賽,提供各種濾鏡、剪輯技能、流行音樂等素材,即使普通用戶沒有創新力,跟風模仿也能拍出看起來酷炫的、專屬于自己的15秒“大片”。

          3. 頂層把精致人格和品牌決心展示得更直觀

          在站外,抖音秉承“精致”理念開展一系列PUGC營銷活動,持續影響用戶的感知,并不斷強化精致的人格特質。

          抖音冠名綜藝節目

          在消除障礙(Convenience)方面,抖音通過冠名一些和明星互動的綜藝節目,比如中國有嘻哈、快樂大本營、天天向上等,借助節目酷炫的舞臺效果和高度吻合的受眾群體,消除年用戶從產品感知到下載應用之間存在的疑慮——抖音是否有實力助我提升知名度?事實上,抖音已經幫助不少草根明星(如劉宇寧、陸超等)登上主流媒體的舞臺,讓用戶看到了展示自我的機會、成為明星的希望。

          在用戶運營(Communication)方面,除了大眾明星入駐,引入其他平臺的網紅和大V,還與多家MCN機構合作,扶持和培養平臺內的潛力用戶,保證內容的質量和持續產出。同時,不定期更新挑戰活動、熱門搜索、加強品牌合作激勵用戶創作,以增強用戶對抖音的依賴和活躍。在參與創作的過程中,用戶也能得到抖音提供的專群維護、不定時禮物、拍攝指導、流量曝光等多方面幫助。

          抖音品牌活動及福利

          寫到這里,你可能會驚嘆一聲:哦,原來如此!

          但是換到自己來做品牌、運營產品,你就會一臉懵逼了。要么無從下手,要么覺得每天的工作就是打雜,不知道做這些運營動作意義何在。你制定的運營計劃也可能是雜亂無章,運營策略多半也是效仿其他大品牌,在各個產品生命周期階段沒有什么不同。

          產品運營人的苦惱

          產品運營的策略制定

          所以,我們應該從抖音身上學如何“做正確的事“,并且”正確地做事“——把產品當作對社會有貢獻的人來培養,尋找能讓它快速被社會接納的人格特質(做正確的事),然后在不同的成長階段制定相應的歷練計劃,并長期堅持做事風格的一致性,而不是一天一個花樣模糊了用戶對品牌的印象(正確地做事)。

          從抖音身上學如何“做正確的事“

          第一步,了解產品所屬的行業發展到哪一階段

          行業生命周期曲線

          探索期:市場供給量和需求量低,增長速度只有3%左右。只有一部分有研發實力的企業在生產產品,因為試錯率高、生產成本高,使得企業不敢做出太多花樣,產品售價普遍也高。對于消費者來說,老產品的轉換成本高,新產品的功能還不穩定,普遍是觀望態度。

          成長期:市場供給量和需求量增加,增長速度達到10%以上。這時候產品技術已取得突破性進展,可大規模復制生產,部分產品還是供不應求。隨著進入市場的企業數量增多,企業的競爭就是價格戰(紅包、補貼)和產品多元化,助長了市場搶購風氣,強化了消費者對價格的敏感性。

          成熟期:市場供給量和需求量接近峰值,增長速度在5%以下。企業占有的市場份額越多,其產品銷路越寬賣得越好。企業之間的競爭從瓜分空白市場變成了搶占對手的市場,營銷手段多了起來,導致消費者出現了選擇困難癥,這時企業的口碑開始發揮了作用。

          衰退期:市場供給量和需求量下降,增長速度在0%以下。消費者的消費觀念改變了,市場出現了供過于求的現象,而替代品日益增多又帶來沖擊,企業開始大批量清理庫存,行業標桿在現有的口碑基礎上向新產業轉型,小工廠則因資金不足被迫退出市場。

          從行業的發展規律來看,只有產品的企業會因行業變化而被淘汰,但是有好口碑的企業卻不會,因為它可以引導用戶消費來應對市場挑戰。

          第二步,在當前發展階段應為用戶帶來耳目一新

          新媒體時代下的需求

          首先,根據行業供給和需求特點細分出公司產品的目標用戶群,描繪能給產品創造最大價值的核心人群畫像,然后洞察他們在社交生活中未被得到滿足的需求:在新媒體時代,每個人都想成為“主角”,年輕人更急于尋求個性表達,但自拍效果不夠酷,也沒有太多技能加持。

          其次,了解標桿/競爭對手又是如何解決這些現實沖突,對目標用戶群說了什么。作為抖音的頭號競爭對手——快手,以“記錄世界,記錄你”作為Slogan,具有很濃的個人主義色彩,乍一看以為是幫助感情受到挫折的人走出傷痛,學會自我療愈。

          最后,順勢而為,不違背或超前于行業發展,將核心人群的需求痛點與公司產品利益點建立連接,從事實主張、認知區隔、情感主張和價值區隔中挑選適合當下的一種廣告內容。

          不同生命階段的產品廣告內容

          事實主張:根據品牌內部能力或資源的明顯優勢進行突出定位,傳達所具備的事實優勢壁壘,直擊重點。如:早期的抖音——讓崇拜從這里開始。

          適用評估:企業在行業中具有先發優勢,在組織資源方面具有強大的科研能力、上市速度快,在產品/服務方面是行業唯一,不容易被模仿/超越。

          認知區隔:在受眾心中建立獨特記憶認知,讓受眾對于該品牌和其他某種特定行為或體驗產生強關聯,從而做出選擇。如:現在的抖音——記錄美好生活。

          適用評估:行業中出現了新的關鍵驅動因素,消費者心智中還有其他尚未被占領的空間。

          情感主張:提煉品牌中蘊含的情感因素,通過向受眾傳遞美好的情感聯想,讓受眾將心中期望憧憬與品牌實現關聯。如:格力——讓世界愛上中國造。

          適用評估:除功能性需求外,目標用戶群還有情感性需求未被滿足。相比競爭對手,產品包裝設計、信息傳播更能觸動用戶情感,公司愿景能夠引發消費者共鳴。

          價值區隔:傳達品牌對于受眾產生的某種獨特價值觀,且能通過產品體現在受眾心中形成心理認同,產生價值共鳴。如:耐克——Just do it。

          適用評估:已經積累了一定數量的用戶群,公司愿景切換用戶心理并引領時代,取得了行業KOL、大V等背書。

          第三步,扮演極具感染力的人來講品牌價值所在

          品牌人格原型及特質

          將創始人/團隊個性特征和公司產品的發展愿景兩方面因素綜合匹配,找到適合公司長遠發展的人格原型。

          不同的品牌塑造如同人的成長,不同的基因決定了不同的人格,不同的人格決定了不同的傳播調性,不同的傳播調性又決定了受眾對品牌的不同反應。有生命力的長壽品牌是具有人格原型的。一個成功的品牌人格,可以讓品牌拉近與用戶的情感距離,并且在用戶心智刻畫出鮮明難忘的品牌形象。

          所以,確立相應的品牌人格后,找出核心人群最可能聽具有什么樣人格特質的人說的話,然后學習這類人的表達方式、行動風格等,以此作為產品運營的人設模板并長期堅持不斷強化。

          第四步,明確當前產品生命階段的任務是什么

          產品生命周期曲線

          產品所處的生命周期階段一般可從以下幾個方面進行判斷:

          1. 將產品日下載量/銷量變化趨勢曲線與生命周期的“S”曲線擬合;
          2. 將產品累計下載量/銷量與市場總量及潛在用戶總量對比,評估上升的可能性;
          3. 將產品的百度指數與行業標桿/頭部競品的對比,客觀評價產品的差距;
          4. 觀察產品的日/月活量、營收等平臺數據波動情況,預判上升的幅度大小。

          探索期:日下載量/銷量極低,市場占有率幾乎為0。該階段主要任務就是尋找高質量的種子用戶,讓用戶參與產品體驗,并對產品提出優化建議。種子用戶一般控制在100個左右,多了會交流不過來,嚴重地會導致分不清主次需求。這些種子用戶可以是有一定圈子影響力的同事或朋友,與產品沾邊的相關領域意見領袖(KOL),微信(群)、微博、垂直論壇等網絡大V。那么如何讓對方搭理我們愿意成為種子用戶?我們得事先準備一個鼓舞人心的品牌故事,愿景是致力于解決某一個非常緊迫的社會難題,現正尋找一群志同道合的人一起攻克。

          成長期:日下載量/銷量、 日/月活量、營收增多而且增速較快,但與頭部競品的市場占有率相比還有很大差距。該階段的主要任務就是激發用戶尚未意識到或未被滿足的需求,一旦使用產品后能夠給生活帶來不一樣的感覺。例如,有些年輕人喜歡自拍模仿明星,但鮮有人圍觀,最終淪為自娛自樂。于是抖音推出一條“找呀找呀找愛豆”競猜H5,一下子刷爆了朋友圈,為模仿者,也為抖音帶來了巨大的流量關注。在那之后,抖音開始找一些明星合作增加產品流量和話題,讓更多的用戶也加入到抖音隊伍中來。

          讓抖音一炮而紅的宣傳

          成熟期:日下載量/銷量基本穩定,已占據了一定市場規模,但日/月活量、營收仍在增加。該階段主要任務就是對用戶進行精細化運營,圍繞用戶的地域、人口特征、消費品類、品牌偏好,購物行為、生活場景和用戶價值七大維度進行用戶分群貼標簽,然后收集用戶貢獻值,如活躍度、購買力情況等數據將用戶按二八原則分為不同等級:1%為重要客戶,19%為主要客戶,30%為普通客戶,50%為長尾客戶,讓不同等級用戶享受到不同的專屬服務,培養用戶的忠誠度和提高客單價。

          衰退期:日下載量/銷量、日/月活量、營收均出現明顯下滑。該階段主要任務就是牢牢把握住核心用戶,通過發起有影響力的社會公益活動擴大品牌影響力的邊界,開發出和產品/服務強相關的常用周邊產品,滿足用戶的新需求。同時關注行業的潛在進入者動態,通過升維或降維的方式嘗試創新轉型,升維是指增加自身的實力,給同質化的產品或服務附加超值的東西;降維是指用高級、全套的打法平移到相對落后的空間,如車企推出網約車服務。

          第五步,按照原先擬定的品牌人格組織內容運營

          人格是貫穿始終的唯一宗旨,包括內容定位、行文風格、推送時間等在長時間內都是保持不變,讓內容集成為一個有人格特質、高辨識度的符號。

          內容運營的基本流程

          首先,打好內容定位的核心基礎,分析用戶TA是誰、在哪、每天在做什么、有什么偏好等等,制作出不同于競品的差異化內容,如圖文、視頻等類型上的差異,漫畫、直播、彈幕等形式上的差異,獵奇、愉悅、慰藉等需求上的差異,形成產品特有的內容亮點。內容亮點是熱點和特點的集合,熱點源于長期不斷積累的素材庫、百度熱搜和微博熱搜,特點則是產品特性和品牌人格的總結。

          其次,建立內容生產的素材庫和人才庫。素材庫的建立主要通過以下三種方式,一是對時事保持敏感,搜集和整理每天的新聞熱點;二是對輿論保持敏感,通過百度搜索風云榜、微博熱搜實時榜等網絡渠道了解當下的熱點;三是保持的運營思維,收藏和分析最近的優秀內容案例。

          人才庫的建立通常是先尋找和記錄的優質內容創作者,包括個人和機構,然后聯系和維護這些優質內容創作者,平時也多加留意熱點文章的作者,隨時更新和完善優質內容創作者的信息。

          當然人才的關系維護離不開一整套合理的合作機制:找到能夠幫我們傳達信息和組織其他用戶的超級用戶后,要用產品資源對其進行包裝和曝光,如:經紀團隊挖出楊超越“村花”這個事件點后,通過節目再不斷發酵出各種新的事件。然后再找更多的超級用戶,復制這一模式,不斷重復循環操作。也可以從不同角度對內容生產者設置不同的獎項,如:微博之夜,由官方定小獎,用戶投票定大獎,讓80%的用戶參與評選,最終給20%的用戶頒獎。

          再次,有組織地進行內容加工和包裝。常見的高級包裝有專題和專欄兩種:專題是特定事件的內容集合(世界杯專題)、特定時間的內容集合(6月原創歌曲排行榜)、特定場景的內容集合(抖in City 美好生活節)。專欄是特定主題的連續內容(今日說法)、特定領域的連續內容(舌尖上的中國)、特定形式的連續內容(焦點訪談)。

          一般加工流程是根據內容的定位確定要生產的內容主題,搭建最終想要呈現的內容框架,用最酷或最土的方式擊中用戶。再從素材庫中挑選出有用的元素,撰寫一個優秀的內容策劃方案,在特定的時間推一些能引起目標用戶情感共鳴的話題。也可以把內容框架和素材給到人才庫的創作者,將他們生產的內容包裝成一個欄目。又或者把所有具備共性的內容抽離出來,提煉出一個主題,這個主題是沒有立場之分,能夠引起正反方辯論的。

          最后,在用戶可觸達的渠道發布內容。試想在0預算的情況下,借勢熱點利用產品功能以不同的方式(Push、Banner、客服等)內推給用戶,或者在免費平臺(微信微博)建立基礎的外推渠道。也可以在一條或多頭內容上加入玩法(如:評論、直播、彈幕、測試等),引導用戶進行互動傳播。為了能讓用戶有意愿參與,可按用戶分層(人群、年齡、工作標簽、用戶等級等)、內容分層(超級內容、頭部內容、信息流內容)、時間場景(上班、周末、休假等)、空間場景(辦公、餐廳、酒店等)進行內容精準推薦。個人建議,任何欄目或版塊都應有C位內容,把它們放在最顯眼的位置。

          第六步,策劃活動讓品牌人格常在用戶眼前晃動

          根據活動舉辦的周期性,活動類型可分為常規活動、原創活動和節點活動三類。

          常規活動,主要包括各種節假日活動、用戶共知的節日活動,比如世界杯、NBA總決賽、奧斯卡頒獎等;

          原創活動,主要是一些品牌積累到了一定用戶基礎并有廣泛的品牌認知,結合產品本身特點打造的專屬性活動,比如天貓的雙十一,京東的618等;

          節點活動,主要是針對產品生命周期的用戶特點開展的重大運營活動,比如為了需求喚醒、信息互動在拉新階段開展的【新人紅包】,為了培養使用習慣、購買升級在促活階段開展的【每日簽到】,為了增強購買升級、精細服務在轉化階段開展的【首單立減】,為了建立用戶口碑在傳播階段開展的【分享有禮】。

          節點活動目的及手段

          每一個活動必須要有一個目標,活動運營的目的無非就是提升粘性、加快用戶轉化、增強消費決策、提升用戶復購和擴大活動傳播五個要點。

          活動策劃的目的及手段

          提升用戶粘性,實現該目標的關鍵點(或者說如何讓用戶養成使用產品的習慣)是讓用戶在產品上消耗時間,有時間投入,或對產品價值有一定預期,常用的手段就是每日簽到各種任務;

          加快用戶轉化,實現該目標的關鍵點(或者說如何讓用戶產生付費的意愿)是突出產品在某一方面的實用價值,常用的手段是紅包/優惠券,新人特權;

          增強消費決策,實現該目標的關鍵點(或者說如何催促用戶盡快下單)是通過價格沖擊和優惠策略刺激用戶消費,常用的手段是限時秒殺,特殊優惠;

          提升用戶復購,實現該目標的關鍵點(或者說如何讓用戶多次購買,買的更多)是為用戶提供信息篩選或新奇有趣的內容,常用的手段是內容/商品精選,特定補貼;

          擴大活動傳播,實現該目標的關鍵點(或者說如何讓用戶主動分享產品信息)是賦予產品更多的內容娛樂價值和社交價值,常用的手段是轉發/邀請有獎,測試小游戲。

          第七步,以強化品牌人格為目的開展用戶運營

          本著與用戶交朋友的心態服務用戶,循序漸進為用戶謀福利,讓用戶感知到并不斷強化品牌的人格特質。

          用戶運營的基本流程

          拉新:利用高頻使用、剛需、大眾化需求等流量爆品(如公交刷卡、比價搜索等),以豐富內容形式(如直播、短視頻、吉祥物IP等)來吸引用戶的策略,進行搜索引擎、新媒體平臺、應用商店等線上宣傳,或掃樓發傳單、贈品合作、地面廣告等線下硬廣。但是宣傳渠道并不是越多越好,事前需要考慮到關于流量效率的問題并做好渠道調研,是不是精準流量來的用戶,拉新渠道不精準,獲客成本高,用戶質量差。

          在宣傳過程中,適當使用折扣、抽獎、代金券、紅包、限時限量購等營銷工具,或者團購、預售/眾籌、邀請返利、二級分銷等社群裂變方法。值得注意的是每一款產品都應該有一個核心的拉新手段/方式,東打一棒西揍一拳,如此從各個渠道得到的流量不會太多。所以建立快速穩定的熱點擴散渠道是有必要的,推薦一種方法:強控公司員工朋友圈,然后維護核心種子用戶群,再利用雙微一抖等第三方管理工具進行效果檢測,最后對每個熱點創意進行總結、復盤和優化。

          盤活:從用戶接觸產品后需要有一個引導文案或指導規范,突出產品核心功能、利益點,讓用戶知道這款產品是干嘛用的,對TA有什么好處,給他什么東西希望TA留下來。常見的引導方案就是設計豐富的新手特權,引導用戶嘗試不同類型資產投資,比如完成簽到、轉發、評論、收藏等各種任務就可以獲得各種獎勵。用戶能夠完整地使用或操作產品全流程,才算是一個有效的、相對穩定的新用戶。如果產品服務體驗不精準/認知度差,新用戶容易流失、產品口碑差。

          防流失:一次給用戶發多張券,分為不同的面額和門檻,引導用戶完成5次以上消費,同時設置連續購買多單就給獎勵,縮短付費時間間隔,加速用戶生命周期的進化。還可以搭建常態化的活動體系,在固定的時間點(如:每天10點,每月8日等)開展每日秒殺、限時搶購、會員日等的促銷活動,讓用戶形成具備記憶點的活動預期。

          對于一個已經流失的用戶,可以通過電話召回(重點用戶)、短信/App消息、品牌宣傳(老用戶)、大型促銷活動等手段召回,為他們提供限時特權、紅包/抵用券、新品發布/重大改版、重點促銷商品等新體驗。為什么我們寧愿花時間去召回一個老用戶而不是尋找新用戶,因為召回一個老用戶帶來的效益要遠大于一個新用戶的獲取成本,老用戶已經熟悉我們的產品,不需要太多的教育成本,而且能夠直接帶來效益的。當然并不是真的等到用戶已經流失才會采取行動,我們可以通過RFM模型監控用戶活躍情況,對潛在的流失用戶進行預警,并且采取相應的挽留措施。

          強粘性:建立用戶成長體系,比如用戶積分體系、積分商城,用戶等級體系、會員體系、勛章、證書等。用戶成長體系是一整套驅動用戶成長的運營機制,是在用戶數據模型的基礎上,比如交易類產品的累計交易金額、交易頻次、交易類型、交易質量等,內容類產品的用戶參與度、內容貢獻度、用戶活躍度等,工具類產品的用戶活躍度、用戶ARPU值、用戶訪問時長等,找到用戶成長的關鍵路徑和核心驅動力,從而搭建用戶成長的激勵通道和連接用戶行為的觸達通道。

          促轉化:常見的有提高客單價、增加關聯購買和拓展盈利模式三種手段。提高客單價手段再細化就是滿額立減、滿件立減的滿減優惠,加N元送配件/日化用品的加價購,第二件半價的N件N折。制作場景、季節專題或推出產品套裝就是為了增加關聯購買。在拓展盈利模式里,除了線下探索,將產品和服務延展到線下,尋找產品/服務強相關的常用周邊商品,提高品類的豐富度。還可以設置會員專屬服務,不同等級會員享受不同的服務,首次付費有優惠。

          在用戶運營工作中最難的是搭建用戶成長體系。當然不建議產品剛上線就做用戶成長體系,當用戶達到一定量級,知道哪些用戶對產品貢獻值最大后再考慮要不要做、怎么做,而且用戶等級不易超過4級。據調查,抖音至今都沒有對外公開(就當不存在)用戶成長體系,用戶也不會知道自己處于哪個等級,有哪些特殊權益。

          用戶成長體系搭建的基本流程

          首先建模型,可以沿用用戶漏斗模型,提升從訪客、下單到支付每個環節的運營效率,實時關注用戶反饋,看用戶是在哪個點流失的,這個點就是要優化的地方;也可以沿用用戶生命周期模型,判斷用戶處于哪一階段,設定對應的運營規則;還可以沿用用戶價值模型,根據用戶在不同階段對產品的貢獻度,設定對應的運營規則。

          其次搭通道,激勵通道主要有秒殺/限時搶購、抽獎、代金券/紅包、特權等級、積分/成長值/經驗值、任務引導;觸達通道主要包括關鍵訪問路徑、短信、郵件、產品通知功能、微信渠道、個性化Push等。

          最后促成長,通常采用補貼策略、在成長節點觸發消息推送和抓牢動力引擎三種方式。動力引擎主要包括:內容類產品的高質量、率、內容形態豐富,電商類產品的低價格、高品質、物流快、品類豐富,教育類產品的優秀師資、系統化教學,金融類產品的供給渠道、電商化、游戲化,出行/外賣類產品的供給驅動、匹配、優質服務,工具類產品的解決具體問題的效率。

          第八步,環顧行業發展部署下一階段運營重心

          了解行業發展及競爭對手動態,及時調整品牌的價值主張,加快催熟成長期的產品,或擴大品牌影響力的邊界延長產品的成熟期。無論運營重心怎么變,但品牌人格和人格特質不能改變。

          在擴大品牌影響力的邊界方面,除了借助用戶故事都自帶自傳播的特點,對有代表性的用戶故事進行包裝,前提是故事一定要真實,把品牌人格的塑造得更富有真情實感。個人推薦發起社會公益活動,就像螞蟻森林一樣打破虛擬空間,通過線上攢能量種樹活動,再把4億+用戶的這份參與熱情展現到線下——在沙漠種真實的樹苗。

           文章來源:人人都是產品經理    作者:炒冷飯的二叔


          用戶不信任你做的產品/界面?不如試試這5招

          鶴鶴




          前言:


          什么是產品的信任感??


          指:基于產品為用戶提供‘可靠服務、價值依賴’的一種情感體驗。


          這種體驗不僅影響著用戶黏性的強弱、業務目標的實現,也影響著不同生命周期下給產品給來的價值。如圖:



          而在產品與用戶間建立信任感的過程中,我驚奇地發現有3個因素貫穿始終:理念 > 內容 > 表現。


          ‘基于什么樣的理念,向用戶傳遞什么內容,并且怎么表現?!?/strong>



          所以,未來3篇文章主要圍繞‘信任感的打造’,希望能系統性地認識它,挖掘更多工作上可實用的小技巧。


          今天先分享第一篇:信任感的理念層



          信任的本質:是讓人覺得真誠、可靠、放心等。換句話說,它就是一種‘為用戶著想,建立產品溫度’的理念/方向,從而引導后續的內容都圍繞該理念而進行。


          那么,如何才能為用戶著想,慢慢建立起對產品的信任感呢??


          既然為用戶著想,那么可以試著從‘減少用戶的投入成本’切入。



          Part1:減少健康投入


          健康投入,指用戶使用我們的產品,可能會對身體上帶來直接或潛在的負擔/影響。比如視頻看久了,眼睛就會感到酸痛。


          針對這些負擔與影響進行的一些關懷提示,可以用戶提前消除、減輕這些痛苦,拉進與用戶間的距離。


          不同產品類型擁有專有的關懷點,所以比較通用的主要有5個:使用時長、使用姿勢、夜間休息、夜間護眼、音量大小。



          a.使用時長提示:


          除純工具類產品之外,大部分用戶在產品上都有一定的使用時長(尤其是內容消費類產品),對于‘連續使用N分鐘、或者滿足特定時長’的用戶,可針對該時長進行休息提示。


          如有道精品課,在用戶觀看課程滿40分鐘 時有個時長提醒:




          b.夜深關懷提示:


          深夜本身是一個休息的時間,但還是有大量的‘夜貓子’根本停不下來。不管是主動性的娛樂消費,還是被動性的信息/工作處理,都將手機‘進行到底’。


          此時對于‘深夜忙碌’中的用戶,夜間的關懷就是一個切入點:比如企業微信,會在深夜啟動頁上展示 ‘夜深了,xxx’的文案提示。



          雖然只是簡單的一句話,但還是能感受到鵝廠對員工的關懷。


          而且不管是C端還是B端,只要有用戶在深夜使用產品的可能,都可針對性地給予關懷設計,體現產品的‘人性’。




          c.夜間護眼提示:


          夜晚周圍的光線會變得幽暗,部分手機屏幕會自動變亮。時間一長會嚴重刺激用戶雙眼,并造成視覺疲勞(尤其是小學生群體)。


          此時對用戶進行護眼提示,不失為一個用戶關懷點。


          再如有道精品課:




          d.使用姿勢提示:


          我們日常都會將手機橫過來看視頻、看八卦。而且相信各位都有過這樣的經歷:



          當手機長期處于某個屏幕狀態+重力傾向時,用戶難免會出現手酸脖痛的情況。


          此時進行使用姿勢的糾正提醒,亦能起到關心用戶的效果,從而建立良好的產品印象。


          還是以有道精品課為例:



          e.音量提示:


          這個大伙都知道,過大的音量會影響耳朵聽力。一般出現在各種音頻、視頻的產品與功能中。





          Part2:減少金錢投入


          沒有人不會在意自己的錢包(除非你是對錢不感興趣...),金錢上的收入與支出很大程度上會影響 人們對某事物的看法。


          產品也是如此,若能幫用戶減少金錢上的支出,或者帶來真實收入。不僅能極大提升用戶對你的信任度與黏性,還能增加產品的競爭力。


          比如高德地圖的打車功能,能顯示所有車型的價格預覽,幫助用戶選擇所需價格的車型。



          無論是商品優惠券,還是返利。



          只要能幫助用戶錢包上的‘節源’或‘開流’,都能引發用戶的信任感,從而信賴產品。




          Part3:減少情感投入


          情感投入,是指用戶基于內心活動和情緒感受,對某事物所表現出來的一種想法。


          這種想法制約著 用戶是否接受我們的產品服務。一般體現在:安全性、性價比、真實性 3個方面。


          a.安全性 - 放不放心:


          人們面對某事物 可能會帶來的傷害/損失時,都會有一種本能的“警惕感”。


          就拿此次疫情來說,對于有‘出差住房’訴求的用戶來說,‘住的安全’是重中之重。因此尋找一家‘無感染、每日消毒、衛生干凈’的安全酒店,可以減低用戶選擇我們的警惕感。



          而在酒店列表中,帶有‘嚴格消毒’、‘健康守護’等安全標簽的酒店,會給人帶來一種安全、放心、信賴的心理效應,從而提升該酒店的轉化率。




          b.性價比 - 值不值得


          性價比是人們衡量‘付出成本與回報價值’間的一種決策依據,沒有誰會喜歡付出小于回報的事物。


          而為了讓用戶降低這種決策依據,除了自我服務/實力的展示外,往往需要一種“參照物”來凸顯性價比。


          如美團上的‘滿減神器’,通過不同的食物/價錢間的對比,讓用戶買到最具性價比的食物。



          而“參照物” 的形式多種多樣,不管是競品數據。



          老版本也屬于一種競品


          還是是各種優惠信息、額外禮物/禮包、售后服務等等。



          目的都是通過該參照物,向用戶傳遞一種‘劃算’、‘值得’的心理效應。



          c.真實性 - 真不真實:


          光是性價比高還只是片面依據,至于內容是否屬實,成為了我們與用戶建立信任感 中最重要的影響因素。因為沒有人喜歡被騙、喜歡虛假事物。


          而真實性的建立,在‘電商領域’應該被運用得最多。如大牌背書、證書授權、專家介紹、明星代言、官方保證、銷量成績、用戶反饋...等等。




          展示自己的真實、最具實力的一面即可,別過度吹噓與包裝,用戶又不傻。




          Part4:減少腦力投入


          人們一向不喜歡復雜的東西,除了不易理解外,更擔心因為自己的理解錯誤,會給帶來意外的損失。


          幫助用戶減少記憶負荷、順暢完成操作目標,是每個產品必不可少的設計點。


          如微信轉賬,輸入數字時會檢測對應的數額,減去用戶邊輸入 邊計算“這是多少錢”的腦力投入。



          而且對比支付寶的轉賬,微信這點確實做到了‘洞察用戶需求’。



          再如賬號注冊,提供‘剩余步驟’能讓用戶了解 當前處于哪一步、預測完成整個操作還需多久。





          Part5:減少體力投入


          除了記憶負荷,‘操作負荷’的減少也是一種‘為用戶著想‘的方式。我們身邊也存在太多這方面的例子:


          如手機上,如淘寶的快鏈彈窗、支付寶的轉賬提示,都是前置用戶的目標,縮短操作流程。



          如電腦上,如Mac會保存耳機音量。


          下次插入耳機時,會將揚聲時的音量,自動調整至上次耳機插入時 所記錄的音量。這樣就免去了重新調整音量的操作。


          以網易云音樂為例:注意 揚聲時和耳機插入后 的音量變化.



          這些都是幫助用戶快速使用,從而減少體力操作的方式。除了前置用戶目標、保存記錄 外,常見的還有:給予默認值、自動選擇/處理、多選與批量等等。



          總結:


          以上就是關于理念篇的內容,讓今后的內容設計有了明確的方向。下面是走查表,平時設計功能、制作界面的時候可以看看,增加產品的溫度。領取方式:公眾號回復【信任1】


          轉自:ui中國-

          淺析如何設計交互缺省頁

          鶴鶴

          大綱



          1 哪些狀態需要缺省頁  
          2 缺省頁的表現形式
          3 缺省頁的設計技巧

          導語:缺省頁指頁面的信息內容為空或信息響應異常的狀態;設計缺省狀態的作用不僅是引導用戶在異常邊界狀態的操作提示,同時也是安撫用戶體驗情緒的重要場景;更重要的是為邊界場景營造出良好用戶體驗。通過分析缺省狀態產生的原理,從而更為準確的把握交互缺省頁的設計原則。


          1 哪些狀態需要缺省頁 

          談到缺省頁面可能是設計師最容易忽略輸出的范圍,可能直到對接的開發同學提出來,“這個頁面,如果沒有數據的時候,該怎么顯示???”。為了更好的把控設計缺省頁交互狀態,首先要了解缺省頁出現的原理。App頁面內容(包括圖片、文字、數據字段等等)都是請求服務器數據,順利返回后,正常顯示到客戶端頁面。在了解清楚基礎實現邏輯后,就可以開始梳理整理缺省狀態的設計思路。



          圖1 缺省狀態的場景梳理圖


          缺省狀態包括:系統層、信息層、空白層。
          系統層:指當用戶請求服務器時,返回提示請求提交失敗,并檢測到失敗原因時呈現的頁面;例如:加載失敗、服務器異常、無網絡等;頁面一般會有重新請求的快捷按鈕。文案上可做失敗原因的細分描述,也可節約成本使用網絡異常的統一文案。

                              


                                                         
          信息層:請求服務器數據成功,但返回的數據異常的頁面;例如:內容已刪除、內容已下架、內容不存在;文案內容以提示數據類型的缺失為主。顯示形式除了常有的全屏缺省圖,還會出現在數據列表下單一內容缺失的缺省模塊化的情況,例如:單一作品在書架上顯示已下架。





          空白層:請求服務器數據成功,但顯示無數據;內容頁在無數據時需要缺省狀態進行表達;例如:頁面空數據、搜索無結果等??瞻醉撁鎸儆谡>W絡顯示場景,所以一般會在缺省頁附帶有相似屬性模塊的用戶引導,爭取用戶重復消費的目標,滿足用戶的操作的訴求。



          最后根據每個不同的缺省狀態,梳理產品相對應的場景。逐一根據場景特點來設計頁面內容。那缺省頁的設計有哪些表現形式呢?


           2 缺省頁的表現形式 

          沒有用心設計的缺省頁無法給用戶帶來良好用戶體驗,并可能給用戶帶來困擾,如下圖:某小眾直播平臺的拉新邀請頁面,無邀請記錄狀態下沒有任何有效反饋信息,用戶不能明確得知到底是網絡問題還是賬號同步出錯亦或者是沒有一次邀請。正確的缺省頁設計內容理應明確表達出符合用戶心理預期的視覺場景表達(圖形);和使用易理解和語法恰當的表達當前的異常狀態(標題)甚至于引導用戶解決問題的文案描述。



          圖5 缺省頁的錯誤示范

          2-1視覺圖案+文案

          此類缺省設計形式一般應用于表達系統性無響應或初始空白態的缺省場景。視覺圖案一般使用app吉祥物或主色調延展出的icon或插畫來表示缺省狀態;文字:通常為“標題”或“標題+描述”結構;標題通常是表達出現缺省的原因;描述文案則說明結束缺省狀態的解決辦法,如“請檢查網絡是否順暢”  等等。



                         
          2-2 視覺圖案+文案+引導

          此類缺省設計形式一般運用于需要用戶引導操作來達到業務目標的缺省場景。在視覺圖案+文案的基礎上加入引導模塊,主要作用于避免用戶在數據邊界的狀態下,會因為無法達到操作目而提高的跳出率。引導模塊的內容包括:相似屬性內容,相似行為目標按鈕或解決缺省狀態操作按鈕,加入引導,用戶進行某項行為或者感知某些信息,對于功能的教學和使用頻率的提升有著重要作用。引導模塊的形式也是日新月異,逐漸變成新用戶業務引導的作用,不僅限于頁面平鋪,也可以做成固定氣泡微動效,例如:抖音的發布缺省頁。

           



                                           
          3 缺省頁的設計技巧 

          缺省頁除了常規的提示型設計方法,還有許多其他的設計技巧,幫助用戶體驗在遇到困難,更好地安撫用戶的情緒。這些設計技巧有些是替代原來的缺省內容,讓用戶有更多地消費空間與深度。有些是拓展缺省狀態的補充內容,讓用戶不容易跳出頁面,增加用戶的消費時長。具體如下:

          3-1 使用推薦內容

          缺省狀態中的空白層非常影響邊界情況的用戶體驗,提出一種假設,是否可以刻意推薦相同屬性的內容呢?這樣的界面既不會顯得蒼白無力又可以留住用戶的注意力。相似性的內容也可以解決用戶目標的迫切性。所以說,這種方法非常適合內容型產品中使用。例如:新用戶在打開電商產品的購物車時候,理應是空白無消費行為的操作記錄。那么平臺方通過用戶畫像與熱門排行算法推薦了一個商品流。這樣可以解決用戶無目標性挑選的訴求,增加消費時長。至于產品如果確定用戶畫像的推薦算法,可以通過獲取第三方登錄的個人基本數據之后,才給我推薦了數據庫內相對應標簽的熱門商品,這樣推薦的精準度也會高些。 
                       




          3-2 使用緩存

          是否使用緩存內容代替缺省狀態?根據產品特性來判斷,工具類、金融類等同類型產品不適合使用緩存;因為用戶交互操作的數據必須保持實時性與真實性。而內容型、電商類等類型產品適合使用緩存來代替缺省狀態;理由:用戶消費內容的轉化路徑是先消費后轉化的行為特點,不存在系統操作門檻,且緩存內容可以代替產品的缺省狀態,安撫用戶操作失敗所帶來跳出率過高的風險。

          3-3 情感化表達

          當缺省頁給到用戶時,通常省時省力的做法就是老老實實告訴用戶當前的狀態,最多配上一個具有通識性的灰色icon。但是,秉持著以用戶體驗為己任的時代,我們其實可以把缺省內容表達得更加生動形象一些。在這里會加入一些情感化的表達,而不是僅僅只是做到準確的目標而已,比如加上活潑的插圖故事,或者把文案寫得更加擬人化、喜劇化一些。這些配圖在讓用戶明白當前的狀態的同時,往往也能引發用戶會心一笑,從而彌補空白頁面帶來的失落感甚至可以帶給用戶一些正面的情感。如下圖:
                           



          3-4 提供新任務

          通常缺省頁的引導模塊都著眼于解決當前任務。如果碰到沒有解決方案的情況(例如:404,服務器崩潰等)可以提供給用戶具有情感共情的新任務,讓他們暫時忘記無法達到目標的挫敗感,又有體諒的情懷。幫助建立正向積極的品牌價值觀。例如:訪問騰訊網時訪問失敗的時候,網頁除了顯示404狀態之外,還會顯示騰訊“寶貝回家”的公益尋人計劃。將缺省頁與公益內容相結合,不僅改善到用戶缺省狀態。也貫徹騰訊價值觀“用戶為本,科技向善”的輸出。一個好的缺省頁也可以承擔社會責任,讓公益傳播到每個角落。



          圖10 騰訊網404公益任務缺省頁

          結語:作為設計師有時會聽到需求方表述“這種極少出現的情況,我們可以暫且不管它。”但是細節見真章,所有優秀的體驗設計都必須照顧到方方面面的缺省情況。讓每個用戶的流量價值發揮到最大,產生相互信任的良好的品牌關系。這樣的平臺生態是良性的,這樣的產品會更有流量轉化的商業化價值。


          轉自:ui中國-騰訊動漫TCD

          最近爆火的小宇宙APP,有哪些值得關注的產品細節?

          濤濤

          「小宇宙」是即刻團隊開發的播客App,目前已上架各大應用商店,僅能通過邀請碼使用,導致一時間微博上出現「一碼難求」的情況,那它與其他播客App有什么不同?

          內容推薦

          進入App即展示「信息流」推薦,每日更新3條播客「信息流」推薦,聽的時間越長,會推薦越多更加精準的播客內容。那用戶如何判斷推薦的單集是否有自己想要聽的內容呢?如下圖所示,頁面上單集信息展示條目的空間較大,在首屏約能展示2條的單集內容,除了基礎的節目封面、節目名稱及單集名稱外,小宇宙巧妙地通過將熱門評論外顯,輔以展示播放量及評論數量,引導用戶點擊進入二級頁面,進而形成轉化。

          強大的搜索

          不同于傳統播客App僅能搜節目名稱,小宇宙還支持搜索單集和用戶,甚至是節目內頁的內容也可以搜索到。對于那些「我對欄目本身不感興趣,只想聽其中的某一集」或者「我關注某個人或某個話題,想聽聽看關于他的一切」的節目,使用單集搜索功能可以更簡單地直達所需。

          讓人驚喜的是,在節目內頁長按選中任意詞匯可以呼出「智能搜索」功能,這個智能體現在它允許用戶選擇用magi搜索和用互動百科搜索相關內容。這讓我想到了Mac上一款很好用的工具PopClip(通過對文本內容的擴展來提升操作效率),小宇宙的智能搜索對于邊聽節目邊扒關鍵詞的「考據黨」來說,無疑會大幅提升在聽節目時獲取資訊的效率。

          更便捷的互動

          在單集播放界面,除了常規的進度條拖拽、快速后退/前進等功能外,還有一個點贊功能,用戶聽到精彩的內容或引起共鳴的部分可以通過點贊進行互動,從點贊功能的反饋到進度條的高度的升起都能夠進行實時的反饋,同時也可以幫助其他聽眾了解單集內容的關注點,以此為??營造出符合他當時?為和感知的情景,可以達到提高用戶的參與度的目的。

          評論頁的輸入框常駐于頁面底部,點擊后輸入框高度延伸,引導用戶評論互動,此時用戶如果是通過單集播放界面進入的評論頁面,還會出現標記時點的選項,勾選后評論內容即帶上了單集內容的時點。而時點高亮色+下劃線的表現形式可以引導用戶聚焦注意力和點擊進行內容的收聽,當其他用戶點擊帶評論中的時點即可直接跳轉至對應時間點播放單集內容,為??之間的互動建?起羈絆,方便用戶間的討論交流,進而提升了點擊率和單集的收聽率。

          另外,在用戶輸入評論時,輸入框并不是以模態的形式出現,在用戶評論的過程中依然可以滾動頁面進行交互操作,這樣做的好處在于不打斷用戶操作的連續性。以iPhone X的屏幕高度為例,除去標題欄+評論輸入框+鍵盤高度外,留給評論本身的空間僅有大約1/3左右,在空間有限的情況下,用戶滾動屏幕查閱感興趣的評論或針對性進行回復的行為非常連貫,非模態的處理可以進一步降低用戶互動的門檻。

          這里有一個改進小建議,輸入框內的預制文案可以換成引導性更強的內容或由系統自動生成一個場景適合的評論,這樣不需要用戶自己思考寫什么內容,降低用戶評論操作的成本,提高用戶參與度。

          小宇宙目前在社交上的嘗試處于用戶友好型狀態,用戶可以查看他人的播客訂閱列表,發現同好,即「相近信息的收集愛好者容易獲得共鳴」。如果這個有共同興趣愛好的人剛好是用戶收聽的主播,就可以很容易拉近聽眾和主播的距離,主播對于聽眾來說不再是手機屏幕后面冷冰冰的聲音,他更像你的一個朋友,你可以去了解他的喜好,討論共同話題,更好地跟他進行互動。

          情感化共鳴

          不同的主題表達了不同情感,針對不同的社會群體的設計風格也會有所不同。

          回到產品本身,小宇宙根據播客節目封面的主題色來適配不同播客信息頁的視覺風格,營造出適合不同播客風格的氛圍,以此來傳達不同播客節目的特點。

          若在熱門的單集下評論,且評論點贊數最高,小宇宙領航員(官方賬號)會給你留言,告知「你的評論上首頁啦!」,通過這種與用戶互動的方式激起??的情感認同,提升用戶評論的積極性。

          點擊「加入播放列表」,通過動效形式給予用戶反饋,讓信息的展示更生動自然,為產品增添趣味性的同時,給予用戶更美好的操作體驗。

          下拉刷新動效中以「宇宙」圖形為載體,將產品的Logo融入了其中,賦予產品獨有的個性和靈氣,讓用戶切身感受到這是一個可以探索的「宇宙」,而不是一個冰冷的工具,在減少用戶等待過程中引發的負面情緒的同時強化了品牌形象。

          可以改進的地方

          產品中使用了較多無文字按鈕,對于初次使用的用戶來說存在一定認知負荷,不知道這些都是什么功能。

          存在相同樣式的圖標承載不同功能操作的問題:「播放列表」功能在「我的播客」頁中點擊后是加入播放列表操作,而在播放列表模態彈窗中,點擊后進入播放列表的編輯狀態,同個樣式的按鈕承載了不同功能操作,容易引起用戶困惑,帶來冗余的學習成本。

          當你收藏了一篇文章,收藏按鈕狀態從「收藏」變為「已收藏」,點擊「已收藏」可以取消收藏狀態,這是用戶對于兩個狀態切換已有的認知。而小宇宙的加入播放列表功能,在點擊加入播放列表后,再次點擊「播放列表」按鈕,會提示「已在播放列表」,而不是通常認知中的「取消加入播放列表」,容易帶來認知錯誤。

          列表右側的「已訂閱」按鈕,視覺感知上像是不可點的狀態,點擊后提示「已取消」,再次點擊提示「訂閱成功」。對于有明顯狀態變化的功能性操作可以省去toast,這里通過「已訂閱」和「訂閱」狀態的按鈕樣式變化已經給予用戶清晰的傳達反饋,再次提示反而多余。

          小宇宙的體驗分析就到這里,總的來說雖然存在一些需要優化的細節(畢竟還處于邀請碼體驗階段),但是設計師可以學習的設計亮點有很多,感興趣的小伙伴可以下載體驗一番,也歡迎大家一起留言交流。

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合