1.VueRouter 配置

创建 router 的 js 文件

const frameIn = [
  {
    path: '/head1',
    redirect: { name: 'head1-1' },
    component: layoutHeaderAside,
    children: [
      // 首页
      {
        path: 'page1-1',
        name: 'head1-1',
        meta: {
          auth: true,
          title: '第一页'
        },
        component: _import('demo/page1')
      }
    ]
  },

// 重新组织后导出
export default [...frameIn, ...frameOut, ...errorPage]

实例化 VueRouter 对象

import Vue from 'vue'
import VueRouter from 'vue-router'
// 路由数据
import routes from './routes'
// 导出路由 在 main.js 里使用
const router = new VueRouter({
  routes
})

在 main.js 中注册 router

import router from './router'

new Vue({
  router,
  store,
  i18n,
  render: (h) => h(App),

封装路由守卫

router.beforeEach(async (to, from, next) => {
  // 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
  await store.dispatch('d2admin/page/isLoaded')
  // 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198
  await store.dispatch('d2admin/size/isLoaded')
  // 进度条
  NProgress.start()
  // 关闭搜索面板
  store.commit('d2admin/search/set', false)
  // 验证当前路由所有的匹配中是否需要有登录验证的
  if (to.matched.some((r) => r.meta.auth)) {
    // 这里暂时将cookie里是否存有token作为验证是否登录的条件
    // 请根据自身业务需要修改
    const token = util.cookies.get('token')
    if (token && token !== 'undefined') {
      next()
    } else {
      // 没有登录的时候跳转到登录界面
      // 携带上登陆成功之后需要跳转的页面完整路径
      next()
      /* next({
        name: 'login',
        query: {
          redirect: to.fullPath
        }
      }) */
      // https://github.com/d2-projects/d2-admin/issues/138
      NProgress.done()
    }
  } else {
    // 不需要身份校验 直接通过
    next()
  }
})

router.afterEach((to) => {
  // 进度条
  NProgress.done()
  // 多页控制 打开新的页面
  store.dispatch('d2admin/page/open', to)
  // 更改标题
  util.title(to.meta.title)
})

2.侧边栏配置

menu.js 中配置菜单的 json 数据

export const menuAside = supplementPath([
  { path: '/index', title: '首页', icon: 'home' },
  {
    title: '/head1',
    icon: 'folder-o',
    children: [
      { path: '/page1', title: '页面 1-1' },
      { path: '/page2', title: '页面 1-2' },
      { path: '/page3', title: '页面 1-3' }
    ]
  },

监听路由变化动态生成侧边栏

watch: {
  '$route.matched': {
    handler(val) {
      const menuAsideTem = menuAside.find((item) => {
        return item.title === val[0].path
      })
      let aide = []
      if (menuAsideTem && menuAsideTem.children) {
        aide = menuAsideTem.children
      }
      this.$store.commit('d2admin/menu/asideSet', aide)
    }
  }
}

效果如下
image.png
菜单顶栏也是相同的原理

3.vuex 统一状态管理

state => 基本数据

// 设置文件
import setting from '@/setting.js'

export default {
  namespaced: true,
  state: {
    // 顶栏菜单
    header: [],
    // 侧栏菜单
    aside: [],

官方推荐用计算属性访问属性, 用mapStatus 辅助函数进一步简化写法,因为采用了 module,所以会带上路径 'd2admin/menu',同时需要对 namespace 的支持


import { mapState } from 'vuex'
 
computed: {
  ...mapState('d2admin/menu', [
    'aside',
    'asideCollapse',
    'asideTransition'
  ])
},
// 等效于:
computed: {
  aside() {
    return this.$store.state('d2admin/menu', aside)
},
 
// 'd2admin/menu' 是因为使用了 modules,需要对 namespace 的支持
export default {
  namespaced: true,
  modules
}
 
// 在其它地方使用 state 中的数据
this.aside.map(menu => createMenu.call(this, h, menu)) 

mutations => 提交同步修改

mutations: {
  asideSet (state, menu) {
    // store 赋值
    state.aside = menu
  }
}
// 在别处提交修改
this.$store.commit('d2admin/menu/asideSet', menuAsideTem.children)

modules => 统一管理

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
 
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}
 
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

get => 对数据做加工处理

const getters = {
 getToDo (state) {
 return state.todos.filter(item => item.done === true)
 // filter 迭代过滤器 将每个item的值 item.done == true 挑出来, 返回的是一个数组
 }
}

...mapGetters({
 todosALise: 'getToDo' // getToDo 不是字符串,对应的是getter里面的一个方法名字
                       //取一个别名 todosALise
 })

新建并导出 vuex 对象,方便在全局注册

import Vue from 'vue'
import Vuex from 'vuex'
// 导入 vuex 的 json 文件
import d2admin from './modules/d2admin'
// 注册 vuex
Vue.use(Vuex)
// 新建 vuex 对象 引入 d2admin
export default new Vuex.Store({
  modules: {
    d2admin
  }
})

4.axios

请求封装

function createService () {
  // 创建一个 axios 实例
  const service = axios.create()
  // 请求拦截
  service.interceptors.request.use(
    (config) => config,
    (error) => {
      // 发送失败
      console.log(error)
      return Promise.reject(error)
    }
  )
  // 响应拦截
  service.interceptors.response.use(
    (response) => {
      // dataAxios 是 axios 返回数据中的 data
      const dataAxios = response.data
      // 这个状态码是和后端约定的
      const { code } = dataAxios
      // 根据 code 进行判断
      if (code === undefined) {
        // 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
        return dataAxios
      } else {
        // 有 code 代表这是一个后端接口 可以进行进一步的判断
        switch (code) {
          case 0:
            // [ 示例 ] code === 0 代表没有错误
            return dataAxios.data
          case 'xxx':
            // [ 示例 ] 其它和后台约定的 code
            errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`)
            break
          default:
            // 不是正确的 code
            errorCreate(`${dataAxios.msg}: ${response.config.url}`)
            break
        }
      }
    },
    (error) => {
      const status = get(error, 'response.status')
      /* switch (status) {
        case 400: error.message = '请求错误'; break
        case 401: error.message = '未授权,请登录'; break
        case 403: error.message = '拒绝访问'; break
        case 404: error.message = `请求地址出错: ${error.response.config.url}`; break
        case 408: error.message = '请求超时'; break
        case 500: error.message = '服务器内部错误'; break
        case 501: error.message = '服务未实现'; break
        case 502: error.message = '网关错误'; break
        case 503: error.message = '服务不可用'; break
        case 504: error.message = '网关超时'; break
        case 505: error.message = 'HTTP版本不受支持'; break
        default: break
      } */
      errorLog(error)
      return Promise.reject(error)
    }
  )
  return service
}

function createRequestFunction (service) {
  return function (config) {
    // cookies 中获取 token
    const token = util.cookies.get('token')
    const configDefault = {
      headers: {
        Authorization: token,
        'Content-Type': get(config, 'headers.Content-Type', 'application/json')
      },
      timeout: 3000,
      baseURL: process.env.VUE_APP_API,
      data: {}
    }
    return service(Object.assign(configDefault, config))
  }
}

5.跨域问题

proxy: {
      '/api': {
        target: 'http://192.168.8.11:8080',
        ws: true,
        changeOrigin: true,
        // 重写的代码
        pathRewrite: {
          '^/api/*': ''
        }
      }
    }

6.scss 语法的使用

$ 符号用于声明可被引用的变量

$red: red;
$g: green;

body {
  p {
    color: $red;
    &:hover {
      color: $g;
    }
  }
}

% 用于声明可被继承的变量

// 全局设置一个水平垂直居中的样式
%flex-center-row {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
}
// 在需要的地方继承它
.contain {
  width: 100%;
  height: 100%;
  padding: 15px;
  box-sizing: border-box;
  @extend %flex-center-row;
}

image.png

7. 基于 el-table 进行二次封装

封装 el-table, 通过传 JSON 的方法去维护表格;

<template>
  <div>
    <el-table :data="tableData">
      <template v-for="column in columns">
        <el-table-column :prop="column.prop" :label="column.label" v-if="!column.slotName" :key="column.label">
        </el-table-column>
        <el-table-column :label="column.label" v-if="column.slotName" :key="column.label">
            // 作用域插槽, 拿到行上数据
          <template v-slot="slotProps">
            // 具名插槽, 用于回显自定义的内容
            <slot :name="column.slotName" :scope="slotProps"></slot>
          </template>
        </el-table-column>
      </template>
    </el-table>
  </div>
</template>

<script>
export default {
  props: {
    columns: {
      type: Array
    },
    tableData: {
      type: Array
    }
  },
  data() {
    return {}
  }
}
</script>

在页面中使用

<template>
  <div class="main">
    <h1>MyTable</h1>
    <MyTable :columns="columns" :tableData="tableData">
      <template v-slot:type="slotProps">
        <!-- 自定义模板 -->
        <span v-if="slotProps.scope.row.type === 1">
          <el-tag>singer</el-tag>
        </span>
        <span v-else>
          <el-tag>player</el-tag>
        </span>
        <!-- 自定义格式化函数 -->
        {{formatterType(slotProps.scope.row)}}
      </template>
      <template v-slot:operate="slotProps">
        <el-button @click="handleClick(slotProps.scope.row.date)">编辑</el-button>
      </template>
    </MyTable>
  </div>
</template>

<script>
import MyTable from '@/components/my-table'
import { columns, tableData } from './index'
export default {
  components: { MyTable },
  data() {
    return {
      columns,
      tableData
    }
  },
  methods: {
    handleClick(msg) {
      this.$message.success(msg)
    },
    formatterType(row) {
      if (row.type === 1) {
        return '歌手'
      } else {
        return '玩家'
      }
    }
  }
}
</script>

<style scoped>
.main {
  margin-top: 20px;
  padding: 30px;
  box-sizing: border-box;
  background: #fff;
}
</style>

// index.js
export const columns = [
  { prop: 'date', label: '日期' },
  { prop: 'name', label: '姓名' },
  { label: '类型', slotName: 'type', prop: 'type' },
  { prop: 'address', label: '地址' },
  { label: '操作', slotName: 'operate' }
]
export const tableData = [
  {
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  },
  {
    date: '2016-05-04',
    name: '王er虎',
    address: '上海市普陀区金沙江路 1517 弄'
  },
  {
    date: '2016-05-01',
    name: '王小虎bb',
    address: '上海市普陀区金沙江路 1519 弄',
    type: 1
  },
  {
    date: '2016-05-03',
    name: 'zz',
    address: '上海市普陀区金沙江路 1516 弄'
  }
]

image.png


逃跑计划
8 声望1 粉丝

一个前端


下一篇 »
webpack 配置