基于vite + vue3 +ts 搭建一个前端项目

一、创建项目

1. 创建一个项目文件夹,并打开bash
2. 输入指令:npm init vite
注意:如果此时在创建项目过程中,不能通过键盘上下键,选择项目选项。改用:winpty npm.cmd init vite
3. 然后选择项目选项。
在这里插入图片描述
4. npm i 安装依赖
5. 最后npm run dev启动项目即可。

二、基本配置

1. 基本配置
npm install @types/node --save-dev

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  server: {
    host: '0.0.0.0',
    port: 8080,
  },
  // 导入路径简写
  resolve: {
    alias: [
      {
        find: '@',
        replacement: resolve(__dirname, 'src'),
      },
    ],
  },
});

2. 配置路由

npm install vue-router@4

在这里插入图片描述
在这里插入图片描述

3. 安装sass/scss

npm install -D sass sass-loader
npm install -D scss scss-loader

4. 自动引入
vue component 文档
auto-import

npm i unplugin-auto-import unplugin-vue-components -D

npm run dev, 执行完成之后, 会生成两个文件 auto-imports.d, components.d
:如果引入后,启动报错,升级node版本,要求14以上。
window升级node版本:where node查看node位置,然node官网下载,替换旧node文件即可。

5. 配置Element-plus的按需引入

首先引入依赖 npm i element-plus
在vite.config.js文件中配置

import Components from 'unplugin-vue-components/vite';
import AutoImport from 'unplugin-auto-import/vite';
import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
plugins: [
    vue(),
    //自动导入模块,不需要在文件中再手动引入
    AutoImport({
      imports: ['vue'],
      dts: 'src/auto-import.d.ts',
    }),
    Components({
      resolvers:[ElementPlusResolver()]
    }),
  ],

配置后,直接使用组件,会自动导入,不用再手动引入。main.ts文件中,也不用再配置Element-Plus

<template>
  <div>
    <Home class="chose">首页</Home>
    <el-input size="small"></el-input>
  </div>
</template>
<script setup lang="ts">
</script>

<style scoped lang="scss">
.chose {
  font-size: 16px;
  color: aqua;
}
</style>

6. eslint 配置

npm i 插件
在这里插入图片描述
新建.eslintrc.js文件。

module.exports = {
    root: true,
    env: {
        browser: true,
        node: true
    },
    parser: 'vue-eslint-parser',
    parserOptions: {
        parser: '@typescript-eslint/parser',
        ecmaVersion: 'latest',
        sourceType: 'module',
        jsxPragma: 'vue'
    },
    settings: {
        'import/resolver': {
            alias: {
                map: ['@', './src']
            }
        }
    },
    extends: [
        // 这里新增vue3支持
        'plugin:vue/vue3-recommended',
        'plugin:@typescript-eslint/recommended'
        // 'prettier',
        // 'plugin:prettier/recommended',
    ],
    plugins: ['vue'],
    rules: {
        // @typescript-eslint
        '@typescript-eslint/explicit-function-return-type': 'off', // 需要函数和类方法的显式返回类型
        '@typescript-eslint/no-explicit-any': 'off', // 禁止使用该 any 类型
        '@typescript-eslint/no-var-requires': 'off', // 不允许使用 require 语句,除了在 import 语句中
        '@typescript-eslint/no-empty-function': 'off', // 禁止空函数
        '@typescript-eslint/no-empty-interface': 'off', // 禁止空接口
        '@typescript-eslint/no-use-before-define': 'warn', // 在定义之前禁止使用变量
        '@typescript-eslint/ban-ts-comment': 'off', // 禁止 @ts-<directive> 使用评论或在指令后要求描述
        '@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
        '@typescript-eslint/no-non-null-assertion': 'off', // '!'不允许使用后缀运算符的非空断言
        '@typescript-eslint/explicit-module-boundary-types': 'off', // 需要导出函数和类的公共类方法的显式返回和参数类型
        '@typescript-eslint/no-unused-vars': [ // 禁止未使用的变量
            'warn',
            {
                argsIgnorePattern: '^_',
                varsIgnorePattern: '^_',
            },
        ],
        // vue
        // 'vue/custom-event-name-casing': 1, // 为自定义事件名称强制使用特定大小写
        'vue/attributes-order': 'off', // 强制执行属性顺序
        'vue/one-component-per-file': 'off', // 强制每个组件都应该在自己的文件中
        'vue/html-closing-bracket-newline': 'off', // 在标签的右括号之前要求或禁止换行
        'vue/multiline-html-element-content-newline': 'off', // 在多行元素的内容之前和之后需要换行符
        'vue/singleline-html-element-content-newline': 'off', // 在单行元素的内容之前和之后需要换行符
        'vue/multi-word-component-names': 'off', //强制组件名称必须是由多个单词组成
        'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
        'vue/require-default-prop': 'off', // 需要 props 的默认值
        // 'vue/html-indent': ['warn', 2], // 在<template>中强制一致缩进
        'vue/html-indent': 'off', // 在<template>中强制一致缩进
        // 'vue/html-self-closing': 1, // 执行自闭合的风格
        'vue/max-attributes-per-line': 'off', // 强制每行属性的最大数量
        'vue/no-v-html': 'off',
        // // ESLint
        'space-before-function-paren': 'off' // 强制在 function的左括号之前使用一致的空格
    }
};


vite.config.ts配置

import eslintPlugin from 'vite-plugin-eslint';
plugins: [
    eslintPlugin({
      emitError: true, //打印错误
      // emitError: 'production' === NODE_ENV
    },
    AutoImport({
      eslintrc: {
        enabled: true, // Default `false`
        filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
        globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
      },
    }),
  ],

7. 封装axios请求
utils/useAxiosApi.ts文件:

import { useAxios } from '@vueuse/integrations/useAxios';
import axios, { AxiosRequestConfig } from 'axios';
// 创建一个axios实例
const instance = axios.create({
  withCredentials: false,
  timeout: 30 * 60 * 1000, //设置超时
});
// loading
let loading = ref();
//正在请求的数量
let requestCount = 0;
//显示loading
const showLoading = () => {
  if (requestCount === 0 && !loading) {
    loading = ElLoading.service({
      text: '正在加载中......',
      background: 'rgba(0, 0, 0, 0.7)',
      spinner: 'el-icon-loading',
    }) as any;
  }
  requestCount++;
};
//隐藏loading
const hideLoading = () => {
  requestCount--;
  if (requestCount == 0) {
    //@ts-ignore
    loading.close();
  }
};

//请求拦截器
instance.interceptors.request.use(
  (config) => {
    showLoading();
    // const token = store.state.user.token;
    // if (token) {
    config.headers = {
      ...config.headers,
      Authorization: `unauthorized`,
    };
    // }
    return config;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  }
);
instance.interceptors.response.use(
  (response) => {
    hideLoading();
    return response.data;
  },
  (error) => {
    if (error.response && error.response.status) {
      const status = error.response.status;
      let message = ref('' as any);
      switch (status) {
        case 400:
          message = '请求错误';
          break;
        case 401:
          message = '请求错误';
          break;
        case 404:
          message = '请求地址出错';
          break;
        case 408:
          message = '请求超时';
          break;
        case 500:
          message = '服务器内部错误!';
          break;
        case 501:
          message = '服务未实现!';
          break;
        case 502:
          message = '网关错误!';
          break;
        case 503:
          message = '服务不可用!';
          break;
        case 504:
          message = '网关超时!';
          break;
        case 505:
          message = 'HTTP版本不受支持';
          break;
        default:
          message = '请求失败';
      }
      ElMessage.error(message);
      return Promise.reject(error);
    }
  }
);
export default function useAxiosApi(url: string, config: AxiosRequestConfig) {
  return useAxios(url, config, instance);
}
export { axios };

使用:

import request from '../utils/useAxiosApi';
export function integrations() {
  return request(`url`, params={});
}

8. 状态管理VUEX
store/index.ts文件:
在这里插入图片描述
main.ts文件中声明:

import store from './store';
const app = createApp(App);
app.use(store).mount('#app');

modules/index.ts文件:

// 批量直接引入模块
const modulesFiles = import.meta.globEager('./*.ts');
const modules: any = {};
for (const key in modulesFiles) {
  modules[key.replace(/(\.\/|\.ts)/g, '')] = modulesFiles[key].default;
}
export { modules, modulesFiles };

自定义模块:modules/mainStore.ts

import { Module } from 'vuex';

const mainStore: Module<MainStore, unknown> = {
  state() {
    return {
      num: 10,
    };
  },
  getters: {},
  mutations: {},
  actions: {},
};
export default mainStore;

注:MainStore是自定义去全局接口。项目目录下新建global.ts文件。定义为该接口类型后,属性要与该接口声明的保持一致。详情参考typeScript
9. 常用工具,自定义拖拽指令。
src/directive/index.ts文件:

export default {
  install(app: any){
    app.directive('dialogPage', {
      mounted(el: any){
        const dialogHeaderEl = el.querySelector('.page-title');
        const dragDom = el;
        dialogHeaderEl.style.cssText += ';cursor:move;'
        // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
        const sty = (function() {
          if ((window.document as any).currentStyle) {
            return (dom: any, attr: any) => dom.currentStyle[attr];
          } else{
            return (dom: any, attr: any) => getComputedStyle(dom)[attr];
          }
        })()

        dialogHeaderEl.onmousedown = (e: any) => {
          // 鼠标按下,计算当前元素距离可视区的距离
          const disX = e.clientX - dialogHeaderEl.offsetLeft;
          const disY = e.clientY - dialogHeaderEl.offsetTop;

          const screenWidth = document.body.clientWidth; // body当前宽度
          const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)

          const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
          const dragDomheight = dragDom.offsetHeight; // 对话框高度

          const minDragDomLeft = dragDom.offsetLeft;
          const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;

          const minDragDomTop = dragDom.offsetTop;
          const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;


          // 获取到的值带px 正则匹配替换
          let styL = sty(dragDom, 'left');
          let styT = sty(dragDom, 'top');

          // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
          if(styL.includes('%')) {
            styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
            styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
          }else {
            styL = +styL.replace(/\px/g, '');
            styT = +styT.replace(/\px/g, '');
          }

          document.onmousemove = function (e) {
            // 通过事件委托,计算移动的距离
            let left = e.clientX - disX;
            let top = e.clientY - disY;

            // 边界处理
            if (-(left) > minDragDomLeft) {
              left = -(minDragDomLeft);
            } else if (left > maxDragDomLeft) {
              left = maxDragDomLeft;
            }

            if (-(top) > minDragDomTop) {
              top = -(minDragDomTop);
            } else if (top > maxDragDomTop) {
              top = maxDragDomTop;
            }

            // 移动当前元素
            dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
          };

          document.onmouseup = function () {
            document.onmousemove = null;
            document.onmouseup = null;
          };
        }
      }
    })
  }
}

在main.ts中挂载指令:
在这里插入图片描述

使用:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值