【Midway+Vue3】初始化一个 Vue 项目 (前端篇 01)

初始化 vue-ts 项目

pnpm create vite tutulist-web-app --template vue-ts

安装 vscode 插件

Volar

vue3 语法支持,此插件并不兼容 vue2,使用时需要将 vue2 插件禁用

vue3-snippets-for-vscode

可根据关键词快速键入 vue3 相关代码

配置 lint

eslint

vue-cli 创建的项目不一样,vite 创建的项目是不带 eslint,所以需要手动去配

# eslint 和 eslint vue 插件
pnpm install --save-dev eslint eslint-plugin-vue

# vite 接入 eslint
pnpm install vite-plugin-eslint --save-dev

pnpm i @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
@typescript-eslint/parser

增加 eslint 解析 typescript 的能力

@typescript-eslint/eslint-plugin

eslint 插件,为 typescript 代码提供 lint 规则

prettier

eslint-plugin-prettier

用于将 prettier 的 错误报错给 eslint

eslint-config-prettier

因为 eslintprettier 都可以去做格式化代码,这就造成两者在使用上会出现冲突,它主要负责两者的冲突

pnpm install prettier eslint-plugin-prettier eslint-config-prettier --save-dev

创建 .eslintrc.json文件

{
  "env": {
    "browser": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:vue/vue3-recommended",
    "prettier",
    "plugin:prettier/recommended"
  ],
  "plugins": ["vue", "@typescript-eslint"],
  "parserOptions": {
    "ecmaVersion": 12,
    "parser": "@typescript-eslint/parser",
    "sourceType": "module"
  },
  "rules": {
    "vue/multi-word-component-names": "off",
    "no-unused-vars": [
      "error",
      {
        "varsIgnorePattern": ".*",
        "args": "none",
        "vars": "all",
        "ignoreRestSiblings": true,
        "argsIgnorePattern": "^_"
      }
    ]
  }
}

自动格式化代码

借助 vscode Prettier 插件 格式化代码

下载 Prettier vscode 插件,然后在设置中搜索 editor.default formatter使用 Prettier

Prettier 这里我们就不配了,使用官方插件默认格式化就好。

但如果在团队中,尤其成员之前使用不同的编辑器,那么就需要配置一下 Prettier 统一代码风格了。

在这里插入图片描述

开启保存时格式化文件

设置中搜索 formatOnSave
在这里插入图片描述

配置 rules

vue/multi-word-component-names

创建 vue 组件时,可以使用单个单词
在这里插入图片描述

no-unused-vars

声明但未使用的变量,当变量名以 _ 为前缀时,可忽略错误

"rules": {
  "vue/multi-word-component-names": "off",
  "no-unused-vars": [
    "error",
    {
      "varsIgnorePattern": ".*",
      "args": "none",
      "vars": "all",
      "ignoreRestSiblings": true,
      "argsIgnorePattern": "^_"
    }
  ]
}

vite 接入 eslint

使用此 vite 插件可以将 eslint 的错误信息展示到浏览器上
在这里插入图片描述

代码配置

vite.config.ts 中引入 eslintPlugin

import eslintPlugin from 'vite-plugin-eslint';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [eslintPlugin()]
})

配置路径别名

导入 path 时,可能会报类型错误,需安装 @types/node

pnpm install --save-dev @types/node

vite.config.js

import { resolve } from "path";

export default defineConfig({
  plugins: [eslintPlugin(), vue()],
  resolve: {
    alias: {
      "@": resolve(__dirname, "/src"),
    },
  },
});

tsconfig.json

compilerOptions 处添加

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

配置 vue-router

pnpm install vue-router@4

配置 router

import { createRouter, createWebHashHistory } from "vue-router";
import type { RouteRecordRaw } from "vue-router";

import Home from "@/pages/home/index.vue";
import Calendar from "@/pages/calendar/index.vue";
import Setting from "@/pages/setting/index.vue";

const routes: RouteRecordRaw[] = [
  {
    path: "/",
    name: "homePage",
    component: Home,
    meta: {
      title: "首页",
    },
  },
  {
    path: "/calendar",
    name: "calendar",
    component: Calendar,
    meta: {
      title: "日历",
    },
  },
  {
    path: "/setting",
    name: "setting",
    component: Setting,
    meta: {
      title: "设置",
    },
  },
];
const router = createRouter({
  history: createWebHashHistory(), // hash 模式
  routes,
});

export default router;
让应用支持 router
import { createApp } from "vue";

import router from "./routes";

import App from "./App.vue";

import "./index.css";

const app = createApp(App);

app.use(router);
app.mount("#app");
App.vue 中 添加 router-view
<script setup lang="ts"></script>

<template>
  <router-view />
</template>

<style></style>

配置 keep-alive

keep-alive 和 vue2 写法还不太一样

文档地址

缓存 homesetting 组件

<router-view v-slot="{ Component }">
  <keep-alive :include="['home', 'setting']">
    <component :is="Component" />
  </keep-alive>
</router-view>
指定组件 name

对于 script setup 语法,我们可以安装 vite-plugin-vue-setup-extend插件,让其支持 name 属性

<script setup lang="ts" name="setting">
import { onMounted } from "vue";

onMounted(() => {
  console.log("setting");
});
</script>

配置 Pinia

中文文档 官方文档

安装

pnpm install pinia

import { createPinia } from "pinia";

app.use(createPinia());
关于 Vuex 和 Pinia 的对比,可以看以下几篇文章

Pinia与Vuex的对比:Pinia是Vuex的良好替代品吗?

我把vue3项目中的vuex去除了,改用 pinia

Pinia 官方文档

定义 store

  1. defineStore 定义 store
  2. state 定义状态
  3. actions 定义方法,既可以定义同步也也可以定义异步方法
import { defineStore } from "pinia";

// calendar 定义唯一key
const useCalendarStore = defineStore("calendar", {
  state: () => ({
    isStartSunday: false,
  }),
  actions: {
    setStartSundaySync(value: boolean) {
      this.isStartSunday = value;
    },
    async setStartSunday() {
      const data = await getInfo();
      this.isStartSunday = data;
    },
  },
});

export default useCalendarStore;

在 Vue 组件中使用

<script setup lang="ts">
import useCalendarStore from "@/stores/calendar";
const caledarStore = useCalendarStore();

onMounted(() => {
  // 获取 state
  const isStartSunday = caledarStore.isStartSunday;
  // 触发 actions
  caledarStore.setStartSunday(false);
});
</script>

配置 Tailwind CSS

  1. 如果不想花太多时间去写 css,那么其实可以尝试使用下 tailwind css 这种原子化 css
    1. 当然目前社区中原子化 css 的方案还有很多,大家根据自己喜好选择
  1. 虽然要记那么多的 classname,但是有 vscode 插件啊,用起来之后你就会觉得其实还挺香的

安装和初始化

这里官方文档已经说的够详细了,就直接贴文档

VS Code 类名提示

安装插件 Tailwind CSS IntelliSense
在这里插入图片描述

处理编辑器警告

@tailwind 报警告

解决方法:装一个 postcss vscode 插件

PostCSS Language Support

参考链接
在这里插入图片描述
在这里插入图片描述

插件提示不生效

设置中输入 quickSuggestions ,将 strings 置为 on
在这里插入图片描述

tailwindcss 使用 @apply 时 报 warning

解决方案: https://github.com/tailwindlabs/tailwindcss/discussions/5258

  1. 下载 vscode 插件 PostCSS Language Support
  2. css.lint.unknownAtRules: ignore
    1. 如果你在项目中使用的是 scss,那么把 css 改成 scss 即可

css.lint.unknow 设置为 ignore
在这里插入图片描述

配置 axios

axios + ts 案例

axios 封装每个团队有每个团队的习惯和规范,没有最好的,用起来爽就行;

request 函数
  1. 接收 axios request config 配置对象,并返回一个 Promise 类型的 API.BaseResponseType
  2. API.BaseResponseType 接受一个 泛型 T,用于约束后端返回的数据 data 的类型
const request = async <T>(
  config: AxiosRequestConfig
): Promise<API.BaseResponseType<T>> => {
  try {
    const { data } = await axiosInterface(config);
    return data;
  } catch (error) {
    return Promise.reject(error);
  }
};
声明 API 的 namespcae
  1. 创建一个单独的 namespcae API,用于约束与后端交互的数据类型
declare namespace API {
  type BaseResponseType<T> = {
    code: number;
    message: string;
    data: T;
  };
}

需要在 .eslintrc.json文件中添加配件

"globals": {
  "API": "readonly"
},
API.BaseResponseType

后端返回最基本的 响应数据 结构
在这里插入图片描述

通过在 request 函数中传入泛型约束后端返回的具体数据结构
export const loginByPassword = async (loginInfo: LoginByPassword) => {
  return await request<{
    accessToken: string;
    refreshToken: string;
  }>({
    url: "/user/loginByPassword",
    method: "post",
    data: loginInfo,
  });
};

在这里插入图片描述

配置 Refresh Token

登录成功后,后端会返回两个 token, accessTokenrefreshToken,有效时间分别为 2天 和 4天;
在这里插入图片描述

用户在使用过程中,如果后端返回 401 状态码,就代表 accessToken 过期了。这时候要缓存过期后的请求函数,同时发送一个新的请求并携带 refreshToken 去从后端获取新的 token,获取新的 token 成功后,再执行之前缓存过的请求函数

如果获取新token 的请求返回的状态码非 200,那么代表 refreshToken 也过期了,这时候需要跳转到登录页,重新登录

const handleRefreshToken = async () => {
  const { code, data } = await request<{
    accessToken: string;
    refreshToken: string;
  }>({
    url: "/user/refreshToken",
    method: "post",
    data: {
      refreshToken: window.localStorage.getItem(UserTokenEnum.REFRESH_TOKEN),
    },
  });
  if (Number(code) === 200) {
    localStorage.setItem(UserTokenEnum.ASSET_TOKEN, data.accessToken);
    localStorage.setItem(UserTokenEnum.REFRESH_TOKEN, data.refreshToken);

    axiosInterface.defaults.headers.common[
      "Authorization"
    ] = `Bearer ${data.accessToken}`;

    // 执行 token 失效后缓存的请求函数
    catchRequestFunc.forEach(async (catchFunc) => {
      await catchFunc();
    });
  } else {
    // refreshtoken 也过期了,那么跳登录页,重新登录
    const globalStore = useGlobalStore();
    globalStore.handleLogout();

    catchRequestFunc = [];
    router.push({
      name: "homePage",
    });
    window.$message.warning("请重新登录");
  }
};
完整代码
import axios from "axios";
import router from "@/router";

import useGlobalStore from "@/stores/global";

import { UserTokenEnum } from "@/types/user";
import type { AxiosRequestConfig, AxiosResponse } from "axios"

const netWorkCodeMaps: Record<number, string> = {
  404: "404 Not Found",
  405: "Method Not Allowed",
  504: "网关错误",
  500: "服务器错误",
} as const;

const axiosInterface = axios.create({
  baseURL: `/api`,
  timeout: 10000,
  headers: {
    "content-type": "application/json",
  },
});

// 缓存 token 过期后的请求函数
let catchRequestFunc: Array<() => void> = [];

// 请求拦截
axiosInterface.interceptors.request.use((config: AxiosRequestConfig) => {
  const token = localStorage.getItem(UserTokenEnum.ASSET_TOKEN);
  if (token) {
    const { headers } = config;
    headers!.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截
axiosInterface.interceptors.response.use(
  async (response: AxiosResponse<API.BaseResponseType<any>>) => {
    const { status, data } = response;
    if (status === 200) {
      const { code, message } = data;
      const responseCode = Number(code);

      // token 过期
      if (responseCode == 401) {
        // 缓存过期后的请求函数
        new Promise((resolve) => {
          catchRequestFunc.push(() => {
            resolve(request(response.config));
          });
        });
        // 通过 reference token 获取新 token
        await handleRefreshToken();
      } else if (responseCode === 403) {
        router.push({
          name: "homePage",
        });
      } else if (responseCode !== 200) {
        // 业务中非 200 的状态码一律弹出
        window.$message.error(message);
      }
    }
    return response;
  },
  ({ response }) => {
    // 请求失败,也弹出状态码
    window.$message.error(netWorkCodeMaps[response.status] || "服务器错误");
  }
);

const handleRefreshToken = async () => {
  const { code, data } = await request<{
    accessToken: string;
    refreshToken: string;
  }>({
    url: "/user/refreshToken",
    method: "post",
    data: {
      refreshToken: window.localStorage.getItem(UserTokenEnum.REFRESH_TOKEN),
    },
  });
  if (Number(code) === 200) {
    localStorage.setItem(UserTokenEnum.ASSET_TOKEN, data.accessToken);
    localStorage.setItem(UserTokenEnum.REFRESH_TOKEN, data.refreshToken);

    axiosInterface.defaults.headers.common[
      "Authorization"
    ] = `Bearer ${data.accessToken}`;

    // 执行 token 失效后缓存的请求函数
    catchRequestFunc.forEach(async (catchFunc) => {
      await catchFunc();
    });
  } else {
    // refreshtoken 也过期了,那么跳登录页,重新登录
    const globalStore = useGlobalStore();
    globalStore.handleLogout();

    catchRequestFunc = [];
    router.push({
      name: "homePage",
    });
    window.$message.warning("请重新登录");
  }
};

// 对外暴露 request 请求函数
const request = async <T>(
  config: AxiosRequestConfig
): Promise<API.BaseResponseType<T>> => {
  try {
    const { data } = await axiosInterface(config);
    return data;
  } catch (error) {
    return Promise.reject(error);
  }
};

export default request;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在Midway配置Swagger,您需要执行以下步骤: 1. 首先,确保您已经安装了Midway Server和Swagger插件。您可以使用以下命令进行安装: ``` $ npm install midway $ npm install @midwayjs/swagger ``` 2. 在您的Midway应用程序的配置文件(`config/config.default.ts`)中,添加Swagger插件的配置: ```typescript export const swagger = { enable: true, package: '@midwayjs/swagger', }; ``` 3. 在您的控制器文件中,使用装饰器 `@Provide()` 和 `@Controller()` 来定义您的路由和控制器类。例如: ```typescript import { Provide, Controller, Get } from '@midwayjs/decorator'; @Provide() @Controller('/users') export class UserController { @Get('/') async getUsers() { // 处理获取用户的逻辑 } } ``` 4. (可选) 如果您想为路由添加更多的描述信息,您可以在控制器类上使用装饰器 `@Description()` 和 `@Summary()`。例如: ```typescript import { Provide, Controller, Get, Description, Summary } from '@midwayjs/decorator'; @Provide() @Controller('/users') @Description('用户管理') export class UserController { @Get('/') @Summary('获取所有用户') async getUsers() { // 处理获取用户的逻辑 } } ``` 5. 最后,在命令行中运行以下命令来生成Swagger文档: ``` $ midway swagger generate ``` 这将在您的项目根目录下生成一个名为 `swagger.json` 的Swagger JSON文件。 6. 现在,您可以通过访问 `http://localhost:7001/swagger-ui.html` 来查看您的Swagger文档,并测试您的API。 这样,您就成功地在Midway中配置了Swagger。请注意,上述步骤基于Midway v2.x版本。如果您使用的是较旧的版本,请参考相应的文档进行配置。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值