# Naive UI 完全指南 **Repository Path**: adminfern/naive-ui--the-ultimate-guide ## Basic Information - **Project Name**: Naive UI 完全指南 - **Description**: **适合人群:** 本书专为零基础前端新手打造,只要你会一点点 HTML/CSS/JavaScript,就能跟着一步步学会 Naive UI。 **版本:** v3.0(企业实战扩展版 · 含天气查询完整项目) **适用 Naive UI 版本:** 2.x(Vue 3 + TypeScript) **阅读建议:** 按篇章顺序阅读,每章末尾的「本章踩坑小结」和「本章回顾」一定要看! - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-18 - **Last Updated**: 2026-07-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Naive UI 完全指南——从零基础到企业级实战 > **适合人群:** 本书专为零基础前端新手打造,只要你会一点点 HTML/CSS/JavaScript,就能跟着一步步学会 Naive UI。 > **版本:** v3.0(企业实战扩展版 · 含天气查询完整项目) > **适用 Naive UI 版本:** 2.x(Vue 3 + TypeScript) > **阅读建议:** 按篇章顺序阅读,每章末尾的「本章踩坑小结」和「本章回顾」一定要看! --- # 第一篇章 基础认知与环境搭建(零基础入门) > **🎯 本篇你将学会:** > - 理解什么是 UI 组件库,Naive UI 相比 Element Plus 有什么独特优势 > - 从零搭建一个 Vue 3 + Naive UI 的开发环境 > - 掌握三种引入方式的区别和适用场景 > - 配置全局主题、语言和组件尺寸 > - 使用图标系统和字体 ## 第1章 走进 Naive UI > **📖 学习目标:** 理解什么是 UI 组件库、Naive UI 跟别的组件库有什么不同、什么场景适合用它。 ### 1.1 先说人话:什么是"UI 组件库"? 如果你是新手,听到"UI 组件库"这个词可能会觉得很高大上。其实用大白话讲就是: > 一堆**已经写好了样式的界面"积木"**,你只需要拿过来拼一拼,就能快速搭出一个好看的后台管理系统。 举个例子:你想在网页上加一个按钮。如果手写,你需要写 HTML、写 CSS 样式、处理点击效果、处理禁用状态……但用了 Naive UI,你只需要: ```vue 点我一下 ``` 就这么一行,一个漂亮的蓝色按钮就出来了,自带悬浮效果、点击动画、禁用状态,什么样式都不用你管。 > 💡 **新手理解:** 组件 = 一块可复用的"积木"。按钮是一个组件,输入框是一个组件,弹窗也是一个组件。把这些组件拼起来,就是一个完整的页面。 ### 1.2 Naive UI 是什么?为什么选它? **Naive UI** 是目前国内非常流行的一个 Vue 3 组件库。"Naive"这个名字不是"幼稚"的意思,作者的原意是"朴素、纯粹"——用最少的心智负担,写出最好的界面。 > 🔥 **凭什么选它而不是 Element Plus?** > > | 你关心的点 | Naive UI | Element Plus | > |-----------|----------|-------------| > | 主题定制(公司要求换品牌色) | ⭐⭐⭐⭐⭐ 非常灵活 | ⭐⭐⭐ 一般 | > | 暗黑模式(需要一键切换) | ✅ **内置支持**,一行代码搞定 | ❌ 需要自己配 | > | TypeScript 提示(代码补全舒不舒服) | ⭐⭐⭐⭐⭐ 行业天花板 | ⭐⭐⭐⭐ 不错 | > | 组件数量(功能够不够用) | 90+ 个 | 70+ 个 | > | 学习难度(新手好不好上手) | 平缓 | 平缓 | > ⚠️ **新手提醒:** Naive UI **只支持 Vue 3**。如果你还在学 Vue 2(老教程),那 Naive UI 用不了。现在学 Vue 就从 Vue 3 开始,别学 Vue 2 了,那是过去的版本。 ### 1.3 哪些项目适合用 Naive UI? - 📊 **后台管理系统**(最常见的场景)——公司内部的员工管理、订单管理、数据报表 - 📈 **数据分析平台**——Naive UI 的表格组件性能很强,支持 10 万行数据不卡顿 - 🏢 **SaaS 系统**——租户管理、权限配置、账单系统 - 🎨 **需要定制品牌色的项目**——主题系统是 Naive UI 的核心卖点 - 🔧 **企业内部工具**——快速搭建,上线即用 - 🌤️ **内容展示型网站**(博客、天气查询工具等)——本书第42-44章的天气项目就是最好的例子 ### 1.4 Naive UI 的"家族成员" ```bash # 你需要装的几个包(别慌,后面会手把手教你装): naive-ui → 核心组件库(按钮、表格、弹窗……) @vicons/ionicons5 → 图标包(600+ 个图标随便用) vfonts → 官方推荐字体(让页面更好看) ``` > 💡 **打个比方:** `naive-ui` 是乐高积木的主体零件包,`@vicons/ionicons5` 是装饰零件包,`vfonts` 是贴纸。三个配合使用才完整。 > 📝 **本章回顾:** Naive UI 是一个基于 Vue 3 的现代化组件库,最大的优势是主题定制灵活、暗黑模式内置、TypeScript 支持极好。适合中后台管理系统、数据分析平台等各种企业级应用。 --- ## 第2章 开发环境搭建与项目初始化 > **📖 学习目标:** 从零开始创建一个 Vue 3 + TypeScript 项目,安装 Naive UI,并且在浏览器中看到第一个组件。 ### 2.1 你需要先装好这些东西 | 工具 | 需要什么版本 | 怎么检查有没有装 | 去哪里下载 | |------|-------------|----------------|-----------| | **Node.js** | >= 16.x(推荐 18.x) | 打开终端输入 `node -v` | https://nodejs.org(选 LTS) | | **npm** 或 **pnpm** | npm 随 Node 自带,pnpm 推荐单独装 | `npm -v` 或 `pnpm -v` | `npm i -g pnpm` | | **VSCode** | 最新版 | 打开看一眼 | https://code.visualstudio.com | > ⚠️ **新手最常踩的坑:** Node.js 版本太低! > 很多新手电脑上可能装过很老的 Node.js(比如 12.x 或 14.x)。**Naive UI + Vite 5 要求 Node.js >= 16**。如果你不确定版本,先在终端执行 `node -v` 检查一下。如果不是 16 以上,去 Node.js 官网下载 LTS 版覆盖安装。 > 💡 **为什么推荐 pnpm?** 因为它比 npm 快很多(硬盘空间占用少),而且是 Vue 官方推荐的包管理器。如果你不想折腾,用 npm 也可以,只是安装依赖会慢一点。 ### 2.2 创建项目(跟着敲,别跳步) 打开终端(Windows 用户推荐用 **PowerShell** 或 VSCode 自带的终端),执行: ```bash # 第1步:用 Vite 创建 Vue 3 项目 # 这条命令会下载一个脚手架模板,帮你搭好基础目录结构 pnpm create vue@latest my-admin-app ``` 执行后终端会问你好几个问题。**新手建议全部选 Yes**(除了 Testing 那几项可以选 No): ```bash ✔ Project name: … my-admin-app # 项目名,直接回车就行 ✔ TypeScript? … Yes # 选 Yes!(Naive UI 的 TypeScript 支持是亮点) ✔ JSX Support? … No # 新手暂时用不到,选 No ✔ Vue Router? … Yes # 路由:页面跳转必备,选 Yes ✔ Pinia? … Yes # 状态管理:以后会用到,选 Yes ✔ Vitest? … No # 测试工具:新手暂时不用,选 No ✔ ESLint? … Yes # 代码检查:帮你发现低级错误,选 Yes ✔ Prettier? … Yes # 代码格式化:让代码更好看,选 Yes ``` > 💡 **Vite 是什么?** 你可以把它理解成一个"开发服务器",它比老旧的 Webpack 快很多——改一行代码,浏览器几乎瞬间就能看到效果。Vue 官方现在强力推荐用 Vite 创建项目。 ```bash # 第2步:进入项目目录 cd my-admin-app # 第3步:安装项目的基础依赖 # 这一步会下载 Vue、Vue Router、Pinia 等基础库 pnpm install ``` ### 2.3 安装 Naive UI 和它的朋友们 ```bash # 安装核心库(所有组件都在里面) pnpm add naive-ui # 安装图标库(按钮上的小图标全靠它) # IonIcons 5 是官方最推荐的图标集,样式最统一 pnpm add @vicons/ionicons5 # 安装推荐字体(让中英文混排更好看) pnpm add vfonts ``` > ⚠️ **注意:** 有些新手教程会让你用 `npm install`,效果是一样的。选你喜欢的就行。如果下载慢,可以配一下淘宝镜像: > ```bash > npm config set registry https://registry.npmmirror.com > ``` ### 2.4 验证一下:写出第一个 Naive UI 页面 先把 `src/App.vue` 的**全部内容替换**成下面这段代码: ```vue {{ message }} 点我试试 ``` 保存文件后,启动开发服务器: ```bash # 在项目目录下执行 pnpm dev # 终端会显示类似:http://localhost:5173/ # 用浏览器打开这个地址 ``` > 💡 如果浏览器里看到了一个绿色标签和一个蓝色按钮,**恭喜你!** Naive UI 已经成功装好了。如果没看到,跳到第 38 章看看有没有对应的解决方案。 ### 2.5 VSCode 配置(让编辑器更好用) 在项目根目录创建 `.vscode/settings.json`: ```json { // 保存文件时自动格式化代码,不用手动调整缩进 "editor.formatOnSave": true, // Vue 文件的代码片段优先用 kebab-case(也就是 n-button 而不是 NButton) "volar.completion.preferredTagNameCase": "kebab", "volar.completion.preferredAttrNameCase": "kebab" } ``` > 💡 **为什么要加这些配置?** `volar` 是 Vue 3 的官方 VSCode 插件(以前叫 Vetur,现在改名了)。装了它之后,你在模板里输入 ` 📝 **本章回顾:** 创建 Vue 3 项目 → 安装 naive-ui / @vicons/ionicons5 / vfonts → 写第一个测试页面 → 启动 dev server 看到效果。注意 Node.js >= 16,推荐用 pnpm。 --- ## 第3章 Naive UI 三种引入方式深度解析 > **📖 学习目标:** 搞清楚"全量引入""自动按需""手动按需"的区别,知道项目里该用哪一种。 > 💡 **先理解一个概念:打包体积。** 你写的代码最终会被"打包"成浏览器能运行的文件。如果你把整个 Naive UI 都打包进去(包括你不用的组件),那用户打开网页就要下载一个很大的文件——网页打开就会变慢。**按需引入**的意思就是:你用哪个组件,就只打包哪个,没用到的统统不打包。 ### 3.1 方式一:完整引入(新手练手推荐,但正式项目不要用) ```ts // src/main.ts import { createApp } from 'vue' // 把整个 Naive UI 一次性全部导入 import naiveUI from 'naive-ui' import App from './App.vue' const app = createApp(App) // 把 Naive UI 全部注册到 Vue 中 app.use(naiveUI) app.mount('#app') ``` > ✅ **优点:** 最简单,写模板直接写组件名,不用 import。 > ❌ **缺点:** 打包体积最大,用户打开网页要多下载约 200KB 的代码。 > 📌 **建议:** 只在你"随便玩玩"或者"写一个 demo 给同事看"的时候用这种方式。正式上线项目**不要用**。 ### 3.2 方式二:自动按需引入(最省事,中小项目推荐) 这种方式借助两个 Vite 插件,让你**在模板里直接写 ``,插件自动帮你把 NButton 组件导入进来**,同时保证只打包你用到的组件。 **第1步:安装插件** ```bash pnpm add -D unplugin-auto-import unplugin-vue-components # -D 的意思是装在开发依赖里(devDependencies),最终上线不会打包进去 ``` **第2步:配置 `vite.config.ts`** ```ts // vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), AutoImport({ imports: ['vue', 'vue-router', 'pinia'], dts: 'src/auto-imports.d.ts' }), Components({ resolvers: [NaiveUiResolver()], dts: 'src/components.d.ts' }) ] }) ``` > ⚠️ **新手最容易忘的事:改完 `vite.config.ts` 后要重启开发服务器!** > Vite 配置文件改了之后不会热更新,你需要 `Ctrl+C` 停掉服务器,再重新 `pnpm dev`。 **第3步:直接在模板中使用——不用 import!** ```vue 自动按需引入 ``` ### 3.3 方式三:手动按需引入(大项目推荐,代码依赖最清晰) 这是**本书首推**的方式。每用到一个组件,就在 ` 聚焦到输入框 ``` ### 3.4 三种方式对比(一图胜千言) | 引入方式 | 上手难度 | 打包体积 | 代码清晰度 | 适合什么场景 | |---------|---------|---------|----------|------------| | **全量引入** | ⭐ 最简单 | ❌ 最大 | 一般 | 快速原型、Demo | | **自动按需** | ⭐⭐ 中等 | ✅ 最小 | 较好 | 中小型正式项目 | | **手动按需** | ⭐⭐⭐ 多写几行 import | ✅ 最小 | ✅ 最清晰 | 中大型企业项目 | > 📌 **本书建议:** 学习阶段用全量引入(省事),公司项目用手动按需(清晰)。 > ⚠️ **本章踩坑小结:** > 1. 自动按需改了 `vite.config.ts` 一定要**重启 dev server**! > 2. 手动按需时,别忘了还要 import 对应的 **类型**(`InputInst` 等),否则 TypeScript 会报类型错误。 > 3. 不管哪种方式,**` ``` > ⚠️ **重要理解:** NConfigProvider 本身不会在当前页面产生任何可见的 DOM 元素(如果你看到多了一层 div,可以加上 `abstract` 属性消除)。它只是提供一个"配置上下文"。 ### 4.2 全局换肤:改一次,所有地方都变 ```vue 绿色按钮 这个卡片使用了 8px 的圆角 ``` > 💡 **技巧:怎么找到某个组件有哪些变量可以改?** > 在 VSCode 中,把光标放到 `themeOverrides` 的类型 `GlobalThemeOverrides` 上,按 `F12`(转到定义),就能看到所有组件的变量列表。或者参考本书**附录B**。 ### 4.3 切换语言:一行代码国际化 ```vue 中文 English 确认 ``` ### 4.4 全局组件尺寸:一次设置,所有组件统一大小 ```vue 默认 medium 按钮 单独设为 large ``` > ⚠️ **本章踩坑小结:** > 1. **NConfigProvider 一定要包在最外层**,否则里面的组件拿不到主题配置。 > 2. 改了 `theme-overrides` 后发现**某些属性不生效**?——检查 CSS 变量名有没有拼写错误(附录B查)。 > 3. 多个 NConfigProvider **嵌套**时,里层的会覆盖外层的——这是个特性不是 bug,但新手容易搞晕。 > 📝 **本章回顾:** NConfigProvider 是 Naive UI 所有组件接收主题/语言/尺寸配置的唯一通道。主题定制通过 `theme-overrides` 对象实现,支持 common(全局)和组件级(Button、Card 等)两种粒度。 --- ## 第5章 图标系统与字体全局配置 > **📖 学习目标:** 学会在按钮、菜单里加图标,理解 Naive UI 图标的工作方式,配置中英文字体。 ### 5.1 图标怎么用?先装后导 Naive UI 的图标是**独立**的包,不会随组件库自动安装。你需要单独装一个图标库。 ```bash # 三选一,推荐第一个(IonIcons 5,风格最统一) pnpm add @vicons/ionicons5 # IonIcons(iOS 风格,推荐) pnpm add @vicons/fa # Font Awesome(Web 经典风格) pnpm add @vicons/material # Material Design(Google 风格) ``` > 💡 **为什么图标要单独装?** 因为不同项目可能喜欢不同风格的图标,Naive UI 不希望替你做选择。你自己决定用哪个,然后只装你需要的——这样打包体积更小。 ### 5.2 n-icon:图标的"容器" Naive UI 用 `n-icon` 组件包裹图标,统一控制尺寸、颜色、深度。 ```vue ``` > 💡 **depth 是什么?** 它是一个快速调节图标颜色的参数。你不必手动调色,只需要给 depth 一个 1-5 的数字。它实际上是基于文字颜色做的透明度计算——depth 越大越透明。 ### 5.3 实战:在按钮、菜单中加图标 ```vue 搜索 ``` > ⚠️ **新手容易犯的错:** `#icon` 插槽里要放 ``,**不要**直接把图标组件放进去。`` 是图标的容器,统一管理大小和颜色。把图标裸放在 `#icon` 里可能会导致对齐问题。 ### 5.4 字体配置(vfonts) ```bash pnpm add vfonts ``` ```ts // src/main.ts import 'vfonts/Lato.css' import 'vfonts/FiraCode.css' ``` ```vue ``` > 💡 **为什么要区分 fontFamily 和 fontFamilyMono?** > 正文(中文)用"非等宽"字体,每个汉字宽度一样,但英文 i 和 W 宽度不同——这样读起来自然。 > 代码用"等宽"字体,每个字母(包括空格)宽度一模一样——这样代码才能对齐,缩进不会乱。 ### 5.5 进阶:自定义 SVG 图标 当图标库里没有你想要的图标时,可以自己写 SVG(也可以从 iconfont.cn 下载): ```vue ``` > ⚠️ **本章踩坑小结:** > 1. 图标库没有默认装!一定要 `pnpm add @vicons/ionicons5`。 > 2. 图标一定要用 `n-icon` 包裹,不要裸放,否则颜色/大小不受控制。 > 3. 如果你发现图标不显示——检查 import 路径是否正确。IonIcons 5 的图标都在 `@vicons/ionicons5` 下导出,名字格式是 `XxxOutline`(空心)、`Xxx`(实心)、`XxxSharp`(尖锐)。 > 📝 **本章回顾:** 图标系统通过 `@vicons/*` 独立包 + `n-icon` 容器实现。推荐 IonIcons 5 图标集。字体用 `vfonts` 包,区分正文和代码两种字体。 --- # 第二篇章 通用基础组件精讲(页面基石) > **🎯 本篇你将学会:** > - NButton 的所有类型、尺寸、变体和状态 > - NSpace 间距布局和 NText 文本组件 > - NTag / NBadge / NDivider / NPopover 辅助组件 > - NCard 卡片容器和 NModal 弹窗的两种用法 ## 第6章 按钮、间距、文本类基础组件 > **📖 学习目标:** 掌握 NButton 的所有类型和状态,学会用 NSpace 排版,用 NText 显示文字。 ### 6.1 NButton——每天点击几十次的按钮 NButton 的属性非常多,但只要记住几个**最常用的组合**就够了。 ```vue default 默认 primary 主要操作 info 提示 success 成功 warning 警告 error 危险 超小 小 中(默认) 大 默认(实心背景) tertiary(半透明) quaternary(几乎透明) text(纯文字) {{ loading ? '保存中...' : '保存' }} 禁用状态 幽灵按钮 虚线按钮 块级按钮(宽度 100%) 左 中 右 ``` > 💡 **什么时候用哪个 type?** > - `primary`:页面最主要的操作(保存、提交、确认) > - `error`:危险操作(删除、清空、注销) > - `success`:正向操作(通过、启用、完成) > - `warning`:需要谨慎的操作(退回、暂停) > - `info`:中性提示(详情、帮助) > ⚠️ **注意:** `loading` 为 `true` 时按钮自动变为不可点击。你不需要额外设置 `disabled`。 ### 6.2 NSpace——新手最容易忽视的布局利器 > 💡 NSpace 的作用非常简单:**给一组元素之间自动加间距**。 ```vue 按钮1 按钮2 按钮3 第一行 第二行 搜索条件区域 搜索 标签{{ i }} ``` ### 6.3 NText——不仅仅是文字 ```vue 信息文字 成功文字 警告文字 错误文字 加粗 斜体 下划线 删除线 code 等宽样式 Ctrl + C 最深 默认 depth=3 ``` > ⚠️ **本章踩坑小结:** > 1. NButton 的 `loading` 和 `disabled` 是两个独立属性。loading 时会自动禁用点击,但事件绑定还在。 > 2. NSpace 的间距对**直接子元素**生效。如果你子元素外面又包了一层 div,那 div 才是 NSpace 的子元素。 > 3. `n-button-group` 里的按钮**不要加 `round`**,否则拼接处会断开。 > 📝 **本章回顾:** NButton 有 6 种颜色类型、4 种尺寸、4 种变体,loading 和 disabled 是两个常用状态。NSpace 是一维间距工具,NText 可控制文字的颜色/粗细/修饰。 --- ## 第7章 标签、徽标、提示、分割线辅助组件 > **📖 学习目标:** 掌握 NTag(标签)、NBadge(角标)、NDivider(分割线)、NPopover(气泡提示)的使用。 ### 7.1 NTag——状态标签首选 ```vue 默认 主要 信息 成功 警告 错误 待处理 已完成 已拒绝 {{ tag.label }} 审核通过 ``` > 💡 **`bordered` 参数:** `true`(默认)是浅色底 + 深色文字,适合表格里用。`false` 是深色底 + 白色文字,视觉冲击力强,适合仪表盘。 ### 7.2 NBadge——角标/红点 ```vue 消息 通知 更新日志 ``` ### 7.3 NDivider——分割线 ```vue 文字居中 文字靠左 用户管理 132 人在线 ``` ### 7.4 NPopover——悬浮/点击弹出的气泡 ```vue 悬浮我 VIP 会员可以享受 8 折优惠和多倍积分。 点击我 操作1 操作2 ``` > ⚠️ **本章踩坑小结:** > 1. NTag 的 `closable` 触发的是 `@close` 事件,**不是** `@click`。 > 2. NBadge 的 `value` 为 0 时默认不显示。想显示 0,加 `show-zero`。 > 3. NPopover 的 `trigger` 默认是 `hover`,但如果内容有按钮想点击,要换成 `click`,否则菜单在鼠标移开后就消失了。 > 📝 **本章回顾:** NTag 用于状态展示,NBadge 用于角标提醒,NDivider 用于视觉分割,NPopover 用于悬浮/点击气泡提示。 --- ## 第8章 卡片、气泡提示、弹窗基础容器 > **📖 学习目标:** 学会使用 NCard 组织内容块,NModal 弹出对话框,掌握容器类组件的基本思路。 ### 8.1 NCard——页面内容的基本容器 > 💡 NCard 几乎是每个后台管理系统页面的**最小内容单元**。表单放卡片里、表格放卡片里、统计数字也放卡片里。 ```vue 这是卡片的主体内容。 VIP会员 姓名:张三 年龄:28岁 取消 保存 1,280 较昨日 +12.5% ``` ### 8.2 NModal——弹窗/对话框 > ⚠️ **NModal 有两种用法,非常容易搞混!** > **方式 A:** `preset="dialog"` —— 最快速,Naive UI 帮你把标题、内容、按钮都排好了。适合简单的确认/提示。 > **方式 B:** 自定义内容(不用 preset)—— 你完全控制弹窗内部结构。适合表单弹窗、详情弹窗。 ```vue 打开 dialog 弹窗 打开表单弹窗 取消 保存 ``` > ⚠️ **超级重要的踩坑提醒:** > 1. NModal 弹出后,**背景页面会自动禁用滚动**——这是个好设计,但如果你的弹窗内容也需要滚动,要给弹窗内的容器设固定高度 + `overflow: auto`。 > 2. `preset="dialog"` 时,`positive-click` 和 `negative-click` **不会自动关闭弹窗**,你需要手动把 `show` 设为 `false`。 > 3. 如果你同时弹了多个 Modal,**层级(z-index)可能会错乱**——Naive UI 内部会自动处理,但如果你发现后面的弹窗盖不住前面的,检查是否有全局 z-index 污染。 > 📝 **本章回顾:** NCard 是最常用的容器组件,NModal 有两种用法(preset 和自定义)。弹窗关闭需要手动控制 show 的值,这是新手最容易忘记的地方。 --- # 第三篇章 表单组件全解(B端核心) > **🎯 本篇你将学会:** > - 所有基础输入组件(文本框、密码框、文本域、数字框) > - 四种选择组件(单选、复选、下拉、级联)的区别和选择 > - 日期时间组件和格式化 > - 文件上传的完整处理流程 > - NForm 表单系统的校验体系(必填、格式、异步、条件) > - 动态表单和联动表单的实现 ## 第9章 基础输入类组件使用详解 > **📖 学习目标:** 掌握 NInput、NInputNumber 的基本用法,理解 v-model:value 的工作原理。 ### 9.1 NInput——输入框全家桶 ```vue ¥ 元 ``` > 💡 **v-model:value 怎么理解?** 它是 Vue 3 的"双向绑定"语法糖。相当于同时做了两件事: > 1. 把 `text` 的值作为输入框的初始值传给组件(`:value="text"`) > 2. 监听组件内部的更新事件,同步回 `text`(`@update:value="text = $event"`) ### 9.2 实时输入校验——手机号校验示例 ```vue {{ phoneError }} ``` > ⚠️ **本章踩坑小结:** > 1. `type="textarea"` 时 `prefix`/`suffix` 插槽可能不显示(文本域默认不支持)。 > 2. `n-input-number` 的 `v-model:value` 绑定的是 **number 类型**,初始值要设成 `0` 而不是 `'0'`。 > 3. `show-password-on` 默认值是 `mousedown`。如果点一下就松开导致看不到,改成 `click`。 > 📝 **本章回顾:** NInput 支持文本框、密码框、文本域三种形态。v-model:value 实现双向绑定。实时校验用 @input 事件,配合 status 属性显示校验状态。 --- ## 第10章 选择类组件:单选、复选、下拉、级联选择 > **📖 学习目标:** 掌握四种选择组件,知道什么场景用哪个。 ### 10.1 NRadio / NRadioGroup——单选 ```vue 男 女 ``` > 💡 **用 n-radio 还是 n-radio-button?** 表单里面(比如性别选择)用 n-radio;页面顶部筛选栏(比如按日期/状态筛选)用 n-radio-button。后者视觉更强,暗示"切换"而非"填写"。 ### 10.2 NCheckbox / NCheckboxGroup——复选 ```vue 我同意用户协议 {{ opt.label }} 全选 ``` > 💡 **indeterminate(半选态)怎么理解:** 当你只勾了"阅读"和"运动"时,"全选"复选框不是空的,也不是打勾的,而是显示**一条横线**——这叫半选态。 ### 10.3 NSelect——下拉选择器 这是**使用频率最高**的选择组件。 ```vue ``` > ⚠️ **选择器常见坑:** > 1. `options` 里的 `label` 是给用户看的,`value` 是给程序用的。不要搞混。 > 2. 多选模式下,`v-model:value` 绑定的是**数组**(`string[]`),不是字符串。 > 3. 远程搜索时 `filterable` 必须为 `true`,`remote` 必须为 `true`,然后监听 `@search`——三个条件缺一不可。 ### 10.4 NCascader——级联选择器 ```vue ``` > ⚠️ **本章踩坑小结:** > 1. NRadioGroup 绑定的变量类型取决于 option 的 `value` 类型。 > 2. NCheckboxGroup 绑定的永远是**数组**,即使只有一个被选中也是 `['reading']`。 > 3. NCascader 的 `value` 是**路径数组**(如 `['zhejiang','hangzhou']`),不是单个值。 > 📝 **本章回顾:** 四种选择组件各有适用场景:NRadio 用在小选项数,NCheckbox 用于多选,NSelect 是最灵活的下拉,NCascader 用于层级数据。远程搜索需同时开启 filterable + remote + @search。 --- ## 第11章 时间日期类组件全方位使用 > **📖 学习目标:** 掌握 NDatePicker 和 NTimePicker 的基本用法,知道怎么限制可选日期范围。 ### 11.1 NDatePicker——日期选择器 ```vue 选择日期 选择日期范围 选择日期和时间 选择月份 选择年份 ``` ### 11.2 限制可选范围 + 快捷选项 ```vue ``` > ⚠️ **日期组件最容易踩的坑:** > 1. `v-model:value` 绑定的值是**数字类型的时间戳**(如 `1716086400000`),不是字符串。需要显示字符串时用 `value-format` 或 dayjs 库。 > 2. `is-date-disabled` 的 `ts` 是每天 00:00:00 的时间戳,返回 `true` 表示禁用。 > 3. 日期范围选择器 vs 单个日期选择器:`v-model:value` 的类型不同(数组 vs 数字)。 > 📝 **本章回顾:** NDatePicker 支持 date/daterange/datetime/month/year 五种类型。value 是数字时间戳。限制日期用 is-date-disabled,快捷选项用 shortcuts。 --- ## 第12章 文件上传组件全场景实战 > **📖 学习目标:** 学会使用 NUpload 上传文件,处理文件大小/类型限制、多文件上传、头像上传。 ### 12.1 NUpload 基础拖拽上传 ```vue 点击或拖拽文件到此区域上传 支持 JPG/PNG/GIF/PDF,最大 5MB ``` ### 12.2 头像上传(单文件+本地预览) ```vue ``` > ⚠️ **上传组件踩坑提醒:** > 1. `beforeUpload` 的返回值是 `boolean`。返回 `false` 阻止上传,返回 `true` 继续。别忘了 return! > 2. 如果你用了 `custom-request`,**`action` 属性就不生效了**——所有逻辑走 `custom-request`。 > 3. 头像上传的 `:max="1"` 表示**最多保留 1 个文件**。选了新的,旧的会自动移除。 > 📝 **本章回顾:** NUpload 通过 custom-request 自定义上传逻辑,beforeUpload 做前置检查(大小/类型)。头像上传场景用 max=1 + FileReader 实现本地预览。 --- ## 第13章 Form 表单系统与高阶校验 > **📖 学习目标:** 掌握 NForm 的完整用法——数据绑定、校验规则、提交处理。 ### 13.1 NForm 基础三要素 > 💡 一个完整的 NForm 由三个东西组成: > 1. **`model`**(表单数据对象)——存所有字段的值 > 2. **`rules`**(校验规则)——告诉程序每个字段怎么校验 > 3. **NFormItem**(表单项)——每个输入字段,通过 `path` 关联到 `model` 中的属性 ```vue 提交 重置 ``` ### 13.2 异步校验——用户名是否已被注册 ```vue ``` > 💡 **validator 的三条返回规则:** > - 返回 `Promise.resolve()` 或不返回 → 校验通过 > - 返回 `new Error('xxx')` 或 `Promise.reject()` → 校验失败,显示 Error 的 message > - 返回 `false` → 校验失败,显示默认错误信息 > ⚠️ **本章踩坑小结:** > 1. `rules` 里每个字段的 `key`(如 `username`)必须和 `model` 的字段名**完全一致**,不一致校验不触发。 > 2. `trigger` 决定什么时候触发校验。`'blur'` = 失去焦点;`'change'` = 值变化 + 失去焦点。 > 3. `formRef.value?.validate()` 是**手动触发**全表单校验。它不会自动在提交时执行——你需要自己在 submit 函数里调用它。 > 📝 **本章回顾:** NForm 的三要素是 model、rules、NFormItem。rules 支持 required/min/max/pattern/email 等内置规则,也支持 async validator 自定义异步校验。提交时手动调用 validate()。 --- ## 第14章 动态表单、联动表单、复杂表单实战 > **📖 学习目标:** 学会动态增减表单项、实现字段联动、处理条件必填。 ### 14.1 动态增减表单项——类似简历中的"教育经历" ```vue 教育经历 新增 删除此条 ``` > ⚠️ **动态表单最容易踩的坑:** > 1. `v-for` 的 `key` 必须用**不会变的唯一标识**(如自增 id),**禁止用 index**。因为删除中间一条后 index 会变,导致 Vue 渲染出 bug。 > 2. `path` 里的数组索引语法是 `education[0].school`,中间没有空格。 ### 14.2 联动表单——选择了"有手机"才出现手机号输入框 ```vue 仅邮箱联系 同时提供手机号 ``` > 📝 **本章回顾:** 动态表单用 `v-for` + 唯一 id 实现增删。联动表单用 `computed` 动态生成 rules + `v-if` 控制显示/隐藏。 --- # 第四篇章 布局与容器组件精讲 > **🎯 本篇你将学会:** > - NGrid + NGi 栅格系统和响应式断点 > - NLayout 系列构建标准后台管理布局(侧边栏 + 头部 + 内容) > - NDrawer / NTabs / NCollapse 三种容器组件的选择与使用 > - 多级菜单折叠、菜单搜索、面包屑与标签页联动 ## 第15章 栅格布局系统与自适应布局 > **📖 学习目标:** 掌握 NGrid + NGi 的用法,理解响应式断点。 ### 15.1 栅格基础——NGrid + NGi > 💡 栅格系统就是把页面横向划分成若干"列",Naive UI 默认一行 24 列(因为 24 可以被 1、2、3、4、6、8、12 整除,排列组合最多)。 ```vue 占 3 列中的 2 列 占剩下的 1 列 占满一整行 ``` ### 15.2 响应式栅格 ```vue 内容 ``` > 📝 **本章回顾:** NGrid 默认 24 列,span 决定跨列数。cols 和 span 都支持响应式断点语法。 --- ## 第16章 全局页面布局(侧边栏、头部、内容、底部) > **📖 学习目标:** 使用 NLayout 系列组件搭建一个标准后台管理界面,含多级菜单、面包屑、标签页联动。 ### 16.1 NLayout 组件全家福 ``` NLayout → 最外层容器 ├─ NLayoutHeader → 顶部栏 ├─ NLayout (has-sider) → 主体区域 │ ├─ NLayoutSider → 侧边栏 │ └─ NLayoutContent → 内容区 └─ NLayoutFooter → 底部 ``` ### 16.2 完整标准后台布局(含多级菜单+面包屑+标签页联动) ```vue Admin 后台 A 管理员 {{ item }} ``` > 💡 **多页签(历史标签页)** 是后台系统中非常实用的功能。用户每点击一个菜单,就在顶部标签页栏添加一个新标签,点击标签可以直接切换已打开过的页面,关闭标签则移除。这比纯面包屑的体验好很多。 > ⚠️ **布局高度问题:** 用 `position="absolute"` + `top`/`bottom` 让布局撑满屏幕。如果内容区域出现双滚动条,检查 NLayoutContent 是否设置了固定的 `overflow`。 > 📝 **本章回顾:** NLayout 系列是完整页面布局的骨架。多级菜单用 children 嵌套,菜单搜索用 computed 动态过滤,多页签联动提升用户体验。 --- ## 第17章 抽屉、弹窗、标签页、折叠面板容器组件 > **📖 学习目标:** 在弹窗、抽屉、标签页、折叠面板之间选对正确的容器组件。 ### 17.1 NDrawer——抽屉(从侧边滑出的面板) > 💡 **什么时候用 Drawer 而不是 Modal?** > - 表单内容少(1-3 个字段)→ Modal > - 表单内容多、需要更多操作空间 → Drawer > - 查看详情(不打断当前操作)→ Drawer ```vue 右侧抽屉 左侧抽屉 顶部抽屉 底部抽屉 取消 保存 ``` ### 17.2 NTabs——标签页切换 ```vue 基础信息内容 详细信息内容 操作日志内容 ``` ### 17.3 NCollapse——折叠面板 ```vue 执行 pnpm add naive-ui 即可。 使用 NConfigProvider 的 theme-overrides。 不支持。Naive UI 只支持现代浏览器。 ``` > ⚠️ **本章踩坑小结:** > 1. NDrawer 用 `placement="top"` 或 `"bottom"` 时,`:width` 不生效,改用 `:height`。 > 2. NTabs 的 `animated` 默认是 `false`,设为 `true` 会有滑动切换动画——但内容复杂时可能会卡。 > 3. NCollapse 的 `v-model:value` 绑定的是展开项的 `name` 数组,不是布尔值。 > 📝 **本章回顾:** Drawer 是从侧边滑出的面板,适合大表单和详情展示;Tabs 是标签页切换,Collapse 是可折叠内容。三者各有适用场景。 --- # 第五篇章 数据展示高级组件 > **🎯 本篇你将学会:** > - NDataTable 的完整用法:基础表格、排序、筛选、行内编辑、虚拟滚动 > - 表格导出 CSV/Excel、列拖拽排序、表头分组 > - NTree 和 NTreeSelect 的树形数据操作 > - NAvatar、NProgress、NRate 等辅助展示组件 ## 第18章 高性能表格组件 > **📖 学习目标:** 掌握 NDataTable 的基本用法,学会排序/筛选/行内编辑/虚拟滚动/导出/列拖拽。 ### 18.1 NDataTable 基础——五分钟搭一个表格 ```vue ``` > 💡 **h() 函数是什么?** `h` 是 Vue 的 `createVNode` 的别名。你在 template 里写 `编辑`,等价于在 `render` 函数里写 `h(NButton, {}, { default: () => '编辑' })`。因为列配置里的 `render` 不是写在 template 里的,所以必须用 `h()` 函数来创建组件。 ### 18.2 表格行内编辑 ```vue ``` ### 18.3 虚拟滚动——10000 行也不卡 ```vue :virtual-scroll="true" bordered striped /> ``` > ⚠️ **虚拟滚动三要素:** `max-height` + `virtual-scroll` + 每行高度一致。缺任何一个都会导致异常。 ### 18.4 导出 CSV / Excel > 💡 前端导出不需要后端配合。用纯 JS 就能把表格数据导出为 CSV 文件,用户可以直接用 Excel 打开。 ```ts // src/utils/exportCsv.ts /** * 将二维数组数据导出为 CSV 文件下载 * @param filename 文件名(不含扩展名) * @param headers 表头数组 * @param rows 数据行(二维数组) */ export function exportCsv(filename: string, headers: string[], rows: any[][]) { // 拼接 CSV 内容:表头 + 数据行 const csvContent = [ headers.join(','), ...rows.map(row => row.map(cell => `"${String(cell ?? '').replace(/"/g, '""')}"`).join(',')) ].join('\n') // 添加 BOM 头,确保 Excel 正确识别中文 const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${filename}.csv` a.click() URL.revokeObjectURL(url) } ``` ```vue 导出 CSV ``` ### 18.5 列拖拽排序 ```vue ``` > 💡 Naive UI 内置了列宽拖拽(`resizable`),列顺序可通过操作 columns 数组实现。 > ⚠️ **本章踩坑小结:** > 1. 列配置里的 `key` 必须和 data 中字段名**完全一致**,否则那列显示空白。 > 2. `render` 函数里用的组件必须已在文件顶部 import。 > 3. 虚拟滚动必须同时满足 `max-height` + `virtual-scroll` + 固定行高三个条件。 > 4. 导出 CSV 时记得加 BOM 头(`\uFEFF`),否则 Excel 打开中文乱码。 > 📝 **本章回顾:** NDataTable 是最复杂的组件之一。columns + data 是核心,render 用 h() 函数,虚拟滚动解决大数据量问题,导出用纯 JS 即可。 --- ## 第19章 树形组件与树形下拉组件 > **📖 学习目标:** 掌握 NTree 和 NTreeSelect 的用法。 ### 19.1 NTree——树形组件 ```vue 已选中: {{ checkedKeys.join(', ') || '无' }} ``` > ⚠️ **树形组件的 key 必须唯一!** 每个节点的 `key` 在整个树中不能重复。即使不同树枝下的两个节点有相同的 key,也会导致勾选/展开状态混乱。 ### 19.2 NTreeSelect——树形下拉 ```vue ``` > 📝 **本章回顾:** NTree 用于树形数据展示和选择,NTreeSelect 是带下拉的树形选择器。key 的全局唯一性是最关键的注意点。 --- ## 第20章 列表、头像、进度条、数值统计、评分组件 > **📖 学习目标:** 掌握几个高频的数据展示组件。 ### 20.1 NAvatar + NBadge——头像和角标 ```vue 张 李 ``` ### 20.2 NProgress——进度条 ```vue ``` ### 20.3 NRate——评分 ```vue 你的评分:{{ rateText }} ``` > 📝 **本章回顾:** NAvatar 支持图片和文字头像,NProgress 支持线形/环形/仪表盘,NRate 是星级评分组件。 --- # 第六篇章 全局交互与反馈组件 > **🎯 本篇你将学会:** > - useMessage / useDialog / useNotification 三种全局反馈 API 的使用 > - 消息队列管理与路由切换自动关闭 > - NSpin 加载遮罩、NWatermark 水印、NPopconfirm 气泡确认框 > - 分页组件的高阶封装与业务适配 ## 第21章 消息提示、弹窗确认、通知弹窗全局 API > **📖 学习目标:** 掌握 useMessage、useDialog、useNotification 的调用方式及消息队列管理。 ### 21.1 useMessage——顶部弹出的消息条 ```vue 显示各类消息 加载消息(3s后变成功) ``` > ⚠️ **useMessage 最大的坑:必须在 setup 里调用!** > 如果你在普通函数里(比如在 axios 拦截器里)调用 `useMessage()`,会报错: > `"Cannot read properties of null (reading 'useDialog')"` > > **解决办法:** 在 setup 里调用 `useMessage()`,把返回的 `message` 对象通过参数传给函数,或者用 Pinia store 存起来。 ### 21.2 消息队列管理 + 路由切换自动关闭 > 💡 实际开发中,你可能在短时间内触发多条消息(例如批量操作),而路由切换时残留的旧消息不应该继续显示。 ```ts // src/composables/useMessageQueue.ts import { useMessage } from 'naive-ui' import { onBeforeUnmount } from 'vue' import type { MessageApi, MessageReactive } from 'naive-ui' /** * 消息队列管理——支持批量消息和自动清理 */ export function useMessageQueue() { const msg = useMessage() const activeMessages = new Set() // 创建消息并跟踪 function show(type: 'success' | 'info' | 'warning' | 'error', content: string) { const m = msg[type](content, { duration: 3000, onAfterLeave: () => activeMessages.delete(m) // 消失后从集合移除 }) activeMessages.add(m) return m } // 清除所有活动消息(路由切换时调用) function clearAll() { activeMessages.forEach(m => m.destroy()) activeMessages.clear() } // 组件卸载时自动清除 onBeforeUnmount(() => clearAll()) return { show, clearAll } } ``` ```vue ``` ### 21.3 useDialog——弹窗确认框 ```vue ``` ### 21.4 useNotification——右侧弹出的通知 ```vue ``` > 💡 **消息、弹窗、通知,我该用哪个?** > - **Message(顶部消息条):** 最轻量,操作结果即时反馈 > - **Dialog(弹窗确认框):** 需要用户确认才继续 > - **Notification(右侧通知):** 重要但非阻塞的通知 > 📝 **本章回顾:** useMessage 最轻量(操作反馈),useDialog 需确认(删除等),useNotification 非阻塞通知。三者都必须在 setup 内调用。消息队列管理可避免路由切换后的残留消息。 --- ## 第22章 加载遮罩、水印、气泡确认框 ### 22.1 NSpin——加载遮罩 ```vue 切换加载 加载中时这个区域会被遮罩覆盖 ``` ### 22.2 NWatermark——水印 ```vue 受保护的内容 截图也会带上水印文字。 ``` ### 22.3 NPopconfirm——气泡确认框 ```vue msg.success('已删除')"> 删除 确认要永久删除这条记录吗? ``` > 📝 **本章回顾:** NSpin 用于加载遮罩,NWatermark 用于安全保护,NPopconfirm 用于轻量确认(比 Dialog 更轻)。 --- ## 第23章 分页组件高阶封装与业务适配 ### 23.1 NPagination 基础 ```vue ``` ### 23.2 通用分页 Hooks ```ts // src/composables/usePagination.ts import { reactive, computed } from 'vue' export function usePagination(defaultPageSize = 10) { const pagination = reactive({ page: 1, pageSize: defaultPageSize, itemCount: 0, showSizePicker: true, pageSizes: [10, 20, 30, 50], showQuickJumper: true, onChange: (page: number) => { pagination.page = page }, onUpdatePageSize: (pageSize: number) => { pagination.pageSize = pageSize pagination.page = 1 // 关键:切换每页条数时回到第1页 } }) const totalPages = computed(() => Math.ceil(pagination.itemCount / pagination.pageSize)) function setTotal(n: number) { pagination.itemCount = n } function resetPage() { pagination.page = 1 } return { pagination, totalPages, setTotal, resetPage } } ``` > ⚠️ **分页最容易踩的坑:** 切换每页条数后,`page` 不会自动回到 1——你需要自己在 `onUpdatePageSize` 里设。搜索/筛选后也需要调用 `resetPage()`。 > 📝 **本章回顾:** 分页组件通过 onChange 和 onUpdatePageSize 控制。封装 usePagination hooks 可复用分页逻辑。搜索/筛选后别忘了回第1页。 --- # 第七篇章 主题定制与样式深度改造(Naive UI 核心特色) > **🎯 本篇你将学会:** > - Naive UI 主题系统的 CSS 变量底层原理 > - 浅色主题自定义和暗黑模式切换 > - 主题编辑器使用 > - **新增!** CSS 深度定制技巧(:deep() 穿透、CSS Module、Tailwind 共存、自定义动画、毛玻璃、打印样式) > - **新增!** 设计系统构建(品牌色→Design Token、多套主题切换、间距/阴影体系) ## 第24章 Naive UI 主题系统底层原理 ### 24.1 一句话说清楚主题系统 > Naive UI 的所有颜色、圆角、字号等视觉参数都存储在 **CSS 变量(CSS Variables)** 中。修改变量 = 改变外观。 ``` CSS 变量长这样: --n-primary-color: #18A058; 组件源码里是这样用的: background-color: var(--n-primary-color); 你想换主题色?把值改了就行: --n-primary-color: #ff0000; ``` ### 24.2 主题作用域层级 ``` NConfigProvider(最外层全局主题) └── NConfigProvider(里层,可以局部覆盖) └── 单个组件 theme-overrides(最细粒度) ``` > 💡 这个层级意味着:你可以全局设一个默认主题,然后在某个页面用里层的 NConfigProvider 覆盖为"红色主题",不影响其他页面。 > 📝 **本章回顾:** 主题系统基于 CSS 变量,通过 NConfigProvider 实现层级作用域控制。 --- ## 第25章 浅色主题自定义与全局样式修改 ```vue 绿色按钮 这个卡片用了 8px 圆角和 24px 内边距 ``` > 📝 **本章回顾:** theme-overrides 对象分 common(全局)和组件级(Button/Card 等)两种粒度。 --- ## 第26章 暗黑模式开发与明暗主题无缝切换 ```vue 🌙 暗黑 ☀️ 浅色 ``` > 💡 **暗黑模式持久化:** 把 `isDark` 存到 `localStorage`,下次打开时读回。参考 Pinia 主题 Store:`localStorage.getItem('theme') === 'dark'`。 ### 26.1 Pinia 主题 Store ```ts // src/stores/theme.ts import { defineStore } from 'pinia' import { ref } from 'vue' export const useThemeStore = defineStore('theme', () => { const isDark = ref(localStorage.getItem('theme') === 'dark') function toggleTheme() { isDark.value = !isDark.value localStorage.setItem('theme', isDark.value ? 'dark' : 'light') } return { isDark, toggleTheme } }) ``` > 📝 **本章回顾:** darkTheme 是 Naive UI 内置的暗黑主题对象。配合 localStorage 和 Pinia 可实现持久化的主题偏好。暗黑模式下需要对文字颜色、边框等做专属覆盖。 --- ## 第27章 官方主题编辑器零基础使用教程 > 💡 Naive UI 提供了一个**可视化主题编辑器**: > **地址:** https://www.naiveui.com/zh-CN/os-theme ### 使用步骤: 1. 访问上述地址 2. 左侧选择组件(Button、Card、Input…) 3. 右侧调整颜色/圆角/字号,**实时预览** 4. 调好后点击「导出」 5. 把导出的 JSON 对象复制到项目的 `theme-overrides` 里 > ⚠️ 编辑器和实际写代码用的变量名**完全一致**,你在编辑器里改了什么,复制出来就是这段代码。 > 📝 **本章回顾:** 主题编辑器是零代码定制主题的最快方式。导出 JSON → 复制到项目 → 完成。 --- ## 第28章 局部主题覆盖、单组件独立样式定制 ### 28.1 单个组件覆盖(theme-overrides 属性) ```vue 紫色圆角按钮(只影响我) 普通按钮 ``` ### 28.2 嵌套 NConfigProvider 局部覆盖 ```vue 蓝色按钮 红色按钮(仅此层) ``` ### 28.3 CSS 深度定制——从覆盖到重构(本章核心新增) #### 28.3.1 :deep() 穿透 scoped 的完整选择器优先级链 ```vue ``` > 💡 **什么时候用 :deep() 什么时候用 theme-overrides?** > - `theme-overrides`:能覆盖的变量,优先用它(稳定、类型安全) > - `:deep()`:theme-overrides 覆盖不了的样式(渐变背景、阴影、动画等),作为最后手段 #### 28.3.2 自定义动画与过渡效果 ```vue ``` #### 28.3.3 毛玻璃卡片(Glassmorphism) > 💡 毛玻璃是近年来非常流行的设计风格——半透明背景 + 模糊滤镜 + 细边框。 ```vue 背景会被模糊处理,形成毛玻璃效果。 这个效果在暗色背景下尤其好看。 ``` #### 28.3.4 自定义滚动条样式 ```vue ``` #### 28.3.5 CSS Module 与 Naive UI 的配合 ```vue CSS Module 按钮 ``` #### 28.3.6 Tailwind CSS 与 Naive UI 的共存方案 ```bash # 安装 Tailwind CSS pnpm add -D tailwindcss @tailwindcss/vite ``` ```ts // vite.config.ts —— Tailwind 与 Naive UI 共存 import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [vue(), tailwindcss()] }) ``` ```vue 提交 ``` > 💡 **Tailwind + Naive UI 最佳实践:** > - 外层布局用 Tailwind(间距、背景、响应式) > - 交互组件用 Naive UI(按钮、表格、弹窗) > - Naive UI 的样式不会被 Tailwind 的 reset 影响(因为 Naive UI 用了 CSS 变量 + scoped) #### 28.3.7 打印样式适配 ```vue ``` ### 28.4 设计系统构建——品牌色体系与 Design Token(本章核心新增) > 💡 设计系统(Design System)不是大厂的专利。即使你一个人开发,建立一套简单的 Design Token 体系,也能让主题维护成本降低 80%。 #### 28.4.1 从品牌 VI 到 Naive UI 主题变量的完整映射 ```ts // src/theme/tokens.ts —— Design Token 定义 /** * 设计系统的核心:一个 JSON 对象,定义所有视觉参数 * 品牌色、间距、阴影、字号全在这里 */ export const designTokens = { // 品牌色(从 VI 手册提取) brand: { primary: '#4F46E5', // Indigo-600(主品牌色) primaryLight: '#818CF8', // Indigo-400 primaryDark: '#3730A3', // Indigo-800 secondary: '#10B981', // Emerald-500(辅助色) danger: '#EF4444', // Red-500 warning: '#F59E0B', // Amber-500 info: '#3B82F6' // Blue-500 }, // 间距系统(基于 4px 基准) spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48 }, // 阴影层级 shadow: { sm: '0 1px 2px rgba(0,0,0,0.05)', md: '0 4px 6px rgba(0,0,0,0.07)', lg: '0 10px 25px rgba(0,0,0,0.1)', xl: '0 20px 50px rgba(0,0,0,0.15)' }, // 字阶体系 font: { xs: '12px', sm: '13px', base: '14px', md: '16px', lg: '20px', xl: '24px', xxl: '32px' }, // 圆角 radius: { sm: '4px', md: '6px', lg: '8px', xl: '12px', full: '9999px' } } // ===== 将 Design Token 转化为 Naive UI 可用的 theme-overrides ===== import type { GlobalThemeOverrides } from 'naive-ui' export function tokensToNTheme(tokens: typeof designTokens, isDark = false): GlobalThemeOverrides { const { brand, shadow, font, radius } = tokens return { common: { primaryColor: brand.primary, primaryColorHover: brand.primaryLight, primaryColorPressed: brand.primaryDark, infoColor: brand.info, successColor: brand.secondary, warningColor: brand.warning, errorColor: brand.danger, borderRadius: radius.md, fontSize: font.base, fontSizeSmall: font.xs, fontSizeMedium: font.md, fontSizeLarge: font.lg, // 暗黑模式下的特殊值 ...(isDark ? { bodyColor: '#0F111A', cardColor: '#1A1C29', textColor1: '#E5E7EB', textColor2: '#D1D5DB', textColor3: '#9CA3AF', borderColor: '#2D3042' } : {}) }, Card: { borderRadius: radius.lg, paddingMedium: '24px', boxShadow: shadow.sm } } } ``` #### 28.4.2 多套主题无缝切换 ```ts // src/theme/themes.ts import { tokensToNTheme, designTokens } from './tokens' // 品牌 A(默认 Indigo 主题) export const themeA = tokensToNTheme(designTokens, false) export const themeADark = tokensToNTheme({ ...designTokens, brand: { ...designTokens.brand, primary: '#818CF8', primaryLight: '#A5B4FC', primaryDark: '#6366F1' } }, true) // 品牌 B(绿色主题——不同客户用不同配色) const greenTokens = { ...designTokens, brand: { ...designTokens.brand, primary: '#059669', primaryLight: '#34D399', primaryDark: '#047857' } } export const themeB = tokensToNTheme(greenTokens, false) // 品牌 C(橙色主题) const orangeTokens = { ...designTokens, brand: { ...designTokens.brand, primary: '#EA580C', primaryLight: '#FB923C', primaryDark: '#C2410C' } } export const themeC = tokensToNTheme(orangeTokens, false) ``` ```vue ``` > ⚠️ **CSS 覆盖的风险:** Naive UI 的 CSS 类名虽然稳定,但大版本升级时仍可能变化。优先用 `theme-overrides`,CSS 覆盖(`:deep()`)作为**最后手段**。 > 📝 **本章回顾:** 局部覆盖可用组件级 theme-overrides 或嵌套 NConfigProvider。CSS 深度定制用 :deep()、自定义动画、毛玻璃等。Design Token 体系使主题维护更系统化,多品牌切换只需换一套 Token 对象。 --- # 第八篇章 工程化配置与性能优化 > **🎯 本篇你将学会:** > - Vite 自动按需引入配置和分包策略 > - TypeScript 类型系统全覆盖 > - 大数据场景性能优化(虚拟滚动、shallowRef) > - 项目规范、组件封装、工具函数抽离 ## 第29章 自动按需引入与打包体积优化 ```ts // vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import { resolve } from 'path' export default defineConfig({ plugins: [ vue(), AutoImport({ imports: ['vue', 'vue-router', 'pinia'], dts: 'src/auto-imports.d.ts' }), Components({ resolvers: [NaiveUiResolver()], dts: 'src/components.d.ts' }) ], resolve: { alias: { '@': resolve(__dirname, 'src') } }, build: { rollupOptions: { output: { manualChunks: { 'naive': ['naive-ui'], 'vue-vendor': ['vue', 'vue-router', 'pinia'], 'icons': ['@vicons/ionicons5'] } } } } }) ``` > 💡 **分包的作用:** 浏览器可以缓存大文件。用户第二次打开时,`naive.js` 不会被重新下载(内容没变),只下载业务代码——加载速度提升。 > 📝 **本章回顾:** 自动按需引入用 unplugin 插件,分包策略用 manualChunks 把大库拆出去。 --- ## 第30章 TypeScript 类型系统全覆盖配置 ```json { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "jsx": "preserve", "paths": { "@/*": ["./src/*"] }, "types": ["naive-ui/volar"] } } ``` ### 所有可用的类型导入 ```ts // 实例类型 import type { InputInst, ButtonInst, DataTableInst, FormInst, SelectInst, UploadInst } from 'naive-ui' // 配置类型 import type { GlobalThemeOverrides, FormRules } from 'naive-ui' // 数据类型 import type { DataTableColumns, SelectOption, TreeOption, MenuOption, UploadFileInfo } from 'naive-ui' ``` > 📝 **本章回顾:** strict: true 开启严格类型检查,types: ["naive-ui/volar"] 启用 Volar 类型增强。 --- ## 第31章 大数据场景性能优化方案 ### 31.1 三条最重要的优化原则 1. **虚拟滚动**:超过 100 行的表格,开 `virtual-scroll`。 2. **shallowRef**:大数据量不用深层响应,用 `shallowRef` 代替 `ref`。 3. **computed 预先过滤**:不要在模板里直接写 `.filter()`。 ```ts // ❌ ref:对数组中每个对象都做响应式处理(慢) const bigList = ref([...]) // ✅ shallowRef:只监听数组整体的替换(快) const bigList = shallowRef([...]) // 更新时替换整个数组 bigList.value = newData ``` > 📝 **本章回顾:** 虚拟滚动(表格 > 100 行)+ shallowRef(大数据列表)+ computed(提前过滤)是性能优化的三把斧。 --- ## 第32章 项目规范、组件封装、全局工具抽离 ### 32.1 推荐目录结构 ``` src/ ├── api/ # axios 实例 + 业务模块 ├── components/ # 通用业务组件 ├── composables/ # usePagination.ts, useForm.ts ├── layouts/ # 布局组件 ├── router/ # 路由 ├── stores/ # Pinia ├── theme/ # 主题 Token ├── types/ # 全局类型 ├── utils/ # format.ts, validate.ts, exportCsv.ts └── views/ # 页面 ``` ### 32.2 统一导入 Naive UI ```ts // src/utils/naive.ts export { NButton, NInput, NSelect, NDataTable, NForm, NFormItem } from 'naive-ui' export type { DataTableColumns, FormRules, FormInst } from 'naive-ui' export { useMessage, useDialog, useNotification } from 'naive-ui' ``` > 📝 **本章回顾:** 清晰的项目结构 + 统一导入入口 + 通用工具函数是工程化的基础。 --- # 第九篇章 企业级完整项目实战 > **🎯 本篇你将学会:** > - 从零搭建后台管理系统项目骨架 > - 布局 + 路由权限 + 暗黑模式 + Pinia 状态管理 > - 通用列表页(搜索/筛选/分页/批量操作/删除/编辑) > - 弹窗表单新增/编辑业务 > - 文件上传、图片预览、水印封装 ## 第33章 后台管理系统项目骨架搭建 ```bash pnpm create vue@latest admin-pro -- --typescript cd admin-pro pnpm add naive-ui @vicons/ionicons5 vfonts pnpm add vue-router pinia axios dayjs pnpm add -D unplugin-auto-import unplugin-vue-components @types/node ``` ### 33.1 axios 封装 ```ts // src/api/request.ts import axios from 'axios' import type { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios' const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE || '/api', timeout: 15000 }) service.interceptors.request.use((config: InternalAxiosRequestConfig) => { const token = localStorage.getItem('token') if (token && config.headers) config.headers.Authorization = `Bearer ${token}` return config }) service.interceptors.response.use( (res: AxiosResponse) => { if (res.data.code !== 0) return Promise.reject(new Error(res.data.message)) return res.data.data }, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export const get = (url: string, params?: any) => service.get(url, { params }) export const post = (url: string, data?: any) => service.post(url, data) export const put = (url: string, data?: any) => service.put(url, data) export const del = (url: string) => service.delete(url) ``` > 📝 **本章回顾:** 项目骨架 = 脚手架创建 + 依赖安装 + axios 封装 + 路由配置。axios 拦截器是统一处理 token 和错误的关键。 --- ## 第34章 布局系统 + 路由权限 + 暗黑模式整合 > 💡 完整实现见第 16 章和第 26 章。三个核心点: > - NLayout 系列构建骨架 > - Pinia 管理暗黑模式 > - 路由守卫处理登录和权限 --- ## 第35章 通用列表页、搜索筛选、分页、删除、编辑实战 ### 35.1 列表页完整流程(含批量操作) > 核心流程: > ``` > 页面进入 → fetchData() > → 搜索 → resetPage() → fetchData() > → 分页 → onChange → fetchData() > → 删除 → Popconfirm → API → fetchData() > → 批量删除 → checkedKeys → confirm → API → fetchData() > → 编辑 → router.push('/user/edit/:id') > ``` ```vue 搜索 重置 新增用户 批量删除 {{ checkedRowKeys.length ? `(${checkedRowKeys.length})` : '' }} ``` > ⚠️ **列表页最易犯的错:** 搜索后忘记把分页重置为第 1 页。你如果停留在第 5 页搜索,后端返回的数据可能只有 1 页,表格就显示空白了。 > 📝 **本章回顾:** 列表页 = 搜索 + 操作栏 + 表格 + 分页。批量操作的核心是 checkedRowKeys + type: 'selection'。搜索后必须重置到第 1 页。 --- ## 第36章 弹窗表单新增编辑业务场景落地 > 核心流程: > - 新增:打开空表单弹窗 → 填写 → POST API → 刷新列表 > - 编辑:打开弹窗 → GET API 加载旧数据 → 修改 → PUT API → 刷新列表 > ⚠️ **编辑弹窗的坑:** 关闭弹窗后旧的表单数据可能残留——每次打开弹窗前重置 formData。 > 📝 **本章回顾:** 新增/编辑用同一个弹窗,通过路由参数 :id? 区分。打开弹窗前必须重置数据。 --- ## 第37章 文件上传、图片预览、水印业务封装 > 见第 12 章和第 22 章。上传用 customRequest + beforeUpload,预览用 NImage lazy,水印用 NWatermark。 > 📝 **本章回顾:** 上传封装的要点是 customRequest(自定义请求)和 beforeUpload(前置检查)。水印通过 NWatermark 组件包裹内容区域实现。 --- # 第十篇章 疑难问题排查与实战踩坑手册 > **🎯 本篇你将学会:** 快速定位和解决 Naive UI 开发中最常见的错误和 bug。每个问题都附带排查思路。 ## 第38章 环境安装与引入报错解决方案 | 错误现象 | 排查思路 | 解决办法 | |---------|---------|---------| | `Cannot find module 'naive-ui'` | 检查 node_modules 里有没有 naive-ui 目录 | `pnpm add naive-ui` | | `[Vue warn]: Failed to resolve component: n-button` | 检查 script 里有没有 import,或 app.use 有没有调用 | import { NButton } from 'naive-ui' | | `useDialog is not defined` | 是不是在 setup 外面调用了?比如在独立的 .ts 文件里 | 在 ` 🌤️ 天气查询 ``` ### 43.6 城市搜索组件 ```vue 最近搜索: {{ h.name }}, {{ h.country }} ``` ### 43.7 当前天气卡片 ```vue {{ getWeatherIcon(w.current.weatherCode) }} {{ w.current.temperature }}°C {{ getWeatherDesc(w.current.weatherCode) }} {{ w.city }}, {{ w.country }} 💧 湿度 {{ w.current.humidity }}% 💨 风速 {{ w.current.windSpeed }} km/h 🕐 更新 {{ w.current.time?.slice(11, 16) }} ``` ### 43.8 7天预报组件 ```vue ``` ### 43.9 逐小时温度曲线图(Chart.js) ```bash pnpm add chart.js ``` ```vue ``` ### 43.10 主题切换组件 ```vue ``` > 📝 **本章回顾:** 天气应用的完整代码包括:API 层(fetch 封装 + Open-Meteo 接口)、主题层(天空蓝渐变)、搜索组件(远程搜索 + 历史记录)、当前天气卡片、7天预报表格、Chart.js 温度曲线图、暗黑模式切换。四态处理(加载/错误/空/数据)让体验更完整。 --- ## 第44章 天气应用工程化收尾 ### 44.1 环境变量配置 ```env # .env VITE_GEOCODING_API=https://geocoding-api.open-meteo.com/v1/search VITE_FORECAST_API=https://api.open-meteo.com/v1/forecast ``` ```ts // src/api/weather.ts —— 改用环境变量 const GEOCODING_BASE = import.meta.env.VITE_GEOCODING_API const FORECAST_BASE = import.meta.env.VITE_FORECAST_API ``` ### 44.2 移动端响应式适配 ```vue ``` ### 44.3 Vercel 免费部署 ```bash # 安装 Vercel CLI pnpm i -g vercel # 部署 vercel # 按提示操作: # ? Set up and deploy? → Y # ? Which scope? → 选你的账号 # ? Link to existing project? → N # ? Project name? → weather-app # ? Directory? → ./ # ? Override settings? → N ``` 部署后你会得到一个 `https://weather-app-xxx.vercel.app` 的链接,全世界都能访问。 > 💡 Vercel 对个人项目完全免费,支持自动 HTTPS、自定义域名、Git 自动部署。 ### 44.4 项目目录结构总结 ``` weather-app/ ├── src/ │ ├── api/ │ │ └── weather.ts # API 层(fetch 封装 + 接口函数) │ ├── weather/ │ │ └── weatherCodes.ts # WMO 天气码映射 │ ├── theme/ │ │ └── skyTheme.ts # 天空蓝主题配置 │ ├── components/ │ │ ├── WeatherSearch.vue # 城市搜索(远程 + 历史记录) │ │ ├── CurrentWeather.vue # 当前天气卡片 │ │ ├── DailyForecast.vue # 7天预报表格 │ │ ├── HourlyChart.vue # 温度曲线图(Chart.js) │ │ └── ThemeToggle.vue # 暗黑模式切换按钮 │ ├── App.vue # 主页面(四态处理) │ └── main.ts ├── .env └── package.json ``` > 📝 **本章回顾:** 环境变量管理 API 地址、响应式适配移动端、Vercel 一键部署。完整项目约 10 个文件,代码量不到 500 行,但涵盖了真实项目中的所有关键环节。 --- # 第十篇章(续)疑难问题排查与实战踩坑手册 > (第38-41章已在前面呈现,此处为扩展补充) ### 天气项目常见 Bug | 问题 | 原因 | 解决 | |------|------|------| | 搜索城市返回空 | 城市名需要英文(如 Beijing 而不是 北京) | 使用英文名搜索,或在 geocoding API 加 `language=zh` | | 温度图表不显示 | Chart.js 未注册组件 | `Chart.register(...registerables)` | | fetch 请求超时 | 网络不稳定或 API 服务波动 | 使用 AbortController 设置超时,显示友好错误 | | 暗黑模式下图表颜色看不清 | Chart.js 不跟随 Naive UI 主题 | 在主题切换时重新渲染图表,更新颜色配置 | --- # 附录 ## 附录A Naive UI 全组件清单与功能对照表 ### 通用 NButton、NButtonGroup、NSpace、NText、NTag、NBadge、NIcon ### 布局 NLayout、NLayoutHeader、NLayoutContent、NLayoutSider、NLayoutFooter、NGrid、NGi、NCard ### 导航 NMenu、NTabs/NTabPane、NBreadcrumb/NBreadcrumbItem、NDropdown、NPageHeader ### 数据录入 NInput、NInputNumber、NSelect、NCascader、NDatePicker、NTimePicker、NRadio/NRadioGroup、NCheckbox/NCheckboxGroup、NSwitch、NSlider、NUpload、NTransfer、NColorPicker、NRate ### 数据展示 NDataTable、NTree、NTreeSelect、NList/NListItem、NThing、NDescriptions/NDescriptionsItem、NAvatar、NImage、NProgress、NNumberAnimation、NStatistic、NCalendar ### 反馈 NModal、NDrawer/NDrawerContent、NPopover、NPopconfirm、NTooltip、NSpin、NWatermark、NResult、NEmpty、NSkeleton API:useMessage、useDialog、useNotification、useLoadingBar ### 其他 NConfigProvider、NScrollbar、NBackTop、NCode、NCountdown、NEllipsis --- ## 附录B 全局主题变量完整版手册 ```ts // Common(全局公用变量) primaryColor // 主色 infoColor // 信息色 successColor // 成功色 warningColor // 警告色 errorColor // 错误色 // 每种颜色都有 Hover/Pressed/Suppl 变体 textColor1 / textColor2 / textColor3 // 文字深浅三级 bodyColor / cardColor / modalColor // 背景三级 borderColor / borderRadius / fontFamily / fontSize / boxShadow ``` ### 组件变量命名规则 `组件名` + `变量名`(首字母大写) 例如 `Button.borderRadius`、`Input.borderFocus`、`Card.paddingMedium` --- ## 附录C 项目通用工具函数封装源码 ```ts // ===== src/utils/format.ts ===== import dayjs from 'dayjs' export function formatDate(date: number | string | Date, format = 'YYYY-MM-DD HH:mm:ss') { if (!date) return '' return dayjs(date).format(format) } export function formatMoney(cents: number) { return (cents / 100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') } export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 B' const units = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(1024)) return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i] } // 日期处理增强工具集 export function getRelativeTime(date: string | Date): string { const now = dayjs() const target = dayjs(date) const diffMin = now.diff(target, 'minute') if (diffMin < 1) return '刚刚' if (diffMin < 60) return `${diffMin} 分钟前` const diffHour = now.diff(target, 'hour') if (diffHour < 24) return `${diffHour} 小时前` const diffDay = now.diff(target, 'day') if (diffDay < 30) return `${diffDay} 天前` return formatDate(date) } export function getWeekday(date: string | Date): string { const map = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] return map[dayjs(date).day()] } // ===== src/utils/validate.ts ===== export function isValidPhone(v: string) { return /^1[3-9]\d{9}$/.test(v) } export function isValidEmail(v: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) } export function isValidIDCard(v: string) { return /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(v) } // ===== src/utils/exportCsv.ts ===== export function exportCsv(filename: string, headers: string[], rows: any[][]) { const csv = [ headers.join(','), ...rows.map(row => row.map(c => `"${String(c ?? '').replace(/"/g, '""')}"`).join(',')) ].join('\n') const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url; a.download = `${filename}.csv`; a.click() URL.revokeObjectURL(url) } // ===== src/utils/index.ts ===== export function debounce any>(fn: T, delay: number) { let timer: ReturnType return (...args: Parameters) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay) } } export function throttle any>(fn: T, delay: number) { let last = 0 return (...args: Parameters) => { const now = Date.now(); if (now - last >= delay) { last = now; fn(...args) } } } export function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2) } export function deepClone(obj: T): T { return JSON.parse(JSON.stringify(obj)) } ``` --- ## 附录D 官方资源与学习渠道汇总 | 资源 | 地址 | |------|------| | 📖 官方文档 | https://www.naiveui.com | | 📦 GitHub | https://github.com/tusen-ai/naive-ui | | 🎨 主题编辑器 | https://www.naiveui.com/zh-CN/os-theme | | 🔣 IonIcons 图标 | https://ionic.io/ionicons | | 💬 Discord | https://discord.gg/Pqv7Mev5xT | | 🌤️ Open-Meteo API | https://open-meteo.com | | 📊 Chart.js 文档 | https://www.chartjs.org | ### 推荐学习路线(0→1) 1. **第1-8章**(入门):理解组件库,搭环境,基础组件 2. **第9-14章**(进阶):表单是核心——输入框、选择器、校验 3. **第15-20章**(深入):表格、树、布局——后台 80% 的代码 4. **第21-23章**(交互):消息、弹窗、通知——用户反馈 5. **第24-28章**(亮点):主题定制 + CSS 深度定制——Naive UI 看家本领 6. **第29-32章**(工程化):上线前优化 7. **第33-37章**(实战):后台管理系统,从头到尾 8. **第38-41章**(排错):遇到 bug 回来查 9. **第42-44章**(扩展):天气查询工具——前端项目的完整闭环 --- ## 附录E 天气应用完整源码清单 ``` weather-app/ ├── index.html ├── package.json ├── vite.config.ts ├── tsconfig.json ├── .env # 环境变量 └── src/ ├── main.ts # 入口文件 ├── App.vue # 主页面(四态处理 + 主题) ├── api/ │ └── weather.ts # fetch 封装 + Open-Meteo 接口 ├── weather/ │ └── weatherCodes.ts # WMO 天气码 → 中文/图标的映射表 ├── theme/ │ └── skyTheme.ts # 天空蓝主题(浅色 + 暗黑) └── components/ ├── WeatherSearch.vue # 城市搜索(远程 + 历史记录) ├── CurrentWeather.vue # 当前天气卡片 ├── DailyForecast.vue # 7天预报表格 ├── HourlyChart.vue # 24小时温度曲线图(Chart.js) └── ThemeToggle.vue # 暗黑模式切换按钮 ``` > 💡 整个天气项目仅 **10 个文件、约 400 行业务代码**,但涵盖了: > - 远程 API 调用(fetch + AbortController) > - 搜索防抖(debounce 300ms) > - 搜索历史(localStorage) > - 主题系统(天空蓝渐变 + 暗黑适配) > - 四态处理(加载/错误/空/数据) > - 数据可视化(Chart.js 折线图) > - 响应式布局(移动端适配) > - 一键部署(Vercel) --- > 🎉 **恭喜你读完了整本书!** > > 从零基础到能独立搭建后台管理系统 + 天气查询工具,你已经完成了从"新手"到"Naive UI 熟练工"的转变。 > 剩下的就是:多写、多练、多踩坑。踩完的每一坑都会让你变得更强大。 > > **编码愉快!🚀**
姓名:张三
年龄:28岁
点击或拖拽文件到此区域上传
支持 JPG/PNG/GIF/PDF,最大 5MB
pnpm add naive-ui
NConfigProvider
theme-overrides
截图也会带上水印文字。
背景会被模糊处理,形成毛玻璃效果。
这个效果在暗色背景下尤其好看。