从 0 开始搭建Vue3.x 项目工程环境

作为一名前端工程师,只会写业务代码是不行了,项目搭建是我们成长路上必备得一课.今天我以vue3+vite+pinia+ts为基础搭建一个完整的vue框架,下面进入正题

架构搭建

请确保你的电脑上成功安装 Node.js,本项目使用 Vite 构建工具,需要 Node.js 版本 >= 12.0.0

查看 Node.js 版本:

node -v

建议将 Node.js 升级到最新的稳定版本:

# 使用 nvm 安装最新稳定版 Node.js
nvm install stable

使用 Vite 快速初始化项目雏形

  • 使用 NPM:

    npm init vite
    
  • 使用 Yarn:

    yarn create vite
    

然后按照终端提示完成以下操作:

  1. 输入项目名称

    例如:本项目名称 vite-vue3

  2. 选择模板

    本项目需要使用 Vue3 + TypeScript,所以我们选择 vue-ts,会自动安装 Vue3 和 TypeScript。

从 0 开始搭建Vue3.x 项目工程环境

从 0 开始搭建Vue3.x 项目工程环境

你还可以通过附加的命令行选项直接指定项目名和模板,本项目要构建 Vite + Vue3 + TypeScript 项目,则运行:

```
# npm 6.x
npm init @vitejs/app vite-vue3 --template vue-ts

# npm 7+(需要额外的双横线)
npm init @vitejs/app vite-vue3 -- --template vue-ts

# yarn
yarn create @vitejs/app vite-vue3 --template vue-ts
```
  1. 安装依赖

    npm install
    
  2. 启动项目

    npm run dev
    

从 0 开始搭建Vue3.x 项目工程环境

如上图,表示 Vite + Vue3 + TypeScript 简单的项目骨架搭建完毕,下面我们来为这个项目集成 Vue Router、pinia、Element Plus、Axios、Stylus/Sass/Less。

修改 Vite 配置文件

Vite 配置文件 vite.config.ts 位于根目录下,项目启动时会自动读取。

本项目先做一些简单配置,例如:设置 @ 指向 src 目录、 服务启动端口、打包路径、代理等。

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import {resolve}  from 'path'

    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [vue()],
      resolve:{
        alias:{
          '@':resolve(__dirname,'src')//设置 `@` 指向 `src` 目录
        }
      },
      base: './', // 设置打包路径
      server: {
        port: 4000, // 设置服务启动端口号
        open: true, // 设置服务启动时是否自动打开浏览器
        cors: true // 允许跨域

        // 设置代理,根据我们项目实际情况配置
        // proxy: {
        //   '/api': {
        //     target: 'http://xxx.xxx.xxx.xxx:8000',
        //     changeOrigin: true,
        //     secure: false,
        //     rewrite: (path) => path.replace('/api/', '/')
        //   }
        // }
      }
    })

关于 Vite 更多配置项及用法,请查看 Vite 官网 vitejs.dev/config/

当前文件目录如下:

从 0 开始搭建Vue3.x 项目工程环境

现在我们开始配置路由

集成路由工具 Vue Router

  1. 安装支持 Vue3 的路由工具 vue-router

    npm i vue-router
    
  2. 创建 src/router/index.ts 文件

    src 下创建 router 目录,然后在 router 目录里新建 index.ts 文件:

    scss
     └── src/
         ├── router/
             ├── index.ts  // 路由配置文件
             
    
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import Home from '@/views/home.vue'

const routers: RouteRecordRaw[] = [
    {
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/pageOne',
        name: 'pageOne',
        component: () => import('@/views/PageOne.vue') // 懒加载组件
    },
]
const router = createRouter({
    history: createWebHashHistory(),
    routes: routers
})

export default router

根据本项目路由配置的实际情况,你需要在 src 下创建 views 目录,用来存储页面组件。

main.ts 文件中挂载路由配置

import { createApp } from 'vue'
import App from './App.vue'

import router from './router/index'

createApp(App).use(router).mount('#app')

集成状态管理工具 pinia

  • pinia相较于vuex的优点(详情可查询pinia官网

    • Devtools 支持

    • 追踪 actions、mutations 的时间线

    • 在组件中展示它们所用到的 Store

    • 让调试更容易的 Time travel

  • 热更新

    • 不必重载页面即可修改 Store
    • 开发时可保持当前的 State
  • 插件:可通过插件扩展 Pinia 功能

  • 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。

  • 支持服务端渲染

    npm install pinia
    

    创建一个 pinia 实例 (根 store) 并将其传递给应用:

    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import App from './App.vue'
    
    const pinia = createPinia()
    const app = createApp(App)
    
    app.use(pinia)
    app.mount('#app')
    
  • 创建 src/store/index.ts 文件

    src 下创建 store 目录,然后在 store 目录里新建 index.ts 文件:

    scss
    复制代码
    └── src/
        ├── store/
            ├── index.ts  // store 配置文件
    

在深入研究核心概念之前,我们得知道 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:


import { defineStore } from 'pinia'

// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
  // 其他配置...
})

这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use…  是一个符合组合式函数风格的约定。

defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。

Option Store

与 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactions 与 getters 属性的 Option 对象

js

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

为方便上手使用,Option Store 应尽可能直观简单。

Setup Store

也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

js

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

集成 UI 框架 Element Plus

  1. 安装支持 Vue3 的 UI 框架 Element Plus

    
    npm i element-plus
    
  2. main.ts 文件中挂载 Element Plus

    ts
    复制代码
    import { createApp } from 'vue'
    import App from './App.vue'
    
    import ElementPlus from 'element-plus'
    import 'element-plus/lib/theme-chalk/index.css'
    
    createApp(App).use(ElementPlus).mount('#app')
    

集成 HTTP 工具 Axios

  1. 安装 Axios

    npm i axios
    
  2. 配置 Axios

    为了使项目的目录结构合理且规范,我们在 src 下创建 utils 目录来存储我们常用的工具函数。

    Axios 作为 HTTP 工具,我们在 utils 目录下创建 axios.ts 作为 Axios 配置文件:

    scss
    复制代码
    └── src/
        ├── utils/
            ├── axios.ts  // Axios 配置文件
    
  • import Axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    const baseURL = 'https://api.github.com'
    
    const axios = Axios.create({
      baseURL,
      timeout: 20000 // 请求超时 20s
    })
    
    // 前置拦截器(发起请求之前的拦截)
    axios.interceptors.request.use(
      (response) => {
        /**
         * 根据你的项目实际情况来对 config 做处理
         * 这里对 config 不做任何处理,直接返回
         */
        return response
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    
    // 后置拦截器(获取到响应时的拦截)
    axios.interceptors.response.use(
      (response) => {
        /**
         * 根据你的项目实际情况来对 response 和 error 做处理
         * 这里对 response 和 error 不做任何处理,直接返回
         */
        return response
      },
      (error) => {
        if (error.response && error.response.data) {
          const code = error.response.status
          const msg = error.response.data.message
          ElMessage.error(`Code: ${code}, Message: ${msg}`)
          console.error(`[Axios Error]`, error.response)
        } else {
          ElMessage.error(`${error}`)
        }
        return Promise.reject(error)
      }
    )
    
    export default axios
    
  1. 使用 Axios
    在需要使用 Axios 文件里,引入 Axios 配置文件,参考如下:

    html
    复制代码
    <template></template>
    <script lang="ts">
      import { defineComponent } from 'vue'
      import axios from '../utils/axios'
    
      export default defineComponent({
        setup() {
          axios
            .get('/users/XPoet')
            .then((res) => {
              console.log('res: ', res)
            })
            .catch((err) => {
              console.log('err: ', err)
            })
        }
      })
    </script>
    

集成 CSS 预编译器 Stylus/Sass/Less

本项目使用 CSS 预编译器 Sass,直接安装为开发依赖即可。Vite 内部已帮我们集成了相关的 loader,不需要额外配置。同理,你也可以使用 Stylus 或 Less 等。

  1. 安装

    npm i less -D
    # or
    npm i sass -D
    npm i stylus -D
    
  2. 使用

    html
    复制代码
    <style lang="Sass">
      ...
    </style>
    

至此,一个基于 TypeScript + Vite + Vue3 + Vue Router + Vuex + Element Plus + Axios + Stylus/Sass/Less 的前端项目开发环境搭建完毕,项目 Demo 托管在,需要的同学可以去下载下来,参考学习。

代码规范

代码规范的重要性不言而喻,特别是在多人合作的项目中尤其重要

本文讲解如何使用 EditorConfig + Prettier + ESLint 组合来实现代码规范化。

这样做带来好处:

  • 解决团队之间代码不规范导致的可读性差和可维护性差的问题。
  • 解决团队成员不同编辑器导致的编码规范不统一问题。
  • 提前发现代码风格问题,给出对应规范提示,及时修复。
  • 减少代码审查过程中反反复复的修改过程,节约时间。
  • 自动格式化,统一编码风格,从此和脏乱差的代码说再见。

集成 EditorConfig 配置

EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。

官网:editorconfig.org

在项目根目录下增加 .editorconfig 文件:

# Editor configuration, see http://editorconfig.org

# 表示是最顶层的 EditorConfig 配置文件
root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

注意:

  • VSCode 使用 EditorConfig 需要去插件市场下载插件 EditorConfig for VS Code

    从 0 开始搭建Vue3.x 项目工程环境

  • JetBrains 系列(WebStorm、IntelliJ IDEA 等)则不用额外安装插件,可直接使用 EditorConfig 配置。

集成 Prettier 配置

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

官网:prettier.io/

  1. 安装 Prettier

    npm i prettier -D
    
  2. 创建 Prettier 配置文件

    Prettier 支持多种格式的配置文件,比如 .json.yml.yaml.js等。

    在本项目根目录下创建 .prettierrc 文件。

  3. 配置 .prettierrc

    在本项目中,我们进行如下简单配置,关于更多配置项信息,请前往官网查看 Prettier-Options

    
     // @see: https://www.prettier.cn
    
    module.exports = {
    // 超过最大值换行
    printWidth: 130,
    // 缩进字节数
    tabWidth: 2,
    // 使用制表符而不是空格缩进行
    useTabs: true,
    // 结尾不用分号(true有,false没有)
    semi: true,
    // 使用单引号(true单双引号,false双引号)
    singleQuote: false,
    // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
    quoteProps: "as-needed",
    // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
    bracketSpacing: true,
    // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
    trailingComma: "none",
    // 在JSX中使用单引号而不是双引号
    jsxSingleQuote: false,
    //  (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 ,always:不省略括号
    arrowParens: "avoid",
    // 如果文件顶部已经有一个 doclock,这个选项将新建一行注释,并打上@format标记。
    insertPragma: false,
    // 指定要使用的解析器,不需要写文件开头的 @prettier
    requirePragma: false,
    // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
    proseWrap: "never",
    // 在html中空格是否是敏感的 "css" - 遵守CSS显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的
    htmlWhitespaceSensitivity: "ignore",
    // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
    endOfLine: "auto",
    // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
    rangeStart: 0,
    rangeEnd: Infinity,
    // Vue文件脚本和样式标签缩进
    vueIndentScriptAndStyle: false,
    // 是否使用根目录下的EditorConfig配置文件
    useEditorConfig: false
    

};

```
  1. Prettier 安装且配置好之后,就能使用命令来格式化代码

    # 格式化所有文件(. 表示所有文件)
    npx prettier --write .
    

注意:

  • VSCode 编辑器使用 Prettier 配置需要下载插件 Prettier – Code formatter

    从 0 开始搭建Vue3.x 项目工程环境

Prettier 配置好以后,在使用 VSCode 或 WebStorm 等编辑器的格式化功能时,编辑器就会按照 Prettier 配置文件的规则来进行格式化,避免了因为大家编辑器配置不一样而导致格式化后的代码风格不统一的问题。

集成 ESLint 配置

ESLint 是一款用于查找并报告代码中问题的工具,并且支持部分问题自动修复。其核心是通过对代码解析得到的 AST(Abstract Syntax Tree 抽象语法树)进行模式匹配,来分析代码达到检查代码质量和风格问题的能力。

正如前面我们提到的因团队成员之间编程能力和编码习惯不同所造成的代码质量问题,我们使用 ESLint 来解决,一边写代码一边查找问题,如果发现错误,就给出规则提示,并且自动修复,长期下去,可以促使团队成员往同一种编码风格靠拢。

  1. 安装 ESLint

    可以全局或者本地安装,作者推荐本地安装(只在当前项目中安装)。

    npm i eslint -D
    
  2. 配置 ESLint

    ESLint 安装成功后,执行 npx eslint --init,然后按照终端操作提示完成一系列设置来创建配置文件。

    • How would you like to use ESLint? (你想如何使用 ESLint?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 To check syntax, find problems, and enforce code style(检查语法、发现问题并强制执行代码风格)

    • What type of modules does your project use?(你的项目使用哪种类型的模块?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 JavaScript modules (import/export)

    • Which framework does your project use? (你的项目使用哪种框架?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 Vue.js

    • Does your project use TypeScript?(你的项目是否使用 TypeScript?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 Yes

    • Where does your code run?(你的代码在哪里运行?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 Browser 和 Node(按空格键进行选择,选完按回车键确定)

    • How would you like to define a style for your project?(你想怎样为你的项目定义风格?)

      从 0 开始搭建Vue3.x 项目工程环境

      我们这里选择 Use a popular style guide(使用一种流行的风格指南)

    • Which style guide do you want to follow?(你想遵循哪一种风格指南?)

      我们这里选择 standard

      此时,我们在 ESLint 配置了 Airbnb JavaScript 规则,在编码时,所有不符合 standard 风格的代码,编辑器都会给出提示,并且可以自动修复。

    • What format do you want your config file to be in?(你希望你的配置文件是什么格式?)

    从 0 开始搭建Vue3.x 项目工程环境

    我们这里选择 JavaScript

    • Would you like to install them now with npm?(你想现在就用 NPM 安装它们吗?)

    从 0 开始搭建Vue3.x 项目工程环境

    根据上面的选择,ESLint 会自动去查找缺失的依赖,我们这里选择 Yes,使用 NPM 下载安装这些依赖包。

  3. ESLint 配置文件 .eslintrc.js

    上一步操作完成后,会在项目根目录下自动生成 .eslintrc.js 配置文件:

    js
    复制代码
    module.exports = {
      env: {
        browser: true,
        es2021: true,
        node: true
      },
      extends: ['plugin:vue/essential', 'airbnb-base'],
      parserOptions: {
        ecmaVersion: 12,
        parser: '@typescript-eslint/parser',
        sourceType: 'module'
      },
      plugins: ['vue', '@typescript-eslint'],
      rules: {}
    }
    

    根据项目实际情况,如果我们有额外的 ESLint 规则,也在此文件中追加。

注意:

  • VSCode 使用 ESLint 配置文件需要去插件市场下载插件 ESLint

    从 0 开始搭建Vue3.x 项目工程环境

虽然,现在编辑器已经给出错误提示和修复方案,但需要我们一个一个去点击修复,还是挺麻烦的。很简单,我们只需设置编辑器保存文件时自动执行 eslint --fix 命令进行代码风格修复。

  • VSCode 在 settings.json 设置文件中,增加以下代码:

    js
    复制代码
     "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
     }
    

解决 Prettier 和 ESLint 的冲突

通常大家会在项目中根据实际情况添加一些额外的 ESLint 和 Prettier 配置规则,难免会存在规则冲突情况。

本项目中的 ESLint 配置中使用了 Airbnb JavaScript 风格指南校验,其规则之一是代码结束后面要加分号,而我们在 Prettier 配置文件中加了代码结束后面不加分号的配置项,这样就有冲突了,会出现用 Prettier 格式化后的代码,ESLint 检测到格式有问题的,从而抛出错误提示。

解决两者冲突问题,需要用到 eslint-plugin-prettiereslint-config-prettier

  • eslint-plugin-prettier 将 Prettier 的规则设置到 ESLint 的规则中。
  • eslint-config-prettier 关闭 ESLint 中与 Prettier 中会发生冲突的规则。

最后形成优先级:Prettier 配置规则 > ESLint 配置规则

  • 安装插件

    npm i eslint-plugin-prettier eslint-config-prettier -D
    
  • .eslintrc.js 添加 prettier 插件

    module.exports = {
      ...
      extends: [
        'plugin:vue/essential',
        'airbnb-base',
        'plugin:prettier/recommended' // 添加 prettier 插件
      ],
      ...
    }
    

这样,我们在执行 eslint --fix 命令时,ESLint 就会按照 Prettier 的配置规则来格式化代码,轻松解决二者冲突问题。

集成 husky 和 lint-staged

我们在项目中已集成 ESLint 和 Prettier,在编码时,这些工具可以对我们写的代码进行实时校验,在一定程度上能有效规范我们写的代码,但团队可能会有些人觉得这些条条框框的限制很麻烦,选择视“提示”而不见,依旧按自己的一套风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,ESLint 也就形同虚设。

所以,我们还需要做一些限制,让没通过 ESLint 检测和修复的代码禁止提交,从而保证仓库代码都是符合规范的。

为了解决这个问题,我们需要用到 Git Hook,在本地执行 git commit 的时候,就对所提交的代码进行 ESLint 检测和修复(即执行 eslint --fix),如果这些代码没通过 ESLint 规则校验,则禁止提交。

实现这一功能,我们借助 husky + lint-staged

husky —— Git Hook 工具,可以设置在 git 各个阶段(pre-commitcommit-msgpre-push 等)触发我们的命令。
lint-staged —— 在 git 暂存的文件上运行 linters。

配置 husky

  • 自动配置(推荐)

    使用 husky-init 命令快速在项目初始化一个 husky 配置。

    npx husky-init && npm install
    

    这行命令做了四件事:

    1. 安装 husky 到开发依赖 从 0 开始搭建Vue3.x 项目工程环境

    2. 在项目根目录下创建 .husky 目录 从 0 开始搭建Vue3.x 项目工程环境

    3. .husky 目录创建 pre-commit hook,并初始化 pre-commit 命令为 npm test 从 0 开始搭建Vue3.x 项目工程环境

    4. 修改 package.jsonscripts,增加 "prepare": "husky install" 从 0 开始搭建Vue3.x 项目工程环境

    到这里,husky 配置完毕,现在我们来使用它:

    husky 包含很多 hook(钩子),常用有:pre-commitcommit-msgpre-push。这里,我们使用 pre-commit 来触发 ESLint 命令。

    修改 .husky/pre-commit hook 文件的触发命令:

    eslint --fix ./src --ext .vue,.js,.ts
    

但是又存在一个问题:有时候我们明明只改动了一两个文件,却要对所有的文件执行 eslint --fix。假如这是一个历史项目,我们在中途配置了 ESLint 规则,那么在提交代码时,也会对其他未修改的“历史”文件都进行检查,可能会造成大量文件出现 ESLint 错误,显然不是我们想要的结果。

我们要做到只用 ESLint 修复自己此次写的代码,而不去影响其他的代码。所以我们还需借助一个神奇的工具 lint-staged

配置 lint-staged

lint-staged 这个工具一般结合 husky 来使用,它可以让 husky 的 hook 触发的命令只作用于 git add那些文件(即 git 暂存区的文件),而不会影响到其他文件。

接下来,我们使用 lint-staged 继续优化项目。

  1. 安装 lint-staged

    npm i lint-staged -D
    
  2. package.json里增加 lint-staged 配置项

    从 0 开始搭建Vue3.x 项目工程环境

    "lint-staged": {
      "*.{vue,js,ts}": "eslint --fix"
    },
    

    这行命令表示:只对 git 暂存区的 .vue.js.ts 文件执行 eslint --fix

  3. 修改 .husky/pre-commit hook 的触发命令为:npx lint-staged

    从 0 开始搭建Vue3.x 项目工程环境

至此,husky 和 lint-staged 组合配置完成。

规范目录结构

最终文件结构如图所示

├── publish/
└── src/
    ├── assets/                    // 静态资源目录
    ├── common/                    // 通用类库目录
    ├── components/                // 公共组件目录
    ├── router/                    // 路由配置目录
    ├── store/                     // 状态管理目录
    ├── style/                     // 通用 CSS 目录
    ├── utils/                     // 工具函数目录
    ├── views/                     // 页面组件目录
    ├── App.vue
    ├── main.ts
    ├── shims-vue.d.ts
├── index.html
├── tsconfig.json                  // TypeScript 配置文件
├── vite.config.ts                 // Vite 配置文件
└── package.json

本项目完整的代码托管在 GitHub 仓库,有需要的同学可以去下载下来,参考学习。喜欢的同学们可以一键三联,后续我也会不定期更新,去逐步完善这个框架.有好的建议或者想法可以一起交流,本文也是采众家之所长为己用,如有错误请指摘,共同进步

原文链接:https://juejin.cn/post/7349002962867912704 作者:jeremy_胡

(0)
上一篇 2024年3月22日 下午4:51
下一篇 2024年3月22日 下午5:02

相关推荐

发表回复

登录后才能评论