搭建vue3,TypeScript,pinia,scss,element-plus,axios,echarts,vue-router,babylon,eslint,babel,拖拽,rem自适应大屏

1、项目构建

1.1、使用vite初始化项目

1.1.1、创建项目文件夹
mkdir my-vue3
1.1.2、进入项目文件夹
cd my-vue3
1.1.3、初始化项目
npm init vite@latest
1.1.4、输入项目名称

在这里插入图片描述

1.1.5、选择vue

在这里插入图片描述

1.1.6、选择TypeScript

在这里插入图片描述

1.1.7、查看当前源(非必要)
npm config get registry
1.1.8、更换为国内镜像(非必要)
npm config set registry=http://registry.npm.taobao.org/
1.1.9、进入项目
cd vue3-ts-scss
1.1.10、安装依赖
npm install
1.1.11、运行项目
npm run dev
1.1.12、修改部分报错信息

找不到模块“@vitejs/plugin-vue”。你的意思是要将 “moduleResolution” 选项设置为 “node”,还是要将别名添加到 “paths” 选项中?
在根目录tsconfig.node.json文件中修改

"moduleResolution": "node"

在根目录tsconfig.json文件中修改

"moduleResolution": "node"

1.2、项目基础配置

1.2.1、引入 @types/node 包

types/node 是 TypeScript 官方提供的一个类型声明文件包,它包含了 Node.js 核心模块的类型声明。在使用 TypeScript 编写 Node.js 应用程序时,为了获得更好的类型检查和代码提示,你可以通过安装 @types/node 包来获取 Node.js 核心模块的类型声明。

npm install @types/node --save-dev
1.2.2、配置别名

在根目录vite.config.ts文件中添加

import { resolve } from 'path';

export default defineConfig({
  resolve: {
    //别名配置,引用src路径下的东西可以通过@如:import Layout from '@/layout/index.vue'
    alias: [
      {
        find: '@',
        replacement: resolve(__dirname, 'src'),
      },
    ],
  },
});
1.2.3、指定 Vite 开发服务器监听的主机地址

在根目录vite.config.ts文件中添加

export default defineConfig({
	base: './',
  	server: {
    	host: '0.0.0.0',
    	port: 5000,
    	open: true,
  	},
})
1.2.4、配置ts文件采用@方式导入

在根目录tsconfig.json文件中添加配置

{
  "compilerOptions": {
  	"suppressImplicitAnyIndexErrors": true, //允许字符串用作下标
    "ignoreDeprecations": "5.0", //高版本上句报错,此句解决。如此句报错可注释掉
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "exclude": ["node_modules"] // // ts排除的文件
}

在根目录tsconfig.json文件中修改配置

{
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
1.2.5、使用全局变量

在根目录中新建.env.development文件

# 页面标题
VITE_APP_TITLE = 配置VUE3+TS+scss+axios+pinia

# 开发环境配置
VITE_APP_ENV = 'development'

# 开发环境
VITE_APP_BASE_API = '/dev-api'

# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

在根目录中新建.env.production文件

# 页面标题
VITE_APP_TITLE = 配置VUE3+TS+scss+axios+pinia

# 生产环境配置
VITE_APP_ENV = 'production'

# 生产环境
VITE_APP_BASE_API = '/prod-api'

# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'
1.2.6、配置按需引入文件

安装babel-plugin-import

npm install babel-plugin-import --save-dev 

1.3、添加代码检测

添加 ESLint 和 Prettier 可以帮助团队保持一致的编码风格,减少代码中的错误和问题,并提高代码的可读性和可维护性,从而提高开发效率和团队协作效果。

1.3.1、安装eslint
npm install --save-dev eslint eslint-plugin-vue
1.3.2、安装vite-plugin-eslint(eslint结合vite使用)
npm add -D vite-plugin-eslint
1.3.3、安装eslint-parser
npm add -D @babel/core 
npm add -D @babel/eslint-parser
1.3.4、配置vite.config.ts
import eslintPlugin from 'vite-plugin-eslint';
plugins: [
	vue(),
	eslintPlugin({
		include: ['src/**/*.ts', 'src/**/*.vue', 'src/*.ts', 'src/*.vue'],
	}),
]
1.3.5、VsCode安装ESLint插件

在商店中查找ESLint扩展,然后安装它,不需要配置,在项目内如果集成了eslint的npm包,这个插件会根据配置文件,对代码检查问题进行高亮提示(红色波浪线是错误提示,黄色波浪线是警告提示),然后根据弹出框指示修改就可以了。

1.3.6、安装Prettier
npm add -D prettier
1.3.7、安装Prettier兼容eslint的插件
npm add -D eslint-config-prettier
1.3.8、安装eslint的prettier
npm add -D eslint-plugin-prettier
1.3.9、在根目录下创建.eslintrc.js文件
/** .eslintrc.js
 * 在VSCode中安装ESLint插件,编写过程中检测代码质量
 * ESLint 代码质量校验相关配置
 * 这里使用prettier作为代码格式化工具,用ESLint做代码质检
 * 相关配置使用下面extends扩展先做默认设置
 * 在.prettierrc.js文件中配置好后,格式化规则会以.prettierrc.js作为最终格式,所以不建议在本文件中做代码格式化相关配置
 * 相关prettier配置ESLint会默认加载为代码质检 格式化以prettier为主
 * 在本配置文件中只做代码质量约束规范配置
 */
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  extends: [
    'eslint-config-prettier',
    'eslint:recommended', // 使用推荐的eslint
    'plugin:@typescript-eslint/recommended',
    'plugin:vue/vue3-recommended', // 使用插件支持vue3
    'plugin:vue/vue3-essential',
    //1.继承.prettierrc.js文件规则  2.开启rules的 "prettier/prettier": "error"  3.eslint fix的同时执行prettier格式化
    'plugin:prettier/recommended',
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    parser: '@typescript-eslint/parser',
  },
  plugins: [],
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly',
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? ['error', { allow: ['error', 'warn'] }] : 'off', //生产模式不允许使用log
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', //生产默认不允许使用debugger
    '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }], //变量声明未使用
    '@typescript-eslint/no-explicit-any': 'off', // 允许ts使用any
    // '@typescript-eslint/no-var-requires': 'off', // 强制使用 import 且不允许使用 require 设置off关闭检查
    'vue/require-v-for-key': 'error', // 对保留元素检查 vue3中v-for会自动追加key值,所以不用再强制添加key属性,所以不检查key的填写
    'vue/valid-v-for': 'error', // 对于非保留(自定义)元素检查  vue3中v-for会自动追加key值,所以不用再强制添加key属性,所以不检查key的填写
    // // 添加组件命名忽略规则 vue官方默认规则是多单词驼峰来进行组件命名
    'vue/multi-word-component-names': 'off',
  },
};
1.3.10、在根目录下创建.prettierrc.js文件

如果不想格式化某些文件可以再添加一个.prettierignore的文件,用法和.gitignore文件差不多,将不需要格式化的文件夹或文件通过正则匹配或者具名的方式添加进去,这样就不会格式化对应的文件了。

module.exports = {
    // 一行最多多少个字符
    printWidth: 200,
    // 指定每个缩进级别的空格数
    tabWidth: 2,
    // 使用制表符而不是空格缩进行
    useTabs: false,
    // 在语句末尾是否需要分号
    semi: true,
    // 是否使用单引号
    singleQuote: true,
    // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
    quoteProps: 'as-needed',
    // 在JSX中使用单引号而不是双引号
    jsxSingleQuote: false,
    // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
    trailingComma: 'es5',
    // 在对象文字中的括号之间打印空格
    bracketSpacing: true,
    // jsx 标签的反尖括号需要换行
    jsxBracketSameLine: false,
    // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
    arrowParens: 'always',
    // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
    rangeStart: 0,
    rangeEnd: Infinity,
    // 指定要使用的解析器,不需要写文件开头的 @prettier
    requirePragma: false,
    // 不需要自动在文件开头插入 @prettier
    insertPragma: false,
    // 使用默认的折行标准 always\never\preserve
    proseWrap: 'preserve',
    // 指定HTML文件的全局空格敏感度 css\strict\ignore
    htmlWhitespaceSensitivity: 'css',
    // Vue文件脚本和样式标签缩进
    vueIndentScriptAndStyle: false,
    //在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
    //然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
    //对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
    // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
    endOfLine: 'auto',
};
1.3.11、配置vscode保存自动格式化文件

在VSCode中搜索Prettier安装Prettier - Code formatter扩展
打开vscod→文件→首选项→设置,指定文件名,即可根据文件的配置去做代码校验
在这里插入图片描述
打开vscod→文件→首选项→设置,搜索default formatter选择Prettier为首选项
在这里插入图片描述

1.3.12、选择使用的格式化文档

右键点击(使用…格式化文档),选择Prettier - code - formatter (默认值)

1.3.13、选择格式化操作选项

需要重启vscode才能生效
在这里插入图片描述

1.4、集成sass

1.4.1、安装sass
npm install -D sass sass-loader
1.4.2、在src/assets/styles目录下创建reset.scss文件
*:after,
*:before {
  box-sizing: border-box;
  outline: none;
}
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
  font: inherit;
  font-size: 16px;
  margin: 0;
  padding: 0;
  vertical-align: baseline;
  border: 0;
}

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}

body {
  line-height: 1;
}

ol,
ul {
  list-style: none;
}

blockquote,
q {
  quotes: none;
  &:before,
  &:after {
    content: '';
    content: none;
  }
}

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
sup {
  top: -.5em;
}
sub {
  bottom: -.25em;
}

table {
  border-spacing: 0;
  border-collapse: collapse;
}

input,
textarea,
button {
  font-family: inhert;
  font-size: inherit;
  color: inherit;
}

select {
  text-indent: .01px;
  text-overflow: '';
  border: 0;
  border-radius: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
}

select::-ms-expand {
  display: none;
}

code,
pre {
  font-family: monospace, monospace;
  font-size: 1em;
}
1.4.3、在src/assets/styles目录下创建variables.scss文件
// base color
$blue: #324157;
$red: #C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow: #FEC171;
$panGreen: #30B08F;
1.4.4、在src/assets/styles目录下创建mixin.scss文件
@mixin clearfix {
  &:after {
    content: "";
    display: table;
    clear: both;
  }
}

@mixin scrollBar {
  &::-webkit-scrollbar-track-piece {
    background: #d3dce6;
  }

  &::-webkit-scrollbar {
    width: 6px;
  }

  &::-webkit-scrollbar-thumb {
    background: #99a9bf;
    border-radius: 20px;
  }
}

@mixin relative {
  position: relative;
  width: 100%;
  height: 100%;
}

@mixin triangle($width, $height, $color, $direction) {
  $width: $width/2;
  $color-border-style: $height solid $color;
  $transparent-border-style: $width solid transparent;
  height: 0;
  width: 0;

  @if $direction==up {
    border-bottom: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  }

  @else if $direction==right {
    border-left: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
  }

  @else if $direction==down {
    border-top: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  }

  @else if $direction==left {
    border-right: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
  }
}
1.4.5、在src/assets/styles目录下创建index.scss文件
@import "./reset.scss";
@import "./variables.scss";
@import "./mixin.scss";
1.4.6、配置全局样式

在src/main.ts文件中新增

import './assets/styles/index.scss';
1.4.7、文件中的使用
<style lang="scss">
.box {
	color: $red;
	@include box();
}
</style>

1.5、集成element-plus

1.5.1、安装element-plus
npm install element-plus --save
1.5.2、安装element-plus icon
npm install @element-plus/icons-vue
1.5.3、Volar 支持

在根目录tsconfig.json中新增

{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}
1.5.4、按需引入element-plus组件

首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

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

在根目录vite.config.ts文件中新增

import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})
1.5.5、国际化配置

在src/App.vue文件中新增

<template>
  <el-config-provider :locale="locale">
    <App/>
  </el-config-provider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import zhCn from 'element-plus/es/locale/lang/zh-cn';

const locale = ref(zhCn);
</script>
1.5.5、按需加载自定义主题样式

在src/assets/styles目录下新建element.scss

@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': red,
    ),
  ),
);

在src/assets/styles/index.scss文件中添加

@import "./element.scss";

在根目录vite.config.ts文件中添加

export default defineConfig({
  plugins: [
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
      ],
    }),
  ],
})

1.6、集成pinia全局状态管理

1.6.1、安装pinia
npm install pinia
1.6.2、全局引入pinia

在src目录main.ts新增

import { createPinia } from 'pinia';

// 实例化 Pinia
const pinia = createPinia();
app.use(pinia);
1.6.3、创建store文件

在src/store目录下创建user.ts

import { defineStore } from 'pinia';
import { computed, reactive, ref } from 'vue';

export interface Userinfo {
  id: string;
  username: string;
  email: string;
  phone: string;
  address: string;
  birthDate?: number;
  lastLoginTime?: number;
  role: number;
}

export const useUserStore = defineStore('user', () => {
  //state
  const userinfo = reactive<Userinfo>({
    id: '',
    username: '',
    email: '',
    phone: '',
    address: '',
    birthDate: 0,
    lastLoginTime: 0,
    role: 0,
  });
  const isLogined = ref<boolean>(false);

  //getters
  const getUserinfo = computed(() => userinfo);

  //action
  const login = (): void => {
    console.log('login');
  };
  const logout = (): void => {
    console.log('logout');
  };

  return { userinfo, isLogined, getUserinfo, login, logout };
});
1.6.4、页面中使用

相关用法和hooks一致

<template>
<div>{{ getUserinfo.username }}</div>
</template>

<script setup lang="ts">
import { useUserStore } from '@/store/user';

const { getUserinfo } = useUserStore();
</script>

1.7、集成页面路由

1.7.1、安装vue-router
npm install vue-router @types/vue-router
1.7.2、使用路由

在src/main.ts文件中新增

import router from './router/index';

app.use(router);

在src/App.vue文件中修改

<template>
  //..
    <router-view></router-view>
  //..
</template>
1.7.4、配置路由

在src/router目录下新建index.ts文件

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';

const routes: RouteRecordRaw[] = [
  {
    path: '/redirect',
    component: Layout,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index.vue'),
      },
    ],
  },
  {
    path: '/login',
    component: () => import('@/views/login.vue'),
  },
  {
    path: '/register',
    component: () => import('@/views/register.vue'),
  },
  {
    path: '/:pathMatch(.*)*',
    component: () => import('@/views/error/404.vue'),
  },
  {
    path: '/401',
    component: () => import('@/views/error/401.vue'),
  },
  {
    path: '',
    component: Layout,
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home.vue'),
        name: 'Home',
        meta: { title: '首页', icon: 'dashboard', affix: true },
      },
    ],
  },
  {
    path: '/user',
    component: Layout,
    redirect: 'noredirect',
    children: [],
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes: routes,
});

export default router;

1.7.5、创建相关路由页面

在src/layout目录下新增index.vue

<template>
  <div class="layout">
    <div>layout</div>
  </div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
.layout {
  width: 10rem;
  height: 10px;
  background: red;
}
</style>

在src/views/redirect目录新增index.vue

<template>redirect</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

在src/views目录新增login.vue

<template>login</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

在src/views目录新增register.vue

<template>register</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

在src/views/error目录新增401.vue

<template>401</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

在src/views/error目录新增404.vue

<template>401</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

在src/views目录新增home.vue

<template>home</template>

<script setup lang="ts"></script>

<style scoped lang="scss"></style>

1.8、集成操作Cookie工具

1.8.1、安装 js-cookie
npm install js-cookie
1.8.2、创建类型声明文件

在根目录中创建js-cookie.d.ts文件

declare module 'js-cookie' {
  export function set(name: string, value: any, options?: any): void;
  export function get(name: string): string | undefined;
  export function remove(name: string, options?: any): void;
  export function getJSON(name: string): any;
}

1.9、集成HTTP请求工具

1.9.1、安装axios
npm install axios
1.9.2、配置axios

借鉴若依框架的请求
在src/plugins目录下创建cache.ts文件

const sessionCache = {
  set(key: string, value: string) {
    if (!sessionStorage) {
      return;
    }
    if (key != null && value != null) {
      sessionStorage.setItem(key, value);
    }
  },
  get(key: string) {
    if (!sessionStorage) {
      return null;
    }
    if (key == null) {
      return null;
    }
    return sessionStorage.getItem(key);
  },
  setJSON(key: string, jsonValue: any) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue));
    }
  },
  getJSON(key: string) {
    const value = this.get(key);
    if (value != null) {
      return JSON.parse(value);
    }
  },
  remove(key: string) {
    sessionStorage.removeItem(key);
  },
};
const localCache = {
  set(key: string, value: string) {
    if (!localStorage) {
      return;
    }
    if (key != null && value != null) {
      localStorage.setItem(key, value);
    }
  },
  get(key: string) {
    if (!localStorage) {
      return null;
    }
    if (key == null) {
      return null;
    }
    return localStorage.getItem(key);
  },
  setJSON(key: string, jsonValue: any) {
    if (jsonValue != null) {
      this.set(key, JSON.stringify(jsonValue));
    }
  },
  getJSON(key: string) {
    const value = this.get(key);
    if (value != null) {
      return JSON.parse(value);
    }
  },
  remove(key: string) {
    localStorage.removeItem(key);
  },
};

export default {
  /**
   * 会话级缓存
   */
  session: sessionCache,
  /**
   * 本地缓存
   */
  local: localCache,
};

在src/utils目录下创建auth.ts文件

import Cookies from 'js-cookie';

const TokenKey: string = 'Admin-Token';

export function getToken() {
  return Cookies.get(TokenKey);
}

export function setToken(token) {
  return Cookies.set(TokenKey, token);
}

export function removeToken() {
  return Cookies.remove(TokenKey);
}

在src/utils目录下创建index.ts文件

/**
 * 参数处理
 * @param {*} params  参数
 */
export function tansParams(params) {
  let result = '';
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    const part = encodeURIComponent(propName) + '=';
    if (value !== null && value !== '' && typeof value !== 'undefined') {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
            const params = propName + '[' + key + ']';
            const subPart = encodeURIComponent(params) + '=';
            result += subPart + encodeURIComponent(value[key]) + '&';
          }
        }
      } else {
        result += part + encodeURIComponent(value) + '&';
      }
    }
  }
  return result;
}

// 验证是否为blob格式
export function blobValidate(data) {
  return data.type !== 'application/json';
}

在src/plugins目录下创建errorCode.ts文件

export default {
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  default: '系统未知错误,请反馈给管理员',
};

安装file-saver

npm install file-saver

在src/utils目录下创建request.ts文件

import axios, { AxiosInstance } from 'axios';
import { App } from 'vue';
import { getToken } from '@/utils/auth';
import { tansParams, blobValidate } from '@/utils/index';
import cache from '@/plugins/cache';
import errorCode from '@/plugins/errorCode';
import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus';
import { useUserStore } from '@/store/user';
import { saveAs } from 'file-saver';

let downloadLoadingInstance;
// 是否显示重新登录
export const isRelogin = { show: false };

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
// 对应国际化资源文件后缀
axios.defaults.headers['Content-Language'] = 'zh-cn';

// 创建一个 Axios 实例
const axiosInstance: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 替换成你的 API 地址
  timeout: 10000, // 设置请求超时时间
});

// request拦截器
axiosInstance.interceptors.request.use(
  (config) => {
    // 是否需要设置 token
    const isToken = (config.headers || {}).isToken === false;
    // 是否需要防止数据重复提交
    const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
    if (getToken() && !isToken) {
      config.headers['Authorization'] = 'Bearer ' + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    // get请求映射params参数
    if (config.method === 'get' && config.params) {
      let url = config.url + '?' + tansParams(config.params);
      url = url.slice(0, -1);
      config.params = {};
      config.url = url;
    }
    if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
      const requestObj = {
        url: config.url,
        data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
        time: new Date().getTime(),
      };
      const sessionObj = cache.session.getJSON('sessionObj');
      if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
        cache.session.setJSON('sessionObj', requestObj);
      } else {
        const s_url = sessionObj.url; // 请求地址
        const s_data = sessionObj.data; // 请求数据
        const s_time = sessionObj.time; // 请求时间
        const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
        if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
          const message = '数据正在处理,请勿重复提交';
          console.warn(`[${s_url}]: ` + message);
          return Promise.reject(new Error(message));
        } else {
          cache.session.setJSON('sessionObj', requestObj);
        }
      }
    }
    return config;
  },
  (error) => {
    console.log(error);
    Promise.reject(error);
  }
);

// 响应拦截器
axiosInstance.interceptors.response.use(
  (res) => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default'];
    // 二进制数据则直接返回
    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
      return res.data;
    }
    if (code === 401) {
      if (!isRelogin.show) {
        isRelogin.show = true;
        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' })
          .then(() => {
            isRelogin.show = false;
            useUserStore()
              .logout()
              .then(() => {
                location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'home';
              });
          })
          .catch(() => {
            isRelogin.show = false;
          });
      }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
    } else if (code === 500) {
      ElMessage({ message: msg, type: 'error' });
      return Promise.reject(new Error(msg));
    } else if (code === 601) {
      ElMessage({ message: msg, type: 'warning' });
      return Promise.reject(new Error(msg));
    } else if (code !== 200) {
      ElNotification.error({ title: msg });
      return Promise.reject('error');
    } else {
      return Promise.resolve(res.data);
    }
  },
  (error) => {
    console.log('err' + error);
    let { message } = error;
    if (message == 'Network Error') {
      message = '后端接口连接异常';
    } else if (message.includes('timeout')) {
      message = '系统接口请求超时';
    } else if (message.includes('Request failed with status code')) {
      message = '系统接口' + message.substr(message.length - 3) + '异常';
    }
    ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
    return Promise.reject(error);
  }
);

// 通用下载方法
export function download(url, params, filename, config) {
  downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
  return axiosInstance
    .post(url, params, {
      transformRequest: [
        (params) => {
          return tansParams(params);
        },
      ],
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      responseType: 'blob',
      ...config,
    })
    .then(async (response) => {
      const isBlob = blobValidate(response.data);
      if (isBlob) {
        const blob = new Blob([response.data]);
        saveAs(blob, filename);
      } else {
        const resText = await response.data.text();
        const rspObj = JSON.parse(resText);
        const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
        ElMessage.error(errMsg);
      }
      downloadLoadingInstance.close();
    })
    .catch((r) => {
      console.error(r);
      ElMessage.error('下载文件出现错误,请联系管理员!');
      downloadLoadingInstance.close();
    });
}

// 将 Axios 实例添加到 Vue 实例的原型链中
export const setupAxios = (app: App<Element>) => {
  app.config.globalProperties.$axios = axiosInstance;
};

export default axiosInstance;

1.9.3、通过代理方式处理前端跨域问题

编辑根目录vite.config.ts文件

export default defineConfig({
	//...
	server: {
		//...
		proxy: {
	      '/dev-api': {
	        target: 'http://127.0.0.1:8080',
	        changeOrigin: true,
	        rewrite: (p) => p.replace(/^\/dev-api/, ''),
	      },
	      '/prod-api': {
	        target: 'http://127.0.0.1:8080',
	        changeOrigin: true,
	        rewrite: (p) => p.replace(/^\/prod-api/, ''),
	      },
	    },
	}
})
1.9.4、创建api

在src/api目录下创建login.ts

import request from '@/utils/request';

// 登录方法
export function login(username: string, password: string, code: string) {
  const data = {
    username,
    password,
    code,
  };
  return request({
    url: '/login',
    headers: {
      isToken: false,
    },
    method: 'post',
    data: data,
  });
}

// 获取用户详细信息
export function getInfo() {
  return request({
    url: '/getInfo',
    method: 'get',
  });
}
1.9.5、在页面中使用
<script lang="ts" setup>
import { onMounted } from 'vue';
import { getInfo } from '@/api/login';

onMounted(() => {
  getInfo().then((response) => {
    console.log(response);
  });
});
</script>

1.10、集成图表展示工具

1.10.1、安装ECharts
npm install echarts --save
1.10.2、配置按需引用

在src/utils目录下创建echarts.ts文件

// 引入 echarts 核心模块。
import * as echarts from 'echarts/core';
//引入柱状图和折线图组件。
import { BarChart, LineChart } from 'echarts/charts';
// 引入标题、提示框、网格、数据集和数据转换器组件。
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  // 数据集组件
  DatasetComponent,
  // 内置数据转换器组件 (filter, sort)
  TransformComponent,
} from 'echarts/components';
//引入标签布局和通用过渡动画特性。
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器。
import { CanvasRenderer } from 'echarts/renderers';

import type {
  // 系列类型的定义后缀都为 SeriesOption
  BarSeriesOption,
  LineSeriesOption,
} from 'echarts/charts';

import type {
  // 组件类型的定义后缀都为 ComponentOption
  TitleComponentOption,
  TooltipComponentOption,
  GridComponentOption,
  DatasetComponentOption,
} from 'echarts/components';
import type { ComposeOption } from 'echarts/core';

// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type EChartsOption = ComposeOption<BarSeriesOption | LineSeriesOption | TitleComponentOption | TooltipComponentOption | GridComponentOption | DatasetComponentOption>;

/** 
    注册必须的组件,包括标题、提示框、网格、数据集、数据转换器,
    以及柱状图、折线图、标签布局、通用过渡动画和 Canvas 渲染器。
*/
echarts.use([TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, BarChart, LineChart, LabelLayout, UniversalTransition, CanvasRenderer]);
// 导出
export default echarts;
1.10.3、页面中使用echarts
<template>
  <div ref="chart" style="width: 600px; height: 400px"></div>
</template>

<script lang="ts" setup>
import type { EChartsOption } from '@/utils/echarts';
import echarts from '@/utils/echarts';
import { onMounted, getCurrentInstance } from 'vue';

var option: EChartsOption;

option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line',
    },
  ],
};

onMounted(() => {
  const proxy = getCurrentInstance();
  var chartDom = proxy?.refs.chart;
  var myChart = echarts.init(<HTMLElement>chartDom);
  option && myChart.setOption(option);
});
</script>

1.11、配置大屏自适应

1.11.1、使用插件

在public目录下创建flexible.ts

(function (win, lib) {
  const doc = win.document;
  const docEl = doc.documentElement;
  let metaEl = doc.querySelector('meta[name="viewport"]');
  const flexibleEl = doc.querySelector('meta[name="flexible"]');
  let dpr = 0;
  let scale = 0;
  let tid;
  const flexible = lib.flexible || (lib.flexible = {});

  if (metaEl) {
    console.warn('将根据已有的meta标签来设置缩放比例');
    const match = metaEl.getAttribute('content')?.match(/initial-scale=([d.]+)/);
    if (match) {
      scale = parseFloat(match[1]);
      dpr = parseInt((1 / scale).toString());
    }
  } else if (flexibleEl) {
    const content = flexibleEl.getAttribute('content');
    if (content) {
      const initialDpr = content.match(/initial-dpr=([d.]+)/);
      const maximumDpr = content.match(/maximum-dpr=([d.]+)/);
      if (initialDpr) {
        dpr = parseFloat(initialDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));
      }
      if (maximumDpr) {
        dpr = parseFloat(maximumDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));
      }
    }
  }

  if (!dpr && !scale) {
    // var isAndroid = win.navigator.appVersion.match(/android/gi);
    const isIPhone = win.navigator.appVersion.match(/iphone/gi);
    const devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
      // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
      if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
        dpr = 3;
      } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
        dpr = 2;
      } else {
        dpr = 1;
      }
    } else {
      // 其他设备下,仍旧使用1倍的方案
      dpr = 1;
    }
    scale = 1 / dpr;
  }

  docEl.setAttribute('data-dpr', dpr.toString());
  if (!metaEl) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
    if (docEl.firstElementChild) {
      docEl.firstElementChild.appendChild(metaEl);
    } else {
      const wrap = doc.createElement('div');
      wrap.appendChild(metaEl);
      doc.write(wrap.innerHTML);
    }
  }

  function refreshRem() {
    let width = docEl.getBoundingClientRect().width;
    if (width / dpr > 1920) {
      // 这个位置划重点 1920是设计稿的大小 如果你的设计稿是750 那么就需要将1920替换成750
      width = (docEl.clientWidth / 1920) * 1920;
    }
    const rem = width / 10;
    docEl.style.fontSize = rem + 'px';
    flexible.rem = win.rem = rem;
  }

  win.addEventListener(
    'resize',
    function () {
      clearTimeout(tid);
      tid = setTimeout(refreshRem, 300);
    },
    false
  );
  win.addEventListener(
    'pageshow',
    function (e) {
      if (e.persisted) {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
      }
    },
    false
  );

  if (doc.readyState === 'complete') {
    doc.body.style.fontSize = 12 * dpr + 'px';
  } else {
    doc.addEventListener(
      'DOMContentLoaded',
      function () {
        doc.body.style.fontSize = 12 * dpr + 'px';
      },
      false
    );
  }

  refreshRem();

  flexible.dpr = win.dpr = dpr;
  flexible.refreshRem = refreshRem;
  flexible.rem2px = function (d) {
    let val = parseFloat(d) * this.rem;
    if (typeof d === 'string' && d.match(/rem$/)) {
      val += 'px';
    }
    return val;
  };
  flexible.px2rem = function (d) {
    let val = parseFloat(d) / this.rem;
    if (typeof d === 'string' && d.match(/px$/)) {
      val += 'rem';
    }
    return val;
  };
})(window, window['lib'] || (window['lib'] = {}));
1.11.2、安装cssrem扩展

在vscode商店中搜索cssrem扩展并安装
在这里插入图片描述
配置自动转换(右键扩展→扩展设置),如设计稿为1920 * 1080,这该数值为 1920 / 10 = 192
在这里插入图片描述

1.11.3、使用cssrem扩展

编辑根目录下index.html文件

<html lang="en">
  <body>
    //...
    <script type="module" src="/public/flexible.ts"></script>
  </body>
</html>

1.12、集成拖拽插件

1.12.1、安装Pragmatic drag and drop
npm i @atlaskit/pragmatic-drag-and-drop-react-drop-indicator
1.12.2、页面中使用

链接: Pragmatic drag and drop官网

import {draggable} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';


2、技术选型

2.1、为何选择vite

2.1.1、快速的开发启动速度

Vite 以其快速的开发启动速度而闻名。它利用了现代浏览器的原生 ES 模块支持,将开发服务器和构建工具的性能优化到极致,因此在启动开发服务器和进行热模块更新时表现出色。

2.1.2、基于 ES 模块的开发

Vite 使用原生 ES 模块来开发,这意味着你可以直接在浏览器中运行你的代码,而无需将其转换为 CommonJS 或其他模块格式。这简化了开发过程,同时也提高了开发效率。

2.1.3、支持现代的 JavaScript 和 CSS 特性

Vite 内置了对最新 JavaScript 和 CSS 特性的支持,包括模块化、动态导入、CSS 变量等。这使得开发者能够更轻松地使用现代的前端技术来构建项目。

2.1.4、插件系统

Vite 提供了丰富的插件系统,允许开发者通过插件来扩展和定制构建过程。这使得 Vite 可以与各种工具和框架集成,满足不同项目的需求。

2.1.5、Vue.js 生态系统的良好支持

Vite 由 Vue.js 的作者开发,因此与 Vue.js 框架集成得非常好。Vue 3 默认支持 Vite,而且 Vite 也提供了一些针对 Vue.js 的优化。

2.1.6、适用于小型项目和原型开发

由于其快速的启动速度和简洁的配置,Vite 特别适用于小型项目和原型开发,可以帮助开发者更快地搭建起项目框架并进行迭代。

2.2、为何选择vue3

2.2.1、响应式性能提升

性能比Vue2块1.5-2倍。性能的提升主要是通过响应式Q系统的提升(vue3使用proxy对象重写响应式)以及编译优化(优化编译和重写虚拟dom、优化diff算法)来完成。

2.2.2、代码体积更小

相比Vue2,Vue3按需编译,整体体积变小了。除了移出一些不常用的API,值得一提的是Tree shanking任何一个函数,如ref、reactive、computed等,仅仅在用到的时候才打包,没用到的模块都被去掉,打包的整体体积变小。

2.2.3、支持组合API(Composition Api)

Vue2使用Options Api(选项api),而Vue3使用Composition Api (组合api)和setup语法糖,向下兼容vue2的Options Api(选项api)。

2.2.4、更好的 ts 支持

Vue 新增了 DefineComponent 函数,使组件在 ts 下,更好的利用参数类型推断。如:reactive 和 ref 很具有代表性。

2.2.5、更先进的组件

①vue 中可以不需要根节点,多个元素或标签可并列存在;②可以把 teleport 中的内容添加到任意的节点内;③允许程序在等待异步组件渲染一些后备的内容,可以让我们创建一个平滑的用户体验。

2.3、为何选择TypeScript

2.3.1、静态类型检查

TypeScript 提供了静态类型检查功能,可以在编译时捕获潜在的错误和类型不匹配问题,有助于提高代码质量和可维护性。通过类型检查,可以在开发过程中更早地发现错误,并减少在运行时出现的问题。

2.3.2、增强的编辑器支持

TypeScript 提供了丰富的类型信息,这使得编辑器能够提供更强大的代码提示、自动完成和重构功能。特别是在使用支持 TypeScript 的编辑器(如 Visual Studio Code)时,能够获得最佳的开发体验。

2.3.3、更好的文档和可读性

通过在代码中添加类型注解,可以提供更清晰和可读的文档。类型信息可以帮助开发人员理解代码的意图,并使代码更易于维护和理解。

2.3.4、更好的团队协作

类型信息可以提供更明确的接口定义,有助于不同团队成员之间更好地协作。通过类型定义,可以清楚地了解函数和组件的输入输出,从而减少沟通成本和误解。

2.3.5、渐进式采用

TypeScript 是 JavaScript 的超集,这意味着你可以逐步将现有的 JavaScript 代码转换为 TypeScript,而不需要一次性进行全面的重构。这使得在现有项目中引入 TypeScript 变得更加容易和灵活。

2.3.6、生态系统支持

TypeScript 在社区和生态系统方面拥有强大的支持,有大量的第三方库和工具可以与之兼容。同时,许多流行的框架和库(如 Vue、React、Angular 等)都提供了对 TypeScript 的良好支持。

2.4、为何选择pinia

2.4.1、简洁而强大的状态管理

Pinia 是一个基于 Vue 3 的状态管理库,它提供了简洁而强大的 API,使得状态管理变得更加直观和容易。Pinia 的设计理念是将状态管理的复杂性降到最低,同时保持灵活性和可扩展性。

2.4.2、类型安全

Pinia 充分利用了 TypeScript 的类型系统,提供了类型安全的状态管理解决方案。通过使用 TypeScript,可以在编译时捕获潜在的错误和类型不匹配问题,从而提高代码质量和可维护性。

2.4.3、响应式和异步支持

Pinia 内置了对 Vue 3 的响应式系统的支持,可以轻松地管理和响应状态的变化。同时,Pinia 还提供了对异步操作的支持,使得在处理异步逻辑时更加方便和简洁。

2.4.4、轻量级和可扩展性

Pinia 的代码库非常轻量,没有过多的依赖和复杂的概念,这使得它非常适合于构建中小型应用。同时,Pinia 也提供了丰富的插件系统和扩展点,可以根据需要进行定制和扩展。

2.4.5、与 Vue 生态系统的集成

作为 Vue 3 的状态管理库,Pinia 与 Vue 生态系统无缝集成,可以与 Vue Router、Vue DevTools 等其他 Vue 相关工具和库配合使用,为开发提供更加便利的体验。

2.4.6、活跃的社区和文档支持

Pinia 拥有一个活跃的社区和完善的文档支持,你可以在社区中获取到丰富的资源和解决方案,并且能够获得及时的技术支持和反馈。

2.5、为何集成eslint和babel

2.5.1、代码质量保障

Eslint 是一个代码质量检查工具,它可以帮助团队确保代码的一致性、可读性和可维护性。通过配置 Eslint 规则,可以捕获代码中的潜在问题和错误,从而提高代码的质量和稳定性。

2.5.2、规范化团队开发

Eslint 提供了丰富的规则和配置选项,可以根据团队的开发风格和偏好进行定制。通过统一的代码规范,可以提高团队成员之间的协作效率,并降低代码维护的成本。

2.5.3、提高开发效率

Eslint 可以集成到代码编辑器中,提供实时的代码检查和提示,帮助开发人员及时发现和解决问题。这可以减少代码审查和调试的时间,提高开发效率。

2.5.4、支持现代 JavaScript 特性

Babel 是一个 JavaScript 编译器,可以将新版本的 JavaScript 代码转换为向后兼容的代码,从而确保代码在不同环境中的兼容性。通过使用 Babel,可以使用最新的 JavaScript 特性,而无需担心兼容性问题。

2.5.5、生态系统支持

Eslint 和 Babel 都拥有庞大的生态系统,有大量的插件和扩展可以满足不同项目的需求。无论是处理特定的代码风格还是支持特定的 JavaScript 特性,都可以在生态系统中找到相应的解决方案。

2.5.6、持续更新和改进

Eslint 和 Babel 都是活跃的项目,持续地更新和改进。他们及时跟进最新的 JavaScript 标准和最佳实践,保持与时俱进,为开发人员提供更好的工具和体验。

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小智不爱学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值