个人 vite 项目整理

863 阅读4分钟

小白笔记整理 整理中非常乱......

vite CDN 加速

  • vite-plugin-cdn-import

vite.CDN.config.ts

推荐使用 饿了么的CDN加速 //npm.elemecdn.com

import type { Options } from 'vite-plugin-cdn-import';
​
export const viteCDNConfig = (): Options => {
  return {
    modules: [
      {
        name: 'vue',
        var: 'Vue',
        // path: '//cdn.jsdelivr.net/npm/vue@3.2.39/dist/vue.global.prod.js',
        path: '//npm.elemecdn.com/vue@3.2.39/dist/vue.global.prod.js',
      },
      // 一些基于 vue-demi 开发的框架依赖于这个包
      {
        name: 'vue-demi',
        var: 'VueDemi',
        // path: '//cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/index.iife.js',
        path: '//npm.elemecdn.com/vue-demi@0.13.11/lib/index.iife.js',
      },
      {
        name: 'vue-router',
        var: 'VueRouter',
        // path: '//cdn.jsdelivr.net/npm/vue-router@4.1.5/dist/vue-router.global.prod.js',
        path: '//npm.elemecdn.com/vue-router@4.1.5/dist/vue-router.global.prod.js',
      },
      {
        name: 'axios',
        var: 'axios',
        // path: '//cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js',
        path: '//npm.elemecdn.com/axios@0.27.2/dist/axios.min.js',
      },
      // pinia 20 kb
      {
        name: 'pinia',
        var: 'Pinia',
        path: '//npm.elemecdn.com/pinia@2.0.22/dist/pinia.iife.prod.js',
      },
      {
        name: 'echarts',
        var: 'echarts',
        // path: '//unpkg.zhimg.com/echarts@5.3.3/dist/echarts.min.js',
        // path: '//cdn.jsdelivr.net/npm/echarts@5.3.3/dist/echarts.simple.js',
        // path: '//cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.min.js',
        path: '//npm.elemecdn.com/echarts@5.3.3/dist/echarts.min.js',
      },
      {
        name: 'vue-request',
        var: 'VueRequest',
        // path: '//cdn.jsdelivr.net/npm/vue-request@1.2.4/dist/vue-request.min.js',
        path: '//npm.elemecdn.com/vue-request@1.2.4',
      },
      {
        name: '@arco-design/web-vue',
        var: 'ArcoVue',
        // path: '//cdn.jsdelivr.net/npm/@arco-design/web-vue@2.36.1/dist/arco-vue.min.js',
        path: '//npm.elemecdn.com/@arco-design/web-vue@2.36.1/dist/arco-vue.min.js',
        css: '//npm.elemecdn.com/@arco-design/web-vue@2.36.1/dist/arco.min.css',
      },
      /*
       接近 800kb直接放弃
      {
        name: 'vue-echarts',
        var: 'vue-echarts',
        path: '//cdn.jsdelivr.net/npm/vue-echarts@6.2.3/dist/index.umd.min.js',
      },*/
    ],
  };
};

vite.config.ts

  • unplugin-vue-components/vite cdn 打包有冲突,不知道该怎么解决
  • AutoImport.imports 如果指定了 vue 会报找不到 vue异常
  • AutoImport.dirs 指定的包,需要重新启动 vite 才会 生成在 auto-import.d.ts 中......
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
// import { ArcoResolver } from 'unplugin-vue-components/resolvers';
// import Components from 'unplugin-vue-components/vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { Plugin as importToCDN } from 'vite-plugin-cdn-import';
import { viteMockServe } from 'vite-plugin-mock';
import Pages from 'vite-plugin-pages';
import Layouts from 'vite-plugin-vue-layouts';
import { viteCDNConfig } from './vite.CDN.config';
​
export const vitePluginsConfig = (command: string): Plugin[] => {
  return [
    vue(),
    AutoImport({
      include: [
        /.[tj]sx?$/, // .ts, .tsx, .js, .jsx
        /.vue$/,
        /.vue?vue/, // .vue
      ],
      resolvers: [],
      dts: './src/types/auto/imports.d.ts',
      imports: ['@vueuse/core', 'vue-router', 'pinia'], // 这里没指定 vue
      dirs: ['./src/hooks', './src/common/utils'],
    }),
    /*Components({
      resolvers: [
        ArcoResolver({
          sideEffect: true,
          importStyle: 'css',
        }),
      ],
      directoryAsNamespace: true,
      dts: './src/types/auto/components.d.ts',
    }),*/
    visualizer({
      open: true,
    }),
    importToCDN(viteCDNConfig()),
    viteMockServe({
      mockPath: './src/server/mock',
      localEnabled: command === 'serve',
      supportTs: true,
    }),
    Pages({
      dirs: [
        {
          dir: './src/pages',
          baseRoute: '/',
        },
      ],
      exclude: ['**/components/*.vue'],
      importMode: 'async',
      extensions: ['vue'],
    }),
    Layouts({
      layoutsDirs: './src/common/layouts',
      defaultLayout: 'default',
    }),
  ];
};

ts 警告处理

tsconfig.node.json 添加 include

{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "vite.config.ts",
    "vite.plugins.config.ts",
    "vite.resolve.config.ts",
    "vite.build.config.ts",
    "vite.CDN.config.ts"
  ]
}

集成 unocss

在项目中你能够编写 windicss 语法

能够使用 windicss 语法

<template>
  <h1 class="text-blue-500 text-[2rem]">Login 登陆页面</h1>
</template>

最终生成 css

.text-blue-500 {
  --un-text-opacity: 1;
  color: rgba(59, 130, 246, var(--un-text-opacity));
}
​
.text-[2rem] {
  font-size: 2rem;
}

或者通过 @apply 指定

注意 依赖 @unocss/transformer-directives

<style scoped>
  .br_title {
    @apply text-blue-500 text-[2rem];
  }
</style>

集成 Icon

在项目中你能够轻松使用 iconify 网站的所有 icon 图标 以及 本地指定目录的 icon 图标

1. iconify


image-20220912224423919-16630037770731.png 你可以访问该网站 icones.js.org 复制任意需要的图标名, 到你的项目中

image-20220912225808600.png 注意你得加上 icon- , 如需要自定义前缀 前往 unocss.config.ts 配置

vue

<template>
  <div class="icon-fluent-alert-20-filled" />
</template>

2. 本地 Icon


  • ./src/assets/banner/

  • ./src/assets/icon/

    目录也能通过 class 获取 svg, 你只需要加上

    icon-br-icon-[svg文件名]icon-br-banner-[svg文件名] 就能获取 svg

vue

<template>
  <div class="icon-br-icon-manager" />
​
  <!-- 配置了 mask, 彩图需要加 ?bg 后缀   -->
  <icon icon="br-banner-not-fount?bg" />
</template>

[小案例]

img

<template>
    <!-- A basic anchor icon from Phosphor icons -->
    <div class="i-ph-anchor-simple-thin" />
    <!-- An orange alarm from Material Design Icons -->
    <div class="i-mdi-alarm text-orange-400" />
    <!-- A large Vue logo -->
    <div class="i-logos-vue text-3xl" />
    <!-- Sun in light mode, Moon in dark mode, from Carbon -->
    <button class="i-carbon-sun dark:i-carbon-moon" />
    <!-- Twemoji of laugh, turns to tear on hovering -->
    <div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />
</template>

0. 代码实现

package.json

  "devDependencies": {
    "@iconify/json": "^2.1.105",
    "@unocss/preset-attributify": "^0.45.18",
    "@unocss/preset-icons": "^0.45.18",
    "@unocss/preset-wind": "^0.45.18",
    "unocss": "^0.45.18",
}

vite.config.ts

import { defineConfig } from 'vite';
import Unocss from 'unocss/vite';
​
const config = defineConfig(({ command }) => {
  return {
    plugins: [, /*...*/ Unocss() /* <- 只加这个 🧬 */],
  };
});
​
export { config as default };

unocss.config.ts

  • @unocss/preset-wind

    • windicss 语法 你能够指定类 py-4py-[2rem]...... 生成 css
    • .py-4 {
          padding-top: 1rem;
          padding-bottom: 1rem;
      }
      ​
      .py-4 {
          padding-top: 2rem;
          padding-bottom: 2rem;
      }
      
  • @unocss/preset-icons

    • 让你能够 通过类 生成 @iconify/json icon 图标

image-20220913005058951.png

<div class="icon-logos-vitejs" /> <!-- vite 图标 -->
<div class="icon-logos-vitejs?bg" /> <!-- vite 图标 -->
  • @unocss/preset-attributify

    • 少写前缀 😅
    <template>
      <!-- windiscss -->
      <h2 class="text-teal-500">案例</h2>
    ​
      <!-- windiscss + unocss + @unocss/preset-attributify -->
      <h2 text="teal-500">案例</h2>
    </template>
    

unocss.config.ts

根目录下

import {
  defineConfig,
  presetWind,
  presetIcons,
  presetAttributify,
} from 'unocss';
import transformerDirective from '@unocss/transformer-directives';
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
​
export default defineConfig({
  presets: [
    presetIcons({
      mode: 'mask', // 自定义 br-icon 默认是 bg 模式加载不能修改字体颜色
      scale: 1.4,
      prefix: 'icon-',
      extraProperties: {
        display: 'inline-block',
        'vertical-align': 'middle',
      },
      collections: {
        // 自定义路径下的 svg -> 类 format: icon-br-banner-[filename] or icon-br-icon-[filename]
        'br-banner': FileSystemIconLoader('./src/assets/banner', (svg) => svg),
        'br-icon': FileSystemIconLoader('./src/assets/icon', (svg) => svg),
      },
      customizations: {
        iconCustomizer(collection, icon, props) {
          // banner 尺寸调整
          if (collection === 'br-banner') {
            props.height = '20em';
            props.width = '20em';
          }
        },
      },
    }),
    presetWind(),
    presetAttributify(),
  ],
  // 能够使用 @apply......
  transformers: [transformerDirective()],
});

集成 day.js

对日期进行处理

距离现在 距离 n 秒 / n 分 / n 时 / n 天 / n 年

import { day } from '~plugins/day';
​
day(createAt).fromNow(); // 19 年前

config

import dayjs from 'dayjs';
import isLeapYear from 'dayjs/plugin/isLeapYear';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/zh-cn';
​
dayjs.extend(isLeapYear);
dayjs.extend(relativeTime);
dayjs.locale('zh-cn');
​
export { dayjs as day };

集成 axios

IApiEntity.d.ts

export interface IApiEntity<T = unknown> {
  status: number;
  message: string;
  data: T;
}

api/index.ts

import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
import { Message } from '@arco-design/web-vue';
import type { IApiEntity } from '~types/entity/IApiEntity';
import { closeLoading, startLoading } from '~plugins/nprogress';
​
const server = axios.create({
  baseURL: import.meta.env.VITE_BASE_API,
  timeout: 1000 * 7,
});
​
// 请求拦截器
server.interceptors.request.use(
  (conf: AxiosRequestConfig) => {
    // 添加验证请求头......
    return conf;
  },
  (err: AxiosError) => {
    // err
    return Promise.reject(err);
  }
);
​
// 响应拦截器
server.interceptors.response.use(
  async (res: AxiosResponse<IApiEntity>) => {
    const { status, message, data: resData } = res.data;
​
    switch (true) {
      case status === 200: {
        break;
      }
      // ......
      default: {
        Message.error(message);
        break;
      }
    }
​
    return res;
  },
  (err: AxiosError) => {
    return Promise.reject(err);
  }
);
​
const useBaseHttp = <T>(config: AxiosRequestConfig) => {
  startLoading();
  return (
    new Promise() <
    T >
    ((resolve, reject) => {
      server
        .request(config)
        .then((response) => {
          resolve(response.data);
        })
        .catch((error) => {
          reject(error);
        })
        .finally(() => {
          closeLoading();
        });
    })
  );
};
​
const useHttp = <T>(config: AxiosRequestConfig) => {
  return useBaseHttp < IApiEntity < T >> config;
};
​
export { useHttp as default, useHttp, useBaseHttp };

api 案例

UserMessageDto.ts

export interface UserMessageDto {
  id: number;
  message: string;
  createAt: string;
  user: IUserEntity;
}

IBaseEntity.d.ts

export interface IBaseEntity {
  // 主键
  id: number;
  // 创建时间
  createAt: string;
  // 最后更新时间
  updateAt: string;
  // 创建人
  createBy: number;
  // 最后更新人
  updateBy: number;
  // 是否启用 `0` 关闭  `1` 开启
  isStatus: boolean;
  // 逻辑删除 `0` 未删除 `null` 删除
  isDeleted: boolean;
  // 备注
  remark: string;
  // 乐观锁
  version: number;
  // ip
  ip: string;
  // ip address
  ipAddr: string;
}

IUserEntity.d.ts

import type { IBaseEntity } from '~types/entity/IBaseEntity';
​
declare global {
  interface IUserEntity extends IBaseEntity {
    username: string;
    email: string;
    address: string;
    gender: boolean;
    age: number;
    icon: string;
    loginCount: number;
    ip: string;
    ipAddr: string;
    birthday: string;
  }
}
import useHttp from '~/server';
import type { UserMessageDto } from '~dto/user/UserMessageDto';
​
/**
 * @since 2022-9-10
 * @description 获取当前用户信息
 */
export const getUserSelfMessagesApi = () => {
  return useHttp<UserMessageDto[]>({
    method: 'GET',
    url: '/user/self',
  });
};

使用

  • 集成 vue-request 请求库
<script lang="ts" setup>
  import SimpleCartList from '~comp/message/SimpleCartList.vue';
  import { getUserSelfMessagesApi } from '~api/user';
  import { useRequest } from 'vue-request';
  import { computed } from 'vue';
  import type { UserMessageDto } from '~dto/user/UserMessageDto';
​
  const {
    data: res,
    loading,
    refresh,
  } = useRequest(getUserSelfMessagesApi, {
    manual: false,
  });
  const list = computed<UserMessageDto[]>(() => {
    return res.value?.status === 200 ? res.value.data : [];
  });
</script><template>
  // a-xx by arco ui comp.
  <a-space>
    <h3>Default 默认页面</h3>
    <a-button :loading="loading" @click="refresh">刷新</a-button>
  </a-space>
​
  // 自定义组件
  <SimpleCartList :list="list" :loading="loading" />
</template><style scoped></style>

集成 nprogress

加载进度条

路由跳转

import { createRouter, createWebHashHistory } from 'vue-router';
​
// routes......const router = createRouter({
  history: createWebHashHistory(),
  routes: routes,
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  scrollBehavior(to, from, savedPosition) {
    // 始终滚动到顶部
    return { top: 0 };
  },
});
​
// eslint-disable-next-line @typescript-eslint/no-unused-vars
router.beforeEach(async ({ fullPath, name }, from) => {
  // 跳转 前加载
  startLoading();
});
​
// eslint-disable-next-line @typescript-eslint/no-unused-vars
router.afterEach((to, from, failure) => {
  // 跳转 后结束加载
  closeLoading();
});
​
export { router as default, router };

请求

import type { AxiosRequestConfig } from 'axios';
import { closeLoading, startLoading } from '~plugins/nprogress';
​
const useBaseHttp = <T>(config: AxiosRequestConfig) => {
  startLoading();
  return (
    new Promise() <
    T >
    ((resolve, reject) => {
      server
        .request(config)
        .then((response) => {
          setTimeout(() => resolve(response.data), ANALOG_DELAY);
        })
        .catch((error) => {
          reject(error);
        })
        .finally(() => {
          closeLoading();
        });
    })
  );
};

config

import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
​
NProgress.configure({
  showSpinner: false, // 是否显示加载 icon
  easing: 'ease',
  speed: 500,
});
​
const startLoading = () => {
  NProgress.start();
};
​
const closeLoading = () => {
  NProgress.done();
};
​
export { startLoading, closeLoading, NProgress };