本地网站建设,莆田seo接单,怎样做网站吸引客户,最牛网站建设是谁一.要实现的功能展示 二.分步解决
1.vue2搭建
nodejs安装下载https://blog.csdn.net/weixin_55992854/article/details/121140754?spm1001.2014.3001.5506
nvm安装下载
nvm安装教程
vue脚手架搭建
https://blog.csdn.net/qq_48164590/article/details/129440134
2.代码…一.要实现的功能展示二.分步解决1.vue2搭建nodejs安装下载https://blog.csdn.net/weixin_55992854/article/details/121140754?spm1001.2014.3001.5506nvm安装下载nvm安装教程vue脚手架搭建https://blog.csdn.net/qq_48164590/article/details/1294401342.代码详解2.1Vue 单文件组件SFC模板template区域script区域style scoped区域template 必须有且仅有一个根元素如.component-container 支持 Vue 核心指令v-model双向绑定、v-for循环、v-if/v-show条件渲染、事件名事件绑定等 插值表达式{{ }}用于渲染响应式数据。 /template script // 脚本区域存放组件逻辑数据、方法、生命周期等 export default { // 1. 组件名称必填建议 PascalCase 命名 name: BaseTemplate, // 2. 接收父组件传递的属性可选 props: { // 示例接收父组件传递的标题 title: { type: String, // 属性类型 default: 默认标题 // 默认值 } }, // 3. 响应式数据核心 data() { return { // 组件内部标题优先使用父组件传递的title componentTitle: this.title, // 输入框绑定值 inputValue: , // 其他自定义数据 count: 0 }; }, // 4. 计算属性依赖数据自动更新可选 computed: { // 示例处理输入内容的长度 inputLength() { return this.inputValue.trim().length; } }, // 5. 监听属性监听数据变化可选 watch: { // 监听inputValue变化 inputValue(newVal, oldVal) { console.log(输入内容变化, 新值, newVal, 旧值, oldVal); } }, // 6. 生命周期钩子组件生命周期阶段执行可选 created() { // 组件创建完成数据已初始化DOM未渲染 console.log(组件创建完成); }, mounted() { // 组件挂载完成DOM已渲染可操作DOM console.log(组件挂载完成); }, // 7. 方法业务逻辑处理核心 methods: { /** * 按钮点击事件处理 */ handleClick() { this.count; alert(按钮点击了${this.count}次输入内容长度${this.inputLength}); } } }; /script style scoped /* 样式区域组件样式scoped表示样式仅作用于当前组件避免污染 */ /style2.2 添加用户——template区域 !-- 姓名字段必填失去焦点触发姓名验证 -- div classform-item label forname span classpoint*/span姓名: /label input typetext !-- 文本输入框 -- idname !-- 与label绑定提升可访问性 -- v-modeluser.name !-- 双向绑定到user对象的name属性 -- placeholder请输入姓名 !-- 占位提示 -- blurcheckName !-- 失去焦点时触发姓名验证方法 -- stylemargin-bottom: 10px; !-- 行内样式底部间距 -- / !-- 姓名验证错误提示nameEmpty为true时显示 -- div v-ifnameEmpty classpoint stylemargin: 0 0 15px 60px;请输入姓名/divscript区域 // 数据定义响应式数据 data() { return { // 新增/编辑用户的表单数据 user: { id: 0, // 用户ID自增 name: , // 姓名 age: , // 年龄 email: , // 邮箱 selected: false,// 复选框选中状态 edit: false, // 是否处于编辑状态 tmp: {} // 编辑时保存原始数据用于取消编辑恢复 }, list: [], // 用户列表数组存储所有用户数据 nameEmpty: false, // 姓名验证是否为空 ageEmpty: false, // 年龄验证是否为空/非法 emailEmpty: false,// 邮箱验证是否格式错误 selected: [], // 选中行的索引数组批量操作核心 selectedAll: false,// 全选框状态 num: 0 // 自增ID计数器用于生成唯一用户ID }; }, method(){ addUser() { // 触发所有字段验证确保提交时强制校验 this.checkName(); this.checkAge(); this.checkEmail(); // 若任意字段验证失败终止方法 if (this.nameEmpty || this.ageEmpty || this.emailEmpty) return; // 校验姓名是否已存在避免重复 if (this.list.some(item item.name this.user.name)) { alert(姓名已存在); return; } // 构造新用户对象ID自增 const newUser { id: this.num, // ID自增每次新增1 name: this.user.name, // 姓名 age: this.user.age, // 年龄 email: this.user.email,// 邮箱 selected: false, // 初始未选中 edit: false, // 初始非编辑状态 tmp: {} // 初始空对象用于编辑缓存 }; // 将新用户添加到列表 this.list.push(newUser); // 重置表单清空输入框重置验证状态 this.resetForm(); // 提示添加成功 alert(添加成功); }, }/** * 重置表单方法 * 1. 清空用户表单数据 * 2. 重置所有验证错误状态 */ resetForm() { this.user { id: this.num, name: , age: , email: , selected: false, edit: false, tmp: {} }; // 重置验证状态 this.nameEmpty false; this.ageEmpty false; this.emailEmpty false; }, /** * 姓名验证方法失去焦点触发 * 校验姓名是否为空去除首尾空格 */ checkName() { this.nameEmpty this.user.name.trim() ; }, /** * 年龄验证方法失去焦点触发 * 1. 校验是否为空 * 2. 校验是否在0-120之间非法则清空并标记错误 */ checkAge() { // 空值校验 if (this.user.age || this.user.age 0 || this.user.age 120) { this.ageEmpty true; // 标记验证失败 this.user.age ; // 清空非法输入 } else { this.ageEmpty false;// 验证通过 } }, /** * 邮箱验证方法失去焦点触发 * 1. 使用正则校验邮箱格式 * 2. 去除首尾空格后校验避免空格导致的误判 */ checkEmail() { // 邮箱正则表达式匹配标准邮箱格式 const emailReg /^[a-zA-Z0-9_.-][a-zA-Z0-9-](\.[a-zA-Z0-9-])*\.[a-zA-Z0-9]{2,6}$/; // 校验空值/格式错误都标记为验证失败 this.emailEmpty !emailReg.test(this.user.email.trim()); },2.3 删除的相关方法/** * 删除单行用户方法 * param {Number} index - 当前行索引 * 1. 弹出确认框取消则终止 * 2. 删除列表中对应行 * 3. 从选中数组移除当前索引避免数据残留 */ deleteUser(index) { // 确认删除用户取消则不执行后续逻辑 if (confirm(确定要删除该用户吗)) { // splice删除从index位置删除1个元素 this.list.splice(index, 1); // 从选中数组移除当前索引避免后续批量操作出错 this.selected this.selected.filter(i i ! index); } }, /** * 批量删除选中用户方法 * 1. 校验是否有选中项无则提示并终止 * 2. 确认删除取消则终止 * 3. 倒序删除避免索引错乱清空选中状态 */ deleteSelected() { // 无选中项则提示并终止 if (this.selected.length 0) { alert(请选择要删除的用户); return; } // 确认删除 if (confirm(确定要删除选中的用户吗)) { // 倒序排序避免正序删除导致索引错乱核心 // 例如选中[0,1]正序删0后1变成0再删1会删错行 this.selected.sort((a, b) b - a).forEach(index { this.list.splice(index, 1); // 逐个删除选中行 }); // 清空选中数组 this.selected []; // 取消全选框状态 this.selectedAll false; } }, /** * 删除所有用户方法 * 1. 校验列表是否为空空则提示并终止 * 2. 确认删除取消则终止 * 3. 清空列表重置选中状态 */ deleteAll() { // 列表为空则提示 if (this.list.length 0) { alert(暂无用户可删除); return; } // 确认删除 if (confirm(确定要删除所有用户吗)) { this.list []; // 清空用户列表 this.selected []; // 清空选中数组 this.selectedAll false;// 取消全选框 } },2.4 编辑的相关方法/** * 编辑单行用户方法 * param {Number} index - 当前行索引 * 1. 标记当前行为编辑状态 * 2. 缓存原始数据用于取消编辑恢复 */ editUser(index) { const item this.list[index]; item.edit true; // 标记为编辑状态 // 缓存原始数据避免编辑中取消时丢失原数据 item.tmp { name: item.name, age: item.age, email: item.email }; }, /** * 保存编辑后的用户数据 * param {Number} index - 当前行索引 * 1. 校验姓名不能为空 * 2. 校验年龄0-120之间 * 3. 校验邮箱格式正确 * 4. 校验姓名与其他行不重复排除自身 * 5. 验证通过则退出编辑状态清空缓存提示成功 */ saveUser(index) { const item this.list[index]; // 姓名不能为空去除首尾空格 if (item.name.trim() ) { alert(姓名不能为空); return; } // 年龄必须在0-120之间 if (item.age || item.age 0 || item.age 120) { alert(请输入0-120之间的有效年龄); return; } // 邮箱格式校验 const emailReg /^[a-zA-Z0-9_.-][a-zA-Z0-9-](\.[a-zA-Z0-9-])*\.[a-zA-Z0-9]{2,6}$/; if (!emailReg.test(item.email.trim())) { alert(请输入正确的邮箱格式); return; } // 校验姓名是否与其他行重复排除当前行 const isDuplicate this.list.some((i, idx) idx ! index i.name item.name); if (isDuplicate) { alert(姓名已存在); return; } // 验证通过退出编辑状态 item.edit false; item.tmp {}; // 清空缓存数据 alert(修改成功); // 提示修改成功 }, /** * 取消编辑方法 * param {Number} index - 当前行索引 * 1. 退出编辑状态 * 2. 恢复缓存的原始数据 * 3. 清空缓存 */ cancelEdit(index) { const item this.list[index]; item.edit false; // 退出编辑状态 // 恢复原始数据从tmp缓存中取 item.name item.tmp.name; item.age item.tmp.age; item.email item.tmp.email; item.tmp {}; // 清空缓存 }, editSelected() { // 无选中项则提示 if (this.selected.length 0) { alert(请选择要修改的用户); return; } // 遍历选中行逐个标记为编辑状态并缓存数据 this.selected.forEach(index { const item this.list[index]; item.edit true; item.tmp { name: item.name, age: item.age, email: item.email }; }); }2.5 全选/单选的相关方法/** * 全选/取消全选方法全选框状态变化触发 * 1. 遍历列表同步所有行的复选框状态 * 2. 更新选中索引数组全选时生成所有索引取消时清空 */ selectAll() { // 遍历列表将所有行的selected同步为全选框状态 this.list.forEach((item) { item.selected this.selectedAll; }); // 更新选中索引数组全选则生成所有索引取消则清空 this.selected this.selectedAll ? this.list.map((_, index) index) // map生成索引数组[0,1,2...] : []; // 取消全选则清空 }, /** * 单行复选框状态变化方法 * param {Number} index - 当前行索引 * 1. 获取当前行数据 * 2. 选中则添加索引去重取消则移除索引 * 3. 取消时同步取消全选框状态 */ handleCheckboxChange(index) { // 获取当前行的用户对象 const item this.list[index]; // 复选框选中添加索引到选中数组避免重复 if (item.selected) { if (!this.selected.includes(index)) { this.selected.push(index); } } else { // 复选框取消从选中数组移除当前索引 this.selected this.selected.filter(i i ! index); // 只要有一行取消全选框必为未选中 this.selectedAll false; } },三.相关知识点汇总一、模板语法template核心1. 插值表达式{{ }}作用渲染响应式数据到页面文本渲染案例对应{{ index 1 }}序号、{{ item.name }}用户名注意只能写表达式如运算、方法调用不能写语句如if/for2. 指令Vue 特有的标签属性以v-开头指令作用案例对应v-model双向绑定表单元素↔数据v-modeluser.name、v-modelitem.selectedv-for循环渲染列表v-for(item, index) in list :keyitem.idv-if/v-else条件渲染元素是否显示v-if!item.edit、v-iflist.length 0事件名绑定事件简写全称v-onblurcheckName、clickaddUser、changeselectAll:属性名绑定元素属性简写全称v-bind:keyitem.id循环必须加唯一 key3. 关键细节v-for必须加:key用唯一值如item.id避免列表渲染错乱v-model适配不同表单类型文本框 / 邮箱框v-modelxxx字符串数字框v-modelxxx需校验数值类型复选框v-modelxxx布尔值 / 数组blur失去焦点表单验证常用避免实时校验干扰用户输入。二、响应式数据script核心1.data选项作用定义组件内部的响应式数据格式必须是函数返回对象避免组件复用数据污染案例对应jsdata() { return { user: { name: , age: }, // 表单数据 list: [], // 列表数据 nameEmpty: false // 验证状态 }; }响应式特性数据变化 → 页面自动更新如this.nameEmpty true会触发错误提示显示。2.methods选项作用定义组件的业务方法处理点击、验证、数据操作案例对应事件处理addUser()新增、deleteUser()删除表单验证checkName()、checkEmail()数据操作selectAll()全选、saveUser()保存编辑关键方法内通过this访问data中的数据如this.list、this.user.name。3. 常用数组方法列表操作核心方法作用案例对应push()向数组末尾添加元素this.list.push(newUser)新增用户splice()删除 / 替换数组元素this.list.splice(index, 1)删除行filter()筛选数组返回新数组this.selected.filter(i i ! index)移除索引forEach()遍历数组this.list.forEach(item { ... })全选map()遍历数组并返回新数组this.list.map((_, index) index)生成索引数组some()检查是否有元素满足条件this.list.some(item item.name this.user.name)姓名重复校验三、事件处理1. 常用事件类型事件名触发时机案例对应click点击元素clickaddUserblur元素失去焦点blurcheckNamechange表单元素值变化changeselectAll复选框2. 事件传参无参clickaddUser→ 方法直接定义addUser() { ... }有参clickdeleteUser(index)→ 方法接收参数deleteUser(index) { ... }传事件对象inputhandleInput($event)→ 方法接收handleInput(e) { ... }。3. 核心逻辑批量删除的「倒序删除」问题正序删除列表元素会导致索引错乱如删索引 0 后原索引 1 变成 0解决this.selected.sort((a, b) b - a).forEach(index { ... })先倒序再删除。四、表单验证实战重点1. 核心规则姓名trim()去首尾空格判断是否为空this.user.name.trim() 年龄校验数值范围0-120非法值清空并标记错误邮箱正则校验格式/^[a-zA-Z0-9_.-][a-zA-Z0-9-](\.[a-zA-Z0-9-])*\.[a-zA-Z0-9]{2,6}$/重复校验some()检查列表中是否已存在相同姓名排除自身。2. 验证时机失去焦点校验blur提升用户体验提交前强制校验addUser()中先调用checkName()/checkAge()/checkEmail()。五、样式style scoped1.scoped作用样式仅作用于当前组件避免全局样式污染原理Vue 自动为组件元素添加唯一属性样式仅匹配该属性。2. 常用样式技巧表单元素统一边框、圆角、内边距聚焦时修改边框色按钮hover状态修改背景色cursor: pointer显示手型表格border-collapse: collapse合并边框th加背景色区分表头。六、Vue 基础高频问题1. 为什么data是函数组件复用时每个实例拥有独立的数据源避免数据共享导致的错乱如多个用户列表组件共用一个list。2. 双向绑定原理v-model是语法糖文本框v-modelxxx≈:valuexxx inputxxx $event.target.value复选框v-modelxxx≈:checkedxxx changexxx $event.target.checked。3. 什么是响应式数据变化 → Vue 自动更新页面无需手动操作 DOM注意直接修改数组下标 / 对象属性如this.list[0].name xxx是响应式的但this.list[0] { ... }需用Vue.setVue 2。4.label与input绑定的意义label fornameinput idname点击标签可聚焦输入框提升操作体验屏幕阅读器能识别标签与输入框的关联提升可访问性。四.源代码template div div h2添加用户/h2 div classform-item label fornamespan classpoint*/span姓名/label input typetext idname v-modeluser.name placeholder请输入姓名 blurcheckName stylemargin-bottom: 10px; / /div div v-ifnameEmpty classpoint stylemargin: 0 0 15px 60px;请输入姓名/div div classform-item label foragespan classpoint*/span年龄/label input typenumber idage v-modeluser.age placeholder请输入年龄 blurcheckAge stylemargin-bottom: 10px; / /div div v-ifageEmpty classpoint stylemargin: 0 0 10px 60px;请输入正确的年龄/div div classform-item label foremailspan classpoint*/span邮箱/label input typeemail idemail v-modeluser.email placeholder请输入邮箱 blurcheckEmail stylemargin-bottom: 10px; / /div div v-ifemailEmpty classpoint stylemargin: 0 0 10px 60px;请输入正确的邮箱/div button clickaddUser classbtn提交/button button clickresetForm classbtn重置/button /div hr stylewidth: 700px; margin: 20px 0;/ div classuser-list h2用户列表/h2 table classuser-table thead tr th stylewidth: 23px; input typecheckbox v-modelselectedAll changeselectAll classall-check/ /th th stylewidth: 23px;序号/th th stylewidth: 80px;姓名/th th stylewidth: 30px;年龄/th th stylewidth: 120px;邮箱/th th stylewidth: 80px;操作/th /tr /thead tbody tr v-iflist.length 0 td colspan6 styletext-align: left;暂无用户信息/td /tr tr v-for(item, index) in list :keyitem.id td stylewidth: 23px; input typecheckbox v-modelitem.selected changehandleCheckboxChange(index) / /td td stylewidth: 23px;{{ index 1 }}/td td stylewidth: 80px; span v-if!item.edit{{ item.name }}/span input v-else typetext v-modelitem.name stylewidth: 80px;/ /td td stylewidth: 30px; span v-if!item.edit{{ item.age }}/span input v-else typenumber v-modelitem.age stylewidth: 30px;/ /td td stylewidth: 120px; span v-if!item.edit{{ item.email }}/span input v-else typetext v-modelitem.email stylewidth: 120px;/ /td td stylewidth: 80px; button clickeditUser(index) v-if!item.edit classbtn修改/button button clicksaveUser(index) v-else classbtn保存/button button clickdeleteUser(index) v-if!item.edit classbtn删除/button button clickcancelEdit(index) v-else classbtn取消/button /td /tr tr td colspan6 button clickdeleteAll classbtn删除所有/button button clickdeleteSelected classbtn批量删除/button button clickeditSelected classbtn批量修改/button /td /tr /tbody /table /div /div /template script export default { name: UserManager, data() { return { user: { id: 0, name: , age: , email: , selected: false, edit: false, tmp: {} }, list: [], nameEmpty: false, ageEmpty: false, emailEmpty: false, selected: [], selectedAll: false, num: 0 }; }, methods: { addUser() { this.checkName(); this.checkAge(); this.checkEmail(); if (this.nameEmpty || this.ageEmpty || this.emailEmpty) return; if (this.list.some(item item.name this.user.name)) { alert(姓名已存在); return; } const newUser { id: this.num, name: this.user.name, age: this.user.age, email: this.user.email, selected: false, edit: false, tmp: {} }; this.list.push(newUser); this.resetForm(); alert(添加成功); }, resetForm() { this.user { id: this.num, name: , age: , email: , selected: false, edit: false, tmp: {} }; this.nameEmpty false; this.ageEmpty false; this.emailEmpty false; }, checkName() { this.nameEmpty this.user.name.trim() ; }, checkAge() { if (this.user.age || this.user.age 0 || this.user.age 120) { this.ageEmpty true; this.user.age ; } else { this.ageEmpty false; } }, checkEmail() { const emailReg /^[a-zA-Z0-9_.-][a-zA-Z0-9-](\.[a-zA-Z0-9-])*\.[a-zA-Z0-9]{2,6}$/; this.emailEmpty !emailReg.test(this.user.email.trim()); }, selectAll() { this.list.forEach((item) { item.selected this.selectedAll; }); this.selected this.selectedAll ? this.list.map((_, index) index) : []; }, handleCheckboxChange(index) { const item this.list[index]; if (item.selected) { if (!this.selected.includes(index)) { this.selected.push(index); } } else { this.selected this.selected.filter(i i ! index); this.selectedAll false; } }, deleteUser(index) { if (confirm(确定要删除该用户吗)) { this.list.splice(index, 1); this.selected this.selected.filter(i i ! index); } }, deleteSelected() { if (this.selected.length 0) { alert(请选择要删除的用户); return; } if (confirm(确定要删除选中的用户吗)) { this.selected.sort((a, b) b - a).forEach(index { this.list.splice(index, 1); }); this.selected []; this.selectedAll false; } }, deleteAll() { if (this.list.length 0) { alert(暂无用户可删除); return; } if (confirm(确定要删除所有用户吗)) { this.list []; this.selected []; this.selectedAll false; } }, editUser(index) { const item this.list[index]; item.edit true; item.tmp { name: item.name, age: item.age, email: item.email }; }, saveUser(index) { const item this.list[index]; if (item.name.trim() ) { alert(姓名不能为空); return; } if (item.age || item.age 0 || item.age 120) { alert(请输入0-120之间的有效年龄); return; } const emailReg /^[a-zA-Z0-9_.-][a-zA-Z0-9-](\.[a-zA-Z0-9-])*\.[a-zA-Z0-9]{2,6}$/; if (!emailReg.test(item.email.trim())) { alert(请输入正确的邮箱格式); return; } const isDuplicate this.list.some((i, idx) idx ! index i.name item.name); if (isDuplicate) { alert(姓名已存在); return; } item.edit false; item.tmp {}; alert(修改成功); }, cancelEdit(index) { const item this.list[index]; item.edit false; item.name item.tmp.name; item.age item.tmp.age; item.email item.tmp.email; item.tmp {}; }, editSelected() { if (this.selected.length 0) { alert(请选择要修改的用户); return; } this.selected.forEach(index { const item this.list[index]; item.edit true; item.tmp { name: item.name, age: item.age, email: item.email }; }); } } }; /script style scoped .point { color: red; font-size: 12px; margin-right: 5px; } .form-item { margin: 3px 0; font-size: 14px; } input { border: 1px solid #ccc; border-radius: 4px; height: 22px; padding: 0 5px; } input:focus { outline: none; border-color: #999; } .btn { margin: 0 5px 5px 0; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; background-color: #f5f5f5; cursor: pointer; } .btn:hover { background-color: #bfbcbc; } .user-table { border-collapse: collapse; width: 700px; margin-top: 10px; } .user-table th, .user-table td { border: 1px solid #ccc; padding: 8px; text-align: left; } .user-table th { background-color: #f5f5f5; font-weight: bold; } /style