├── .browserslistrc ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ └── images │ │ ├── banner0.jpg │ │ ├── banner1.jpg │ │ ├── banner2.jpg │ │ ├── door-key.png │ │ ├── gzh.jpg │ │ ├── home_page_bg.png │ │ ├── open-door.png │ │ ├── simple_01.png │ │ ├── simple_02.png │ │ ├── simple_03.png │ │ ├── simple_04.png │ │ ├── systme_01.png │ │ ├── systme_02.png │ │ ├── systme_03.png │ │ ├── systme_04.png │ │ ├── systme_05.png │ │ └── systme_06.png ├── main.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── store │ ├── index.ts │ └── modules │ │ ├── Home │ │ ├── api.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ └── types.ts │ │ └── Login │ │ ├── api.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ └── types.ts ├── style │ └── reset.less ├── utils │ ├── axios.config.ts │ ├── global.ts │ └── interface.ts └── views │ ├── Home │ ├── components │ │ ├── HomeContent.vue │ │ └── HomeSwiper.vue │ └── index.vue │ ├── Index │ └── index.vue │ ├── Login │ └── index.vue │ └── Mine │ └── index.vue ├── tsconfig.json ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 原文: [掘金文章](https://juejin.im/post/6887867687897301006) 2 | 3 | 关注公众号: 微信搜索 `web全栈进阶` ; 收货更多的干货 4 | 5 | ## 运行项目 6 | - ` yarn install --> yarn serve ` 7 | - 由于是测试服,后台环境tag时常改变,偶尔会遇到接口报错的情况 8 | 9 | ## 一、开篇 10 | - `vue3.0beta`版正式上线,作为新技术热爱者,新项目将正式使用`vue3.0`开发; 接下来总结(对自己技术掌握的稳固)介绍(分享有需要的猿友) 11 | - 上篇博客介绍了`vue3.0`常用语法及开发技巧;有需要的请点击 [Vue3.0 进阶、环境搭建、相关API的使用](https://juejin.im/post/6877832502590111757) 12 | - 觉得对您有用的 `github` 点个 `star` 呗 13 | - 项目`github`地址:`https://github.com/laijinxian/vue3-typescript-template` 14 | 15 | ## 二、项目介绍(移动端) 16 | - 1)技术栈: `vue3 + vuex + typescript + webpack + vant-ui + axios + less + postcss-pxtorem(rem适配)` 17 | - 2)没用官方构建工具`vite`原因:`vite` 坑还真的不少,有时候正常写法`webpack`没问题, 在`vite`上就报错;一脸懵逼的那种, `vite` 的`github` 提 Issues 都没用, 维护人员随便回答了下就把我的 `Issues` 给关了,我也是醉了; 18 | - 3)不过自己还是很期待 `vite` 的, 等待他成熟吧, 在正式使用; 19 | - 4)涉及点:目前只贴出项目初期的几个功能 20 | - `webpack require` 自动化注册路由、自动化注册异步组价 21 | - `axios` 请求封装(请求拦截、响应拦截、取消请求、统一处理) 22 | - `vuex` 业务模块化、 接管请求统一处理 23 | 24 | ## 三、项目搭建 25 | 可参考上篇文章 [Vue3.0 进阶、环境搭建、相关API的使用](https://juejin.im/post/6877832502590111757) 26 | 1. `vue-cli、vue` 下载最新版本 27 | 2. 执行命令 `vue create my_app_name` 28 | 3. 执行完上面命令接下来选择手动配置(第三个),不要选择默认配置,有很多我们用不上,我的选择如下图: 29 | ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de9c45a9a40543df9c7240c62b747c27~tplv-k3u1fbpfcp-watermark.image) 30 | 31 | ## 三、项目主要功能 32 | **1. `webpack require` 自动化注册路由、自动化注册异步组价** 33 | ``` 34 | // 该文件在 utils 下的 global.ts 35 | // 区分文件是否自动注册为组件,vue文件定义 isComponents 字段; 区分是否自动注册为路由定义 isRouter 字段 36 | // 使用方式分别在 main.ts 里方法asyncComponent() 以及路由文件router下的index.ts 方法 vueRouters() 37 | 38 | import { defineAsyncComponent } from 'vue' 39 | import { app } from '../main' 40 | import { IRouter } from './interface' 41 | 42 | // 获取所有vue文件 43 | function getComponent() { 44 | return require.context('../views', true, /\.vue$/); 45 | } 46 | 47 | // 首字母转换大写 48 | function letterToUpperCase(str: string): string { 49 | return str.charAt(0).toUpperCase() + str.slice(1); 50 | } 51 | 52 | // 首字母转换小写 53 | function letterToLowerCase(str: string): string { 54 | return str.charAt(0).toLowerCase() + str.slice(1); 55 | } 56 | 57 | export const asyncComponent = (): void => { 58 | 59 | // 获取文件全局对象 60 | const requireComponents = getComponent(); 61 | 62 | requireComponents.keys().forEach((fileSrc: string) => { 63 | 64 | const viewSrc = requireComponents(fileSrc); 65 | 66 | const fileNameSrc = fileSrc.replace(/^\.\//, '') 67 | 68 | const file = viewSrc.default; 69 | 70 | if (viewSrc.default.isComponents) { 71 | 72 | // 异步注册组件 73 | let componentRoot = defineAsyncComponent( 74 | () => import(`@/views/${fileNameSrc}`) 75 | ) 76 | 77 | app.component(letterToUpperCase(file.name), componentRoot) 78 | } 79 | }); 80 | }; 81 | 82 | // 获取路由文件 83 | export const vueRouters = (): IRouter[] => { 84 | 85 | const routerList: IRouter[] = []; 86 | 87 | const requireRouters = getComponent(); 88 | 89 | requireRouters.keys().forEach((fileSrc: string) => { 90 | 91 | // 获取 components 文件下的文件名 92 | const viewSrc = requireRouters(fileSrc); 93 | 94 | const file = viewSrc.default; 95 | 96 | // 首字母转大写 97 | const routerName = letterToUpperCase(file.name); 98 | 99 | // 首字母转小写 100 | const routerPath = letterToLowerCase(file.name); 101 | 102 | const fileNameSrc = fileSrc.replace(/^\.\//, ''); 103 | 104 | if (file.isRouter) { 105 | routerList.push({ 106 | path: `/${routerPath}`, 107 | name: `${routerName}`, 108 | component: () => import(`@/views/${fileNameSrc}`) 109 | }); 110 | } 111 | }); 112 | return routerList; 113 | }; 114 | ``` 115 | **2. `axios` 请求封装(请求拦截、响应拦截、取消请求、统一处理)** 116 | ``` 117 | import axios, { AxiosRequestConfig, AxiosResponse, Canceler } from 'axios' 118 | import router from '@/router' 119 | import { Toast } from 'vant' 120 | 121 | if (process.env.NODE_ENV === 'development') { 122 | // 开发环境 123 | axios.defaults.baseURL = `https://test-mobileapi.qinlinkeji.com/api/` 124 | } else { 125 | // 正式环境 126 | axios.defaults.baseURL = `正式环境地址` 127 | } 128 | 129 | let sourceAjaxList: Canceler[] = [] 130 | 131 | export const axionInit = () => { 132 | axios.interceptors.request.use((config: AxiosRequestConfig) => { 133 | // 设置 cancel token 用于取消请求 (当一个接口出现401后,取消后续多有发起的请求,避免出现好几个错误提示) 134 | config.cancelToken = new axios.CancelToken(function executor(cancel: Canceler): void { 135 | sourceAjaxList.push(cancel) 136 | }) 137 | 138 | // 存在 sessionId 为所有请求加上 sessionId 139 | if (localStorage.getItem(`h5_sessionId`) && config.url!.indexOf('/user/login') < 0) config.url += ('sessionId=' + localStorage.getItem(`h5_sessionId`)) 140 | if (!config.data) config.data = {} 141 | return config 142 | }, function (error) { 143 | // 抛出错误 144 | return Promise.reject(error) 145 | }) 146 | 147 | axios.interceptors.response.use((response: AxiosResponse) => { 148 | const { status, data } = response 149 | if (status === 200) { 150 | // 如果不出现错误,直接向回调函数内输出 data 151 | if (data.code === 0) { 152 | return data 153 | } else if (data.code === 401) { 154 | // 出现未登录或登录失效取消后面的请求 155 | sourceAjaxList.length && sourceAjaxList.length > 0 && sourceAjaxList.forEach((ajaxCancel, index) => { 156 | ajaxCancel() // 取消请求 157 | delete sourceAjaxList[index] 158 | }) 159 | Toast({ 160 | message: data.message, 161 | duration: 2000 162 | }) 163 | return router.push('/login') 164 | } else { 165 | return data 166 | } 167 | } else { 168 | return data 169 | } 170 | }, error => { 171 | const { response } = error 172 | // 这里处理错误的 http code or 服务器或后台报错 173 | if (!response || response.status === 404 || response.status === 500) { 174 | if (!response) { 175 | console.error(`404 error %o ${error}`) 176 | } else { 177 | if (response.data && response.data.message) { 178 | Toast.fail({ 179 | message: '请求异常,请稍后再试!', 180 | duration: 2000 181 | }) 182 | } 183 | } 184 | } 185 | return Promise.reject(error.message) 186 | }) 187 | } 188 | ``` 189 | **3. `vuex` 业务模块化、 接管请求统一处理** 190 | ``` 191 | import { Module } from 'vuex' 192 | import { IGlobalState, IAxiosResponseData } from '../../index' 193 | import * as Types from './types' 194 | import { IHomeState, ICity, IAccessControl, ICommonlyUsedDoor, IcurrentCommunity } from './interface' 195 | import * as API from './api' 196 | 197 | const state: IHomeState = { 198 | cityList: [], 199 | currentCommunity: { 200 | communityId: '', 201 | communityName: '' 202 | }, 203 | commonlyUsedDoor: { 204 | doorControlId: '', 205 | doorControlName: '' 206 | }, 207 | accessControlList: [] 208 | } 209 | 210 | const home: Module = { 211 | namespaced: true, 212 | state, 213 | actions: { 214 | // 获取小区列表 215 | async [Types.GET_CITY_LIST]({ commit, rootState }) { 216 | console.log(rootState.login.userInfo.userId) 217 | const result = await API.getCityList(rootState.login.userInfo.userId) 218 | if (result.code !== 0) return 219 | commit(Types.GET_CITY_LIST, result.data) 220 | commit(Types.SET_CURRENT_COMMUNIRY, result.data[0]) 221 | }, 222 | // 获取小区门禁列表 223 | async [Types.GET_ACCESS_CONTROL_LIST]({ commit, rootState }) { 224 | const result = await API.getCityAccessControlList({ 225 | userId: rootState.login.userInfo.userId, 226 | communityId: state.currentCommunity.communityId 227 | }) 228 | if (result.code !== 0) return 229 | commit(Types.GET_ACCESS_CONTROL_LIST, result.data.userDoorDTOS) 230 | commit(Types.SET_COMMONLY_USERDOOR, result.data.commonlyUsedDoor) 231 | }, 232 | }, 233 | mutations: { 234 | // 设置小区列表 235 | [Types.GET_CITY_LIST](state, cityList: ICity[]) { 236 | if (cityList.length !== 0) state.cityList = cityList 237 | }, 238 | // 设置小区门禁列表 239 | [Types.GET_ACCESS_CONTROL_LIST](state, accessControlList: IAccessControl[]) { 240 | if (accessControlList.length !== 0) return state.accessControlList = accessControlList 241 | }, 242 | // 设置当前小区 243 | [Types.SET_CURRENT_COMMUNIRY](state, currentCommunity: IcurrentCommunity) { 244 | state.currentCommunity.communityId = currentCommunity.communityId 245 | state.currentCommunity.communityName = currentCommunity.communityName 246 | }, 247 | // 设置常用小区 248 | [Types.SET_COMMONLY_USERDOOR](state, commonlyUsedDoor: ICommonlyUsedDoor) { 249 | state.commonlyUsedDoor = commonlyUsedDoor 250 | state.commonlyUsedDoor = commonlyUsedDoor 251 | } 252 | } 253 | } 254 | 255 | export default home 256 | ``` 257 | **4. `home` 文件代码** 258 | ``` 259 | 286 | 287 | 334 | 335 | 353 | ``` 354 | **5. `login` 文件代码** 355 | ``` 356 | 393 | 394 | 462 | 463 | 475 | ``` 476 | ## 四、项目ui 477 | ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b24cd073cbe4d89a25fd14ef24cb678~tplv-k3u1fbpfcp-watermark.image) 478 | ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6adf633bd1b94901b09f277c9aeec2f0~tplv-k3u1fbpfcp-watermark.image) 479 | 480 | ## 五、结语 481 | 以上为个人实际项目开发总结, 有不对之处欢迎留言指正 482 | 483 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-ts-template-ljx", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.20.0", 11 | "vant": "^3.0.0-beta.3", 12 | "vue": "^3.0.0", 13 | "vue-router": "^4.0.0-0", 14 | "vuex": "^4.0.0-0" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-router": "~4.5.0", 18 | "@vue/cli-plugin-typescript": "~4.5.0", 19 | "@vue/cli-plugin-vuex": "~4.5.0", 20 | "@vue/cli-service": "~4.5.0", 21 | "@vue/compiler-sfc": "^3.0.0", 22 | "compression-webpack-plugin": "^6.0.3", 23 | "image-webpack-loader": "^7.0.1", 24 | "less": "^3.0.4", 25 | "less-loader": "^5.0.0", 26 | "postcss-pxtorem": "^5.1.1", 27 | "qs": "^6.9.4", 28 | "typescript": "~3.9.3", 29 | "uglifyjs-webpack-plugin": "^2.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/assets/images/banner0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/banner0.jpg -------------------------------------------------------------------------------- /src/assets/images/banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/banner1.jpg -------------------------------------------------------------------------------- /src/assets/images/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/banner2.jpg -------------------------------------------------------------------------------- /src/assets/images/door-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/door-key.png -------------------------------------------------------------------------------- /src/assets/images/gzh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/gzh.jpg -------------------------------------------------------------------------------- /src/assets/images/home_page_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/home_page_bg.png -------------------------------------------------------------------------------- /src/assets/images/open-door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/open-door.png -------------------------------------------------------------------------------- /src/assets/images/simple_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/simple_01.png -------------------------------------------------------------------------------- /src/assets/images/simple_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/simple_02.png -------------------------------------------------------------------------------- /src/assets/images/simple_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/simple_03.png -------------------------------------------------------------------------------- /src/assets/images/simple_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/simple_04.png -------------------------------------------------------------------------------- /src/assets/images/systme_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_01.png -------------------------------------------------------------------------------- /src/assets/images/systme_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_02.png -------------------------------------------------------------------------------- /src/assets/images/systme_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_03.png -------------------------------------------------------------------------------- /src/assets/images/systme_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_04.png -------------------------------------------------------------------------------- /src/assets/images/systme_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_05.png -------------------------------------------------------------------------------- /src/assets/images/systme_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laijinxian/vue3-typescript-template/bd541510ff5ca99325e20e87f521a241eba02f11/src/assets/images/systme_06.png -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp, defineAsyncComponent } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import './style/reset.less' 6 | import { axionInit } from '@/utils/axios.config' 7 | import { asyncComponent } from '@/utils/global'; 8 | 9 | import 'vant/lib/index.css' 10 | import { Swipe, SwipeItem, Toast, Field, Button, Cell, CellGroup, Tabbar, TabbarItem , Lazyload, DropdownMenu, DropdownItem, Popup, NoticeBar } from 'vant'; 11 | 12 | export const app = createApp(App) 13 | 14 | axionInit() 15 | asyncComponent() 16 | 17 | app.use(Lazyload) 18 | app.use(Swipe) 19 | app.use(SwipeItem) 20 | app.use(Field) 21 | app.use(Toast) 22 | app.use(Button) 23 | app.use(Cell) 24 | app.use(CellGroup) 25 | app.use(Tabbar) 26 | app.use(TabbarItem) 27 | app.use(DropdownMenu) 28 | app.use(DropdownItem) 29 | app.use(Popup) 30 | app.use(NoticeBar) 31 | 32 | app.use(store).use(router).mount('#app') 33 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | 3 | import { vueRouters } from '@/utils/global' 4 | 5 | const routes: Array = [ 6 | ...vueRouters(), 7 | { 8 | path: '/', 9 | name: 'Login', 10 | component: () => import(`@/views/Login/index.vue`) 11 | } 12 | ] 13 | 14 | const router = createRouter({ 15 | history: createWebHistory(process.env.BASE_URL), 16 | routes 17 | }) 18 | 19 | export default router 20 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import { IHomeState } from './modules/Home/interface' 3 | import { IIndexState } from './modules/Login/interface' 4 | import home from './modules/Home' 5 | import login from './modules/Login' 6 | 7 | // 全局状态 8 | export interface IGlobalState { 9 | home: IHomeState, 10 | login: IIndexState 11 | } 12 | 13 | // 后台接口返回参数类 14 | export interface IAxiosResponseData { 15 | code: number, 16 | data: any, 17 | message: string 18 | } 19 | 20 | const store = createStore({ 21 | mutations: { 22 | }, 23 | actions: { 24 | }, 25 | modules: { 26 | home, 27 | login 28 | } 29 | }) 30 | 31 | export default store 32 | -------------------------------------------------------------------------------- /src/store/modules/Home/api.ts: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios' 3 | import { AGetBuilding } from './interface' 4 | 5 | // 获取小区列表 6 | export const getCityList = (userId: string | number) => axios.post(`app/user/communityInfo?userId=${userId}&`) 7 | 8 | // 获取小区门禁列表 9 | export const getCityAccessControlList = (data: AGetBuilding) => axios.post(`app/user/queryUserDoorByCache?`, data) -------------------------------------------------------------------------------- /src/store/modules/Home/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { IGlobalState, IAxiosResponseData } from '../../index' 3 | import * as Types from './types' 4 | import { IHomeState, ICity, IAccessControl, ICommonlyUsedDoor, IcurrentCommunity } from './interface' 5 | import * as API from './api' 6 | 7 | const state: IHomeState = { 8 | cityList: [], 9 | currentCommunity: { 10 | communityId: '', 11 | communityName: '' 12 | }, 13 | commonlyUsedDoor: { 14 | doorControlId: '', 15 | doorControlName: '' 16 | }, 17 | accessControlList: [] 18 | } 19 | 20 | const home: Module = { 21 | namespaced: true, 22 | state, 23 | actions: { 24 | // 获取小区列表 25 | async [Types.GET_CITY_LIST]({ commit, rootState }) { 26 | console.log(rootState.login.userInfo.userId) 27 | const result = await API.getCityList(rootState.login.userInfo.userId) 28 | if (result.code !== 0) return 29 | commit(Types.GET_CITY_LIST, result.data) 30 | commit(Types.SET_CURRENT_COMMUNIRY, result.data[0]) 31 | }, 32 | // 获取小区门禁列表 33 | async [Types.GET_ACCESS_CONTROL_LIST]({ commit, rootState }) { 34 | const result = await API.getCityAccessControlList({ 35 | userId: rootState.login.userInfo.userId, 36 | communityId: state.currentCommunity.communityId 37 | }) 38 | if (result.code !== 0) return 39 | commit(Types.GET_ACCESS_CONTROL_LIST, result.data.userDoorDTOS) 40 | commit(Types.SET_COMMONLY_USERDOOR, result.data.commonlyUsedDoor) 41 | }, 42 | }, 43 | mutations: { 44 | // 设置小区列表 45 | [Types.GET_CITY_LIST](state, cityList: ICity[]) { 46 | if (cityList.length !== 0) state.cityList = cityList 47 | }, 48 | // 设置小区门禁列表 49 | [Types.GET_ACCESS_CONTROL_LIST](state, accessControlList: IAccessControl[]) { 50 | if (accessControlList.length !== 0) return state.accessControlList = accessControlList 51 | }, 52 | // 设置当前小区 53 | [Types.SET_CURRENT_COMMUNIRY](state, currentCommunity: IcurrentCommunity) { 54 | state.currentCommunity.communityId = currentCommunity.communityId 55 | state.currentCommunity.communityName = currentCommunity.communityName 56 | }, 57 | // 设置常用小区 58 | [Types.SET_COMMONLY_USERDOOR](state, commonlyUsedDoor: ICommonlyUsedDoor) { 59 | state.commonlyUsedDoor = commonlyUsedDoor 60 | state.commonlyUsedDoor = commonlyUsedDoor 61 | } 62 | } 63 | } 64 | 65 | export default home 66 | -------------------------------------------------------------------------------- /src/store/modules/Home/interface.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @ 页面常用类开头 I 4 | * @ 接口参数类开头 A 5 | */ 6 | 7 | export interface IHomeState { 8 | cityList: ICity[], 9 | currentCommunity: IcurrentCommunity, 10 | commonlyUsedDoor: ICommonlyUsedDoor, 11 | accessControlList: IAccessControl[] 12 | } 13 | 14 | export interface ICity { 15 | cityName: string, 16 | cityId: string, 17 | communityId: string, 18 | communityName: string, 19 | provinceName: string, 20 | districtName: string 21 | } 22 | 23 | export interface IAccessControl { 24 | doorControlId: string, 25 | doorControlName: string 26 | } 27 | 28 | export interface IcurrentCommunity{ 29 | communityId: string, 30 | communityName: string 31 | } 32 | 33 | export interface ICommonlyUsedDoor{ 34 | doorControlId: string, 35 | doorControlName: string 36 | } 37 | 38 | export interface AGetBuilding{ 39 | communityId: string | number, 40 | userId: string | number 41 | } -------------------------------------------------------------------------------- /src/store/modules/Home/types.ts: -------------------------------------------------------------------------------- 1 | 2 | // 获取轮播图列表 3 | export const GET_SLIDER_LIST = 'GET_SLIDER_LIST' 4 | 5 | // 获取小区权限列表 6 | export const GET_CITY_LIST = 'GET_CITY_LIST' 7 | 8 | // 获取小区门禁列表 9 | export const GET_ACCESS_CONTROL_LIST = 'GET_ACCESS_CONTROL_LIST' 10 | 11 | // 设置当前小区 12 | export const SET_CURRENT_COMMUNIRY = 'SET_CURRENT_COMMUNIRY' 13 | 14 | // 设置常用门禁 15 | export const SET_COMMONLY_USERDOOR = 'SET_COMMONLY_USERDOOR' -------------------------------------------------------------------------------- /src/store/modules/Login/api.ts: -------------------------------------------------------------------------------- 1 | 2 | import axios from 'axios' 3 | import qs from 'qs' 4 | import { ALoginData, ACodeData } from './interface' 5 | 6 | // 获取手机验证码 7 | export const getSmsCode = (data: ACodeData) => axios.post(`sms/v1/sendValidCode`, qs.stringify(data)) 8 | 9 | // 登录 10 | export const onLogin = (data: ALoginData) => axios.post(`app/login`, data) -------------------------------------------------------------------------------- /src/store/modules/Login/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { IGlobalState, IAxiosResponseData } from '../../index' 3 | import { IIndexState, IUserInfo } from './interface' 4 | import * as Types from "./types" 5 | import * as API from './api' 6 | 7 | const state: IIndexState = { 8 | // 方便测试写死测试手机号验证码 9 | phone: '13201010003', 10 | sms: '116688', 11 | userInfo: { 12 | userId: '', 13 | communitylist: [], 14 | sessionId: 'string', 15 | } 16 | } 17 | 18 | const login: Module = { 19 | namespaced: true, 20 | state, 21 | actions: { 22 | async [Types.GET_SMS_CODE]({ state }) { 23 | return API.getSmsCode({ 24 | phone: state.phone 25 | }) 26 | }, 27 | async [Types.ON_LOGIN]({ state }) { 28 | return API.onLogin({ 29 | appChannel: 0, 30 | mobile: state.phone, 31 | smsCode: state.sms 32 | }) 33 | } 34 | }, 35 | mutations: { 36 | [Types.SAVE_PHONE](state, phone: string) { 37 | state.phone = phone 38 | }, 39 | [Types.SAVE_SMS_CODE](state, sms: string) { 40 | state.sms = sms 41 | }, 42 | [Types.SAVE_USER_INFO](state, info: IUserInfo) { 43 | state.userInfo.communitylist = info.communitylist 44 | state.userInfo.sessionId = info.sessionId 45 | state.userInfo.userId = info.userId 46 | } 47 | } 48 | } 49 | 50 | export default login 51 | -------------------------------------------------------------------------------- /src/store/modules/Login/interface.ts: -------------------------------------------------------------------------------- 1 | export interface IIndexState { 2 | phone: string, 3 | sms: string, 4 | userInfo: IUserInfo 5 | } 6 | 7 | export interface IUserInfo { 8 | communitylist: ICommunity[], 9 | sessionId: string, 10 | userId: string | number 11 | } 12 | 13 | export interface ICommunity { 14 | cityName: string, 15 | cityCode: string 16 | } 17 | 18 | export interface ALoginData { 19 | appChannel: number, 20 | mobile: string, 21 | smsCode: string 22 | } 23 | 24 | export interface ACodeData { 25 | phone: string 26 | } -------------------------------------------------------------------------------- /src/store/modules/Login/types.ts: -------------------------------------------------------------------------------- 1 | // 保存手机号 2 | export const SAVE_PHONE = 'SAVE_PHONE' 3 | 4 | // 保存验证码 5 | export const SAVE_SMS_CODE = 'SAVE_SMS_CODE' 6 | 7 | // 获取手机验证码 8 | export const GET_SMS_CODE = 'GET_SMS_CODE' 9 | 10 | // 登录 11 | export const ON_LOGIN = 'ON_LOGIN' 12 | 13 | // 保存用户信息 14 | export const SAVE_USER_INFO = 'SAVE_USER_INFO' 15 | -------------------------------------------------------------------------------- /src/style/reset.less: -------------------------------------------------------------------------------- 1 | /* 清除内外边距 */ 2 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 3 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 4 | pre, /* text formatting elements 文本格式元素 */ 5 | fieldset, lengend, button, input, textarea, /* form elements 表单元素 */ 6 | th, td { /* table elements 表格元素 */ 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | h1 { font-size: 18px; /* 18px / 12px = 1.5 */ } 12 | h2 { font-size: 16px; } 13 | h3 { font-size: 14px; } 14 | h4, h5, h6 { font-size: 100%; } 15 | 16 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 17 | code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */ 18 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 19 | 20 | /* 重置列表元素 */ 21 | ul, ol { list-style: none; } 22 | 23 | /* 重置文本格式元素 */ 24 | a { text-decoration: none; } 25 | a:hover { text-decoration: underline; } 26 | 27 | abbr[title], acronym[title] { /* 注:1.ie6 不支持 abbr; 2.这里用了属性选择符,ie6 下无效果 */ 28 | border-bottom: 1px dotted; 29 | cursor: help; 30 | } 31 | 32 | q:before, q:after { content: ''; } 33 | 34 | /* 重置表单元素 */ 35 | legend { color: #000; } /* for ie6 */ 36 | fieldset, img { border: none; } /* img 搭车:让链接里的 img 无边框 */ 37 | /* 注:optgroup 无法扶正 */ 38 | button, input, select, textarea { 39 | font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */ 40 | } 41 | 42 | /* 重置表格元素 */ 43 | table { 44 | border-collapse: collapse; 45 | border-spacing: 0; 46 | } 47 | 48 | /* 重置 hr */ 49 | hr { 50 | border: none; 51 | height: 1px; 52 | } 53 | 54 | /* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */ 55 | html, body { height: 100%; width: 100%; overflow: hidden} 56 | 57 | /* 设置默认字体 */ 58 | body, button, input, select, textarea { /* for ie */ 59 | font: 12px/1 "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */ 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/axios.config.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, AxiosResponse, Canceler } from 'axios' 2 | import router from '@/router' 3 | import { Toast } from 'vant' 4 | 5 | if (process.env.NODE_ENV === 'development') { 6 | // 开发环境 7 | axios.defaults.baseURL = `https://test-mobileapi.qinlinkeji.com/api/` 8 | } else { 9 | // 正式环境 10 | axios.defaults.baseURL = `正式环境地址` 11 | } 12 | 13 | let sourceAjaxList: Canceler[] = [] 14 | 15 | export const axionInit = () => { 16 | axios.interceptors.request.use((config: AxiosRequestConfig) => { 17 | // 设置 cancel token 用于取消请求 (当一个接口出现401后,取消后续多有发起的请求,避免出现好几个错误提示) 18 | config.cancelToken = new axios.CancelToken(function executor(cancel: Canceler): void { 19 | sourceAjaxList.push(cancel) 20 | }) 21 | 22 | // 存在 sessionId 为所有请求加上 sessionId 23 | if (localStorage.getItem(`h5_sessionId`) && config.url!.indexOf('/app/login') < 0) config.url += ('sessionId=' + localStorage.getItem(`h5_sessionId`)) 24 | if (!config.data) config.data = {} 25 | return config 26 | }, function (error) { 27 | // 抛出错误 28 | return Promise.reject(error) 29 | }) 30 | 31 | axios.interceptors.response.use((response: AxiosResponse) => { 32 | const { status, data } = response 33 | if (status === 200) { 34 | // 如果不出现错误,直接向回调函数内输出 data 35 | if (data.code === 0) { 36 | return data 37 | } else if (data.code === 401) { 38 | // 出现未登录或登录失效取消后面的请求 39 | sourceAjaxList.length && sourceAjaxList.length > 0 && sourceAjaxList.forEach((ajaxCancel, index) => { 40 | ajaxCancel() // 取消请求 41 | delete sourceAjaxList[index] 42 | }) 43 | Toast({ 44 | message: data.message, 45 | duration: 2000 46 | }) 47 | return router.push('/') 48 | } else { 49 | return data 50 | } 51 | } else { 52 | return data 53 | } 54 | }, error => { 55 | const { response } = error 56 | // 这里处理错误的 http code or 服务器或后台报错 57 | if (!response || response.status === 404 || response.status === 500) { 58 | if (!response) { 59 | console.error(`404 error %o ${error}`) 60 | } else { 61 | if (response.data && response.data.message) { 62 | Toast.fail({ 63 | message: '请求异常,请稍后再试!', 64 | duration: 2000 65 | }) 66 | } 67 | } 68 | } 69 | return Promise.reject(error.message) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/global.ts: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from 'vue' 2 | import { app } from '../main' 3 | import { IRouter } from './interface' 4 | 5 | // 获取所有vue文件 6 | function getComponent() { 7 | return require.context('../views', true, /\.vue$/); 8 | } 9 | 10 | // 首字母转换大写 11 | function letterToUpperCase(str: string): string { 12 | return str.charAt(0).toUpperCase() + str.slice(1); 13 | } 14 | 15 | // 首字母转换小写 16 | function letterToLowerCase(str: string): string { 17 | return str.charAt(0).toLowerCase() + str.slice(1); 18 | } 19 | 20 | export const asyncComponent = (): void => { 21 | 22 | // 获取文件全局对象 23 | const requireComponents = getComponent(); 24 | 25 | requireComponents.keys().forEach((fileSrc: string) => { 26 | 27 | const viewSrc = requireComponents(fileSrc); 28 | 29 | const fileNameSrc = fileSrc.replace(/^\.\//, '') 30 | 31 | const file = viewSrc.default; 32 | 33 | if (viewSrc.default.isComponents) { 34 | 35 | // 异步注册组件 36 | let componentRoot = defineAsyncComponent( 37 | () => import(`@/views/${fileNameSrc}`) 38 | ) 39 | 40 | app.component(letterToUpperCase(file.name), componentRoot) 41 | } 42 | }); 43 | }; 44 | 45 | // 获取路由文件 46 | export const vueRouters = (): IRouter[] => { 47 | 48 | const routerList: IRouter[] = []; 49 | 50 | const requireRouters = getComponent(); 51 | 52 | requireRouters.keys().forEach((fileSrc: string) => { 53 | 54 | // 获取 components 文件下的文件名 55 | const viewSrc = requireRouters(fileSrc); 56 | 57 | const file = viewSrc.default; 58 | 59 | // 首字母转大写 60 | const routerName = letterToUpperCase(file.name); 61 | 62 | // 首字母转小写 63 | const routerPath = letterToLowerCase(file.name); 64 | 65 | const fileNameSrc = fileSrc.replace(/^\.\//, ''); 66 | 67 | if (file.isRouter) { 68 | routerList.push({ 69 | path: `/${routerPath}`, 70 | name: `${routerName}`, 71 | component: () => import(`@/views/${fileNameSrc}`) 72 | }); 73 | } 74 | }); 75 | return routerList; 76 | }; -------------------------------------------------------------------------------- /src/utils/interface.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'vue' 2 | 3 | export interface IRouter { 4 | path: string, 5 | name: string, 6 | component: Component 7 | } -------------------------------------------------------------------------------- /src/views/Home/components/HomeContent.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 103 | 104 | -------------------------------------------------------------------------------- /src/views/Home/components/HomeSwiper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 48 | -------------------------------------------------------------------------------- /src/views/Home/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 76 | 77 | -------------------------------------------------------------------------------- /src/views/Index/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/Login/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 107 | 108 | -------------------------------------------------------------------------------- /src/views/Mine/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 64 | 65 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // 自己自定义的的 ts 校验规则; 有点偏严格; 建议初始入手的小伙伴或者项目中时常报错警告的情况下使用下面注释掉的项目自带的 ts 校验规则 2 | { 3 | "compilerOptions": { 4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 5 | /* Basic Options */ 6 | // 编译输出目标 ES 版本 "incremental": true/* Enable incremental compilation */ 7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | // 采用的模块系统 9 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 10 | // 编译过程中需要引入的库文件的列表 11 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"], /* Specify library files to be included in the compilation. */ 12 | // 允许编译javascript文件 13 | "allowJs": true, /* Allow javascript files to be compiled. */ 14 | // "checkJs": true, /* Report errors in .js files. */ 15 | "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 16 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 17 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 18 | // 是否包含可以用于 debug 的 sourceMap 19 | "sourceMap": true, /* Generates corresponding '.map' file. */ 20 | // "outFile": "./", /* Concatenate and emit output to single file. */ 21 | // "outDir": "./", /* Redirect output structure to the directory. */ 22 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 23 | // "composite": true, /* Enable project compilation */ 24 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // 从 tslib 导入外部帮助库: 比如__extends,__rest等 28 | "importHelpers": true, /* Import emit helpers from 'tslib'. */ 29 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 30 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 31 | 32 | /* 以严格模式解析 Strict Type-Checking Options */ 33 | "strict": true, /* Enable all strict type-checking options. */ 34 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 35 | // "strictNullChecks": true, /* Enable strict null checks. */ 36 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 37 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 38 | // 定义一个变量就必须给它一个初始值 39 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 40 | // 忽略 this 的类型检查, Raise error on this expressions with an implied any type. 41 | "noImplicitThis": false, /* Raise error on 'this' expressions with an implied 'any' type. */ 42 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 43 | 44 | /* Additional Checks */ 45 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 46 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 47 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 48 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 49 | 50 | /* Module Resolution Options */ 51 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 52 | // 解析非相对模块名的基准目录 53 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 54 | // 指定特殊模块的路径 55 | "paths": { "@/*": ["src/*"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 56 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 57 | // "typeRoots": [], /* List of folders to include type definitions from. */ 58 | // 设置引入的定义文件 59 | "types": ["node", "webpack-env", "mocha", "chai"],/* Type declaration files to be included in compilation. */ 60 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 61 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 62 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 63 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 64 | 65 | /* Source Map Options */ 66 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 68 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 69 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 70 | 71 | // 启用装饰器 /* Experimental Options */ 72 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 73 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 74 | 75 | /* Advanced Options */ 76 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 77 | // 给错误和消息设置样式,使用颜色和上下文。 78 | "pretty": true, 79 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 80 | }, 81 | // ts 管理的文件 82 | "include": [ 83 | "src/**/*.ts", 84 | "src/**/*.tsx", 85 | "src/**/*.vue", 86 | "tests/**/*.ts", 87 | "tests/**/*.tsx" 88 | ], 89 | // ts 排除的文件 90 | "exclude": ["node_modules"] 91 | } 92 | 93 | // { 94 | // "compilerOptions": { 95 | // "target": "es5", 96 | // "module": "esnext", 97 | // "strict": true, 98 | // "jsx": "preserve", 99 | // "importHelpers": true, 100 | // "moduleResolution": "node", 101 | // "skipLibCheck": true, 102 | // "esModuleInterop": true, 103 | // "allowSyntheticDefaultImports": true, 104 | // "sourceMap": true, 105 | // "baseUrl": ".", 106 | // "types": [ 107 | // "webpack-env" 108 | // ], 109 | // "paths": { 110 | // "@/*": [ 111 | // "src/*" 112 | // ] 113 | // }, 114 | // "lib": [ 115 | // "esnext", 116 | // "dom", 117 | // "dom.iterable", 118 | // "scripthost" 119 | // ] 120 | // }, 121 | // "include": [ 122 | // "src/**/*.ts", 123 | // "src/**/*.tsx", 124 | // "src/**/*.vue", 125 | // "tests/**/*.ts", 126 | // "tests/**/*.tsx" 127 | // ], 128 | // "exclude": [ 129 | // "node_modules" 130 | // ] 131 | // } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | // 代码压缩 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 4 | 5 | // gzip压缩 6 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 7 | 8 | // 是否为生产环境 9 | const isProduction = process.env.NODE_ENV !== 'development' 10 | 11 | // 本地环境是否需要使用cdn; 值对应 true / false 12 | const devNeedCdn = true 13 | 14 | // cdn链接 15 | // 插件或者框架具体cdn链接,可去官网查找,一般都会有对应的cdn链接地址; 16 | // 项目资源 如打包后的静态资源(改变不频繁) css, js, img 等资源 可上传到公司cdn服务器;在把对应的链接写入css 即可 17 | const cdn = { 18 | // cdn:模块名称和模块作用域命名(对应window里面挂载的变量名称) 19 | externals: { 20 | // vue: 'vue 官方cdn链接', 21 | // vuex: 'Vuex 官方cdn链接', 22 | // 'vue-router': 'VueRouter 官方cdn链接' 23 | }, 24 | // cdn的css链接 25 | css: [], 26 | // cdn的js链接 27 | js: [] 28 | } 29 | 30 | module.exports = { 31 | productionSourceMap: false, 32 | devServer: { 33 | port: 8088, // 端口号 34 | host: 'localhost', 35 | https: false, // https:{type:Boolean} 36 | open: true, //配置自动启动浏览器 37 | // 可配置多个代理 proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理 38 | proxy: { 39 | '/api1': { 40 | target: '', 41 | ws: true, 42 | changeOrigin: true 43 | }, 44 | '/api2': { 45 | target: '' 46 | } 47 | } 48 | }, 49 | css: { 50 | loaderOptions: { 51 | // rem 适配; 项目中写实际的ui稿尺寸即可, 插件会自动换算 52 | // selectorBlackList 配置项 排除某些你不想转换rem的样式, 比如ui框架,插件的像素值; 项目中的话不想换算rem的建议统一前置类名 53 | postcss: { 54 | plugins: [ 55 | // require('autoprefixer')({ 56 | // browsers: [ 57 | // 'Android 4.1', 58 | // 'iOS 7.1', 59 | // 'Chrome > 31', 60 | // 'ff > 31', 61 | // 'ie >= 8' 62 | // ], 63 | // }), 64 | require('postcss-pxtorem')({ 65 | rootValue: 37.5, 66 | propList: ['*'], 67 | minPixelValue: 2, // 最小像素值 68 | selectorBlackList: ['.ignore', '.hairlines','van-'] // 排除van-开头(即vant库中的css样式)的class名 69 | }) 70 | // require('postcss-pxtorem')({ //配置项,详见官方文档 71 | // viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750. 72 | // viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334. 73 | // unitPrecision: 3, // (指定`px`转换为视窗单位值的小数位数(很多时候无法整除). 74 | // viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw. 75 | // selectorBlackList: ['.ignore', '.hairlines','van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名. 76 | // minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值. 77 | // mediaQuery: false // 允许在媒体查询中转换`px`. 78 | // }), // 换算的基数 79 | ] 80 | } 81 | } 82 | }, 83 | chainWebpack: config => { 84 | // ============压缩图片 start============ 85 | config.module 86 | .rule('images') 87 | .use('image-webpack-loader') 88 | .loader('image-webpack-loader') 89 | .options({ bypassOnDebug: true }) 90 | .end() 91 | // ============注入cdn start============ 92 | config.plugin('html').tap(args => { 93 | // 生产环境或本地需要cdn时,才注入cdn 94 | if (isProduction || devNeedCdn) args[0].cdn = cdn 95 | return args 96 | }) 97 | }, 98 | configureWebpack: config => { 99 | // 用cdn方式引入,则构建时要忽略相关资源 100 | if (isProduction || devNeedCdn) config.externals = cdn.externals 101 | 102 | // 生产环境相关配置 103 | if (isProduction) { 104 | // 代码压缩 105 | config.plugins.push( 106 | new UglifyJsPlugin({ 107 | uglifyOptions: { 108 | //生产环境自动删除console 109 | compress: { 110 | warnings: false, // 若打包错误,则注释这行 111 | drop_debugger: true, 112 | drop_console: true, 113 | pure_funcs: ['console.log'] 114 | } 115 | }, 116 | sourceMap: false, 117 | parallel: true 118 | }) 119 | ) 120 | 121 | // gzip压缩 122 | const productionGzipExtensions = ['html', 'js', 'css'] 123 | config.plugins.push( 124 | new CompressionWebpackPlugin({ 125 | filename: '[path].gz[query]', 126 | algorithm: 'gzip', 127 | test: new RegExp( 128 | '\\.(' + productionGzipExtensions.join('|') + ')$' 129 | ), 130 | threshold: 10240, // 只有大小大于该值的资源会被处理 10240 131 | minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理 132 | deleteOriginalAssets: false // 删除原文件 133 | }) 134 | ) 135 | 136 | // 公共代码抽离 137 | config.optimization = { 138 | splitChunks: { 139 | cacheGroups: { 140 | vendor: { 141 | chunks: 'all', 142 | test: /node_modules/, 143 | name: 'vendor', 144 | minChunks: 1, 145 | maxInitialRequests: 5, 146 | minSize: 0, 147 | priority: 100 148 | }, 149 | common: { 150 | chunks: 'all', 151 | test: /[\\/]src[\\/]js[\\/]/, 152 | name: 'common', 153 | minChunks: 2, 154 | maxInitialRequests: 5, 155 | minSize: 0, 156 | priority: 60 157 | }, 158 | styles: { 159 | name: 'styles', 160 | test: /\.(sa|sc|c)ss$/, 161 | chunks: 'all', 162 | enforce: true 163 | }, 164 | runtimeChunk: { 165 | name: 'manifest' 166 | } 167 | } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | --------------------------------------------------------------------------------