<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-link進行跳轉

          <router-link

          :to="{

          path: 'yourPath',

              params: {

              name: 'name',

                  dataObj: data

          },

          query: {

              name: 'name',

                  dataObj: data

          }

          }">

          </router-link>

          二. 通過編程導航 $router進行路由跳轉

          1.路徑后拼接參數

          通過路徑后直接拼接來傳遞參數



          getDescribe(id) {

          // 直接調用$router.push 實現攜帶參數的跳轉

                  this.$router.push({

                    path: /describe/${id},

                  })



          對應路由配置

          注意:此方法需要修改對應路由配置,需要在path中添加/:id來對應 $router.push 中path攜帶的參數。



           {

               path: '/describe/:id',

               name: 'Describe',

               component: Describe

             }



          獲取傳遞的參數值



          this.$route.params.id

          1. 通過params來傳遞參數

            傳遞參數

            通過路由屬性中的name來確定匹配的路由,通過params來傳遞參數。



             this.$router.push({

                      name: 'Describe',

                      params: {

                        id: id

                      }

                    })



            對應路由配置

            注意這里不能使用:/id來傳遞參數了,因為已經使用params來攜帶參數了。



            {

                 path: '/describe',

                 name: 'Describe',

                 component: Describe

               }



            獲取參數



            this.$route.params.id

            1
          2. 通過query來傳遞參數

            傳遞參數

            使用path來匹配路由,然后通過query來傳遞參數

            這種情況下 query傳遞的參數會顯示在url后面?id=?



            this.$router.push({

                      path: '/describe',

                      query: {

                        id: id

                      }

                    })



            對應路由配置



             {

                 path: '/describe',

                 name: 'Describe',

                 component: Describe

               }



            獲取參數



            this.$route.query.id




          人工智能行業常用名詞科普

          資深UI設計者

          本文整理了人工智能行業中設計師需要理解的一些名詞和內容。

          一方面供自己學習思考,另一方面也希望能幫助到準備投入到人工智能行業的設計師。之前聽有的朋友講到,覺得自己沒有計算機背景,有點害怕進入到這樣一個領域來。

          沒有計算機背景沒有關系,只要對這個行業充滿好奇,一個個的問題解決掉,在你眼前的迷霧都會散去的。

          先簡單舉幾個人工智能在生活中有在應用的例子:

          像現在有的超市寄存物件,開箱時采用的人臉識別;像家里購置的智能音響,時不時還能跟它聊上幾句;像接聽到的銀行電話(是的,對方可能是機器人噢);像在淘寶上咨詢的客服小蜜;像你手機里的虛擬助手….等等這些都是人工智能在生活中的應用。

          人工智能在設計領域的應用也相當廣泛,具體可以看這篇文章:

          這幾個例子是在生活中比較普遍能接觸到的,實際人工智能應用的領域還在不斷的擴大,我們甚至都無法想象到,未來的生活會是怎樣的狀態和場景。

          在這家公司之前,我做過語音交互類的產品交互設計。當時在定義人與設備進行語音交互時,會是怎樣的一個交互場景。從說喚醒詞到發出指令,從收到反饋到繼續對話。喚醒后等待的時間、結束的規則等等這些。

          而現在,我大部分時間是在設計工具,如何讓使用者能快速的創建出一個智能機器人。如何讓機器人的創建者方便快捷的添加機器人的相關數據和創建出對話場景。

          所以在進行這些工具的設計之前,有些名詞概念,會需要設計師來了解一下,能讓我們更好的理解人工智能的一些原理以及能夠讓設計師具象化到實際的設計中,甚至能基于此技術/原理來進行相關的創新或研究。

          整理內容如下:(內容基于工作及自身理解,如有概念理解錯誤,歡迎指正)

          下面嘗試用較易理解方式來解釋這些名詞:

          與機器人進行對話,首先就需要讓機器人懂我們說的話,這其中,就需要來關注到自然語言處理,通過自然語言處理技術,能夠實現我們與機器之間「無障礙」對話。

          • 自然語言處理(NLP):是人類與機器溝通的中介,需要靠它來理解、處理和運用自然語言
          • 自然語言理解(NLU):指的是機器的語言理解能力,將人類語言轉化為機器可理解的內容
          • 自然語言生成(NLG):指的是機器通過一系列的分析處理后,把計算機數據轉化生成為自然語言內容,讓人類可理解

          我把這三者關系畫了張圖示,我是以這樣的方式理解的

          從圖中可進一步看出,NLU 和 NLG 是 NLP 的子集,而 NLP 是人與機器溝通中很重要的存在。

          涉及到語音就會經常聽到 ASR 和 TTS

          語音識別(ASR):將語音內容轉為文字

          如微信里面,當別人發的語音信息不方便外放收聽時,可以轉為文字查看

          語音合成(TTS):將文字內容轉為語音

          如現在很多的閱讀軟件,支持播放,有的就是利用 TTS,直接將文本內容轉為語音播放出來。

          我試著將上面提到的 NLP 和 ASR、TTS 組合起來,關系可以如下圖所示

          當我們說一句話的時候,機器知道我們表達的是什么嗎?

          意圖(Intent):一個人希望達到的目的,或者解釋為想要做什么,他的動機是什么。

          如:

          • 我對天貓精靈音箱說「聲音太小了」,那我的意圖是什么?意圖是「將音量調大」。
          • 「看下明天上海飛北京的航班信息?!?直接意圖:查航班信息,潛在意圖:「買機票」?

          槽位(Slot):可以理解為系統要向用戶收集的關鍵信息。

          如:

          「買張明天從上海到北京的機票」

          上面這句話中,獲取到意圖(買機票);提取關鍵信息 時間(明天)、地點(出發地:上海;到達地:北京)
          這些關鍵的信息就是槽位,當系統獲知到這些信息后,就能去執行下一步動作。

          還可以這樣理解,當我們去銀行營業廳辦理卡的時候,會填寫一張表,表每個要填寫的選項,就是一個個的槽位。槽位就是為你服務的人員要從你那收集的關鍵信息。

          實體(Entity):用戶在語句中提到的具體信息

          實體這詞放在生活中,我們很容易理解,就是實實在在的物體,像桌子、電腦、熊貓等等這些都是實體。

          但是在人機對話中,機器理解人的語句內容,會識別出語句中的實體信息(如:地點、人名、歌曲名等),然后進行標記。

          那槽位和實體是不是講的是一回事?只是不同的說法?

          我之前有一度陷入這樣的困惑中,但其實這兩者還是有所區別的。比如,一個實體是數字,但是在語句中,數字將代表不同的含義。

          如:

          人:有沒有10元的鮮花? 機器人:玫瑰花10元一支 。

          這句話中,實體number「10」,但這個 10 在句子中表達的是價格,所以收集到的槽位信息是價格:「10元」

          這樣說可能還是不太能理解,那我們可以先了解下,在一句表達中,需要進行槽位信息收集,但機器如何知道「買張明天從上海到北京的機票」中,「上?!故浅鞘?,并且「上?!故浅霭l地呢?

          「上海」這個詞會被建立在一個城市實體詞庫中,這是「上?!鼓鼙蛔R別到是「城市」的原因。

          其次,通過將解析槽位加入語料中,加以訓練讓機器學習相關表述結構,來獲知該句式中,收集到的第一個城市是出發地,于是把第一個城市填到對應的槽位中。

          使用什么工具來讓機器知道,這個信息是要提取的信息?

          解析器(Parser):抽取/解析用戶語句中的關鍵信息

          上一個講到實體,這里講到的解析器則是這么個工具,用來抽取這些信息。比如會有些通用的解析器如時間解析器、城市解析器、歌手解析器等等。

          解析器的類型也比較多,如通用解析器、詞典解析器、正則解析器、組合解析器等等,這里就不再擴展開講具體解析器,實在過于復雜了。

          命名實體識別(NER):用來識別具有特定意義的實體。主要會包括像機構、地名、組織等。

          是不是發現,解析器和 NER 在做差不多的事情?我是這樣理解的,解析器的話是一個更大的存在,其中包括了 NER。解析器下會有不同類型和不同功能的工具來實現關鍵信息的識別/抽取。

          在我們與機器人對話時,一般會涉及到四個不同類型的對話,開放域的聊天、任務驅動的對話、問答(FAQ)和推薦。

          上面是在有次分享中提到的,這四個不同類型的對話,在機器人平臺中,會需要借助不同的功能模塊來實現。

          任務對話(Task Dialogue ):有上下文聯系,就像我們要去訂票、訂餐之類的一段任務型的對話。

          我們公司產品中,任務引擎模塊就是做這個任務對話的創建,比如,要訂機票的場景。用戶在這個訂機票的場景中,會涉及到的對話內容、流程的設計。

          知識圖譜(Knowledge Graph):這個可以理解為可視化關聯信息。
          比如:查詢一個明星的身高、年齡,他的學校、他的女友,他的相關作品,這些基于這個人而構建的信息庫,都可以通過知識圖譜在做整理。并且在構建時能夠做到可視化的了解。

          要讓機器人知道,它腦子里有貨了!

          訓練(Train):這個概念可以這樣理解,比如你創建了個機器人,但是它什么都還不懂,于是你塞了堆知識給他,這時,它就需要自己訓練學習了。訓練好了,就能回答你塞的那堆知識里的問題了。

          講到這就忍不住想用這個學習的例子,來簡單講下一般機器人的創建流程。像我們在學校,會經歷上課學習新知識-復習溫習-考試-整理錯題集,以此循環進行。

          這個創建機器人的流程也是一樣通過知識的導入/創建-訓練-測試-優化-上線-優化,以此循環,不斷強化機器人,讓它越來越智能。

          其他:

          數據標注:將對話日志中的有價值數據做標注(標記/匹配/關聯之類)。

          因為人的表達萬千,多種表達方式都代表的同一個意思。有時用戶說了句話,是語料庫中并不包含,于是機器人可能就答非所問了。

          Ai 訓練師們就可以將這些數據信息標注到對應的問題中去,這樣當用戶再用同樣方式表述時,機器人就能如預期回答了。

          講到標注想到之前在朋友圈很火的你畫我猜,谷歌推出的這個小游戲席卷朋友圈。他們用了個如此聰明的做法,其實我們參與其中的做法就是在做數據標注,而且還是主動提供數據的那種。

          這也反映了,數據對于機器人的重要性,通過不斷的進行數據維護和補充數據,機器人就會越來越理解人,表達也會越來越智能。就跟我們學習一樣,不斷學習才能夠理解其他的含義,甚至當認知能力提升了,看待問題的角度才能不一樣。

          文章來源:優設

          平面排版如何打造節奏感?

          資深UI設計者

          我們在做設計的時候總是會聽到,要注意「節奏感」。關于節奏感這個詞,大多數人似懂非懂,可能明白它是什么意思,但是在設計中都應該注意哪些節奏感,卻還沒有一個比較清晰的思路。我們都知道音樂是有不同種類的,有古典音樂,有搖滾音樂等,它們之間的不同,很多時候是節奏給我們帶來的感受不同。在設計上也是一樣的,掌握節奏感就能控制畫面的變化和氣質的傳達。

          什么是節奏感

          先理解什么是節奏感。

          我們常說的生活節奏,可以理解為,假如你在規定時間里,你只需要上班和回家兩點一線的生活。

          但是當你有了孩子還要去幼兒園接送,晚上還要去健身等,這樣就是生活節奏加快了。

          我們常聽到的節奏感其實是來源于音樂的。

          不同節奏感的音樂會給人不同的感受,就像音樂1這種,會給你一種平靜舒緩的感覺。音樂2這種就會有種比較喧鬧、比較活潑的感覺。

          說到設計的中的節奏感,從繪畫里也可以看出,這是吳冠中的繪畫作品,看似很簡單的畫面,但是這種水流的蜿蜒的感覺讓這個畫面變得靈動起來。

          去掉這些流動的線之后,整個畫面就變得很平靜了。失去了那種蜿蜒流潺的感覺。

          在我們很多圖像網站里都用了這種瀑布流的方式,不僅是為了方便不同尺寸的圖片的載入,在很多時候這種方式,相對于平鋪的圖片放置會更讓人舒服而不枯燥。

          節奏與韻律

          在平面設計里,我們在學習平面構成的時候節奏與韻律是常常放在一起說的。

          我們可以看海報去理解,第一張海報那幾條魚擺放的位置是沒什么規律的,但是它卻能形成節奏感。那么在第二張海報,我們能清晰地感受到它的節奏與韻律的變化。所以節奏是有規律或者無規律的變化的,韻律是有規律的變化的。

          包括去百度百科查找相關信息也是表達類似這樣的意思。

          一個版面里的節奏影響是多方向面的,有文字編排、色彩搭配、圖像處理等。

          文字編排

          今天我們要講的是文字編排里的節奏感。在文字編排上我們為什么要掌握節奏感呢?

          我們在看一些純文字的書籍的時候,很容易犯困。

          這就是因為書籍的文字編排幾乎沒有節奏感,所以相對來說是枯燥的。

          有一些不同的書籍設計或者雜志會在里面加入圖片、對文字與版式進行處理,使它們變得有變化性,就會調節這種節奏感。讓畫面閱讀不再枯燥。當然了,畫面左邊是因為大量的信息的傳達不適合做過多的變化,否則閱讀過程會有阻礙。這也是根據不同種類的信息從而把握不同節奏感的結果。

          所以在這個畫面里標題是節奏感比較強的,在保證閱讀性的同時也做到了裝飾性。那么由于下方信息量比較多,這些文字又需要被快速傳達,所以這些文字的編排就會趨向于閱讀性。

          掌握文字編排的節奏感對畫面傳達的氣質的影響是非常大的。就像這兩張海報,它們的背景圖片氣質是很像的,都是天空的大留白,但是,完全不同的文字編排,就讓第一張海報表現出活潑可愛的感覺,第二張海報表現出平靜安靜的感覺。

          我們可以理解為,隨著幾乎沒有節奏感到節奏感比較弱再到節奏感強,它的畫面是可以呈現出由靜止到舒緩再到動感的。

          在文字編排中有非常多的對比,是一個非常大的系統,今天我們主要去梳理一下文字編排時的一些影響因素,以及在文字編排上一些需要注意的細節。(我們今天會講這些影響我們文字編排的節奏感,字體種類、大小、長短、位置、疏密、顏色、組合形狀和方向。)

          1. 字體種類

          文字種類就是我們對文字的類別進行分類,在字體種類里一般分成了襯線體與非襯線體,但是在中文里,我覺得主要由這五大類組成,分別是黑體,宋體,楷體,圓體與書法體。

          包括字體家族里的不同字重。

          不僅是這樣,對于同一個字體我們還分常規體、窄體、扁體。

          一段文字只選擇上面的所說的變化就已經可以有很多種了。我拿了一段數學公式裝?。這里我們算出有 105 種。所以文字編排時稍微不注意就會有太多的變化性。

          更何況我們經常在版面里加入其他國家語言的文字,這些都會影響文字的節奏感。

          所以我們要學會控制這種節奏感,一般來說我們日常比較常用的就是黑體配無襯線體,宋體配襯線體。并且在字重上我們都要注意協調。盡量使它們看一起是一樣的粗細度。

          不同的字體搭配起來是有一定難度的。這種節奏感就好像不同風格的音樂的結合,它們之間的銜接與融合會比單純的某一種風格的音樂制作起來更難。

          這種不同類型的文字的搭配,對于排版和運用的能力有一定要求,運用不好就會傳達不了畫面的氣質與信息。在電商里的反例是比較多的。在我看到的這張圖里,它同時用了無襯線體與襯線體的結合,畫面沒有清晰呈現出準確的氣質。大家可能會說它是簡約高貴風,但是這是一個大范圍,在簡約高貴里有現代的高貴,有復古的高貴,還有一些與眾不同的高貴等。

          我們嘗試把左邊的襯線體換成無襯線看看。是不是有種現代都市的氣質?

          如果都統一成襯線的話,是不是一種精致的時尚感就出來了呢。統一這種文字的種類能更加精準地傳達畫面的氣質。

          剛剛我們也說到字體的混搭會產生混亂和怪異感,要慎用,但是如果我們的畫畫就需要這種感覺呢,當然就可以用這種特性來故意營造一種怪異感,當畫面的字體種類越多的時候,所營造出來的節奏感會更強烈。

          我們看這個畫面也是這樣的,周圍的很多圖形和各種各種樣的字體類型讓這個畫面變得搞怪活潑。

          就算去掉了周圍的圖形,文字這傳達出來的搞怪感覺也依然存在。

          接下來我們用這個文案來做一個案例演示下,由于平時很多都是用同一種類型字體搭配,那么這一次就挑戰一下,我就打算做一稿用不同類型字體的版面。看看會有什么樣的效果。

          首先我在版面中劃分版塊,填上相應的文字,在這里可以看到,我同時用了襯線與無襯線的字體,還用了具有手寫性質的字體。不僅如此, 所有的文字我都用了窄體而不是常見的常規體來增強這種怪異的節奏感。

          最后加上一些圖形處理一下負空間,這個案例就完成了。

          為了減少影響,我把圖片遮住單看文字組,我把這些字體都變成黑體了,對比可以發現,還是原來的文字組更有搞怪奇異的節奏感。右邊這個因為板式與圖形的完整性使得它看起來并沒有很大的問題,但是它的確是缺少了像左邊這種古典與現代結合的節奏感。

          2. 大小

          文字的大小節奏可能會有人理解為這樣,但是我們一般不會這么做。

          我們更多地是用在標題與內文的對比。

          節奏變化比較大的文字組會給人一種沖擊力,讓人無法忽視標題的存在,就好像我在大聲告訴你一句話的感覺,會比較強硬與喧鬧。

          節奏比較平緩的文字組表現出一種精致、安靜的感覺。

          我們把它們放到畫面里看下,這個標題與內文的對比很大,并且在這個版面中占據一定的大小,這種時候有沒有覺得這個畫面呈現出一種,好像在播放新聞的感覺,好像在說這個小島有什么重大新聞一樣。

          但是當標題變小時,整個畫面呈現出比較平緩的節奏感,很符合畫面傳達出來的安靜舒緩的感覺。

          這種方式很多作品都有,這種標題和內文的對比,或者說是文字在版面的占比比較大時,就可以體現出這種很強烈的節奏,讓人很難忽視這些文字內容。同時畫面也更容易傳達出一種力量感。

          往往想要營造這種安靜感的時候,比較注重畫面整體的感覺,就不需要太多的文字變化,甚至為了區別文字層級而需要有的文字大小,也盡量地在減少對比。

          3. 長短

          我們都知道音頻都是這種長短不一的聲波圖,因為這種長短不一的變化感受會給人帶來節奏感。

          所以也有以這種形式來編排文字,表現節奏感的系列廣告作品。

          它比較經常出現在居中對齊的文字組上。

          但是我們要注意的是,你會發現很少情況在兩個極端之間直接過度,比如說一長一短。

          在沒有文字大小的對比情況下,如果文字的長短對比太小,我們先不說節奏感,我們可能會有種疑問,它到底是想兩端對齊還是居中對齊的呢?所以這種兩端模棱兩可的情況最好避免。不能模棱兩可,對比太大又會不夠美觀,所以我們去創造節奏感的時候要注意這些問題。

          畢竟它不是讓人閱讀時間很長的文字,所以我們就需要調節這種節奏讓它看起來美觀而且不枯燥。

          這是我隨便在購物網站截圖的,你會發現它們的文字編排都很注意我剛剛說的短長短的節奏感。

          4. 位置

          我找了兩張都是黑色背景并且配圖比例也差不多的海報作對比說明這個問題。

          因為圖像的干擾我就同時去掉了圖片,還有也把右邊紅色的字變成了白色,它們之間的對比變得清晰了,左邊的文字規劃在版面中顯得更有活力,右邊版面的文字集中在一塊,就像我們前面提到的小說書籍內頁一樣。這樣的文字編排在閱讀上會比左邊的缺少節奏感。但是我在這里要說的一點是,左邊海報本身的圖像沒有右邊的有沖擊力與活力,所以我們如果要真正做對比的話。

          用左邊的圖像放置在兩個不同的版面來作對比,這樣大家應該能感覺到差別。左邊的版面雖然沒有表現特別強的節奏感覺,但是至少版面不是特別壓抑的。至于右邊的我們會覺得很沉靜,配合這種黑色的背景比左邊更壓抑。這就是文字編排在版面的影響。

          不僅是文字組之間,標題的有意放置不同的位置就可以營造這種節奏感,是因為它依然可以使得我們的閱讀視線發生變化。

          比較隨意編排的文字組也會比,比較拘謹正式的文字組看起來更有節奏、更活潑。

          接下來我們用案例來演示。

          首先選擇纖細的宋體會比較符合這個畫面的氣質,很多人可能把文字組放在畫面四個角就算大功告成了,但是這個畫面既沒有一個亮點吸引我們,而且畫面里的元素都非常得散,沒有體現出一點活潑的味道。

          這個時候我們可以效仿剛剛我們看到的標題的做法,拉開距離,調整位置讓它有上下浮動的節奏感,包括文字上我都做了一些切割移動讓它們活潑,再加上線條讓它們更有聯系感。這個畫面就會比剛剛活潑多了。

          案例完成。

          5. 疏密

          左邊緊湊的文字字距會呈現出一種張力,一種急促的節奏,營造一種緊張感。右邊寬松的字距畫面會更緩和,不同的字距在版面中有寬松對比,也營造出了一種節奏對比。不會感到枯燥。

          很多人可能沒太去注意這些文字編排的小細節,這兩版里哪一個更符合平緩的節奏感呢?答案是下面這個。第一張這樣做也沒錯的,但是我們想讓文字也能相應地呈現出一種透氣感的話,第二張的會更加符合。

          6. 顏色

          顏色的問題大家應該了解了很多了,這里就簡單提一下,就像我們在營造一種氛圍的時候選擇的氣球顏色都是非常重要的,我們選擇飽和度比較高,顏色種類比較多的氣球的時候,是想營造一種熱鬧活潑的感覺的。但是如果想營造一種浪漫安靜的氛圍時就用了很多白色或者淡色調的顏色。

          就像這個 banner 一樣,中間不同顏色的文字為這個畫面增加了很強的節奏感。變得很活潑。

          很多時候我們不需要太強的節奏感,所以我們經常給文字做一點的顏色變化,來讓畫面更鮮活。就像這個 banner,如果它沒有了左邊那個粉色的顏色的跳躍。

          這個畫面就會變得很沉靜。

          如果,畫面文字的粉色變多,它的節奏感又會變得更強。

          不僅是因為這個顏色本身的跳躍性比較高,而且也是因為顏色的不停切換導致這種節奏感的增強。所以不同顏色的占比也是需要考慮的。在這里主要是因為模特身上沒有玫紅色,所以左邊不適合用過多的玫紅色,用全黑都會顯得很沉悶,所以這里選擇用一次玫紅色讓這個畫面鮮活。

          6. 組合形狀

          我們在這些變化的圖形中替換兩個不同形狀的圖形,我們可以感覺到替換后的圖形里的變化會更多,呈現出來的活潑性更強了。當畫面中的不同形狀更多,就會趨向于一種混亂。

          用不同的圖形在版面里,有區分不同信息的作用,但是這種方式也是增加版面節奏感的一種方式。在很多促銷的傳單會經??吹健?

          這個畫面沒有人物圖片,僅僅是通過文字的編排就能傳達出這種熱鬧的節奏感。

          除了顏色本身的熱鬧性,其中比較大的影響因素。就是文字在各種不同形狀里的編排,讓這個畫面呈現出熱鬧的氛圍。

          7. 方向

          隨著方向變化越來越多,畫面會趨向于更有動感的的節奏。但是節奏感覺也是有度的,一旦變化性很多,那么這個版面就會顯得雜亂。我們也可以簡單理解為亂的元素的占比決定著你畫面呈現出來的節奏感的強烈。

          像第一張海報一樣,這種有點古老的作品我們一般會認為是比較正式和比較嚴肅的,但是這些方向不一的文字編排,向我們說明了這不是一個嚴肅的展覽,而是具有活潑的屬性,從而吸引不同的觀看群體。包括右邊這個海報的文字編排都讓這個畫面的節奏感變得更強了,與人物夸張的肢體語言也相呼應。

          傾斜的文字編排在電商里是出現得比較多的,微微傾斜文字就可以強調這種活潑的節奏感。

          我們來看一個比較明顯的例子,畫面只有一個黃色的色塊和文字編排以及一個不規則的圖形,但是這個畫面呈現出來的感覺卻很活潑。

          其實去掉這圖形,也并沒有影響原有的氣質,是因為文字的編排的方向性的對比讓它的節奏感增強了。

          7. 案例演示

          那么說到這里其實今天的內容也就結束了,這次的案例就給大家演示下怎么用這些知識去做一張海報。

          首先我們要分析這個畫面,圖片本身是比較具有節奏感的,因為不是我們平??吹囊m子的視角,而且人物有一種運動過程的動勢。

          這個負空間非常不規則,如果文字在這個畫面負空間上直接加字的話,可能就會顯得比較亂,但是我們又要做出迎合這個圖片的節奏但是不亂的畫面,應該怎么做呢?

          首先不采取直接在圖上加字的方式,把圖片縮小,再添加一個深色的底色,這樣這張圖片和這個畫面里就是一個整體了。

          由于我們要壓住圖片的節奏感,不讓它太亂,所以我在周圍的空間編排文字是呈現一個既有文字層級關系,但是整體是呈現矩形的文字組?,F在這個畫面看起來的確不會亂,但是文字還是少了和圖片活潑氣質契合的節奏感。

          分析一下這個畫面,是宇宙系列的感覺,所以我就加入了環形的文字,然后再加一點與圖片呼應的顏色。這個畫面就會比之前更和諧。在做的過程通過減少變化與增加變化讓畫面逐漸接近自己的預期。

          總結一下今天我們講的,我們今天講的是文字編排中的節奏感,在一開始,我分別給大家講了從生活節奏、音樂節奏、再到設計的節奏去理解什么是節奏感,然后我還講了關于文字節奏感對于畫面的重要性,無論是對信息的傳達還是對氣質的表現,它的影響是非常大的。最主要的部分,我講了影響文字編排的節奏都有哪些因素,比如文字類型、大小、方向、長短、文字組合形態等。把握自己要傳達的節奏,才能正確傳達信息。大家也可以用這種方式去看作品,分析它的節奏感是通過什么方式形成的。久而久之,對節奏感的掌握就會更加熟練與精準。

          文章來源:優設

          「卡片式設計」知識點

          資深UI設計者

          卡片式設計對于我們來說并不陌生,從設計類網站上或市場上的一些 APP 中也會看到很多的卡片式設計的案例,卡片式設計也是 UI 設計中最常用的方式之一。

          最近在新項目的設計中也嘗試使用了卡片式設計,結合實際項目中的一些思考進行總結并歸納出一些卡片式設計的小知識點。同時希望通過本次的總結進行知識沉淀,以及跟大家一起探討下卡片式設計。

          來源于日常

          在現實生活中的卡片式設計可以說是無處不在,例如身份證、交通卡、銀行卡、名片、便利貼、撲克牌、游戲卡……諸如此類的生活常見品都是以卡片的方式存在,其共同點都使用一個容器承載著內容,并且具有「便攜性、信息簡潔和相對獨立性」。

          UI設計中卡片的使用場景

          在項目設計之初我分析了一些使用卡片設計的 App,并且從中整理總結了幾個較為常見的卡片式設計的使用場景。

          1. Feed流

          卡片式的 feed 流設計是一種非常常見的設計,早在前幾年 Facebook、Google+ 等產品就使用了這一方式,Feed 流作為一種長內容的媒介,用戶需要長時間的滑動看內容并篩選有效信息,卡片式設計很好的解決了內容與內容的區塊分隔,讓用戶在長屏幕滑動中依舊可以很好的明確識別每一塊的內容。

          實際案例-淘寶微淘

          2. 瀑布流設計

          瀑布流的出現讓單屏區域內顯示更多的內容,而內容較多的情況下,使用卡片式設計可以較好的對內容進行了區域劃分,讓上下左右的內容從整體中具有相對獨立性。

          實際案例-Pinterest

          3. 左右滑動組合型內容

          卡片式設計具有較強的層次感,相比于平鋪更能呈現內容可滑動的感受,并且塊狀化的設計讓內容具有較高的區域分割感。

          實際案例-QQ音樂

          4. Tips提醒

          作為非界面固定內容,卡片式設計可以讓 tips 提醒設計變得更自由,在符合用戶體驗的基礎上,它可以出現在任何我們想要它出現的位置。

          實際案例-淘票票會員提醒

          5. 結合手勢的單塊可互動內容

          若頁面中有且只有一個主內容,并且需要用戶進行快速篩選時,可考慮這種結合卡片式設計與手勢設計的方式。大大增強了用戶對于設計的體驗感知和豐富視覺表現。

          實際案例-探探首頁

          6. 卡券類設計

          卡券類的設計實際上是一種物化映射的過程,我們在現實中看到的卡券造型,結合卡片式的擬物化設計,讓用戶在屏幕上可以更直觀的感知,提升了設計的代入感。

          實際案例-京東領券中心

          7. 集合型功能入口

          集合型功能入口往往會有多個入口,使用卡片式設計讓入口形成一個區域整體,可以做到既統一又相對獨立。

          實際案例-淘寶微淘關注賬號

          8. 個人主頁頂部內容卡片

          個人主頁的設計往往會在氛圍上營造沉浸感,卡片式的設計可以把關鍵信息進行概括收歸,讓原本單個的內容形成一個整體。

          實際案例-美團外賣會員

          規則探討

          基礎的卡片設計規則,相信大家在一些系統級別的設計指導規范中也或多或少都能了解到,不同平臺的規范差異其實不會有太多本質性的區別,更多的是處理技巧或方式的差異,而每個設計師對其理解的角度也會具有一些差異化,這里分享下我對于卡片式設計的一些基礎想法。

          1.卡片的質感打磨

          同樣的卡片設計,不同的人做出來的感受可能會有所差別,而表達卡片質感的主要關鍵點在于:卡片形體、投影深度、卡片顏色對比,我們需要了解這些基礎知識點之后,再結合實際的 APP 風格進行設計。

          卡片形體

          就像圖標的圖形設計一樣,不一樣的形體也能表達出不一樣的氣質,因此在設計的時候我們需要依據整體的風格進行表達。異形卡片的設計,可以讓原有方方正正的卡片表達出差異化,從造型上打破一些傳統的處理方式,再結合一些 IP 人物元素可以更加直觀的表達出具體的內容氛圍。

          投影深度

          投影的視覺效果,會直接影響整體卡片的質感,太深太大的投影會顯得整體卡片過于厚重,太淺太小的投影則顯得過度生硬,因此合理的數值比例可以讓卡片看起來自然有質感。在項目中我常用的一組數值規律是 1:2 或 1:3,例如 Y 軸偏移 10px,模糊度則設定為 20 或 30px,這樣成比例的數值出來的效果會較為自然,如下圖:

          卡片顏色對比

          卡片與背景的顏色對比會影響這卡片的整體質感,在設計時我們需要把握好卡片與底色的對比,不同的明暗對比出來的質感也會有差異。這里有兩點建議:

          • 卡片色與背景色不宜太過接近或使用同一顏色,因為會影響卡片整體的空間質感或使得卡片的邊緣銳度下降;
          • 深色背景上,盡量讓卡片與背景色在同一色系或者明度不要差異太大,避免過于突兀。

          2. 邊距的設定

          在使用卡片式設計時,經常會糾結邊距的設定,寬邊還是窄邊?多少像素更為合適?我經常會帶著這種疑問去設計。

          基于內容的簡單規則

          卡片式設計作為設計的表現形式,最終是為了承載內容,因此邊距的寬窄也需要依賴于實際內容的判斷。結合我在項目中的嘗試分享以下幾點:

          多窄少寬

          卡片內容較多是使用窄邊距,讓卡片具有足夠的空間來展現內容,內容較少則可以考慮采用寬邊距來打造整體的視覺空間感,如下圖 app store 和淘寶的設計對比。當然這只是一個建議,實際還得具體問題具體分析。

          再如一些瀑布流、宮格、橫滑模塊較多的 APP 設計亦是如此,在內容較多的情況下會把邊距壓縮到最小的合理間距。

          內外成比例

          以最外邊為基礎值往里設計,間距以固定比例進行縮減,雖然沒有刪格來得規范,但也可以讓設計變得有跡可循。

          基于刪格

          刪格系統解決了一些基礎的板式問題,有助于提升設計的規范性,讓設計更加有跡可循。在設定卡片式的邊距時可以適當應用刪格系統,讓邊距與內容形成固定的關系,這樣可以幫助整體的卡片設計更加具有細節和規則。

          4.卡片的標題設定

          標題的設定主要考慮以下幾點:1.是在卡片內還是卡片外;2.標題的字號設定多少更合適;3.標題是否加粗?

          卡片內或外的對比

          在項目設計中讓我較為糾結的是:標題應該在卡片內還是卡片外?通過了一些案例的嘗試之后,我總結了一個規則(需要依據內容的形態而進行設計):當卡片內容是獨立的模塊或模塊中只有一個大標題時可設定在卡片內;當卡片內容是以組合呈現或者具有延續性內容時設定在卡片外,形成最外層的主標題。

          標題的字號設定

          標題主要作用為 2 點:1.簡短說明每個模塊的內容;2.讓用戶在長頁面瀏覽中起到引導、定位的作用。

          通過一些嘗試發現:1.當內容較少時,并不需要太大的字號即可起到標題的作用;2.當內容較多時,較小的標題字號則容易被沉入內容中,讓用戶在瀏覽的過程中難以發現,而導致信息獲取缺失;3.建議標題與正文字號大小差異在 6-10px,這樣可以更好的拉開差異,讓標題更具有標題感。

          字體是否加粗

          常規思維下我們都會對標題進行加粗,我在實際中的經驗得到的總結是:需要看手機系統或不同廠商的機型。我在項目之初都對標題進行了加粗,但后續在跟進還原時看到的效果并不理想,特別是 Android 的機型上,因為我們使用的是系統默認字體,android 系統很多字體并未對系統進行優化,而是使用微軟雅黑,微軟雅黑在android 系統上再加粗,就會顯得整個系統的外輪廓特別粗糙,最后我們依據不同的機型進行了差異化的選擇。

          4. 圓角的規則

          圓角的設定實際上沒有太多的原則問題,只要符合整體的風格調性即可。當然不同的圓角也能表達出不同的質感,大圓角表達柔和、小圓角表達硬朗。

          圓角的規則設定

          以卡片的圓角作為基礎的參考值往內推算整體的圓角使用規范,卡片與卡內的元素形成合理的比例規則,而非隨意根據「經驗」進行設定。

          圓角大小差異對比

          大小的差異化呈現出不同的視覺感受和風格差異,我們在設計時更多需要考慮我們設計的產品風格或氣質是適合大圓角還是小圓角,而非依據一些設計網站上的流行趨勢。因此基于不同的風格或者實際內容場景下進行設定才更為合理。

          5. 寬窄間距對比

          卡片式設計相比拉通式設計更需要考慮設計中的透氣感。在常規情況下,對內容邊距及四周邊距進行調整,讓內容之間具有較好的空間呼吸感,從而讓設計得到留白的效果。如下圖對比案例,在基礎刪格不變的情況下,每個間距都在原有基礎上擴大了12px(接近 1.33 倍),從而讓內容具有較為舒適的寬度進行閱讀

          優點分析

          選擇某一種設計方式的重點在于我們了解這種方式的優點,并且可以把這些優點融合到我們的設計當中。在項目設計中,我總結了幾點卡片式設計的優點。

          1. 優化模塊化,提升內容區域感

          模塊化的設計也是我們日常中會應用到的方法,結合卡片式的設計可以讓模塊化的規則變得更加簡單,增加了模塊之間的可復用性和延展性。而當內容較多的情況下,使用卡片式設計可以有效直接的形成區域分隔,從視覺感知上就對內容進行了分隔,提升用戶獲取信息的效率。

          2. 提升內容獨立性

          在組合型的內容設計上,使用卡片式設計可以讓每個小塊內容呈現相對獨立的展現特性,結合模塊化的設計,可以在一大片關聯的內容中,做到既統一又相對獨立。

          3. 增強視覺空間感

          卡片式的設計可以提升整體設計層次感,通過投影、前后顏色的設定,讓內容與背景之間產生視覺空間感。

          4. 增強視覺表現力

          在設計中我們可以對卡片進行異形設計,用來達到我們想要的風格表現。當然在一個頁面內盡量不要太多,盡量使用頁面中的首個卡片進行差異化處理,讓整體表現出一點不同即可。

          5. 增強可點性

          卡片式設計產生的空間感,讓每個模塊更加突出,相比扁平式的處理方式,卡片式從視覺感官上會較為突出,從感官上更具可點擊感知。

          缺點及建議

          任何一種設計方式都會有其利弊,最終選擇某一種其實不過就是當下最適合而已,而在嘗試中我也總結了幾點卡片式設計存在的一些缺點,當然只是個人的思考而已。

          1. 橫向空間利用率降低

          卡片式設計的存在左右邊距,因此在有限的屏寬內內容橫向區域相比于拉通式設計有所減少,在內容較多的情況下可以適當調小卡片左右邊距。

          2. 避免過多的層級

          從整體來說,卡片式的設計本身就是增加了基礎背景的層級表現,其視覺層級相比拉通式更為豐富,因此不建議在卡片上再二次疊加塊狀式設計,避免造成層級復雜。在項目中也會遇到內容層級需要多層級的表現,從中總結了2種方式:

          • 利用不拉通分割線;
          • 利用淺色背景底色。

          3. 不適合長文或內容多的表達

          若在設計上使用了卡片式的設計風格,但在一些長文表現的界面建議去除卡片。長文章的頁面更強調閱讀的沉浸感,用戶需要更多的專注于文字,這時候無邊的體驗更適合。

          4. 把握好界面的分區,避免過于擁擠的排版

          卡片設計具有獨特的視覺空間感,但卡片與卡片之間也會有分隔,因此在設計時更應該對內容進行歸納,避免產生過多的小塊卡片而導致排版過于擁擠、凌亂或者內容不夠寬度展現。

          總結

          無論是卡片式或者拉通平鋪的方式,其最終的目的都是為了服務于內容,我們在做設計的過程中只是選擇適合于呈現我們內容的一種方式。根據具體的內容情況給出合理/合適的設計判斷才是我們需要不斷提升的關鍵點,切莫流于形式而忽略了內容設計本身的重要性。

          文章來源:優設

          B端產品的設計理念

          ui設計分享達人

          這篇文章主要是從什么是B端產品,B端產品與C端產品的差異性,以及如何從設計角度切入B端產品等做具體說明

          前言:在當下的市場環境中,企業內部的運營管理效率問題因為團隊規模迅速擴張而逐漸凸顯。此時,B端產品的助力就顯得尤為重要。這篇文章主要闡述了B端的定義和方向,以及與C端產品的差異性,并且如何從設計角度切入B端產品等方向做具體說明。


          什么是B端產品?

          B端產品也叫2B(to Business)產品,使用對象一般為企業客戶或組織。B端產品幫助企業或組織通過協同辦公,解決某類管理問題,承擔著為企業或組織提升效率、降低成本、控制風險從而提高企業收入,減少企業內部損耗的重要職責。B端產品的工作是合理實現企業需求,提高產品核心競爭力,并提升市場價值。


          B端產品有哪些方向?

          根據B端產品的服務對象,我們歸納為三個方向:

          1:業務平臺方向  

          2:辦公協同方向

          3:商家管理方向

          這三個方向基本上涵蓋了企業對內及對外的經營活動及業務運營的工作范圍。接下來我會一一詳細介紹。


          1:業務平臺方向

          業務平臺方向是指供業務平臺使用并且對這些產品提供支持。其中再細分則包括垂直業務線、基礎服務產品線、交易平臺產品線。



          舉兩個常見的例子:


          CRM:客戶關系管理(Customer Relationship Management)。廣義上的CRM包括從客戶開發、管理、營銷、服務的客戶全生命周期管理;狹義的CRM是指給銷售人員使用的銷售過程管理軟件。是通過以客戶為中心的管理模式,提高企業的銷售力量來達到為企業賺錢為目的。


          通過CRM系統我們可以知道: 

          1:我們的客戶在哪里?(售前市場調查)

          2:哪個產品更暢銷

          3:針對客戶進行分析

          4:銷售結果預測等等


          ERP:企業資源計劃(Enterprise Resource Planning)是針對物資資源管理、人力資源管理、財務資源管理、信息資源管理集成一體化的企業管理軟件。例:ERP以一項計劃為出發點,該計劃可以是市場的一個大訂單,或者是企業的一個戰略目標,那實現該訂單需要企業的多項的資源的支持,則需要用到人力,生產資源,設備,財務,采購,客戶資源等。ERP是通過對這些資源的有效計劃利用,公司高層通過掌握、管理、控制等手段來實現預期目標。適合大企業或者成熟的企業應用。



          2:辦公協同方向

          支持企業內部辦公管理運轉的業務系統,屬于辦公協同產品。

          例:OA,即辦公自動化(Office Automation)。是比較常用的辦公軟件,基于工作流概念,使企業內部人員方便快捷地共享信息,協同工作。


          3:商家管理平臺

          平臺型互聯網公司為商家提供了交易的平臺。為了保證平臺的持續、良性運轉,公司需要對入駐的商家進行資質審核和服務管理,這就需要設計并開發企業內部使用的商戶管理系統;同時公司需要給商家提供一套強大的經營管理后臺,方便商家進行自主管理。從業務管理視角來看,商家管理方向大致分為圖下所示的兩大系統。

          小結:上述所列分類為大類區分,有的產品即可歸屬于交易,又可歸屬于基礎服務,所以不必嚴格按著分類走,還需根據公司具體情況做具體分析。



          B端產品的特點?

          1:B端產品大都有行業特性或場景特性,目標用戶一般是群體。

          B端產品用戶群體是某個業務團隊或組織,需要共同協作來完成工作,所以需要B端產品來幫助他們實現分工協作。


          2:B端產品業務邏輯復雜,子業務多樣化。

          B端產品背后的業務復雜度高,人員、分工、協作、流程、規則隨時可能調整,這就需要我們有非常強的抽象能力和邏輯思維,尋求看似散亂無章的業務共性,進行合理整理和設計。


          3:效能第一

          B端產品的目標是解決組織的某類業務問題,因此聚焦于流程,提升業務效能是最重要的。



          B端產品和C端產品的差異性

          1:需求來源不同

          C端產品的需求來源于用戶,使用C端產品的是獨立的個人。而B端產品需求已經存在,來源于公司內部或外部。


          2:產品設計不同

          C端產品經理通常關注產品的點擊率,轉化率,增長率等。而設計B端產品的本質是提升企業內部工作效率,所以更注重優化用戶操作流程,提能。


          3:收益量化不同

          C端產品關注的核心指標主要包括DAU、UV、PV、CVR等,任何功能的設計都可以明確考核指標,容易量化和評判項目收益。

          B端產品要支持、解決業務問題,但業務成效的影響因素非常多,很多時候并非取決于B端產品設計的好壞。


          4:核心功能點不同

          C端產品有核心功能點,B端產品的功能多且每個功能都具有必要性。



          面對B端產品如何分析和入手?

          1:了解業務流程和產品定位

          在做B端產品之前,我們需要對即將做的業務有一個充分的理解和認知,不同部門使用的產品不同,則相對應的設計方案也不相同,這就需要我們充分了解業務流程,針對性的進行梳理。

          例:如果我們要做報稅系統,那么我們要知道報稅的流程有哪些,這樣可以幫我們規避許多不必要的問題。


          2:功能流程歸類

          把雜亂的功能整理清楚,提高用戶效能。


          3:讓產品落地并不斷生長(價值體系搭建)

          這是整個產品中最核心的點。何謂價值體系?對于B端的產品而言,客戶最關心它能為實際的工作帶來哪些便利,所以對于一個B端產品,解決問題的價值就是最好的推廣。


          4:整合設計,迭代優化

          對于設計部門,我們需要考慮設計的功能點有沒有遺漏?交互框架搭建的過程中,隨著產品發展,是否考慮到了其更多功能的擴展性。



          提高B端產品的品質,需要考慮的方向

          1:功能引導

          功能引導是產品降低用戶學習成本最通用的手段之一。簡單說就是使產品學習起來簡單,易用,用最快最清晰的方式觸達產品核心,可劃分為內嵌式和互動探索式兩類。


          來源:語雀(內嵌式)


          來源:Teambition (互動探索式)


          設計要點:

          1: 文案精準,通俗易懂

          2: 與品牌風格保持一致

          3: 增加趣味性

          4: 挖掘合適的出現場景,在最終呈現上做到簡潔克制


          功能引導最重要的是要契合產品本身,在合適的時機,以恰當的方式,在不剝奪用戶探索權利的情況下,去引導用戶更好地使用這款產品。



          2:認知減負


          幫助用戶認知減負的常見手段有可讀性優化、復雜性簡化等。

          可讀性優化上,可以通過關鍵詞提煉,可視化圖表等方式,降低用戶閱讀大塊內容時的產生的心理壓力和抵觸感。


          例:圖一中列表1和列表2的對比,通過數據可視化的方式讓用戶更為有效的查看數據,從而對業務有更加直觀的了解。

          圖一:來源:某廣告平臺(可讀性優化)


          圖二 來源: Teambition(可視化圖表)



          復雜性簡化上,可以通過減少頁面上不必要的功能信息,減少干擾信息。


          例如teambition的登陸頁面,點擊“更多登錄方式”則可以看到相對不常用的元素。將不常用的元素收起來,減少頁面上低頻率使用的功能,減少視覺上的混亂。

          來源: Teambition 登錄界面(復雜性簡化)


          設計要點:

          1: 避免不必要的元素

          2: 利用普遍的設計模式

          3: 減少不必要的任務

          4: 最小化可選項

          5: 保證可讀性


          3:學習模式

          對于一些面向固定人群使用的產品(比如企業數字化平臺、智能工廠系統等),面對復雜的系統,有時簡單的新手教程并不能解決業務復雜性的本身帶來的操作門檻,此時向專業用戶提供幫助文檔或教學視頻等學習工具,就變得尤為重要。

          來源: 用友(教學視頻)


          結尾:以上就是對B端產品的初略思考,其實想說無論是B端還是C端,每個產品都有自己相應的價值,我們在設計的過程中需要根據具體的業務和場景進行具體分析。


          一分鐘制作精美HTML郵件格式的元旦祝福郵件

          seo達人

          歲月不居,時節如流。轉眼之間2019悄然而過,回首2019,我們豪情滿懷,漫天風雪風景好;展望2020,我們重任在肩,陽光普照寫佳績。舟至中流需奮進,風好正是揚帆時。



          在2019最后一天里,可能你還想做這么的一件事,給客戶發一份精美的郵件祝福,但卻無從下手,怎么辦?拉易網能幫到你,準備好素材后,只需一分鐘就能生成精美的HTML郵件格式。話不多說,直接上視頻。





          一分鐘制作精美HTML郵件格式的元旦祝福郵件



          走著走著…



          2019已接近尾聲



          一眨眼就是一天



          一回頭就是一年



          一轉身就是一輩子



          人生總有太多的來不及

          B端設計,我總結了這些交互設計要點

          資深UI設計者

          B 端工作看起來總是沒有 C 端工作那么有趣,面臨的限制比較多,面對客戶(金主爸爸),很多時候總是左右為難。在實際工作中,面對這些情況該怎么辦?筆者根據自己的 B 端工作經驗,總結了一些交互設計的要點。

          從事 B 端 SaaS 行業已經兩年有余,從最開始面對需求的茫然無措,到現在已經有了自己的一些基本方法論,這期間經歷了從有人帶到自己摸索的一個階段。

          每天看看文章、看看書,大家講的都是 C 端的產品,C 端的交互和體驗;每天看同行們分析的不是如何提高用戶活躍量,就是 APP 的設計風格。這在我一個 B 端交互看來,無比羨慕啊。

          C 端項目的設計師感覺每天和一線用戶打交道,忙得不亦樂乎,可以與用戶直接對接,可以添加有趣生動的文案。

          而對于一個做 SaaS 的 B 端來說,Boss 常說的話就是:

          你這個顏色太鮮艷了。

          我們是 B 端,你這種頁面布局不合適吧。

          這個文案太幼稚了,不適合我們產品的調性。

          中規中矩就好,不要太跳。

          整理了一堆的字段,畫了無數的線框和流程圖,卻拿不出 C 端設計師才有的豐富多彩的作品集,

          盡管如此,設計的原則是通用的,無論是尼爾森十大可用性原則,還是格式塔原理,在設計方案的落實上,都有著相同的方法論。

          無意在此討論 B 端和 C 端之間的差異,僅以此文章來總結下我自己的一些工作經驗,如有錯誤,敬請指出。

          從業務需求的對接,到界面交互的細節,從功能的設計要點,再到產品的發展導向,我總結出了以下幾個方面,逐步展開:

          • 提煉需求
          • 減少出錯
          • 提率
          • 提高通用性
          • 中正原則

          提煉需求

          C 端設計師最開心的可能就是收到用戶的反饋需求了吧,這樣表示自己的產品還有人在用,然后建個群讓用戶開開心心吐槽,完了就從里面提煉適合產品的一些需求和建議。

          而對于 B 端設計師來說,如何處理需求是其成長的第一關,尤其是 SaaS 行業,不會處理需求,產品的功能更新將會遇到極大的問題。

          B 端的用戶不像 C 端,雖然可以用用戶畫像來進行歸類和分析。但是由于 B 端的直接用戶是付費用戶,付了錢的就是大爺,因此大爺提的需求你敢不應?

          用戶提的需求亂七八糟,有些是體驗問題,有些是功能問題,有些就是屬于比較極端的需求。那種傳說中甲方要你做一個可以根據手機屏顯示顏色而改變手機殼顏色的需求,在 B 端行業也是存在的。

          那么問題來了,我們該如何處理紛繁雜亂的需求呢,我從收集需求-評估需求-需求落地挨個講起:

          1. 收集需求

          如果你也打算像 C 端產品體驗群那樣建立一個群,完了將自己的用戶聚集在那里,靜靜等待需求的話,我勸你三思而后行。你可以這么做,但是應該明確群的目標,可以收集需求,可以是 bug 反饋群,也可以是更新通知群;但是不要將其變成一個純粹的用戶反饋群,因為這會導致用戶的吐槽聲音過大,而讓潛在的用戶流失。

          B 端的客戶一旦使用你們的系統,就要付出相應的金錢和時間代價,不是說想換一家就能換。因此他對于已經使用中的用戶反饋是極其看重的,B 端的產品很大程度是靠著同行間的口碑傳播從而取得了良好的效益。如果一個新用戶想進群了解,結果一進去就發現大家都在吐槽這個系統的不足之處,那么可想而知他的選擇是什么。

          因此,最好的需求收集方式是通過運營、市場以及客服的同事們的反饋,因為他們是離客戶最近的人,他們每天都跟客戶打交道,了解這個行業從業者的一些基本情況。很多時候,客戶回訪和運營對接的時候用戶會提出一些功能的建議。

          通常的一種情況是,在 SaaS 行業,你的一個客戶的流失意味著你的競爭對手多一個客戶。因此一般市場都會有競爭對手的信息,他們會知曉客戶從我們平臺流失到其他平臺的一些原因。最重要的是,他們也為你提供了絕佳的競品資料,進而可以進一步明確需求。

          收集好的需求,該怎么處理呢?

          工具之前我用的是 trello,很好用,你可以將需求按照類型分為不同的看板,規劃產品的進度。

          之后由于團隊的原因,改用 teambition,功能要豐富點,可以寫測試案例等,對于國內用戶比較友好;可以按照 KANO 模型將需求劃分為不同的類型,用以安排產品。

          KANO模型

          基本(必備)型需求——Must-beQuality/ Basic Quality

          一個產品應該具有的基本功能,能滿足用戶的基本需求,比如,微信的基本功能是即時通訊。

          用戶不會因為該功能的出現而覺得滿意,因為這是基本的、必備的一項功能。如果連這個都沒法滿足,用戶滿意度會大幅下降。

          期望(意愿)型需求——One-dimensional Quality/ Performance Quality

          用戶迫切希望產品能提供的功能,當提供該需求時,用戶滿意度會大幅上升,當不提供該需求時,用戶滿意度會下降。

          比如百度網盤一直為人詬病的下載限速,無法對單次下載而付費。而需要開通會員,用戶的抱怨恰好說明了其痛點;當提供單次下載付費的服務時,用戶滿意度明顯提升。

          興奮(魅力)型需求—Attractive Quality/ Excitement Quality

          用戶對該類型的需求并無明顯的期望,但是若產品能夠提供該類需求,用戶滿意度會極大提升,也會培養用戶的忠誠度。

          比如,很多產品都有實驗室功能,即對那些不是必備需求的功能設計一個開關,用戶打開后可以進行使用。對于有的用戶來講,這些功能很大程度上會帶來驚喜。當然,每個人期望不一樣,實驗室方案也可以視為另類的灰度測試,用以驗證用戶對于功能的需求。

          無差異型需求——Indifferent Quality/Neutral Quality

          產品無論是否滿足該類需求,用戶的滿意度是不會變化的,正如用戶不會因為收到「瑪莎拉蒂5元代金券」而感到開心。因此在 B 端行業,開發時間有限的情況下,無需為該類需求投入資源。

          比如優化一個用戶訪問量很少的網頁,也無需因為領導或者客戶的個人喜好而改變后臺頁面的顏色。無論使其鮮艷活潑還是穩重大氣,后臺滿足基本的視覺要求和規范即可。當然,這并不意味著你可以把后臺設計的簡陋和雜亂。

          反向(逆向)型需求——Reverse Quality

          當提供方向性需求的功能時,會招致大部分用戶滿意度的大幅下降。比如常見的在搜索中摻加廣告、強制用戶授權過多個人信息以及推送大量無用的消息等,會導致用戶的反感。

          2. 評估需求

          通常需求的收集不是你一個人在進行,產品經理、老板以及其他同事也會幫助你收集,匯總到你這里的需求會開會進行討論,下一步要做什么。

          做之前首先要對需求進行評估,評估的原則基本是按照 KANO 模型來,但是也會有比較特殊的情況。

          交互設計師需要注意的是,你除了需要關注用戶體驗的問題以外,還要與開發一起評估該需求的實現;你不懂技術沒關系,開發會告訴你該需求的落地會出現什么問題,在實現上會有什么難度。這些在評估需求的階段都要提出來,并且在交互層面解決掉,否則你畫出了原型以后,開發告訴你這個頁面必須要修改,否則開發成本過大,無法在排期內完成,這個時候你再去改交互稿將會費時費力。

          評估完需求,定好下期開發的需求后就結束了么?

          其實并沒有,因為需求收集不可能是一個固定的階段,它是漸進的、不確定的。因此收集需求和評估需求會不斷在實際工作中疊加和重復,比如你制定好了需求優先級,已經著手開發了;這時候老板或者領導突然又增加了一個優先級很高的需求,所以你得重新安排這些需求。如何在敏捷開發中保質保量的完成工作任務,這是一場斗智斗勇的 battle。

          3. 需求落地

          前面講到需求評估的時候,需要與開發人員一起評估技術難度。其實在你將需求落地為交互原型的時候,也需要持續溝通,這完全是為了避免因為技術問題而產生修改設計稿或溝通不順暢的問題。

          如果你是在做的過程中,持續與開發人員保持溝通,能了解到他們是如何實現這個功能的。當然,有些基本的設計原則所不允許的事完全不用屈服于他們的「淫威」,畢竟他們得按照你的方案來。如果開發懶惰而想通過最簡單的辦法來實現,用戶體驗又是極其不友好,那么請直接對其說「NO」。

          比如常見的刪除的二次確認等彈窗,一般最好的體驗是在按鈕旁邊彈出;但有些開發懶得寫彈窗,直接調用瀏覽器的彈窗,即瀏覽器頂部經常出現的那種確認彈窗,這個時候你要堅決告訴開發,這樣搞是堅決不行的。

          需求的落地伴隨著競品分析,判斷一個需求是否靠譜、落地方案是否成熟的一個重要途徑就是競品分析,可以通過調查研究相關競品是如何進行設計的。這對于我們有著非常大的幫助,可以避免很多的彎路,甚至能避免犯錯,提高交互方案的可靠性。競品分析又是個比較繁雜的事項,如果是有特殊要求可以做成報告的形式,如果僅是去參考對照的話只需要去體驗競品即可。

          減少出錯

          B 端的業務比較重要,尤其是涉及到數據的增刪改查和金額的計算,一旦出錯,將會導致無法挽回的后果。因此在出錯前和出錯后,應該有相應的挽回機制。

          1. 出錯前

          內容編輯類的功能應該提供自動保存草稿功能,防止沒有及時保存而丟失內容;刪除、禁止等較重操作應該有二次確認,防止用戶誤刪。

          對于操作流程應該建立明確的引導機制,長表單可以分開按步驟填寫。

          提示信息應該明確而及時。比如一個表單需要登錄才能填寫,在未登錄的狀態下,應該先提示其登錄再填寫否則用戶在填寫大量信息后,因為一個錯誤而前功盡棄。

          系統內的禁止信息、警告信息、提示信息建立一套相應的規則。

          2. 出錯后

          • 應該建立回收站機制,刪除后可以找回;
          • 可對用戶操作進行建立回撤機制,用戶如果操作失誤,可及時回撤;
          • 對系統的操作進行記錄,明確到時間、客戶端、操作者信息、操作內容、操作的類型(增刪改)等。

          提率

          用戶的時間就是金錢,這對于 B 端商家角色中尤為重要,大量訂單的處理、表格化的導入和導出、批量管理和網站運營等方面,對于效率有著極高的要求,商家通過可視化界面來完成某項任務。

          在這其中,影響最大的莫過于交互方式了,相信各位一定用過各大銀行的網站,頁面的導航關聯性弱、結構不合理、提示不明確、交互流程過長,甚至有的網站光是登錄,就得大費周章。

          如何提率,我認為主要從以下幾個方面著手:

          1. 提高易用性

          如果你的產品,讓人看一眼就能上手,那么說明是非常友好的設計。易用性不一定意味著簡單和低智,依據復雜守恒定理(泰勒斯定理),每個應用程序都有自己內在的、無法簡化的復雜度。

          所以,提高易用性意味著要設計合理的交互,易用性分為對新手(小白用戶)友好和對老用戶(專家用戶)友好,包括數量最大的中間用戶。

          如果你的界面是僅僅對于新手友好,那么可能完成的任務都是簡單而輕松的。但是對于老用戶,他需要更復雜的功能來處理大量龐雜的任務;因此在設計的時候,既要提供傻瓜式的操作方式,也要對專家用戶提供提率的工具。

          2. 智能化

          此處的智能化既包括通過大數據或者人工智能自動將操作步驟變得簡潔,也包括諸如一些字段輸入、自動定位、圖片識別、OCR 等方式來使用戶的效率得以提升的功能。

          以前的用戶要摳圖可能會在 ps 中操作好幾個步驟才能完成,但是隨著機器學習技術的發展,ps 已經可以更加智能的摳圖。當然,還包括其他功能的智能化。

          在 B 端 SaaS 領域,智能化也是一個重要的趨勢,針對不同的商家所面臨的不同的行業領域,我們或許可以提供更加全面且友好的服務。

          3. 場景化思維

          如果你深入了解你的用戶,去觀察他們整個行業的運作模式,以及他們對于業務的處理方式,你就會明白你的產品的走向。

          你需要深入每一個場景,比如,在戶外旅游領域,發布旅游產品線路的可能是在辦公室中的編輯人員,帶隊出行的是在戶外場景中的導游,現場簽到的可能是集合點的管理人員,而處理訂單的又是另一個坐在辦公室里的小伙伴。

          他們需要協同工作,才能使各項工作順利展開,發布活動-用戶報名-訂單管理-報名人統計-活動成行-集合點簽到-帶隊出發-旅游結束-活動評價-領隊評價-交易成功,這一系列流程中,面臨的角色是復雜的,而意外也是常有的事。比如報名人無法參加活動而導致的退款、活動因為天氣原因而無法成行、戶外活動發生意外等。

          你需要做的就是:

          • 站在辦公室編輯人員的角度上,你需要為他提供兼容性很高的編輯器和快捷方便的發布流程,比如在系統內接入微信公眾號的管理,可以將系統內的文章一鍵發布到微信公眾號上,也可以通過系統推送產品信息。你需要為其設計友好的相冊和視頻管理工具以宣傳旅游產品;
          • 站在導游和管理人員的角度上,你需要為其設計在戶外場景(移動場景)下也能使用的簽到工具、臨時退款工具、活動消息通知工具等;
          • 站在訂單管理員的角度上,你需要為其設計規范的導出表格格式,也需要為其設計修改報名人和訂單信息的功能,有必要時,你還需要為其設計單獨的報名渠道和特殊的付款方式(線下付款)。

          場景化的思維會讓你設身處地為一線操作用戶的體驗而努力。因此,交互稿完成以后,徹底回退到小白用戶的身份,假裝自己是在某個場景下要做某件事,通過你的交互方案,能否順利完成自己的目標。

          提高通用性

          此處的通用性是指,在 SaaS 軟件領域,不同客戶所面臨的行業有一定的差別,可能這個功能對于 A 用戶極其重要,但對于 B 用戶,該功能并不重要。比如有的客戶想要在前臺展示某活動的報名人的姓名以增加真實性,用以吸引用戶參加;但是有的客戶就明確反對該功能,認為這個功能侵犯了用戶的隱私。

          諸如此類的需求相離、甚至相反的情況太普遍了。合適的解決方式是建立兩套開關,一套是由 SaaS 服務提供商的統一后臺來配置,用以區分比較大的行業差別,比如電商領域中,可以配置店鋪類型為:美妝、服飾、食品、電子產品等;另一套開關在客戶的站點后臺,用以控制不同的站之間的差別,比如前臺界面樣式、交易流程、相關字段或菜單的前臺顯示等。

          另外比較重要的一點,也是最基本的一點,軟件設計中存在一個原則就是高內聚低耦合,模塊化設計,用以提高系統內部組件的復用。

          交互設計也是同樣的道理,可以將你的商品視為一個模塊,那么這個模塊無論是添加到網站,還是小程序,我只需要創建一個商品即可,可以同步更新到不同的平臺。

          另外,訂單的管理只需要添加相關的標記即可(比如來自小程序的訂單標記為小程序,淘寶訂單標記為淘寶等),一個平臺發布,多個平臺同步,有效提高了運營人員的效率。

          同樣的,如果你的 pc 端產品和移動端產品沒有實質性的運營差異(即當成兩種模式來運營),那么很多數據(如文案、圖片、banner等)的獲取可以采用同一個數據源 。

          最后,提高系統內的一致性,減少用戶認知成本。在同一平臺內的頁面,樣式和交互上要盡量保持一致性,不要有的地方是總金額,有的地方是總價格,這會讓用戶犯迷糊。提高通用性,也意味著你需要關注并熟悉系統內不同功能之間的關聯性,盡量做到統一處理。

          中正原則

          在進行商業化運作和產品功能的優化時,對于正向的用戶需要激勵,對于負向的用戶需要限制。

          這是我在讀完有贊的白鴉寫的關于有贊產品設計原則的文章后,影響最深的一個原則,他寫到:

          我們的使命是幫助每一位重視產品和服務的商家成功。「每一位」和「商家成功」是我們最重要的關鍵詞,我們要服務的是每一位商家,然后幫助每一位商家成功,但是為了整個生態的健康,那些不重視產品和服務的商家,我們是(可以)不服務的。所以我們在產品設計原則上,在產品做一些功能的選擇上,如果這個功能做完了會導致商家不重視產品和服務,我們是不會/能做的。

          舉個例子:消費者購買之后(可以)有一個評價,我們的購物評價是要么開啟要么不開啟這個功能。我們不接受商家去刪購物評價,因為商家一旦可以刪了消費者的差評,他就(很可能)不會那么重視產品和服務了。所以有贊永遠不會提供刪除商品評價的功能,商家要么就不開啟??梢圆挥?,如果要用就要接受有人說你不好,商家可以去跟消費者溝通,溝通完了消費者自己改,但是我們不提供讓商家刪壞評價的功能。所以,這就是最基本的有贊產品設計原則,我們只服務重視產品和服務的商家,我們所有的產品設計原則都是需要這樣。

          ——《白鴉內部培訓:企業服務類產品的底層邏輯和有贊產品設計原則》

          更多內容請看:

          我將其概括為一個產品的中正原則,即中立且保持正向原則。

          一方面,對于企業未來的發展而言,正向的用戶能帶給平臺無盡的潛力,平臺可以和商家一起成長,而負向的用戶,則會給平臺帶來隱患。比如,淘寶既限制商家的違規運營和欺詐客戶,也限制 C 端用戶的惡意薅羊毛,在商家和用戶之間做一個中立者和調節者的角色。

          我在做需求的時候,也遇到過很多的負向需求,這在商家看來是一個必須的功能,作為平臺應該提供。但是若是提供給商家,則會對消費者的利益造成損害,刪除差評就是一個很好的例子。

          看了白鴉對于有贊產品原則的闡釋,我覺得每一個平臺類的產品,都應該保持自己的中正原則:

          • 擁有數據的公司不要倒賣用戶的數據;
          • 搜索公司不應該干擾搜索結果的公正性;
          • 通訊公司應該尊重用戶的隱私;
          • 平臺公司應該在商家和消費者之間做一個良好的服務者和調節者的中間角色。

          在 B 端行業,口碑傳播是非常重要的一種被動營銷方式,很多 B 端公司都是低調的潛行者,堅持中正原則,并不會損害自己的利益,反而會獲得商家的尊重和消費者的信賴。

          總結

          啰啰嗦嗦說了這么多,比較細碎,不乏邏輯凌亂的片段,但也算是對自己兩年以來對于 B 端交互的一些心得吧。

          其實交互的原則基本都是通用的,無非就是根據平臺屬性做一定的調整,不同的是產品所處的行業,每一個產品都無法脫離其所處的行業而存在,這也是使用產品的具體場景。

          因此做一個產品前,一定要了解行業,去熟悉行業的通用做法,了解行業的專業術語和規范,研究行業的所屬群體等,這樣你就會做出真正適合市場且能讓大多數用戶滿意的產品。

          文章來源:優設

          為了提高閱讀體驗,總結了這份中文排印三原則

          資深UI設計者

          給大家看兩張圖,這兩頁的文字內容相同,你更想看哪本?

          不出意外的話,大家應該會選右手邊的吧(選左邊的請自覺去面壁)。(所拍書籍為《西文字體》,高岡昌生 )

          雖然大家不是專業做文字排印的,但對文字排版的感知力,其實是生而有之的。

          中文也是如此,優秀的出版社(譯林出版社、廣西師范大學出版社等),為了讓閱讀體驗順暢、版面舒適,在文字排印上做了許多工序。

          正是這些工序,提升了書籍正文的閱讀體驗。想要在閱讀軟件上打造優秀的閱讀頁,這些工序正是我們需要借鑒的。

          通過設計師與開發的共同努力,我們最近完成了這一文字排版能力的建造,讓閱讀頁的效果能夠更上一層樓。

          最終效果如下:

          本文將從以下幾個部分說明這些工序存在的理由、實現的邏輯等。

          細致的來看,文章包括以下內容:

          優秀的文字排印三原則與實現工序

          通過前期的大量學習與調研(專家觀點:小林章先生、鳥海修先生、劉慶先生等人關于字體排印 or 字體設計的講座、W3C 中文排版需求(強烈建議大家看這個)、孔雀計劃的文章、字體排印的專著:《平面設計中的網格系統》、《字體排印》、《西文字體》等;本次改版幾乎所有的功能與邏輯都參照了以上專著與文章。)

          文字排印要遵循的三個原則

          我們把文字書排版時的工序,總結為「文字排印要遵循的三個原則」:

          1. 盡量保證字間距恒定

          原則說明

          中文排版中,字與字之間的間距被稱為「字間距」。

          文字間距會影響閱讀節奏。字間距大的文章,閱讀速度會變慢。因此,散文、詩歌在排版時,會刻意調大字間距。

          下面的圖,僅憑自己感受,選一張更好的:

          不出意外的話,應該是覺得下圖更好看。

          嘗試默讀一下,你會發現,上邊的圖片,最后兩行字間距被拉大,閱讀速度放慢;而這不是作者的本意,換言之,這會破壞閱讀節奏。

          因此,我們把「保證字間距恒定」作為首要原則,來保障閱讀節奏感。

          備注(建議第二次看文章的時候再讀):需要說明的是,部分字面較大的字體(方正博雅宋、蘭亭黑等)在書籍排版時為了契合書籍內容的調性,有時會刻意設置字間距,這與「字間距保持恒定」的原則并不沖突。在電子閱讀軟件中,由于無法針對特定書籍進行調整,因此本次設計實際上是保持「密排」(字與字之間沒有額外添加字間距,保留字體原始的間距)。

          工序

          行長是字號的整數倍。相同字號下,漢字字寬固定(就是字號本身),漢字標點的字寬同樣也是字號本身(除了個別標點之外,例如破折號)

          漢字排版時,沒有額外字間距的情況下,是上圖所示的字面框依次密排。

          因為中文書籍的正文排版常用兩端對齊,如果行長不是字號的整數倍,則漢字之間會有異常的行間距出現。

          更嚴重的是:閱讀軟件字號可變的情況下,行長不可能做到適應所有字號且字間距不會被拉大。

          行長是字號的整數倍是中文字體排印中標點擠壓等的前提。
          ——《孔雀計劃》,原文鏈接:https://thetype.com/2017/07/12513/

          在閱讀軟件中,隨著字號調整,如果沿用「版心寬度固定」的思路,難免存在字間距被拉大的情況。
          對此,我們調查了國內外知名中文閱讀軟件,發現:KindleAPP 能隨著字號變化自由變動,但這會導致:改變字號大小時,版心寬度略微變化。

          有此顧慮,我們做了一個測試。結果證明,大家不會發現版心寬度有變化。這說明用戶投入到閱讀當中、調整字號時,并不會因為版心寬度變化而有不適,甚至不會感知。通過測試,打消了我們的顧慮。
          最終我們大膽采取了「版心寬度跟隨字號調整而變化」,來確?!感虚L是字號的整數倍」。

          雖然「版心寬度跟隨字號變化」并沒有不適,但我們需要保證在多種屏幕尺寸、字號下,版心占據屏幕的區域都舒適。

          面對這個問題,我們制定了一個公式,可根據屏幕大小、字體大小等,自動調整版心寬度。確保「行長是字號的整數倍」的同時,保證頁面美觀。

          標點符號「優先推入式避頭尾」。如圖所示,為宋抄本《孫子算經》;在古代,書籍排版可以做到字間距恒定,原因是古代不存在「標點」,也就沒有「標點避頭尾」導致的種種問題。

          而現代漢語存在標點符號,有的標點不能放在行首,有的不能放在行尾。

          我們把不能放在行首的標點叫做避頭標點,如逗號、頓號、句號等;把不能放置在行尾的叫做避尾標點,如前引號、前括號等。

          「推出式」避頭尾是大部分閱讀軟件的做法:

          以避頭標點為例,若此標點被排到了行首,「推出式」 的做法是從上一行拉一個字放在本行。如下圖所示:

          然而這么做的話,上一行的字間距被拉大,打斷了閱讀節奏(閱讀節奏放慢)。

          我們發現專業的排版軟件(Indesign)和出版社(廣西師范大學理想國系列、人民文學出版社、譯林出版社等知名出版社)的做法是「優先推入式避頭尾」,這種方式可以很好地解決「僅推出式」造成的問題。如下圖所示:

          △ 《少數派報告》譯林出版社

          通過「優先推入式避頭尾」,上圖中標出的雙引號的寬度被擠壓了一半,如果它保留為「全寬」,就沒辦法排在這一行,這就是「優先推入式避頭尾」的最終效果。

          以避頭尾標點為例,「優先推入式」避頭尾在這種情況下會將本行內標點寬度擠壓,為避頭尾的標點騰出空間,如下圖所示:

          上面的圖可以看到:通過「優先推入式標點擠壓」,第一行的字間距沒有被拉大,保持了密排。

          通過將本行內的標點寬度進行擠壓后,騰出了空間給本來排不到的逗號,確保了字間距的恒定。然后只有在本行內標點無法壓縮出足夠空間時,才會選擇「推出式」的處理方式。

          因此這種處理方式叫做「優先推出式」標點避頭尾。

          行內標點擠壓。因為相鄰標點擠壓、行首段首擠壓,會出現部分標點符號占據半寬的情況。這種時候,一行的末尾可能正好有漢字或標點軋在了邊框上,如下圖所示,為漢字軋在邊框的情況:

          遇到這種情況,通過擠壓行內標點寬度,從而騰出空間給最后一個字。這種做法叫做「行內標點擠壓」

          標點懸掛的邏輯和配套內容。存在另外一種處理方式來避頭,叫做「標點懸掛」,即將標點懸掛在文本框外。

          然而這種采用「標點懸掛」,需要配套做「行尾強制半寬」,如下圖所示:

          然而行尾強制半寬帶來的問題是字間距被拉大,違反了原則一(盡量保證字間距恒定),對于寬度有限的手機屏幕,盡量不要改動。因此最終我們沒有采用標點懸掛的處理方式。
          Type is Beautiful 網站中對此有詳盡的思考,如有興趣請看:https://thetype.com/kongque/

          2. 版心灰度均衡

          原則說明

          書籍排版中,文字所在的范圍稱為「版心」。

          經驗老道的文字排印設計師,檢查正文排版效果時,最常用的方法是:離遠看頁面,就像蒙上一層磨砂玻璃一樣,檢查整個頁面是否疏密均衡(《字體排印》,高岡昌生先生),也叫做「灰度均衡」(龜倉雄策先生稱之為「濃淡勻稱」,from《疾風迅雷》)?;叶染獾陌嫘目梢宰屨麄€頁面美觀的同時,也保證了讀者閱讀中不會被突然的空白打斷。

          如下圖所示,第一張圖因為一些原因導致了頁面中有許多「窟窿」,顯得零碎,灰度明顯不均衡。而第二張圖,通過「標點擠壓」將這些「窟窿」填上,整個版面更像是一個整體,灰度更均衡。

          看倒數第二行的「乃跪地罪曰:‘大人何故’」兩個標點連續的地方,從整體的角度看,會不會覺得這里有一個窟窿?

          總的來說,灰度均衡的版面整體感更強、視覺上更舒適,因此也是相當重要的原則。
          工序:

          相鄰標點擠壓

          眾所周知的,漢字是方塊字,在字體設計時會被放在一個方形里;中文標點同樣也會被放置于同等大小的方框之中,如下圖所示。

          如上圖所示,中文標點所占體積一般遠小于漢字,因此當多個標點符號連續排列時,會讓版面在這里好像有一個窟窿。專業的中文排版中會做的事情是「相鄰標點擠壓」。如下圖所示:

          如上圖所示,有連續標點存在時,通過壓縮標點所占的寬度,從而補上「窟窿」。

          3. 版面齊整

          原則說明

          與西文書籍的左側對齊不同,中文書籍(橫排)傳統而言是講究兩端對齊。這是被大多數國人認可的中文排版方式,因此不再贅述。

          工序

          行首段首標點擠壓。當行首出現標點符號,會感覺左側不齊:

          可以看到,處理前版心左側因為有單引號,看起來第一行沒有和第二行左對齊,處理后效果回歸正常。同樣的,段首的標點也需要擠壓。

          實際落地時的經驗與產出

          知道了以上內容,我們需要把它變成開發需要的邏輯。具體如下:

          1. 相鄰標點擠壓邏輯

          在 W3C 的《中文排版需求》中,對相鄰標點擠壓的具體做法為:

          • 依照中國大陸的常見的排版規則,當結束夾注符號出現在頓號、逗號、句號之后時,縮減兩者間二分之一個漢字大小的空白;而在港臺則不會做此調整。
          • 當頓號、逗號、句號出現在結束夾注符號之后時,縮減其間二分之一個漢字大小的空白。
          • 當開始夾注符號出現在頓號、逗號、句號之后時,縮減其間二分之一個漢字大小的空白。
          • 當開始夾注符號出現在結束夾注符號之后時,縮減其間二分之一個漢字大小的空白。
          • 當兩個(或以上)開始夾注符號連續排列時,縮減其間二分之一個漢字大小的空白。
          • 當兩個(或以上)結束夾注符號連續排列時,縮減其間二分之一個漢字大小的空白。
          • 當間隔號出現于結束夾注符號之后時,縮減其間四分之一個漢字大小的空白。
          • 當間隔號出現于開始夾注符號之前時,縮減其間四分之一個漢字大小的空白。

          通過梳理,我們將其簡化描述為 4個邏輯(實際邏輯與 W3C 基本一致):

          • 「1個結束夾注符」后面是:開始夾注符、結束夾注符、頓、逗、句(包括全寬句點)、冒、分,就擠。
          • 「1個開始夾注符」后面是:開始夾注符,就擠。
          • 「頓、逗、句(包括全寬句點)」后面是:開始夾注符、結束夾注符,就擠。
          • 「分、冒」后面是:開始夾注符,就擠。

          備注:成對出現的標點叫做夾注符,如雙引號、書名號等;其中細分為開始夾注符與結束夾注符。
          此外,我們注意到,一些出版書在以上邏輯之外,把問號與嘆號與[頓、逗、句]歸為一類,實際效果良好,因此出于問號與嘆號同樣占據字面不多的邏輯,采用了這種分類法。然而隨后在走查階段發現線上部分字體的問號與句號占據字面的位置不同。

          這促使我們關注到標點在字面中占據的位置,我們梳理了客戶端所有字體的中文標點。

          發現漢儀樂喵、方正蘭亭黑、漢儀啟體等幾款字體的問號字面占據異常,如果進行相鄰標點擠壓可能會造成標點粘連的情況,因此最終我們決定保持問號與嘆號不參與相鄰標點擠壓。

          2. 避頭、避尾標點匯總表:

          以上邏輯實施需要首先讓程序判定哪些標點是避頭尾標點,因此我們梳理了所有漢字標點并分為避頭、避尾兩類(部分標點即避頭又避尾)。

          漢字標點符號與西文的標點符號許多時候僅憑肉眼難以分辨(如,與,前者是西文標點中的逗號,后者是中文標點中的逗號),因此我們使用Unicode 碼為每一個標點精準劃分,確保不會造成錯誤。

          3. 行內標點擠壓與「優先推入式避頭尾」邏輯:

          「行內標點擠壓」與「優先推入式避頭尾」其實本質上都是對行內標點寬度進行壓縮,因此在邏輯上歸為一類。根據具體解決邏輯的不同將其分為以下四類:

          針對這四種情況要做的具體事項補充在右側:

          至于推出邏輯,則稍簡單些,書籍中有時候會遇到連續幾個標點符號都是避尾標點的情況(或連續避頭標點),因此對推出的邏輯設定為:

          備注:國內知名的字體設計與排印網站 Type is Beautiful 中有介紹,行內標點擠壓的方式有多種,「開明式」「全部半寬式」「平均式」等等…我們選擇了「平均式」(有權重),開發難度會稍小些、效果也更可控制,不再贅述。

          4. 整體邏輯流程圖

          但是僅僅通過文字性質的描述還不夠,我們需要能讓程序理解的邏輯。

          由于部分處理方式之間互相干擾,因此根據這四種方式影響的內容不同,以「對其他處理方式的干擾程度」從高到低排序,并串聯成整整體的邏輯圖,以保證整體邏輯簡單、不重復,如下圖所示。

          在此基礎上,我們將文章中第二部分所梳理的邏輯細節填充進流程圖內,并合并重復流程,最終獲得如下流程圖,方便開發理解和工作:

          5. 小結

          通過「避頭尾標點列表」「可擠壓標點與擠壓空間」「廣義的推入邏輯具體說明」「整體邏輯流程圖」四個文件,我們可以順暢的將設計要求傳達給開發。

          最終,通過這些工序,我們可以在最大限度上保證字間距恒定、版面齊整和內容灰度均衡。提升閱讀流暢性和閱讀頁的體驗。

          總結

          文字排印作為一個古老的技藝,從排版工人操作實體字模的時代,經歷了照排時代,來到了數字排版的當下,排版的自由度和效率已然成倍提高,然而由于許多原因至今這些排版的工序只在出版社等專業領域流通。

          相對于紙質書籍,電子閱讀在易攜帶性、閱讀方便性、多媒體輔助閱讀上有得天獨厚的優勢,然而最基礎的閱讀體驗有時候不如紙質書籍,我們希望通過我們的努力,電子閱讀在未來,能夠讓讀者獲得全面超過紙質書籍的閱讀體驗。

          當然,文字在移動端的體驗上限遠不止如此,一些產品僅靠網格系統與字體排印加上優秀的字體,已經做出了令人驚艷的體驗。

          如上圖所示,通過優秀的明朝體、網格系統,物書堂出品的幾個詞典 APP 的界面讓人驚艷,文字之美還有很多可能,這也是我們的努力方向。我們也知道,當前客戶端內中英混排、英文排版等方面,依然有進步空間,未來也會進一步完善。

          文章來源:優設

          微信小程序總結跳轉方式,解決跳轉失效問題

          seo達人

          微信小程序跳轉方式

          1.navigator 跳轉

          最常見的跳轉方法就是運用<navigator url="../../.."></navigator>進行跳轉,只要在url中添加跳轉頁面的路徑即可。



          代碼

          <navigator url="../skill/skill">

              ........//跳轉涵蓋內部所有代碼形成的頁面

          </navigator>

          1

          2

          3

          2.運用 bindtap在js中實現頁面的跳轉

          比起navigator的跳轉,個人更喜歡用bindtap跳轉,運用更靈活,也不用去除點擊樣式。

          bindtap='home'(home位置隨便替換)



          代碼

          //wxml

           <view  bindtap='home'>

           </view>



          //js

          home: function (e) {

               wx.navigateTo({

                 url: '../../..'        //跳轉鏈接

               })

             }



          **重點

          在開發的過程中,突然遇到以上兩種方式都無法實現跳轉,也不會報錯的情況,經過查詢資料,發現內部調用wx.switchTab可以很好的解決這一現象



          代碼

          home: function (e) {

                wx.switchTab({

                  url: '../../..'

                })

              }


          Vuex源碼分析

          seo達人

          一、前言

          我們都知道,vue組件中通信是用props進行父子通信,或者用provide和inject注入的方法,后者類似與redux的porvider,父組件使用,包含在里面的子組件都可以使用,provide/inject用法看我的博客(provide/inject用法),provide/indect官方文檔,不過provide/indect一般用的不多,都是用前者,但是props有一個問題,父傳子沒問題,但是子后面還有子,子后面還有子,子子孫孫無窮盡也,所以這就要一層層的傳下去,太麻煩了,所以vuex就派上用場了,vuex作為一個很輕量的狀態管理器,有著簡單易用的的API接口,在任意組件里面都能使用,獲取全局狀態,統一獲取改變,今天,就看一下源碼怎么實現的。



          二、準備

          1)先去github上將vuex源碼下載下來。

          2)打開項目,找到examples目錄,在終端,cd進入examples,執行npm i,安裝完成之后,執行node server.js



          3)執行上述之后,會得到下方的結果,表示編譯完成,打開瀏覽器 輸入 localhost:8080



          4) 得到頁面,點擊counter



          5)最終,我們就從這里出發調試吧。





          三、調試

          找到counter目錄下的store.js 寫一個debugger,瀏覽器打開F12,刷新頁面,調試開始。



          1.Vue.use()

          先進入Vue.use(Vuex),這是Vue使用插件時的用法,官方文檔也說了,Vue.use,會調用插件的install方法,所以如果你要寫插件的話,你就要暴露一個install方法,詳情請看vue官方文檔之use的用法



          即use會調用下方的方法(debugger會進入)



          function initUse (Vue) {

            Vue.use = function (plugin) {

              var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));

              if (installedPlugins.indexOf(plugin) > -1) {

                return this

              }



              // additional parameters

              var args = toArray(arguments, 1);

              args.unshift(this);

              if (typeof plugin.install === 'function') { 

              // 如果插件的install是一個function,調用install,將 this指向插件,并將Vue作為第一個參數傳入

              // 所以調用vuex吧this指向vuex,并吧vue當參數傳入

                plugin.install.apply(plugin, args);

              } else if (typeof plugin === 'function') {

                plugin.apply(null, args);

              }

              installedPlugins.push(plugin);

              return this

            };

          }





          1.1 vuex的install方法

          在源碼目錄的src目錄下的store.js文件里最下方有個install函數,會調用它



          debugger進入后



          export function install (_Vue) { // _Vue是上面說的Vue作為第一個參數 ,指針this指向Vuex

            if (Vue && _Vue === Vue) {

             // 如果你在開發環節,你使用了兩次Vue.use(Vuex) 就會報下方的錯誤,提醒你vue只能被use一次,可以自行試試

              if (process.env.NODE_ENV !== 'production') {

                console.error(

                  '[vuex] already installed. Vue.use(Vuex) should be called only once.'

                )

              }

              return

            }

            Vue = _Vue

            applyMixin(Vue) // 調用applyMixin方法

          }



          1.2 vuex的applyMixin方法

          applyMixin方法在mixin.js文件里 同樣在src目錄下



          export default function (Vue) {

            const version = Number(Vue.version.split('.')[0]) // 獲取vue的版本



            if (version >= 2) { // vue的版本是2.xx以上 執行vue的mixin混入,該函數不懂用法請查看官方文檔,

            // mixin合并混入操作,將vuexInit 方法混入到beforeCreate生命周期,意思就是當執行beforeCreate周期的時候

            // 會執行vuexInit 方法

              Vue.mixin({ beforeCreate: vuexInit })

            } else { // 1.xxx版本太老了 現在基本不用,暫不講解,可以自行了解

              // override init and inject vuex init procedure

              // for 1.x backwards compatibility.

              const _init = Vue.prototype._init

              Vue.prototype._init = function (options = {}) {

                options.init = options.init

                  ? [vuexInit].concat(options.init)

                  : vuexInit

                _init.call(this, options)

              }

            }



            /*

             
          Vuex init hook, injected into each instances init hooks list.

             /



            function vuexInit () { 

            // 因為該方法是在beforeCreate下執行,而beforeCreate的this指向為Vue 所以this === Vue

            // 獲得vue里面的option配置,這里涉及到vue的源碼,以后再講vue ,

            //所以這就是我們為什么要在new Vue的時候,傳遞一個store進去的原因,

            //因為只有傳進去,才能在options中獲取到store

            /


            var vm = new Vue({

          el: "#app",

          data() {return{}},

          store

          })

          */

              const options = this.$options

              // store injection

              if (options.store) { 

              // 如果options對象中store有,代表是root ,如果options.store是函數,執行調用options.store()

              // 如果是對象直接options.store 賦值給this.$stroe

            // 這也就是我們為什么能夠在Vue項目中直接this.$store.disptach('xxx')的原因,從這里獲取

                this.$store = typeof options.store === 'function'

                  ? options.store()

                  : options.store

              } else if (options.parent && options.parent.$store) { 

              // 如果options.store為空,則判斷options.parent.$store有沒有 從父元素判斷有沒有store,

              //從而保證子元素父元素公用一個store實例

                this.$store = options.parent.$store

              }

            }

          }





          1.3 Vue.use(Vuex)總結

          至此,Vue.use(Vuex)全部分析完成,總結,就是Vue.use調用Vuex的install的方法,然后install使用mixin混入beforecreate生命周期中混入一個函數,當執行生命周期beforecreate的時候回執行vuexInit 。你可以慢慢調試,所以要好好利用下方的調試按鈕,第二個按鈕執行下一步,第三個進入方法,兩個配合使用。





          2.new Vuex.Store

          Vue.use(Vuex)已經結束,再回到counter目錄下的store.js文件



          export default new Vuex.Store({

            state,

            getters,

            actions,

            mutations

          })





          debugger進入,Vuex.Store方法在src目錄下的store.js文件下



          export class Store {

            constructor (options = {}) {

            // 這里的options是在counter定義的 state,getters,actions,mutations當做參數傳進來

              // Auto install if it is not done yet and window has Vue.

              // To allow users to avoid auto-installation in some cases,

              // this code should be placed here. See #731

              if (!Vue && typeof window !== 'undefined' && window.Vue) {

              // 掛載在window上的自動安裝,也就是通過script標簽引入時不需要手動調用Vue.use(Vuex)

                install(window.Vue)

              }



              if (process.env.NODE_ENV !== 'production') { 

               // 開發環境 斷言,如果不符合條件 會警告,這里自行看

                assert(Vue, must call Vue.use(Vuex) before creating a store instance.)

                assert(typeof Promise !== 'undefined', vuex requires a Promise polyfill in this browser.)

                assert(this instanceof Store, store must be called with the new operator.)

              }



              const {

                plugins = [],

                strict = false

              } = options



              // store internal state

              //下方是在Vuex的this上掛載一些對象,這里暫且不要知道他們要來干什么

              // 因為是源碼分析,不要所有的代碼都清除,第一次源碼分析,你就只當他們是掛載對象,往下看

              this._committing = false

              this._actions = Object.create(null)

              this._actionSubscribers = []

              this._mutations = Object.create(null)

              this._wrappedGetters = Object.create(null)

              // ModuleCollection代表模塊收集,形成模塊樹

              this._modules = new ModuleCollection(options) //碰到第一個不是定義空對象,debugger進去,分析在下面

              this._modulesNamespaceMap = Object.create(null)

              this._subscribers = []

              this._watcherVM = new Vue()

              this._makeLocalGettersCache = Object.create(null)



              // bind commit and dispatch to self

              const store = this

              const { dispatch, commit } = this

              this.dispatch = function boundDispatch (type, payload) { // 綁定dispatch的指針為store

                return dispatch.call(store, type, payload)

              }

              this.commit = function boundCommit (type, payload, options) { // 綁定commit的指針為store

                return commit.call(store, type, payload, options)

              }



              // strict mode

              this.strict = strict



              const state = this._modules.root.state // 獲取到用戶定義的state



              // init root module.

              // this also recursively registers all sub-modules

              // and collects all module getters inside this._wrappedGetters

              // 初始化root模塊 注冊getters,actions,mutations 子模塊

              installModule(this, state, [], this._modules.root)



              // initialize the store vm, which is responsible for the reactivity

              // (also registers _wrappedGetters as computed properties)

              // 初始化vm 用來監聽state getter

              resetStoreVM(this, state)



              // apply plugins

              // 插件的注冊

              plugins.forEach(plugin => plugin(this))

          // 下方的是開發工具方面的 暫不提

              const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools

              if (useDevtools) {

                devtoolPlugin(this)

              }

            }

            }



          2.1 new ModuleCollection

          ModuleCollection函數在src目錄下的module目錄下的module-collection.js文件下



          export default class ModuleCollection {

            constructor (rawRootModule) { // rawRootModule === options

              // register root module (Vuex.Store options)

              this.register([], rawRootModule, false)

            }

          }



          2.1.1 register()



           register (path, rawModule, runtime = true) {

           // register([],options,false)

              if (process.env.NODE_ENV !== 'production') {

                assertRawModule(path, rawModule) // 開發環境斷言,暫忽略

              }



              const newModule = new Module(rawModule, runtime)

              if (path.length === 0) { // path長度為0,為根節點,將newModule 賦值為root

                this.root = newModule

              } else {

                const parent = this.get(path.slice(0, -1))

                parent.addChild(path[path.length - 1], newModule)

              }



              // register nested modules

              if (rawModule.modules) { // 如果存在子模塊,遞歸調用register,形成一棵樹

                forEachValue(rawModule.modules, (rawChildModule, key) => {

                  this.register(path.concat(key), rawChildModule, runtime)

                })

              }

            }

          1

          2

          3

          4

          5

          6

          7

          8

          9

          10

          11

          12

          13

          14

          15

          16

          17

          18

          19

          20

          21

          2.1.2 new Module()

          Module函數在同目錄下的modele.js文件下



          export default class Module {

          // new Module(options,false)

            constructor (rawModule, runtime) {

              this.runtime = runtime

              // Store some children item

              this._children = Object.create(null)

              // Store the origin module object which passed by programmer

              this._rawModule = rawModule // 將options放到Module上 用_raModele上

              const rawState = rawModule.state // 將你定義的state取出



              // Store the origin module's state

              // 如果你定義的state為函數,調用函數,為對象,則取對象 賦值為module上的state上

              this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}

            }

          }



          所以Module的this內容為如下:



          2.1.3 installModule



          function installModule (store, rootState, path, module, hot) {

          // installModule(Vuex,state,[],module) module是前面 new ModuleCollection產生的對象

            const isRoot = !path.length

            const namespace = store._modules.getNamespace(path)



            // register in namespace map

            if (module.namespaced) {

              if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {

                console.error([vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')})

              }

              store._modulesNamespaceMap[namespace] = module

            }



            // set state

            if (!isRoot && !hot) {

              const parentState = getNestedState(rootState, path.slice(0, -1))

              const moduleName = path[path.length - 1]

              store._withCommit(() => {

                if (process.env.NODE_ENV !== 'production') {

                  if (moduleName in parentState) {

                    console.warn(

                      [vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"

                    )

                  }

                }

                Vue.set(parentState, moduleName, module.state)

              })

            }

          // 設置當前上下文

            const local = module.context = makeLocalContext(store, namespace, path)

          // 注冊mutation

            module.forEachMutation((mutation, key) => {

              const namespacedType = namespace + key

              registerMutation(store, namespacedType, mutation, local)

            })

          // 注冊action

            module.forEachAction((action, key) => {

              const type = action.root ? key : namespace + key

              const handler = action.handler || action

              registerAction(store, type, handler, local)

            })

          // 注冊getter

            module.forEachGetter((getter, key) => {

              const namespacedType = namespace + key

              registerGetter(store, namespacedType, getter, local)

            })

          // 逐一注冊子module

            module.forEachChild((child, key) => {

              installModule(store, rootState, path.concat(key), child, hot)

            })

          }



          2.1.4 makeLocalContext



          // 設置module的上下文,綁定對應的dispatch、commit、getters、state

          function makeLocalContext (store, namespace, path) {

            const noNamespace = namespace === ''



            const local = {

             //noNamespace 為true 使用原先的 至于后面的 不知道干啥用的 后面繼續研究

              dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {

                const args = unifyObjectStyle(_type, _payload, _options)

                const { payload, options } = args

                let { type } = args



                if (!options || !options.root) {

                  type = namespace + type

                  if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {

                    console.error([vuex] unknown local action type: ${args.type}, global type: ${type})

                    return

                  }

                }



                return store.dispatch(type, payload)

              },



              commit: noNamespace ? store.commit : (_type, _payload, _options) => {

              //noNamespace 為true 使用原先的

                const args = unifyObjectStyle(_type, _payload, _options)

                const { payload, options } = args

                let { type } = args



                if (!options || !options.root) {

                  type = namespace + type

                  if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {

                    console.error([vuex] unknown local mutation type: ${args.type}, global type: ${type})

                    return

                  }

                }



                store.commit(type, payload, options)

              }

            }



            // getters and state object must be gotten lazily

            // because they will be changed by vm update

            Object.defineProperties(local, {

              getters: {

                get: noNamespace

                  ? () => store.getters

                  : () => makeLocalGetters(store, namespace)

              },

              state: {

                get: () => getNestedState(store.state, path)

              }

            })



            return local

          }



          function getNestedState (state, path) {

            return path.reduce((state, key) => state[key], state)

          }

          2.1.5 registerMutation

          mutation的注冊,會調用下方方法,將wrappedMutationHandler函數放入到entry中



          function registerMutation(store, type, handler, local) {

           // entry和store._mutations[type] 指向同一個地址

            var entry = store._mutations[type] || (store._mutations[type] = []);

            entry.push(function wrappedMutationHandler(payload) {

              handler.call(store, local.state, payload);

            });

          }





          2.1.6 forEachAction

          action的注冊,會調用下方方法,將wrappedActionHandler函數放入到entry中



          function registerAction(store, type, handler, local) {

            var entry = store._actions[type] || (store._actions[type] = []);

             // entry和store._actions[type]指向同一個地址

            entry.push(function wrappedActionHandler(payload) {

              var res = handler.call(store, {

                dispatch: local.dispatch,

                commit: local.commit,

                getters: local.getters,

                state: local.state,

                rootGetters: store.getters,

                rootState: store.state

              }, payload);

              if (!(0, _util.isPromise)(res)) {

                res = Promise.resolve(res);

              }

              if (store._devtoolHook) {

                return res.catch(function (err) {

                  store._devtoolHook.emit('vuex:error', err);

                  throw err;

                });

              } else {

                return res;

              }

            });

          }



          因為entry和上面的_action[type],_mutations[type] 指向同一個地址,所以:



          2.1.7 forEachGetter

          getter的注冊,會調用下方方法



          function registerGetter(store, type, rawGetter, local) {

            if (store._wrappedGetters[type]) {

              if (true) {

                console.error('[vuex] duplicate getter key: ' + type);

              }

              return;

            }

            store._wrappedGetters[type] = function wrappedGetter(store) {

              return rawGetter(local.state, // local state

              local.getters, // local getters

              store.state, // root state

              store.getters // root getters

              );

            };

          }







          2.1.8 resetStoreVM



          function resetStoreVM (store, state, hot) {

            const oldVm = store._vm //將_vm用變量保存



            // bind store public getters

            store.getters = {}

            // reset local getters cache

            store._makeLocalGettersCache = Object.create(null)

            const wrappedGetters = store._wrappedGetters // 獲取installModule方法完成的_wrappedGetters內容

            const computed = {}

            forEachValue(wrappedGetters, (fn, key) => {

              // use computed to leverage its lazy-caching mechanism

              // direct inline function use will lead to closure preserving oldVm.

              // using partial to return function with only arguments preserved in closure environment.

              computed[key] = partial(fn, store)

              Object.defineProperty(store.getters, key, {

              // 攔截get返回store._vm[key]中的值,即可以通過 this.$store.getters.xxx訪問值

                get: () => store._vm[key],

                enumerable: true // for local getters

              })

            })



            // use a Vue instance to store the state tree

            // suppress warnings just in case the user has added

            // some funky global mixins

            const silent = Vue.config.silent

            // silent設置為true,取消所有日志警告等

            Vue.config.silent = true

            store._vm = new Vue({ // 將state,getter的值進行監聽,從這里就可以看出getter其實就是采用的vue的computed

              data: {

                $$state: state

              },

              computed

            })

            // 恢復原先配置

            Vue.config.silent = silent



            // enable strict mode for new vm

            if (store.strict) {

              enableStrictMode(store)

            }

          // 若存在舊的實例,解除對state的引用,等dom更新后把舊的vue實例銷毀

            if (oldVm) {

              if (hot) {

                // dispatch changes in all subscribed watchers

                // to force getter re-evaluation for hot reloading.

                store._withCommit(() => {

                  oldVm._data.$$state = null

                })

              }

              Vue.nextTick(() => oldVm.$destroy())

            }

          }



          看到這里,你應該對vuex有初步的了解



           // 這也是我們為什么能用訪問到state,getter的原因

           //this.store.dispatch('xxx') ,this.$store.dispatch('xxx')

          1

          2

          相信你也有點亂,其實上面很多我沒講到的不是我不想講,是具體我也不知道干啥的,看源碼學習呢,看主要就行,后面理解多了,其他的就慢慢都會,你不可能剛開始看,就每一行,他寫的每一句的用途都知道是干什么的,只能先看主要方法,在慢慢琢磨,一步步來吧,別急,現在我來畫一個流程圖,更好的去理解吧。

          2.1.9 流程圖





          3.連貫

          import Vue from 'vue'

          import Counter from './Counter.vue'

          import store from './store'



          new Vue({

            el: '#app',

            store,

            render: h => h(Counter)

          })



          當運行new Vue的時候,傳入了store,前面minix beforecreate,執行到beforecreate鉤子時,會調用vueInit函數,就可以在this.$store取到store對象了,因為options.store有值了 ,不為空,這樣就連貫到了,所以這就是為什么可以用this.$store取值。


          日歷

          鏈接

          個人資料

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

          存檔

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