├── .env.development ├── babel.config.js ├── src ├── store │ ├── demo │ │ ├── state.js │ │ ├── types.js │ │ ├── getters.js │ │ ├── mutations.js │ │ ├── actions.js │ │ └── index.js │ └── index.js ├── assets │ ├── logo.png │ └── less │ │ ├── base.less │ │ ├── settings.less │ │ ├── variables │ │ ├── breakpoints.less │ │ ├── colors.less │ │ ├── fonts.less │ │ └── layout.less │ │ ├── common │ │ └── common.less │ │ ├── utils │ │ ├── icon.less │ │ └── helper.less │ │ └── base │ │ └── reset.less ├── views │ ├── About.vue │ ├── common │ │ └── 404.vue │ ├── router │ │ └── router.vue │ ├── Demo.vue │ └── Home.vue ├── App.vue ├── config │ └── index.js ├── router │ ├── demo │ │ └── index.js │ └── index.js ├── axios │ ├── api │ │ ├── common.js │ │ └── user.js │ ├── consolelog.js │ └── config.js ├── main.js ├── components │ └── HelloWorld.vue └── libs │ └── tools.js ├── .env.testing ├── .env.production ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.js ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── .gitignore ├── .eslintrc.js ├── jest.config.js ├── LICENSE ├── package.json ├── README.md └── vue.config.js /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV = "development" 2 | 3 | VUE_APP_API = "/apiv1" -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'] 3 | } 4 | -------------------------------------------------------------------------------- /src/store/demo/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | demoData: [] 3 | } 4 | -------------------------------------------------------------------------------- /.env.testing: -------------------------------------------------------------------------------- 1 | NODE_ENV = "testing" 2 | 3 | VUE_APP_API = "https://test.shuke123.com" -------------------------------------------------------------------------------- /src/store/demo/types.js: -------------------------------------------------------------------------------- 1 | /* 加载列表 */ 2 | export const LOAD_DEMO = 'LOAD_DEMO' 3 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV = "production" 2 | 3 | VUE_APP_API = "https://open.shuke123.com" -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhanPhty/vuecli3-less-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhanPhty/vuecli3-less-template/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/store/demo/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | updateDemo: state => { 3 | return state 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/store/demo/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './types.js' 2 | 3 | export default { 4 | /* 加载数据 */ 5 | [types.LOAD_DEMO] (state, data) { 6 | state.demoData = data 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/less/base.less: -------------------------------------------------------------------------------- 1 | @import "settings"; 2 | @import "base/reset"; 3 | 4 | // util工具类 5 | @import "utils/icon"; 6 | @import "utils/helper"; 7 | 8 | // 全局通用 9 | @import "common/common"; 10 | -------------------------------------------------------------------------------- /src/store/demo/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './types.js' 2 | 3 | export default { 4 | /* 加载列表 */ 5 | load_demo_data: ({ commit }, data) => { 6 | commit(types.LOAD_DEMO, data) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/less/settings.less: -------------------------------------------------------------------------------- 1 | @import (reference) "variables/breakpoints"; 2 | @import (reference) "variables/colors"; 3 | @import (reference) "variables/layout"; 4 | @import (reference) "variables/fonts"; 5 | -------------------------------------------------------------------------------- /src/assets/less/variables/breakpoints.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | // Breakpoint values 4 | 5 | @small: 540px; 6 | @medium: 768px; 7 | @large: 1024px; 8 | @xLarge: 1280px; 9 | @xxLarge: 1600px; 10 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * @token在Cookie中存储的天数,默认1天 4 | */ 5 | cookieExpires: 30, 6 | /** 7 | * @默认打开的首页的路由name值,默认为home 8 | */ 9 | homeName: 'home' 10 | } 11 | -------------------------------------------------------------------------------- /src/views/common/404.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/views/router/router.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | // 引入modules 5 | import demo from './demo' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | demo 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /src/store/demo/index.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | import actions from './actions' 5 | 6 | export default { 7 | namespaced: true, 8 | state, 9 | mutations, 10 | actions, 11 | getters 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /src/assets/less/common/common.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //----------------------------------------------------- 4 | // common.less, 全局通用样式 5 | //----------------------------------------------------- 6 | .page-bg-gray { 7 | position: relative; 8 | height: 100%; 9 | width: 100%; 10 | background-color: @colorF; 11 | } 12 | -------------------------------------------------------------------------------- /src/router/demo/index.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: '', 4 | name: 'demohome', 5 | component: () => import('@/views/Home'), 6 | meta: { 7 | title: '首页' 8 | } 9 | }, 10 | { 11 | path: 'about', 12 | name: 'demoabout', 13 | component: () => import('@/views/About'), 14 | meta: { 15 | title: '关于Demo' 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/axios/api/common.js: -------------------------------------------------------------------------------- 1 | import axiosApi from '@/axios/config' 2 | 3 | /** 4 | * 用户登录 5 | * @param {String} url 接口url 6 | * @param {String} method 请求方式 7 | * @param {Obj} params 参数 8 | * params.username 用户名 9 | * params.password 密码 10 | * @param {Fn} cb 回调函数 11 | * @return 12 | */ 13 | export const get = params => { 14 | return axiosApi( 15 | { 16 | url: '/admins', 17 | method: 'get' 18 | }, 19 | params 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | // base样式 7 | import '@/assets/less/base.less' 8 | 9 | Vue.config.productionTip = false 10 | 11 | // 设置title 12 | router.beforeEach((to, from, next) => { 13 | if (to.meta.title) { 14 | document.title = to.meta.title 15 | } 16 | next() 17 | }) 18 | 19 | new Vue({ 20 | router, 21 | store, 22 | render: h => h(App) 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }], 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 6 | '^.+\\.jsx?$': 'babel-jest' 7 | }, 8 | moduleNameMapper: { 9 | '^@/(.*)$': '/src/$1' 10 | }, 11 | snapshotSerializers: ['jest-serializer-vue'], 12 | testMatch: ['**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'], 13 | testURL: 'http://localhost/' 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/less/utils/icon.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //------------------- 4 | // fonticon 5 | //------------------- 6 | @font-face { 7 | font-family: "iconfont"; 8 | } 9 | 10 | .icon, 11 | [class^="icon-"] { 12 | 13 | } 14 | 15 | .icon-pulse { 16 | animation: rotate-spin 1s infinite steps(7); 17 | } 18 | 19 | .icon-spin { 20 | animation: rotate-spin 2s infinite linear; 21 | } 22 | 23 | @keyframes rotate-spin { 24 | 0% { 25 | transform: rotate(0deg); 26 | } 27 | 28 | 100% { 29 | transform: rotate(359deg); 30 | } 31 | } 32 | 33 | .icon-add:before { content: "\e629"; } -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | // 按模块管理引用路由 5 | import demo from './demo' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | // mode: 'history', 11 | routes: [ 12 | { 13 | path: '/404', 14 | name: 'nofind', 15 | component: () => import('@/views/common/404'), 16 | meta: { 17 | title: '找不到页面' 18 | } 19 | }, 20 | { 21 | path: '/', 22 | component: () => import('@/views/Demo'), 23 | meta: { 24 | title: 'demo页面' 25 | }, 26 | children: [...demo] 27 | } 28 | ] 29 | }) 30 | -------------------------------------------------------------------------------- /src/views/Demo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/assets/less/variables/colors.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @black: #000; 4 | @white: #fff; 5 | @gold: #ac955f; 6 | @success: #4caf50; 7 | @error: #ff5252; 8 | @warning: #ffc105; 9 | @disabled: #aaa; 10 | 11 | // 背景色,文本色,边框色,链接色 12 | //----------------------------------------------------- 13 | @colorText: #333; 14 | @colorBg: #fff; 15 | @colorBorder: #dbdbdb; 16 | @colorLink: #08c; 17 | @colorPlaceholder: #999; 18 | @colorDisabled: #aaa; 19 | @colorOverlay: rgba(0, 0, 0, .7); // 遮罩层颜色 20 | 21 | 22 | // 基本颜色 23 | //----------------------------------------------------- 24 | @color3: #333; 25 | @color6: #666; 26 | @color9: #999; 27 | @colorC: #ccc; 28 | @colorF: #f5f5f5; 29 | 30 | @blue: #007aff; 31 | @orange: #ff9500; 32 | @red: #ff3b30; 33 | @green: #4cd964; 34 | @primary: #007aff; -------------------------------------------------------------------------------- /src/axios/consolelog.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | // 为生产环境修改配置... 3 | console.log('%c呀!被发现了~', 'color:red; font-size: 26px;') 4 | } else if (process.env.NODE_ENV === 'development') { 5 | // 为开发环境修改配置... 6 | console.log('%c当前开发环境\nby:zhanpthy\n461632311@qq.com', 'color:red') 7 | } else if (process.env.NODE_ENV === 'testing') { 8 | // 为测试环境修改配置... 9 | console.log('%c当前测试环境\nby:zhanpthy\n461632311@qq.com', 'color:red') 10 | } 11 | 12 | // 示例 13 | // import { login } from '@/axios/api/user' 14 | // 15 | // return new Promise((resolve, reject) => { 16 | // login(data) 17 | // .then(res => { 18 | // if (res.code === 200) { 19 | // 20 | // } 21 | // resolve(res) 22 | // }) 23 | // .catch(err => { 24 | // reject(err) 25 | // }) 26 | // }) 27 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 41 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | shuke_template 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/axios/api/user.js: -------------------------------------------------------------------------------- 1 | import axiosApi from '@/axios/config' 2 | 3 | /** 4 | * 用户登录 5 | * @param {String} url 接口url 6 | * @param {String} method 请求方式 7 | * @param {Obj} params 参数 8 | * params.username 用户名 9 | * params.password 密码 10 | * @param {Fn} cb 回调函数 11 | * @return 12 | */ 13 | export const login = params => { 14 | return axiosApi( 15 | { 16 | url: '/admins/login', 17 | method: 'put' 18 | }, 19 | params 20 | ) 21 | } 22 | 23 | /** 24 | * 用户登录 25 | * @param {String} url 接口url 26 | * @param {String} method 请求方式 27 | * @param {Obj} params 参数 28 | * params.username 用户名 29 | * params.type 找回密码type: 'modify_pwd' 30 | * @param {Fn} cb 回调函数 31 | * @return 32 | */ 33 | export const sendCode = params => { 34 | return axiosApi( 35 | { 36 | url: '/sms/sendcode', 37 | method: 'post' 38 | }, 39 | params 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/less/variables/fonts.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | // text sizes 4 | @text-x-large: 34px; 5 | @text-large: 24px; 6 | @text-medium: 20px; 7 | @text-small: 18px; 8 | @text-x-small: 15px; 9 | @text-base: 14px; 10 | 11 | // font相关 12 | //----------------------------------------------------- 13 | @fontSize: @text-base; 14 | @fontLineHeight: 1.5; 15 | @fontFamily: system-ui, -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei UI", "Microsoft YaHei", Roboto, "Hiragino Sans GB", "Source Han Sans CN", Fira Sans, "Droid Sans", "Helvetica Neue", arial, sans-serif; 16 | // 苹方, 冬青黑体,微软雅黑UI(win8.1+),微软雅黑,思源黑体(安卓) 17 | @fontCn: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", "Source Han Sans CN" sans-serif; 18 | // 前两个为safari chrome, ios4.0+, ios4.0-, Android 4.0+, Android 4.0-, windows&windows Phone, 19 | @fontEn: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Roboto, "Droid Sans", "Seogoe UI", Arial; 20 | 21 | // iconfont默认字体大小 22 | @iconfontSize: 16px; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ZhanPhty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assets/less/variables/layout.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | // Grid 4 | @max-width: 1200px; 5 | @max-width-narrow: 720px; 6 | 7 | // Spacing - 间距 8 | @horizontal-padding-small: 30px; 9 | @horizontal-padding-medium: 60px; 10 | 11 | // Containers - 容器 12 | @limitWidth: @max-width + (@horizontal-padding-medium * 2); 13 | 14 | // 元素上下间距 15 | //----------------------------------------------------- 16 | @gap: 20px; 17 | 18 | 19 | // header,footer等的高度 20 | //----------------------------------------------------- 21 | @barHeight: 44px; 22 | 23 | 24 | // radius 25 | //----------------------------------------------------- 26 | @radiusBase: 5px; 27 | @radiusSmall: 3px; 28 | 29 | 30 | // timing-function 31 | //----------------------------------------------------- 32 | @timingFunction: cubic-bezier(0.42, 0, 0.58, 1); 33 | 34 | // active state switch 35 | //----------------------------------------------------- 36 | @borderBoxSwitch: true; 37 | @activeStateSwitch: true; 38 | 39 | // z-index 40 | //----------------------------------------------------- 41 | @zIndexHeader: 1000; 42 | @zIndexFooter: 2000; 43 | @zIndexPopup: 3000; 44 | @zIndexOverlay: 4000; // 默认高于header和footer部分 45 | 46 | // z-index 47 | //----------------------------------------------------- 48 | @zIndexHeader: 1000; 49 | @zIndexFooter: 2000; 50 | @zIndexPopup: 3000; 51 | @zIndexOverlay: 4000; // 默认高于header和footer部分 -------------------------------------------------------------------------------- /src/assets/less/utils/helper.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //----------------------------------------------------- 4 | // helper less 5 | //----------------------------------------------------- 6 | 7 | // 盒子模型栅格 8 | .ui-flex { 9 | display: flex; 10 | } 11 | 12 | .ui-flex__item{ 13 | flex: 1; 14 | } 15 | 16 | .ui-flex__center { 17 | align-items: center; 18 | } 19 | 20 | // float 21 | .ui-fl { 22 | float: left; 23 | } 24 | 25 | .ui-fr { 26 | float: right; 27 | } 28 | 29 | // font-size 30 | .ui-fs12 { 31 | font-size: 12px; 32 | } 33 | 34 | .ui-fs13 { 35 | font-size: 13px; 36 | } 37 | 38 | .ui-fs14 { 39 | font-size: 14px; 40 | } 41 | 42 | .ui-fs15 { 43 | font-size: 15px; 44 | } 45 | 46 | .ui-fs16 { 47 | font-size: 16px; 48 | } 49 | 50 | .ui-fs18 { 51 | font-size: 18px; 52 | } 53 | 54 | // color 55 | .ui-color-c { 56 | color: #ccc; 57 | } 58 | 59 | .ui-color-9 { 60 | color: #999; 61 | } 62 | 63 | .ui-color-6 { 64 | color: #666; 65 | } 66 | 67 | .ui-p10 { 68 | padding: 10px; 69 | } 70 | 71 | // 字体样式 72 | .ui-fw-bold { 73 | font-weight: bold; 74 | } 75 | 76 | .ui-fw-normal { 77 | font-weight: normal !important; 78 | } 79 | 80 | .ui-full-width { 81 | width: 100%; 82 | } 83 | 84 | .ui-pos-s { 85 | position: static !important; 86 | } 87 | 88 | // 视觉隐藏元素但保持屏幕阅读器 89 | .ui-visually-hidden { 90 | border: 0; 91 | clip: rect(0 0 0 0); 92 | height: 1px; 93 | margin: -1px; 94 | overflow: hidden; 95 | padding: 0; 96 | position: absolute !important; 97 | width: 1px; 98 | } 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuecli3-template", 3 | "version": "1.0.1", 4 | "private": true, 5 | "description": "project (http://www.uizph.com)", 6 | "author": "zhanphty", 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint", 11 | "build:test": "vue-cli-service build --mode testing", 12 | "test:unit": "vue-cli-service test:unit" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.18.0", 16 | "vue": "^2.6.10", 17 | "vue-axios": "^2.1.4", 18 | "vue-router": "^3.0.6", 19 | "vuex": "^3.1.1" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^3.7.0", 23 | "@vue/cli-plugin-eslint": "^3.7.0", 24 | "@vue/cli-plugin-unit-jest": "^3.7.0", 25 | "@vue/cli-service": "^3.7.0", 26 | "@vue/eslint-config-standard": "^4.0.0", 27 | "@vue/test-utils": "^1.0.0-beta.29", 28 | "babel-core": "7.0.0-bridge.0", 29 | "babel-eslint": "^10.0.1", 30 | "babel-jest": "^23.6.0", 31 | "eslint": "^5.16.0", 32 | "eslint-plugin-vue": "^5.1.0", 33 | "less": "^3.9.0", 34 | "less-loader": "^4.1.0", 35 | "style-resources-loader": "^1.2.1", 36 | "vue-cli-plugin-env": "^1.0.0", 37 | "vue-cli-plugin-style-resources-loader": "^0.1.3", 38 | "vue-template-compiler": "^2.6.10" 39 | }, 40 | "prettier": { 41 | "printWidth": 120, 42 | "tabWidth": 2, 43 | "semi": false, 44 | "singleQuote": true 45 | }, 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions", 49 | "not ie <= 8" 50 | ], 51 | "license": "MIT" 52 | } 53 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 42 | 43 | 44 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuecli3+axios+vue-router+vuex项目通用模板 2 | 二次封装axios、预设less等、配置router、vuex 3 | 最基础的项目模板,其他插件根据需求自行添加 4 | 5 | ## 技术栈 6 | vuex + vue-router + axios + less + eslint/prettier 7 | 8 | ## 安装运行 9 | ``` 10 | git clone https://github.com/ZhanPhty/vuecli3-less-template.git 11 | 12 | cd vuecli3-template 13 | 14 | yarn 或者 npm install 15 | 16 | yarn serve 或者 npm run serve 17 | ``` 18 | 19 | ### 命令说明 20 | ``` 21 | yarn serve 或者 npm run serve // 启动开发环境 22 | 23 | yarn build:test 或者 npm run build:test // 打包测试环境 24 | 25 | yarn build 或者 npm run build // 打包生成环境 26 | 27 | yarn lint 或者 npm run lint //使用eslint+prettier格式化代码 28 | 29 | vue ui //启动图形化界面 30 | ``` 31 | 32 | ## 结构目录 33 | 34 | ```* public/ 35 | * dist/ --------打包文件夹 36 | * src/ --------源代码根目录 37 | * assets/ --------静态资源存放 38 | * less/ --------预设less 39 | * base/ --------html初始化样式 40 | * common/ --------项目通用样式 41 | * variables/ --------less参数 42 | * base.less --------页面引用文件 43 | * settings.less --------配置入口文件 44 | * axios/ --------二次封装axios,更优雅的使用Promise 45 | * api/ --------api模块存放文件夹 46 | * config.js --------封装axios配置 47 | * components/ --------模板文件 48 | * router/ --------router路由 49 | * demo/ --------配置路由demo 50 | * index.js --------router.children配置 51 | * index.js --------router模块统一引用入口 52 | * store/ --------vuex 53 | * demo/ --------配置vuex示例(模块化) 54 | * index.js --------vuex模块统一引用入口 55 | * views/ --------视图 56 | * tests/ --------单元测试 57 | * .env.development --------开发环境配置文件 58 | * .env.production --------生产环境配置文件 59 | * .env.testing --------测试环境配置文件 60 | * .eslintrc.js --------eslint配置文件 61 | * vue.config.js --------vuecli3配置文件 62 | ``` 63 | ## 项目说明 64 | 在vue-cli3脚手架的基础上,添加了一些必要的配置文件,同时也保持最纯洁的模板,根据项目可引用其他框架。 65 | 更适合大型项目及多人协作 66 | * 添加了一些less参数、reset.css等 67 | * 对axios二次封装,可以更优雅的请求接口了 68 | * 对router模块化引用,方便维护 69 | * vuex同样模块化,分类管理 70 | 71 | 72 | ### less 73 | 使用 `style-resources-loader` 配置全局参数 74 | 75 | 76 | ### axios 77 | 封装后的axios,更优雅的使用axios 78 | ##### 示例(@/axios/api/user.js文件下的login接口) 79 | 首先页面引用文件 80 | `import { login } from 'api/user'` 81 | 82 | 调用login() 83 | `login({ 84 | userid: 1234 85 | }).then(res => { 86 | console.log(res) 87 | }) 88 | ` 89 | 90 | ### router 91 | 模块化router,对一级以下的路由`(routes.children)`进行分类管理 92 | 93 | 94 | ### store 95 | 模块化vuex,使用官方的modules引入各个模块 96 | * index.js 97 | * state.js 98 | * types.js 99 | * actions.js 100 | * getters.js 101 | * mutations.js 102 | 103 | 104 | ### vue.config.js 105 | > pluginOptions.env.TEST 106 | 107 | 自定义全局变量,打印测试`console.log(process.env.TEST)` 108 | 109 | 110 | ##### vue-cli其他配置 111 | 传送门: [vue-cli3](https://cli.vuejs.org/zh/). 112 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const resolve = (dir) => path.join(__dirname, dir) 3 | 4 | module.exports = { 5 | // 项目部署的基础路径 6 | // 我们默认假设你的应用将会部署在域名的根部, 7 | // 比如 https://www.my-app.com/ 8 | // 如果你的应用时部署在一个子路径下,那么你需要在这里 9 | // 指定子路径。比如,如果你的应用部署在 10 | // https://www.foobar.com/my-app/ 11 | // 那么将这个值改为 `/my-app/` 12 | publicPath: './', 13 | 14 | // 将构建好的文件输出到哪里 15 | outputDir: 'dist', 16 | 17 | // 放置静态资源的地方 (js/css/img/font/...) 18 | assetsDir: './static', 19 | 20 | // 默认起始页文件 21 | indexPath: 'index.html', 22 | 23 | // 默认在生成的静态资源文件名中包含hash以控制缓存 24 | filenameHashing: true, 25 | 26 | // 是否在保存的时候使用 `eslint-loader` 进行检查。 27 | // 有效的值:`ture` | `false` | `"error"` 28 | // 当设置为 `"error"` 时,检查出的错误会触发编译失败。 29 | lintOnSave: true, 30 | 31 | // 使用带有浏览器内编译器的完整构建版本 32 | // 查阅 https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时 33 | // compiler: false, 34 | 35 | // 是否为生产环境构建生成 source map? 36 | productionSourceMap: false, 37 | 38 | // 调整内部的 webpack 配置。 39 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/webpack.md 40 | chainWebpack: config => { 41 | config.resolve.alias 42 | .set('@', resolve('src')) 43 | .set('components', resolve('src/components')) 44 | .set('assets', resolve('src/assets')) 45 | .set('api', resolve('src/axios/api')) 46 | .set('libs', resolve('src/libs')) 47 | 48 | config.output.chunkFilename(`js/[name].[chunkhash:8].js`) 49 | }, 50 | 51 | configureWebpack: () => { 52 | if (process.env.NODE_ENV === 'production') { 53 | // 为生产环境修改配置... 54 | console.log('生产环境') 55 | } else if (process.env.NODE_ENV === 'development') { 56 | // 为开发环境修改配置... 57 | console.log('开发环境') 58 | } else if (process.env.NODE_ENV === 'testing') { 59 | // 为测试环境修改配置... 60 | console.log('测试环境') 61 | } 62 | }, 63 | 64 | // CSS 相关选项 65 | css: { 66 | // 是否开启 CSS source map? 67 | sourceMap: process.env.NODE_ENV !== 'production', 68 | 69 | // 为预处理器的 loader 传递自定义选项。比如传递给 70 | loaderOptions: { 71 | less: { 72 | javascriptEnabled: true 73 | } 74 | }, 75 | 76 | // 为所有的 CSS 及其预处理文件开启 CSS Modules。 77 | // 这个选项不会影响 `*.vue` 文件。 78 | modules: false 79 | }, 80 | 81 | // 在生产环境下为 Babel 和 TypeScript 使用 `thread-loader` 82 | // 在多核机器下会默认开启。 83 | parallel: require('os').cpus().length > 1, 84 | 85 | // 配置 webpack-dev-server 行为。 86 | devServer: { 87 | host: 'localhost', 88 | port: 8020, 89 | https: false, 90 | hotOnly: false, 91 | open: false, 92 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/cli-service.md#配置代理 93 | proxy: { 94 | '/apiv1': { 95 | target: 'https://open.shuke123.com', 96 | changeOrigin: true, 97 | pathRewrite: { 98 | '^/apiv1': '/' 99 | } 100 | }, 101 | '/apiv2': { 102 | target: 'http://abc.pwdev.club', 103 | changeOrigin: true, 104 | pathRewrite: { 105 | '^/apiv2': '/' 106 | } 107 | } 108 | } 109 | }, 110 | 111 | // 第三方插件的选项 112 | pluginOptions: { 113 | env: { 114 | TEST: 'vue.config.js-->pluginOptions.env:TEST Global Parameters' 115 | }, 116 | 'style-resources-loader': { 117 | preProcessor: 'less', 118 | patterns: [ 119 | path.resolve(__dirname, './src/assets/less/settings.less') 120 | ] 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/axios/config.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import vueAxios from 'vue-axios' 4 | 5 | // 打印控制台,跳过eslint 6 | import consolelog from '@/axios/consolelog' // eslint-disable-line 7 | 8 | Vue.use(vueAxios, axios) 9 | 10 | /** 11 | * [http request 拦截器] 12 | * @return 13 | */ 14 | axios.interceptors.request.use( 15 | config => { 16 | // 判断localStorage中是否存在api_token 17 | if (localStorage.getItem('access_token')) { 18 | // 存在将access_token写入 request header 19 | config.headers = { 20 | 'access-token': `${localStorage.getItem('access_token')}` 21 | } 22 | } 23 | return config 24 | }, 25 | error => { 26 | return Promise.reject(error) 27 | } 28 | ) 29 | 30 | /** 31 | * [返回状态判断(添加响应拦截器)] 32 | * @return 33 | */ 34 | axios.interceptors.response.use( 35 | response => { 36 | return response 37 | }, 38 | error => { 39 | return Promise.resolve(error.response) 40 | } 41 | ) 42 | 43 | function errorState (response) { 44 | // 如果http状态码正常,则直接返回数据 45 | if (response && (response.status === 200 || response.status === 304 || response.status === 400)) { 46 | return response.data 47 | } else { 48 | return { 49 | status: -404, 50 | msg: '网络异常' 51 | } 52 | } 53 | } 54 | 55 | function successState (res) { 56 | // 统一判断后端返回的错误码 57 | if (res.data.code === 200) { 58 | return true 59 | } else { 60 | toast(res.data.msg || '网络错误') 61 | } 62 | } 63 | 64 | /** 65 | * [toast 弹窗] 66 | * @param {String} text 内容 67 | * @param {Number} duration 延迟 68 | * @return 69 | */ 70 | function toast (text, duration) { 71 | if (toast.busy) return 72 | toast.busy = true 73 | duration = duration || 2500 74 | setTimeout(function () { 75 | toast.busy = false 76 | }, duration) 77 | 78 | let div = document.createElement('div') 79 | 80 | Object.assign(div.style, { 81 | padding: '5px 10px', 82 | color: '#fff', 83 | fontSize: '12px', 84 | lineHeight: 2, 85 | position: 'fixed', 86 | top: '50%', 87 | margin: '-100px auto 0', 88 | left: 0, 89 | right: 0, 90 | width: '150px', 91 | textAlign: 'center', 92 | borderRadius: '5px', 93 | zIndex: 99999999, 94 | background: 'rgba(0,0,0,0.7)' 95 | }) 96 | div.classList.add('toast') 97 | div.textContent = text 98 | document.body.appendChild(div) 99 | 100 | setTimeout(function () { 101 | div.parentNode && div.parentNode.removeChild(div) 102 | }, duration) 103 | } 104 | 105 | /** 106 | * [confuse 添加公共参数] 107 | * @param {Obj} data 参数 108 | * data.auth_key [验证] 109 | * data.token [token --> 参数 + token] 110 | * data.timestamp [时间戳] 111 | * @return {Obj} 112 | */ 113 | function confuse (data) { 114 | // let paraStr = '' 115 | 116 | // for (let value of Object.keys(data).sort()) { 117 | // if (data[value] !== undefined && data[value] !== null) { 118 | // paraStr += data[value].toString() 119 | // } 120 | // } 121 | // data.auth_key = process.env.VUE_APP_AUTH_KEY 122 | // data.token = md5(paraStr + process.env.VUE_APP_TOKEN) 123 | // data.timestamp = new Date().getTime() 124 | 125 | return data 126 | } 127 | 128 | /** 129 | * [配置axios] 130 | * @param {Obj} opts 配置 131 | * opts.method 请求方式 [*必填] 132 | * opts.baseURL axios默认url 133 | * opts.url 请求url [*必填] 134 | * opts.headers 请求headers 135 | * @param {Obj} data 请求数据 136 | * @return {Obj} res 137 | */ 138 | const httpServer = (opts, data) => { 139 | // 设置默认headers 140 | let headers = {} 141 | 142 | switch (opts.method) { 143 | case 'post': 144 | headers = { 'X-Requested-With': 'XMLHttpRequest' } 145 | break 146 | case 'get': 147 | break 148 | case 'put': 149 | headers = { 'X-Requested-With': 'XMLHttpRequest' } 150 | break 151 | case 'delete': 152 | break 153 | } 154 | 155 | // http默认配置 156 | let httpDefaultOpts = { 157 | method: opts.method, // 必填 158 | baseURL: opts.baseURL || process.env.VUE_APP_API, 159 | url: opts.url, // 必填 160 | timeout: 10 * 1000, 161 | params: confuse(data), 162 | data: confuse(data), 163 | headers: Object.assign(headers, opts.headers) 164 | } 165 | 166 | if (opts.method === 'get') { 167 | delete httpDefaultOpts.data 168 | } else { 169 | delete httpDefaultOpts.params 170 | } 171 | 172 | let promise = new Promise((resolve, reject) => { 173 | axios(httpDefaultOpts) 174 | .then(res => { 175 | successState(res) 176 | resolve(res.data) 177 | }) 178 | .catch(response => { 179 | errorState(response) 180 | reject(response) 181 | }) 182 | }) 183 | 184 | return promise 185 | } 186 | 187 | export default httpServer 188 | -------------------------------------------------------------------------------- /src/libs/tools.js: -------------------------------------------------------------------------------- 1 | export const forEach = (arr, fn) => { 2 | if (!arr.length || !fn) return 3 | let i = -1 4 | let len = arr.length 5 | while (++i < len) { 6 | let item = arr[i] 7 | fn(item, i, arr) 8 | } 9 | } 10 | 11 | /** 12 | * @param {Array} arr1 13 | * @param {Array} arr2 14 | * @description 得到两个数组的交集, 两个数组的元素为数值或字符串 15 | */ 16 | export const getIntersection = (arr1, arr2) => { 17 | let len = Math.min(arr1.length, arr2.length) 18 | let i = -1 19 | let res = [] 20 | while (++i < len) { 21 | const item = arr2[i] 22 | if (arr1.indexOf(item) > -1) res.push(item) 23 | } 24 | return res 25 | } 26 | 27 | /** 28 | * @param {Array} arr1 29 | * @param {Array} arr2 30 | * @description 得到两个数组的并集, 两个数组的元素为数值或字符串 31 | */ 32 | export const getUnion = (arr1, arr2) => { 33 | return Array.from(new Set([...arr1, ...arr2])) 34 | } 35 | 36 | /** 37 | * @param {Array} target 目标数组 38 | * @param {Array} arr 需要查询的数组 39 | * @description 判断要查询的数组是否至少有一个元素包含在目标数组中 40 | */ 41 | export const hasOneOf = (targetarr, arr) => { 42 | return targetarr.some(_ => arr.indexOf(_) > -1) 43 | } 44 | 45 | /** 46 | * @param {String|Number} value 要验证的字符串或数值 47 | * @param {*} validList 用来验证的列表 48 | */ 49 | export function oneOf (value, validList) { 50 | for (let i = 0; i < validList.length; i++) { 51 | if (value === validList[i]) { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | /** 59 | * @param {Number} timeStamp 判断时间戳格式是否是毫秒 60 | * @returns {Boolean} 61 | */ 62 | const isMillisecond = timeStamp => { 63 | const timeStr = String(timeStamp) 64 | return timeStr.length > 10 65 | } 66 | 67 | /** 68 | * @param {Number} timeStamp 传入的时间戳 69 | * @param {Number} currentTime 当前时间时间戳 70 | * @returns {Boolean} 传入的时间戳是否早于当前时间戳 71 | */ 72 | const isEarly = (timeStamp, currentTime) => { 73 | return timeStamp < currentTime 74 | } 75 | 76 | /** 77 | * @param {Number} num 数值 78 | * @returns {String} 处理后的字符串 79 | * @description 如果传入的数值小于10,即位数只有1位,则在前面补充0 80 | */ 81 | const getHandledValue = num => { 82 | return num < 10 ? '0' + num : num 83 | } 84 | 85 | /** 86 | * @param {Number} timeStamp 传入的时间戳 87 | * @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间 88 | */ 89 | const getDate = (timeStamp, startType) => { 90 | const d = new Date(timeStamp * 1000) 91 | const year = d.getFullYear() 92 | const month = getHandledValue(d.getMonth() + 1) 93 | const date = getHandledValue(d.getDate()) 94 | const hours = getHandledValue(d.getHours()) 95 | const minutes = getHandledValue(d.getMinutes()) 96 | const second = getHandledValue(d.getSeconds()) 97 | let resStr = '' 98 | if (startType === 'year') resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second 99 | else resStr = month + '-' + date + ' ' + hours + ':' + minutes 100 | return resStr 101 | } 102 | 103 | /** 104 | * @param {String|Number} timeStamp 时间戳 105 | * @returns {String} 相对时间字符串 106 | */ 107 | export const getRelativeTime = timeStamp => { 108 | // 判断当前传入的时间戳是秒格式还是毫秒 109 | const IS_MILLISECOND = isMillisecond(timeStamp) 110 | // 如果是毫秒格式则转为秒格式 111 | if (IS_MILLISECOND) Math.floor((timeStamp /= 1000)) 112 | // 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型 113 | timeStamp = Number(timeStamp) 114 | // 获取当前时间时间戳 115 | const currentTime = Math.floor(Date.parse(new Date()) / 1000) 116 | // 判断传入时间戳是否早于当前时间戳 117 | const IS_EARLY = isEarly(timeStamp, currentTime) 118 | // 获取两个时间戳差值 119 | let diff = currentTime - timeStamp 120 | // 如果IS_EARLY为false则差值取反 121 | if (!IS_EARLY) diff = -diff 122 | let resStr = '' 123 | const dirStr = IS_EARLY ? '前' : '后' 124 | // 少于等于59秒 125 | if (diff <= 59) resStr = diff + '秒' + dirStr 126 | // 多于59秒,少于等于59分钟59秒 127 | else if (diff > 59 && diff <= 3599) resStr = Math.floor(diff / 60) + '分钟' + dirStr 128 | // 多于59分钟59秒,少于等于23小时59分钟59秒 129 | else if (diff > 3599 && diff <= 86399) resStr = Math.floor(diff / 3600) + '小时' + dirStr 130 | // 多于23小时59分钟59秒,少于等于29天59分钟59秒 131 | else if (diff > 86399 && diff <= 2623859) resStr = Math.floor(diff / 86400) + '天' + dirStr 132 | // 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前 133 | else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) resStr = getDate(timeStamp) 134 | else resStr = getDate(timeStamp, 'year') 135 | return resStr 136 | } 137 | 138 | /** 139 | * @returns {String} 当前浏览器名称 140 | */ 141 | export const getExplorer = () => { 142 | const ua = window.navigator.userAgent 143 | const isExplorer = exp => { 144 | return ua.indexOf(exp) > -1 145 | } 146 | if (isExplorer('MSIE')) return 'IE' 147 | else if (isExplorer('Firefox')) return 'Firefox' 148 | else if (isExplorer('Chrome')) return 'Chrome' 149 | else if (isExplorer('Opera')) return 'Opera' 150 | else if (isExplorer('Safari')) return 'Safari' 151 | } 152 | 153 | /** 154 | * @description 绑定事件 on(element, event, handler) 155 | */ 156 | export const on = (function () { 157 | if (document.addEventListener) { 158 | return function (element, event, handler) { 159 | if (element && event && handler) { 160 | element.addEventListener(event, handler, false) 161 | } 162 | } 163 | } else { 164 | return function (element, event, handler) { 165 | if (element && event && handler) { 166 | element.attachEvent('on' + event, handler) 167 | } 168 | } 169 | } 170 | })() 171 | 172 | /** 173 | * @description 解绑事件 off(element, event, handler) 174 | */ 175 | export const off = (function () { 176 | if (document.removeEventListener) { 177 | return function (element, event, handler) { 178 | if (element && event) { 179 | element.removeEventListener(event, handler, false) 180 | } 181 | } 182 | } else { 183 | return function (element, event, handler) { 184 | if (element && event) { 185 | element.detachEvent('on' + event, handler) 186 | } 187 | } 188 | } 189 | })() 190 | 191 | /** 192 | * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性 193 | * 如果没有传入key这个参数,则判断obj对象是否有键值对 194 | */ 195 | export const hasKey = (obj, key) => { 196 | if (key) return key in obj 197 | else { 198 | let keysArr = Object.keys(obj) 199 | return keysArr.length 200 | } 201 | } 202 | 203 | /** 204 | * @param {*} obj1 对象 205 | * @param {*} obj2 对象 206 | * @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串 207 | */ 208 | export const objEqual = (obj1, obj2) => { 209 | const keysArr1 = Object.keys(obj1) 210 | const keysArr2 = Object.keys(obj2) 211 | if (keysArr1.length !== keysArr2.length) return false 212 | else if (keysArr1.length === 0 && keysArr2.length === 0) return true 213 | else return !keysArr1.some(key => obj1[key] !== obj2[key]) 214 | } 215 | -------------------------------------------------------------------------------- /src/assets/less/base/reset.less: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //----------------------------------------------------- 4 | // reset less 5 | // 包括normalize,清零重置 6 | //----------------------------------------------------- 7 | 8 | // normalize 7.0 9 | // http://necolas.github.io/normalize.css/ 10 | //----------------------------------------------------- 11 | 12 | // 1. Prevent mobile text size adjust after orientation change, without disabling user zoom. 13 | // 2. Remove the gray background color from tap, default value is inherit 14 | html { 15 | -ms-text-size-adjust: 100%; // 1 16 | -webkit-text-size-adjust: 100%; // 1 17 | -webkit-tap-highlight-color: transparent; // 2 18 | height: 100%; 19 | } 20 | 21 | // 1. Remove default margin 22 | body { 23 | margin: 0; // 1 24 | font-size: @fontSize; 25 | font-family: @fontFamily; 26 | line-height: @fontLineHeight; 27 | color: @colorText; 28 | background-color: @colorBg; 29 | min-height: 100%; 30 | padding-top: env(safe-area-inset-top); //为导航栏+状态栏的高度 88px 31 | padding-left: env(safe-area-inset-left); //如果未竖屏时为0 32 | padding-right: env(safe-area-inset-right); //如果未竖屏时为0 33 | padding-bottom: env(safe-area-inset-bottom); //为底下圆弧的高度 34px 34 | } 35 | 36 | // HTML5 display definitions 37 | //----------------------------------------------------- 38 | 39 | // Correct `block` display not defined for any HTML5 element in IE 8/9. 40 | // Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox. 41 | // Correct `block` display not defined for `main` in IE 11. 42 | article, 43 | aside, 44 | details, 45 | figcaption, 46 | figure, 47 | footer, 48 | header, 49 | hgroup, 50 | main, 51 | menu, 52 | nav, 53 | section, 54 | summary { 55 | display: block; 56 | } 57 | 58 | // 1. Correct `inline-block` display not defined in IE 9-. 59 | audio, 60 | canvas, 61 | progress, 62 | video { 63 | display: inline-block; // 1 64 | } 65 | 66 | // Add the correct display in iOS 4-7. 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | // Add the correct vertical alignment in Chrome, Firefox, and Opera. 73 | progress { 74 | vertical-align: baseline; 75 | } 76 | 77 | // Address `[hidden]` styling not present in IE 8/9/10. 78 | // Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 79 | [hidden], 80 | template { 81 | display: none; 82 | } 83 | 84 | // Links 85 | //----------------------------------------------------- 86 | 87 | // 1. Remove the gray background color from active links in IE 10. 88 | // 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 89 | // 3. Improve readability when focused and also mouse hovered in all browsers. 90 | 91 | a { 92 | background: transparent; // 1 93 | -webkit-text-decoration-skip: objects; 94 | /* 2 */ 95 | text-decoration: none; 96 | color: nth(@colorLink, 1); 97 | 98 | &:active { 99 | outline: 0; // 3 100 | } 101 | } 102 | 103 | // Text-level semantics 104 | //----------------------------------------------------- 105 | 106 | // 1. Remove the bottom border in Chrome 57- and Firefox 39-. 107 | // 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 108 | 109 | abbr[title] { 110 | border-bottom: none; 111 | /* 1 */ 112 | text-decoration: underline; 113 | /* 2 */ 114 | text-decoration: underline dotted; 115 | /* 2 */ 116 | } 117 | 118 | // Prevent the duplicate application of `bolder` by the next rule in Safari 6. 119 | b, 120 | strong { 121 | font-weight: inherit; 122 | } 123 | 124 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 125 | b, 126 | strong { 127 | font-weight: bold; 128 | } 129 | 130 | // Address styling not present in Safari and Chrome. 131 | dfn { 132 | font-style: italic; 133 | } 134 | 135 | // Address styling not present in IE 8/9. 136 | mark { 137 | background: #ff0; 138 | color: #000; 139 | } 140 | 141 | // Address inconsistent and variable font size in all browsers. 142 | small { 143 | font-size: 80%; 144 | } 145 | 146 | // Prevent `sub` and `sup` affecting `line-height` in all browsers. 147 | sub, 148 | sup { 149 | font-size: 75%; 150 | line-height: 0; 151 | position: relative; 152 | vertical-align: baseline; 153 | } 154 | 155 | sup { 156 | top: -0.5em; 157 | } 158 | 159 | sub { 160 | bottom: -0.25em; 161 | } 162 | 163 | // Embedded content 164 | //----------------------------------------------------- 165 | 166 | // 1. Remove border when inside `a` element in IE 8/9/10. 167 | img { 168 | border: 0; // 1 169 | vertical-align: middle; 170 | max-width: 100%; 171 | } 172 | 173 | // Correct overflow not hidden in IE 9/10/11. 174 | svg:not(:root) { 175 | overflow: hidden; 176 | } 177 | 178 | // Grouping content 179 | //----------------------------------------------------- 180 | 181 | // 1. Contain overflow in all browsers. 182 | // 2. Improve readability of pre-formatted text in all browsers. 183 | pre { 184 | overflow: auto; // 1 185 | white-space: pre; // 2 186 | white-space: pre-wrap; // 2 187 | word-wrap: break-word; // 2 188 | } 189 | 190 | // 1. Address odd `em`-unit font size rendering in all browsers. 191 | code, 192 | kbd, 193 | pre, 194 | samp { 195 | font-family: monospace, monospace; // 1 196 | font-size: 1em; // 2 197 | } 198 | 199 | // Forms 200 | //----------------------------------------------------- 201 | 202 | // Known limitation: by default, Chrome and Safari on OS X allow very limited 203 | // styling of `select`, unless a `border` property is set. 204 | 205 | // 1. Correct color not being inherited. 206 | // Known issue: affects color of disabled elements. 207 | // 2. Correct font properties not being inherited. 208 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 209 | button, 210 | input, 211 | optgroup, 212 | select, 213 | textarea { 214 | color: inherit; // 1 215 | font: inherit; // 2 216 | margin: 0; // 3 217 | vertical-align: middle; 218 | } 219 | 220 | // Show the overflow in IE. 221 | // 1. Show the overflow in Edge. 222 | // 2. Show the overflow in Edge, Firefox, and IE. 223 | button, 224 | input, 225 | // 1 226 | select { 227 | // 2 228 | overflow: visible; 229 | } 230 | 231 | // Address inconsistent `text-transform` inheritance for `button` and `select`. 232 | // All other form control elements do not inherit `text-transform` values. 233 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 234 | // Correct `select` style inheritance in Firefox. 235 | button, 236 | select { 237 | text-transform: none; 238 | } 239 | 240 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 241 | // and `video` controls. 242 | // 2. Correct inability to style clickable `input` types in iOS. 243 | // 3. Improve usability and consistency of cursor style between image-type 244 | // `input` and others. 245 | button, 246 | html input[type="button"], 247 | // 1 248 | input[type="reset"], 249 | input[type="submit"] { 250 | -webkit-appearance: button; // 2 251 | cursor: pointer; // 3 252 | } 253 | 254 | // Re-set default cursor for disabled elements. 255 | [disabled] { 256 | cursor: default; 257 | } 258 | 259 | // Remove inner padding and border in Firefox 4+. 260 | button::-moz-focus-inner, 261 | input::-moz-focus-inner { 262 | border: 0; 263 | padding: 0; 264 | } 265 | 266 | // 1. Address Firefox 4+ setting `line-height` on `input` using `!important` in 267 | // the UA stylesheet. 268 | input { 269 | line-height: normal; // 1 270 | border-width: 0; 271 | } 272 | 273 | // It's recommended that you don't attempt to style these elements. 274 | // Firefox's implementation doesn't respect box-sizing, padding, or width. 275 | 276 | // 1. Address box sizing set to `content-box` in IE 8/9/10. 277 | // 2. Remove excess padding in IE 8/9/10. 278 | input[type="checkbox"], 279 | input[type="radio"] { 280 | box-sizing: border-box; // 1 281 | padding: 0; // 2 282 | } 283 | 284 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain 285 | // `font-size` values of the `input`, it causes the cursor style of the 286 | // decrement button to change from `default` to `text`. 287 | input[type="number"]::-webkit-inner-spin-button, 288 | input[type="number"]::-webkit-outer-spin-button { 289 | height: auto; 290 | } 291 | 292 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome. 293 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome 294 | // (include `-moz` to future-proof). 295 | input[type="search"] { 296 | -webkit-appearance: textfield; // 1 297 | box-sizing: border-box; 298 | } 299 | 300 | // Remove inner padding and search cancel button in Safari and Chrome on OS X. 301 | // Safari (but not Chrome) clips the cancel button when the search input has 302 | // padding (and `textfield` appearance). 303 | input[type="search"]::-webkit-search-cancel-button, 304 | input[type="search"]::-webkit-search-decoration { 305 | -webkit-appearance: none; 306 | } 307 | 308 | // Define consistent border, margin, and padding. 309 | fieldset { 310 | border: 1px solid #c0c0c0; 311 | margin: 0 2px; 312 | padding: 0.35em 0.625em 0.75em; 313 | } 314 | 315 | // 1. Correct the text wrapping in Edge and IE. 316 | // 2. Correct the color inheritance from `fieldset` elements in IE. 317 | // 3. Remove the padding so developers are not caught out when they zero out 318 | // `fieldset` elements in all browsers. 319 | legend { 320 | box-sizing: border-box; 321 | /* 1 */ 322 | color: inherit; 323 | /* 2 */ 324 | display: table; 325 | /* 1 */ 326 | max-width: 100%; 327 | /* 1 */ 328 | padding: 0; 329 | /* 3 */ 330 | white-space: normal; 331 | /* 1 */ 332 | } 333 | 334 | // 1. Remove default vertical scrollbar in IE 8/9/10/11. 335 | textarea { 336 | overflow: auto; // 1 337 | resize: vertical; 338 | vertical-align: top; 339 | } 340 | 341 | // Don't inherit the `font-weight` (applied by a rule above). 342 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 343 | optgroup { 344 | font-weight: bold; 345 | } 346 | 347 | // webkit focus outline 348 | input, 349 | select, 350 | textarea, 351 | button { 352 | outline: 0; 353 | } 354 | 355 | // Android 下 input focus 消除高亮外框 356 | textarea, 357 | input { 358 | -webkit-user-modify: read-write-plaintext-only; 359 | } 360 | 361 | // ie10 clear & password 362 | input::-ms-clear, 363 | input::-ms-reveal { 364 | display: none; 365 | } 366 | 367 | // 表单placeholder样式 368 | // 注意不可联合申明,否则无效 369 | // Firefox 19+ 370 | input::-moz-placeholder, 371 | textarea::-moz-placeholder { 372 | color: @colorPlaceholder; 373 | } 374 | 375 | // Internet Explorer 10+ 376 | input:-ms-input-placeholder, 377 | textarea:-ms-input-placeholder { 378 | color: @colorPlaceholder; 379 | } 380 | 381 | // Safari and Chrome 382 | input::-webkit-input-placeholder, 383 | textarea::-webkit-input-placeholder { 384 | color: @colorPlaceholder; 385 | } 386 | 387 | // 如不支持placeholder添加class 或者 设置提示的placeholder 388 | .placeholder { 389 | color: @colorPlaceholder; 390 | } 391 | 392 | // Tables 393 | //----------------------------------------------------- 394 | // Remove most spacing between table cells. 395 | table { 396 | border-collapse: collapse; 397 | border-spacing: 0; 398 | } 399 | 400 | td, 401 | th { 402 | padding: 0; 403 | } 404 | 405 | // 根据使用习惯,对normalize上进行补充 406 | //----------------------------------------------------- 407 | 408 | h1, 409 | h2, 410 | h3, 411 | h4, 412 | h5, 413 | h6, 414 | p, 415 | figure, 416 | form, 417 | blockquote { 418 | margin: 0; 419 | } 420 | 421 | // ul ol dl 422 | ul, 423 | ol, 424 | li, 425 | dl, 426 | dd { 427 | margin: 0; 428 | padding: 0; 429 | } 430 | 431 | ul, 432 | ol { 433 | list-style: none outside none; 434 | } 435 | 436 | // hn 437 | h1, 438 | h2, 439 | h3 { 440 | line-height: 2; 441 | font-weight: normal; 442 | } 443 | 444 | h1 { 445 | font-size: 18px; 446 | } 447 | 448 | h2 { 449 | font-size: 16px; 450 | } 451 | 452 | h3 { 453 | font-size: 14px; 454 | } 455 | 456 | // icon 457 | i { 458 | font-style: normal; 459 | } 460 | 461 | // 清除子元素浮动 462 | .clearfix { 463 | } 464 | --------------------------------------------------------------------------------