├── .eslintignore ├── .browserslistrc ├── src ├── images │ └── logo.jpg ├── views │ ├── login │ │ ├── login.scss │ │ └── login.vue │ ├── app.vue │ ├── zrest │ │ ├── zrest.js │ │ └── zrest.vue │ ├── home │ │ ├── 404.vue │ │ ├── home.vue │ │ └── homeInner.vue │ ├── slot-layout.vue │ ├── stage3 │ │ └── stage3.vue │ ├── stage1 │ │ └── stage1.vue │ ├── stage2 │ │ └── stage2.vue │ ├── themeColor │ │ ├── changeColor.vue │ │ └── themeColor.vue │ └── iconfontPreview │ │ └── iconfontPreview.vue ├── css │ ├── override.scss │ ├── index.scss │ ├── defines.scss │ ├── element-var-changed.scss │ ├── project.scss │ ├── flexible.scss │ └── utils.scss ├── iconfont │ ├── fonts │ │ ├── my-app-icon.ttf │ │ ├── my-app-icon.woff │ │ ├── my-app-icon.woff2 │ │ ├── font.css │ │ ├── _font-preview.html │ │ ├── fonts.js │ │ └── my-app-icon.svg │ ├── svgs │ │ ├── calendar.svg │ │ ├── warning.svg │ │ ├── success.svg │ │ ├── bookmark.svg │ │ └── state-beinvited.svg │ └── css-template.njk ├── js │ ├── utils │ │ ├── userInfo.js │ │ ├── loadScripts.js │ │ ├── storageUtil.js │ │ ├── msgDialog.js │ │ ├── httpUtil.js │ │ ├── frequence.js │ │ ├── loading.js │ │ └── domUtil.js │ ├── $x.js │ ├── validData.js │ ├── loadScripts.js │ ├── themeColorClient.js │ └── axios.js ├── pages │ ├── pageB.js │ ├── login.js │ ├── iconfontPreview │ │ ├── entry.js │ │ └── template.html │ ├── themeColor.js │ └── index.js ├── router │ ├── spinRoute.js │ └── routerMain.js ├── component │ ├── debugInfo.vue │ ├── footCode.vue │ ├── leftMenu.vue │ └── topHeader.vue ├── store │ └── index.js └── index.html ├── static └── favicon.ico ├── postcss.config.js ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── .gitignore ├── .editorconfig ├── mock ├── root │ ├── buyProducts.js │ ├── api │ │ ├── test_api.js │ │ ├── getInfo.js │ │ ├── test_data.js │ │ └── getMenus.js │ ├── .js │ └── getProducts.js ├── mock文件说明.txt ├── maven.js ├── proxy80.js ├── static-config.js ├── mockClient.js └── mock-config.js ├── .npmrc ├── config ├── app-config.js ├── serverMap.js └── index.js ├── babel.config.js ├── .eslintrc.js ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >= 49 2 | Firefox >= 63 3 | Safari >= 12 4 | Edge >= 17 5 | iOS >= 8 6 | IE >= 10 7 | -------------------------------------------------------------------------------- /src/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzsrc/vue-element-ui-scaffold-webpack4/HEAD/src/images/logo.jpg -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzsrc/vue-element-ui-scaffold-webpack4/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /src/views/login/login.scss: -------------------------------------------------------------------------------- 1 | /*IFTRUE_isDebug 2 | .cond-msg { 3 | background: #fffbc3; 4 | } 5 | FITRUE_isDebug */ 6 | -------------------------------------------------------------------------------- /src/css/override.scss: -------------------------------------------------------------------------------- 1 | @import "defines"; 2 | //在这里写覆盖饿了么的全局样式 3 | 4 | .primay-color { 5 | color: $--color-primary; 6 | } 7 | -------------------------------------------------------------------------------- /src/iconfont/fonts/my-app-icon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzsrc/vue-element-ui-scaffold-webpack4/HEAD/src/iconfont/fonts/my-app-icon.ttf -------------------------------------------------------------------------------- /src/iconfont/fonts/my-app-icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzsrc/vue-element-ui-scaffold-webpack4/HEAD/src/iconfont/fonts/my-app-icon.woff -------------------------------------------------------------------------------- /src/iconfont/fonts/my-app-icon.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzsrc/vue-element-ui-scaffold-webpack4/HEAD/src/iconfont/fonts/my-app-icon.woff2 -------------------------------------------------------------------------------- /src/views/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /src/js/utils/userInfo.js: -------------------------------------------------------------------------------- 1 | export default { 2 | token: '', 3 | color: '', 4 | setUserInfo(userInfo) { 5 | Object.assign(this, userInfo) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({}), 4 | require('postcss-pxtorem')({rootValue: 100, propList: ['*']}) 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .idea 4 | dist/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | test/unit/coverage 9 | test/e2e/reports 10 | selenium-debug.log 11 | -------------------------------------------------------------------------------- /src/css/index.scss: -------------------------------------------------------------------------------- 1 | @import "../iconfont/fonts/font.css"; 2 | @import "override.scss"; 3 | @import "defines.scss"; 4 | @import "flexible.scss"; 5 | @import "utils.scss"; 6 | @import "project.scss"; 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | -------------------------------------------------------------------------------- /mock/root/buyProducts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | body: function (query, post) { 4 | return { 5 | data: '', 6 | msg: '', 7 | status: 0 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mock/root/api/test_api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | delay: 1000, 4 | body: { 5 | delayed: '1 seconds', 6 | data: { a: 11, b: 22, id: 1 }, 7 | msg: '', 8 | status: 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/css/defines.scss: -------------------------------------------------------------------------------- 1 | @import "./element-var-changed.scss"; 2 | //theme-chalk的变量名 3 | @import "../../node_modules/element-ui/packages/theme-chalk/src/common/var.scss"; 4 | 5 | //自定义颜色变量 6 | $my-custom-color: #0cdd3a; 7 | $my-custom-color2: #c655dd; 8 | -------------------------------------------------------------------------------- /src/views/zrest/zrest.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import zrest from './zrest.vue'; 3 | 4 | require('../../css/utils.scss'); 5 | 6 | 7 | new Vue({ 8 | el: '#app', 9 | template: '', 10 | components: { zrest } 11 | }) 12 | -------------------------------------------------------------------------------- /mock/mock文件说明.txt: -------------------------------------------------------------------------------- 1 | Mock对象的字段: 2 | 3 | disabled: 1或true表示禁用mock. 此时的请求将反向代理到接口服务器(proxyTarget). 4 | status: 返回的http状态. 默认为200 5 | headers: 返回的http头. 默认为Content-Type: application/json; charset=utf-8。可省略。 6 | body: 返回的http主体. 可为json对象或字符串 7 | delay: 延时返回的毫秒数 8 | -------------------------------------------------------------------------------- /src/iconfont/svgs/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /mock/maven.js: -------------------------------------------------------------------------------- 1 | // 实现maven仓库转发 2 | 3 | const config = { 4 | mockEnabled: false, 5 | proxyTarget: function (urlPart) { 6 | return 'https://maven.aliyun.com/repository/public' 7 | }, 8 | isHttps: false, // 是否https 9 | port: 8099 // 端口 10 | } 11 | module.exports = config 12 | -------------------------------------------------------------------------------- /src/views/home/404.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /mock/proxy80.js: -------------------------------------------------------------------------------- 1 | // 实现将本地80端口代理到9998端口,配合hosts配置,用于调试微信(因为微信必须使用域名) 2 | 3 | const config = { 4 | mockEnabled: false, 5 | proxyTarget: function (urlPart) { 6 | return 'http://localhost:8090' 7 | }, 8 | isHttps: false, // 是否https 9 | port: 80 // 端口 10 | } 11 | module.exports = config 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://r.cnpmjs.org 2 | sass_binary_site=https://npmmirror.com/mirrors/node-sass/ 3 | phantomjs_cdnurl=https://npmmirror.com/mirrors/phantomjs 4 | ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ 5 | chromedriver_cdnurl=http://npm.taobao.org/mirrors/chromedrive 6 | puppeteer_download_host=https://npmmirror.com/mirrors 7 | -------------------------------------------------------------------------------- /src/views/slot-layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/pages/pageB.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | require('../css/index.scss'); 3 | new Vue({ 4 | el: '#app', 5 | template: 6 | '
\n' + 7 | '
Multipage sample
\n' + 8 | '

This is pageB here

\n' + 9 | ' Back\n' + 10 | '
', 11 | components: {} 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /mock/root/.js: -------------------------------------------------------------------------------- 1 | //本文件模拟根目录的响应数据(类似index.html的作用) 2 | var fs = require('fs') 3 | module.exports = { 4 | disabled: 1, 5 | headers: { 6 | 'Content-Type': 'text/html' 7 | }, 8 | body: function (query, post) { 9 | return ` 10 | 11 | 12 | Hi, dynamic-mocker is running. 13 | 14 | 15 | ` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/static-config.js: -------------------------------------------------------------------------------- 1 | // 将一个目录作为http服务启动 2 | 3 | const config = { 4 | mockEnabled: false, // 设置mock失效,使用proxy功能 5 | proxyTarget: false, // 设置proxy失效,使用static功能 6 | static: { 7 | index: 'index.html', 8 | path: '../dist' 9 | }, 10 | isHttps: false, // 是否https 11 | port: 8061 // 端口 12 | } 13 | module.exports = config 14 | -------------------------------------------------------------------------------- /src/pages/login.js: -------------------------------------------------------------------------------- 1 | //Login page 2 | 3 | import Vue from 'vue'; 4 | import login from '../views/login/login.vue' 5 | import debugInfo from '../component/debugInfo'; 6 | 7 | require('../css/index.scss'); 8 | 9 | //调试信息组件 10 | Vue.use(debugInfo) 11 | 12 | new Vue({ 13 | el: '#app', 14 | template: '', 15 | components: { login } 16 | }) 17 | 18 | -------------------------------------------------------------------------------- /mock/root/api/getInfo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | status: 200, 4 | body: function (query, post) { 5 | return { 6 | status: 0, 7 | msg: '', 8 | data: { 9 | name: '张三', 10 | avatar: '', 11 | token: +new Date() 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/css/element-var-changed.scss: -------------------------------------------------------------------------------- 1 | //可在本文件中覆盖-theme-chalk同名变量 2 | //会将此文件附加在 "~element-ui/packages/theme-chalk/src/common/var.scss" 之前(通过join-file-content-plugin实现); 3 | 4 | //覆盖主色 5 | $--color-primary: #f67a17; //去掉 !default 6 | 7 | $--color-primary-light-95: mix(#fff, $--color-primary, 95%); 8 | $--color-primary-dark-1: mix(#000, $--color-primary, 10%); 9 | $--color-primary-dark-2: mix(#000, $--color-primary, 20%); 10 | $--color-primary-dark-7: mix(#000, $--color-primary, 70%); 11 | -------------------------------------------------------------------------------- /src/pages/iconfontPreview/entry.js: -------------------------------------------------------------------------------- 1 | import iconfontPreview from '../../views/iconfontPreview/iconfontPreview'; 2 | import Vue from 'vue'; 3 | require('../../css/index.scss'); 4 | 5 | 6 | new Vue({ 7 | el: '#app', 8 | template: ` 9 |
10 |

Iconfont demo. You can modify or add a svg file in [src/iconfont/svgs], and see it refreshing.


11 | 12 |
`, 13 | components: { iconfontPreview } 14 | }) 15 | -------------------------------------------------------------------------------- /src/pages/themeColor.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import $x from '../js/$x' 3 | import themeColor from '../views/themeColor/themeColor.vue' 4 | import { initThemeColor } from '../js/themeColorClient' 5 | 6 | require('../css/index.scss'); 7 | 8 | // window.Vue = Vue 9 | 10 | Vue.prototype.$ELEMENT = { size: 'small' } 11 | 12 | // 通用组件,便于处理 13 | Vue.prototype.$x = Vue.$x = $x; 14 | 15 | initThemeColor() 16 | new Vue({ 17 | el: '#app', 18 | render: h => h(themeColor), 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /mock/root/getProducts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | body: function (query, post) { 4 | return { 5 | data: [ 6 | { id: 1, title: 'iPad 4 Mini', price: 500.01, inventory: 2 }, 7 | { id: 2, title: 'H&M T-Shirt White', price: 10.99, inventory: 10 }, 8 | { id: 3, title: 'Charli XCX - Sucker CD', price: 19.99, inventory: 5 } 9 | ], 10 | msg: '', 11 | status: 0 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /config/app-config.js: -------------------------------------------------------------------------------- 1 | var getElementUISeries = require('webpack-theme-color-replacer/forElementUI/getElementUISeries') 2 | 3 | module.exports = { 4 | LOGIN_PATH: './', 5 | title: 'vue + webpack4 + element-ui脚手架项目', 6 | description: 'vue + webpack4 + element-ui脚手架项目', 7 | 8 | themeColor: '#f67a17', 9 | otherColors: ['#10213a', '#fefeff'], //titleBg、titleFg 10 | getThemeColors: function (primaryColor, otherColors) { 11 | return getElementUISeries(primaryColor, otherColors);//element-ui主色系列 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mock/mockClient.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import mockClient from 'dynamic-mocker/lib/client.js' 4 | 5 | var config = require('./mock-config.js') 6 | 7 | mockClient.setup(config, { 8 | '/api/getInfo': require('./root/api/getInfo.js'), 9 | '/api/getMenus': require('./root/api/getMenus.js'), 10 | '/api/test_api': require('./root/api/test_api.js'), 11 | '/api/test_data': require('./root/api/test_data.js'), 12 | '/buyProducts': require('./root/buyProducts.js'), 13 | '/getProducts': require('./root/getProducts.js') 14 | }) 15 | -------------------------------------------------------------------------------- /src/router/spinRoute.js: -------------------------------------------------------------------------------- 1 | /* 2 | 使vue-router懒加载时可以显示一个加载提示,避免网速慢时无响应 3 | 用法: 4 | const route = { 5 | path: '/page-layout', 6 | component: () => spinRoute.require(import('./pageLayout/pageLayout.vue')) 7 | }; 8 | */ 9 | 10 | 'user strict'; 11 | import loading from '../js/utils/loading'; 12 | 13 | export default { 14 | require(componentPromise) { 15 | loading.show(); 16 | return componentPromise.then(component => { 17 | loading.close(); 18 | return component 19 | }) 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/views/stage3/stage3.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/views/stage1/stage1.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/iconfont/svgs/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | warning1 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/iconfont/svgs/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | success 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/component/debugInfo.vue: -------------------------------------------------------------------------------- 1 | 11 | 24 | -------------------------------------------------------------------------------- /src/iconfont/svgs/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/js/utils/loadScripts.js: -------------------------------------------------------------------------------- 1 | export default function loadScripts(urls, callback) { 2 | if (typeof urls === 'string') urls = [urls] 3 | let loaded = 0 4 | urls.map(url => { 5 | if (document.querySelector('script[src="' + url + '"]')) { 6 | onScriptLoad() 7 | } 8 | else { 9 | const script = document.createElement('script') 10 | script.src = url 11 | script.onload = onScriptLoad 12 | document.querySelector('head').appendChild(script) 13 | } 14 | }) 15 | 16 | function onScriptLoad() { 17 | loaded++ 18 | if (loaded === urls.length) { 19 | if (callback) callback() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/css/project.scss: -------------------------------------------------------------------------------- 1 | // 项目的公告样式 2 | 3 | .global-mask { 4 | background: transparent; 5 | } 6 | 7 | /*滚动条样式*/ 8 | ::-webkit-scrollbar { 9 | border-radius: 8px; 10 | width: 8px; 11 | height: 8px; 12 | } 13 | 14 | //::-webkit-scrollbar-track { 15 | // border-radius: 8px; 16 | // -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); 17 | // background: linear-gradient(90deg, #efefef 0%, #fff 78%, #efefef 100%); 18 | //} 19 | 20 | ::-webkit-scrollbar-thumb { 21 | border-radius: 8px; 22 | -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, .3); 23 | background: linear-gradient(90deg, #efefef 0%, #fff 78%, #efefef 100%); 24 | } 25 | 26 | ::-webkit-scrollbar-thumb:vertical { 27 | background: linear-gradient(0deg, #efefef 0%, #fff 78%, #efefef 100%); 28 | } 29 | -------------------------------------------------------------------------------- /src/views/stage2/stage2.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /src/js/$x.js: -------------------------------------------------------------------------------- 1 | import StorageUtil from './utils/storageUtil.js' 2 | import userInfo from './utils/userInfo.js' 3 | import domUtil from './utils/domUtil.js' 4 | import msgDialog from './utils/msgDialog.js' 5 | import httpUtil from './utils/httpUtil.js' 6 | //日期格式化 7 | import formatDate from 'date-any/formatDate.js' 8 | 9 | const util = { 10 | clone(obj, deep) { 11 | if (!obj) return obj; 12 | if (deep) return JSON.parse(JSON.stringify(obj)); 13 | return Array.isArray(obj) ? obj.slice(0) : Object.assign({}, obj) 14 | }, 15 | noop() { 16 | } 17 | } 18 | 19 | const mixed = { 20 | ...util, 21 | ...msgDialog, 22 | ...httpUtil, 23 | storage: new StorageUtil(), 24 | userInfo, 25 | formatDate 26 | } 27 | 28 | Object.assign(domUtil, mixed) 29 | 30 | export default domUtil 31 | -------------------------------------------------------------------------------- /src/component/footCode.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | -------------------------------------------------------------------------------- /src/js/utils/storageUtil.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class StorageUtil { 4 | constructor(storage) { 5 | this.storage = storage || localStorage 6 | // 缓存 7 | this.caches = {} 8 | } 9 | 10 | getObj(name) { 11 | const r = this.caches[name] 12 | if (r !== undefined) return r 13 | try { 14 | const str = this.storage.getItem(name); 15 | return str && JSON.parse(str); 16 | } catch (e) { 17 | console.warn(e) 18 | } 19 | } 20 | 21 | setObj(name, obj) { 22 | this.caches[name] = obj 23 | if (obj === undefined) { 24 | this.storage.removeItem(name); 25 | } else { 26 | this.storage.setItem(name, JSON.stringify(obj)); 27 | } 28 | } 29 | } 30 | 31 | export default StorageUtil 32 | -------------------------------------------------------------------------------- /config/serverMap.js: -------------------------------------------------------------------------------- 1 | var configs = { 2 | //本地开发环境的接口地址(npm run dev) 3 | dev: { 4 | base: '//localhost:8087', //本地开发调试用的服务器地址,修改不会影响发布 5 | node_api: '//127.0.0.1:7001', //本地开发调试用的服务器地址,修改不会影响发布 6 | }, 7 | //待发布的开发环境的接口地址(npm run build-dev) 8 | dev_build: { 9 | base: '//xxx.dev61.xxx.com', 10 | node_api: '//xxx-node.dev61.xxx.com', 11 | }, 12 | //测试环境的接口地址 13 | test: { 14 | base: '//aaa-service-test.xxx.com', 15 | node_api: '//bbb-node-service-test.xxx.com', 16 | }, 17 | //演示环境的接口地址 18 | demo: { 19 | base: '//aaa-demo.xxx.com', 20 | node_api: '//bbb-node-service-demo.xxx.com' 21 | }, 22 | //生产环境的接口地址 23 | prod: { 24 | base: '//aaa.xxx.com', 25 | node_api: '//bbb-node-service.xxx.com', 26 | } 27 | } 28 | module.exports = configs[process.env.ENV_CONFIG] 29 | 30 | -------------------------------------------------------------------------------- /src/js/validData.js: -------------------------------------------------------------------------------- 1 | /* 2 | 输入数据校验组件 3 | */ 4 | export default { 5 | checkMobile(rule, value, callback) { 6 | if (!value) { 7 | return callback(new Error('请输入手机号码')); 8 | } else if (!(/^1[345678]\d{9}$/.test(value))) { 9 | return callback(new Error('手机号格式不正确')); 10 | } else { 11 | return callback(); 12 | } 13 | }, 14 | checkIdCard(rule, value, callback) { 15 | const reg = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; 16 | if (!value) { 17 | return callback(); 18 | } else if (!(reg.test(value))) { 19 | return callback(new Error('请输入正确的公民身份证号')); 20 | } else { 21 | return callback(); 22 | } 23 | }, 24 | checkEmail(value) { 25 | return /^[\w\.\-]*\w@[\w\.\-]+\.[\w\.\-]+$/.test(value) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mock/root/api/test_data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | delay: 200, 4 | status: 200, 5 | headers: { 6 | server: 'dynamic-mocker', 7 | 'set-cookie': 'foo=bar; path=/', 8 | 'cache-control': 'no-cache' 9 | }, 10 | body: function (query, post) { 11 | const pageIndex = post.pageIndex 12 | const pageSize = post.pageSize 13 | return { 14 | status: 0, 15 | data: { 16 | datas: new Array(pageSize).fill().map((n, i) => { 17 | const id = pageIndex * pageSize + i 18 | return { 19 | id: id, 20 | name: '张三' + id, 21 | date: +new Date() - i * 100000 22 | } 23 | }), 24 | total: 111, 25 | }, 26 | msg: 'ok' 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/js/utils/msgDialog.js: -------------------------------------------------------------------------------- 1 | //为element-ui的Message添加默认参数 2 | import { Loading, Message, MessageBox } from 'element-ui' 3 | 4 | function toastCtor(key, options) { 5 | const defaultOptions = { 6 | showClose: true, 7 | duration: 4000, 8 | message: options, 9 | } 10 | const fn = key ? Message[key] : Message; 11 | return fn(Object.assign(defaultOptions, options)) 12 | } 13 | 14 | const toast = Object.assign(toastCtor.bind(null, null), Message); 15 | ['success', 'warning', 'info', 'error'].forEach(key => { 16 | toast[key] = toastCtor.bind(null, key) 17 | }) 18 | 19 | //为element-ui的MessageBox添加默认参数 20 | MessageBox.setDefaults({ closeOnClickModal: false, closeOnPressEscape: true }); 21 | 22 | export default { 23 | msgBox: MessageBox, 24 | alert: MessageBox.alert, 25 | confirm: MessageBox.confirm, 26 | prompt: MessageBox.prompt, 27 | toast: toast, 28 | loading: Loading.service 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import $x from '../js/$x' 3 | import router from '../router/routerMain.js' 4 | import store from '../store/index.js' 5 | import main from '../views/app.vue' 6 | import { Button, Table, TableColumn, Pagination } from 'element-ui' 7 | import { initThemeColor } from '../js/themeColorClient' 8 | import debugInfo from '../component/debugInfo.vue' 9 | 10 | require('../css/index.scss'); 11 | 12 | // 仅对 npm run build-preview 时,使用客户端mock数据(拦截XMLHttpRequest) 13 | /* IFTRUE_isPreview */ 14 | require('../../mock/mockClient') 15 | /* FITRUE_isPreview */ 16 | 17 | Vue.prototype.$ELEMENT = { size: 'small' } 18 | 19 | // 通用组件,便于处理 20 | Vue.prototype.$x = Vue.$x = $x; 21 | 22 | // 常用组件在这注册。即可实现按需加载,又不必每个页面调用Vue.use。 23 | Vue.use(Button).use(Table).use(TableColumn).use(Pagination); 24 | 25 | //调试信息组件 26 | Vue.use(debugInfo) 27 | 28 | initThemeColor() 29 | new Vue({ 30 | el: '#app', 31 | router, 32 | store, 33 | render: h => h(main), 34 | }); 35 | 36 | /* IFDEBUG 37 | window.$x = $x 38 | IFDEBUG */ 39 | -------------------------------------------------------------------------------- /src/js/utils/httpUtil.js: -------------------------------------------------------------------------------- 1 | import axios from '../axios'; 2 | 3 | // 统一axios的get、post等调用方式 4 | export function createHttp(axios) { 5 | const httpUtil = { 6 | axios, 7 | get(url, pars, config) { 8 | return axios.get(toQueryUrl(url, pars), config) 9 | }, 10 | }; 11 | 12 | ['post', 'put'].map(method => { 13 | httpUtil[method] = (url, pars, config) => { 14 | return axios[method](url, pars, config) 15 | } 16 | }) 17 | httpUtil.delete = (url, pars, config) => { 18 | return axios.delete(url, pars && { data: pars }, config) 19 | } 20 | httpUtil.toQueryUrl = toQueryUrl; 21 | return httpUtil 22 | } 23 | 24 | export default createHttp(axios); 25 | 26 | function toQueryUrl(rawUrl, pars) { 27 | if (pars) { 28 | pars.f_rnd = +new Date(); //防止火狐缓存GET请求 29 | rawUrl += rawUrl.indexOf('?') > -1 ? '&' : '?'; 30 | rawUrl += Object.keys(pars).map(key => key + '=' + encodeURIComponent(pars[key])).join('&'); 31 | } 32 | return rawUrl 33 | }; 34 | -------------------------------------------------------------------------------- /src/iconfont/fonts/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: my-app-icon; 3 | src: url("my-app-icon.woff2?948e5646") format("woff2"), 4 | url("my-app-icon.woff?948e5646") format("woff"), 5 | url("my-app-icon.ttf?948e5646") format("truetype"), 6 | url("my-app-icon.svg?948e5646#my-app-icon") format("svg"); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | [class^="my-icon"], [class*=" my-icon"] { 12 | font-family: 'my-app-icon'; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | font-style: normal; 16 | } 17 | 18 | 19 | .my-icon-bookmark::before { 20 | content: "\12c"; 21 | } 22 | 23 | .my-icon-calendar::before { 24 | content: "\12d"; 25 | } 26 | 27 | .my-icon-state-beinvited::before { 28 | content: "\12e"; 29 | } 30 | 31 | .my-icon-success::before { 32 | content: "\12f"; 33 | } 34 | 35 | .my-icon-warning::before { 36 | content: "\130"; 37 | } 38 | 39 | .my-icon-pwd_msk::before { 40 | content: "\7f"; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/js/loadScripts.js: -------------------------------------------------------------------------------- 1 | export function loadScripts(urls, callback) { 2 | if (typeof urls === 'string') urls = [urls] 3 | if (urls.length === 0) { 4 | if (callback) callback() 5 | return 6 | } 7 | let loaded = 0 8 | const loadedIndex = {} 9 | urls.map((url, index) => { 10 | if (document.querySelector('script[src="' + url + '"]')) { 11 | onScriptLoad(index) 12 | } else { 13 | const script = document.createElement('script') 14 | script.setAttribute('src', url) 15 | const cb = onScriptLoad.bind(script, index) 16 | script.onload = cb 17 | script.onerror = cb 18 | document.querySelector('head').appendChild(script) 19 | } 20 | }) 21 | 22 | function onScriptLoad(index) { 23 | if (!loadedIndex[index]) { 24 | loaded++ 25 | loadedIndex[index] = 1 26 | 27 | if (loaded === urls.length) { 28 | if (callback) callback() 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | corejs: 3, 7 | useBuiltIns: 'usage', 8 | modules: false, 9 | //开发环境,不做es6转换,以便于调试 10 | targets: process.env.NODE_ENV === 'development' ? 'fully supports es6' : 'browserslist config' 11 | } 12 | ], 13 | '@vue/babel-preset-jsx', // for babel7 14 | ], 15 | plugins: [ 16 | //'@babel/plugin-syntax-dynamic-import', 17 | //'@babel/plugin-transform-runtime', //用了useBuiltIns不需要它 18 | //['@babel/plugin-proposal-class-properties'], 19 | //babel7不用这个:'@vue/babel-plugin-transform-vue-jsx', 20 | [ 21 | 'babel-plugin-component', 22 | { 23 | libraryName: 'element-ui', 24 | styleLibraryName: '~node_modules/element-ui/packages/theme-chalk/src', 25 | ext: '.scss' 26 | } 27 | ] 28 | ], 29 | comments: false, 30 | } 31 | -------------------------------------------------------------------------------- /src/js/utils/frequence.js: -------------------------------------------------------------------------------- 1 | export default { 2 | //频率控制 函数连续调用时,fn 执行频率限定为 1次/waitMs。立即执行 3 | throttle: function throttle (waitMs, fn) { 4 | let lastRun = 0; 5 | return function () { 6 | const now = +new Date(); 7 | if (now - lastRun > waitMs) { 8 | lastRun = now; 9 | fn.apply(null, arguments); 10 | } 11 | } 12 | }, 13 | //空闲控制 返回函数连续调用时,空闲时间必须大于或等于 waitMs,fn 才会执行。延迟执行 14 | debounce: function debounce (waitMs, fn) { 15 | let lastCall, args, timeout; 16 | return function r () { 17 | lastCall = +new Date(); 18 | args = arguments; 19 | if (!timeout) { 20 | timeout = setTimeout(later, waitMs); 21 | } 22 | }; 23 | 24 | function later () { 25 | const past = +new Date() - lastCall; 26 | if (past > waitMs) { 27 | timeout = null; 28 | fn.apply(null, args) 29 | } 30 | else { 31 | timeout = setTimeout(later, waitMs - past + 1) 32 | } 33 | } 34 | } 35 | }; -------------------------------------------------------------------------------- /src/js/themeColorClient.js: -------------------------------------------------------------------------------- 1 | import client from 'webpack-theme-color-replacer/client' 2 | import themeUtil from 'webpack-theme-color-replacer/themeUtil' 3 | 4 | import appConfig from '../../config/app-config.js' 5 | 6 | export let curColor = appConfig.themeColor 7 | 8 | // 动态切换主题色 9 | export function changeThemeColor(newColor) { 10 | var customB = parseInt(Math.random() * 256).toString(16); // 按你需要生成颜色 11 | if (customB.length == 1) customB = '0' + customB 12 | const options = { 13 | newColors: themeUtil.getMyColors(newColor, ['#88' + customB + customB, '#' + customB + '88' + customB]), 14 | } 15 | return client.changer.changeColor(options, Promise) 16 | .then(t => { 17 | curColor = newColor 18 | localStorage.setItem('theme_color', curColor) 19 | }); 20 | } 21 | 22 | export function initThemeColor() { 23 | const savedColor = localStorage.getItem('theme_color') 24 | if (savedColor) { 25 | document.body.style.display = 'none' 26 | curColor = savedColor; 27 | changeThemeColor(savedColor).finally(() => { 28 | document.body.style.display = '' 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/iconfontPreview/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 25 | 26 | 27 | 28 |
29 |
Loading...
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/themeColor/changeColor.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 36 | 41 | 46 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | //import productList from './modules/productList' 4 | 5 | Vue.use(Vuex) 6 | 7 | // state 8 | const state = { 9 | topMenus: [], 10 | topMenuIndex: 0, 11 | } 12 | 13 | // getters 14 | const getters = { 15 | leftMenus: (state, getters) => { 16 | const topMenu = state.topMenus[state.topMenuIndex] 17 | return (topMenu && topMenu.children) || [] 18 | }, 19 | } 20 | 21 | // actions 22 | const actions = { 23 | setTopMenus({ commit }, { topMenus, vm }) { 24 | commit('SET_TOPMENUS', topMenus) 25 | var path = vm.$route.path 26 | var index = topMenus.findIndex(menu => { 27 | if (menu.url === path) return true 28 | if (menu.children) return menu.children.find(m => m.url === path) 29 | }) 30 | if (index > -1) commit('SET_TOPMENUINDEX', index) 31 | }, 32 | setTopMenuIndex({ commit }, index) { 33 | commit('SET_TOPMENUINDEX', index); 34 | }, 35 | }; 36 | 37 | // mutations 38 | const mutations = { 39 | SET_TOPMENUS(state, topMenus) { 40 | state.topMenus = topMenus 41 | }, 42 | SET_TOPMENUINDEX(state, index) { 43 | state.topMenuIndex = index 44 | }, 45 | } 46 | 47 | export default new Vuex.Store({ 48 | namespaced: true, 49 | state, 50 | getters, 51 | actions, 52 | mutations, 53 | //modules: { 54 | //productList, 55 | //}, 56 | }) 57 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | function getSourceMapPath() { 5 | // 根据安全级别,改成只有开发者知道的文件夹名或动态加密算法生成。(npm run show-map查看) 6 | // 这样既可在需要时进行手动添加源码映射方便调试,又可避免了源码泄露。 7 | var projName = path.basename(process.cwd()) 8 | var md5 = require('crypto').createHash('md5');//定义加密方式:md5不可逆,此处的md5可以换成任意hash加密的方法名称; 9 | md5.update('my-' + projName); 10 | return '_map' + md5.digest('hex'); //加密后的值d 11 | } 12 | 13 | var ret = { 14 | build: { 15 | assetsRoot: path.resolve(__dirname, '../dist'), 16 | assetsSubDirectory: '', 17 | assetsPublicPath: '', // 使用相对路径,可不受路径层次限制 18 | productionSourceMap: true, 19 | sourceMapPath: getSourceMapPath(), 20 | }, 21 | dev: { 22 | port: 8090, 23 | autoOpenBrowser: true, 24 | assetsSubDirectory: '', 25 | assetsPublicPath: '/', 26 | // proxyTable: { 27 | // '/api': { 28 | // target: 'http://localhost:3000', 29 | // changeOrigin: true, 30 | // pathRewrite: {'^/api': ''} 31 | // } 32 | // }, 33 | // CSS Sourcemaps off by default because relative paths are "buggy" 34 | // with this option, according to the CSS-Loader README 35 | // (https://github.com/webpack/css-loader#sourcemaps) 36 | // In our experience, they generally work as expected, 37 | // just be aware of this issue when enabling this option. 38 | cssSourceMap: true 39 | } 40 | } 41 | 42 | module.exports = ret; 43 | -------------------------------------------------------------------------------- /src/js/utils/loading.js: -------------------------------------------------------------------------------- 1 | import { Loading } from 'element-ui'; 2 | 3 | //const MASK_DELAY = 5000; 4 | const DefaultMaskOptions = { customClass: 'global-mask', target: 'html > body' } 5 | 6 | const loading = { 7 | show(options) { 8 | if (!this.unique) { 9 | this.unique = Loading.service(options); 10 | } 11 | }, 12 | showMask() { 13 | const mask = document.querySelector('.global-mask'); 14 | if (mask) { 15 | mask.classList.remove('global-mask'); 16 | } 17 | }, 18 | close() { 19 | setTimeout(() => { 20 | if (this.unique) { 21 | this.unique.close(); 22 | this.unique = null; 23 | } 24 | }, 100) 25 | }, 26 | } 27 | 28 | export default { 29 | //计数器,防止出现多个 30 | count: 0, 31 | show(options) { 32 | if (options !== false) { 33 | options = Object.assign({}, DefaultMaskOptions, options) 34 | try { 35 | this.count++; 36 | loading.show(options); 37 | //延时显示loading蒙板 38 | //if (this.timer) clearTimeout(this.timer); 39 | //this.timer = setTimeout(() => loading.showMask(), MASK_DELAY); 40 | } catch (e) { 41 | } 42 | } 43 | }, 44 | close(options) { 45 | try { 46 | if (options !== false) { 47 | if (this.count > 0) this.count--; 48 | if (this.count === 0) { 49 | //if (this.timer) clearTimeout(this.timer) 50 | loading.close() 51 | } 52 | } 53 | } catch (e) { 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mock/mock-config.js: -------------------------------------------------------------------------------- 1 | var frontFiles = /^(\/\w+|)\/(h5\/.+|$|\w+\.html|favicon\.ico|__webpack_hmr|.+hot-update.+)/ 2 | 3 | const config = { 4 | mockEnabled: true, 5 | mockPath: ['root', 'root-old'], //模拟文件根目录 6 | proxyTarget(uri) { //后台接口服务地址(代理目标),为空表示不代理 7 | var frontPart = frontFiles.exec(uri.pathname) 8 | if (frontPart) { 9 | if (frontPart && frontPart[1]) { //url带了虚拟目录,转前端时不要虚拟目录 10 | uri.pathname = frontPart[2] 11 | uri.setChanged(); 12 | } 13 | //前端页面,到h5 14 | return 'http://localhost:8090' 15 | } 16 | //非前端页面(ajax和v9页面相关) 17 | return 'http://localhost:8080' //.后端 18 | }, 19 | isHttps: false, //是否https 20 | port: 8087, //端口 21 | proxyOptions: { 22 | changeOrigin: true, //支持用IP远程访问 23 | }, 24 | checkPath: function (urlPath) { //urlPath校验函数,返回true表示需要进行mock处理,为false直接走代理 25 | return true 26 | }, 27 | beforeResponse: function (respData, req) { //数据返回前的回调钩子,respData包含status、headers、body属性 28 | respData.headers['access-control-allow-origin'] = req.headers.origin || req.headers.Origin || ''; 29 | respData.headers['access-control-allow-credentials'] = 'true'; 30 | respData.headers['access-control-allow-headers'] = req.headers['access-control-request-headers'] || req.headers['Access-Control-Request-Headers'] || ''; 31 | respData.headers['access-control-max-age'] = '6000'; 32 | respData.headers['access-control-allow-methods'] = 'PUT,POST,GET,DELETE,PATCH,OPTIONS'; 33 | 34 | respData.headers.P3P = 'CP="CAO PSA OUR"'; 35 | }, 36 | mapFile(pathname, req) { 37 | return pathname 38 | }, 39 | // genClientJs: '../src/js/mockClient.js', // 生成mockClient.js 40 | samePreview: false, // true - mock预览时disabled开关也生效(默认false,预览时忽略所有开关) 41 | logData: true, // mock预览时打印模拟数据 42 | title: 'My App' 43 | } 44 | module.exports = config; 45 | -------------------------------------------------------------------------------- /src/views/home/home.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 66 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 25 | 39 | 40 | 41 | 42 |
43 |
44 | 45 | 47 | 48 |
Loading...
49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /src/router/routerMain.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import spinRoute from './spinRoute'; 4 | 5 | //同步加载,合并打包 6 | import home from '../views/home/home.vue'; 7 | 8 | Vue.use(Router) 9 | 10 | //组件懒加载,下载也页面组件js时显示spin状态 11 | const stage3 = () => spinRoute.require(import('../views/stage3/stage3.vue')); 12 | 13 | const router = new Router({ 14 | mode: 'hash', 15 | routes: [ 16 | { 17 | path: '/', 18 | component: home, //sync 19 | children: [ 20 | { path: '', component: () => spinRoute.require(import('../views/home/homeInner.vue')) }, 21 | { path: '/theme', component: () => spinRoute.require(import('../views/themeColor/themeColor.vue')) }, 22 | { path: '/stage1', component: () => spinRoute.require(import('../views/stage1/stage1.vue')) }, 23 | { 24 | path: '/stage2', 25 | component: () => spinRoute.require(import('../views/stage2/stage2.vue')), 26 | children: [ 27 | { 28 | path: '/stage2/stage3', 29 | component: stage3 30 | }, 31 | ] 32 | }, 33 | { 34 | path: '/icons', 35 | component: () => spinRoute.require(import('../views/iconfontPreview/iconfontPreview.vue')) 36 | }, 37 | { path: '/zrest', component: () => spinRoute.require(import('../views/zrest/zrest.vue')) }, 38 | { 39 | path: '/layout', 40 | component: () => spinRoute.require(import('../views/slot-layout.vue')) 41 | }, 42 | { path: '*', component: () => spinRoute.require(import('../views/home/404.vue')) } 43 | ], 44 | }, 45 | ], 46 | }) 47 | /*IFDEBUG 48 | //禁用重复警告 49 | 'push,replace'.split(',').map(method => router[method] = function () { 50 | return Router.prototype[method].apply(this, arguments).catch(t => 0) 51 | }) 52 | FIDEBUG*/ 53 | export default router; 54 | -------------------------------------------------------------------------------- /src/component/leftMenu.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 53 | 54 | 72 | -------------------------------------------------------------------------------- /src/views/login/login.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 51 | 73 | -------------------------------------------------------------------------------- /src/iconfont/css-template.njk: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: {{ fontName }}; 3 | {% if formats.indexOf('eot')>-1 -%} 4 | src: url("{{ cssFontPath }}{{ fontName }}.eot"); 5 | {%- endif -%} 6 | {%- set eotIndex = formats.indexOf('eot') -%} 7 | {%- set woff2Index = formats.indexOf('woff2') -%} 8 | {%- set woffIndex = formats.indexOf('woff') -%} 9 | {%- set ttfIndex = formats.indexOf('ttf') -%} 10 | {%- set svgIndex = formats.indexOf('svg') -%} 11 | 12 | src: {% if eotIndex != -1 -%} 13 | url("{{ cssFontPath }}{{ fontName }}.eot?{{ fileMark }}#iefix") format("embedded-opentype") 14 | {%- set nothing = formats.splice(eotIndex, 1) -%} 15 | {%- if formats.length != 0 -%}, 16 | {% else -%}; {% endif -%} 17 | {%- endif -%} 18 | {%- if woff2Index != -1 -%} 19 | url("{{ cssFontPath }}{{ fontName }}.woff2?{{ fileMark }}") format("woff2") 20 | {%- set nothing = formats.splice(woff2Index, 1) -%} 21 | {%- if formats.length != 0 -%}, 22 | {% else -%}; {% endif -%} 23 | {%- endif -%} 24 | {%- if woffIndex != -1 -%} 25 | url("{{ cssFontPath }}{{ fontName }}.woff?{{ fileMark }}") format("woff") 26 | {%- set nothing = formats.splice(woffIndex, 1) -%} 27 | {%- if formats.length != 0 -%}, 28 | {% else -%}; {% endif -%} 29 | {%- endif -%} 30 | {%- if ttfIndex != -1 -%} 31 | url("{{ cssFontPath }}{{ fontName }}.ttf?{{ fileMark }}") format("truetype") 32 | {%- set nothing = formats.splice(ttfIndex, 1) -%} 33 | {%- if formats.length != 0 -%}, 34 | {% else -%}; {% endif -%} 35 | {%- endif -%} 36 | {%- if svgIndex != -1 -%} 37 | url("{{ cssFontPath }}{{ fontName }}.svg?{{ fileMark }}#{{ fontName }}") format("svg"); 38 | {%- endif %} 39 | font-weight: normal; 40 | font-style: normal; 41 | } 42 | 43 | [class^="{{ cssPrefix }}"], [class*=" {{ cssPrefix }}"] { 44 | font-family: '{{ fontName }}'; 45 | -webkit-font-smoothing: antialiased; 46 | -moz-osx-font-smoothing: grayscale; 47 | font-style: normal; 48 | } 49 | 50 | {% for glyph in glyphs %} 51 | .{{ cssPrefix }}-{{ glyph.name }}::before { 52 | content: "\{{ glyph.unicode[0].charCodeAt(0).toString(16) }}"; 53 | } 54 | {% endfor %} 55 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint', 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | extends: [ 13 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 14 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 15 | 'plugin:vue/essential', 16 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 17 | 'standard' 18 | ], 19 | // required to lint *.vue files 20 | plugins: [ 21 | 'vue' 22 | ], 23 | // add your custom rules here 24 | rules: { 25 | //"off"或0 -关闭规则 26 | //"warn" 或1 - 开启规则, 使用警告 程序不会退出 27 | //"error"或2 - 开启规则, 使用错误 程序退出 28 | 29 | indent: ['error', 4], 30 | 'vue/script-indent': ['error', 4, { baseIndent: 1 }], 31 | //分号 32 | semi: 'off', 33 | 'spaced-comment': 'off', 34 | eqeqeq: 1, 35 | 'no-useless-escape': 'off', 36 | 'brace-style': 0, //大括号风格 37 | curly: 'off', //[2, "all"],//必须使用 if(){} 中的{} 38 | 'space-before-function-paren': ['off', 'always'], //函数定义时括号前面要不要有空格 39 | 'no-new': 'off', 40 | 'comma-dangle': 'off', //对象字面量项尾不能有逗号 41 | 'no-return-assign': 'warn', //return 语句中不能有赋值表达式 42 | 'eol-last': 0, 43 | 'no-multiple-empty-lines': 0, 44 | //'quotes': 'off', 45 | //'comma-spacing': 'off', 46 | 'handle-callback-err': 0, 47 | 'padded-blocks': 0, 48 | 'no-duplicate-imports': 0, 49 | 'operator-linebreak': 0, 50 | 'no-undef': 2, 51 | 'no-var': 1, //是否可使用var 52 | 'no-extend-native': 0, 53 | 'no-sequences': 0, 54 | 55 | // allow paren-less arrow functions 56 | 'arrow-parens': 0, 57 | // allow async-await 58 | 'generator-star-spacing': 0, 59 | // allow debugger during development 60 | 'no-debugger': 2, 61 | 'no-eval': 0, 62 | //'standard/no-callback-literal': 1, 63 | 'array-callback-return': 1, 64 | }, 65 | overrides: [ 66 | { 67 | files: ['*.vue'], 68 | rules: { 69 | indent: 'off' 70 | } 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /mock/root/api/getMenus.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disabled: 0, 3 | status: 200, 4 | body: function (query, post) { 5 | return { 6 | status: 0, 7 | msg: '', 8 | data: [ 9 | { 10 | title: 'Home', 11 | url: '/', 12 | icon: 'el-icon-home' 13 | }, 14 | { 15 | title: 'Theme', 16 | url: '/theme', 17 | icon: 'el-icon-theme' 18 | }, 19 | { 20 | title: 'Layout', 21 | url: '/layout', 22 | icon: 'el-icon-grid' 23 | }, 24 | { 25 | title: 'Page1', 26 | url: '/stage1', 27 | children: [{ 28 | title: 'stage1', 29 | url: '/stage1', 30 | icon: 'el-icon-document' 31 | }, { 32 | title: 'stage2', 33 | url: '/stage2', 34 | icon: 'el-icon-s-goods' 35 | }, { 36 | title: 'stage3', 37 | url: '/stage2/stage3', 38 | icon: 'el-icon-setting' 39 | }, { 40 | title: 'rest tool', 41 | url: '/zrest', 42 | icon: 'el-icon-menu' 43 | }] 44 | }, 45 | { 46 | title: 'Page2', 47 | url: '/page2', 48 | children: [{ 49 | title: 'Icons', 50 | url: '/icons', 51 | icon: 'el-icon-setting' 52 | }, { 53 | title: 'stage1', 54 | url: '/stage1', 55 | icon: 'el-icon-document' 56 | }, { 57 | title: 'stage2', 58 | url: '/stage2', 59 | icon: 'el-icon-goods' 60 | }, { 61 | title: 'rest tool', 62 | url: '/zrest', 63 | icon: 'el-icon-menu' 64 | }] 65 | } 66 | ] 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/views/iconfontPreview/iconfontPreview.vue: -------------------------------------------------------------------------------- 1 | 47 | 68 | 89 | -------------------------------------------------------------------------------- /src/js/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | dom操作类:主要用来绑定事件 3 | */ 4 | function QueryEls(selector, context) { 5 | if (!selector) 6 | this.els = []; 7 | else if (typeof selector === 'string') //选择器 8 | this.els = (context || document).querySelectorAll(selector); 9 | else if (selector.addEventListener) 10 | this.els = [selector]; //单个dom元素 11 | else 12 | this.els = selector; //dom元素集合 13 | callEach(this.els, (el, i) => { 14 | this[i] = el 15 | }); //支持this[index]取dom节点 16 | } 17 | 18 | QueryEls.prototype = { 19 | //绑定事件 20 | on(event, handlerFn) { 21 | return this.each(el => el.addEventListener(event, handlerFn)) 22 | }, 23 | //解绑事件 24 | off(event, handlerFn) { 25 | return this.each(el => el.removeEventListener(event, handlerFn)) 26 | }, 27 | //从第一个子节点查找 28 | find(selector) { 29 | const el = this.els[0]; //注意,只取第一个 30 | return el ? new QueryEls(selector, el) : new QueryEls(); 31 | }, 32 | //遍历, fn(el, index) 33 | each(fn) { 34 | callEach(this.els, fn); 35 | return this; 36 | }, 37 | elDo(doByEl, index = 0) { //针对第一个元素进行操作,或者返回值 38 | const e = this.els[index]; 39 | return e && doByEl(e); 40 | }, 41 | attr(name, val) { 42 | return getOrSet(this.els, e => e.getAttribute(name), e => e.setAttribute(name, val), arguments) 43 | }, 44 | prop(name, val) { 45 | return getOrSet(this.els, e => e[name], e => e[name] = val, arguments) 46 | } 47 | } 48 | 49 | function getOrSet(els, getter, setter, args) { 50 | if (args.length === 1) { 51 | return els[0] && getter(els[0]) 52 | } else { 53 | callEach(els, el => setter(el)) 54 | } 55 | } 56 | 57 | function callEach(arr, fn) { 58 | for (let i = 0; i < arr.length; i++) { 59 | try { 60 | fn(arr[i], i); 61 | } catch (e) { 62 | } 63 | } 64 | } 65 | 66 | const $x = function (selector, context) { 67 | return new QueryEls(selector, context) 68 | }; 69 | 70 | // $x.postTo = function postTo(url, data, target) { 71 | // var form = document.createElement('form'); 72 | // form.style.display = 'none'; 73 | // form.method = 'post'; 74 | // form.action = url; 75 | // form.target = target || ''; 76 | // for (var n in data) { 77 | // if (typeof (data[n]) != 'object' && typeof (data[n]) != 'function') { 78 | // var input = form.appendChild(document.createElement('input')) 79 | // input.name = n; 80 | // input.value = data[n] 81 | // } 82 | // } 83 | // document.body.appendChild(form).submit(); 84 | // document.body.removeChild(form); 85 | // } 86 | 87 | export default $x; 88 | -------------------------------------------------------------------------------- /src/css/flexible.scss: -------------------------------------------------------------------------------- 1 | /*移动端,响应式布局 2 | var rate = 0.6,std; // 375/(1/.16) /100 = 0.6 3 | var ret = [320, 360, 375, 400, 414, 440, 480, 520, 560, 600, 640, 680, 720, 750].map(i=>{ 4 | var num = (i/0.6).toFixed(5).replace(/\.[0]+$/,''); 5 | if(i==375) std = num 6 | return ('@media (min-width:'+i+'px){html{font-size:'+ num +'% !important;}}') 7 | } 8 | ).join('\r\n') + '\r\n@media (min-width:768px){html{font-size:'+ std +'% !important;}}'; 9 | copy(ret); 10 | console.log(ret); 11 | */ 12 | 13 | /*768以上作为电脑屏,同iphone6的375。以rem为单位,每rem表示100像素*/ 14 | 15 | //@media (max-width: 319px) { 16 | // html { 17 | // font-size: 533.33333% !important; 18 | // } 19 | //} 20 | //@media (min-width: 320px) { 21 | // html { 22 | // font-size: 533.33333% !important; 23 | // } 24 | //} 25 | // 26 | //@media (min-width: 360px) { 27 | // html { 28 | // font-size: 600% !important; 29 | // } 30 | //} 31 | // 32 | //@media (min-width: 375px) { 33 | // html { 34 | // font-size: 625% !important; 35 | // } 36 | //} 37 | // 38 | //@media (min-width: 400px) { 39 | // html { 40 | // font-size: 666.66667% !important; 41 | // } 42 | //} 43 | // 44 | //@media (min-width: 414px) { 45 | // html { 46 | // font-size: 690% !important; 47 | // } 48 | //} 49 | // 50 | //@media (min-width: 440px) { 51 | // html { 52 | // font-size: 733.33333% !important; 53 | // } 54 | //} 55 | // 56 | //@media (min-width: 480px) { 57 | // html { 58 | // font-size: 800% !important; 59 | // } 60 | //} 61 | // 62 | //@media (min-width: 520px) { 63 | // html { 64 | // font-size: 866.66667% !important; 65 | // } 66 | //} 67 | // 68 | //@media (min-width: 560px) { 69 | // html { 70 | // font-size: 933.33333% !important; 71 | // } 72 | //} 73 | // 74 | //@media (min-width: 600px) { 75 | // html { 76 | // font-size: 1000% !important; 77 | // } 78 | //} 79 | // 80 | //@media (min-width: 640px) { 81 | // html { 82 | // font-size: 1066.66667% !important; 83 | // } 84 | //} 85 | // 86 | //@media (min-width: 680px) { 87 | // html { 88 | // font-size: 1133.33333% !important; 89 | // } 90 | //} 91 | // 92 | //@media (min-width: 720px) { 93 | // html { 94 | // font-size: 1200% !important; 95 | // } 96 | //} 97 | // 98 | //@media (min-width: 750px) { 99 | // html { 100 | // font-size: 1250% !important; 101 | // } 102 | //} 103 | 104 | // 上述媒体查询用这一句css全搞定 105 | // 100vw / 375px = 0.2666667 vw/px 106 | // 为便于换算,设定1rem = 100px, 即 1rem = 26.66667vw 107 | html { 108 | font-size: 26.66667vw !important; 109 | } 110 | 111 | /*768以上作为PC端处理*/ 112 | @media (min-width: 768px) { 113 | html { 114 | font-size: 625% !important; 115 | } 116 | } 117 | 118 | /*默认字体14px*/ 119 | body { 120 | font-size: 14px; 121 | } -------------------------------------------------------------------------------- /src/component/topHeader.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 65 | 100 | -------------------------------------------------------------------------------- /src/js/axios.js: -------------------------------------------------------------------------------- 1 | /* 2 | 自定义的axios 第3个参数(config)字段意义: 3 | { 4 | showError: 默认弹出toast框提示。showError='alert'表示弹出alert框。showError===false默认不弹出 5 | maskOptions: 表示请求时显示遮罩层的选项。默认{body: true} 6 | } 7 | 8 | */ 9 | 10 | import Axios from 'axios' 11 | import appConfig from '../../config/app-config' 12 | import userInfo from './utils/userInfo' 13 | import msgDlg from './utils/msgDialog' 14 | import loading from './utils/loading' 15 | 16 | const serverMap = require('../../config/serverMap.js') 17 | 18 | const axios = Axios.create({ 19 | baseURL: serverMap.base, 20 | withCredentials: true, 21 | timeout: 20000 22 | }) 23 | axios.defaults.headers.post['Content-Type'] = 'application/json' 24 | 25 | 26 | axios.defaults.transformRequest = function (request) { 27 | return JSON.stringify(request) 28 | } 29 | 30 | const ShowMsg = '系统异常,请稍后重试~' 31 | // http请求拦截器 32 | axios.interceptors.request.use(function (config) { 33 | config.headers.token = userInfo.token 34 | //将 {node_api}/xxx/yyy 的url替换为对应服务的前缀 35 | config.url = config.url.replace(/^\{(\w+)\}/, (m, $1) => serverMap[$1] || ''); 36 | 37 | //遮罩层 38 | loading.show(config.maskOptions) 39 | 40 | return config 41 | }, fail) 42 | 43 | // 0-成功,1-session超时,2-系统错误(提示ShowMsg),其他:提示错误 44 | const ResStatus = { 45 | OK: 0, 46 | SessionFail: 1, 47 | SysErr: 2, 48 | } 49 | // http响应拦截器 50 | axios.interceptors.response.use(function (res) { 51 | loading.close(res.config.maskOptions) 52 | const data = res.data || {}; 53 | const status = Number(data.status); 54 | if (status === ResStatus.SessionFail) { 55 | if (axios.inLogin) return 56 | axios.inLogin = 1 57 | return new Promise((resolve, reject) => { 58 | msgDlg.confirm('登录已过期,请重新登录').then(() => { 59 | doLogin() 60 | reject(data) 61 | }) 62 | }); 63 | } else if (status !== ResStatus.OK) { //错误 64 | console.error(data) 65 | if (status === ResStatus.SysErr || !data.msg) { 66 | data.msg = ShowMsg 67 | } 68 | showErr(res.config, data.msg); 69 | return Promise.reject(data) 70 | } 71 | return data.data 72 | }, fail); 73 | 74 | function doLogin() { 75 | return new Promise((resolve, reject) => { 76 | msgDlg.alert('尚未登录或登录超时,请重新登录', '提示', { 77 | callback: action => { 78 | if (action === 'cancel') 79 | reject(new Error()) 80 | else { 81 | let url = appConfig.LOGIN_PATH 82 | const path = location.href.match(/https?:\/\/[^\/]+(\/.+)/i)[1] 83 | if (path && path !== '/main.html#/') { 84 | url += url.indexOf('?') > -1 ? '&' : '?' 85 | url += 'redirectUrl=' + encodeURIComponent(path) 86 | } 87 | location.href = url 88 | resolve() 89 | } 90 | } 91 | }) 92 | }); 93 | } 94 | 95 | function fail(error) { 96 | if (error.config) loading.close(error.config.maskOptions); 97 | if (!error.msg) error.msg = ShowMsg; 98 | showErr(error.config, ShowMsg); 99 | console.error(error) 100 | return Promise.reject(error) 101 | } 102 | 103 | function showErr(config, errmsg) { 104 | if (errmsg) { 105 | if (config && config.showError === 'alert') 106 | msgDlg.alert(errmsg, { type: 'error' }); 107 | else 108 | msgDlg.toast.error(errmsg); 109 | } 110 | } 111 | 112 | export default axios 113 | -------------------------------------------------------------------------------- /src/iconfont/fonts/_font-preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IconFonts Preview Demo -- my-app-icon 6 | 7 | 8 | 9 | 61 | 69 | 70 |

webpack-iconfont-plugin-nodejs

71 |
Use textbox like password (prevent to save password):
72 |

73 | Modify or add a svg file in dir [src/iconfont/svgs], they will be parsed to iconfonts with css, and hot-loaded.
74 | 修改或者添加svg文件到[src/iconfont/svgs]目录,将自动生成为iconfonts以及配套的css,支持热重载。

75 |
76 |
77 |

my-app-icon (6 icons, click to copy)

78 | Class name prefix: my-icon-   You can use it like: <i class="my-icon-success"></i> 79 |
80 |
81 |
82 | 83 |
84 | 85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 | 93 |
94 | 95 |
96 |
97 | 98 |
99 | 100 |
101 |
102 | 103 |
104 | 105 |
106 |
107 | 108 |
109 | 110 |
111 |
112 | 113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-element-ui-scaffold-webpack4 2 | 3 | # 注: 4 | * **目前已升级为webpack5**,如需要使用webpack4,可切换分支:`git checkout webpack4`。 5 | 6 | * **vue3 + element-ui + webpack5 版本:可切换分支:`git checkout vue3` ** 7 | 8 | 本项目为vue下使用了element-plus并基于webpack5构建的多页面、多环境方案脚手架项目。 9 | 10 | [项目效果预览1](http://test.hz300.com/webpack4/) 11 | [项目效果预览2](https://hzsrc-vue-webpack4-elementui.netlify.com/) 12 | 13 | 14 | ## 1. 安装 15 | ``` 16 | git clone https://github.com/hzsrc/vue-element-ui-scaffold-webpack4.git 17 | # git checkout vue3 18 | cd vue-element-ui-scaffold-webpack4 19 | npm install 20 | ``` 21 | 22 | ## 2. 特性 23 | ### 基于webpack5 + babel@7 构建 24 | 更快的构建速度,更小的打包文件体积。 25 | 26 | ### 多页面实现 27 | 两种方式,自动输出html页面文件(html-webpack-plugin实现): 28 | * 在src/pages目录下添加任意js文件。js文件作为webpack入口;html页面模板是public/index.html,页面的文件名为js的文件名。 29 | * 在src/pages目录下建立任意文件夹,包含entry.js、template.html两个文件。entry.js作为webpack入口;html页面模板是template.html,页面的文件名为建立的文件夹名。 30 | 31 | ### 自动用svg生成iconfont字体图标,支持webpack热重载 32 | 开发时在src/iconfont/svgs目录下,修改或添加、删除svg文件,可自动生成字体图标(支持ttf,woff2,woff,eot,svg)及配套的css样式、html预览;同时热重载立即可以看到效果。 33 | 也可npm run build-font手动生成这些文件。 34 | 无需再手动去icomoon.io或iconfont.cn生成和修改字体图标、css、图标预览了。 35 | 基于[webpack-iconfont-plugin-nodejs](https://github.com/hzsrc/webpack-iconfont-plugin-nodejs)实现。 36 | 37 | ### mock数据实现 38 | 项目可采用[dynamic-mocker](https://github.com/hzsrc/dynamic-mocker)作为后端接口的数据模拟。 39 | 模拟数据位于mock文件夹下,采用js文件实现,易于理解且方便灵活。 40 | 41 | 启用方法: 42 | 1、npm run dev默认会同时启动mock服务。 43 | 2、单独运行:npm run mock。 44 | 45 | 配置文件: 46 | 1、config/serverMap.js中的接口服务地址为:base: '"//localhost:8085"' 47 | 2、mock/mock-config.js文件配置mock各种参数。 48 | 49 | ### element-plus按需加载,主题色全局切换 50 | css按需加载的来源直接指向element-plus的scss文件,而不是预编译的css文件。通过join-file-content-plugin插件在编译时将src/assets/css/element-theme/theme-changed.scss文件 附加到element-plus主题变量文件theme-chalk/src/common/var.scss之前,实现了在修改scss变量后即可立马查看效果,无需预先编译element-plus的scss文件为css文件。同时可以在项目任意地方引用element-plus的scss变量。 51 | 52 | ### 运行时动态调整主题色(含自写的主题样式) 53 | 利用[webpack-theme-color-replacer](https://github.com/hzsrc/webpack-theme-color-replacer)插件,在webpack构建时提取css中含有主题色的样式规则,生成一个css/theme-colors.css文件。然后在网页运行时,下载这个css文件,动态替换其中的颜色为自定义主题色。由于只提取了颜色相关的css,故速度比下载element-plus整个css要快很多。而且不仅仅是element-plus的样式,项目中的自写样式的主题色也可以一并替换掉。 54 | 55 | ### 源码映射 56 | 发布代码时生成源码映射文件到统一的源码映射文件夹,并在测试环境自动映射。生产环境为了代码安全,不进行自动映射,如需调试支持chrome通过url手动映射源码。 57 | 根据安全要求,这个源码映射文件夹名是只有开发者知道的文件夹名。或采是用动态加密算法生成此文件夹名。或者将这些源码映射文件放到需要进行登录验证的网站目录下。目录的名称请根据需要自行在`config/index.js`文件的`getSourceMapPath`函数中修改。 58 | 这样既可在出现bug需要进行线上调试时,快速手动添加源码映射来方便调试,又避免了源码泄露。 59 | 60 | ### 响应式布局 61 | 采用vw+rem的简洁方案实现响应式布局。 62 | 使用postcss-pxtorem插件自动将css中的单位由px转化为rem,开发时仍使用px做为css长度单位。1rem = 100px,调试时换算方便。pc和移动端通用(移动端最好将element-plus换为其他UI框架)。 63 | 64 | ### 浏览器兼容性 65 | 兼容IE10及以上、Chrome、Firefox、Safari、QQ、360、2345等浏览器。如果需要改为移动端,请修改.browsersrc为移动端版本。 66 | 67 | ## 3. 命令说明 68 | ### 本地开发 69 | ``` 70 | npm run dev 71 | ``` 72 | 本地开发调试。使用config/serverMap.js中的dev配置的后端接口服务地址。 73 | 74 | ### 发布测试环境 75 | ``` 76 | npm run build-test 77 | ``` 78 | 用于测试环境部署。js带源码映射,css无源码映射。使用config/serverMap.js中的test配置的接口服务地址。 79 | 80 | ### 发布生产环境 81 | ``` 82 | npm run build 83 | ``` 84 | 用于生产环境部署。使用config/serverMap.js中的prod配置的接口服务地址。 85 | 86 | ### 发布演示环境 87 | ``` 88 | npm run build-demo 89 | ``` 90 | 配置同生产环境,仅接口服务地址不同,使用config/serverMap.js中的demo配置的接口服务地址。 91 | 92 | ### 发布开发环境 93 | ``` 94 | npm run build-dev 95 | ``` 96 | 用于发布部署到开发环境服务器,适用于需要发布到服务器才能调试的情形。使用config/serverMap.js中的dev配置的接口服务地址。 97 | 98 | ### 启用mock数据发布 99 | ``` 100 | npm run build-preview 101 | ``` 102 | 会启用静态mock数据,无需后端服务,使用mock数据来模拟ajax调用(前提是对应的api接口写了mock数据)。 103 | 等同于`npm run build --preview && npm run play-dist`。 104 | 105 | ### 查看dist目录运行结果 106 | ``` 107 | npm run play-dist 108 | ``` 109 | 以dist目录为根目录,启动一个本地静态http服务,用于查看发布后dist目录的运行结果。 110 | 111 | ### 启动mock服务 112 | ``` 113 | npm run mock 114 | ``` 115 | 当后端接口服务尚未完成时,可用于模拟后端接口数据调试前端功能。 116 | 117 | 118 | ### 代理到80端口或443端口 119 | ``` 120 | npm run proxy80 121 | ``` 122 | 通过将现有端口(80xx端口)代理到80端口或443端口,可实现访问时隐藏端口,也可实现https访问。结合系统hosts配置127.0.0.1为指定的域名,可直接用域名访问本地调试页面,用于调试一些必须使用域名访问的场景(例如调试微信,详见:https://www.cnblogs.com/hz-blog/p/wechat-local-debug-domain.html)。 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/views/home/homeInner.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 104 | 105 | 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-element-ui-scaffold-webpack4", 3 | "version": "1.0.9", 4 | "description": "vue-element-ui-scaffold-webpack4", 5 | "author": "huangzheng", 6 | "private": true, 7 | "scripts": { 8 | "dev": "cross-env NODE_ENV=development ENV_CONFIG=dev node build/dev-server.js --mock", 9 | "start": "npm run dev", 10 | "build-dev": "cross-env NODE_ENV=production ENV_CONFIG=dev_build node build/build.js", 11 | "build-test": "cross-env NODE_ENV=production ENV_CONFIG=test node build/build.js", 12 | "build-demo": "cross-env NODE_ENV=production ENV_CONFIG=demo node build/build.js", 13 | "build": "cross-env NODE_ENV=production ENV_CONFIG=prod node build/build.js", 14 | "build-preview": "npm run build --preview && npm run play-dist", 15 | "play-dist": "node -e \"require('dynamic-mocker').start('./mock/static-config.js')\"", 16 | "mock": "node -e \"require('dynamic-mocker').start('./mock/mock-config.js')\" ", 17 | "proxy80": "node -e \"require('dynamic-mocker').start('./mock/mock-proxy80.js')\" ", 18 | "lint": "eslint --ext .js,.vue src", 19 | "fixlint": "eslint --fix --ext .js,.vue src mock", 20 | "build-font": "node -e \"require('./build/svg2font.js').build()\"", 21 | "show-map": "node -e \"console.log(require('./config').build.sourceMapPath)\"", 22 | "rmcache": "node -e \"require('rimraf')('./node_modules/.cache', console.log)\"", 23 | "set-names": "node -e \"require('vue-auto-name')('src')\"" 24 | }, 25 | "dependencies": { 26 | "axios": "^1.7.5", 27 | "date-any": "^1.0.7", 28 | "echarts": "^5.3.0", 29 | "element-ui": "^2.15.14", 30 | "http-vue-loader-mime": "^1.4.3", 31 | "lodash": "*", 32 | "qrcode-vue": "^1.2.0", 33 | "slot-layout": "^1.0.5", 34 | "vue": "^2.6.12", 35 | "vue-router": "^3.5.4", 36 | "vue-smooth-dnd": "^0.8.1", 37 | "vuex": "^3.6.2" 38 | }, 39 | "devDependencies": { 40 | 41 | "@babel/core": "^7.23.3", 42 | "@babel/plugin-proposal-class-properties": "^7.18.6", 43 | "@babel/preset-env": "^7.23.3", 44 | "@babel/preset-stage-2": "^7.8.3", 45 | "@babel/register": "^7.22.15", 46 | "@vue/babel-preset-jsx": "^1.4.0", 47 | "autoprefixer": "^10.2.5", 48 | "babel-eslint": "^10.1.0", 49 | "babel-loader": "^8.3.0", 50 | "babel-plugin-component": "^1.1.1", 51 | "bluebird": "^3.7.2", 52 | "chalk": "^4.1.0", 53 | "copy-dir": "^1.3.0", 54 | "copy-webpack-plugin": "^8.1.1", 55 | "core-js": "^3.33.3", 56 | "cross-env": "^7.0.3", 57 | "cross-spawn": "^7.0.3", 58 | "css-loader": "^5.2.7", 59 | "css-minimizer-webpack-plugin": "^3.4.1", 60 | "dynamic-mocker": "^1.2.19", 61 | "eslint": "^7.25.0", 62 | "eslint-config-standard": "^16.0.2", 63 | "eslint-friendly-formatter": "4.0.1", 64 | "eslint-plugin-import": "^2.28.1", 65 | "eslint-plugin-node": "11.1.0", 66 | "eslint-plugin-promise": "^5.1.0", 67 | "eslint-plugin-standard": "4.0.2", 68 | "eslint-plugin-vue": "^7.9.0", 69 | "eventsource-polyfill": "^0.9.6", 70 | "express": "^4.19.2", 71 | "file-loader": "^6.2.0", 72 | "glob": "<=7.*", 73 | "globby": "^11.0.3", 74 | "html-webpack-plugin": "^5.5.0", 75 | "join-file-content-plugin": "latest", 76 | "js-conditional-compile-loader": "^1.0.16", 77 | "js-file-tool": "^1.0.8", 78 | "mini-css-extract-plugin": "^2.7.6", 79 | "opn": "^6.0.0", 80 | "ora": "^5.4.0", 81 | "postcss-loader": "6.2.1", 82 | "postcss-pxtorem": "6.1.0", 83 | "rimraf": "^3.0.2", 84 | "sass": "=1.32.13", 85 | "sass-loader": "^13.3.2", 86 | "shelljs": "^0.8.5", 87 | "text-encode": "^1.0.3", 88 | "uglify-js": "^3.13.5", 89 | "url-loader": "^4.1.1", 90 | "vue-auto-name": "^1.0.5", 91 | "vue-loader": "^15.11.1", 92 | "vue-style-loader": "^4.1.3", 93 | "vue-template-compiler": "^2.6.12", 94 | "webpack": "^5.94.0", 95 | "webpack-bundle-analyzer": "^4.10.1", 96 | "webpack-dev-middleware": "^5.3.4", 97 | "webpack-hot-middleware": "^2.25.4", 98 | "webpack-iconfont-plugin-nodejs": "^1.0.36", 99 | "webpack-merge": "^5.10.0", 100 | "webpack-theme-color-replacer": "^1.5.2" 101 | }, 102 | "engines": { 103 | "node": ">= 4.0.0", 104 | "npm": ">= 3.0.0" 105 | }, 106 | "directories": { 107 | "test": "test" 108 | }, 109 | "repository": { 110 | "type": "git" 111 | }, 112 | "keywords": [] 113 | } 114 | -------------------------------------------------------------------------------- /src/css/utils.scss: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100% 3 | } 4 | 5 | body { 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | ul { 12 | padding-left: 0; 13 | margin: 0 14 | } 15 | 16 | li { 17 | list-style: none; 18 | } 19 | 20 | h1, h2, h3, h4, h5, h6 { 21 | margin: 0; 22 | font-weight: normal; 23 | } 24 | 25 | a { 26 | cursor: pointer; 27 | text-decoration: none; 28 | } 29 | 30 | div, section, nav, p, article, header, footer, h1, h2, h3, h4, h5, h6, ul, li, ol { 31 | box-sizing: border-box; 32 | } 33 | 34 | body, input, select, textarea, button { 35 | font-family: 'Microsoft YaHei', "PingFang SC", "Hiragino Sans GB", -apple-system, Arial; 36 | } 37 | 38 | .full { 39 | height: 100%; 40 | width: 100%; 41 | } 42 | 43 | /* 自适应布局*/ 44 | .full-ctn { 45 | position: relative; 46 | width: 100%; 47 | height: 100%; 48 | box-sizing: border-box; 49 | } 50 | 51 | .full-ctn > * { 52 | position: absolute; 53 | height: 100%; 54 | width: 100%; 55 | box-sizing: border-box; 56 | } 57 | 58 | .lined { 59 | border-bottom: 1px solid #eee; 60 | } 61 | 62 | .toplined { 63 | border-top: 1px solid #eee; 64 | } 65 | 66 | .shaddowed { 67 | box-shadow: 0 0 4px 1px rgba(#000, 0.1); 68 | } 69 | 70 | .pre { 71 | white-space: pre-wrap; 72 | line-height: 1.5; 73 | word-break: break-all; 74 | } 75 | 76 | .hide { 77 | display: none; 78 | } 79 | 80 | .hide_v { 81 | visibility: hidden; 82 | } 83 | 84 | .inlnb { 85 | display: inline-block; 86 | } 87 | 88 | .no-border { 89 | border: none; 90 | } 91 | 92 | .bordered { 93 | border: 1px solid #ddd; 94 | } 95 | 96 | .radius { 97 | border-radius: 5px; 98 | } 99 | 100 | .pointer, .pointer * { 101 | cursor: pointer; 102 | } 103 | 104 | .lay-tbl { 105 | display: table; 106 | } 107 | 108 | .lay-cell { 109 | display: table-cell; 110 | vertical-align: middle; 111 | } 112 | 113 | .left { 114 | float: left; 115 | } 116 | 117 | .right { 118 | float: right; 119 | } 120 | 121 | .clearfix { 122 | clear: both; 123 | } 124 | 125 | .clearfix-child:after { 126 | content: ''; 127 | clear: both; 128 | display: block; 129 | } 130 | 131 | .b-center { 132 | margin: auto; 133 | } 134 | 135 | .t-left { 136 | text-align: left; 137 | } 138 | 139 | .t-center { 140 | text-align: center; 141 | } 142 | 143 | .flex { 144 | display: flex; 145 | } 146 | 147 | .f-middle { 148 | display: flex; 149 | align-items: center; 150 | } 151 | 152 | .f-center { 153 | display: flex; 154 | justify-content: center; 155 | } 156 | 157 | .f-wrap { 158 | flex-wrap: wrap; 159 | } 160 | 161 | .f-center { 162 | display: flex; 163 | align-items: center; 164 | justify-content: center; 165 | } 166 | 167 | .f-column { 168 | display: flex; 169 | flex-direction: column; 170 | } 171 | 172 | .no-shrink { 173 | flex-shrink: 0; 174 | } 175 | 176 | .f-grow { 177 | flex-grow: 1; 178 | } 179 | 180 | .f-end { 181 | justify-content: flex-end; 182 | } 183 | 184 | .t-right { 185 | text-align: right; 186 | } 187 | 188 | .v-top { 189 | vertical-align: top; 190 | } 191 | 192 | .v-middle { 193 | vertical-align: middle; 194 | } 195 | 196 | .v-bottom { 197 | vertical-align: bottom; 198 | } 199 | 200 | .w100 { 201 | width: 100%; 202 | } 203 | 204 | .h100 { 205 | height: 100%; 206 | } 207 | 208 | table { 209 | border-collapse: collapse; 210 | border-spacing: 0; 211 | } 212 | 213 | .clear:after { 214 | content: "."; 215 | display: block; 216 | height: 0; 217 | clear: both; 218 | visibility: hidden 219 | } 220 | 221 | .ellipsis { 222 | white-space: nowrap; 223 | overflow: hidden; 224 | text-overflow: ellipsis; 225 | } 226 | 227 | .nowrap { 228 | white-space: nowrap; 229 | } 230 | 231 | .auto-bar { 232 | overflow: auto; 233 | } 234 | 235 | .absolute { 236 | position: absolute; 237 | } 238 | 239 | .relative { 240 | position: relative; 241 | } 242 | 243 | .white-bg { 244 | background: #fff; 245 | } 246 | 247 | .pre { 248 | white-space: pre-wrap; 249 | } 250 | 251 | .bold { 252 | font-weight: bold; 253 | } 254 | 255 | //最大4块,自动调整为4、3、2、1块并撑满 256 | .flex-4 { 257 | flex-basis: 21%; 258 | flex-grow: 1; 259 | } 260 | 261 | @media (max-width: 1400px) { 262 | .flex-4 { 263 | flex-basis: 26%; 264 | } 265 | } 266 | 267 | @media (max-width: 1100px) { 268 | .flex-4 { 269 | flex-basis: 34%; 270 | } 271 | } 272 | 273 | @media (max-width: 800px) { 274 | .flex-4 { 275 | flex-basis: 51%; 276 | } 277 | } 278 | 279 | 280 | //常用的10px 20px间距和空白 281 | $areas: (t: top, r: right, b: bottom, l: left); 282 | $sizes: 20 10; 283 | @each $size in $sizes { 284 | .pd-#{$size} { 285 | padding: #{$size}px; 286 | } 287 | .mg-#{$size} { 288 | margin: #{$size}px; 289 | } 290 | } 291 | 292 | @each $size in $sizes { 293 | @each $key, $area in $areas { 294 | .pd#{$key}-#{$size} { 295 | padding-#{$area}: #{$size}px; 296 | } 297 | .mg#{$key}-#{$size} { 298 | margin-#{$area}: #{$size}px; 299 | } 300 | } 301 | } 302 | 303 | //0间距 304 | @each $key, $area in $areas { 305 | .pd#{$key}-0 { 306 | padding-#{$area}: 0; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/iconfont/svgs/state-beinvited.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | state-beinvited 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/themeColor/themeColor.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 126 | 127 | 174 | -------------------------------------------------------------------------------- /src/iconfont/fonts/fonts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default [ 3 | { 4 | "name": "bookmark", 5 | "unicode": "12c", 6 | "svg": "\n\n \n Path 2\n Created with Sketch.\n \n \n \n \n \n \n\n" 7 | }, 8 | { 9 | "name": "calendar", 10 | "unicode": "12d", 11 | "svg": "\n \n" 12 | }, 13 | { 14 | "name": "state-beinvited", 15 | "unicode": "12e", 16 | "svg": "\n\nstate-beinvited\n\n\n" 17 | }, 18 | { 19 | "name": "success", 20 | "unicode": "12f", 21 | "svg": "\n\nsuccess\n\n\n" 22 | }, 23 | { 24 | "name": "warning", 25 | "unicode": "130", 26 | "svg": "\n\nwarning1\n\n\n" 27 | }, 28 | { 29 | "name": "pwd_msk", 30 | "unicode": "7f", 31 | "svg": "" 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /src/views/zrest/zrest.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 307 | 308 | 319 | -------------------------------------------------------------------------------- /src/iconfont/fonts/my-app-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | 55 | 58 | 61 | 64 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 94 | 97 | 100 | 103 | 106 | 109 | 112 | 115 | 118 | 121 | 124 | 127 | 130 | 133 | 136 | 139 | 142 | 145 | 148 | 151 | 154 | 157 | 160 | 163 | 166 | 169 | 172 | 175 | 178 | 181 | 184 | 187 | 190 | 193 | 196 | 199 | 202 | 205 | 208 | 211 | 214 | 217 | 220 | 223 | 226 | 229 | 232 | 235 | 238 | 241 | 244 | 247 | 250 | 253 | 256 | 259 | 262 | 265 | 268 | 271 | 274 | 277 | 280 | 283 | 286 | 289 | 292 | 295 | 298 | 301 | 304 | 307 | 310 | 313 | 314 | 315 | 316 | --------------------------------------------------------------------------------