基于vue-cli4搭建公司统一框架

起始:

  • 公司原来每个项目由各自负责的开发人员搭建,使用的技术、框架、版本和目录都各不相同,不方便后期维护和升级;
  • 公司各个项目基础布局和交互基本相同,统一框架能提高后期开发效率;
  • 提升个人知识底蕴

相关技术及功能

  • 使用 axios 封装接口, 拦截器统一添加请求头和处理错误信息
  • 使用 vue-i18n 处理多语言
  • 使用 sass-resources-loader 全局注入 scss 自定义变量,方便主题切换
  • 使用 vue-router 导航守卫处理页面访问权限
  • 使用 vue 的 .env 配置不同环境的配置文件
  • 使用 js-Cookie 存储 token、lang、theme 等用户配置项
  • 自定义 svg-icon 全局组件,可自由使用 svg 字体图标
  • public/env 静态配置文件,不打包情况下更新配置信息

目录

|- public                        
    |- env
        |- lang                          多语言配置文件夹
            |- en.json
            |- zh.json
    |- favicon.ico
    |- index.html
|- src
    |- api                                   接口
        |- login.js
    |- assets                              项目内静态文件
        |- images
            |- logo.png
    |- components                      
        |- Icon.vue                        字体图标 svg-icon 组件
        |- NavBar.vue                   页头导航栏
    |- i18n
        |- index.js
    |- icons                                 图标 svg 图片位置
        |- svg
            |- arrow-down.svg
        |- index.js
    |- router
        |- index.js
    |- store
        |- modules
            |- app.js
            |- user.js
        |- getters.js
        |- index.js
    |- style
        |- common.scss                 基础样式
        |- element.scss                  element-ui 样式覆盖
        |- index.scss                      
        |- var.scss                           样式定义及主题封装
    |- utils
        |- auth.js                               一些基础公用的方法
        |- components.js                  自定义封装的一些js组件
        |- request.js                          axios 配置
    |- Views.vue                            
        |- Home
            |- index.vue
        |- Login
            |- index.vue
    |- App.vue
    |- main.js
    |- permission.js                           权限控制
|- .env.development                        开发环境env配置
|- .emv.pre                                       预生产
|- .env.production                             生产
|- .env.test                                        测试
|- babel.config.js
|- package.json                              
|- README.md
|- vue.config.js                               

开发

  1. 本文用的是vue-cli4,直接上代码
// 如果还没安装vue-cli的  先安装
npm install -g @vue/cli
// 创建项目 template-web
vue create template-web
// 根据自己需求选择需要的依赖,也可以都不选后面自己加
  1. 项目创建完成后,先让他跑起来吧
// 安装依赖 (一般创建项目的时候已经安装好了)
npm install
// 启动项目
npm run serve
image.png
  1. 脚手架创建项目到这结束了,下面开始对应公司风格的开发了,先思考下我们项目需要哪些功能,需要用到哪些依赖。
依赖

dependencies(项目依赖)

npm install **** -S
* element-ui   组件库,不用问,用的顺手
* js-cookie      cookie 处理用具,方便cookie的增删改查
* vue-i18n       国际化工具
* vue-router     全家桶必备
* vuex              全家桶必备
* lodash           非必须,我们公司项目数据处理比较多,lodash比较方便

devDependencies(开发依赖)

npm install **** -D
* sass-resources-loader   全局 scss 变量
* svg-sprite-loader            svg 图标需求
* node-sass                      项目必备
* sass-loader                     scss必备
* 其他依赖看开发习惯安装
功能
* logo    左侧logo图,点击可以回到首页
* 菜单   菜单导航
* 多语言切换
* 主题切换
* 登录 / 注册 / 用户信息 入口
* 权限菜单过滤(是否登录访问页面不同,或更具用户等级)
* 登录(未登录,已登录)
* 字体图标组件,可引入svg字体图标
* 登出(手动登出和自动登出)
  1. 我们先建一个首页,引入navbar组件,这样我们就能开发框架里的内容了
    App.vue
<template>
  <div id="app">
    <nav-bar></nav-bar>
    <router-view id="page-content"></router-view>
  </div>
</template>
<script>
import NavBar from "./components/NavBar";
export default {
  components: {NavBar}
}
</script>
  1. 先配置初始化多语言。
// /src/ i18n/index.js
import Vue from 'vue'
import I18n from 'vue-i18n'
import {getLang, setLang} from '../utils/auth'

Vue.use(I18n)

let locale = 'zh'
if (getLang()) {
  locale = getLang()
} else {
  setLang(locale)
}

const i18n = new I18n({
  locale: 'zh',
  messages: {
    zh: require(`@public/env/lang/zh.json`),
    en: require(`@public/env/lang/en.json`),
  }
})

export default i18n

// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './i18n'
import ElementUI from 'element-ui'

new Vue({
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount('#app')

多语言的具体配置参考官方文档

* 多语言需要根据用户选择,缓存到本地,这样下次打开的时候能保留原来选中的语言,我这里将当前语言存储到了cookie中,第一次进入时获取本地语言(默认简体中文)

一般语言表json文件也是放在 src/i18n/langs 下的,不过考虑到项目打包发布后,产品运营那边经常需要修改文案,或后台返回的错误编码的增减等。我们将langs 提取到了 public 中,这样修改后不需要重新打包部署,只需替换下对应 json 就好了

// auth.js
import Cookies from 'js-cookie'

const langName = 'lang'

const getLang = () => {
  return Cookies.get(langName)
}

const setLang = (lang) => {
  return Cookies.set(langName, lang)
}
* 多语言 json,每个json文件的字段名应该是一致的,否则可能出现翻译不全的情况
// zh.json
{
  "common": {
    "errorMsg": "报错啦",
    "dark": "暗系",
    "light": "亮系",
    "zh": "中文",
    "en": "英文",
    "login": "登录",
    "registered": "注册"
  },
  "nav": {
    "home": "首页",
    "dataList": "数据统计"
  },
  "user": {
    "info": "用户信息",
    "account": "个人账号",
    "massage": "消息中心",
    "logout": "退出",
    "userInfo": "个人中心"
  },
  "codes": {
    "250011": "产品名称已存在!"
  }
}
* 项目中使用多语言
// template 
<div>{{$t('user.info')}}</div>

// js
this.loginText = this.$t('common.login')

// router 需要先定义语言对应字段,在页面使用的时候使用该字段匹配
// /src/router/index.js
export const navRoutes = [
  {
    path: "/index",
    name: "home",
    meta: {
      title: "nav.home",
    },
    component: () => import("@/views/Home")
  },
  {
    path: "/index1",
    name: "home1",
    meta: {
      title: "nav.dataList",
      login: true
    },
    component: () => import("@/views/Home")
  }
]
// /src/components/NavBar.vue
<router-link v-for="item in navRoutes" :to="{name: item.name}" :key="item.name">{{$t(item.meta.title)}}</router-link>

vue-i18n 和 vue-router、vuex一样的使用方式,可以在js文件中,vue文件中使用,更多操作还是去开官方文档吧

  1. 多语言配置完了当然要打开看看效果啦(样式,elementUI使用这些就不说了):


    image.png

    image.png
  2. 你说切换怎么写的?这个先不急,我们先看把主题的配置也一起完成了。主题的切换有多种:

* 主题色:一般这种都是定义主题的基本色,然后计算出几个不同层次的颜色,切换的时候只需要更改几个基础色就可以了
* elementUI(或其他工具库)的主题,elementUI 多种配置切换方法(依然看官网去)
* 切换项目中大部分色值,没有规律,这个就需要特别定制了(我们就盘它)

为了方便开发,我们这里需要将var.scss中的变量全局化,不需要每个地方单独引入,利用 sass-resources-loader 依赖,简单就能实现了。

// vue.config.js
module.exports = {
  // ...
  chainWebpack: (config) => {
    // ...
    const oneOfsMap = config.module.rule('scss').oneOfs.store
    oneOfsMap.forEach(item => {
      item
        .use('sass-resources-loader')
        .loader('sass-resources-loader')
        .options({
          // 全局变量文件路径,只有一个时可将数组省去
          resources: ['src/style/var.scss']
        })
        .end()
    })
  }
}
  • 预期效果:切换主题后,背景色和字体颜色发生变化,按钮,表单等样式不变(白天模式和夜间模式切换)
  • 主题配色配置和使用逻辑
// /src/style/var.scss
// 基本色
$--color-black: #000000;
$--color-white: #FFFFFF;
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;

// 主题色
$themes: (
  light: (
    bg_color_max: #f0f1f4,
    bg_color_base: #C0C4CC,
    
    font_color_max: #000104,
    font_color_base: #303133,
  ),
  dark: (    
    bg_color_max: #000104,
    bg_color_base: #303133,
    
    font_color_max: #f0f1f4,
    font_color_base: #C0C4CC,
  )
);

// 遍历主题map
@mixin themeify {
  @each $theme-name, $theme-map in $themes {
    // 将map 提升为全局变量
    $theme-map: $theme-map !global;
    // 根据html标签的data-theme属性判断当前使用的theme-name
    // #{} 是sass的插值方法;  & 是sass嵌套里父容器的标识;  @content 是混合器插槽(类似slot)
    [data-theme="#{$theme-name}"] & {
      @content;
    }
  }
};

// 声明一个方法,获取key对应的颜色
@function themed($key) {
  @return map-get($theme-map, $key)
};

// 获取背景色
@mixin bg_color($color) {
  @include themeify() {
    background-color: themed($color) !important;
  }
};
// 获取字体颜色
@mixin f_color($color) {
  @include themeify() {
    color: themed($color) !important;
  }
};
// 获取边框颜色
@mixin bor_color($color) {
  @include themeify() {
    border-color: themed($color) !important;
  }
};

这里需要注意的就是sass语法的使用了,看不懂的可以多读几遍,和js逻辑基本一致

上图片(丑了点,效果还是杠杠的):


image.png
  1. 主题切换和多语言切换都开发完了,这下我们可以来试试怎么使用了,图片也都看到了,直接在 navbar 中实现吧。
    /src/components/NavBar.vue
<template>
  <div id="nav">
    <div class="logo">
      <router-link :to="{path: '/index'}">
        <img :src="logo" alt="">
      </router-link>
    </div>
    <div class="nav-bar">
      <router-link v-for="item in navRoutes" :to="{name: item.name}" :key="item.name">{{$t(item.meta.title)}}</router-link>
    </div>
    <div class="user-setting">
      <div class="user-setting__item">
        <el-dropdown>
          <span class="el-dropdown-link">
            <span>
              {{$t(langs[$store.getters.lang])}}
            </span>
            <svg-icon icon-class="arrow-down"></svg-icon>
          </span>
          <el-dropdown-menu class="dropdown-menu" slot="dropdown">
            <el-dropdown-item v-for="(item, key) in langs" :key="key" @click.native="checkLang(key)">{{$t(item)}}</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
      <div class="user-setting__item">
        <el-dropdown>
          <span class="el-dropdown-link">
            <span>
              {{$t(themes[$store.getters.theme])}}
            </span>
            <svg-icon icon-class="arrow-down"></svg-icon>
          </span>
          <el-dropdown-menu class="dropdown-menu" slot="dropdown">
            <el-dropdown-item v-for="(item, key) in themes" :key="key" @click.native="checkTheme(key)">{{$t(item)}}</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
      <div v-if="$store.getters.userInfo.username" class="user-setting__item">
        <el-dropdown>
          <span class="el-dropdown-link">
            <span>
              {{$t('user.userInfo')}}
            </span>
            <svg-icon icon-class="arrow-down"></svg-icon>
          </span>
          <el-dropdown-menu class="dropdown-menu" slot="dropdown">
            <el-dropdown-item>{{$t('user.info')}}</el-dropdown-item>
            <el-dropdown-item>{{$t('user.account')}}</el-dropdown-item>
            <el-dropdown-item>{{$t('user.massage')}}</el-dropdown-item>
            <el-dropdown-item divided @click.native="logout">{{$t('user.logout')}}</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
      <div v-else class="user-setting__item">
        <router-link :to="{name: 'login'}">{{$t('common.login')}}</router-link>
        <span>/</span>
        <router-link :to="{name: 'login'}">{{$t('common.registered')}}</router-link>
      </div>
    </div>
  </div>
</template>
<script>
import { navRoutes } from "../router";

export default {
  components: {},
  data() {
    return {
      logo: require('../assets/images/logo.png'),
      navRoutes,
      themes: {
        dark: 'common.dark',
        light: 'common.light'
      },
      langs: {
        zh: 'common.zh',
        en: 'common.en'
      }
    }
  },
  created() {
    // 打开项目的时候,需要先读取本地信息,进行一次初始化
    this.checkTheme(this.$store.getters.theme)
    this.checkLang(this.$store.getters.lang)
  },
  methods: {
    checkTheme(theme) {
      this.$store.dispatch('setTheme', theme)
      window.document.documentElement.setAttribute('data-theme', theme)
    },
    checkLang(lang) {   // 多语言切换
      this.$store.dispatch('setLang', lang)
      this.$i18n.locale = lang
    },
    logout() {
      // 定时器模拟远程登出,登出成功后本地登出
      setTimeout(() => {
        this.$store.dispatch('Logout').then(res => {
          this.$router.push({name: 'login'})
        })
      }, 100);
    },
  }
}
</script>

/src/store/modules/app.js

import Cookies from 'js-cookie'
import {getLang, setLang} from '../../utils/auth'

const app = {
  state: {
    theme: Cookies.get('theme') || 'dark',
    lang: getLang() || 'zh'
  },
  mutations: {
    SET_THEME: (state, theme) => {
      state.theme = theme
      Cookies.set('theme', theme)
    },
    SET_LANG: (state, lang) => {
      state.lang = lang
      setLang(lang)
    }
  },
  actions: {
    setTheme({ commit }, theme) {
      commit('SET_THEME', theme)
    },
    setLang({ commit }, lang) {
      commit('SET_LANG', lang)
    },
  }
}

export default app

完美搞定,具体思路:

* 初始化项目的时候需要读取本地用户习惯,初始化语言和主题
* 切换的时候需要修改vuex中的当前语言(因为语言可能涉及到多个地方,比如接口)
* 切换的时候需要同步更新cookie中的语言和主题
  1. 其实很多项目的主题切换时头部和尾部时不变的(哈哈~),这边只是为了展示功能效果,具体的情况还是需要根据公司项目需求。下面我们来看看登录登出功能。
    现在登录大多数已手机校验码 + 行为验证,我们也要考虑如此接入。
    /src/views/Login/index.vue
<template>
  <div class="login-page">
    <div class="content">
      <div class="content__img">
        <img class="content__img_img" :src="imgUrl" alt="">
      </div>
      <div class="content__form-content">
        <div class="content__title text-center">
          欢迎登录
        </div>
        <el-form :model="loginForm">
          <el-form-item label="账号" prop="username">
            <el-input v-model="loginForm.username" placeholder="账号"></el-input>
          </el-form-item>
          <el-form-item label="手机验证" prop="validNumber">
            <el-input v-model="loginForm.validNumber" placeholder="验证码">
              <el-button v-if="!sended" slot="append" @click="openJY" type="text">发送验证码</el-button>
              <span v-if="sended" slot="append">剩余{{count}}S</span>
            </el-input>
          </el-form-item>
        </el-form>
        <div class="content__submit">
          <el-button class="content__submit-btn" type="primary" @click="login">登录</el-button>
        </div>
        <div class="footer-line text-right">
          <el-button type="text" size="mini">忘记密码</el-button>
          <el-button type="text" size="mini">注册账号</el-button>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  components: {},
  data() {
    return {
      imgUrl: require('../../assets/images/login_bg_1.png'),
      sended: false,
      loginForm: {},
      count: 60
    }
  },
  created() {},
  methods: {
    login() {
      // 校验通过——定时器模拟登录接口调用
      setTimeout(() => {
        // 登录成功调用本地存储登录信息
        let data = {
          token: '123456',
          userInfo: {
            username: '13819855293',
            userid: 1,
            name: 'yexiangmin'
          }
        }

        this.$store.dispatch('Login', data).then(res => {
          this.$router.replace('/index')
        }).catch(msg => {
          this.$message.error(msg)
        })
      }, 100);
    },
    openJY() {
      this._confirm('极验校验').then(res => {
        this.sended = true
        this.count = 60
        setInterval(() => {
          this.count--
          if (this.count === 0) {
            this.sended = false
          }
        }, 1000);
      }).catch(e => {})
    },
  }
}
</script>

/src/store/modules/user.js

import { setToken, removeToken, getUserInfo, setUserInfo } from "../../utils/auth";

const user = {
  state: {
    userInfo: getUserInfo() || {}
  },

  mutations: {
    SET_USERINFO(state, userInfo) {
      state.userInfo = userInfo
      setUserInfo(userInfo)
    }
  },

  actions: {
    Login({ commit }, data) {
      return new Promise((resolve, reject) => {
        if (data.token && data.userInfo && data.userInfo.username) {
          commit('SET_USERINFO', data.userInfo)
          setToken(data.token)
          resolve()
        }else {
          reject('登录信息获取失败')
        }
      })
    },
    Logout({ commit }) {
      return new Promise((resolve, reject) => {
        commit('SET_USERINFO', {})
        removeToken()
        resolve()
      })
    }
  }
}

export default user

这里我就偷懒下,就不去模拟接口了,有兴趣可以安装 Mock 来模拟接口调用,关于mockjs这里有一篇文章写的很好:mockjs介绍
具体登录思路:

* 输入账号后,点击发送验证码弹出校验
* 校验成功发送短信验证
* 输入短信登录
* 登录成功保存登录信息(一般登录接口都会返回token,userInfo等信息),我们将这些信息存储到cookie种,userInfo因为项目种可能多个地方用到,可以存储一份到vuex

登出其实也差不多意思,但是这里要分2种情况,一种是手动登出,一种是自动登出。
手动登出好理解,就用用户点退出按钮,我们需要跳转到登录页面;
自动登出则是因为 token 过期,请求了一些需要登录的接口后跳转到登录页;

有些奇怪,为啥是一些需要登录的接口,这里说一下我的考虑,面对用户的项目大部分页面都是可以自由访问的,只有一部分页面时需要登录后才能访问,这里我们就需要进行区分了。

我们可以在路由导航守卫进行拦截
/src/permission.js

import router from "./router";
import { getToken, getUserInfo } from "./utils/auth";

router.beforeEach((to, from, next) => {
  let isLogin = getToken() && getUserInfo().username
  if (isLogin) {
    if (to.name === 'login') {
      next('/index')        
    }else{
      next()
    }
  } else {
    if (to.meta.login === true) {
      next({name: 'login', query: {redirect: to.fullPath}})
    }else{
      next()
    }
  }
})

具体思路:

* 如果能获取登录信息,则不让进入登录页面,其他页面直接进入
* 如果没有登录,需要判断该页面是否需要登录,不需要直接进入,需要的化跳转到登录页面,(to.meta.login:是否需要登录,可以看上面的router/index.js内容)
* 那如果登录信息过期了呢?这里就是自动登出的内容了,也是为什么都请求登出接口了还要调用一次本地登出(Logout)方法来清除本地登录信息的原因之一,还有一个原因看上方代码(自己去理解)

/src/utils/request.js

/**
 * axios 配置
 */

import axios from 'axios'
import { Message } from 'element-ui'
import router from '@/router'
import store from '@/store'
import { getToken, removeToken } from '@/utils/auth'

// create an axios instance 
const service = axios.create({
  baseURL: `${window.env.url}/admin`,
  timeout: 50000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    if (getToken()) {
      config.headers['token'] = getToken()
    }
    return config
  },
  error => {
    Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code === 41001) {
      Message({
        message: '请先登录',
        type: 'error',
        duration: 3 * 1000
      })
      store.dispatch('Logout')
      router.push('/login')
      return
    }
    if (res.code === 200) {
      return res
    } else {
      Message({
        message: res.msg || '操作失败',
        type: 'error',
        duration: 3 * 1000
      })
    }
    return Promise.reject(res.msg || '操作失败')
  }, error => {
    console.log('err' + error)// for debug
    Message({
      message: '数据获取失败,请联系客服',
      type: 'error',
      duration: 3 * 1000
    })
    // router.push('/login')
    return Promise.reject(error)
  })

export default service

好啦,主要的几个功能就到这里了,如果内容有写错的地方欢迎大家来吐槽。后续完善的化继续补充吧,累了累了。

对哦,还是给个仓库地址吧。
码云:https://gitee.com/webxingjie/vue-template
github:https://github.com/sinjie/vue-template-web

结语

其实每个公司都有自己的一套风格,我这只是列举了一个最基本的框架功能,可以根据公司具体的风格更深度的定制框架,通用组件等(外包除外)。

其他

例如:

  1. elementUI 表格组件二次封装


    image.png
<template>
  <div class="w-100 flex-column flex-1 overflow-hidden">
    <el-table
      class="w-100 flex-1"
      :data="data"
      :height="height"
      stripe
      header-cell-class-name="table-header-cell"
      @selection-change="changeSelect"
    >
      <el-table-column
        v-if="hasSelection"
        fixed="left"
        type="selection"
        width="50"
      ></el-table-column>
      <el-table-column
        v-for="(col, index) in columns"
        show-overflow-tooltip
        :label="col.label"
        :key="col.prop + index"
        :width="col.width"
      >
        <template slot-scope="scope">
          <div v-if="col.type == 'link'">
            <el-link type="primary"  @click="$emit('clickBtn', scope.row, {name:col.type,prop:col.prop})">{{ scope.row[col.prop] }}</el-link>
          </div>
          <div v-else-if="col.type == 'switch'">
            <el-switch
              v-model="scope.row[col.prop]"
              active-color="#13ce66"
              inactive-color="#ff4949"
              @change="$emit('clickBtn', scope.row, {name:col.type,prop:col.prop})" 
              :active-value="1"
              :inactive-value="0"
            ></el-switch>
          </div>
          <div v-else-if="col.type == 'switchStatus'">
            <el-switch
              v-model="scope.row[col.prop]"
              active-color="#13ce66"
              inactive-color="#ff4949"
              @change="$emit('clickBtn', scope.row, {name:col.type,prop:col.prop})" 
              :active-value="1"
              :inactive-value="0"
              :disabled="scope.row[col.prop]==1"
            ></el-switch>
          </div>
          <div v-else-if="col.type == 'number'">
            <el-input-number
              v-model="scope.row[col.prop]"
              @change="
                $emit('clickBtn', scope.row, { name: col.type, prop: col.prop })
              "
              :min="1"
              :max="100"
              size="mini"
              :precision="0"
            ></el-input-number>
          </div>
          <div v-else-if="col.type == 'selectData'">
            {{ getSelectItem(col.selectData, scope.row[col.prop]) }}
          </div>
          <div v-else-if="col.type == 'image'">
            <img class="td__image" :src="scope.row[col.prop]" />
          </div>
          <div v-else-if="col.type == 'parameter_val'">
            <p v-for="(value,key) in scope.row[col.prop]" :key="key" >
              <template v-if="key == 'feeType'">
                {{ value == '1' ? '固定项' : '百分比' }}
              </template>
              <template v-else-if="key=='url'">
              </template>
              <template v-else>
                {{ value }}
              </template>
            </p>
          </div>
          <div v-else>{{ scope.row[col.prop] }}</div>
        </template>
      </el-table-column>
      <el-table-column
        v-if="operating && operating.length"
        fixed="right"
        :width="operatingWidth"
        label="操作"
      >
        <template slot-scope="scope">
          <span v-for="btn in operating" :key="btn.name" class="table-operation__btn">
            <el-button
              v-if="btn.show ? btn.show(scope.row) : true"
              :type="btn.type || 'text'"
              :disabled="btn.disabled ? btn.disabled(scope.row) : false"
              @click="$emit('clickBtn', scope.row, btn)"
              >{{ btn.label }}</el-button
            >
          </span>
        </template>
      </el-table-column>
    </el-table>
    <div class="mt-16 overflow-hidden" v-if="hasPagination">
      <el-pagination
        class="pagination"
        @current-change="changePage"
        :current-page.sync="pages.page"
        :total="pages.total || 0"
        :page-size="pages.size || 10"
        layout="total, prev, pager, next, jumper"
        background
      ></el-pagination>
    </div>
  </div>
</template>
<script>
/**
 * @param {Array} data                       表格数据
 * @param {Array} columns                    表格列配置
 *   @param {String} prop   -列字段
 *   @param {String} label  -列表头名
 *   @param {Number} width  -列宽
 *   @param {String} type   -解析类型
 * @param {Boolean} [hasSelection = false]   是否多选
 * @param {Boolean} [hasPagination = true]   是否需要分页
 * @param {Object} pages                     分页对象
 *   @param {Number} page   -当前页
 *   @param {Number} size   -每页条数
 *   @param {Number} total  -总条数
 * @param {Array} operating                  操作列按钮列表
 *   @param {String} label     -按钮名称
 *   @param {String} type      -按钮类型
 *   @param {String} name      -唯一标识
 *   @param {Function} disabled-是否禁用,默认参数当前行对象
 *   @param {Function} show    -是否显示,默认参数当前行对象
 * @param {Number} operatingWidth            操作列宽度
 * @callback clickBtn                        操作按钮点击事件
 *   @param {Object} row   -当前行对象
 *   @param {Object} btn   -当前按钮对象
 * @callback changePage                      翻页事件
 * @callback changeSelect                    多选事件
 */

export default {
  components: {},
  props: {
    height: {
      type: String,
      default: '446'
    },
    data: {
      type: Array,
      required: true,
    },
    columns: {
      type: Array,
      required: true,
    },
    hasSelection: {
      type: Boolean,
      default: false,
    },
    hasPagination: {
      type: Boolean,
      default: true,
    },
    pages: Object,
    operating: Array,
    operatingWidth: {
      default: "140",
    },
  },
  data() {
    return {}
  },
  created() {},
  methods: {
    getSelectItem(data, val) {
      if (Array.isArray(data) && val !== undefined) {
        let item = data.find((one) => one.value === val)
        if (item && item.label) {
          return item.label
        }
        return "-"
      }
      if (data === undefined) {
        console.error(`components/table.vue error: 当type="selectData"时,必须传入选项列表selectData数组,例:
        {
          label: "类型",
          prop: "discountType",
          type: "selectData",
          selectData: [
            {
              label: '全部状态',
              value: ''
            },
            {
              label: '已上线',
              value: 0
            },
            {
              label: '已下线',
              value: 1
            },
            {
              label: '已过期',
              value: 2
            },
          ]
        }`)
      }
      return "-"
    },
    changeSelect(selection) {
      this.$emit("changeSelect", selection)
    },
    changePage(val) {
      this.$emit("changePage", val)
    },
  },
}
</script>
<style lang="scss" scoped>
.pagination {
  float: right;
}
.table-operation__btn {
  .el-button {
    margin-right: 8px;
  }
}
.td__image {
  width: 120px;
  vertical-align: middle;
}
</style>
  1. fileUpload.js 文件上传
import request from "@/utils/request"

export default {
  data() {
    return {
      fileList: [],
      imageUrl: '',
      uploadLoading: false
    }
  },
  methods: {
    handleAvatarSuccess() {},
    handleChange(file, fileList) {
      this.fileList = fileList
      this.uploadLoading = true
    },
    upload(param) {
      if (this.uploadValidate) {
        if (this.uploadValidate()) {
          this.fileUpload(param)
        }
      } else {
        this.fileUpload(param)
      }
      const fileSize = Math.round((param.file.size * 100) / (1024 * 1024)) / 100
      const types = ["png", "jpg", "jpeg", "PNG", "JPEG", "JPG"]
      const fileName = param.file.name

      const isType = types.some((type) => {
        return param.file.type.indexOf(type) >= 0
      })

      if (fileSize > 2) {
        this.$message({
          type: "warning",
          message: "文件大小不可超过2MB !",
        })
        return
      }

      if (fileName.length && fileName.length >= 100) {
        this.$message({
          type: "warning",
          message: "文件名不可超过100个字符 !",
        })
        return
      }
    },
    fileUpload(param) {
      let file
      file = new FormData()
      file.append("file", param.file)
      file.append("fileName",  param.file.name)
      file.append("description", "")
      file.append("tagIds", [])
      // debugger
      request({
        url: 'file/upload',
        method: 'post',
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        data: file
      }).then((res) => {
        this.uploadLoading = false
        if (res.code == 10200) {
          this.imageUrl = res.data
        }
      }).catch(e => {
        this.uploadLoading = false
      })
    },
  },
}
  1. 环境变量配置:
    /.env.development
VUE_APP_VERSION=0.0.1
VUE_APP_BASE_URL=http://localhost:8080

/.env.productment

VUE_APP_VERSION=0.0.1
VUE_APP_BASE_URL=http://10.10.10.10:8080

/pakege.json

...
"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build --mode development",
    "build:prod": "vue-cli-service build --mode productment"
  },
...
  1. 其他还有一些通用配置,具体看个人项目的需要
  • git 提交忽略配置 .gitignore
  • babel.config.js babel配置
  • doker 配置
  • eslint 配置
  • .vscode 编辑器工作区配置
  • README 项目文档
  • 权限控制
    ... 有机会在补充吧
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。