整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          C# 控件封裝方法及其思路- TAB標簽頁

          C# 控件封裝方法及其思路- TAB標簽頁

          .先展現示例

          TabPage


          實際應用示例


          尾部貼完整代碼,若只需要實現功能,直接復制代碼即可。

          1.首先分析 頁面需求,需要添加 標簽 ,點擊標簽傳遞參數。委托思路。

          第一步:分析標簽結構

          標簽頁面:展示圖標。文本信息。關閉按鈕。

          public class TabHeader : FlowLayoutPanel

          受限聲明一個控件,繼承 FlowLayoutPanel 繼承此控件主要是關鍵代碼系統(tǒng)控件已經都具有,不用自己造輪子,按照真正開發(fā)思路,還是可以繼承 control控件自定義分割,重新封裝。根據個人喜好。

          標簽頁 制作:

           public class TabHeaderItem : WenControl

          聲明一個標簽頁 繼承WenControl : 此控件是個人封裝的基礎控件,若要了解可以查看往期文章。或者開源搜索 WenSkin。可以找到相關介紹。

                      private TabHeader owner;
                      public TabHeaderItem(TabHeader owner, string path)
                      {
                          this.owner=owner;
                          this.Path=path;
                          this.Width=120;
                          this.Height=30;
          
                          WenButton button=new WenButton()
                          {
                              Size=new Size(16, 16),
                              Image=Properties.Resources.close,
                              ImageSize=new Size(14, 14),
                              TextImageRelation=TextImageRelation.Overlay,
                              Location=new Point(this.Width - 16 - 4, 7),
                          };
                          button.Click +=Button_Click;
          
                          this.Controls.Add(button);
                          this.Paint +=FileItemControl_Paint;
          
          
                          this.MouseLeave +=TabHeaderItem_MouseLeave;
                          this.MouseEnter +=TabHeaderItem_MouseEnter;
                      }
          

          聲明構造函數,部分細節(jié)后續(xù)代碼注意講解。

          此處最主要是 尺寸信息 當然可以直接 size=new size();個人習慣。



          聲明一個關閉按鈕。可以采用gdi畫,也可以直接添加一個圖片按鈕。

                    private void Button_Click(object sender, EventArgs e)
                      {
                          owner.Controls.Remove(this);
                          this.Dispose();
                      }

          文中就直接調用往期封裝好的按鈕控件。若需要了解可以跳轉往期文章查看。講解了關于按鈕 封裝。

          this.Paint +=FileItemControl_Paint;

          畫標簽 的文字和 內容,可以直接重寫 也可以使用委托畫,個人喜好。

                      private void FileItemControl_Paint(object sender, PaintEventArgs e)
                      {
                          var recImage=new Rectangle(4, 4, 22, 22);
                          var recStr=new Rectangle(28, 4, this.Width - 28 - 4 - 22 - 2, 22);
          
                          Graphics g=e.Graphics.SetGDIHigh();
                          g.DrawImage(Properties.Resources.file, recImage);
          
                          g.DrawString(FileText, this.Font, Brushes.White, recStr, ControlHelper.StringConters);
          
                      }

          this.MouseLeave +=TabHeaderItem_MouseLeave;

          this.MouseEnter +=TabHeaderItem_MouseEnter;

          鼠標事件處理

                    public void ReBackColor()
                      {
                          if (owner.selectedItem==this)
                          {
                              this.BackColor=Color.FromArgb(63, 63, 70);
                          }
                          else
                          {
                              this.BackColor=Color.Transparent;
                          }
                      }
          
                      private void TabHeaderItem_MouseEnter(object sender, EventArgs e)
                      {
                          this.BackColor=Color.FromArgb(62, 62, 64);
                      }
          
                      private void TabHeaderItem_MouseLeave(object sender, EventArgs e)
                      {
                          ReBackColor();
                      }


          主要是鼠標移動到指定位置改變顏色,離開后顏色會變化。直接修改背景顏色即可。

          可以設置一個選中顏色

          需要在主體中明一個選中選線。便于后續(xù)比較改變背景顏色。

                  #region 私有屬性
          
                  private TabHeaderItem selectedItem;
          
                  #endregion

          若需要暴露在 全部 可以將私有屬性改為公有。然后聲明變化。

          接下來就是 添加標簽代碼。

          文中有一個需求就是最新 添加標簽在最前端,并選中。

                  public void Add(string path)
                  {
                      TabHeaderItem f=new TabHeaderItem(this, path);
          
                      foreach (TabHeaderItem item in this.Controls)
                      {
                          if (path==item.Path)
                          {
                              this.Controls.SetChildIndex(item, 0);
                              re(item);
                              return;
                          }
                      }
          
                      f.Click +=(s, e)=>
                      {
                          re(f);
                      };
                      this.Controls.Add(f);
                      re(f);
                      this.Controls.SetChildIndex(f, 0);
          
                      void re(TabHeaderItem item)
                      {
                          ItemChanged?.Invoke(this, new TabEventArgs(path));
                          var ci=selectedItem;
                          selectedItem=item;
                          ci?.ReBackColor();
                          item.ReBackColor();
                      }
                  }
          

          若有其他需求可直接更改即可。

                  #region 委托
          
                  public delegate void TabEventHandler(object sender, TabEventArgs e);
          
                  public class TabEventArgs : EventArgs
                  {
                      public TabEventArgs(string path)
                      {
                          Path=path;
                      }
          
                      public string Path { get; set; }
                  }
          
                  public event TabEventHandler ItemChanged;
          
                  #endregion


          至此,完整解決一個tab標簽。

          完整代碼塊

          EMO

          麻不燒的Github

          配合著源碼,用心看完這篇文章,你便領悟了封裝的精髓,麻雀雖小,五臟俱全。

          前記

          業(yè)務代碼之外的代碼,我想稱之為增值代碼

          什么意思?

          作為一個程序員,你應該除了完成領導安排的任務,你還應該有一些自己的時間,用來“玩”一些比較有意思的事情。

          當現有框架、庫滿足不了我們需求的時候,我們應該嘗試去自己造一些工具。也正是這些你所實現的,成就了他人,造就了自己

          不信,你且想一想,他人會關心你寫的具體的業(yè)務邏輯代碼嗎?我想他們更關心的是,你寫的插件,是如何使用的吧,以及方不方便他們借此完成他們自己業(yè)務代碼。

          再通俗一點,他們不會記住你,但是他們會記住你的Api,因而憶起你

          還有很重要的一點,所有的技術,都是服務于業(yè)務的,否則,就是扯皮。

          背景

          入職新公司以來,一直忙于開發(fā)業(yè)務,過程中,多處用到了領導寫的牛逼工具。說實話,內心由衷的佩服,簡直就是解放生產力,放到古代,就是要被封神滴。

          舉個例子:

          領導花了一段時間,研究出了一個自動表單生成器。之前手寫一個表單配置頁,加上表單驗證,可能需要半天,甚至更久。

          現在呢?所有的表單、樣式及驗證,都可以通過代碼配置實現,二十分鐘可能就完成了。

          由此,我悟出了一個道理:

          重復地做一件事,不如用心地做“一件事”。

          我想,你肯定也想成為他人口中的那個男人,但整天活在自己的世界里,你可能一時并不知道該如何去做,這里我想告訴你:

          成長的一個關鍵性因素,就是來自于模仿。

          對的,你可以先嘗試著去閱讀下他人的代碼,看看別人的實現方式,再者可以去github上溜一圈,優(yōu)秀項目太多了,仿著寫去唄。

          我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取。

          只要你想學,你就一定能學會,只不過是實現的方式好與壞而已,這些是需要后期不斷完善的。

          鑒于本篇文章快要跑題了,不再多述,進入正題...

          正文

          1.組件和插件的區(qū)別與聯(lián)系

          區(qū)別

          • 組件的使用頻率往往大于插件
          • 組件的作用范圍往往小于插件

          聯(lián)系

          • 插件可以封裝組件,組件可以暴露數據給插件

          這里不做過多闡述,有興趣可以參考下勞卜大大的這篇文章,寫的很通俗易懂。

          2.實現插件的必備因素

          基礎

          你需要清楚的知道vue的一些高階知識點以及相關內容,比如

          • Vue.extend構造器
          • $mount手動掛載實例
          • mixin混合注入
          • 父子組件傳參、跨級組件傳參
          • 理解Vue構造函數及prototype原形對象
          • npm官網注冊賬號
          • webpack打包
          • ...

          技巧

          以下這個技巧是今天開發(fā)的時候悟出來的,目測很有用:

          別著急開發(fā),先想著如何在開發(fā)中使用你的插件

          什么意思?順著我的思路捋下去


          因為我想實現一個全局toast插件,大概用法



           this.$toast('那個男人') // todo

          光彈出文案不行,應該有一個控制彈出方向的變量




           this.$toast('那個男人',{ position:'topCenter' })

          全局toast的狀態(tài)應該有多種,比如常見的成功、錯誤、警告、普通...









           // 成功success // 錯誤error // 警告warning // 普通info this.$toast('那個男人',{ position:'topCenter', type:'success' })

          應該有一個時間變量去控制多長時間自動消失toast






          this.$toast('那個男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast })

          會不會存在一種業(yè)務場景,我們不需要自動消失toast







           this.$toast('那個男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: false })

          如果我想在toast結束后,觸發(fā)一些回調動作,比如刪除成功toast后刷新列表頁面










           this.$toast('那個男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: true, callback () { ... } })

          toast的內容,可能會很長,因此應該有兩個變量分別控制toast寬度和高度












           this.$toast('那個男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: true, callback () { ... }, width:300, height:80 })

          至此,基礎功能應該都涵蓋了,這個時候你要去考慮一些內建的問題

          1. 這么多配置項,我作為一個使用者,都關心嗎?或者說,都需要配置嗎?
          2. 針對不同的狀態(tài)(success/error/warning/info),肯定要用不同的顏色區(qū)分,以及使用不同的圖標,他們之間有什么關系嗎?

          配置項多應該怎么解決-默認值

          默認給個type唄,比如我的項目中默認的type是info,當我在使用的時候,沒有傳入type時,默認為info

          因為大部分的toast場景都是短暫的停留在頁面,所以autoClose設置為true

          又因為大部分的toast文案比較短,所以我的默認toast長寬設置為300、80應該足夠了

          ...

          以上默認配置,都可以在使用的時候,傳入參數覆蓋默認參數

          針對不同的狀態(tài),toast圖標、顏色、標題之間有什么聯(lián)系?

          本地存一個map映射配置表,根據傳入的type,我就可以準確的知道圖標、顏色、標題應該是什么

          總結幾點:

          • 插件對外暴露的參數應保持最少原則,聚焦使用者關注點
          • 插件或組件的實現應該要基于使用場景考慮
          • 開發(fā)一款組件或者插件,應該保持軟件工程領域的開放封閉原則
          • 一款好的插件或組件并不是一蹴而就的,往往需要后期使用過程中發(fā)現問題,加以完善
          • 組件或者插件的文檔一定要完善,并不是每一個使用它的人,都關心它的內部實現,他們更關心的可能僅是如何快速上手

          實現

          上文提到過,組件可以暴露數據給插件,對于這句話

          我的理解是,組件是靜態(tài)的,只是對外暴露一些參數入口props。插件,讓我們可以動態(tài)的往其中注入一些自定義參數。具體的實現,還是在組件當中完成。

          于是乎:我寫了一個靜態(tài)組件,通過props定義上文提到的相關變量

          先看下script部分

          再來看下html部分

          可以看到,內部實現其實很簡單,無非就是通過外部傳入的props,控制內部的展示細節(jié)而已

          到這里靜態(tài)組件基本已經完成了(css樣式代碼不在這里貼了)

          注意:

          • props定義的時候,最好用對象的寫法,作為一定的約束
          • 變量名字最好做到見名知意
          • class名的綁定可以充分利用vue提供的數組以及對象形式或者配合計算屬性完成



          靜態(tài)組件怎么變成插件使用呢?

          這里不再做過多闡述,vue封裝插件的常用方法主要有以下四種,有疑惑的話,建議觀閱vue開發(fā)插件,當然我覺得你應該還需要去了解下Vue.extend的用法,插件的實現離不開它哦。

          看下關鍵部分:該文件也是我們后期webpack打包(build)的入口文件

          該文件內容涉及到的知識點,也是開發(fā)一個vue插件最核心的內容。里面的每一行代碼,都充滿了殺機~

          至此,關于插件實現部分基本已經全部完成。

          3.如何將自己的插件上傳到npm上去

          這里的話,網上的教程有很多,我理解你只需要了解以下幾行代碼的作用,就足夠了
























           // webpack.config.js module.exports={ entry: process.env.NODE_ENV==='development' ? './src/main.js' : './src/index.js', output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js', libraryTarget: 'umd' }, ... // package.json { "name": "mbs-toast", "description": "a toast plugin base on Vue2", "version": "1.0.0", "author": "xxx ", "license": "MIT", "private": false, "main": "dist/build.js", "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", ...
          • 這里的entry入口文件配置的意思是,開發(fā)環(huán)境時,入口文件用main.js(方便開發(fā)調試使用),打包時入口文件為index.js
          • filename是打包生成文件的名字,這里是webpack-simple模版默認使用的就是build.js,沒有特殊需求的話,不建議改動
          • libraryTarget屬性可能大家都會比較陌生,因為一般如果只在項目中使用 webpack 不需要關注這兩個屬性,但是如果是開發(fā)類庫、插件,那么這兩個屬性就是必須了解的,不清楚的可以參考詳解webpack中ibraryTarget屬性
          • package.json文件中的main字段,指定了該npm包引用的入口(記住一定要記得添加,并且文件名應與上面第二點提到的保持一致)

          這里我用的模版是自己在官方webpack-simple模版的基礎上做了一些定制化的,里面為了方便我平時開發(fā),加入了scss、eslint,這樣的話,后面就不用每次手動install了,有興趣的可以看下README,定制一份屬于自己的腳手架模板

          在你了解了上述背景后,你只需要執(zhí)行以下幾步即可實現皆大歡喜

          順利的話,現在你已經可以在正式項目中,通過


          npm install -S xxx 安裝你的私有包了

          最后在你的入口文件注冊你的插件



          import toastPlugin from 'xxx'Vue.use(toastPlugin) // 這里Vue.use的第二個參數,可以通過全局配置,做一些自定義配置,有需要的自行前往學習
          到這里,所有的一切,已塵埃落定
          

          你可以在代碼中愉快的使用了









          this.$toast('塵埃落定', { callback () { console.log('hello world') }, type: 'success', // position: 'topRight', autoClose: false})

          最后

          我在寫這個插件之前,在Github上看到一個大神封裝的插件。四個字描述下,嘆為觀止,有興趣的一定要去看下,我相信愛學習的你,一定會收獲滿滿。同時在開發(fā)該插件時,一些樣式及動畫,也做了相應的參考。

          該插件的源碼已經上傳mbs-toast,方便大家參考。同時,上述提到的form表單生成器,我也嘗試著自己實現了一遍,有興趣的可以一起加入哦。所有的插件以及組件目前都匯總在麻不燒的Github里,文檔和README正在不斷完善中~

          碼字不易,且行且珍惜!

          源自:https://juejin.im/post/5dc42069f265da4d3962a8e4

          聲明:文章著作權歸作者所有,如有侵權,請聯(lián)系小編刪除。

          小希這次帶來了進階版的Vue3 + Vite項目框架的封裝搭建

          本文主要的切入點有

          搭建過程

          多入口打包

          一般情況下,項目開發(fā)只有一個入口,只需要配置一個入口,一個項目

          但有時多個同業(yè)務、同類型的項目,有很多可以復用的業(yè)務,組件,工具類等,就可以放在同一個代碼庫里進行維護,不用新建多個代碼庫

          每個項目都有自己獨立的入口,可以獨立打包并進行部署,低耦合,不會相互影響,同時還可以復用相同的組件,業(yè)務等,可以大大地提高開發(fā)效率和后期的維護

          調整項目結構目錄

          如上圖所示,有兩個項目,分別是app1,app2,每個項目都有自己獨有的main.ts入口文件,App.vue文件,以及路由,倉庫pinia,組件等,同時也有共有的組件,utils工具類等

          配置過程

          在package.json中,將下圖單入口的配置

          改為

          {
            "scripts": {
              "dev:app1": "vite serve src/app1/ --config ./vite.config.ts",
              "dev:app2": "vite serve src/app2/ --config ./vite.config.ts",
              "build:app1": "vue-tsc && vite build",
              "build:app2": "vue-tsc && vite build"
            },
          


          這樣配置可實現項目的獨立運行,獨立打包

          在vite.config.ts中配置:

          /* 項目名稱 */
          //采用這種方式可以動態(tài)獲取項目名稱,當然,如果項目少可以手動配置
          let appName=process.env.npm_lifecycle_event
          appName=appName.slice(appName.indexOf(':') + 1) //app1、app2
          
          export default defineConfig({
            root: `./src/${appName}/`,
            build: {
              rollupOptions: {
                input: {
                  [appName]: path.resolve(__dirname, `src/${appName}/index.html`)
                },
                output: {
                  chunkFileNames: 'js/[name]-[hash].js',
                  entryFileNames: 'js/[name]-[hash].js',
                  assetFileNames: '[ext]/[name]-[hash].[ext]',
                }
              }
            }
          })
          


          測試項目的運行和打包構建

          打包構建

          pnpm build:app1
          
          pnpm build:app2
          

          自動化生成項目基礎模版

          基于多入口打包,也就是一個代碼庫同時維護多個同類型的項目情況下,可以通過配置實現自動化生成項目基礎模板,這樣,當需要在代碼庫新建一個新項目時,可以通過命令行快速創(chuàng)建

          前置工具

          inquirer

          這個插件用來詢問用戶輸入項目名稱,這是一個比較在處理命令行交互比較常見的庫

          主要用于實現命令行交互式界面。幫助我們與用戶進行交互式交流

          它有幾個特點:提供錯誤反饋,詢問問題,解析輸入,驗證答案

          詳細可參考 命令行交互工具inquirer

          安裝

          pnpm add  inquirer@^8.0.0 -S
          


          配置過程

          在package.json里添加

          "scripts": {
              "init-app": "node ./src/utils/initApp/index.ts"
          }
          


          當執(zhí)行這個命令時,會自動去執(zhí)行,在本地utils文件夾下的initApp文件里的js腳本,在src目錄下會自動生成一個新的文件夾(項目)

          在utils下新增initApp文件夾以及index.ts和temlate

          在index.ts添加以下代碼

          #!/usr/bin/env node
          console.log('您正在創(chuàng)建項目')
          const path=require('path')
          const fs=require('fs')
          const inquirer=require('inquirer')
          const stat=fs.stat
          const targetDir=path.resolve(__dirname, './template')
          //復制文件目錄
          const copyFile=(targetDir, resultDir)=> {
            // 讀取文件、目錄
            fs.readdir(targetDir, function (err, paths) {
              if (err) {
                throw err
              }
              paths.forEach(function (p) {
                const target=path.join(targetDir, '/', p)
                const res=path.join(resultDir, '/', p)
                let read
                let write
                stat(target, function (err, statsDta) {
                  if (err) {
                    throw err
                  }
                  if (statsDta.isFile()) {
                    read=fs.createReadStream(target)
                    write=fs.createWriteStream(res)
                    read.pipe(write)
                  } else if (statsDta.isDirectory()) {
                    fs.mkdir(res, function () {
                      copyFile(target, res)
                    })
                  }
                })
              })
            })
          }
          
          const question=[
            {
              type: 'input',
              name: 'name',
              message: '請輸入項目名稱:'
            }
          ]
          
          const createProject=()=> {
            // 詢問用戶問題
            inquirer
              .prompt(question)
              .then(({ name })=> {
                // name 為輸入的項目名稱
                name=name.trim()
                if (!name) {
                  console.log('項目目錄不能為空')
                  // 如果輸入空,繼續(xù)詢問
                  createProject()
                  return false
                }
                // 目標路徑,要放在module目錄下
                const resultDir=path.resolve(__dirname, '../../', name)
                // fs.access()方法用于測試文件是否存在
                fs.access(resultDir, function (err, data) {
                  if (err) {
                    // 創(chuàng)建文件
                    fs.mkdir(resultDir, function (err, data) {
                      if (err) {
                        throw err
                      }
                      // 復制模版文件
                      copyFile(targetDir, resultDir)
                    })
                    console.log(`${name} 項目已創(chuàng)建成功`)
                  } else {
                    console.log(`${name} 項目目錄已存在,請輸入其他名稱`)
                    // 不存在,繼續(xù)詢問
                    createProject()
                  }
                })
              })
              .catch((err)=> {
                console.log(err)
              })
          }
          createProject()
          


          注:此代碼copy==> Vue3項目框架搭建封裝,一次學習,終身受益【萬字長文,滿滿干貨】

          在temlate文件夾下新增項目所需要的文件目錄,main.ts以及App.vue是必須的,因為它是獨立的項目

          使用

          app3項目自動生成

          持久化pinia倉庫數據

          數據存儲在緩存(內存)中,優(yōu)點讀寫更快,可以保存任意的js類型數據和對象,比如當我們刷新瀏覽器的時候,數據會丟失,所以需要實現pinia持久化

          持久化插件pinia-plugin-persist

          安裝

          pnpm add pinia-plugin-persist
          


          import { createApp } from 'vue'
          import { createPinia } from 'pinia'
          import piniaPersist from 'pinia-plugin-persist'
          
          const pinia=createPinia()
          pinia.use(piniaPersist)
          
          createApp({})
            .use(pinia)
            .mount('#app')
          


          使用

          // store/use-user-store.ts
          import { defineStore } from 'pinia'
          
          export const useUserStore=defineStore('storeUser', {
            state: ()=> {
              return {
                firstName: 'S',
                lastName: 'L',
                accessToken: 'xxxxxxxxxxxxx'
              }
            },
            actions: {
              setToken (value: string) {
                this.accessToken=value
              }
            },
            persist: {
              enabled: true,
              //這里可以單獨給每個字段配置存儲的形式sessionStorage/localStorage
              //paths配置state里的字段,不同的數據采取不同的存儲方式
              strategies: [
                { storage: sessionStorage, paths: ['firstName', 'lastName'] },
                { storage: localStorage, paths: ['accessToken'] },
              ],
          
            }
          })
          


          strategies 字段說明:

          屬性

          描述

          key

          自定義存儲的 key,默認是 store.$id

          storage

          可以指定localStorage/sessionStorage,或者自定義存儲類型,默認為 sessionStorage

          paths

          state 中的字段名,按組打包儲存

          也可以自定義存儲類型,更多具體配置戳pinia-plugin-persist插件官網地址

          源碼解析

          核心是通過 store.$subscribe去監(jiān)聽倉庫數據,當倉庫數據發(fā)生變化時會觸發(fā)回調,更改本地緩存數據,當刷新后就會從本地緩存取出相關的數據

          import { PiniaPluginContext } from 'pinia'
          
          
          type Store=PiniaPluginContext['store']; //pinia插件上下文
          type PartialState=Partial<Store['$state']>;
          
          //調用函數將倉庫數據存儲到本地
          
          export const updateStorage=(strategy: PersistStrategy, store: Store)=> {
            const storage=strategy.storage || sessionStorage //可以自定義存儲類型,默認為sessionStorage
            const storeKey=strategy.key || store.$id   //可以自定義存儲的 key,默認是 store.$id 
          
           //判斷是否有配置paths,如果沒有就緩存一整個倉庫中的state
            if (strategy.paths) {
            
            //遍歷paths里面的字段,并通過 store.$state[key]獲取相應的數據
              const partialState=strategy.paths.reduce((finalObj, key)=> {
                finalObj[key]=store.$state[key]
                return finalObj
              }, {} as PartialState)
              
              //存儲到本地
              storage.setItem(storeKey, JSON.stringify(partialState))
            } else {
              storage.setItem(storeKey, JSON.stringify(store.$state))
            }
          }
          
          export default ({ options, store }: PiniaPluginContext): void=> {
              //判斷enabled是否為true
              
            if (options.persist?.enabled) {
              const defaultStrat: PersistStrategy[]=[{
                key: store.$id,
                storage: sessionStorage,
              }]
          
              const strategies=options.persist?.strategies?.length ? options.persist?.strategies : defaultStrat
          
              strategies.forEach((strategy)=> {
                const storage=strategy.storage || sessionStorage
                const storeKey=strategy.key || store.$id
                
               //根據key判斷是否在本地緩存中,如果在刷新后會從本地緩存中將數據賦給pinia倉庫的state
                const storageResult=storage.getItem(storeKey)
          
               // 如果本地中存在同步數據,更新倉庫state數據
               //(比如瀏覽器刷新后會進行判斷,如果有數據會賦值給pinia倉庫的state,實現pinia持久化)
                if (storageResult) {
                  store.$patch(JSON.parse(storageResult))
                  updateStorage(strategy, store)
                }
              })
              
              //通過$subscribe監(jiān)聽state,倉庫數據更改會觸發(fā)回調同步更改本地數據
              store.$subscribe(()=> {
                strategies.forEach((strategy)=> {
                  updateStorage(strategy, store)
                })
              })
            }
          }
          


          引入nprogess進度條

          通過顯示進度條的形式,來提高用戶體驗,可用在進入/離開路由時觸發(fā)動畫,也可在發(fā)接口時使用

          安裝

          pnpm add nprogress -S
          


          基本用法

          只需調用start()和done()即可控制進度條。

          NProgress.start();
          NProgress.done();
          


          使用場景

          切換路由

          router.beforeEach((to, from, next)=> {
            NProgress.start()
            next()
          })
          router.afterEach(()=> {
            NProgress.done()
          })
          
          


          發(fā)請求時

          // axios請求攔截器
          axios.interceptors.request.use(
          config=> {
              NProgress.start() // 設置加載進度條(開始..)
              return config
          },
          error=> {
              return Promise.reject(error)
          }
          )
          // axios響應攔截器
          axios.interceptors.response.use(
          function(response) {
              NProgress.done() // 設置加載進度條(結束..)
              return response
          },
          function(error) {
              return Promise.reject(error)
          }
          )
          


          其它詳細配置請戳官網:ricostacruz.com/nprogress/

          viewport 適配方案 - postCSS插件

          介紹

          PostCSS 是一種 JavaScript 工具,可將你的 CSS 代碼轉換為抽象語法樹 (AST),然后提供 API(應用程序編程接口)用于使用 JavaScript 插件對其進行分析和修改。

          Autoprefixer主要功能是解析CSS并使用Can I Use中的值向CSS規(guī)則添加供應商前綴。以兼容各種瀏覽器,部分CSS屬性需要加上不同的前綴以兼容不同的瀏覽器。通過配置Autoprefixer,自動為CSS屬性添加對應瀏覽器的前綴。

          postcss-px-to-viewport 用于將單位為 px 的尺寸轉換為視口單位(vw, vh, vmin, vmax)

          下面用到Autoprefixer和postcss-px-to-viewport這兩個插件進行viewport適配

          PostCSS 配置

          安裝

          pnpm add postcss-px-to-viewport -D
          pnpm add  autoprefixer -D
          


          創(chuàng)建postcss.config.js并配置

          // postcss.config.js
          module.exports=()=> {
            return {
              plugins: {
                autoprefixer: {},
                'postcss-px-to-viewport': {
                  unitToConvert: 'px', // 需要轉換的單位,默認為"px"
                  viewportWidth: 1920, // 設計稿的視口寬度
                  unitPrecision: 5, // 單位轉換后保留的精度
                  propList: ['*'], // 能轉化為vw的屬性列表
                  viewportUnit: 'vw', // 希望使用的視口單位
                  fontViewportUnit: 'vw', // 字體使用的視口單位
                  selectorBlackList: [], // 需要忽略的CSS選擇器,不會轉為視口單位,使用原有的px等單位。
                  minPixelValue: 1, // 設置最小的轉換數值,如果為1的話,只有大于1的值會被轉換
                  mediaQuery: false, // 媒體查詢里的單位是否需要轉換單位
                  replace: true, //  是否直接更換屬性值,而不添加備用屬性
                  exclude: undefined, // 忽略某些文件夾下的文件或特定文件,例如 'node_modules' 下的文件
                  include: undefined, // 如果設置了include,那將只有匹配到的文件才會被轉換
                  landscape: false, // 是否添加根據 landscapeWidth 生成的媒體查詢條件 @media (orientation: landscape)
                  landscapeUnit: 'vw', // 橫屏時使用的單位
                  landscapeWidth: 1920 // 橫屏時使用的視口寬度
                }
              }
            }
          }
          
          


          效果如下

          不同視口寬度,界面會響應性變化


          作者:小希學前端
          鏈接:https://juejin.cn/post/7327965216826032154


          主站蜘蛛池模板: 国产精品区一区二区三在线播放| 亚洲av无码天堂一区二区三区| 成人午夜视频精品一区| 日韩人妻一区二区三区免费| 国产伦精品一区二区三区四区| 无码乱人伦一区二区亚洲一| 国产伦精品一区二区三区免费下载 | 国产激情精品一区二区三区| 精品一区二区三区在线播放视频| 日产一区日产2区| 久久91精品国产一区二区| 日韩一区二区三区精品| 精品视频一区二区三区在线播放| 天美传媒一区二区三区| 在线成人综合色一区| 国产在线aaa片一区二区99| 国产成人一区二区动漫精品| 精品一区精品二区制服| 一区二区三区四区视频| 国产熟女一区二区三区四区五区| 国产成人AV区一区二区三 | 中文字幕一区日韩在线视频 | 久久人做人爽一区二区三区| 免费看一区二区三区四区| 精品亚洲一区二区三区在线播放| 国语对白一区二区三区| 色欲AV蜜桃一区二区三| 亚洲一区二区三区乱码在线欧洲| 日韩人妻不卡一区二区三区| 精品一区二区在线观看| 国产av熟女一区二区三区| 黑人大战亚洲人精品一区| 内射女校花一区二区三区| 中文字幕日本一区| 久久久不卡国产精品一区二区| 一区二区免费国产在线观看| 国产精品污WWW一区二区三区| 一区二区三区内射美女毛片| 亚洲国产精品一区二区第一页免 | 色天使亚洲综合一区二区| 国产情侣一区二区|