excel学习库

excel表格_excel函数公式大全_execl从入门到精通

基于 vue-element-admin 的项目总结

一、项目环境搭建

1.vue-element-admin的了解和介绍

vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。

2.项目模板启动和目录介绍

2.1 git拉取基础项目模板

$ git clone https://github.com/PanJiaChen/vue-admin-template.git shop

2.2 安装项目依赖(定位到项目目录下)

npm install #安装依赖

2.3 启动项目

npm run dev #启动开发模式的服务


项目运行完毕,浏览器会自动打开基础模板的登录页,如上图

2.4 目录结构

├── build                      # 构建相关├── mock                       # 项目mock 模拟数据├── public                     # 静态资源│   │── favicon.ico            # favicon图标│   └── index.html             # html模板├── src                        # 源代码│   ├── api                    # 所有请求│   ├── assets                 # 主题 字体等静态资源│   ├── components             # 全局公用组件│   ├── icons                  # 项目所有 svg icons│   ├── layout                 # 全局 layout│   ├── router                 # 路由│   ├── store                  # 全局 store管理│   ├── styles                 # 全局样式│   ├── utils                  # 全局公用方法│   ├── vendor                 # 公用vendor│   ├── views                  # views 所有页面│   ├── App.vue                # 入口页面│   ├── main.js                # 入口文件 加载组件 初始化等│   └── permission.js          # 权限管理│   └── settings.js          # 配置文件├── tests                      # 测试├── .env.xxx                   # 环境变量配置├── .eslintrc.js               # eslint 配置项├── .babelrc                   # babel-loader 配置├── .travis.yml                # 自动化CI配置├── vue.config.js              # vue-cli 配置├── postcss.config.js          # postcss 配置└── package.json               # package.json

我们在做项目时 其中最关注的就是src目录, 里面是所有的源代码和资源, 至于其他目录, 都是对项目的环境和工具的配置。

3.API模块和请求封装模块介绍

axios的拦截器原理如下

3.1 axios拦截器

const service = axios.create({  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url  // withCredentials: true, // send cookies when cross-domain requests  timeout: 5000 // 超时时间})

3.2 请求拦截器

service.interceptors.request.use(  config => {    if (store.getters.token) {      config.headers['X-Token'] = getToken()    }    return config  },  error => {    return Promise.reject(error)  })

3.3 响应拦截器

service.interceptors.response.use(  response => {    const res = response.data    if (res.code !== 20000) {      Message({        message: res.message || 'Error',        type: 'error',        duration: 5 * 1000      })      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {          confirmButtonText: 'Re-Login',          cancelButtonText: 'Cancel',          type: 'warning'        }).then(() => {          store.dispatch('user/resetToken').then(() => {            location.reload()          })        })      }      return Promise.reject(new Error(res.message || 'Error'))    } else {      return res    }  },  error => {    Message({      message: error.message,      type: 'error',      duration: 5 * 1000    })    return Promise.reject(error)  })

3.4 api模块的单独封装

import request from '@/utils/request'export function login(data) {  return request({    url: '/vue-admin-template/user/login',    method: 'post',    data  })}export function logout() {  return request({    url: '/vue-admin-template/user/logout',    method: 'post'  })}

二、登录模块

1.设置固定的本地访问端口和网站名称

1.1 本地服务端口:在 vue.config.js 中进行设置

vue.config.js 就是vue项目相关的编译,配置,打包,启动服务相关的配置文件,它的核心在于webpack,但是又不同于webpack,相当于改良版的webpack。

在项目下, 我们发现了 .env.development 和 .env.production 两个文件
development => 开发环境
production => 生产环境
当我们运行npm run dev进行开发调试的时候,此时会加载执行 .env.development 文件内容
当我们运行npm run build:prod进行生产环境打包的时候,会加载执行 .env.production 文件内容

1.2 网站名称

网站名称实际在configureWebpack选项中的name选项,通过阅读代码,我们会发现name实际上来源于src目录下的 settings.js 文件。

module.exports = {  title: '小优电商后台管理系统',  sidebarLogo: true}

2.登录页面的基础布局

2.1 设置头部背景

<div class="title-container">  <h3 class="title">    <img src="@/assets/common/login-logo.png" alt="">  </h3></div>

2.2 设置背景图片

.login-container {  background-image: url("https://img0.baidu.com/it/u=3612597965,1770541226&fm=26&fmt=auto&gp=0.jpg");  background-position: center;  background-size: 100% 100%;}

3.登录表单的校验

3.1 用户名和密码的校验

loginRules: {  username: [{ required: true, trigger: 'blur', validator: validateUsername }],  password: [    { required: true, trigger: 'blur' },    { min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }  ]},

3.2 关于修饰符

@keyup.enter属于按键修饰符,如果我们想监听在按回车键的时候触发,可以如下编写

<input v-on:keyup.enter="submit">

4.Vue-Cl.i配置跨域代理

4.1 为什么会出现跨域?

当下,最流行的就是前后分离项目,也就是前端项目和后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域的行为.

4.2 解决开发环境的跨域问题

开发环境的跨域,也就是在vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求,解决跨域问题

proxy: {  '/api/private/v1/': {    target: 'http://127.0.0.1:8888', // 跨域请求的地址    changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域  }}

5.封装单独的登录接口

export function login(data) {  return request({    url: 'login',    method: 'post',    data  })}

6.封装Vuex的登录Action并处理token

6.1 实现store/modules/user.js基本配置

// 状态const state = {}// 修改状态const mutations = {}// 执行异步const actions = {}export default {  namespaced: true,  state,  mutations,  actions}

6.2 设置 token 的共享状态

const state = {  token: null}

6.3 操作 token

在 utils/auth.js 中,基础模板已经为我们提供了获取 token ,设置 token ,删除 token 的方法,可以直接使用

const TokenKey = 'xiaoyou'export function getToken() {  return localStorage.getItem(TokenKey)}export function setToken(token) {  return localStorage.setItem(TokenKey, token)}export function removeToken() {  return localStorage.removeItem(TokenKey)}

6.4 初始化token状态 - store/modules/user.js

import { getToken, setToken, removeToken } from '@/utils/auth'const state = {  token: getToken() // 设置token初始状态   token持久化 => 放到缓存中}

6.5 提供修改token的mutations

const mutations = {  setToken(state, token) {    state.token = token // 设置token  只是修改state的数据  123 =》 1234    setToken(token) // vuex和 缓存数据的同步  },  removeToken(state) {    state.token = null // 删除vuex的token    removeToken() // 先清除 vuex  再清除缓存 vuex和 缓存数据的同步  }}

6.6 封装登录的Action

登录action要做的事情,调用登录接口,成功后设置token到vuex,失败则返回失败

const actions = {  async login(context, data) {    const result = await login(data) // 实际上就是一个promise  result就是执行的结果    if (result.data.success) {      context.commit('setToken', result.data.data)    }  }}

6.7 区分axios在不同环境中的请求基础地址

为什么会有环境变量之分? 如图

可以在 .env.development 和 .env.production 定义变量,变量自动就为当前环境的值
基础模板在以上文件定义了变量VUE_APP_BASE_API,该变量可以作为axios请求的baseURL
在模板中,两个值分别为/dev-api 和 /prod-api

6.8 处理axios的响应拦截器

service.interceptors.response.use(response => {  const { success, message, data } = response.data  if (success) {    return data  } else {    Message.error(message) // 提示错误消息    return Promise.reject(new Error(message))  }}, error => {  Message.error(error.message) // 提示错误信息  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch})

三、主页模块

1.主页的左侧导航样式

左侧导航组件的样式文件 styles/siderbar.scss

1.1 设置左侧导航背景图片

.scrollbar-wrapper {     background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;}

1.2 显示左侧logo图片 src/setttings.js

module.exports = {  title: '小优电商后台管理系统',  fixedHeader: false,  sidebarLogo: true // 显示logo}

1.3 设置头部图片结构 src/layout/components/Sidebar/Logo.vue

<div class="sidebar-logo-container" :class="{ 'collapse':collapse}">  <transition name="sidebarLogoFade">    <router-link key="collapse" class="sidebar-logo-link" to="/">      <img src="@/assets/common/logo.png" class="sidebar-logo ">    </router-link>  </transition></div>

2.设置头部内容的布局和样式

需要把页面设置成如图样式

2.1 头部组件位置 layout/components/Navbar.vue

<div class="app-breadcrumb">   北京小优智慧城市科技有限公司   <span class="breadBtn">V1.0</span></div>

2.2 右侧下拉菜单设置

<div class="right-menu">  <el-dropdown class="avatar-container" trigger="click">    <div class="avatar-wrapper">      <img src="@/assets/common/bigUserHeader.png" class="user-avatar">      <span class="name">管理员</span>      <i class="el-icon-caret-bottom" style="color:#fff" />    </div>    <el-dropdown-menu slot="dropdown" class="user-dropdown">      <router-link to="/">        <el-dropdown-item>          首页        </el-dropdown-item>      </router-link>      <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">        <el-dropdown-item>项目地址</el-dropdown-item>      </a>      <el-dropdown-item divided @click.native="logout">        <span style="display:block;">退出登录</span>      </el-dropdown-item>    </el-dropdown-menu>  </el-dropdown></div>

3. 存储用户信息

3.1 新增变量 userInfo action src/store/modules/user.js

state: {  token: getToken(),  userInfo: {} // 用于存储用户对象信息},

3.2 设置和删除用户资料 mutations

// 设置用户信息setUserInfo(state, userInfo) {  state.userInfo = userInfo},// 删除用户信息removeUserInfo(state) {  state.userInfo = {}}

3.3 建立用户名的映射 src/store/getters.js

const getters = {    token: state => state.user.token,  username: state => state.user.userInfo.username}export default getters

3.4 将头部菜单中的名称换成真实的用户名

<div class="avatar-wrapper">    <img src="@/assets/common/bigUserHeader.png" class="user-avatar" />    <span class="name">{  { username }}</span>    <i class="el-icon-caret-bottom" style="color: #fff" /></div>

4.自定义指令

全局注册自定义指令语法 - 获取焦点指令

Vue.directive('focus', {  inserted: function (el) {    console.log(el.children[0])    el.children[0].focus()  }})

在登录组件中使用此指令

<el-input ref="mobile" v-model="loginForm.username" v-focus placeholder="手机号" name="mobile" type="text" tabindex="1" />

5. 实现登出功能

5.1 登出 action src/store/modules/user.js

logout(context) {  // 删除token  context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的  // 删除用户资料  context.commit('removeUserInfo') // 删除用户信息}

5.2 mutation

removeToken(state) {  state.token = null // 将vuex的数据置空  removeToken() // 同步到缓存},removeUseInfo(state) {  state.userInfo = {}}

5.3 头部菜单调用 action src/layout/components/Navbar.vue

async logout() {  await this.$store.dispatch('user/logout') // 这里不论写不写 await 登出方法都是同步的  this.$router.push(`/login`) // 跳到登录}

6.Token失效的主动介入

6.1 流程图转化代码

src/utils/request.js

const timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key// 获取时间戳export function getTimeStamp() {  return Cookies.get(timeKey)}// 设置时间戳export function setTimeStamp() {  Cookies.set(timeKey, Date.now())}

src/utils/request.js

import axios from 'axios'import store from '@/store'import router from '@/router'import { Message } from 'element-ui'import { getTimeStamp } from '@/utils/auth'const TimeOut = 3600 // 定义超时时间const service = axios.create({// 当执行 npm run dev  => .evn.development => /api => 跨域代理  baseURL: process.env.VUE_APP_BASE_API, // npm  run dev  => /api npm run build =>  /prod-api  timeout: 5000 // 设置超时时间})// 请求拦截器service.interceptors.request.use(config => {  // config 是请求的配置信息  // 注入token  if (store.getters.token) {    // 只有在有token的情况下 才有必要去检查时间戳是否超时    if (IsCheckTimeOut()) {      // 如果它为true表示 过期了      // token没用了 因为超时了      store.dispatch('user/logout') // 登出操作      // 跳转到登录页      router.push('/login')      return Promise.reject(new Error('token超时了'))    }    config.headers['Authorization'] = store.getters.token  }  return config // 必须要返回的}, error => {  return Promise.reject(error)})// 响应拦截器service.interceptors.response.use(response => {  // axios默认加了一层data  const { success, message, data } = response.data  // 要根据success的成功与否决定下面的操作  if (success) {    return data  } else {    // 业务已经错误了 还能进then ? 不能 ! 应该进catch    Message.error(message) // 提示错误消息    return Promise.reject(new Error(message))  }}, error => {  Message.error(error.message) // 提示错误信息  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch})// 超时逻辑  (当前时间  - 缓存中的时间) 是否大于 时间差function IsCheckTimeOut() {  var currentTime = Date.now() // 当前时间戳  var timeStamp = getTimeStamp() // 缓存时间戳  return (currentTime - timeStamp) / 1000 > TimeOut}export default service

6.2 同理,在登录的时候,如果登录成功,设置时间戳

async login(context, data) {  const result = await login(data) // 实际上就是一个promise  result就是执行的结果  context.commit('setToken', result)  setTimeStamp() // 将当前的最新时间写入缓存}

四、路由和页面


因为复杂中台项目的页面众多,不可能把所有的业务都集中在一个文件上进行管理和维护,并且还有最重要的,前端的页面中主要分为两部分,一部分是所有人都可以访问的, 一部分是只有有权限的人才可以访问的,拆分多个模块便于更好的控制

1.新建路由

在 router 目录下新建目录 modules,在此目录中新建各个路由模块
路由模块目录结构

2.设置每个模块的路由规则

// 导出属于用户的路由规则import Layout from '@/layout'export default {  path: '/user', // 路径  name: '', // 给路由规则加一个name  component: Layout, // 组件  // 配置二级路的路由表  children: [{    path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由    name: 'user', // 给路由规则加一个name    component: () => import('@/views/Users'),    // 路由元信息  其实就是存储数据的对象 我们可以在这里放置一些信息    meta: {      title: '用户管理' // meta属性的里面的属性 随意定义    }  }]}

3.静态路由和动态路由临时合并,形成左侧菜单

什么叫临时合并?
动态路由是需要权限进行访问的,但是权限的动态路由访问是很复杂的,我们可以先将 静态路由和动态路由进行合并,不考虑权限问题,后面再解决这个问题
路由主文件 src/router/index.js

// 引入多个模块的规则import Layout from '@/layout'import userRouter from './modules/user'import roleRouter from './modules/role'import rightsRouter from './modules/right'import goodsRouter from './modules/goods'import categoryRouter from './modules/category'import reportsRouter from './modules/report'// 动态路由export const asyncRoutes = [  userRouter, roleRouter, rightsRouter, goodsRouter, categoryRouter, reportsRouter]const createRouter = () => new Router({  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部  routes: [...constantRoutes, ...asyncRoutes]})

五、内容模块

1.过滤器解决时间格式的处理

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

<el-table-column label="入职时间" sortable prop="timeOfEntry">    <template slot-scope="obj">{  {obj.row.timeOfEntry | 过滤器}}</template></el-table-column>

安装 moment

npm i moment

编写过滤器函数

import moment from 'moment'export function formatTime(value) {  return moment(value * 1000).format('YYYY-MM-DD HH:mm:ss')}

在 main.js 中全局注册过滤器

import * as filters from '@/filters'Object.keys(filters).forEach(key => {  Vue.filter(key, filters[key])})

2.新增用户

在 views/user 目录下新建一个弹层组件 src/views/user/components/add-user.vue

<template>  <el-dialog title="新增用户" :visible.sync="dialogVisible" width="50%">    <el-form ref="form" :rules="rules" :model="userForm" label-width="80px">      <el-form-item label="用户名" prop="username">        <el-input v-model="userForm.username" />      </el-form-item>      <el-form-item label="密码" prop="password">        <el-input v-model="userForm.password" />      </el-form-item>      <el-form-item label="手机号" prop="mobile">        <el-input v-model="userForm.mobile" />      </el-form-item>      <el-form-item label="邮箱" prop="email">        <el-input v-model="userForm.email" />      </el-form-item>      <el-form-item label="部门" prop="department_title">        <el-input v-model="userForm.department_title" @focus="getAllDepartment"/>        <el-tree v-if="showTree" v-loading="loading" :data="treeData" :props="{ label: 'department_title' }" @node-click="handleNodeClick"/>      </el-form-item>    </el-form>    <span slot="footer" class="dialog-footer">      <el-button @click="btnCancel">取 消</el-button>      <el-button type="primary" @click="saveUser">确 定</el-button>    </span>  </el-dialog></template><script> import {  getDepartMent } from '@/api/department' import {  tranListToTreeData } from '@/utils' import {  addUser } from '@/api/user' export default {  data() {  return {  treeData: [], // 存储部门的树形数据 showTree: false, // 部门文本框获取焦点时,设置为true,展示部门信息 loading: false, // 显示或隐藏进度 dialogVisible: false, userForm: {  username: '', password: '', email: '', mobile: '', department_id: '', department_title: '' }, rules: {  username: [ {  required: true, trigger: 'blur', message: '用户名不能为空' }, {  min: 6, max: 10, trigger: 'blur', message: '长度在6-10位之间' } ], password: [ {  required: true, trigger: 'blur', message: '密码不能为空' }, {  min: 6, max: 12, trigger: 'blur', message: '长度在6-12位之间' } ], mobile: [ {  required: true, trigger: 'blur', message: '手机号不能为空' }, {  pattern: /^1[3-9]\d{9}$/, trigger: 'blur', message: '手机号格式不正确' } ], email: [ {  required: true, trigger: 'blur', message: '邮箱不能为空' }, {  pattern: /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/,trigger: 'blur', message: '邮箱格式不正确' } ], department_title: [{  required: true, message: '部门不能为空', trigger: 'change' }] } } }, methods: {  onSubmit() {  } } } </script>

父组件中引用,弹出层

import AddUser from './components/add-user'<add-user ref="adduser" />

点击 新增用户 按钮,弹出弹出层

<el-button size="small" type="primary" @click="adduser">新增用户</el-button>

点击按钮展示弹出层的关键,就是设置组件中 el-dialog 组件中的如下属性的值

:visible.sync="dialogVisible"

按钮在父组件,变量 dialogVisible在子组件,如何改变?
可以在子组件中的 props 中新建属性

dialogVisible

然后父组件中为其赋值

<add-user ref="adduser" :dialog-visible="addDialogVisible" />

最后在父组件的 data 中定义变量

addDialogVisible:false

但是上面的解决方案有一个问题:点击对话框右上角的 X ,或者“取消”按钮,或者点击其他区域关闭对话框时,会抛出如下错误

**错误原因:**进行上面的几个操作时,会导致自动修改 props 中的 dialogVisible 变量的值,但这是不允许的
**解决方案:**参考上面的实现,直接在父组件中操作子组件中的 data 变量的值

3.用户导入组件封装

3.1 excel导入功能需要使用npm包 xlsx,所以需要安装xlsx 插件

$ npm i xlsx

3.2 将vue-element-admin提供的导入功能新建一个组件,位置:src/components/UploadExcel

3.3 注册全局的导入excel组件

import CommonTools from './CommonTools'import UploadExcel from './UploadExcel'export default {  install(Vue) {    Vue.component('CommonTools', CommonTools) // 注册工具栏组件    Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件  }}

3.4 新建一个公共的导入页面,挂载路由 src/router/index.js

{  path: '/import',  component: Layout,  hidden: true, // 隐藏在左侧菜单中  children: [{    path: '', // 二级路由path什么都不写 表示二级默认路由    component: () => import('@/views/import')  }]},

3.5 创建import路由组件 src/views/import/index.vue

<template>  <!-- 公共导入组件 -->   <upload-excel :on-success="success" /></template>

3.6 获取导入的excel数据, 导入excel接口

async  success({ header, results }) {  // 如果是导入用户     const userRelations = {       '入职日期': 'create_time',       '手机号': 'mobile',       '用户名': 'username',       '密码': 'password',       '邮箱': 'email',       '部门':'部门'     }     const arr = []     results.forEach(item => {       const userInfo = {}       Object.keys(item).forEach(key => {         userInfo[userRelations[key]] = item[key]       })       arr.push(userInfo)      })     await importUser(arr) // 调用导入接口     this.$router.back()}

六、语言、主题和全屏切换

1.全屏插件的引用

第一步,安装全局插件screenfull

$ npm i screenfull

第二步,封装全屏显示的插件src/components/ScreenFull/index.vue

<template>  <div>    <svg-icon icon-class="fullscreen" style="color:#fff; width: 20px; height: 20px" @click="changeScreen" />  </div></template><script>import ScreenFull from 'screenfull'export default {  methods: {    changeScreen() {      if (!ScreenFull.isEnabled) {        this.$message.warning('此时全屏组件不可用')        return      }      ScreenFull.toggle()    }  }}</script>

第三步,全局注册该组件 src/components/index.js

import ScreenFull from './ScreenFull'Vue.component('ScreenFull', ScreenFull) // 注册全屏组件

第四步,放置于layout/navbar.vue

<screen-full />.right-menu-item {   vertical-align: middle;}

2.动态主题的设置

第一步, 封装颜色选择组件 ThemePicker 代码地址:

<template>  <el-color-picker    v-model="theme"    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"       popper-class="theme-picker-dropdown"  /></template><script>const version = require('element-ui/package.json').version // element-ui version from node_modulesconst ORIGINAL_THEME = '#409EFF' // default colorexport default {  data() {    return {      chalk: '', // content of theme-chalk css      theme: ''    }  },  computed: {    defaultTheme() {      return this.$store.state.settings.theme    }  },  watch: {    defaultTheme: {      handler: function(val, oldVal) {        this.theme = val      },      immediate: true    },    async theme(val) {      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME      if (typeof val !== 'string') return      const themeCluster = this.getThemeCluster(val.replace('#', ''))      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))      console.log(themeCluster, originalCluster)      const $message = this.$message({        message: '  Compiling the theme',        customClass: 'theme-message',        type: 'success',        duration: 0,        iconClass: 'el-icon-loading'      })      const getHandler = (variable, id) => {        return () => {          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)          let styleTag = document.getElementById(id)          if (!styleTag) {            styleTag = document.createElement('style')            styleTag.setAttribute('id', id)            document.head.appendChild(styleTag)          }          styleTag.innerText = newStyle        }      }      if (!this.chalk) {        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`        await this.getCSSString(url, 'chalk')      }      const chalkHandler = getHandler('chalk', 'chalk-style')      chalkHandler()      const styles = [].slice.call(document.querySelectorAll('style'))        .filter(style => {          const text = style.innerText          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)        })      styles.forEach(style => {        const { innerText } = style        if (typeof innerText !== 'string') return        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)      })      this.$emit('change', val)      $message.close()    }  },  methods: {    updateStyle(style, oldCluster, newCluster) {      let newStyle = style      oldCluster.forEach((color, index) => {        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])      })      return newStyle    },    getCSSString(url, variable) {      return new Promise(resolve => {        const xhr = new XMLHttpRequest()        xhr.onreadystatechange = () => {          if (xhr.readyState === 4 && xhr.status === 200) {            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')            resolve()          }        }        xhr.open('GET', url)        xhr.send()      })    },    getThemeCluster(theme) {      const tintColor = (color, tint) => {        let red = parseInt(color.slice(0, 2), 16)        let green = parseInt(color.slice(2, 4), 16)        let blue = parseInt(color.slice(4, 6), 16)        if (tint === 0) { // when primary color is in its rgb space          return [red, green, blue].join(',')        } else {          red += Math.round(tint * (255 - red))          green += Math.round(tint * (255 - green))          blue += Math.round(tint * (255 - blue))          red = red.toString(16)          green = green.toString(16)          blue = blue.toString(16)          return `#${red}${green}${blue}`        }      }      const shadeColor = (color, shade) => {        let red = parseInt(color.slice(0, 2), 16)        let green = parseInt(color.slice(2, 4), 16)        let blue = parseInt(color.slice(4, 6), 16)        red = Math.round((1 - shade) * red)        green = Math.round((1 - shade) * green)        blue = Math.round((1 - shade) * blue)        red = red.toString(16)        green = green.toString(16)        blue = blue.toString(16)        return `#${red}${green}${blue}`      }      const clusters = [theme]      for (let i = 0; i <= 9; i++) {        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))      }      clusters.push(shadeColor(theme, 0.1))      return clusters    }  }}</script><style>.theme-message,.theme-picker-dropdown {  z-index: 99999 !important;}.theme-picker .el-color-picker__trigger {  height: 26px !important;  width: 26px !important;  padding: 2px;}.theme-picker-dropdown .el-color-dropdown__link-btn {  display: none;}.el-color-picker {  height: auto !important;}</style>
import ThemePicker from './ThemePicker'Vue.component('ThemePicker', ThemePicker)

第二步, 放置于layout/navbar.vue

<theme-picker />

3.多语言实现

第一步,我们需要首先国际化的包

$ npm i vue-i18n

第二步,需要单独一个多语言的实例化文件 src/lang/index.js

import customZH from './zh' // 引入自定义中文包import customEN from './en' // 引入自定义英文包Vue.use(VueI18n) // 全局注册国际化包export default new VueI18n({  locale: Cookie.get('language') || 'zh', // 从cookie中获取语言类型 获取不到就是中文  messages: {    en: {      ...elementEN, // 将饿了么的英文语言包引入      ...customEN    },    zh: {      ...elementZH, // 将饿了么的中文语言包引入      ...customZH    }  }})

第三步,在main.js中对挂载 i18n的插件,并设置element为当前的语言

Vue.use(ElementUI, {  i18n: (key, value) => i18n.t(key, value)})new Vue({  el: '#app',  router,  store,  i18n,  render: h => h(App)})

第四步,在左侧菜单应用

<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="$t('route.'+onlyOneChild.name)" />

第五步,封装多语言组件 src/components/lang/index.vue

<template>  <el-dropdown trigger="click" @command="changeLanguage">    <div>      <svg-icon style="color:#fff;font-size:20px" icon-class="language" />    </div>    <el-dropdown-menu slot="dropdown">      <el-dropdown-item command="zh" :disabled="'zh'=== $i18n.locale ">中文</el-dropdown-item>      <el-dropdown-item command="en" :disabled="'en'=== $i18n.locale ">en</el-dropdown-item>    </el-dropdown-menu>  </el-dropdown></template><script>import Cookie from 'js-cookie'export default {  methods: {    changeLanguage(lang) {      Cookie.set('language', lang) // 切换多语言      this.$i18n.locale = lang // 设置给本地的i18n插件      this.$message.success('切换多语言成功')    }  }}</script>

第六步,在Navbar组件中引入

 <lang />

七、打包上线

1.打包之前的路由模式

hash模式 : #后面是路由路径,特点是前端访问,#后面的变化不会经过服务器

history模式:正常的/访问模式,特点是后端访问,任意地址的变化都会访问服务器

改成history模式非常简单,只需要将路由的mode类型改成history即可

const createRouter = () => new Router({  mode: 'history', // require service support  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部  routes: [...constantRoutes] // 改成只有静态路由})

2.webpack排除打包

先找到 vue.config.js, 添加 externalswebpack 不打包 xlsxelement

externals:   {      'vue': 'Vue',      'element-ui': 'ELEMENT',      'xlsx': 'XLSX'   }

3.CDN文件配置

const cdn = {  css: [    // element-ui css    'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 样式表  ],  js: [    'https://unpkg.com/vue/dist/vue.js',     'https://unpkg.com/element-ui/lib/index.js',     'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js', 'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js'  ]}

但是请注意,这时的配置实际上是对开发环境和生产环境都生效的,在开发环境时,没有必要使用CDN,此时我们可以使用环境变量来进行区分

let cdn = { css: [], js: [] }// 通过环境变量 来区分是否使用cdnconst isProd = process.env.NODE_ENV === 'production' // 判断是否是生产环境let externals = {}if (isProd) {  // 如果是生产环境 就排除打包 否则不排除  externals = {    // key(包名) / value(这个值 是 需要在CDN中获取js, 相当于 获取的js中 的该包的全局的对象的名字)    'vue': 'Vue', // 后面的名字不能随便起 应该是 js中的全局对象名    'element-ui': 'ELEMENT', // 都是js中全局定义的    'xlsx': 'XLSX' // 都是js中全局定义的  }  cdn = {    css: [      'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 提前引入elementUI样式    ], // 放置css文件目录    js: [      'https://unpkg.com/vue/dist/vue.js', // vuejs      'https://unpkg.com/element-ui/lib/index.js', // element      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js', // xlsx 相关      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js' // xlsx 相关    ] // 放置js文件目录  }}

4.注入CDN文件到模板

之后通过 html-webpack-plugin注入到 index.html之中:

config.plugin('html').tap(args => {  args[0].cdn = cdn  return args})

找到 public/index.html。通过你配置的CDN Config 依次注入 css 和 js。

<head>  <!-- 引入样式 -->  <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>    <link rel="stylesheet" href="<%=css%>">  <% } %></head><!-- 引入JS --><% for(var js of htmlWebpackPlugin.options.cdn.js) { %>  <script src="ly002/20240412/0ud321umprdbttfk7g1ac789kcsg7o9z8jbllyrz.jpg"></script><% } %>

最后,进行打包

$ npm run build:prod
,https://blog.csdn.net/chuenst/article/details/117065028

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«    2024年12月    »
1
2345678
9101112131415
16171819202122
23242526272829
3031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
    文章归档
      友情链接