# vue3-typescript-template **Repository Path**: tomxiang/vue3-typescript-template ## Basic Information - **Project Name**: vue3-typescript-template - **Description**: No description available - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-08 - **Last Updated**: 2021-05-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: Vue ## README 原文: [掘金文章](https://juejin.im/post/6887867687897301006) 关注公众号: 微信搜索 `web全栈进阶` ; 收货更多的干货 ## 运行项目 - ` yarn install --> yarn serve ` - 由于是测试服,后台环境tag时常改变,偶尔会遇到接口报错的情况 ## 一、开篇 - `vue3.0beta`版正式上线,作为新技术热爱者,新项目将正式使用`vue3.0`开发; 接下来总结(对自己技术掌握的稳固)介绍(分享有需要的猿友) - 上篇博客介绍了`vue3.0`常用语法及开发技巧;有需要的请点击 [Vue3.0 进阶、环境搭建、相关API的使用](https://juejin.im/post/6877832502590111757) - 觉得对您有用的 `github` 点个 `star` 呗 - 项目`github`地址:`https://github.com/laijinxian/vue3-typescript-template` ## 二、项目介绍(移动端) - 1)技术栈: `vue3 + vuex + typescript + webpack + vant-ui + axios + less + postcss-pxtorem(rem适配)` - 2)没用官方构建工具`vite`原因:`vite` 坑还真的不少,有时候正常写法`webpack`没问题, 在`vite`上就报错;一脸懵逼的那种, `vite` 的`github` 提 Issues 都没用, 维护人员随便回答了下就把我的 `Issues` 给关了,我也是醉了; - 3)不过自己还是很期待 `vite` 的, 等待他成熟吧, 在正式使用; - 4)涉及点:目前只贴出项目初期的几个功能 - `webpack require` 自动化注册路由、自动化注册异步组价 - `axios` 请求封装(请求拦截、响应拦截、取消请求、统一处理) - `vuex` 业务模块化、 接管请求统一处理 ## 三、项目搭建 可参考上篇文章 [Vue3.0 进阶、环境搭建、相关API的使用](https://juejin.im/post/6877832502590111757) 1. `vue-cli、vue` 下载最新版本 2. 执行命令 `vue create my_app_name` 3. 执行完上面命令接下来选择手动配置(第三个),不要选择默认配置,有很多我们用不上,我的选择如下图: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de9c45a9a40543df9c7240c62b747c27~tplv-k3u1fbpfcp-watermark.image) ## 三、项目主要功能 **1. `webpack require` 自动化注册路由、自动化注册异步组价** ``` // 该文件在 utils 下的 global.ts // 区分文件是否自动注册为组件,vue文件定义 isComponents 字段; 区分是否自动注册为路由定义 isRouter 字段 // 使用方式分别在 main.ts 里方法asyncComponent() 以及路由文件router下的index.ts 方法 vueRouters() import { defineAsyncComponent } from 'vue' import { app } from '../main' import { IRouter } from './interface' // 获取所有vue文件 function getComponent() { return require.context('../views', true, /\.vue$/); } // 首字母转换大写 function letterToUpperCase(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } // 首字母转换小写 function letterToLowerCase(str: string): string { return str.charAt(0).toLowerCase() + str.slice(1); } export const asyncComponent = (): void => { // 获取文件全局对象 const requireComponents = getComponent(); requireComponents.keys().forEach((fileSrc: string) => { const viewSrc = requireComponents(fileSrc); const fileNameSrc = fileSrc.replace(/^\.\//, '') const file = viewSrc.default; if (viewSrc.default.isComponents) { // 异步注册组件 let componentRoot = defineAsyncComponent( () => import(`@/views/${fileNameSrc}`) ) app.component(letterToUpperCase(file.name), componentRoot) } }); }; // 获取路由文件 export const vueRouters = (): IRouter[] => { const routerList: IRouter[] = []; const requireRouters = getComponent(); requireRouters.keys().forEach((fileSrc: string) => { // 获取 components 文件下的文件名 const viewSrc = requireRouters(fileSrc); const file = viewSrc.default; // 首字母转大写 const routerName = letterToUpperCase(file.name); // 首字母转小写 const routerPath = letterToLowerCase(file.name); const fileNameSrc = fileSrc.replace(/^\.\//, ''); if (file.isRouter) { routerList.push({ path: `/${routerPath}`, name: `${routerName}`, component: () => import(`@/views/${fileNameSrc}`) }); } }); return routerList; }; ``` **2. `axios` 请求封装(请求拦截、响应拦截、取消请求、统一处理)** ``` import axios, { AxiosRequestConfig, AxiosResponse, Canceler } from 'axios' import router from '@/router' import { Toast } from 'vant' if (process.env.NODE_ENV === 'development') { // 开发环境 axios.defaults.baseURL = `https://test-mobileapi.qinlinkeji.com/api/` } else { // 正式环境 axios.defaults.baseURL = `正式环境地址` } let sourceAjaxList: Canceler[] = [] export const axionInit = () => { axios.interceptors.request.use((config: AxiosRequestConfig) => { // 设置 cancel token 用于取消请求 (当一个接口出现401后,取消后续多有发起的请求,避免出现好几个错误提示) config.cancelToken = new axios.CancelToken(function executor(cancel: Canceler): void { sourceAjaxList.push(cancel) }) // 存在 sessionId 为所有请求加上 sessionId if (localStorage.getItem(`h5_sessionId`) && config.url!.indexOf('/user/login') < 0) config.url += ('sessionId=' + localStorage.getItem(`h5_sessionId`)) if (!config.data) config.data = {} return config }, function (error) { // 抛出错误 return Promise.reject(error) }) axios.interceptors.response.use((response: AxiosResponse) => { const { status, data } = response if (status === 200) { // 如果不出现错误,直接向回调函数内输出 data if (data.code === 0) { return data } else if (data.code === 401) { // 出现未登录或登录失效取消后面的请求 sourceAjaxList.length && sourceAjaxList.length > 0 && sourceAjaxList.forEach((ajaxCancel, index) => { ajaxCancel() // 取消请求 delete sourceAjaxList[index] }) Toast({ message: data.message, duration: 2000 }) return router.push('/login') } else { return data } } else { return data } }, error => { const { response } = error // 这里处理错误的 http code or 服务器或后台报错 if (!response || response.status === 404 || response.status === 500) { if (!response) { console.error(`404 error %o ${error}`) } else { if (response.data && response.data.message) { Toast.fail({ message: '请求异常,请稍后再试!', duration: 2000 }) } } } return Promise.reject(error.message) }) } ``` **3. `vuex` 业务模块化、 接管请求统一处理** ``` import { Module } from 'vuex' import { IGlobalState, IAxiosResponseData } from '../../index' import * as Types from './types' import { IHomeState, ICity, IAccessControl, ICommonlyUsedDoor, IcurrentCommunity } from './interface' import * as API from './api' const state: IHomeState = { cityList: [], currentCommunity: { communityId: '', communityName: '' }, commonlyUsedDoor: { doorControlId: '', doorControlName: '' }, accessControlList: [] } const home: Module = { namespaced: true, state, actions: { // 获取小区列表 async [Types.GET_CITY_LIST]({ commit, rootState }) { console.log(rootState.login.userInfo.userId) const result = await API.getCityList(rootState.login.userInfo.userId) if (result.code !== 0) return commit(Types.GET_CITY_LIST, result.data) commit(Types.SET_CURRENT_COMMUNIRY, result.data[0]) }, // 获取小区门禁列表 async [Types.GET_ACCESS_CONTROL_LIST]({ commit, rootState }) { const result = await API.getCityAccessControlList({ userId: rootState.login.userInfo.userId, communityId: state.currentCommunity.communityId }) if (result.code !== 0) return commit(Types.GET_ACCESS_CONTROL_LIST, result.data.userDoorDTOS) commit(Types.SET_COMMONLY_USERDOOR, result.data.commonlyUsedDoor) }, }, mutations: { // 设置小区列表 [Types.GET_CITY_LIST](state, cityList: ICity[]) { if (cityList.length !== 0) state.cityList = cityList }, // 设置小区门禁列表 [Types.GET_ACCESS_CONTROL_LIST](state, accessControlList: IAccessControl[]) { if (accessControlList.length !== 0) return state.accessControlList = accessControlList }, // 设置当前小区 [Types.SET_CURRENT_COMMUNIRY](state, currentCommunity: IcurrentCommunity) { state.currentCommunity.communityId = currentCommunity.communityId state.currentCommunity.communityName = currentCommunity.communityName }, // 设置常用小区 [Types.SET_COMMONLY_USERDOOR](state, commonlyUsedDoor: ICommonlyUsedDoor) { state.commonlyUsedDoor = commonlyUsedDoor state.commonlyUsedDoor = commonlyUsedDoor } } } export default home ``` **4. `home` 文件代码** ``` ``` **5. `login` 文件代码** ``` ``` ## 四、项目ui ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b24cd073cbe4d89a25fd14ef24cb678~tplv-k3u1fbpfcp-watermark.image) ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6adf633bd1b94901b09f277c9aeec2f0~tplv-k3u1fbpfcp-watermark.image) ## 五、结语 以上为个人实际项目开发总结, 有不对之处欢迎留言指正