├── 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 |
2 | 404
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/view/about/about.vue:
--------------------------------------------------------------------------------
1 |
2 | about
3 |
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 |
2 | detail
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/components/header.vue:
--------------------------------------------------------------------------------
1 |
2 | header
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/modules/add/article/article.vue:
--------------------------------------------------------------------------------
1 |
2 | add-article
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/modules/edit/article/article.vue:
--------------------------------------------------------------------------------
1 |
2 | edit-article
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/modules/detail/article/article.vue:
--------------------------------------------------------------------------------
1 |
2 | detail-article
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/layout/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
px-rem
4 |

5 |
6 |
主要按钮1
7 |
8 |
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 |
2 |
3 |
4 |
5 |

6 |
7 |
8 |
9 | {{ realMusicTime }}
10 | {{ totalMusicTime }}
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
457 |
458 |
459 |
460 |
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 | 
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 | }
--------------------------------------------------------------------------------