Ant Design Pro Vue使用心得

19806 ワード

ディレクトリ構造
├── public
│   └── logo.png             # LOGO
|   └── index.html           # Vue     
├── src
│   ├── api                  # Api ajax  
│   ├── assets               #       
│   ├── config               #       ,    ,    
│   ├── components           #       
│   ├── core                 #     ,        ,      
│   ├── router               # Vue-Router
│   ├── store                # Vuex
│   ├── utils                #    
│   ├── locales              #      
│   ├── views                #            
│   ├── App.vue              # Vue     
│   └── main.js              # Vue    JS
│   └── permission.js        #     (      )
├── tests                    #     
├── README.md
└── package.json

ルートとメニュー
きほんこうぞう
ルーティングとメニューはアプリケーションを組織する重要なスケルトンであり、proにおけるルーティングは管理を容易にするためにrouter.config.jsは構成と管理を統一する.
  • ルーティング管理は、routerにおいて、所定の構文に従って管理する.config.jsでルーティングを構成します.
  • メニュー生成は、ルーティング構成に従ってメニューを生成する.メニュー・アイテム名、ネストされたパスがルーティングに高度に結合されています.
  • パンくずアセンブリPageHeaderに内蔵されているパンくずは、足場から提供される構成情報によって自動的に生成されてもよい.

  • ルート
    現在、足場のすべてのルートはrouterを通過しています.config.jsは管理を統一し、vue-routerの構成にhideChildrenInMenu,metaなどのパラメータを追加した.title,meta.icon,meta.permissionで、生成メニューを支援します.次のようになります.
  • hideChildrenInMenuは、メニューに表示する必要のないサブルーティングを隠すために使用される.使用法では、ステップ・フォームの構成を表示できます.
  • hiddenは、サブルーティングを含むこのルーティングをメニューに示さなくてもよい.効果は、otherでのルーティング構成を表示します.
  • meta.titleとmeta.iconは、メニュー項目を生成するテキストとアイコンをそれぞれ表します.
  • meta.permissionは、このルーティングの権限を構成するために使用され、構成されている場合、現在のユーザーの権限が検証され、*が表示されるかどうかを決定します(デフォルト).
  • meta.hiddenは、サブメニューをメニューに表示しないように強制することができる(親hideChildrenInMenuと組み合わせる)
  • .
  • meta.hiddenHeaderContentは、現在のページにPageHeaderコンポーネントのページバンドのパンくずとページタイトルバー
  • を表示しないように強制することができる.
    ルーティング構成アイテム
    /**
     *       :
     *   :sider menu          ,       ,                    
     *
     **/
     {
      redirect: noredirect,  //   
      name: 'router-name',   //    
      hidden: true,          //             ,     。       other       。
      meta: {
        title: 'title',      //     
        icon: 'a-icon',      //     
        keepAlive: true,     //    
        permission:[string]   //           ,                ,        *(     )
        hiddenHeaderContent: true,   //            PageHeader                   
      }
    }
    

    具体的にはhttps://pro.loacg.com/docs/router-and-nav
    メニュー
    メニューはrouter.config.js生成、具体的な論理はsrc/store/modules/permissionである.jsのactions.GenerateRoutesメソッドが実装されます.
    Ant Design Proのレイアウト
    Ant Design Proでは、使用中の汎用レイアウトを抽出し、/components/layoutsディレクトリに配置しました.
  • BasicLayout:ヘッドナビゲーション、サイドバー、通知バーを含むベースページレイアウト
  • UserLayout:登録ページに登録するための汎用レイアウト
  • を抽出する
  • PageView:ベースレイアウト、パンくず、および中間コンテンツ領域(slot)
  • を含む
  • RouterView:空のレイアウトで、2レベルのメニューコンテンツ領域をカスタマイズするために
  • BlankLayout:空白のレイアウト
  • グローバルスタイルの定義
    /*        */
    :global(.text) {
      font-size: 16px;
    }
    
    /*          */
    :global {
      .footer {
        color: #ccc;
      }
      .sider {
        background: #ebebeb;
      }
    }
    //      
    //    css      >>>       
    .test-wrapper >>> .ant-select {
        font-size: 16px;
    }
    
    //    scss, less  ,    /deep/       
    .test-wrapper /deep/ .ant-select {
        font-size: 16px;
    }
    
    // less CSS modules      :global     
    .test-wrapper {
        :global {
            .ant-select {
                font-size: 16px;
            }
        }
    }
    

    サーバとの対話
    Ant Design Proでは、完全なフロントエンドUIインタラクションをサービス側処理プロセスに適用します.
  • UIコンポーネントの相互作用;
  • 統一管理apiサービス要求関数を呼び出す.
  • はパッケージのrequestを用いる.js送信要求;
  • は、サービス側の戻りを取得する.
  • dataを更新します.

  • 上記の手順から、管理メンテナンスを容易にするために、統合されたリクエスト処理は@/src/apiフォルダに配置され、model緯度に従ってファイルを分割するのが一般的であることがわかります.
    api/
      user.js
      permission.js
      goods.js
      ...
    

    ただし、@/src/utils/request.jsはaxiosベースのパッケージであり,POST,GETなどの要求パラメータ,要求ヘッダ,エラーメッセージなどを統一的に処理するのに便利である.具体的にはrequestを参照することができる.js. グローバルrequestブロッキング、responseブロッキング、統合されたエラー処理、baseURL設定などがカプセル化されています.
    例えばapiのユーザ情報を要求する例:
    // api/user.js
    import { axios } fromm '@/utils/request'
    
    const api = {
        info: '/user',
        list: '/users'
    }
    
    //      id       
    export function getUser (id) {
        return axios({
            url: `${api.user}/${id}`,
            method: 'get'
        })
    }
    
    //     
    export function addUser (parameter) {
        return axios({
            url: api.user,
            method: 'post',
            data: parameter
        })
    }
    
    //      // or (id, parameter)
    export function updateUser (parameter) {
        return axios({
            url: `${api.user}/${parameter.id}`, // or `${api.user}/${id}`
            method: 'put',
            data: parameter
        })
    }
    
    //     
    export function deleteUser (id) {
        return axios({
            url: `${api.user}/${id}`,
            method: 'delete',
            data: parameter
        })
    }
    
    //        parameter: { pageSize: 10, pageNo: 1 }
    export function getUsers (parameter) {
        return axios({
            url: api.list,
            method: 'get',
            params: parameter
        })
    }
    
    
    
    
    import { getUser, getUsers } from '@/api/user'
    
    export default {
        data () {
            return {
                id: 0,
                queryParam: {
                    pageSize: 10,
                    pageNo: 1,
                    username: ''
                },
                info: {},
                list: []
            }
        },
        methods: {
            queryUser () {
                const { $message } = this
                getUser(this.id).then(res => {
                    this.info = res.data
                }).catch(err => {
                    $message.error(`load user err: ${err.message}`)
                })
            },
            queryUsers () {
                getUsers(this.queryParam).then(res => {
                    this.list = res
                })
            }
        }
    }
    
    
    **
         *         
         */
        cropImage () {
          this.form.cropimg = this.$refs.cropper.getCroppedCanvas().toDataURL();
        },
        /**
         *     
         */
        sureCrop () {
          this.dialogVisible = false
        },
        /**
         *             
         */
        upCropImg () {
          //           
          if (this.$route.query.id && this.$route.query.id != '') {
            //           
            this.onSubmit()
          } else {
            //         , 64            
            var formdata1 = new FormData();//   form  
            formdata1.append('file', convertBase64UrlToBlob(this.form.cropimg), 'aaa.png');//
            this.$ajax
              .post(this.$api + "/upload/singleUploadImg", formdata1, { headers: { 'Content-Type': 'multipart/form-data' } })
              .then(response => {
                if (response.data.msg == "success" && response.data.code == 1) {
                  this.form.imgUrl = response.data.data.imgUrl
                  this.onSubmit()
                } else {
                  console.log(response)
                  this.$message.error(response.data.msg);
                }
              })
              .catch(function (error) {
                console.log(error);
              });
          }
    
        },
    

    外部モジュールの導入
    $ npm install '    ' --save
    

    使用
    //    
    import Vue from 'vue'
    import VueQuillEditor from 'vue-quill-editor'
    
    // require styles
    import 'quill/dist/quill.core.css'
    import 'quill/dist/quill.snow.css'
    import 'quill/dist/quill.bubble.css'
    
    Vue.use(VueQuillEditor, /* { default global options } */)
    
    
    
    
    //    
    import 'quill/dist/quill.core.css'
    import 'quill/dist/quill.snow.css'
    import 'quill/dist/quill.bubble.css'
    import { quillEditor } from 'vue-quill-editor'
    
    export default {
      components: {
        quillEditor
      },
      data () {
          return {
              content: '<h2>I am Example</h2>',
              editorOption: {
               // something config
              }
          }
      },
      //             ,          changed  
      methods: {
        onEditorBlur(editor) {
          console.log('editor blur!', editor)
        },
        onEditorFocus(editor) {
          console.log('editor focus!', editor)
        },
        onEditorReady(editor) {
          console.log('editor ready!', editor)
        },
        onEditorChange({ editor, html, text }) {
          // console.log('editor change!', editor, html, text)
          this.content = html
        }
      },
      //           editor        ,                      editor  ,      $refs              ref         
      computed: {
        editor() {
          return this.$refs.myTextEditor.quillEditor
        }
      },
      mounted() {
        // you can use current editor object to do something(editor methods)
        console.log('this is my editor', this.editor)
        // this.editor to do something...
      }
    }
    
    

    ビジネス・アイコンのインポート
    参照先:https://pro.loacg.com/docs/biz-icon、
    国際化
    参照先:https://pro.loacg.com/docs/i18n
    権限管理
    参照先:https://pro.loacg.com/docs/authority-management
    カスタム使用規則
  • サイトiconのファイルアドレスを修正publicフォルダにlogo.pngをカスタムに変更するもpublic/index.htmlカスタム
  • 
    
      
        
        
        
        
            
        
      
      
        
        

    -ロゴを交換してください.vueで交換
    
    
    
    import LogoSvg from '@/assets/logo.svg?inline';
    export default {
      name: 'Logo',
      components: {
        LogoSvg
      },
      props: {
        title: {
          type: String,
          default: 'Admin For Ok',    //      
          required: false
        },
        showTitle: {                  //        ,     
          type: Boolean,
          default: true,
          required: false
        }
      }
    }
    
    
    

    Pro権限管理とルーティング制御の考え方分析(大まかな分析)
  • は主に3つのファイルで実現され、srcmockservicesuser.jsファイルは、登録されたロールによって対応する認証規則を取得し、具体的には、そのファイルの下のソースコード
  • を表示することができる.
  • src\config\router.config.jsファイルはルーティングプロファイルであり、ルーティングキャンセルなどを追加することができ、変数asyncRouterMapは主なルーティング配列の集合であり、認証権限を構成することができ、変数constantRouterMapはベースルーティングであり、認証
  • には参加しない.
  • src\permission.jsファイルは動的構成ルーティングの主な論理であり、コードは以下の
  • である.
    import Vue from 'vue'
    import router from './router'
    import store from './store'
    
    import NProgress from 'nprogress' // progress bar
    import 'nprogress/nprogress.css' // progress bar style
    import notification from 'ant-design-vue/es/notification'
    import { setDocumentTitle, domTitle } from '@/utils/domUtil'
    import { ACCESS_TOKEN } from '@/store/mutation-types'
    
    NProgress.configure({ showSpinner: false }) // NProgress Configuration
    
    const whiteList = ['login', 'register', 'registerResult'] // no redirect whitelist     
    
    router.beforeEach((to, from, next) => {
      NProgress.start() // start progress bar
      //        
      to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
      if (Vue.ls.get(ACCESS_TOKEN)) {
        /* has token    token                  */
        if (to.path === '/user/login') {
          next({ path: '/dashboard/workplace' })
          NProgress.done()
        } else {
        //              ,        ,      
          if (store.getters.roles.length === 0) {
          //  mock          
            store
              .dispatch('GetInfo')
              .then(res => {
                const roles = res.result && res.result.role
                //  src\store\modules\permission.js   GenerateRoutes  ,    
                store.dispatch('GenerateRoutes', { roles }).then(() => {
                  //   roles           
                  //           
                  router.addRoutes(store.getters.addRouters)
                  const redirect = decodeURIComponent(from.query.redirect || to.path)
                  if (to.path === redirect) {
                    // hack     addRoutes    ,set the replace: true so the navigation will not leave a history record
                    next({ ...to, replace: true })
                  } else {
                    //        
                    next({ path: redirect })
                  }
                })
              })
              .catch(() => {
                notification.error({
                  message: '  ',
                  description: '        ,   '
                })
                store.dispatch('Logout').then(() => {
                  next({ path: '/user/login', query: { redirect: to.fullPath } })
                })
              })
          } else {
            next()
          }
        }
      } else {
        if (whiteList.includes(to.name)) {
          //        ,    
          next()
        } else {
          next({ path: '/user/login', query: { redirect: to.fullPath } })
          NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
        }
      }
    })
    
    router.afterEach(() => {
      NProgress.done() // finish progress bar
    })
    
    
  • src\store\modules\permission.jsファイルはルートデータの詳細な処理ロジックであり、srcpermissionに協力する.jsファイル使用、コードは以下の通り:
  • import { asyncRouterMap, constantRouterMap } from '@/config/router.config'
    
    /**
     *              ,           
     *
     * @param permission
     * @param route
     * @returns {boolean}
     */
    function hasPermission (permission, route) {
      if (route.meta && route.meta.permission) {
        let flag = false
        for (let i = 0, len = permission.length; i < len; i++) {
          flag = route.meta.permission.includes(permission[i])
          if (flag) {
            return true
          }
        }
        return false
      }
      return true
    }
    
    /**
     *        ,                
     *
     * @param roles
     * @param route
     * @returns {*}
     */
    // eslint-disable-next-line
    function hasRole(roles, route) {
      if (route.meta && route.meta.roles) {
        return route.meta.roles.includes(roles.id)
      } else {
        return true
      }
    }
    
    function filterAsyncRouter (routerMap, roles) {
      const accessedRouters = routerMap.filter(route => {
        if (hasPermission(roles.permissionList, route)) {
          if (route.children && route.children.length) {
            route.children = filterAsyncRouter(route.children, roles)
          }
          return true
        }
        return false
      })
      return accessedRouters
    }
    
    const permission = {
      state: {
        routers: constantRouterMap,
        addRouters: []
      },
      mutations: {
        SET_ROUTERS: (state, routers) => {
          state.addRouters = routers
          state.routers = constantRouterMap.concat(routers)
        }
      },
      actions: {
        GenerateRoutes ({ commit }, data) {
          return new Promise(resolve => {
            const { roles } = data
            const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
            commit('SET_ROUTERS', accessedRouters)
            resolve()
          })
        }
      }
    }
    
    export default permission
    
    

    ドメイン間リクエストの設定
    vueでconfig.jsファイルでの変更
     //     
      devServer: {
        // development server port 8000
        // port: 8000,
        proxy: {
          '/apis': {
            // target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro',
            target: 'http://192.168.1.73:8092/okcloud/',
            // target: 'http://39.107.78.120:8083/okcloud/',
    
            ws: false,
            changeOrigin: true,  //      
            pathRewrite: {
              '^/apis': ''
            }
          }
        }
    

    axiosブロッキング
    ファイルでjsでの設定
    // request interceptor
    service.interceptors.request.use(config => {
      const token = Vue.ls.get(ACCESS_TOKEN)
      if (token) {
        config.headers['okcloud_token'] = token //            token            
      }
      return config
    }, err)
    
    // response interceptor
    service.interceptors.response.use((response) => {
      if (response.data.code === 10000) {
        notification.warning({
          message: '  ',
          description: response.data.message
        })
      } else {
        return response.data
      }
    }, err)
    

    カスタムロールのメニュー権限
  • srcmain.jsファイルに「//import'./permission'//permission control権限制御」
  • を注記
  • カスタムルーティング権限ファイルsrcrouteGuard.js、コードは以下の
  • import Vue from 'vue'
    import router from './router'
    // import store from './store'
    
    import NProgress from 'nprogress' // progress bar
    import 'nprogress/nprogress.css' // progress bar style
    import { setDocumentTitle, domTitle } from '@/utils/domUtil'
    import { ACCESS_TOKEN } from '@/store/mutation-types'
    
    NProgress.configure({ showSpinner: false }) // NProgress Configuration
    
    router.beforeEach((to, from, next) => {
      NProgress.start() // start progress bar
      to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
      if (to.path === '/user/login' && Vue.ls.get(ACCESS_TOKEN)) {
        next({ path: '/dashboard/workplace' })
        NProgress.done()
      } else if (to.path !== '/user/login' && !Vue.ls.get(ACCESS_TOKEN)) {
        next({ path: '/user/login' })
        NProgress.done()
      } else {
        next()
        NProgress.done()
      }
    })
    
    router.afterEach(() => {
      NProgress.done() // finish progress bar
    })
    
    
  • はmainです.jsにimport'./を導入routeGuard’
  • 対srccomponentsMenumenu.jsファイルによるカスタムメニュー改造
  • srcでjsファイルにstoreをmenuに追加し、actionsでメニューを取得する非同期操作を行い、メニュー情報を取得し、グローバル変数動的取得
  • を行う.
  • srcでvueにおけるグローバル変数の参照
  •     ...mapState({
          //      
          menus: state => state.app.menu
        }),
    

    動的メソッドの参照
    ...mapActions(['setSidebar', 'setMenu']),
    

    ダイナミックメソッドの取得を呼び出す
    this.setMenu()