vite+ts+vue3项目初始化 -- eslint+prettier代码格式化

一、配置 @ 代替 src 目录

步骤:
   一、 yarn add @types/node
   二、vite.config.ts 中 和 plugins 平齐 配置
         resolve: {
	        alias:{
	            "@": path.resolve(__dirname,'./src')
	        }
		 }
		 注意 path 需要引入  import path from "path"


注意:配置完路径别名之后,用路径别名引入 ts、vue文件会报错

		在 compilerOptions 对象里面配置如下:

        // 修改引入ts文件报错
        "baseUrl": "./",
        "paths": {
            "@/*": [
                "src/*"
            ]
        }

二、项目中引入scss

只需要在项目中下载sass  scss即可:  yarn add sass scss

三、引入公共样式

@charset "utf-8";
/*全局样式*/
body,ol,ul,h1,h2,h3,h4,h5,h6,p,th,td,dl,dd,form,fieldset,legend,figure,input,textarea,select,div{margin:0;padding:0;box-sizing: border-box;}
html{font-size: 10px; height: 100%; overflow: hidden}
body{word-wrap:break-word; height: 100%; overflow: hidden;font-size: 14px;color: #333}
fieldset,a img,.bor0{border:0;}
li{list-style:none;}
/*a,u,s,del{color:#666;text-decoration:none;} !*文本没有修饰*!*/
/*i,em,b{font-style:normal;font-weight:100;}!*文本不倾斜不加粗*!*/
a img{border:0;}
img{vertical-align:middle;}
table{border-collapse:collapse;}
input,textarea{outline:none;}/*去除谷歌点击蓝色虚框*/
textarea{resize:none;}/*禁止多行文本输入框的拖动*/
#app{height: 100%; overflow: hidden;}
.ind2{text-indent:2em;}
.l{float:left;}
.r{float:right;}
.rel{position:relative;}
.abs{position:absolute;}
.over{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}/*溢出出现省略号*/
.vcenter:after{content:"";display:inline-block;vertical-align:middle;height:100%;width:0;}
.clear{zoom:1;}
.clear:after{content:"";display:block;clear:both;}/*清除浮动*/
.cu{cursor:pointer;}/*鼠标指针为手型*/

.el-textarea__inner{
    font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}

::-webkit-scrollbar {
    width: 6px;
    height: 6px;
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 4px;
}

:hover::-webkit-scrollbar-thumb {
    background: hsla(0, 0%, 53%, 0.4);
}

:hover::-webkit-scrollbar-track {
    background: hsla(0, 0%, 53%, 0.1);
}

四、引入组件报红色波浪线

在这里插入图片描述

在vite-env.d.ts 声明文件中配置如下:
  declare module "*.vue" {
    import { DefineComponent } from "vue"
	    const component: DefineComponent<{}, {}, any>
	    export default component
	}

五、 有些语法不认识需要在vscode中安装 Volar 插件

在这里插入图片描述

六、自动导包(vue、vue-router、element-plus、element-plus样式)

步骤:
   一、yarn add element-plus unplugin-auto-import unplugin-vue-components vite-plugin-style-import consola
   二、在vite.config.ts 中( 在plugins数组内,和 vue() 平级 ):

		import AutoImport from 'unplugin-auto-import/vite'
		import Components from 'unplugin-vue-components/vite'
		import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
   
         //  自动导入element-plus的样式
        //createStyleImportPlugin({
            //resolves: [ElementPlusResolve()],
            //libs: [
              //{
                //libraryName: 'element-plus',
                //esModule: true,
                //resolveStyle: (name: string) => {
                  //return `element-plus/theme-chalk/${name}.css`
                //}
              //}
            //]
        //}),

        Components({//自定义的模块
            dirs: ['src/components'],
            extensions: ['vue', 'ts'],
            resolvers: [ElementPlusResolver()]
        }),
        AutoImport({ // 插件进行自动导入相关的依赖库
            //安装两行后你会发现在组件中不用再导入ref,reactive等
            imports: ['vue', 'vue-router'],
            // 可选,用于自动导入组件类型
            dts: 'src/components.d.ts'
        })

七、配置方向代理和端口号

在vite.config.ts中,与 plugins 平齐:
   server: {
        port: 8080,//自定义端口
        // 是否开启 https
        https: false,
        open: true,
        proxy: {
            '/api': {
                // 后台地址
                target: 'http://127.0.0.1:8300/',
                changeOrigin: true,
                rewrite: path => path.replace(/^\/api/, '')
            },
            '/api2': {
                // 后台地址
                target: 'http://127.0.0.1:8956/',
                changeOrigin: true,
                rewrite: path => path.replace(/^\/api2/, '')
            }
        }
    }

八、父子组件通讯(只写在ts中的方法)

父组件传值给子组件(方式一):
   父组件:
      <Child :msg="msg"></Child>
   子组件:
       defineProps<{
	        msg: string
	   }>()

父组件传值给子组件(方式二 -- 默认值):
   父组件:
      <Child :msg="msg"></Child>
   子组件(使用 withDefaults ,两个参数,第一个参数和上面的是一样的, 第二个参数是默认值):
       withDefaults( defineProps<{
	        msg: string
	    }>(), {
	        msg: "默认值"
	    })



子传父:
    父组件:
       <Common @giveFatherStr="handleData"></Common>
		const handleData = (val: string) => {
		    console.log(val);
		}
	子组件:
	   const emit = defineEmits(['giveFatherStr'])
	    const giveFatherData = () => {
	        emit("giveFatherStr", "传给父组件的值")
	    }
子传父(方式二):
   父组件是一样的
   子组件:
       const emit = defineEmits<{
			(event: "giveFatherStr", flag: string): void
	   }>()
	
	   <button @click="emit('giveFatherStr', '传过去的值')">传值</button>

九、pinia的使用(另外一种是一个方式请看pinia的使用)

步骤(不持久化):
   一、 yarn add pinia
   二、main.ts中:   import { createPinia } from "pinia"
                     const pinia = createPinia()
                     app.use(pinia)
   三、创建store目录,里面创建useUserStore.ts文件:
                        import { defineStore } from "pinia";
						export const useUserStore = defineStore("user", () => {
						    // 定义 userName 状态
						    let userName = ref("")
						    // 设置 userName 状态
						    const setUserName = (payload: string) => {
						        userName.value = payload
						    }
						
						    return {
						        userName, setUserName
						    }
						})

步骤(持久化):
   一、yarn add pinia-plugin-persistedstate
   二、main.ts中:    import piniaPluginPersistedstate  from 'pinia-plugin-persistedstate'
   					  import { createPinia } from "pinia"
                      const pinia = createPinia()
				      pinia.use(piniaPluginPersistedstate)
				      app.use(pinia)
   三、配置持久化: defineStore方法的第三个参数加上:   { persist: true }


配置完成之后就是在组件中使用了:
  import { useUserStore } from "@/store/useUserPinia.ts"
  const store = useUserStore()
  import { storeToRefs } from "pinia"  // 结构出来的数据仍然有响应式
  const { userName } from storeToRefs(store)
  const { setUserName } from store  // 注意 pinia 中的 storeToRefs不能结构方法
  // 取值
  console.log(userName)
  // 存值
  setUserName('需要存进去的值')




十、vue-router的使用

步骤:
   一、yarn add vue-router
   二、创建router目录,里面创建index.ts文件
           import { createRouter, createWebHashHistory, createWebHistory } from "vue-router"
		   const routes = [
			    {
			        path: "/",
			        name: 'Laout',
			        component: () => import("@/pages/Layout.vue")
			    }
			]
			const router = createRouter({
			    history: createWebHistory(),
			    routes
			})
			export default router
   三、在main.ts文件中:
           import router from "@/router/index.ts"
           app.use(router)

十一、vue3结合keep-alive和transition的使用

步骤: 
   一、组件的展示区需要用keep-alive包裹
        <router-view class="view" v-slot="{ Component }">
	        <transition name="fade" mode="out-in" appear>
	            <keep-alive :include="cacheList">
	                <component :is="Component" />
	            </keep-alive>
	        </transition>
	    </router-view>

	css中(实现动画):
	   .fade-enter-from, .fade-leave-to{
	        opacity: 0;
	        transform: translateX(40px);
	    }
	    .fade-enter-avtive, .fade-leave-active{
	        transition: all .3s;
	    }

       cacheList 存在pinia中
       
   二、在列表中(由于 beforeRouteEnter 在setup语法糖中不能直接使用,需要在写一个script标签):
       <!-- 缓存用的 -->
		<script lang="ts">
		    import { cachList } from "@/eNum/index.ts"
		    import { useIndexStore } from "@/store/useIndexStore.ts"
		    const store = useIndexStore()
		    const toList = cachList  //  cachList里面存的是 ['UserDetail', 'RoleDetail', 'ProjectDetail'], 就是详情页面在路由表中配置的名称
		    
		    export default defineComponent({
		        beforeRouteEnter(to: any, _: any, next: any) {
		            next(() => {
		                store.setCacheList([to.name])
		            })
		        },
		        beforeRouteLeave(to: any, from: any, next: any) {
		            if(toList.includes(to.name)) {
		                store.setCacheList([from.name])
		            }else{
		                store.setCacheList([])
		            }
		            next()
		        }
		    })
		</script>

   三、详情页面
        <!-- 缓存用的 -->
		<script lang="ts">
		    import { useIndexStore } from '@/store/useIndexStore.ts'; 
		    const store = useIndexStore()
		    export default defineComponent({
		        beforeRouteLeave(to: any, _: any, next: any) {
		            const { isCache } = to.query
		            let cache = isCache ? JSON.parse(isCache) : false
		            if(cache) {
		                store.setCacheList([to.name])
		            }else{
		                store.setCacheList([])
		            }
		            next()
		        }
		    })
		</script>

十二、axios二次封装

步骤:
   一、新建httpInstance.ts
        import axios from "axios";
		import Router from "@/router/index.ts";
		import { useCachStore } from "@/store/useCachStore.ts";
		import { ElMessage, ElLoading } from "element-plus"
		import { storeToRefs } from "pinia";
		const store = useCachStore()
		const { token } = storeToRefs(store)
		
		let instance = axios.create({
		    withCredentials: true, //token凭证
		    timeout: 1000 * 60 * 1
		});
		
		// 请求动画
		let loading: any;
		// 内存中正在请求的数量
		let loadingNum = 0;
		let text = "拼命加载中...";
		function startLoading() {
		    if (loadingNum == 0) {
		        loading = ElLoading.service({
		            lock: true,
		            text: text,
		            spinner: "el-icon-loading",
		            background: "rgba(0, 0, 0, 0.7)"
		        });
		    }
		    loadingNum++;
		}
		
		function endLoading() {
		    //  关闭加载动画
		    loadingNum > 0 && loadingNum--;
		    if (loadingNum <= 0) {
		        loading && loading.close();
		    }
		}
		
		function checkWhetherString(blackList: string[], url: string) {
		    for (let i = 0; i < blackList.length; i++) {
		        var item = blackList[i];
		        if (url.indexOf(item) != -1) {
		            return true;
		        }
		    }
		    return false;
		}
		
		// 请求拦截
		instance.interceptors.request.use(
		    function (config) {
		        config.headers.Accept = "application/json, text/plain, */*";
		        // 黑名单,写在此处的都不进loading加载动画
		        const blackList = ["/gd/year/v1/in/progress", "im/index/newMsg", "/receiveMessage/v1/getMsgNumber"];
		        const { url } = config;
		        if (!checkWhetherString(blackList, url as string)) {
		            startLoading();
		        }
		        //  请求添加token
		       
		        if (token) {
		            config.headers.Authorization = `Bearer ${token}`;
		        }
		        return config;
		    },
		    function (err) {
		        return Promise.reject(err);
		    }
		);
		
		// 响应拦截
		
		instance.interceptors.response.use(
		    (response) => {
		        endLoading();
		        // token过期
		        if (response.data.code != 0) {
		            // Router.replace("/login");
		        }
		        return response.data;
		    },
		    (err) => {
		        endLoading();
		        // console.log(err)
		        ElMessage.error({
		            message: err?.response?.data?.message
		        });
		        if (err.response.status == 401 || err.response.status == 403) {
		            // sessionStorage.clear();
		            // // removeToken()
		            // Router.replace("/login");
		            // window.location.reload();
		        }
		        return Promise.reject(err.message);
		    }
		);
		
		type Http = {
		    get: any,
		    post: any,
		    put: any,
		    delete: any
		}
		const http: Http = {
		    get: function (url: string, options: any) {
		        return new Promise((resolve, reject) => {
		            instance.get(url, options).then((res: any) => {
		                if (res.code == 20000) {
		                    resolve(res.data);
		                } else {
		                    ElMessage.error({
		                        message: res.message
		                    });
		                    reject(res.message);
		                }
		            })
		            .catch((err) => {
		                reject(err);
		            });
		        });
		    },
		    post: function (url: string, data: any, options: any) {
		        return new Promise((resolve, reject) => {
		            instance
		                .post(url, data, options)
		                .then((res: any) => {
		                    if (res.code == 20000) {
		                        resolve(res.data);
		                    } else {
		                        ElMessage.error({
		                            message: res.message
		                        });
		                        reject(res.message);
		                    }
		                })
		                .catch((err) => {
		                    console.log(err);
		                });
		        });
		    },
		
		    put: function (url: string, data: any, options: any) {
		        return new Promise((resolve, reject) => {
		            instance
		                .put(url, data, options)
		                .then((res: any) => {
		                    if (res.code == 20000) {
		                        resolve(res.data);
		                    } else {
		                        ElMessage.error({
		                            message: res.message
		                        });
		                        reject(res.message);
		                    }
		                })
		                .catch((err) => {
		                    console.log(err);
		                });
		        });
		    },
		
		
		    delete: function (url: string, options: any) {
		        return new Promise((resolve, reject) => {
		            instance
		                .delete(url, options)
		                .then((res: any) => {
		                    if (res.code == 20000) {
		                        resolve(res.data);
		                    } else {
		                        ElMessage.error({
		                            message: res.message
		                        });
		                        reject(res.message);
		                    }
		                })
		                .catch((err) => {
		                    console.log(err);
		                });
		        });
		    }
		};
		
		export default http;


   二、新建api/index.ts文件
        import { UserParam, UserListParams } from "@/type/api";
		import axios from "@/utils/httpInstance/index.ts"
		
		// 用户列表
		export const userList = (params: UserParam /* 参数类型 */): Promise<UserListParams  /* 响应的数据类型 */> => {
		    return axios.get(`/api/user/search`, params);
		};
   

十三、eslint + prettier

1、下载包

pnpm install eslint eslint-plugin-vue eslint-config-prettier prettier eslint-plugin-import eslint-plugin-prettier eslint-config-airbnb-base -D 

pnpm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-import-resolver-alias @types/eslint @types/node -D 

2、配置文件
脚本 增加 “lint:create”: “eslint --init”
执行 pnpm lint:create
开始选择
选择需要的配置点击确认就会生成一个 .eslintrc.cjs 文件

//.eslintrc.cjs

module.exports = {
    // 环境:
    env: {
        // 浏览器
        browser: true,
        // 最新es语法
        es2021: true,
        // node环境
        node: true,
    },
    // 扩展的eslint规范语法,可以被继承的规则
    // 字符串数组:每个配置继承它前面的配置
    // 分别是:
    // eslint-plugin-vue提供的
    // eslint-config-airbnb-base提供的
    // eslint-config-prettier提供的
    // 前缀 eslint-config-, 可省略
    extends: ['plugin:vue/vue3-strongly-recommended', 'airbnb-base', 'prettier'],
    // eslint 会对我们的代码进行检验
    // parser的作用是将我们写的代码转换为ESTree(AST)
    // ESLint会对ESTree进行校验
    parser: 'vue-eslint-parser',
    // 解析器的配置项
    parserOptions: {
        // es的版本号,或者年份都可以
        ecmaVersion: 'latest',
        parser: '@typescript-eslint/parser',
        // 源码类型 默认是script,es模块用module
        sourceType: 'module',
        // 额外的语言类型
        ecmaFeatures: {
            tsx: true,
            jsx: true,
        },
    },
    // 全局自定义的宏,这样在源文件中使用全局变量就不会报错或者警告
    globals: {
        defineProps: 'readonly',
        defineEmits: 'readonly',
        defineExpose: 'readonly',
        withDefault: 'readonly',
    },
    // 插件
    // 前缀 eslint-plugin-, 可省略
    // vue官方提供了一个ESLint插件 eslint-plugin-vue,它提供了parser和rules
    // parser为 vue-eslint-parser,放在上面的parsr字段,rules放在extends字段里,选择合适的规则
    plugins: ['vue', '@typescript-eslint'],
    settings: {
        // 设置项目内的别名
        'import/reslover': {
            alias: {
                map: [['@', './src']],
            },
        },
        // 允许的扩展名
        'import/extensions': ['.js', '.jsx', '.ts', '.tsx', '.mjs'],
    },
    // 自定义规则,覆盖上面extends继承的第三方库的规则,根据组内成员灵活定义
    rules: {
        'no-console': 0,
        'vue/valid-template-root': 0,
        'import/no-extraneous-dependencies': 0,
        'no-param-reassing': 0,
        'vue/multi-word-commponent-names': 0,
        'vue/attribute-hyphenation': 0,
        'vue/v-on-event-hyphenation': 0,
    },
};

3、安装vite的插件配合eslint

pnpm i -D vite-plugin-eslint 

4、配置

// vite.config.ts

import eslintPlugin from "vite-plugin-eslint"
plugins: [vue(),  eslintPlugin()]

5、整合 prettier

//  .prettierrc.cjs

module.exports = {
    // 一行最多多少字符
    printWidth: 80,
    // 使用2个空格缩进
    tabWidth: 2,
    // 使用tab缩进,不使用空格
    useTabs: true,
    // 行尾需要分号
    semi: false,
    // 使用单引号
    singleQuote: true,
    // 对象的key仅在必要时使用引号
    quoteProps: "as-needed",
    // jsx不使用单引号,而使用双引号
    jsxSingleQuote: false,
    // 尾随逗号
    trailingComma: "es5",
    // 大括号内的收尾需要空格
    bracketSpacing: true,
    // 箭头函数,只有一个参数的时候,也需要括号
    arrowParens: "always",
    // 每个文件格式化的范围是文件的全部内容
    rangeStart: 0,
    rangeEnd: Infinity,
    // 不需要写文件开头的@prettier
    requirePragma: false,
    // 不需要自动在文件开头插入@prettier
    insertPragma: false,
    // 使用默认的折行标准
    proseWrap: "always",
    // 根据显示样式决定html要不要折行
    htmlWhitespaceSensitivity: "css",
    // 换行符使用lf
    endOfLine: "lf",
}






// .prettierignore

/dist/*
.local
.husky
.history
.output.js
/node_modules/**
src/.DS_Store

**/*.svg
**/*.sh

/public/*
components.d.ts







// .eslintrcignore

*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.history
/bin
.eslintrc.js
prettier.config.js
/src/mock/*

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.DS_Store
dist-ssr
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode
!.vscode/extensions.json
.idea
*.suo
*.njsproj
*.sln
*.sw?

components.d.ts

6、vscode 全局配置文件 settings.json(也可以在各自的项目中配置)

 "[vue]": {
     "editor.formatOnSave": true,
     "editor.defaultFormatter": "esbenp.prettier-vscode"
 },

// 在保存时格式化文件
"editor.formatOnSave": true,

// 保存时候按照 eslint 格式化代码
 "editor.codeActionsOnSave": {
     "source.fixAll": true,
     "source.fixAll.eslint": true,
     "source.fixAll.stylelint": true
 },

7、使用命令将项目中的所有的代码一键格式化
添加脚本
“prettier-format”: “prettier --config .prettierrc.cjs “src/**/*.{vue,js,ts}” --write”
执行
pnpm prettier-format

以上就实现了代码格式化的校验工作,要是各自的项目都有自己的配置
在 .vscode 目录创建 settings.json 文件

{
	"editor.defaultFormatter": "esbenp.prettier-vscode",
	"editor.formatOnSave": true,
	"editor.codeActionsOnSave": {
		"source.fixAll": true,
		"source.fixAll.eslint": true,
		"source.fixAll.stylelint": true
	},
	"[vue]": {
		"editor.formatOnSave": true,
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[ts]": {
		"editor.formatOnSave": true,
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[less]": {
		"editor.formatOnSave": true,
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[scss]": {
		"editor.formatOnSave": true,
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"typescript.tsdk": "node_modules/typescript/lib"
}

十四、husky

1、安装 包

pnpm i husky lint-staged -D

2、创建 husky

//增加脚本
"prepare": "husky install"

// 执行脚本
pnpm prepare

3、执行命令

npx husky add .husky/pre-commit "npx lint-staged" 

4、package.json 中

"dependencies": {},
"lint-staged": {
  "*.{js,jsx,vue,ts,tsx}": [
    "npm run lint",
    "npm run prettier-format"
  ]
}

到此,提交代码不遵守 eslint 规范就不允许提交

下面是提交日志的校验

1、安装包

pnpm install @commitlint/config-conventional @commitlint/cli -D

2、添加

npx husky add .husky/commit-msg "npx --no -- commitlint --edit ${1}"

3、创建配置文件

// commitlint.config.cjs

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [
            2,
            'always',
            [
                // 编译相关的修改,例如发布版本,对项目构建或者依赖的改动
                'build',
                // 新功能(feature)
                'feat',
                // 修复bug
                'fix',
                // 更新某功能
                'update',
                // 重构
                'refactor',
                // 文档
                'docs',
                // 构建过程或者辅助工具的变动,如增加依赖库等
                'chore',
                // 不影响代码运行的变动
                'style',
                // 撤销commit,回滚到上一个版本
                'revert',
                // 性能优化
                'perf',
                // 测试(单元,集成测试)
                'test',
            ],
        ],
        'type-case': [0],
        'type-empty': [0],
        'scope-empty': [0],
        'scope-case': [0],
        'subject-full-stop': [0, 'never'],
        'subject-case': [0, 'never'],
        'header-max-length': [0, 'always', 74],
    },
};

源代码地址

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值