Vue的基础原理和使用
一、Vue1、前言前端的基础知识参考javaweb前端基础HTML、CSS、Javascript、vue3、ajax/axios-CSDN博客文章浏览阅读225次点赞6次收藏3次。本项目综合运用了三大核心技术实现了一个完整的四段式页面布局导航栏 → 搜索区 → 表格区 → 页脚是前端入门的经典综合练习案例。https://blog.csdn.net/m0_47518801/article/details/163595469?spm1001.2014.3001.5501依赖环境是node.js需先下载node.js安装及配置参考Node.js 下载安装与环境配置全流程保姆级详解| 图文详解快速上手_node安装教程图文详解-CSDN博客文章浏览阅读10w次点赞818次收藏1.3k次。本文是我在学习 Node.js 过程中整理的笔记详细记录了 Node.js 的下载安装、环境配置、全局模块安装、镜像加速设置等操作步骤。虽然是学习笔记但内容非常全面、细致适合初学者和有需要的朋友参考。_node安装教程图文详解https://blog.csdn.net/Natsuago/article/details/145567734?spm1001.2014.3001.55062、vue创建、启动也可以参考如下连接创建快速上手 | Vue.jsVue.js - 渐进式的 JavaScript 框架https://cn.vuejs.org/guide/quick-start.html各选项分析• JSX 支持一般初学者不需要除非你要用 JSX 语法• Router单页面应用路由做项目基本都需要• Pinia状态管理中大型项目需要• Vitest单元测试初学者可以暂时不选• 端到端测试初学者不需要• LinterESLint代码检查建议选上• Prettier代码格式化建议选上1. 对于初学者/学习目的推荐选择• Router ✅做项目基本必须• Pinia ✅状态管理常用• Linter ✅帮助规范代码 - Prettier ✅格式化代码2. 如果是最简单的学习项目可以只选 Router。安装成功的版本查看方式当创建成功的时候直接按照提示做就ok了npm install主要是安装node_modules依赖2.1 bug_1当输入 npm create vuelatest 时报如下错误。核心错误EPERM: operation not permitted, open D:\nodejs\node_cache\_cacache\tmp\***npm 在写入缓存目录时没有权限方式一以管理员身份运行 CMD然后 cd /d D:\MicrosoftVSCodeLocation\vue进入自己对应的目录方式二清除 npm 缓存缓存文件可能已损坏或被锁定npm cache clean --force方式三关闭占用文件的程序以下程序可能锁定了缓存文件• VS Code尤其是打开了相关项目时• 杀毒软件如 360、火绒、Windows Defender• 其他终端/IDE操作1. 关闭所有 VS Code 窗口2. 暂时关闭杀毒软件3. 关闭其他终端4. 重新执行命令方式四手动修改缓存目录权限1. 打开文件资源管理器进入 D:\nodejs\node_cache2. 右键 → 属性 → 安全 → 编辑3. 给当前用户添加 完全控制 权限4. 勾选 替换所有子对象权限5. 确定后重试方式五更换缓存目录推荐长期方案将缓存目录改到用户目录下避免权限问题npm config set cache C:\Users\你的用户名\.npm_cache --global方法六删除缓存目录后重试你自己对应的目录# 手动删除缓存目录 rd /s /q D:\nodejs\node_cache\_cacache然后重新执行npm create vuelatest就可以了2.2 bug_2npm install 依赖冲突npm install 依赖冲突的错误原因oxlint1.74.0 ← 项目实际安装的版本eslint-plugin-oxlint1.73.0 要求 peer oxlint~1.73.0 ← 期望的版本create-vue 脚手架模板中两个包的版本号没对齐属于脚手架的小 bug。方法一加 --legacy-peer-deps跳过 peer 依赖的严格检查直接安装。## 优点简单安全不影响功能缺点每次 install 都要加参数 npm install --legacy-peer-deps ## 如果不想每次都加参数可以全局设置可选 npm config set legacy-peer-deps true方法二加 --force强制安装忽略冲突。## 优点简单缺点可能掩盖其他依赖问题 npm install --force方法三手动修复版本号。推荐 ✅## 优点彻底解决缺点需要手动操作 ## 打开 package.json找到这两行 oxlint: ~1.74.0, eslint-plugin-oxlint: ~1.73.0 ## 改为版本一致 oxlint: ~1.73.0, eslint-plugin-oxlint: ~1.73.0然后重新执行npm install3、vue的项目结构4、vue的两者API风格5、案例在vue项目中基于组合式API完成用户列表渲染script setup import { ref } from vue // 职位映射表 const positionMap { 1: 班主任, 2: 讲师, 3: 学工主管, 4: 教研主管, 5: 咨询师 } // 原始完整数据不会被修改作为查询的数据源 const allEmpList [ { id: 1, name: 令狐冲, gender: 1, avatar: https://api.dicebear.com/7.x/adventurer/svg?seedlinghu, position: 2, hireDate: 2020-08-08, lastOpTime: 2024-12-20 14:30:00 }, { id: 2, name: 任盈盈, gender: 2, avatar: https://api.dicebear.com/7.x/adventurer/svg?seedrenyingying, position: 1, hireDate: 2022-08-01, lastOpTime: 2024-12-19 09:15:00 }, { id: 3, name: 岳不群, gender: 1, avatar: https://api.dicebear.com/7.x/adventurer/svg?seedyuebuqun, position: 4, hireDate: 2021-05-20, lastOpTime: 2024-12-18 16:45:00 } ] // 使用 ref 创建响应式数组 // ref() 将数据包裹为响应式引用 // 在 JS 中通过 .value 访问在模板中自动解包无需 .value // 表格展示的数据查询后会被筛选赋值 const empList ref([...allEmpList]) // 搜索条件姓名v-model 双向绑定 const searchName ref() // 搜索条件性别v-model 双向绑定 const searchGender ref() // 搜索条件职位v-model 双向绑定 const searchPosition ref() // 查询功能 /** * 查询按钮点击事件 * 根据姓名、性别、职位三个条件从 allEmpList 中筛选数据 * 条件为空则不过滤该字段即全部 * * 用户输入 → v-model → searchName/searchGender/searchPosition ↓ 点击【查询】按钮 ↓ handleSearch() 执行 filter ↓ empList.value 筛选结果 ↓ 视图自动更新响应式 */ const handleSearch () { empList.value allEmpList.filter(item { // 姓名条件模糊匹配包含即可忽略大小写 const nameMatch !searchName.value || item.name.includes(searchName.value) // 性别条件精确匹配为空则不过滤 const genderMatch !searchGender.value || item.gender searchGender.value // 职位条件精确匹配为空则不过滤 const positionMatch !searchPosition.value || item.position searchPosition.value // 三个条件同时满足才返回 trueAND 逻辑 return nameMatch genderMatch positionMatch }) } // 清空/重置功能 /** * 清空按钮点击事件 * 重置所有搜索条件并恢复显示全部数据 */ const handleReset () { // 清空搜索条件 searchName.value searchGender.value searchPosition.value // 恢复显示全部数据 empList.value [...allEmpList] } /** * 根据职位编号获取职位名称 * param {string} code - 职位编号 * returns {string} - 职位名称 */ const getPositionText (code) { return positionMap[code] || 未知 } /** * 编辑按钮点击事件 * param {object} item - 当前行数据 */ const handleEdit (item) { alert(正在编辑${item.name}) } /** * 删除按钮点击事件 * 注意操作 ref 数组时需要通过 .value 访问 * param {object} item - 当前行数据 */ const handleDelete (item) { if (confirm(确定要删除「${item.name}」吗)) { // 从展示列表中删除 const index empList.value.findIndex(emp emp.id item.id) if (index ! -1) { empList.value.splice(index, 1) } // 同时从原始数据中删除保持数据一致性 const allIndex allEmpList.findIndex(emp emp.id item.id) if (allIndex ! -1) { allEmpList.splice(allIndex, 1) } } } /script template !-- 第二部分搜索表单区域 -- div classsearch-form !-- submit.prevent 阻止表单默认提交行为改为调用 handleSearch -- form submit.preventhandleSearch !-- 使用 v-model 双向绑定搜索关键字双向绑定即数据模型与视图同步更新 -- label forname姓名:/label input typetext idname namename v-modelsearchName placeholder请输入姓名 label forgender性别:/label select idgender namegender v-modelsearchGender option value请选择/option option value1男/option option value2女/option /select label forposition职位:/label select idposition nameposition v-modelsearchPosition option value请选择/option option value1班主任/option option value2讲师/option option value3学工主管/option option value4教研主管/option option value5咨询师/option /select !-- 查询按钮typesubmit 触发表单提交由 submit.prevent 拦截并调用 handleSearch -- button typesubmit classbtn-primary查询/button !-- 清空按钮typebutton 避免触发表单提交调用 handleReset -- button typereset classbtn-default clickhandleReset清空/button /form /div !-- 第三部分表格展示区Vue挂载区域 -- div classtable-container table !-- 表头 -- thead tr th序号/th th姓名/th th性别/th th头像/th th职位/th th入职日期/th th最后操作时间/th th操作/th /tr /thead !-- 表体使用 v-for 循环渲染响应式数据 -- tbody !-- 空数据提示当列表为空时显示 -- tr v-ifempList.length 0 td colspan8 classempty-tip暂无数据/td /tr tr v-for(item, index) in empList :keyitem.id !-- 序号索引1 -- td{{ index 1 }}/td !-- 姓名 -- td{{ item.name }}/td !-- 性别动态绑定class和文字 -- td span :class[gender-tag, item.gender 1 ? gender-male : gender-female] {{ item.gender 1 ? 男 : 女 }} /span /td !-- 头像使用 :src 动态绑定 -- td img :srcitem.avatar :altitem.name classavatar /td !-- 职位映射为文字 -- td span classposition-tag{{ getPositionText(item.position) }}/span /td !-- 入职日期 -- td{{ item.hireDate }}/td !-- 最后操作时间 -- td{{ item.lastOpTime }}/td !-- 操作按钮 -- td button classbtn btn-edit clickhandleEdit(item)编辑/button button classbtn btn-delete clickhandleDelete(item)删除/button /td /tr /tbody /table /div /template style scoped /* 全局样式重置 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Microsoft YaHei, Arial, sans-serif; background-color: #f5f5f5; display: flex; flex-direction: column; min-height: 100vh; } /* 第一部分顶部导航栏 */ .header { display: flex; justify-content: space-between; align-items: center; background-color: #d0d0d0; padding: 15px 20px; } .header .title { font-size: 28px; font-weight: bold; color: #333; } .header .logout { color: #555; text-decoration: none; font-size: 16px; padding: 8px 16px; border-radius: 4px; transition: all 0.3s ease; } .header .logout:hover { background-color: #ff4d4f; color: #fff; box-shadow: 0 2px 6px rgba(255, 77, 79, 0.4); } /* 第二部分搜索表单区域 */ .search-form { padding: 20px; background-color: #fff; border-bottom: 1px solid #e0e0e0; } .search-form form { display: flex; align-items: center; gap: 15px; flex-wrap: wrap; } .search-form label { font-size: 14px; color: #555; margin-right: 5px; } .search-form input[typetext], .search-form select { height: 32px; padding: 0 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; min-width: 120px; } .search-form input[typetext]:focus, .search-form select:focus { border-color: #4a90d9; outline: none; } .search-form button { height: 32px; padding: 0 20px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; cursor: pointer; transition: all 0.3s; } .btn-primary { background-color: #1890ff; color: #fff; border-color: #1890ff; } .btn-primary:hover { background-color: #40a9ff; box-shadow: 0 2px 8px rgba(24, 144, 255, 0.4); } .btn-default { background-color: #fff; color: #666; border: 1px solid #d9d9d9; } .btn-default:hover { color: #1890ff; border-color: #1890ff; } /* 第三部分表格展示区 */ .main-content { flex: 1; } .table-container { margin: 20px; background-color: #fff; border-radius: 6px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 20px; } .table-container table { width: 100%; border-collapse: collapse; font-size: 14px; } .table-container thead { background-color: #f0f5fa; } .table-container thead th { padding: 12px 15px; text-align: left; font-weight: bold; color: #333; border-bottom: 2px solid #4a90d9; white-space: nowrap; } .table-container tbody td { padding: 12px 15px; border-bottom: 1px solid #e8e8e8; color: #555; vertical-align: middle; } /* 偶数行浅灰色背景 */ .table-container tbody tr:nth-child(even) { background-color: #f5f7fa; } .table-container tbody tr:hover { background-color: #e6f7ff; } .table-container .avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; border: 2px solid #e0e0e0; } /* 操作按钮通用样式 */ .table-container .btn { border: none; padding: 5px 12px; font-size: 12px; border-radius: 4px; cursor: pointer; transition: all 0.3s; margin-right: 8px; } .table-container .btn-edit { background-color: #52c41a; color: #fff; } .table-container .btn-edit:hover { background-color: #73d13d; box-shadow: 0 2px 6px rgba(82, 196, 26, 0.4); } .table-container .btn-delete { background-color: #ff4d4f; color: #fff; } .table-container .btn-delete:hover { background-color: #ff7875; box-shadow: 0 2px 6px rgba(255, 77, 79, 0.4); } /* 性别标签样式 */ .gender-tag { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 12px; } .gender-male { background-color: #e6f3ff; color: #4a90d9; } .gender-female { background-color: #fff0f0; color: #e74c3c; } /* 职位标签样式 */ .position-tag { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 12px; background-color: #f0f5ff; color: #2f54eb; } /* 空数据提示样式 */ .empty-tip { text-align: center; padding: 40px; color: #999; font-size: 16px; } /* 第四部分页脚版权区域 */ .footer { background-color: #555; color: #fff; text-align: center; padding: 20px 0; line-height: 1.8; font-size: 13px; } .footer .company-name { font-size: 14px; letter-spacing: 1px; } .footer .copyright { font-size: 12px; opacity: 0.8; margin-top: 5px; } /style二、Element Plus基于 Vue 3面向设计师和开发者的组件库一个 Vue 3 UI 框架 | Element PlusA Vue 3 based component library for designers and developershttps://element-plus.org/zh-CN/1、element plus 快速入门必须去到对应项目的文件包里下载npm install element-plus --save也可以这样下载cmd在 package.json 中在出现如下就是安装成功# 安装成功执行这条指令查看版本 npm list element-plusmain.ts在“指南”→“快速开始”里import ./assets/main.css import { createApp } from vue import App from ./App.vue import router from ./router // 引入 Element Plus 组件库 import ElementPlus from element-plus import element-plus/dist/index.css // Element Plus 组件默认使用英文这里引入中文语言包 import zhCn from element-plus/es/locale/lang/zh-cn // 一次性导入 Element Plus 的全部图标组件注意全量注册图标会增大打包体积。生产项目更推荐按需导入 import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) app.use(router) // 使用 Element Plus 组件库, 并指定使用中文语言包 app.use(ElementPlus, {locale: zhCn}) // 注册 Element Plus 图标组件 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.mount(#app)其他的参考 Element plus 官方文档就可以了