起始:
- 公司原来每个项目由各自负责的开发人员搭建,使用的技术、框架、版本和目录都各不相同,不方便后期维护和升级;
- 公司各个项目基础布局和交互基本相同,统一框架能提高后期开发效率;
- 提升个人知识底蕴
相关技术及功能
- 使用 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
开发
- 本文用的是vue-cli4,直接上代码
// 如果还没安装vue-cli的 先安装
npm install -g @vue/cli
// 创建项目 template-web
vue create template-web
// 根据自己需求选择需要的依赖,也可以都不选后面自己加
- 项目创建完成后,先让他跑起来吧
// 安装依赖 (一般创建项目的时候已经安装好了)
npm install
// 启动项目
npm run serve
- 脚手架创建项目到这结束了,下面开始对应公司风格的开发了,先思考下我们项目需要哪些功能,需要用到哪些依赖。
依赖
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字体图标
* 登出(手动登出和自动登出)
- 我们先建一个首页,引入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>
- 先配置初始化多语言。
// /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文件中使用,更多操作还是去开官方文档吧
-
多语言配置完了当然要打开看看效果啦(样式,elementUI使用这些就不说了):
image.png
image.png 你说切换怎么写的?这个先不急,我们先看把主题的配置也一起完成了。主题的切换有多种:
* 主题色:一般这种都是定义主题的基本色,然后计算出几个不同层次的颜色,切换的时候只需要更改几个基础色就可以了
* 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逻辑基本一致
上图片(丑了点,效果还是杠杠的):
- 主题切换和多语言切换都开发完了,这下我们可以来试试怎么使用了,图片也都看到了,直接在 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中的语言和主题
- 其实很多项目的主题切换时头部和尾部时不变的(哈哈~),这边只是为了展示功能效果,具体的情况还是需要根据公司项目需求。下面我们来看看登录登出功能。
现在登录大多数已手机校验码 + 行为验证,我们也要考虑如此接入。
/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
结语
其实每个公司都有自己的一套风格,我这只是列举了一个最基本的框架功能,可以根据公司具体的风格更深度的定制框架,通用组件等(外包除外)。
其他
例如:
-
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>
- 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
})
},
},
}
- 环境变量配置:
/.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"
},
...
- 其他还有一些通用配置,具体看个人项目的需要
- git 提交忽略配置 .gitignore
- babel.config.js babel配置
- doker 配置
- eslint 配置
- .vscode 编辑器工作区配置
- README 项目文档
- 权限控制
... 有机会在补充吧