├── src ├── style │ ├── public.less │ └── theme.less ├── utils │ ├── validate.js │ ├── websocket.js │ └── util.js ├── assets │ ├── logo.png │ └── meet.jpg ├── components │ ├── index.js │ └── header.vue ├── view │ ├── 404 │ │ └── 404.vue │ ├── about │ │ └── about.vue │ ├── detail │ │ └── detail.vue │ ├── index │ │ └── index.vue │ └── music │ │ └── music.vue ├── modules │ ├── add │ │ └── article │ │ │ └── article.vue │ ├── edit │ │ └── article │ │ │ └── article.vue │ └── detail │ │ └── article │ │ └── article.vue ├── layout │ └── main.vue ├── plugin │ └── directive.js ├── main.js ├── App.vue ├── store │ └── index.js ├── router │ ├── dynamicRouter.js │ ├── index.js │ └── defaultRouter.js ├── mock │ └── mock.js └── http │ └── request.js ├── .env.production ├── public ├── favicon.ico └── index.html ├── .env.development ├── NLwdLAxhwv.txt ├── .env.test ├── babel.config.js ├── .gitignore ├── jsconfig.json ├── entrance.js ├── package.json ├── vue.config.js └── README.md /src/style/public.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/style/theme.less: -------------------------------------------------------------------------------- 1 | // 自定义主题 2 | @pm-base-color:#ddd; -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV = 'production' 2 | 3 | VUE_APP_SSO='http://http://localhost:9080' -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hangjob/vue-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hangjob/vue-admin/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/meet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hangjob/vue-admin/HEAD/src/assets/meet.jpg -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 开发环境 2 | NODE_ENV='development' 3 | 4 | VUE_APP_SSO='http://http://localhost:9080' 5 | -------------------------------------------------------------------------------- /NLwdLAxhwv.txt: -------------------------------------------------------------------------------- 1 | ADAKAMXA82312N31819H2S8342JN4NSD-DSADSAD-XADSAD1231321-AXSDSADADAD--ASX-AS-SX-A-SD-A-DA-D- -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV = 'production' # 如果我们在.env.test文件中把NODE_ENV设置为test的话,那么打包出来的目录结构是有差异的 2 | VUE_APP_MODE = 'test' 3 | VUE_APP_SSO='http://http://localhost:9080' 4 | outputDir = test -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const header = r => require.ensure([], () => r(require('./header.vue')), 'header'); 4 | 5 | Vue.component('PMheader', header); -------------------------------------------------------------------------------- /src/view/404/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/view/about/about.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ], 5 | plugins: [ 6 | '@babel/plugin-proposal-optional-chaining' 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/view/detail/detail.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/components/header.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/modules/add/article/article.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/modules/edit/article/article.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/modules/detail/article/article.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/layout/main.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /src/plugin/directive.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | const has = { 3 | inserted: function (el, binding) { 4 | // 添加指令 传入的 value 5 | if (!binding.value) { 6 | el.parentNode.removeChild(el); 7 | } 8 | } 9 | } 10 | Vue.directive('has',has) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /test 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "allowSyntheticDefaultImports": true, 6 | "baseUrl": "./", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | } 10 | }, 11 | "exclude": [ 12 | "node_modules" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from '../entrance'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | import 'lib-flexible/flexible.js' 6 | import '@/plugin/directive' 7 | // 加载全局组件 8 | import './components'; 9 | 10 | require('./mock/mock'); 11 | 12 | window.vm = new Vue({ 13 | router, 14 | store, 15 | render: h => h(App), 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /entrance.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | // ElementUI 4 | import ElementUI from 'element-ui'; 5 | Vue.use(ElementUI); 6 | 7 | import http from './src/http/request' 8 | 9 | import 'nprogress/nprogress.css' 10 | 11 | import * as utils from './src/utils/util'; 12 | 13 | 14 | Vue.config.productionTip = false 15 | 16 | Vue.prototype.$utils = utils; 17 | Vue.prototype.$http = http; 18 | Vue.prototype.$bus = new Vue(); 19 | 20 | export default Vue; -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | 32 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex' 2 | import Vue from 'vue' 3 | 4 | Vue.use(Vuex); 5 | export default new Vuex.Store({ 6 | state: { 7 | hasRoute: false 8 | }, 9 | getters: { 10 | 11 | }, 12 | mutations: { 13 | hasRoute(state, val) { 14 | state.hasRoute = val; 15 | }, 16 | setSyncTime(state, val){ 17 | this.dispatch('getSyncTime'); // mutations 调用 actions 18 | this.commit('hasRoute',''); // mutations 调用 mutations 19 | } 20 | }, 21 | actions: { 22 | // actions 调用 mutations 23 | // context.commit('hasRoute',res) 24 | getSyncTime(context){ 25 | context.commit('hasRoute','') 26 | } 27 | } 28 | }) -------------------------------------------------------------------------------- /src/view/index/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 29 | -------------------------------------------------------------------------------- /src/router/dynamicRouter.js: -------------------------------------------------------------------------------- 1 | import http from '@/http/request'; 2 | import defaultRouter from './defaultRouter' 3 | import store from '@/store' 4 | 5 | 6 | 7 | 8 | // 重新构建路由对象 9 | const menusMap = function (menu) { 10 | return menu.map(v => { 11 | const { path, name, component } = v 12 | const item = { 13 | path, 14 | name, 15 | component: () => import(`@/${component}`) 16 | } 17 | return item; 18 | }) 19 | } 20 | 21 | 22 | // 获取路由 23 | const addPostRouter = function (to, from, next, selfaddRoutes) { 24 | http.windPost('/mock/menu') 25 | .then(data => { 26 | console.log(data) 27 | defaultRouter[0].children.push(...menusMap(data.data.list)); 28 | selfaddRoutes(defaultRouter); 29 | store.commit('hasRoute', true); 30 | next({ ...to, replace: true }) 31 | }) 32 | } 33 | 34 | export default addPostRouter; -------------------------------------------------------------------------------- /src/mock/mock.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | 3 | // 获取 mock.Random 对象 4 | const Random = Mock.Random 5 | // mock新闻数据,包括新闻标题title、内容content、创建时间createdTime 6 | const produceNewsData = function () { 7 | let newsList = [] 8 | for (let i = 0; i < 3; i++) { 9 | let newNewsObject = {} 10 | if(i === 0){ 11 | newNewsObject.path = '/add/article'; 12 | newNewsObject.name = 'add-article'; 13 | newNewsObject.component = 'modules/add/article/article'; 14 | } 15 | if(i === 1){ 16 | newNewsObject.path = '/detail/article'; 17 | newNewsObject.name = 'detail-article'; 18 | newNewsObject.component = 'modules/detail/article/article' 19 | } 20 | if(i === 2){ 21 | newNewsObject.path = '/edit/article'; 22 | newNewsObject.name = 'edit-article'; 23 | newNewsObject.component = 'modules/edit/article/article' 24 | } 25 | newsList.push(newNewsObject) 26 | } 27 | return newsList; 28 | } 29 | 30 | 31 | Mock.mock('/mock/menu', {data:{list:produceNewsData},code:1,msg:'请求成功'}) -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | <% for (var i in 11 | htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> 12 | 13 | 14 | <% } %> 15 | 16 | 17 | 18 | 22 |
23 | 24 | <% for (var i in 25 | htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> 26 | 27 | <% } %> 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-04-29 21:32:37 4 | * @LastEditTime: 2020-04-29 22:05:40 5 | * @LastEditors: your name 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue-admin\src\router\index.js 8 | */ 9 | import Vue from 'vue'; 10 | import VueRouter from 'vue-router' 11 | Vue.use(VueRouter) 12 | import NProgress from 'nprogress' 13 | 14 | import defaultRouter from './defaultRouter' 15 | import dynamicRouter from './dynamicRouter'; 16 | 17 | import store from '@/store'; 18 | 19 | 20 | 21 | const router = new VueRouter({ 22 | routes: defaultRouter, 23 | mode: 'hash', 24 | scrollBehavior(to, from, savedPosition) { 25 | // keep-alive 返回缓存页面后记录浏览位置 26 | if (savedPosition && to.meta.keepAlive) { 27 | return savedPosition; 28 | } 29 | // 异步滚动操作 30 | return new Promise((resolve, reject) => { 31 | setTimeout(() => { 32 | resolve({ x: 0, y: 0 }) 33 | }, 200) 34 | }) 35 | } 36 | }) 37 | 38 | // 消除路由重复警告 39 | const selfaddRoutes = function(params) { 40 | router.matcher = new VueRouter().matcher; 41 | router.addRoutes(params); 42 | } 43 | 44 | router.beforeEach((to, from, next) => { 45 | const { hasRoute } = store.state; 46 | NProgress.start(); 47 | if (hasRoute) { 48 | next() 49 | } else { 50 | dynamicRouter(to, from, next, selfaddRoutes) 51 | } 52 | }) 53 | 54 | router.afterEach((to,from)=>{ 55 | NProgress.done(); 56 | NProgress.remove(); 57 | }) 58 | 59 | export default router; -------------------------------------------------------------------------------- /src/router/defaultRouter.js: -------------------------------------------------------------------------------- 1 | const main = r => require.ensure([], () => r(require('@/layout/main.vue')), 'main') 2 | const index = r => require.ensure([], () => r(require('@/view/index/index.vue')), 'index') 3 | const about = r => require.ensure([], () => r(require('@/view/about/about.vue')), 'about') 4 | const detail = r => require.ensure([], () => r(require('@/view/detail/detail.vue')), 'detail') 5 | const music = r => require.ensure([], () => r(require('@/view/music/music.vue')), 'music') 6 | const error = r => require.ensure([], () => r(require('@/view/404/404.vue')), 'error'); 7 | const defaultRouter = [ 8 | { 9 | path: "/", 10 | component: main, // 布局页 -- 放置公共组件 11 | redirect: { 12 | name: "index" 13 | }, 14 | children:[ 15 | { 16 | path: '/index', 17 | component: index, 18 | name: 'index', 19 | meta: { 20 | title: 'index' 21 | } 22 | }, 23 | { 24 | path: '/about', 25 | component: about, 26 | name: 'about', 27 | meta: { 28 | title: 'about' 29 | } 30 | }, 31 | { 32 | path: '/detail', 33 | component: detail, 34 | name: 'detail', 35 | meta: { 36 | title: 'detail' 37 | } 38 | }, 39 | { 40 | path: '/music', 41 | component: music, 42 | name: 'music', 43 | meta: { 44 | title: 'music' 45 | } 46 | } 47 | ] 48 | }, 49 | { 50 | path: '*', 51 | component: error, 52 | name: '404', 53 | meta: { 54 | title: '404' 55 | } 56 | } 57 | ] 58 | 59 | export default defaultRouter; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "vue-cli-service build", 7 | "lint": "vue-cli-service lint", 8 | "dev": "vue-cli-service serve", 9 | "test": "vue-cli-service build --mode test", 10 | "publish": "vue-cli-service build && vue-cli-service build --mode test" 11 | }, 12 | "dependencies": { 13 | "@babel/plugin-proposal-optional-chaining": "^7.9.0", 14 | "axios": "^0.19.2", 15 | "core-js": "^3.6.4", 16 | "element-ui": "^2.13.1", 17 | "jquery": "^3.5.0", 18 | "lib-flexible": "^0.3.2", 19 | "lodash": "^4.17.15", 20 | "nprogress": "^0.2.0", 21 | "qs": "^6.9.3", 22 | "vue": "^2.6.11", 23 | "vue-router": "^3.1.6", 24 | "vuex": "^3.3.0" 25 | }, 26 | "devDependencies": { 27 | "@vue/cli-plugin-babel": "~4.3.0", 28 | "@vue/cli-plugin-eslint": "~4.3.0", 29 | "@vue/cli-service": "~4.3.0", 30 | "babel-eslint": "^10.1.0", 31 | "compression-webpack-plugin": "^3.1.0", 32 | "copy-webpack-plugin": "^5.1.1", 33 | "eslint": "^6.7.2", 34 | "eslint-plugin-vue": "^6.2.2", 35 | "image-webpack-loader": "^6.0.0", 36 | "less": "^3.11.1", 37 | "less-loader": "^6.0.0", 38 | "mockjs": "^1.1.0", 39 | "postcss-pxtorem": "^5.1.1", 40 | "style-resources-loader": "^1.3.3", 41 | "uglifyjs-webpack-plugin": "^2.2.0", 42 | "vue-cli-plugin-style-resources-loader": "~0.1.4", 43 | "vue-template-compiler": "^2.6.11", 44 | "webpack": "^4.43.0", 45 | "webpack-bundle-analyzer": "^3.7.0" 46 | }, 47 | "eslintConfig": { 48 | "root": true, 49 | "env": { 50 | "node": true 51 | }, 52 | "extends": [ 53 | "plugin:vue/essential", 54 | "eslint:recommended" 55 | ], 56 | "parserOptions": { 57 | "parser": "babel-eslint" 58 | }, 59 | "rules": {} 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions", 64 | "not dead" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/websocket.js: -------------------------------------------------------------------------------- 1 | // const WSS_URL = `ws://123.207.136.134:9010/ajaxchattest` 2 | 3 | let setIntervalWesocketPush = null 4 | 5 | export default class websocket { 6 | 7 | constructor(prop){ 8 | this.Socket = null; 9 | this.wxurl = prop.wxurl; 10 | this.createSocket(); 11 | } 12 | 13 | createSocket() { 14 | if (!this.Socket) { 15 | console.log('建立websocket连接') 16 | this.Socket = new WebSocket(this.wxurl) 17 | this.onopenWS(); 18 | this.onmessageWS(); 19 | this.onerrorWS(); 20 | this.oncloseWS(); 21 | } else { 22 | console.log('已创建websocket') 23 | } 24 | } 25 | 26 | /**打开WS之后发送心跳 */ 27 | onopenWS() { 28 | this.Socket.onopen = ()=>{ 29 | console.log('发送心跳') 30 | this.sendPing() //发送心跳 31 | } 32 | } 33 | 34 | /**连接失败重连 */ 35 | onerrorWS() { 36 | this.Socket.onerror = ()=>{ 37 | console.log('连接失败发送重连') 38 | clearInterval(setIntervalWesocketPush); 39 | this.Socket.close() 40 | this.createSocket() //重连 41 | } 42 | } 43 | 44 | /**WS数据接收统一处理 */ 45 | onmessageWS(callback) { 46 | this.Socket.onmessage = (res)=>{ 47 | console.log(res); 48 | if(res.data){ 49 | callback && callback(res.data) 50 | }else{ 51 | console.log('服务端返回的错误状态码') 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 发送数据 58 | * readyState = 3 连接已经关闭或者根本没有建立 59 | * readyState = 2 连接正在进行关闭握手,即将关闭 60 | * readyState = 1 连接成功建立,可以进行通信 61 | * readyState = 0 正在建立连接,连接还没有完成 62 | * @param {*发送内容} content 63 | */ 64 | sendWSPush(content) { 65 | if (this.Socket !== null && this.Socket.readyState === 3) { 66 | this.Socket.close() 67 | this.createSocket() //重连 68 | } else if (this.Socket.readyState === 1) { 69 | this.Socket.send(content) 70 | } else if (this.Socket.readyState === 0) { 71 | setTimeout(() => { 72 | this.Socket.send(content) 73 | }, 5000) 74 | } 75 | } 76 | 77 | 78 | /**关闭WS */ 79 | oncloseWS() { 80 | this.Socket.onclose = ()=>{ 81 | clearInterval(setIntervalWesocketPush) 82 | console.log('websocket已断开') 83 | } 84 | } 85 | 86 | 87 | /**发送心跳 */ 88 | sendPing() { 89 | this.Socket.send('ping') 90 | setIntervalWesocketPush = setInterval(() => { 91 | this.Socket.send('ping') 92 | }, 1000*30) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/http/request.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import merge from 'lodash/merge' 3 | import qs from 'qs' 4 | 5 | const succeeCode = 1; // 成功 6 | 7 | 8 | /** 9 | * 实例化 10 | * config是库的默认值,然后是实例的 defaults 属性,最后是请求设置的 config 参数。后者将优先于前者 11 | */ 12 | const http = axios.create({ 13 | timeout: 1000 * 30, 14 | withCredentials: true, // 表示跨域请求时是否需要使用凭证 15 | headers: { 16 | 'X-Requested-With': 'XMLHttpRequest', 17 | 'Content-Type': 'application/json; charset=utf-8' 18 | } 19 | }); 20 | 21 | /** 22 | * 请求拦截 23 | */ 24 | http.interceptors.request.use(function (config) { 25 | return config; 26 | }, function (error) { 27 | return Promise.reject(error); 28 | }); 29 | 30 | 31 | /** 32 | * 状态码判断 具体根据当前后台返回业务来定 33 | * @param {*请求状态码} status 34 | * @param {*错误信息} err 35 | */ 36 | const errorHandle = (status, response) => { 37 | console.log(response) 38 | switch (status) { 39 | case 401: 40 | break; 41 | case 404: 42 | vm.$message({ message: `请求路径不存在 ${response.request.responseURL}`, type: 'warning', duration: 1000 * 10, showClose: true }); 43 | break; 44 | default: 45 | console.log(err); 46 | } 47 | } 48 | /** 49 | * 响应拦截 50 | */ 51 | http.interceptors.response.use(response => { 52 | if (response.status === 200) { 53 | if (response.data.code == succeeCode) { 54 | return Promise.resolve(response); 55 | } else { 56 | vm.$message({ message: response.data.msg, type: 'warning', duration: 1000 * 10, showClose: true }); 57 | return Promise.reject(response) 58 | } 59 | } else { 60 | return Promise.reject(response) 61 | } 62 | }, error => { 63 | const { response } = error; 64 | if (response) { 65 | // 请求已发出,但是不在2xx的范围 66 | errorHandle(response.status, response); 67 | return Promise.reject(response); 68 | } else { 69 | // 处理断网的情况 70 | if (!window.navigator.onLine) { 71 | vm.$message({ message: '你的网络已断开,请检查网络', type: 'warning' }); 72 | } 73 | return Promise.reject(error); 74 | } 75 | }) 76 | 77 | 78 | /** 79 | * 请求地址处理 80 | */ 81 | http.adornUrl = (url) => { 82 | return url; 83 | } 84 | 85 | /** 86 | * get请求参数处理 87 | * params 参数对象 88 | * openDefultParams 是否开启默认参数 89 | */ 90 | http.adornParams = (params = {}, openDefultParams = true) => { 91 | var defaults = { 92 | t: new Date().getTime() 93 | } 94 | return openDefultParams ? merge(defaults, params) : params 95 | } 96 | 97 | 98 | /** 99 | * post请求数据处理 100 | * @param {*} data 数据对象 101 | * @param {*} openDefultdata 是否开启默认数据? 102 | * @param {*} contentType 数据格式 103 | * json: 'application/json; charset=utf-8' 104 | * form: 'application/x-www-form-urlencoded; charset=utf-8' 105 | */ 106 | http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => { 107 | var defaults = { 108 | t: new Date().getTime() 109 | } 110 | data = openDefultdata ? merge(defaults, data) : data 111 | return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data) 112 | } 113 | 114 | 115 | /** 116 | * windPost请求 117 | * @param {String} url [请求地址] 118 | * @param {Object} params [请求携带参数] 119 | */ 120 | http.windPost = function (url, params) { 121 | return new Promise((resolve, reject) => { 122 | http.post(http.adornUrl(url), params) 123 | .then(res => { 124 | resolve(res.data) 125 | }) 126 | .catch(error => { 127 | reject(error) 128 | }) 129 | }) 130 | } 131 | 132 | 133 | /** 134 | * windJsonPost请求 135 | * @param {String} url [请求地址] 136 | * @param {Object} params [请求携带参数] 137 | */ 138 | http.windJsonPost = function (url, params) { 139 | return new Promise((resolve, reject) => { 140 | http.post(http.adornUrl(url), http.adornParams(params)) 141 | .then(res => { 142 | resolve(res.data) 143 | }) 144 | .catch(error => { 145 | reject(error) 146 | }) 147 | }) 148 | } 149 | 150 | 151 | /** 152 | * windGet请求 153 | * @param {String} url [请求地址] 154 | * @param {Object} params [请求携带参数] 155 | */ 156 | http.windGet = function (url, params) { 157 | return new Promise((resolve, reject) => { 158 | http.get(http.adornUrl(url), { params: params }) 159 | .then(res => { 160 | resolve(res.data) 161 | }) 162 | .catch(error => { 163 | reject(error) 164 | }) 165 | }) 166 | } 167 | 168 | /** 169 | * 上传图片 170 | */ 171 | http.upLoadPhoto = function (url, params, callback) { 172 | let config = {} 173 | if (callback !== null) { 174 | config = { 175 | onUploadProgress: function (progressEvent) { 176 | //属性lengthComputable主要表明总共需要完成的工作量和已经完成的工作是否可以被测量 177 | //如果lengthComputable为false,就获取不到progressEvent.total和progressEvent.loaded 178 | callback(progressEvent) 179 | } 180 | } 181 | } 182 | return new Promise((resolve, reject) => { 183 | http.post(http.adornUrl(url), http.adornParams(params), config) 184 | .then(res => { 185 | resolve(res.data) 186 | }) 187 | .catch(error => { 188 | reject(error) 189 | }) 190 | }) 191 | } 192 | export default http; -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') // 去掉注释 3 | const CompressionWebpackPlugin = require('compression-webpack-plugin'); // 开启压缩 4 | const { HashedModuleIdsPlugin } = require('webpack'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | function resolve(dir) { 8 | return path.join(__dirname, dir) 9 | } 10 | 11 | const isProduction = process.env.NODE_ENV === 'production'; 12 | 13 | // cdn预加载使用 14 | const externals = { 15 | 'vue': 'Vue', 16 | 'vue-router': 'VueRouter', 17 | 'vuex': 'Vuex', 18 | 'axios': 'axios', 19 | "element-ui": "ELEMENT" 20 | } 21 | 22 | const cdn = { 23 | // 开发环境 24 | dev: { 25 | css: [ 26 | 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' 27 | ], 28 | js: [] 29 | }, 30 | // 生产环境 31 | build: { 32 | css: [ 33 | 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' 34 | ], 35 | js: [ 36 | 'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js', 37 | 'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js', 38 | 'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js', 39 | 'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js', 40 | 'https://unpkg.com/element-ui/lib/index.js' 41 | ] 42 | } 43 | } 44 | 45 | module.exports = { 46 | lintOnSave: false, // 关闭eslint 47 | productionSourceMap: false, 48 | publicPath: './', 49 | outputDir: process.env.outputDir, // 生成文件的目录名称 50 | chainWebpack: config => { 51 | 52 | config.resolve.alias 53 | .set('@', resolve('src')) 54 | 55 | // 压缩图片 56 | config.module 57 | .rule('images') 58 | .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/) 59 | .use('image-webpack-loader') 60 | .loader('image-webpack-loader') 61 | .options({ bypassOnDebug: true }) 62 | 63 | // webpack 会默认给commonChunk打进chunk-vendors,所以需要对webpack的配置进行delete 64 | config.optimization.delete('splitChunks') 65 | 66 | config.plugin('html').tap(args => { 67 | if (process.env.NODE_ENV === 'production') { 68 | args[0].cdn = cdn.build 69 | } 70 | if (process.env.NODE_ENV === 'development') { 71 | args[0].cdn = cdn.dev 72 | } 73 | return args 74 | }) 75 | 76 | config 77 | .plugin('webpack-bundle-analyzer') 78 | .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) 79 | }, 80 | 81 | configureWebpack: config => { 82 | const plugins = []; 83 | 84 | if (isProduction) { 85 | plugins.push( 86 | new UglifyJsPlugin({ 87 | uglifyOptions: { 88 | output: { 89 | comments: false, // 去掉注释 90 | }, 91 | warnings: false, 92 | compress: { 93 | drop_console: true, 94 | drop_debugger: false, 95 | pure_funcs: ['console.log'] //移除console 96 | } 97 | } 98 | }) 99 | ) 100 | // 服务器也要相应开启gzip 101 | plugins.push( 102 | new CompressionWebpackPlugin({ 103 | algorithm: 'gzip', 104 | test: /\.(js|css)$/, // 匹配文件名 105 | threshold: 10000, // 对超过10k的数据压缩 106 | deleteOriginalAssets: false, // 不删除源文件 107 | minRatio: 0.8 // 压缩比 108 | }) 109 | ) 110 | 111 | // 用于根据模块的相对路径生成 hash 作为模块 id, 一般用于生产环境 112 | plugins.push( 113 | new HashedModuleIdsPlugin() 114 | ) 115 | 116 | // 拷贝文件到指定目录,比如一些网站在根目录需要验证某些文本, from为文件的路径,还有一个to属性是输出的文件夹路径,不写则默认复制到打包后文件的根目录 117 | plugins.push( 118 | new CopyWebpackPlugin([{ from: './NLwdLAxhwv.txt' }]) 119 | ) 120 | 121 | // 开启分离js 122 | config.optimization = { 123 | runtimeChunk: 'single', 124 | splitChunks: { 125 | chunks: 'all', 126 | maxInitialRequests: Infinity, 127 | minSize: 1000 * 60, 128 | cacheGroups: { 129 | vendor: { 130 | test: /[\\/]node_modules[\\/]/, 131 | name(module) { 132 | // 排除node_modules 然后吧 @ 替换为空 ,考虑到服务器的兼容 133 | const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] 134 | return `npm.${packageName.replace('@', '')}` 135 | } 136 | } 137 | } 138 | } 139 | }; 140 | 141 | // 取消webpack警告的性能提示 142 | config.performance = { 143 | hints: 'warning', 144 | //入口起点的最大体积 145 | maxEntrypointSize: 1000 * 500, 146 | //生成文件的最大体积 147 | maxAssetSize: 1000 * 1000, 148 | //只给出 js 文件的性能提示 149 | assetFilter: function(assetFilename) { 150 | return assetFilename.endsWith('.js'); 151 | } 152 | } 153 | 154 | // 打包时npm包转CDN 155 | config.externals = externals; 156 | } 157 | 158 | return { plugins } 159 | }, 160 | 161 | pluginOptions: { 162 | // 配置全局less 163 | 'style-resources-loader': { 164 | preProcessor: 'less', 165 | patterns: [resolve('./src/style/theme.less')] 166 | } 167 | }, 168 | css: { 169 | loaderOptions: { 170 | postcss: { 171 | plugins: [ 172 | require('postcss-pxtorem')({ 173 | rootValue: 75, // 换算的基数 1rem = 75px 这个是根据750px设计稿来的,如果是620 的就写 62 174 | // 忽略转换正则匹配项。插件会转化所有的样式的px。比如引入了三方UI,也会被转化。目前我使用selectorBlackList字段,来过滤 175 | //如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px 。 176 | selectorBlackList: ['weui', 'mu'], // 177 | propList: ['*'], // 需要做转化处理的属性,如`hight`、`width`、`margin`等,`*`表示全部 178 | }) 179 | ] 180 | } 181 | } 182 | }, 183 | devServer: { 184 | open: false, // 自动启动浏览器 185 | host: '0.0.0.0', // localhost 186 | port: 6060, // 端口号 187 | https: false, 188 | hotOnly: false, // 热更新 189 | proxy: { 190 | '^/sso': { 191 | target: process.env.VUE_APP_SSO, // 重写路径 192 | ws: true, //开启WebSocket 193 | secure: false, // 如果是https接口,需要配置这个参数 194 | changeOrigin: true 195 | } 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /src/view/music/music.vue: -------------------------------------------------------------------------------- 1 | 24 | 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # [最新vite3.x配置](https://github.com/hangjob/vue-vite-admin-ts) 3 | 4 | [查看项目](https://github.com/hangjob/vue-vite-admin-ts) 持续更新 5 | 6 | [演示项目](https://hangjob.github.io/vue-vite-admin-ts/dist/lib.html) 7 | 8 | 9 | # 一份完整的vue-cli3项目基础配置项 10 | 11 | ### 网站例子 12 | [vipbic是一个专注前端开发、网址导航、社区讨论综合网站](https://www.vipbic.com/),该网站使用前后端分离,运用vue-cli3本项目配置 13 | 14 | ## 安装依赖 15 | ``` 16 | cnpm install 17 | ``` 18 | 19 | ### 开发模式 20 | ``` 21 | npm run dev 22 | ``` 23 | 24 | ### 打包测试环境 25 | ``` 26 | npm run test 27 | ``` 28 | 29 | ### 测试和生产一起打包 30 | ``` 31 | npm run publish 32 | ``` 33 | 34 | ### 打包生产环境 35 | ``` 36 | npm run build 37 | ``` 38 | 39 | ### 项目配置功能 40 | 1. 配置全局cdn,包含js、css 41 | 2. 开启Gzip压缩,包含文件js、css 42 | 3. 去掉注释、去掉console.log 43 | 4. 压缩图片 44 | 5. 本地代理 45 | 6. 设置别名,vscode也能识别 46 | 7. 配置环境变量开发模式、测试模式、生产模式 47 | 8. 请求路由动态添加 48 | 9. axios配置 49 | 10. 添加mock数据 50 | 11. 配置全局less 51 | 12. 只打包改变的文件 52 | 13. 开启分析打包日志 53 | 14. 拷贝文件 54 | 15. 添加可选链运算符 55 | 16. 配置px转换rem 56 | 57 | ### 附加功能 58 | 1. vue如何刷新当前页面 59 | 2. 封装WebSocket 60 | 3. 增加指令directive 61 | 4. 添加音乐小插件 62 | 63 | ### 目录结构 64 | ```shell 65 | ├── public 静态模板资源文件 66 | ├── src 项目文件 67 | ├──|── assets 静态文件 img 、css 、js 68 | ├──|── components 全局组件 69 | ├──|── http 请求配置 70 | ├──|── layout 布局文件 71 | ├──|── mock 测试数据 72 | ├──|── modules 放置动态是添加路由的页面 73 | ├──|── plugin 插件 74 | ├──|── router 路由 75 | ├──|── store vuex数据管理 76 | ├──|── utils 工具文件 77 | ├──|── view 页面文件 78 | ├──|── App.vue 79 | ├──|── main.js 80 | ├── .env.development 开发模式配置 81 | ├── .env.production 正式发布模式配置 82 | ├── .env.test 测试模式配置 83 | ├── entrance.js 入口文件 84 | ├── vue.config.js config配置文件 85 | ``` 86 | 87 | ### html模板配置cdn 88 | ```html 89 | <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> 90 | 91 | 92 | <% } %> 93 | 94 | <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> 95 | 96 | <% } %> 97 | 98 | ``` 99 | ```js 100 | // cdn预加载使用 101 | const externals = { 102 | 'vue': 'Vue', 103 | 'vue-router': 'VueRouter' 104 | } 105 | const cdn = { 106 | // 开发环境 107 | dev: { 108 | css: [ 109 | 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' 110 | ], 111 | js: [] 112 | }, 113 | // 生产环境 114 | build: { 115 | css: [ 116 | 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' 117 | ], 118 | js: [ 119 | 'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js', 120 | 'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js' 121 | ] 122 | } 123 | } 124 | chainWebpack: config => { 125 | config.plugin('html').tap(args => { 126 | if (process.env.NODE_ENV === 'production') { 127 | args[0].cdn = cdn.build 128 | } 129 | if (process.env.NODE_ENV === 'development') { 130 | args[0].cdn = cdn.dev 131 | } 132 | return args 133 | }) 134 | } 135 | ``` 136 | 137 | ### 开启Gzip压缩,包含文件js、css 138 | ```js 139 | new CompressionWebpackPlugin({ 140 | algorithm: 'gzip', 141 | test: /\.(js|css)$/, // 匹配文件名 142 | threshold: 10000, // 对超过10k的数据压缩 143 | deleteOriginalAssets: false, // 不删除源文件 144 | minRatio: 0.8 // 压缩比 145 | }) 146 | ``` 147 | ### 去掉注释、去掉console.log 148 | 安装`cnpm i uglifyjs-webpack-plugin -D` 149 | ```js 150 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 151 | new UglifyJsPlugin({ 152 | uglifyOptions: { 153 | output: { 154 | comments: false, // 去掉注释 155 | }, 156 | warnings: false, 157 | compress: { 158 | drop_console: true, 159 | drop_debugger: false, 160 | pure_funcs: ['console.log'] //移除console 161 | } 162 | } 163 | }) 164 | ``` 165 | ### 压缩图片 166 | ```js 167 | chainWebpack: config => { 168 | // 压缩图片 169 | config.module 170 | .rule('images') 171 | .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/) 172 | .use('image-webpack-loader') 173 | .loader('image-webpack-loader') 174 | .options({ bypassOnDebug: true }) 175 | } 176 | ``` 177 | ### 本地代理 178 | ```js 179 | devServer: { 180 | open: false, // 自动启动浏览器 181 | host: '0.0.0.0', // localhost 182 | port: 6060, // 端口号 183 | https: false, 184 | hotOnly: false, // 热更新 185 | proxy: { 186 | '^/sso': { 187 | target: process.env.VUE_APP_SSO, // 重写路径 188 | ws: true, //开启WebSocket 189 | secure: false, // 如果是https接口,需要配置这个参数 190 | changeOrigin: true 191 | } 192 | } 193 | } 194 | ``` 195 | 196 | ### 设置vscode 识别别名 197 | 在vscode中插件安装栏搜索 `Path Intellisense` 插件,打开settings.json文件添加 以下代码 "@": "${workspaceRoot}/src",安以下添加 198 | ```json 199 | { 200 | "workbench.iconTheme": "material-icon-theme", 201 | "editor.fontSize": 16, 202 | "editor.detectIndentation": false, 203 | "guides.enabled": false, 204 | "workbench.colorTheme": "Monokai", 205 | "path-intellisense.mappings": { 206 | "@": "${workspaceRoot}/src" 207 | } 208 | } 209 | ``` 210 | 在项目package.json所在同级目录下创建文件jsconfig.json 211 | ```json 212 | { 213 | "compilerOptions": { 214 | "target": "ES6", 215 | "module": "commonjs", 216 | "allowSyntheticDefaultImports": true, 217 | "baseUrl": "./", 218 | "paths": { 219 | "@/*": ["src/*"] 220 | } 221 | }, 222 | "exclude": [ 223 | "node_modules" 224 | ] 225 | } 226 | ``` 227 | 如果还没看懂的客官请移步[在vscode中使用别名@按住ctrl也能跳转对应路径](https://www.vipbic.com/thread.html?id=88) 228 | 229 | ### 配置环境变量开发模式、测试模式、生产模式 230 | 在根目录新建 231 | #### .env.development 232 | ```js 233 | # 开发环境 234 | NODE_ENV='development' 235 | 236 | VUE_APP_SSO='http://http://localhost:9080' 237 | ``` 238 | #### .env.test 239 | ```js 240 | NODE_ENV = 'production' # 如果我们在.env.test文件中把NODE_ENV设置为test的话,那么打包出来的目录结构是有差异的 241 | VUE_APP_MODE = 'test' 242 | VUE_APP_SSO='http://http://localhost:9080' 243 | outputDir = test 244 | ``` 245 | #### .env.production 246 | ```js 247 | NODE_ENV = 'production' 248 | 249 | VUE_APP_SSO='http://http://localhost:9080' 250 | ``` 251 | #### package.json 252 | ```json 253 | "scripts": { 254 | "build": "vue-cli-service build", //生产打包 255 | "lint": "vue-cli-service lint", 256 | "dev": "vue-cli-service serve", // 开发模式 257 | "test": "vue-cli-service build --mode test", // 测试打包 258 | "publish": "vue-cli-service build && vue-cli-service build --mode test" // 测试和生产一起打包 259 | } 260 | ``` 261 | ### 请求路由动态添加 262 | 在`router/index.js`,核心 263 | ```js 264 | router.beforeEach((to, from, next) => { 265 | const { hasRoute } = store.state; // hasRoute设置一个状态,防止重复请求添加路由 266 | if (hasRoute) { 267 | next() 268 | } else { 269 | dynamicRouter(to, from, next, selfaddRoutes) 270 | } 271 | }) 272 | ``` 273 | 如果动态添加路由抛警告,路由重复添加,需要添加 `router.matcher = new VueRouter().matcher` 274 | 275 | ### axios配置 276 | 其中响应拦截 277 | ```js 278 | const succeeCode = 1; // 成功 279 | /** 280 | * 状态码判断 具体根据当前后台返回业务来定 281 | * @param {*请求状态码} status 282 | * @param {*错误信息} err 283 | */ 284 | const errorHandle = (status, err) => { 285 | switch (status) { 286 | case 401: 287 | vm.$message({ message: '你还未登录', type: 'warning' }); 288 | break; 289 | case 404: 290 | vm.$message({ message: '请求路径不存在', type: 'warning' }); 291 | break; 292 | default: 293 | console.log(err); 294 | } 295 | } 296 | /** 297 | * 响应拦截 298 | */ 299 | http.interceptors.response.use(response => { 300 | if (response.status === 200) { 301 | // 你只需改动的是这个 succeeCode ,因为每个项目的后台返回的code码各不相同 302 | if (response.data.code === succeeCode) { 303 | return Promise.resolve(response); 304 | } else { 305 | vm.$message({ message: '警告哦,这是一条警告消息', type: 'warning' }); 306 | return Promise.reject(response) 307 | } 308 | } else { 309 | return Promise.reject(response) 310 | } 311 | }, error => { 312 | const { response } = error; 313 | if (response) { 314 | // 请求已发出,但是不在2xx的范围 315 | errorHandle(response.status, response.data.msg); 316 | return Promise.reject(response); 317 | } else { 318 | // 处理断网的情况 319 | if (!window.navigator.onLine) { 320 | vm.$message({ message: '你的网络已断开,请检查网络', type: 'warning' }); 321 | } 322 | return Promise.reject(error); 323 | } 324 | }) 325 | ``` 326 | 在`http/request.js` 327 | ```js 328 | import http from './src/http/request' 329 | Vue.prototype.$http = http; 330 | // 使用 331 | this.$http.windPost('url','参数') 332 | ``` 333 | 334 | ### 添加mock数据 335 | ```js 336 | const Mock = require('mockjs') 337 | const produceNewsData = [] 338 | Mock.mock('/mock/menu', produceNewsData) 339 | ``` 340 | Mock支持随机数据,具体参看官网列子 341 | http://mockjs.com/examples.html 342 | 343 | ### 配置全局less 344 | ```js 345 | pluginOptions: { 346 | // 配置全局less 347 | 'style-resources-loader': { 348 | preProcessor: 'less', 349 | patterns: [resolve('./src/style/theme.less')] 350 | } 351 | } 352 | ``` 353 | ### 只打包改变的文件 354 | 安装`cnpm i webpack -D` 355 | ```js 356 | const { HashedModuleIdsPlugin } = require('webpack'); 357 | configureWebpack: config => { 358 | const plugins = []; 359 | plugins.push( 360 | new HashedModuleIdsPlugin() 361 | ) 362 | } 363 | ``` 364 | ### 开启分析打包日志 365 | 安装`cnpm i webpack-bundle-analyzer -D` 366 | ```js 367 | chainWebpack: config => { 368 | config 369 | .plugin('webpack-bundle-analyzer') 370 | .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) 371 | } 372 | ``` 373 | 374 | ### 拷贝文件 375 | 安装`npm i copy-webpack-plugin -D` 376 | ```js 377 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 378 | configureWebpack: config => { 379 | const plugins = []; 380 | plugins.push( 381 | new CopyWebpackPlugin([{ from: './NLwdLAxhwv.txt'}]) 382 | ) 383 | } 384 | ``` 385 | from为文件的路径,还有一个to属性是输出的文件夹路径,不写则默认复制到打包后文件的根目录 386 | 387 | ### 可选链运算符 388 | 安装依赖 389 | ```js 390 | cnpm install @babel/plugin-proposal-optional-chaining -S 391 | ``` 392 | 在babel.config.js中 的 plugins中添加 "@babel/plugin-proposal-optional-chaining" 393 | ```js 394 | module.exports = { 395 | presets: [ 396 | '@vue/cli-plugin-babel/preset' 397 | ], 398 | plugins: [ 399 | '@babel/plugin-proposal-optional-chaining' 400 | ] 401 | } 402 | ``` 403 | 测试 404 | ```js 405 | const obj = { 406 | foo: { 407 | bar: { 408 | baz: 42, 409 | fun: () => { 410 | return 666; 411 | } 412 | } 413 | } 414 | }; 415 | let baz = obj?.foo?.bar?.baz; 416 | let fun = obj?.foo?.bar?.fun(); 417 | console.log(baz); // 42 418 | console.log(fun) // 666 419 | ``` 420 | 421 | ### 配置px转换rem 422 | 安装 423 | ```js 424 | cnpm i lib-flexible -S 425 | cnpm i postcss-pxtorem -D 426 | ``` 427 | 入口js 428 | ```js 429 | import 'lib-flexible/flexible.js' 430 | ``` 431 | 如果不需要转rem,注释即可,也不要引入flexible.js 432 | ```js 433 | css: { 434 | loaderOptions: { 435 | postcss: { 436 | plugins: [ 437 | require('postcss-pxtorem')({ 438 | rootValue : 75, // 换算的基数 1rem = 75px 这个是根据750px设计稿来的,如果是620 的就写 62 439 | // 忽略转换正则匹配项。插件会转化所有的样式的px。比如引入了三方UI,也会被转化。目前我使用selectorBlackList字段,来过滤 440 | //如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px 。 441 | selectorBlackList : ['weui','mu'], // 442 | propList : ['*'], // 需要做转化处理的属性,如`hight`、`width`、`margin`等,`*`表示全部 443 | }) 444 | ] 445 | } 446 | } 447 | } 448 | ``` 449 | 450 | 451 | 452 | ### vue如何刷新当前页面 453 | 刷新当前页面适合在只改变了路由的id的页面,比如查看详情页面,当路由id发生时候,并不会去触发当前页面的钩子函数 454 | 查看`App.vue` 455 | ```js 456 | 461 | 485 | ``` 486 | 然后其它任何想刷新自己的路由页面,都可以这样: `this.reload()` 487 | 488 | 489 | ### 封装WebSocket 490 | 具体实例 `utils\websocket.js` 491 | 492 | 493 | ### 增加指令directive 494 | ```js 495 | import Vue from 'vue'; 496 | const has = { 497 | inserted: function (el, binding) { 498 | // 添加指令 传入的 value 499 | if (!binding.value) { 500 | el.parentNode.removeChild(el); 501 | } 502 | } 503 | } 504 | Vue.directive('has',has) 505 | ``` 506 | ```html 507 | 主要按钮1 508 | ``` 509 | 510 | ### 添加音乐小插件 511 | 512 | 具体实例 `src/view/music` 513 | 启动项目访问music路由`xxxx/#/music` 514 | 515 | ![音乐小插件](https://i.loli.net/2020/06/20/ti6SvdxBLG5XRr3.png) 516 | 517 | ### 如有疑问 518 | QQ群:751432041 519 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 邮箱 3 | * @param {*} s 4 | */ 5 | export const isEmail = (s) => { 6 | return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) 7 | } 8 | 9 | /** 10 | * 手机号码 11 | * @param {*} s 12 | */ 13 | export const isMobile = (s) => { 14 | return /^1[0-9]{10}$/.test(s) 15 | } 16 | 17 | /** 18 | * 电话号码 19 | * @param {*} s 20 | */ 21 | export const isPhone = (s) => { 22 | return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s) 23 | } 24 | 25 | /** 26 | * URL地址 27 | * @param {*} s 28 | */ 29 | export const isURL = (s) => { 30 | return /^http[s]?:\/\/.*/.test(s) 31 | } 32 | 33 | /** 34 | * 是否字符串 35 | */ 36 | export const isString = (o) => { 37 | return Object.prototype.toString.call(o).slice(8, -1) === 'String' 38 | } 39 | 40 | /** 41 | * 是否数字 42 | */ 43 | export const isNumber = (o) => { 44 | return Object.prototype.toString.call(o).slice(8, -1) === 'Number' 45 | } 46 | 47 | /** 48 | * 是否boolean 49 | */ 50 | export const isBoolean = (o) => { 51 | return Object.prototype.toString.call(o).slice(8, -1) === 'Boolean' 52 | } 53 | 54 | /** 55 | * 是否函数 56 | */ 57 | export const isFunction = (o) => { 58 | return Object.prototype.toString.call(o).slice(8, -1) === 'Function' 59 | } 60 | 61 | /** 62 | * 是否为null 63 | */ 64 | export const isNull = (o) => { 65 | return Object.prototype.toString.call(o).slice(8, -1) === 'Null' 66 | } 67 | 68 | /** 69 | * 是否undefined 70 | */ 71 | export const isUndefined = (o) => { 72 | return Object.prototype.toString.call(o).slice(8, -1) === 'Undefined' 73 | } 74 | 75 | /** 76 | * 是否对象 77 | */ 78 | export const isObj = (o) => { 79 | return Object.prototype.toString.call(o).slice(8, -1) === 'Object' 80 | } 81 | 82 | /** 83 | * /是否数组 84 | */ 85 | export const isArray = (o) => { 86 | return Object.prototype.toString.call(o).slice(8, -1) === 'Array' 87 | } 88 | 89 | /** 90 | * 是否时间 91 | */ 92 | export const isDate = (o) => { 93 | return Object.prototype.toString.call(o).slice(8, -1) === 'Date' 94 | } 95 | 96 | /** 97 | * 是否正则 98 | */ 99 | export const isRegExp = (o) => { 100 | return Object.prototype.toString.call(o).slice(8, -1) === 'RegExp' 101 | } 102 | 103 | /** 104 | * 是否错误对象 105 | */ 106 | export const isError = (o) => { 107 | return Object.prototype.toString.call(o).slice(8, -1) === 'Error' 108 | } 109 | 110 | /** 111 | * 是否Symbol函数 112 | */ 113 | export const isSymbol = (o) => { 114 | return Object.prototype.toString.call(o).slice(8, -1) === 'Symbol' 115 | } 116 | 117 | /** 118 | * 是否Promise对象 119 | */ 120 | export const isPromise = (o) => { 121 | return Object.prototype.toString.call(o).slice(8, -1) === 'Promise' 122 | } 123 | 124 | /** 125 | * 是否Set对象 126 | */ 127 | export const isSet = (o) => { 128 | return Object.prototype.toString.call(o).slice(8, -1) === 'Set' 129 | } 130 | 131 | export const ua = navigator.userAgent.toLowerCase(); 132 | 133 | /** 134 | * 是否是微信浏览器 135 | */ 136 | export const isWeiXin = () => { 137 | return ua.match(/microMessenger/i) == 'micromessenger' 138 | } 139 | 140 | /** 141 | * 是否是移动端 142 | */ 143 | export const isDeviceMobile = () => { 144 | return /android|webos|iphone|ipod|balckberry/i.test(ua) 145 | } 146 | 147 | /** 148 | * 是否是QQ浏览器 149 | */ 150 | export const isQQBrowser = () => { 151 | return !!ua.match(/mqqbrowser|qzone|qqbrowser|qbwebviewtype/i) 152 | } 153 | 154 | 155 | /** 156 | * 是否是爬虫 157 | */ 158 | export const isSpider = () => { 159 | return /adsbot|googlebot|bingbot|msnbot|yandexbot|baidubot|robot|careerbot|seznambot|bot|baiduspider|jikespider|symantecspider|scannerlwebcrawler|crawler|360spider|sosospider|sogou web sprider|sogou orion spider/.test(ua) 160 | } 161 | 162 | 163 | /** 164 | * 是否ios 165 | */ 166 | export const isIos = () => { 167 | var u = navigator.userAgent; 168 | if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) { //安卓手机 169 | return false 170 | } else if (u.indexOf('iPhone') > -1) {//苹果手机 171 | return true 172 | } else if (u.indexOf('iPad') > -1) {//iPad 173 | return false 174 | } else if (u.indexOf('Windows Phone') > -1) {//winphone手机 175 | return false 176 | } else { 177 | return false 178 | } 179 | } 180 | 181 | /** 182 | * 是否为PC端 183 | */ 184 | export const isPC = () => { 185 | var userAgentInfo = navigator.userAgent; 186 | var Agents = ["Android", "iPhone", 187 | "SymbianOS", "Windows Phone", 188 | "iPad", "iPod"]; 189 | var flag = true; 190 | for (var v = 0; v < Agents.length; v++) { 191 | if (userAgentInfo.indexOf(Agents[v]) > 0) { 192 | flag = false; 193 | break; 194 | } 195 | } 196 | return flag; 197 | } 198 | 199 | 200 | 201 | /** 202 | * 去除html标签 203 | * @param {*} str 204 | */ 205 | export const removeHtmltag = (str) => { 206 | return str.replace(/<[^>]+>/g, '') 207 | } 208 | 209 | /** 210 | * 获取url参数 211 | * @param {*} name 212 | */ 213 | export const getQueryString = (name) => { 214 | const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 215 | const search = window.location.search.split('?')[1] || ''; 216 | const r = search.match(reg) || []; 217 | return r[2]; 218 | } 219 | 220 | 221 | /** 222 | * 动态引入js 223 | * @param {*} src 224 | */ 225 | export const injectScript = (src) => { 226 | const s = document.createElement('script'); 227 | s.type = 'text/javascript'; 228 | s.async = true; 229 | s.src = src; 230 | const t = document.getElementsByTagName('script')[0]; 231 | t.parentNode.insertBefore(s, t); 232 | } 233 | 234 | /** 235 | * 根据url地址下载 236 | * @param {*} url 237 | */ 238 | export const download = (url) => { 239 | var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; 240 | var isSafari = navigator.userAgent.toLowerCase().indexOf('safari') > -1; 241 | if (isChrome || isSafari) { 242 | var link = document.createElement('a'); 243 | link.href = url; 244 | if (link.download !== undefined) { 245 | var fileName = url.substring(url.lastIndexOf('/') + 1, url.length); 246 | link.download = fileName; 247 | } 248 | if (document.createEvent) { 249 | var e = document.createEvent('MouseEvents'); 250 | e.initEvent('click', true, true); 251 | link.dispatchEvent(e); 252 | return true; 253 | } 254 | } 255 | if (url.indexOf('?') === -1) { 256 | url += '?download'; 257 | } 258 | window.open(url, '_self'); 259 | return true; 260 | } 261 | 262 | /** 263 | * el是否包含某个class 264 | * @param {*} el 265 | * @param {*} className 266 | */ 267 | export const hasClass = (el, className) => { 268 | let reg = new RegExp('(^|\\s)' + className + '(\\s|$)') 269 | return reg.test(el.className) 270 | } 271 | 272 | /** 273 | * el添加某个class 274 | * @param {*} el 275 | * @param {*} className 276 | */ 277 | export const addClass = (el, className) => { 278 | if (hasClass(el, className)) { 279 | return 280 | } 281 | let newClass = el.className.split(' ') 282 | newClass.push(className) 283 | el.className = newClass.join(' ') 284 | } 285 | 286 | /** 287 | * el去除某个class 288 | * @param {*} el 289 | * @param {*} className 290 | */ 291 | export const removeClass = (el, className) => { 292 | if (!hasClass(el, className)) { 293 | return 294 | } 295 | let reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g') 296 | el.className = el.className.replace(reg, ' ') 297 | } 298 | 299 | /** 300 | * 获取滚动的坐标 301 | * @param {*} el 302 | */ 303 | export const getScrollPosition = (el = window) => ({ 304 | x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft, 305 | y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop 306 | }); 307 | 308 | /** 309 | * 滚动到顶部 310 | */ 311 | export const scrollToTop = () => { 312 | const c = document.documentElement.scrollTop || document.body.scrollTop; 313 | if (c > 0) { 314 | window.requestAnimationFrame(scrollToTop); 315 | window.scrollTo(0, c - c / 8); 316 | } 317 | } 318 | 319 | /** 320 | * el是否在视口范围内 321 | * @param {*} el 322 | * @param {*} partiallyVisible 323 | */ 324 | export const elementIsVisibleInViewport = (el, partiallyVisible = false) => { 325 | const { top, left, bottom, right } = el.getBoundingClientRect(); 326 | const { innerHeight, innerWidth } = window; 327 | return partiallyVisible 328 | ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && 329 | ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) 330 | : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth; 331 | } 332 | 333 | /** 334 | * 洗牌算法随机 335 | * @param {*} arr 336 | */ 337 | export const shuffle = (arr) => { 338 | var result = [], 339 | random; 340 | while (arr.length > 0) { 341 | random = Math.floor(Math.random() * arr.length); 342 | result.push(arr[random]) 343 | arr.splice(random, 1) 344 | } 345 | return result; 346 | } 347 | 348 | /** 349 | * 劫持粘贴板 350 | * @param {*} value 351 | */ 352 | export const copyTextToClipboard = (value) => { 353 | var textArea = document.createElement("textarea"); 354 | textArea.style.background = 'transparent'; 355 | textArea.value = value; 356 | document.body.appendChild(textArea); 357 | textArea.select(); 358 | try { 359 | var successful = document.execCommand('copy'); 360 | } catch (err) { 361 | console.log('Oops, unable to copy'); 362 | } 363 | document.body.removeChild(textArea); 364 | } 365 | 366 | 367 | /** 368 | * 判断类型集合 369 | * @param {*} str 370 | * @param {*} type 371 | */ 372 | export const checkStr = (str, type) => { 373 | switch (type) { 374 | case 'phone': //手机号码 375 | return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str); 376 | case 'tel': //座机 377 | return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str); 378 | case 'card': //身份证 379 | return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str); 380 | case 'pwd': //密码以字母开头,长度在6~18之间,只能包含字母、数字和下划线 381 | return /^[a-zA-Z]\w{5,17}$/.test(str) 382 | case 'postal': //邮政编码 383 | return /[1-9]\d{5}(?!\d)/.test(str); 384 | case 'QQ': //QQ号 385 | return /^[1-9][0-9]{4,9}$/.test(str); 386 | case 'email': //邮箱 387 | return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); 388 | case 'money': //金额(小数点2位) 389 | return /^\d*(?:\.\d{0,2})?$/.test(str); 390 | case 'URL': //网址 391 | return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(str) 392 | case 'IP': //IP 393 | return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(str); 394 | case 'date': //日期时间 395 | return /^(\d{4})\-(\d{2})\-(\d{2}) (\d{2})(?:\:\d{2}|:(\d{2}):(\d{2}))$/.test(str) || /^(\d{4})\-(\d{2})\-(\d{2})$/.test(str) 396 | case 'number': //数字 397 | return /^[0-9]$/.test(str); 398 | case 'english': //英文 399 | return /^[a-zA-Z]+$/.test(str); 400 | case 'chinese': //中文 401 | return /^[\\u4E00-\\u9FA5]+$/.test(str); 402 | case 'lower': //小写 403 | return /^[a-z]+$/.test(str); 404 | case 'upper': //大写 405 | return /^[A-Z]+$/.test(str); 406 | case 'HTML': //HTML标记 407 | return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str); 408 | default: 409 | return true; 410 | } 411 | } 412 | 413 | /** 414 | * 严格的身份证校验 415 | * @param {*} sId 416 | */ 417 | export const isCardID = (sId) => { 418 | if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(sId)) { 419 | console.log('你输入的身份证长度或格式错误') 420 | return false 421 | } 422 | //身份证城市 423 | var aCity = { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外" }; 424 | if (!aCity[parseInt(sId.substr(0, 2))]) { 425 | console.log('你的身份证地区非法') 426 | return false 427 | } 428 | 429 | // 出生日期验证 430 | var sBirthday = (sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2))).replace(/-/g, "/"), 431 | d = new Date(sBirthday) 432 | if (sBirthday != (d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate())) { 433 | console.log('身份证上的出生日期非法') 434 | return false 435 | } 436 | 437 | // 身份证号码校验 438 | var sum = 0, 439 | weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2], 440 | codes = "10X98765432" 441 | for (var i = 0; i < sId.length - 1; i++) { 442 | sum += sId[i] * weights[i]; 443 | } 444 | var last = codes[sum % 11]; //计算出来的最后一位身份证号码 445 | if (sId[sId.length - 1] != last) { 446 | console.log('你输入的身份证号非法') 447 | return false 448 | } 449 | 450 | return true 451 | } 452 | 453 | 454 | 455 | 456 | /********************************************* 数字转换 ******************************/ 457 | 458 | 459 | /** 460 | * 随机数范围 461 | */ 462 | export const random = (min, max) => { 463 | if (arguments.length === 2) { 464 | return Math.floor(min + Math.random() * ((max + 1) - min)) 465 | } else { 466 | return null; 467 | } 468 | 469 | } 470 | 471 | /** 472 | * 将阿拉伯数字翻译成中文的大写数字 473 | */ 474 | export const numberToChinese = (num) => { 475 | var AA = new Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"); 476 | var BB = new Array("", "十", "百", "仟", "萬", "億", "点", ""); 477 | var a = ("" + num).replace(/(^0*)/g, "").split("."), 478 | k = 0, 479 | re = ""; 480 | for (var i = a[0].length - 1; i >= 0; i--) { 481 | switch (k) { 482 | case 0: 483 | re = BB[7] + re; 484 | break; 485 | case 4: 486 | if (!new RegExp("0{4}//d{" + (a[0].length - i - 1) + "}$") 487 | .test(a[0])) 488 | re = BB[4] + re; 489 | break; 490 | case 8: 491 | re = BB[5] + re; 492 | BB[7] = BB[5]; 493 | k = 0; 494 | break; 495 | } 496 | if (k % 4 == 2 && a[0].charAt(i + 2) != 0 && a[0].charAt(i + 1) == 0) 497 | re = AA[0] + re; 498 | if (a[0].charAt(i) != 0) 499 | re = AA[a[0].charAt(i)] + BB[k % 4] + re; 500 | k++; 501 | } 502 | 503 | if (a.length > 1) // 加上小数部分(如果有小数部分) 504 | { 505 | re += BB[6]; 506 | for (var i = 0; i < a[1].length; i++) 507 | re += AA[a[1].charAt(i)]; 508 | } 509 | if (re == '一十') 510 | re = "十"; 511 | if (re.match(/^一/) && re.length == 3) 512 | re = re.replace("一", ""); 513 | return re; 514 | } 515 | 516 | /** 517 | * 将数字转换为大写金额 518 | */ 519 | export const changeToChinese = (Num) => { 520 | //判断如果传递进来的不是字符的话转换为字符 521 | if (typeof Num == "number") { 522 | Num = new String(Num); 523 | }; 524 | Num = Num.replace(/,/g, "") //替换tomoney()中的“,” 525 | Num = Num.replace(/ /g, "") //替换tomoney()中的空格 526 | Num = Num.replace(/¥/g, "") //替换掉可能出现的¥字符 527 | if (isNaN(Num)) { //验证输入的字符是否为数字 528 | //alert("请检查小写金额是否正确"); 529 | return ""; 530 | }; 531 | //字符处理完毕后开始转换,采用前后两部分分别转换 532 | var part = String(Num).split("."); 533 | var newchar = ""; 534 | //小数点前进行转化 535 | for (var i = part[0].length - 1; i >= 0; i--) { 536 | if (part[0].length > 10) { 537 | return ""; 538 | //若数量超过拾亿单位,提示 539 | } 540 | var tmpnewchar = "" 541 | var perchar = part[0].charAt(i); 542 | switch (perchar) { 543 | case "0": 544 | tmpnewchar = "零" + tmpnewchar; 545 | break; 546 | case "1": 547 | tmpnewchar = "壹" + tmpnewchar; 548 | break; 549 | case "2": 550 | tmpnewchar = "贰" + tmpnewchar; 551 | break; 552 | case "3": 553 | tmpnewchar = "叁" + tmpnewchar; 554 | break; 555 | case "4": 556 | tmpnewchar = "肆" + tmpnewchar; 557 | break; 558 | case "5": 559 | tmpnewchar = "伍" + tmpnewchar; 560 | break; 561 | case "6": 562 | tmpnewchar = "陆" + tmpnewchar; 563 | break; 564 | case "7": 565 | tmpnewchar = "柒" + tmpnewchar; 566 | break; 567 | case "8": 568 | tmpnewchar = "捌" + tmpnewchar; 569 | break; 570 | case "9": 571 | tmpnewchar = "玖" + tmpnewchar; 572 | break; 573 | } 574 | switch (part[0].length - i - 1) { 575 | case 0: 576 | tmpnewchar = tmpnewchar + "元"; 577 | break; 578 | case 1: 579 | if (perchar != 0) tmpnewchar = tmpnewchar + "拾"; 580 | break; 581 | case 2: 582 | if (perchar != 0) tmpnewchar = tmpnewchar + "佰"; 583 | break; 584 | case 3: 585 | if (perchar != 0) tmpnewchar = tmpnewchar + "仟"; 586 | break; 587 | case 4: 588 | tmpnewchar = tmpnewchar + "万"; 589 | break; 590 | case 5: 591 | if (perchar != 0) tmpnewchar = tmpnewchar + "拾"; 592 | break; 593 | case 6: 594 | if (perchar != 0) tmpnewchar = tmpnewchar + "佰"; 595 | break; 596 | case 7: 597 | if (perchar != 0) tmpnewchar = tmpnewchar + "仟"; 598 | break; 599 | case 8: 600 | tmpnewchar = tmpnewchar + "亿"; 601 | break; 602 | case 9: 603 | tmpnewchar = tmpnewchar + "拾"; 604 | break; 605 | } 606 | var newchar = tmpnewchar + newchar; 607 | } 608 | //小数点之后进行转化 609 | if (Num.indexOf(".") != -1) { 610 | if (part[1].length > 2) { 611 | // alert("小数点之后只能保留两位,系统将自动截断"); 612 | part[1] = part[1].substr(0, 2) 613 | } 614 | for (i = 0; i < part[1].length; i++) { 615 | tmpnewchar = "" 616 | perchar = part[1].charAt(i) 617 | switch (perchar) { 618 | case "0": 619 | tmpnewchar = "零" + tmpnewchar; 620 | break; 621 | case "1": 622 | tmpnewchar = "壹" + tmpnewchar; 623 | break; 624 | case "2": 625 | tmpnewchar = "贰" + tmpnewchar; 626 | break; 627 | case "3": 628 | tmpnewchar = "叁" + tmpnewchar; 629 | break; 630 | case "4": 631 | tmpnewchar = "肆" + tmpnewchar; 632 | break; 633 | case "5": 634 | tmpnewchar = "伍" + tmpnewchar; 635 | break; 636 | case "6": 637 | tmpnewchar = "陆" + tmpnewchar; 638 | break; 639 | case "7": 640 | tmpnewchar = "柒" + tmpnewchar; 641 | break; 642 | case "8": 643 | tmpnewchar = "捌" + tmpnewchar; 644 | break; 645 | case "9": 646 | tmpnewchar = "玖" + tmpnewchar; 647 | break; 648 | } 649 | if (i == 0) tmpnewchar = tmpnewchar + "角"; 650 | if (i == 1) tmpnewchar = tmpnewchar + "分"; 651 | newchar = newchar + tmpnewchar; 652 | } 653 | } 654 | //替换所有无用汉字 655 | while (newchar.search("零零") != -1) 656 | newchar = newchar.replace("零零", "零"); 657 | newchar = newchar.replace("零亿", "亿"); 658 | newchar = newchar.replace("亿万", "亿"); 659 | newchar = newchar.replace("零万", "万"); 660 | newchar = newchar.replace("零元", "元"); 661 | newchar = newchar.replace("零角", ""); 662 | newchar = newchar.replace("零分", ""); 663 | if (newchar.charAt(newchar.length - 1) == "元") { 664 | newchar = newchar + "整" 665 | } 666 | return newchar; 667 | } 668 | 669 | 670 | 671 | /********************************************* 关于数组 ******************************/ 672 | 673 | /** 674 | * 判断一个元素是否在数组中 675 | */ 676 | export const contains = (arr, val) => { 677 | return arr.indexOf(val) != -1 ? true : false; 678 | } 679 | 680 | 681 | /** 682 | * @param {arr} 数组 683 | * @param {fn} 回调函数 684 | * @return {undefined} 685 | */ 686 | export const each = (arr, fn) => { 687 | fn = fn || Function; 688 | var a = []; 689 | var args = Array.prototype.slice.call(arguments, 1); 690 | for (var i = 0; i < arr.length; i++) { 691 | var res = fn.apply(arr, [arr[i], i].concat(args)); 692 | if (res != null) a.push(res); 693 | } 694 | } 695 | 696 | /** 697 | * @param {arr} 数组 698 | * @param {fn} 回调函数 699 | * @param {thisObj} this指向 700 | * @return {Array} 701 | */ 702 | export const map = (arr, fn, thisObj) => { 703 | var scope = thisObj || window; 704 | var a = []; 705 | for (var i = 0, j = arr.length; i < j; ++i) { 706 | var res = fn.call(scope, arr[i], i, this); 707 | if (res != null) a.push(res); 708 | } 709 | return a; 710 | } 711 | 712 | 713 | /** 714 | * @param {arr} 数组 715 | * @param {type} 1:从小到大 2:从大到小 3:随机 716 | * @return {Array} 717 | */ 718 | export const sort = (arr, type = 1) => { 719 | return arr.sort((a, b) => { 720 | switch (type) { 721 | case 1: 722 | return a - b; 723 | case 2: 724 | return b - a; 725 | case 3: 726 | return Math.random() - 0.5; 727 | default: 728 | return arr; 729 | } 730 | }) 731 | } 732 | 733 | /** 734 | * 去重 735 | */ 736 | export const unique = (arr) => { 737 | if (Array.hasOwnProperty('from')) { 738 | return Array.from(new Set(arr)); 739 | } else { 740 | var n = {}, r = []; 741 | for (var i = 0; i < arr.length; i++) { 742 | if (!n[arr[i]]) { 743 | n[arr[i]] = true; 744 | r.push(arr[i]); 745 | } 746 | } 747 | return r; 748 | } 749 | } 750 | 751 | 752 | /** 753 | * 求两个集合的并集 754 | */ 755 | export const union = (a, b) => { 756 | var newArr = a.concat(b); 757 | return this.unique(newArr); 758 | } 759 | 760 | /** 761 | * 求两个集合的交集 762 | */ 763 | export const intersect = (a, b) => { 764 | var _this = this; 765 | a = this.unique(a); 766 | return this.map(a, function (o) { 767 | return _this.contains(b, o) ? o : null; 768 | }); 769 | } 770 | 771 | /** 772 | * 删除其中一个元素 773 | */ 774 | export const remove = (arr, ele) => { 775 | var index = arr.indexOf(ele); 776 | if (index > -1) { 777 | arr.splice(index, 1); 778 | } 779 | return arr; 780 | } 781 | 782 | /** 783 | * 将类数组转换为数组的方法 784 | */ 785 | export const formArray = (ary) => { 786 | var arr = []; 787 | if (Array.isArray(ary)) { 788 | arr = ary; 789 | } else { 790 | arr = Array.prototype.slice.call(ary); 791 | }; 792 | return arr; 793 | } 794 | 795 | /** 796 | * 最大值 797 | */ 798 | export const max = (arr) => { 799 | return Math.max.apply(null, arr); 800 | } 801 | 802 | /** 803 | * 最小值 804 | */ 805 | export const min = (arr) => { 806 | return Math.min.apply(null, arr); 807 | } 808 | 809 | /** 810 | * 求和 811 | */ 812 | export const sum = (arr) => { 813 | return arr.reduce((pre, cur) => { 814 | return pre + cur 815 | }) 816 | } 817 | 818 | /** 819 | * 平均值 820 | */ 821 | export const average = (arr) => { 822 | return this.sum(arr) / arr.length 823 | } 824 | 825 | 826 | 827 | /********************************************* String 字符串操作 ******************************/ 828 | 829 | 830 | /** 831 | * 去除空格 832 | * @param {str} 833 | * @param {type} 834 | * type: 1-所有空格 2-前后空格 3-前空格 4-后空格 835 | * @return {String} 836 | */ 837 | export const trim = (str, type) => { 838 | type = type || 1 839 | switch (type) { 840 | case 1: 841 | return str.replace(/\s+/g, ""); 842 | case 2: 843 | return str.replace(/(^\s*)|(\s*$)/g, ""); 844 | case 3: 845 | return str.replace(/(^\s*)/g, ""); 846 | case 4: 847 | return str.replace(/(\s*$)/g, ""); 848 | default: 849 | return str; 850 | } 851 | } 852 | 853 | /** 854 | * @param {str} 855 | * @param {type} 856 | * type: 1:首字母大写 2:首字母小写 3:大小写转换 4:全部大写 5:全部小写 857 | * @return {String} 858 | */ 859 | export const changeCase = (str, type) => { 860 | type = type || 4 861 | switch (type) { 862 | case 1: 863 | return str.replace(/\b\w+\b/g, function (word) { 864 | return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase(); 865 | 866 | }); 867 | case 2: 868 | return str.replace(/\b\w+\b/g, function (word) { 869 | return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase(); 870 | }); 871 | case 3: 872 | return str.split('').map(function (word) { 873 | if (/[a-z]/.test(word)) { 874 | return word.toUpperCase(); 875 | } else { 876 | return word.toLowerCase() 877 | } 878 | }).join('') 879 | case 4: 880 | return str.toUpperCase(); 881 | case 5: 882 | return str.toLowerCase(); 883 | default: 884 | return str; 885 | } 886 | } 887 | 888 | 889 | /* 890 | * 检测密码强度 891 | */ 892 | export const checkPwd = (str) => { 893 | var Lv = 0; 894 | if (str.length < 6) { 895 | return Lv 896 | } 897 | if (/[0-9]/.test(str)) { 898 | Lv++ 899 | } 900 | if (/[a-z]/.test(str)) { 901 | Lv++ 902 | } 903 | if (/[A-Z]/.test(str)) { 904 | Lv++ 905 | } 906 | if (/[\.|-|_]/.test(str)) { 907 | Lv++ 908 | } 909 | return Lv; 910 | } 911 | 912 | /** 913 | * 函数节流器 914 | * @param {Function} fn 需要执行性的函数 915 | * @param {number} time 时间戳 916 | * @param {number} interval 间隔时间 917 | */ 918 | export const debouncer = (fn, time, interval = 200) => { 919 | if (time - (window.debounceTimestamp || 0) > interval) { 920 | fn && fn(); 921 | window.debounceTimestamp = time; 922 | } 923 | } 924 | 925 | /** 926 | * 在字符串中插入新字符串 927 | * @param {string} soure 源字符 928 | * @param {string} index 插入字符的位置 929 | * @param {string} newStr 需要插入的字符 930 | * @returns {string} 返回新生成的字符 931 | */ 932 | export const insertStr = (soure, index, newStr) => { 933 | var str = soure.slice(0, index) + newStr + soure.slice(index); 934 | return str; 935 | } 936 | 937 | /** 938 | * 判断两个对象是否键值相同 939 | * @param {Object} a 第一个对象 940 | * @param {Object} b 第一个对象 941 | * @return {Boolean} 相同返回true,否则返回false 942 | */ 943 | export const isObjectEqual = (a, b) => { 944 | var aProps = Object.getOwnPropertyNames(a); 945 | var bProps = Object.getOwnPropertyNames(b); 946 | 947 | if (aProps.length !== bProps.length) { 948 | return false; 949 | } 950 | 951 | for (var i = 0; i < aProps.length; i++) { 952 | var propName = aProps[i]; 953 | 954 | if (a[propName] !== b[propName]) { 955 | return false; 956 | } 957 | } 958 | return true; 959 | } 960 | 961 | /** 962 | * 16进制颜色转RGB\RGBA字符串 963 | * @param {String} val 16进制颜色值 964 | * @param {Number} opa 不透明度,取值0~1 965 | * @return {String} 转换后的RGB或RGBA颜色值 966 | */ 967 | export const colorToRGB = (val, opa) => { 968 | 969 | var pattern = /^(#?)[a-fA-F0-9]{6}$/; //16进制颜色值校验规则 970 | var isOpa = typeof opa == 'number'; //判断是否有设置不透明度 971 | 972 | if (!pattern.test(val)) { //如果值不符合规则返回空字符 973 | return ''; 974 | } 975 | 976 | var v = val.replace(/#/, ''); //如果有#号先去除#号 977 | var rgbArr = []; 978 | var rgbStr = ''; 979 | 980 | for (var i = 0; i < 3; i++) { 981 | var item = v.substring(i * 2, i * 2 + 2); 982 | var num = parseInt(item, 16); 983 | rgbArr.push(num); 984 | } 985 | 986 | rgbStr = rgbArr.join(); 987 | rgbStr = 'rgb' + (isOpa ? 'a' : '') + '(' + rgbStr + (isOpa ? ',' + opa : '') + ')'; 988 | return rgbStr; 989 | } 990 | 991 | /** 992 | * 追加url参数 993 | * @param {string} url url参数 994 | * @param {string|object} key 名字或者对象 995 | * @param {string} value 值 996 | * @return {string} 返回新的url 997 | * @example 998 | * appendQuery('lechebang.com', 'id', 3); 999 | * appendQuery('lechebang.com?key=value', { cityId: 2, cityName: '北京'}); 1000 | */ 1001 | export const appendQuery = (url, key, value) => { 1002 | var options = key; 1003 | if (typeof options == 'string') { 1004 | options = {}; 1005 | options[key] = value; 1006 | } 1007 | options = $.param(options); 1008 | if (url.includes('?')) { 1009 | url += '&' + options 1010 | } else { 1011 | url += '?' + options 1012 | } 1013 | return url; 1014 | } 1015 | 1016 | 1017 | /** 1018 | * 判断a数组是否包含b数组中 1019 | */ 1020 | export const getArrRepeat = (arr1,arr2) =>{ 1021 | return arr1.filter((item,index) =>{ 1022 | return arr2.includes(item) 1023 | }) 1024 | } 1025 | 1026 | 1027 | 1028 | /** 1029 | * 将数组分片 1030 | * 列子[1,2,3,4,5,6,7,8] [[1,2,3],[4,5,6],[7,8]] 1031 | */ 1032 | export const arrChunk = (data=[],space=5) => { 1033 | var result = []; 1034 | for (var i = 0, len = data.length; i < len; i += space) { 1035 | result.push(data.slice(i, i + space)); 1036 | } 1037 | return {data:result,total:data.length,space}; 1038 | } 1039 | 1040 | /** 1041 | * 复制内容 1042 | */ 1043 | export const copyToClip = (content) => { 1044 | var aux = document.createElement("input"); 1045 | aux.setAttribute("value", content); 1046 | document.body.appendChild(aux); 1047 | aux.select(); 1048 | document.execCommand("copy"); 1049 | document.body.removeChild(aux); 1050 | console.log('复制成功'); 1051 | } 1052 | 1053 | /** 1054 | * 生成uuid 1055 | */ 1056 | export const generateUUID = () => { 1057 | let d = new Date().getTime(); 1058 | let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 1059 | let r = (d + Math.random() * 16) % 16 | 0; 1060 | d = Math.floor(d / 16); 1061 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16); 1062 | }); 1063 | return uuid; 1064 | } --------------------------------------------------------------------------------