├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── main.js ├── plugins │ └── axios.js ├── router.js ├── store.js └── views │ ├── About.vue │ └── Home.vue └── vue.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 樊小书生 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cli-vant-starter # 2 | 3 | vue-cli 和 vant 结合的项目开发模板,主要内容如下(后续新增的内容会在其后使用方括号标识添加时间): 4 | 5 | 1. 将 `axios` 或者 `$axios` 添加到 `Vue.prototype` ,对 `axios` 的封装是基于 [vue-cli-plugin-axios](https://www.npmjs.com/package/vue-cli-plugin-axios) 插件。 6 | 2. 对 `axios` 的拦截器进行配置,调用 `axios` 发送请求的时候自动添加 `loading` 效果,请求返回的时候,判断请求内容的正确,错误的话直接提示错误信息。 7 | 3. 并发请求时,只有当最后一个请求完成的时候才会关闭 `loading` 效果。 8 | 4. 添加在路由切换的时候取消上个页面未完成的 `axios` 请求。 9 | 5. 添加本地代理 [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) 及相应的配置说明。 10 | 6. 增加自定义参数: 11 | 1. `showLoading`: 是否需要 `loading` 加载,至于样式仍是有你自由控制。 12 | 2. 关于在短时间内发送多条相同请求处理逻辑的相关字段: 13 | 1. 本插件默认是按照仅保留最初的发送请求链接。 14 | 2. `needLast`: 需要最新的发送请求链接,以前发送的可以直接 `cancel` ,这种情况一般适用于搜索框发送请求。 15 | 3. `needAll`: 所有的发送请求链接都需要 16 | 17 | 7. vant 引入组件采用的是[自动按需引入插件](https://youzan.github.io/vant/#/zh-CN/quickstart),均已经配置完善。 18 | 8. Rem 适配,使用以下两个工具: 19 | 1. [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 是一款 postcss 插件,用于将单位转化为 rem 20 | 2. [lib-flexible](https://github.com/amfe/lib-flexible) 用于设置 rem 基准值 21 | 3. 设计图标准按照 375 * 667 的尺寸给出,布局的时候除了 1px 其他全部按照 测量的距离 写就可以了 22 | 23 | ## 模板说明 ## 24 | 25 | 1. 本模板暂时的技术栈为: vue + vue-cli + vue-router + vuex + vant + axios + less(如果你使用的是其他的css预编译器请自行配置) 26 | 27 | ## 项目使用 ## 28 | 29 | 1. 先将本项目 Fork 到你的 github ,方便日后你对其进行修改。 30 | 2. 将你 github 上的远程库克隆到本地。 31 | 32 | 或者是将本项目克隆到本地,去除远程库,再添加你自己的远程库: 33 | 34 | ``` 35 | git remote rm origin 36 | git remote add origin <远程库地址> 37 | ``` 38 | 39 | ### 安装依赖及插件 ### 40 | 41 | ``` 42 | npm install 43 | ``` 44 | 45 | ### 启动开发模式(热更新) ### 46 | 47 | ``` 48 | npm run serve 49 | ``` 50 | 51 | ### 生产模式打包 ### 52 | 53 | ``` 54 | npm run build 55 | ``` 56 | 57 | ### 更多详细配置 ### 58 | 59 | 请看 [Configuration Reference](https://cli.vuejs.org/config/)。 60 | 61 | 如果你使用的是 `vue ui` ,会发现项目名称可能不是你需要的,这个时候只需要将 `package.json` 和 `package-lock.json` 文件中的 `name` 修改为你希望的名称,然后重启项目。 62 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: [ 4 | ['import', { 5 | libraryName: 'vant', 6 | libraryDirectory: 'es', 7 | style: true 8 | }, 'vant'] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cli-vant-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "amfe-flexible": "^2.2.1", 12 | "core-js": "^2.6.5", 13 | "vant": "^2.0.0-beta.2", 14 | "vue": "^2.6.10", 15 | "vue-router": "^3.0.3", 16 | "vuex": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 20 | "@vue/cli-plugin-babel": "^3.8.0", 21 | "@vue/cli-plugin-eslint": "^3.8.0", 22 | "@vue/cli-service": "^3.8.0", 23 | "@vue/eslint-config-standard": "^4.0.0", 24 | "axios": "^0.18.0", 25 | "babel-eslint": "^10.0.1", 26 | "babel-plugin-import": "^1.11.2", 27 | "eslint": "^5.16.0", 28 | "eslint-plugin-vue": "^5.0.0", 29 | "http-proxy-middleware": "^0.19.1", 30 | "less": "^3.0.4", 31 | "less-loader": "^4.1.0", 32 | "postcss-pxtorem": "^4.0.1", 33 | "vue-cli-plugin-axios": "0.0.4", 34 | "vue-template-compiler": "^2.6.10" 35 | }, 36 | "eslintConfig": { 37 | "root": true, 38 | "env": { 39 | "node": true 40 | }, 41 | "extends": [ 42 | "plugin:vue/essential", 43 | "@vue/standard" 44 | ], 45 | "rules": {}, 46 | "parserOptions": { 47 | "parser": "babel-eslint" 48 | } 49 | }, 50 | "postcss": { 51 | "plugins": { 52 | "autoprefixer": {} 53 | } 54 | }, 55 | "browserslist": [ 56 | "> 1%", 57 | "last 2 versions" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxss5201/vue-cli-vant-starter/a7c6804d01dd8af278780055b7d932e0a41276bd/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-cli-vant-starter 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxss5201/vue-cli-vant-starter/a7c6804d01dd8af278780055b7d932e0a41276bd/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import './plugins/axios' 3 | import 'amfe-flexible' 4 | import { Toast } from 'vant' 5 | import App from './App.vue' 6 | import router from './router' 7 | import store from './store' 8 | 9 | Vue.use(Toast) 10 | 11 | Vue.config.productionTip = false 12 | 13 | router.beforeEach((to, from, next) => { 14 | Vue.prototype.$toast.clear() 15 | const CancelToken = Vue.axios.CancelToken 16 | store.state.source.cancel && store.state.source.cancel() 17 | store.commit('setSource', CancelToken.source()) 18 | next() 19 | }) 20 | 21 | new Vue({ 22 | router, 23 | store, 24 | render: h => h(App) 25 | }).$mount('#app') 26 | -------------------------------------------------------------------------------- /src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import Vue from 'vue' 4 | import axios from 'axios' 5 | import store from './../store' 6 | 7 | // Full config: https://github.com/axios/axios#request-config 8 | // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || ''; 9 | // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; 10 | // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 11 | 12 | let config = { 13 | // baseURL: process.env.baseURL || process.env.apiUrl || "" 14 | // timeout: 60 * 1000, // Timeout 15 | // withCredentials: true, // Check cross-site Access-Control 16 | } 17 | 18 | const _axios = axios.create(config) 19 | _axios.CancelToken = axios.CancelToken 20 | _axios.isCancel = axios.isCancel 21 | _axios.all = axios.all 22 | 23 | let pageAxiosList = new Set() // 用于解决同时请求多个 service 接口时,必须等最慢的接口返回数据之后再关闭 loading 24 | let axiosSource // 需要最新的链接的保存参数的地方 25 | 26 | _axios.interceptors.request.use( 27 | function (config) { 28 | // Do something before request is sent 29 | if (config.showLoading && !pageAxiosList.size) { 30 | Vue.prototype.$toast.loading({ 31 | duration: 0, 32 | mask: true, 33 | forbidClick: true, 34 | message: '加载中...', 35 | loadingType: 'spinner' 36 | }) 37 | } 38 | // 检查 pageAxiosList 里面是否有已经发送相同接口 url ,如果有的话直接取消发送 39 | if (config.needLast) { 40 | // 请求链接需要最新的 41 | const CancelToken = Vue.axios.CancelToken 42 | axiosSource && axiosSource.cancel && axiosSource.cancel() 43 | axiosSource = CancelToken.source() 44 | config.cancelToken = axiosSource.token 45 | } else if (config.needAll) { 46 | // console.log("needAll"); 47 | } else { 48 | if (pageAxiosList.has(config.url)) { 49 | return Promise.reject(new Error('alreadySent')) 50 | } 51 | pageAxiosList.add(config.url) 52 | config.cancelToken = store.state.source.token 53 | } 54 | return config 55 | }, 56 | function (error) { 57 | // Do something with request error 58 | Vue.prototype.$toast('网络出错,请重试') 59 | return Promise.reject(error) 60 | } 61 | ) 62 | 63 | // Add a response interceptor 64 | _axios.interceptors.response.use( 65 | function (response) { 66 | // Do something with response data 67 | let nowUrl = response.config.url 68 | if (pageAxiosList.has(nowUrl)) { 69 | pageAxiosList.delete(nowUrl) 70 | } 71 | if (response.config.showLoading && !pageAxiosList.size) { 72 | Vue.prototype.$toast.clear() 73 | } 74 | if (response.data.isok) { 75 | return response.data 76 | } else { 77 | Vue.prototype.$toast('网络出错,请重试') 78 | } 79 | return response 80 | }, 81 | function (error) { 82 | // Do something with response error 83 | if (_axios.isCancel(error)) { 84 | // 判断是否是切换路由导致的取消,如果是的话还需要将 pageAxiosList 清空 85 | for (let item of pageAxiosList.keys()) { 86 | pageAxiosList.delete(item) 87 | } 88 | Vue.prototype.$toast.clear() 89 | } else if (error.message === 'alreadySent') { 90 | console.log('alreadySent') 91 | } else { 92 | Vue.prototype.$toast('网络出错,请重试') 93 | } 94 | return Promise.reject(error) 95 | } 96 | ) 97 | 98 | Plugin.install = function (Vue, options) { 99 | Vue.axios = _axios 100 | window.axios = _axios 101 | Object.defineProperties(Vue.prototype, { 102 | axios: { 103 | get () { 104 | return _axios 105 | } 106 | }, 107 | $axios: { 108 | get () { 109 | return _axios 110 | } 111 | } 112 | }) 113 | } 114 | 115 | Vue.use(Plugin) 116 | 117 | export default Plugin 118 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue') 4 | const About = () => import(/* webpackChunkName: "about" */ './views/About.vue') 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'home', 13 | component: Home 14 | }, 15 | { 16 | path: '/about', 17 | name: 'about', 18 | component: About 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import axios from 'axios' 4 | 5 | Vue.use(Vuex) 6 | const CancelToken = axios.CancelToken 7 | 8 | export default new Vuex.Store({ 9 | state: { 10 | source: CancelToken.source() 11 | }, 12 | mutations: { 13 | setSource (state, obj) { 14 | // 路由切换新建新建取消令牌,并写入store 15 | state.source = obj 16 | } 17 | }, 18 | actions: { 19 | 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer') 2 | const pxtorem = require('postcss-pxtorem') 3 | 4 | module.exports = { 5 | css: { 6 | sourceMap: true, 7 | loaderOptions: { 8 | postcss: { 9 | plugins: [ 10 | autoprefixer({ 11 | browsers: ['Android >= 4.0', 'iOS >= 7'] 12 | }), 13 | pxtorem({ 14 | rootValue: 37.5, 15 | propList: ['*'], 16 | minPixelValue: 1 17 | }) 18 | ] 19 | } 20 | } 21 | }, 22 | devServer: { 23 | proxy: { 24 | '/api': { 25 | target: '', 26 | ws: true, 27 | changeOrigin: true 28 | } 29 | } 30 | } 31 | } 32 | --------------------------------------------------------------------------------