pnpm install vite -D
esbuild
预构建依赖。Esbuild
使用 Go
编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
import { defineConfig } from "vite"
export default defineConfig({})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
///
指令/// <reference types="vite/client" />
src\main.ts
export function render() {
document.getElementById("app")!.innerHTML = "main";
}
render();
if (import.meta.hot) {
import.meta.hot.accept((updatedModule) => updatedModule.render());
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"esModuleInterop": true,
"lib": ["esnext", "dom", "es2018.promise"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
参数 | 解释 |
---|---|
target | 用于指定编译之后的版本目标 |
module | 生成的模块形式:none、commonjs、amd、system、umd、es6、es2015 或 esnext 只有 amd 和 system 能和 outFile 一起使用 target 为 es5 或更低时可用 es6 和 es2015 |
moduleResolution | 选择模块解析策略,有 node 和 classic 两种类型 module-resolution |
strict | 是否启动所有类型检查 |
jsx | react 模式会生成 React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js |
sourceMap | 把 ts 文件编译成 js 文件的时候,同时生成对应的 sourceMap 文件 |
esModuleInterop | 为导入内容创建命名空间,实现 CommonJS 和 ES 模块之间的互相访问 |
lib | 编译时引入的 ES 功能库,包括:es5 、es6、es7、dom 等。如果未设置,则默认为: target 为 es5 时: ["dom", "es5", "scripthost"] target 为 es6 时: ["dom", "es6", "dom.iterable", "scripthost"] |
include | include 也可以指定要编译的路径列表,但是和 files 的区别在于,这里的路径可以是文件夹,也可以是文件 |
.gitignore
node_modules
pnpm-debug.log*
.vscode/*
sh.exe.stackdump
dist
coverage
pnpm install vue
pnpm install @vitejs/plugin-vue -D
vite.config.ts
import { defineConfig } from 'vite'
+import vue from "@vitejs/plugin-vue";
export default defineConfig({
+ plugins: [vue()]
})
src\env.d.ts
/// <reference types="vite/client" />
+declare module "*.vue" {
+ import type { DefineComponent } from "vue";
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
src\main.ts
+import { createApp } from "vue";
+import App from "./App.vue";
+const app = createApp(App)
+app.mount("#app");
src\App.vue
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="Vue3 + TypeScript + Vite2" />
</template>
<style></style>
src\components\HelloWorld.vue
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖ref
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value
属性,指向该内部值,在 JS 中操作数据需要.value
,在模板中读取不需要.value
<script setup>
中必须使用 defineProps API 来声明 props,只在 <script setup>
中才能使用的编译器宏。他们不需要导入且会随着 <script setup>
处理过程一同被编译掉<script setup lang="ts">
import { ref } from 'vue'
import logoUrl from '../assets/logo.png'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
pnpm install typescript vue-tsc -D
package.json
{
"scripts": {
"dev": "vite",
+ "build": "vue-tsc --noEmit && vite build"
},
}
src\msg.ts
interface Msg {
text: string;
}
const msg: Msg = {
text: "zhufeng",
age: 13
}
export default msg
src\App.vue
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
+import msg from "./msg"
</script>
<template>
+ <HelloWorld :msg="msg.text" />
</template>
<style></style>
pnpm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin @vue/eslint-config-typescript -D
名称 | 说明 |
---|---|
eslint | ESLint 是一个用于识别和报告在 ECMAScript/JavaScript 代码中发现的模式的工具 |
eslint-plugin-vue | Vue 的官方 ESLint 插件 |
@typescript-eslint/parser | 一个 ESLint 解析器,它利用 TypeScript-ESTree 允许 ESLint 检查 TypeScript 源代码 |
@typescript-eslint/eslint-plugin | 一个 ESLint 插件,为 TypeScript 代码库提供 lint 规则 |
@vue/eslint-config-typescript | Vue 的 eslint-config-typescript |
.eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/typescript/recommended"
],
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2021
},
rules: {
"no-unused-vars": "off",
"vue/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off"
},
globals: {
defineProps: "readonly"
}
}
.eslintignore
node_modules
dist
*.css
*.jpg
*.jpeg
*.png
*.gif
*.d.ts
package.json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
+ "lint": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet",
+ "lint:fix": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet --fix"
},
}
pnpm install prettier eslint-plugin-prettier @vue/eslint-config-prettier -D
名称 | 说明 |
---|---|
prettier | 代码格式化 |
eslint-plugin-prettier | 作为 ESLint 规则运行得 prettier |
@vue/eslint-config-prettier | Vue 的 eslint-config-prettier |
.eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/typescript/recommended",
+ "prettier",
+ "@vue/eslint-config-prettier"
],
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2021
},
rules: {
+ "prettier/prettier": [
+ "error",
+ {
+ singleQuote: false,
+ tabWidth: 2,
+ indent: 2,
+ semi: false,
+ trailingComma: "none",
+ endOfLine: "auto"
+ }
+ ],
+ "no-unused-vars": "off",
+ "vue/no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": "off"
+ },
globals: {
defineProps: "readonly"
}
}
.prettierrc.js
module.exports = {
singleQuote: false, //使用单引号
semi: false, ////末尾添加分号
tabWidth: 2,
trailingComma: "none",
useTabs: false,
endOfLine: "auto"
}
.prettierignore
node_modules
dist
\n
LF(Line Feed)换行 回车
,即\r\n
CR/LF回车
,即\r
CR(Carriage Return)root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
git commit
之前检查代码,保证所有提交到版本库中的代码都是符合规范的git push
之前执行单元测试,保证所有的提交的代码经过的单元测试git hooks
,它会自动在仓库中的 .git/
目录下增加相应的钩子,比如 pre-commit
钩子就会在你执行 git commit
命令的时候的触发package.json
依赖项中的代码质量工具来安装和配置 husky
和 lint-staged
git commit -m ""
中的描述信息mrm
安装lint-staged
的同时会安装husky
pnpm install mrm -D
npx mrm lint-staged
package.json
{
"name": "vite2vue3ts-prepare",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"lint": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --quiet",
"lint:fix": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --fix",
+ "prepare": "husky install"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^3.2.31"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"@vitejs/plugin-vue": "^2.2.2",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"eslint": "^8.10.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.5.0",
+ "husky": "^7.0.0",
+ "lint-staged": "^12.3.4",
+ "mrm": "^3.0.10",
"prettier": "^2.5.1",
"typescript": "^4.5.5",
"vite": "^2.8.4",
"vue-tsc": "^0.32.0"
},
+ "lint-staged": {
+ "*.{ts,vue}": "eslint --cache --fix"
+ }
}
commit
git commit -m <type>[optional scope]: <description>
type
:用于表明我们这次提交的改动类型,是新增了功能?还是修改了测试代码?又或者是更新了文档?optional scope
:一个可选的修改范围。用于标识此次提交主要涉及到代码中哪个模块description
:一句话描述此次提交的主要内容,做到言简意赅类型 | 描述 |
---|---|
build | 编译相关的修改,例如发布版本、对项目构建或者依赖的改动 |
chore | 其他修改, 比如改变构建流程、或者增加依赖库、工具等 |
ci | 持续集成修改 |
docs | 文档修改 |
feature | 新特性、新功能 |
fix | 修改 bug |
perf | 优化相关,比如提升性能、体验 |
refactor | 代码重构 |
revert | 回滚到上一个版本 |
style | 代码格式修改 |
test | 测试用例修改 |
pnpm install @commitlint/cli @commitlint/config-conventional -D
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"build",
"chore",
"ci",
"docs",
"feature",
"fix",
"perf",
"refactor",
"revert",
"style",
"test"
]
]
}
}
import { defineConfig } from "vite"
+import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
export default defineConfig({
+ resolve: {
+ alias: {
+ "@": resolve("src")
+ }
+ },
plugins: [vue()]
})
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"esModuleInterop": true,
"lib": ["esnext", "dom", "es2018.promise"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
src\App.vue
<script setup lang="ts">
+import HelloWorld from "@/components/HelloWorld.vue"
import msg from "./msg"
</script>
<template>
<HelloWorld :msg="msg.text" />
</template>
<style></style>
#app {
background-color: lightgrey;
}
src\main.ts
import { createApp } from "vue"
import App from "./App.vue"
+import "./global.css"
const app = createApp(App)
app.mount("#app")
style
标签有 scoped
属性时,它的 CSS
只作用于当前组件中的元素data-v-hash
的方式来使 css 有了它对应模块的标识src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
+ <a>超链接</a>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
+a {
+ color: red;
+}
</style>
module
作用的 style
都被保存到$style
对象中src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
+ <a :class="$style.link">超链接</a>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
+<style module>
+.link {
+ color: red;
+}
+</style>
.module.css
为后缀名的 CSS 文件都被认为是一个 CSS modules
文件src\components\HelloWorld.module.css
.link {
color: red;
}
src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
+import style from "./HelloWorld.module.css"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
+ <a :class="style.link">超链接</a>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
-<style module>
-.link {
- color: red;
-}
-</style>
Vite
为Sass
和Less
改进了@import
解析,以保证Vite
别名也能被使用pnpm install sass less -D
src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
import style from "./HelloWorld.module.css"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
<a :class="style.link">超链接</a>
<h2>less</h2>
<h3>sass</h3>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
<style module>
.link {
color: red;
}
</style>
+<style scoped lang="less">
+@color: red;
+h2 {
+ color: @color;
+}
+</style>
+<style scoped lang="scss">
+$color: green;
+h3 {
+ color: $color;
+}
+</style>
vite.config.ts
import { defineConfig } from "vite"
import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
export default defineConfig({
resolve: {
alias: {
"@": resolve("src")
}
},
plugins: [vue()],
+ css: {
+ preprocessorOptions: {
+ scss: {
+ additionalData: '@import "@/styles/theme.scss";'
+ },
+ less: {
+ additionalData: '@import "@/styles/theme.less";'
+ }
+ }
+ }
})
src\styles\theme.less
@color: red;
src\styles\theme.scss
$color: green;
src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
import style from "./HelloWorld.module.css"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
<a :class="style.link">超链接</a>
<h2>less</h2>
<h3>sass</h3>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
<style module>
.link {
color: red;
}
</style>
<style scoped lang="less">
-@color: red;
h2 {
color: @color;
}
</style>
<style scoped lang="scss">
-$color: green;
h3 {
color: $color;
}
</style>
pnpm install autoprefixer -D
postcss.config.js
module.exports = {
plugins: [require("autoprefixer")]
}
.browserslistrc
>0.2%
not dead
not op_mini all
src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
import style from "./HelloWorld.module.css"
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
<a :class="style.link">超链接</a>
<h2>less</h2>
<h3>sass</h3>
+ <div class="postcss"></div>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
<style module>
.link {
color: red;
}
</style>
<style scoped lang="less">
@color: red;
h2 {
color: @color;
}
</style>
<style scoped lang="scss">
$color: green;
h3 {
color: $color;
}
</style>
+<style scoped>
+.postcss {
+ height: 30px;
+ width: 60px;
+ background-color: orange;
+ transform: rotate(25deg);
+}
+</style>
src\components\HelloWorld.vue
<template>
+ <img src="@/assets/logo.png" style="width: 50px" />
</template>
src\components\HelloWorld.vue
<script setup lang="ts">
+import logoUrl from "@/assets/logo.png"
</script>
<template>
+ <img :src="logoUrl" style="width: 50px" />
</template>
src\components\HelloWorld.vue
<template>
+ <div class="logo"></div>
</template>
<style scoped>
.logo {
width: 50px;
height: 50px;
background-image: url(@/assets/logo.png);
background-size: contain;
}
</style>
index.html
<body>
<img src="/logo.png" style="width:50px" />
</body>
pnpm install mockjs vite-plugin-mock -D
vite.config.ts
import { defineConfig } from "vite"
import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
+import { viteMockServe } from "vite-plugin-mock"
export default defineConfig({
resolve: {
alias: {
"@": resolve("src")
}
},
+ plugins: [vue(), viteMockServe()],
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/theme.scss";'
},
less: {
additionalData: '@import "@/styles/theme.less";'
}
}
}
})
mock\auth.ts
import { MockMethod } from "vite-plugin-mock"
export default [
{
url: "/api/currentUser",
method: "get",
response: ({ query }) => {
return {
code: 0,
data: "zhufeng"
}
}
}
] as MockMethod[]
pnpm install axios
mock\auth.ts
import { MockMethod } from "vite-plugin-mock"
export default [
+ {
+ url: "/api/currentUser",
+ method: "get",
+ response: ({ headers }) => {
+ const { token } = headers
+ return {
+ code: 0,
+ data: token
+ }
+ }
+ },
+ {
+ url: "/api/login",
+ method: "post",
+ response: ({ body }) => {
+ //url body,query headers
+ return {
+ code: 0,
+ data: `${body.username}-token`
+ }
+ }
+ }
] as MockMethod[]
src\api\http.ts
import axios, { AxiosRequestConfig } from "axios"
axios.defaults.baseURL = "/api"
axios.defaults.timeout = 10000
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8"
axios.interceptors.request.use(
(config): AxiosRequestConfig => {
const token = window.sessionStorage.getItem("token")
if (token) {
;(config.headers = config.headers || {}).token = token
}
return config
},
(error) => {
throw error
}
)
// 响应拦截
axios.interceptors.response.use((res) => {
return res.data
})
export default axios
src\api\auth.ts
import http from "./http"
import { LoginParams } from "@/typings/auth"
export function login(loginParams: LoginParams) {
return http.post("/login", loginParams)
}
export function getCurrentUser() {
return http.get("/currentUser")
}
src\typings\auth.ts
export interface LoginParams {
username: string
password: string
}
export interface Response {
code: number
data: any
}
src\request.ts
import { LoginParams, Response } from "@/typings/auth"
import { login, getCurrentUser } from "@/api/auth"
const loginParams: LoginParams = { username: "zhufeng", password: "123456" }
login(loginParams).then((result) => {
const token = result.data
window.sessionStorage.setItem("token", token)
getCurrentUser().then((result) => {
console.log(result.data)
})
})
src\main.ts
import { createApp } from "vue"
import App from "./App.vue"
import "./global.css"
+import "@/request"
const app = createApp(App)
app.mount("#app")
pnpm install pinia
src\main.ts
import { createApp } from "vue"
import App from "./App.vue"
import "./global.css"
import "@/request"
+import { createPinia } from "pinia"
const app = createApp(App)
+app.use(createPinia())
app.mount("#app")
src\store\index.ts
import { defineStore } from "pinia"
export const useMainStore = defineStore({
id: "main",
state: () => ({
count: 0,
firstName: "张",
lastName: "三"
}),
//计算属性
getters: {
name: (state) => state.firstName + state.lastName
},
actions: {
async addAmount(amount: number) {
this.count += amount
}
}
})
src\components\HelloWorld.vue
<script setup lang="ts">
import { ref } from "vue"
import logoUrl from "../assets/logo.png"
import style from "./HelloWorld.module.css"
+import { useMainStore } from "@/store"
+import { storeToRefs } from "pinia"
+defineProps<{ msg: string }>()
+const count = ref(0)
+const mainStore = useMainStore()
+//const storeCount = mainStore.count
+const { count: storeCount } = storeToRefs(mainStore)
+const add = () => {
+ //适合多字段改变
+ mainStore.$patch({
+ name: "arch",
+ count: mainStore.count + 1
+ })
+}
+const add2 = () => {
+ //适合多字段改变
+ mainStore.$patch((state) => ({
+ name: "arch2",
+ count: mainStore.count + 2
+ }))
+}
</script>
<template>
<h1>{{ msg }}</h1>
<img :src="logoUrl" />
<div>
<button type="button" @click="count++">count is: {{ count }}</button>
</div>
<a :class="style.link">超链接</a>
<h2>less</h2>
<h3>sass</h3>
<div class="postcss"></div>
+ <div>
+ <p>name:{{ mainStore.name }}</p>
+ <p>count:{{ mainStore.count }}</p>
+ <p>count:{{ storeCount }}</p>
+ <button @click="mainStore.count++">mainStore.count++</button>
+ <button @click="add()">add()</button>
+ <button @click="add2()">add2()</button>
+ <button @click="mainStore.addAmount(3)">mainStore.addAmount(3)</button>
+ <p>name:{{ mainStore.name }}</p>
+ </div>
</template>
<style scoped>
img {
width: 100px;
height: 100px;
}
</style>
<style module>
.link {
color: red;
}
</style>
<style scoped lang="less">
@color: red;
h2 {
color: @color;
}
</style>
<style scoped lang="scss">
$color: green;
h3 {
color: $color;
}
</style>
<style scoped>
.postcss {
height: 30px;
width: 60px;
background-color: orange;
transform: rotate(25deg);
}
</style>
pnpm install vue-router
src\router\index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "home",
component: () => import("@/pages/HomePage.vue")
},
{
path: "/login",
name: "login",
component: () => import("@/pages/LoginPage.vue")
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
src\pages\HomePage.vue
<template>
<h1>Home</h1>
</template>
src\pages\LoginPage.vue
<template>
<form>
用户名 <input />
</form>
</template>
src\main.ts
import { createApp } from "vue"
import App from "./App.vue"
import "./global.css"
import "@/request"
import { createPinia } from "pinia"
+import router from "./router"
const app = createApp(App)
app.use(createPinia())
+app.use(router)
app.mount("#app")
src\App.vue
<script setup lang="ts">
import HelloWorld from "@/components/HelloWorld.vue"
import msg from "./msg"
</script>
<template>
+ <router-view></router-view>
</template>
<style></style>
pnpm install naive-ui vfonts @vicons/ionicons5
<script setup lang="ts">
import HelloWorld from "@/components/HelloWorld.vue"
import msg from "./msg"
+import { h, Component } from "vue"
+import { NIcon, NMenu } from "naive-ui"
+import type { MenuOption } from "naive-ui"
+import { RouterLink } from "vue-router"
+import { HomeOutline, LogInOutline } from "@vicons/ionicons5"
+const menuOptions: MenuOption[] = [
+ {
+ label: () =>
+ h(RouterLink, { to: { name: "home" } }, { default: () => "首页" }),
+ key: "home",
+ icon: () => h(NIcon, null, { default: () => h(HomeOutline) }) //带有插槽的对象
+ },
+ {
+ label: () =>
+ h(RouterLink, { to: { name: "login" } }, { default: () => "登录" }),
+ key: "login",
+ icon: () => h(NIcon, null, { default: () => h(LogInOutline) })
+ }
+]
</script>
<template>
+ <n-menu :options="menuOptions" />
<router-view></router-view>
</template>
<style></style>
VITE_APP_WEB_URL = "/dev"
VITE_APP_WEB_URL = "/prod"
+console.log(import.meta.env)
package.json
{
"scripts": {
+ "build:dev": "vue-tsc --noEmit && vite build --mode development",
+ "build:prod": "vue-tsc --noEmit && vite build --mode production",
},
}