ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue2+ElementUI宠物管理系统前端源码拆解与改造实战

Vue2+ElementUI宠物管理系统前端源码拆解与改造实战 简介这是一套面向宠物店与猫咖场景的前端管理系统源码基于HTML、CSS和JavaScript构建并引入Vue、Axios与Element UI等流行库来完善交互与数据请求适合前端初学者练习参考也适合小店管理者直接体验管理后台界面。资源压缩包共含24个文件包括9张图片、6个HTML页面、4个JavaScript脚本和1个CSS样式表并额外提供字体图标文件和TXT说明文档整体体积2.35MB便于下载和部署。系统已经覆盖登录、用户信息、宠物信息、消息管理等常用模块同时将脚本样式按目录归类开发人员可以快速定位和修改对应功能也可以在此之上扩展预约、商品、会员管理等业务以适配不同门店的个性化需求。目前已有120人学习使用对于希望获得一套可运行、易扩展的前端管理页面样例的学习者来说这份源码具有不错的参考价值与复用价值。1. 从一堆静态页面里拆出猫咖系统的前端骨架接手这套宠物店猫咖管理系统源码时第一感觉是文件不少但结构并不复杂——23 个文件里真正决定系统行为的只有 6 个 HTML 页面、4 个 JS 文件和 1 个 CSS 文件剩下的都是图片和字体资源。这是一个典型的「纯前端 本地数据模拟」方案没有后端服务所有页面之间的跳转、数据传递、消息展示靠的是 Vue 实例挂载、localStorage 存储和 axios 的本地 mock 请求。对于正在做前端开发、尤其是刚接触 Vue 2 和 ElementUI 组件库的人来说这套源码的价值在于它把登录鉴权、用户信息展示、宠物档案管理、消息中心这些后台管理系统的常见模块用最少的外部依赖完整地串了起来。你可以直接改几个文件就把它接入真实接口也可以把它当作一个学习 Vue 实例化、组件通信和页面路由切换的练习样本。下面按「文件职责 → 页面交互 → 数据流转 → 排错改造」的顺序把这套源码拆开。2. 文件架构与模块职责四个 JS 文件才是系统的真正骨架表面上 HTML 文件决定了页面长什么样但把这套系统粘合在一起的是js目录下的四个 JavaScript 文件。很多新手拿到源码后直接打开index.html发现页面元素渲染不出来往往就是因为没有先搞清楚这些脚本的加载顺序和依赖关系。2.1 目录结构与加载顺序解压后的根目录结构如下upload.zip ├── css/ │ └── index.css ├── fonts/ │ ├── element-icons.woff │ └── element-icons.ttf ├── img/ # 9 个图片资源含 logo、头像、宠物照片 ├── js/ │ ├── vue.min.js # Vue 2.x 核心库本地化 │ ├── axios.min.js # HTTP 请求库本地化 │ ├── elementui.js # ElementUI 组件库本地化 │ └── mycommon.js # 公共方法请求封装、数据模拟、公共函数 ├── Login.html # 登录页 ├── index.html # 系统首页/导航入口 ├── index2.html # 信息列表页 ├── user2.html # 用户个人信息页 ├── pet.html # 宠物信息管理页 └── Message.html # 消息中心页加载顺序上index.html的head或body底部一般是这样的引用链!-- 顺序不能乱Vue → ElementUI → axios → 公共方法 -- link relstylesheet hrefcss/index.css link relstylesheet hrefhttps://unpkg.com/element-ui/lib/theme-chalk/index.css script srcjs/vue.min.js/script script srcjs/elementui.js/script script srcjs/axios.min.js/script script srcjs/mycommon.js/script提示ElementUI 的样式依赖fonts目录下的两个字体文件如果只拷贝了 CSS 没拷贝字体图标会显示成方块。这是静态页面移植时最常见的资源缺失问题。2.2 mycommon.js全局配置与数据模拟层mycommon.js是整个项目的核心工具文件。它至少承担三个职责定义公共的请求基础路径、封装统一的 HTTP 方法、mock 登录和列表数据。常见的做法是把它设计成一个全局对象挂到window上这样所有页面都能直接调用。// mycommon.js 核心逻辑示意 window.common { baseURL: http://localhost:8080/api, // 实际使用时替换为后端地址 // 统一请求方法失败时自动弹出 ElementUI 错误提示 request(method, url, data) { return axios({ method: method, url: this.baseURL url, data: data }).then(res { // 约定后端返回 { code: 200, data: ... } if (res.data.code 200) { return res.data.data; } else { this.$message.error(res.data.msg || 请求失败); return Promise.reject(res.data); } }).catch(err { // 开发环境下打印完整错误信息 console.error([common.request], err); return Promise.reject(err); }); } };这段代码虽然只有十几行但已经包含了实际项目中请求层的基本要素baseURL保证接口地址可配置、统一响应拦截让页面代码不用重复处理错误状态、Promise链式调用让业务代码更简洁。如果要把这套前端接到真实后端只需要改baseURL并调整响应解构逻辑即可。2.3 HTML 页面与模块的对应关系六个 HTML 页面分别对应系统中的六个功能模块每个页面都实例化了一个独立的 Vue 实例通过window全局对象来共享数据。文件功能定位核心 Vue 数据Login.html登录鉴权loginForm、rules校验规则index.html系统首页、导航入口menuList、userInfoindex2.html信息列表、数据展示tableData、paginationuser2.html个人中心、资料维护userForm、avatarUrlpet.html宠物档案管理petList、breedOptionsMessage.html消息中心、公告管理messageList、unreadCount这种一个页面一个 Vue 实例的做法在小型后台系统中非常实用。它避免引入 Vue Router 和 Vuex 带来的概念负担页面之间靠window.location.href跳转数据靠 localStorage 或 sessionStorage 传值代码直白排错容易。3. 从登录到首页拆解 Vue ElementUI 的表单交互与路由跳转这一章进入实际操作层面。系统里最完整的一条交互链路是用户输入账号密码 → 前端校验 → 模拟登录请求 → 存储登录状态 → 跳转到首页。这条链路里有几个值得细看的实现点。3.1 登录页的表单校验与本地模拟登录Login.html是系统的入口它使用 ElementUI 的el-form组件配合rules校验规则实现表单验证。核心逻辑如下template el-form :modelloginForm :rulesrules refloginForm el-form-item label用户名 propusername el-input v-modelloginForm.username placeholder请输入用户名/el-input /el-form-item el-form-item label密码 proppassword el-input typepassword v-modelloginForm.password placeholder请输入密码/el-input /el-form-item el-button typeprimary clickhandleLogin登 录/el-button /el-form /template script new Vue({ el: #app, data() { return { loginForm: { username: , password: }, rules: { username: [{ required: true, message: 请输入用户名, trigger: blur }], password: [{ required: true, min: 6, message: 密码长度不能少于6位, trigger: blur }] } }; }, methods: { handleLogin() { this.$refs.loginForm.validate(valid { if (!valid) return; // 使用 common.request 发送登录请求 common.request(post, /login, this.loginForm) .then(data { localStorage.setItem(token, data.token); localStorage.setItem(userInfo, JSON.stringify(data.user)); window.location.href index.html; }); }); } } }); /script这里有一个容易忽略的点this.$refs.loginForm.validate()是 ElementUI 表单组件提供的校验方法它的回调参数valid只有在所有表单项都通过校验时才为true。实际开发中如果发现点击登录没反应优先检查是否忘记在el-form-item上添加prop属性——没有proprules中的校验规则不会绑定到对应字段上。登录成功后代码把token和userInfo写入 localStorage然后用window.location.href做页面跳转。这种跳转方式的优势是简单直接、不依赖路由库每个页面加载时都会重新初始化 Vue 实例数据不会残留劣势是每次跳转都是一次完整的页面刷新无法做局部过渡动画。对于当前这种模块化不深的系统利大于弊。3.2 首页导航动态菜单与页面入口index.html作为登录后的落地页承担着系统导航的功能。它的 Vue 实例中维护了一份菜单配置数组// index.html 中的菜单配置 new Vue({ el: #app, data() { return { activeMenu: dashboard, menuList: [ { title: 首页概览, icon: el-icon-s-home, path: index.html }, { title: 宠物档案, icon: el-icon-s-custom, path: pet.html }, { title: 信息列表, icon: el-icon-s-order, path: index2.html }, { title: 个人中心, icon: el-icon-user, path: user2.html }, { title: 消息中心, icon: el-icon-message, path: Message.html } ], userInfo: JSON.parse(localStorage.getItem(userInfo) || {}) }; }, methods: { // 根据登录用户的角色过滤可见菜单 filterMenu() { if (this.userInfo.role admin) return this.menuList; return this.menuList.filter(item item.path ! Message.html); } } });menuList数组把每个菜单项的标题、图标和跳转路径绑定在一起页面上用v-for循环渲染el-menu-item点击时通过window.location.href完成跳转。filterMenu方法演示了一个很实用的权限过滤思路普通用户看不到消息中心入口管理员则可以看到全部菜单。虽然是前端控制安全性不高但对于纯展示型系统已经够用。3.3 中间页面的数据渲染套路pet.html和index2.html是典型的数据列表页面它们的实现套路几乎一致mounted钩子中调用common.request获取数据赋值给tableData然后通过 ElementUI 的el-table渲染。以pet.html为例new Vue({ el: #app, data() { return { petList: [], loading: false, breedOptions: [英短, 美短, 布偶, 橘猫, 泰迪, 金毛], searchKeyword: }; }, computed: { // 前端关键字过滤避免频繁请求接口 filteredPets() { return this.petList.filter(pet pet.name.includes(this.searchKeyword) || pet.breed.includes(this.searchKeyword) ); } }, mounted() { this.fetchPetList(); }, methods: { fetchPetList() { this.loading true; common.request(get, /pet/list) .then(data { this.petList data; }) .finally(() { this.loading false; }); } } });这里的computed属性filteredPets是一个值得学习的细节。它在前端完成了关键字过滤用户输入搜索词时不触发新的 HTTP 请求响应速度和用户体验都优于每次输入都请求后端。真实项目中如果宠物数据量达到上千条仍然建议把搜索逻辑放到后端通过?keywordxxx传给接口前端只负责展示。当前这套实现更适合数据量在百条以内的管理后台。4. 让数据动起来localStorage 共享、公共请求封装与模块化改造静态页面的短板在于无法持久化数据刷新页面后所有改动都会丢失。这套源码通过 localStorage 和全局对象在一定程度上缓解了这个问题但距离真正可用的系统还有一段距离。这一章先从源码的现有实现讲起再给出改造方向。4.1 localStorage 在用户信息传递中的实际用法由于没有后端登录用户的身份信息需要在多个页面之间共享。源码中使用 localStorage 保存整个用户对象每个页面加载时都从 localStorage 中读取// user2.html 中初始化用户信息 new Vue({ el: #app, data() { return { userForm: { username: , nickname: , phone: , email: , role: , avatar: } }; }, created() { const user JSON.parse(localStorage.getItem(userInfo) || {}); // 将缓存中的用户数据映射到表单 Object.keys(this.userForm).forEach(key { if (user[key] ! undefined) { this.userForm[key] user[key]; } }); }, methods: { saveProfile() { // 更新 localStorage 中的用户信息 localStorage.setItem(userInfo, JSON.stringify(this.userForm)); this.$message.success(资料已更新); } } });这段代码演示了一个典型问题多个页面都需要用户信息每个页面都写一遍JSON.parse(localStorage.getItem(...))维护成本高、容易出错。更好的做法是把这些操作封装进mycommon.js// mycommon.js 中增加用户信息存取方法 window.common { // ... 其他方法 getUser() { try { return JSON.parse(localStorage.getItem(userInfo)) || {}; } catch (e) { return {}; } }, setUser(user) { localStorage.setItem(userInfo, JSON.stringify(user)); }, clearUser() { localStorage.removeItem(userInfo); localStorage.removeItem(token); } };封装后页面里的调用就变成common.getUser()即使将来改成从接口获取用户信息也只需要改common内部实现各页面代码不用动。这体现了设计模式中的「依赖倒置」思想——页面依赖的是common这个抽象接口而不是 localStorage 这个具体实现。4.2 从 mock 数据到真实接口axios 请求改造目前源码中的数据来源是前端写死的 mock 数据所有接口调用都返回Promise.resolve(mockData)。如果要接入真实后端只需要修改mycommon.js中的请求层并把响应处理逻辑拆出来// mock 示例返回 Promise 模拟异步请求 const petMockData [ { id: 1, name: 豆豆, breed: 英短, age: 2, status: 在店 }, { id: 2, name: 雪球, breed: 布偶, age: 1, status: 已售 }, { id: 3, name: 旺财, breed: 金毛, age: 3, status: 在店 } ]; // 改造前每个页面自己写 mock // 改造后统一封装替换 baseURL 即可 function fetchPetList() { return new Promise(resolve { setTimeout(() resolve(petMockData), 300); }); } // 接入真实接口时mycommon.js 中的实现 function fetchPetList() { return axios.get(http://your-server.com/api/pet/list) .then(res res.data.data); }注意mock 数据中的setTimeout模拟了网络延迟这是为了让页面在开发阶段就能看到 loading 效果。接入真实接口后如果觉得每次请求都转圈很烦可以在axios的interceptors里统一处理 loading 开关而不是在业务代码中手动控制。4.3 模块化改造方案让公共代码不再重复随着功能增多把所有代码塞在mycommon.js里会越来越难维护。一个简单有效的改造思路是按照「工具函数 API 接口 公共组件」三个维度拆文件文件职责包含内容utils/storage.js本地存储操作getUser、setUser、getToken、clearAllutils/request.jsaxios 实例封装拦截器、统一错误处理、loadingapi/pet.js宠物接口定义fetchList、addPet、updatePet、deletePetapi/user.js用户接口定义login、logout、getProfile、updateProfilecomponents/公共组件分页条、图片上传、状态标签拆完之后页面里的调用方式变成import { fetchPetList } from ../api/pet.js依赖关系一目了然代码也更容易测试。这套源码目前的结构适合 10 个页面以内的系统超过这个规模就需要考虑 Vue Router Vuex 的单页应用方案了。5. 静态资源路径错乱、CDN 失效与数据持久化三个高频问题的定位方法纯前端系统的坑大多不在逻辑层而在资源加载和运行环境上。这一章整理了这套源码最容易遇到的几类问题全部有明确的定位和修复路径。5.1 页面白屏 / ElementUI 组件不渲染现象打开index.html后页面空白控制台报错Element is not defined或Vue is not defined。原因排查顺序确认js/vue.min.js和js/elementui.js是否成功加载——在浏览器开发者工具的 Network 面板中查看这两个文件的状态码是否为 200。查看脚本加载顺序。elementui.js依赖vue.min.js先加载如果顺序颠倒ElementUI 会报错。检查 CSS 文件是否引入。ElementUI 组件渲染需要样式支撑缺少element-ui/lib/theme-chalk/index.css时组件会出现但样式错乱。如果本地文件缺失最快速的替代方案是换用 CDN!-- 使用 unpkg 提供的 CDN 资源 -- link relstylesheet hrefhttps://unpkg.com/element-ui/lib/theme-chalk/index.css script srchttps://cdn.jsdelivr.net/npm/vue2.6.14/dist/vue.min.js/script script srchttps://unpkg.com/element-ui2.15.14/lib/index.js/script提示生产环境不建议长期依赖第三方 CDN。稳妥做法是把这些资源文件下载到本地和项目一起部署避免因外链失效导致整个系统无法使用。5.2 图片不显示 / 字体文件 404img目录下有 9 张图片页面中引用它们的路径应该以相对路径img/xxx.jpg开头。如果页面放在子目录中路径就变成了../img/xxx.jpg可以通过以下方式统一处理!-- 使用 HTML 的 base 标签统一基准路径 -- base href/pet-shop/fonts目录下的element-icons.woff和element-icons.ttf是 ElementUI 字体图标的核心依赖。如果项目部署在二级路径比如http://host/pet-shop/CSS 中引用的字体路径可能无法解析。此时需要在index.css中显式指定font-face { font-family: element-icons; src: url(../fonts/element-icons.woff) format(woff), url(../fonts/element-icons.ttf) format(truetype); }5.3 数据无法持久化 / 刷新后状态丢失当前源码的数据全部存在 localStorage 中刷新页面不会丢失但存在两个隐患一是 localStorage 的存储上限约 5MB图片、文件不能往里面放二是不同浏览器的存储策略不同用户清理浏览器缓存后数据会被清空。如果想在不引入后端的情况下提升数据可靠性可以用indexedDB替代 localStorage 存储业务数据。下面是一个把宠物列表存入 indexedDB 的简化示例// 使用 indexDB 保存宠物数据的核心逻辑 const dbName PetShopDB; const storeName pets; function openDB() { return new Promise((resolve, reject) { const request indexedDB.open(dbName, 1); request.onupgradeneeded (e) { const db e.target.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName, { keyPath: id }); } }; request.onsuccess (e) resolve(e.target.result); request.onerror (e) reject(e.target.error); }); } async function savePet(pet) { const db await openDB(); const tx db.transaction(storeName, readwrite); tx.objectStore(storeName).put(pet); }localStorage 适合存登录状态、用户偏好这类体量小且非结构化的数据宠物档案、订单记录这类结构化业务数据建议按上例迁移到 indexedDB。如果数据量更大或需要多端同步就必须引入后端数据库前端只负责通过 API 读写。6. 进阶无后端场景下用位运算实现简单的角色权限控制纯前端系统的权限控制一直是个痛点因为代码运行在浏览器里用户可以通过开发者工具直接修改运行数据。但很多内部管理系统并不需要对抗恶意攻击只需要「防君子不防小人」——让普通用户看不到管理入口、无法通过手动修改 URL 访问受保护页面即可。这一章介绍一种用位运算实现的多角色权限方案可直接应用到这套猫咖系统中。先明确需求系统里有三种角色——管理员admin、店员staff、会员member。管理员可以访问全部页面和所有操作按钮店员可以查看宠物档案和用户信息但不能访问消息管理和系统设置会员只能查看宠物列表和修改个人资料。用二进制位来表示每种角色拥有的权限// 权限位定义每个权限独立占用一个二进制位 const PERMISSIONS { VIEW_PET: 1 0, // 1 查看宠物 EDIT_PET: 1 1, // 2 编辑宠物 VIEW_USER: 1 2, // 4 查看用户 EDIT_USER: 1 3, // 8 编辑用户 VIEW_MESSAGE: 1 4, // 16 查看消息 SEND_MESSAGE: 1 5, // 32 发送消息 SETTINGS: 1 6 // 64 系统设置 }; // 各角色的权限集合 const ROLE_PERMISSIONS { admin: PERMISSIONS.VIEW_PET | PERMISSIONS.EDIT_PET | PERMISSIONS.VIEW_USER | PERMISSIONS.EDIT_USER | PERMISSIONS.VIEW_MESSAGE | PERMISSIONS.SEND_MESSAGE | PERMISSIONS.SETTINGS, // 127 staff: PERMISSIONS.VIEW_PET | PERMISSIONS.EDIT_PET | PERMISSIONS.VIEW_USER | PERMISSIONS.VIEW_MESSAGE, // 23 member: PERMISSIONS.VIEW_PET | PERMISSIONS.EDIT_USER // 9 };把这个权限表集成到mycommon.js中并提供一个每次页面加载时执行一次的方法// mycommon.js 增加权限验证模块 window.common { // ... 已有方法 PERMISSIONS: PERMISSIONS, ROLE_PERMISSIONS: ROLE_PERMISSIONS, // 检查当前用户是否拥有指定权限 hasPermission(permission) { const user this.getUser(); const rolePerms this.ROLE_PERMISSIONS[user.role]; if (rolePerms undefined) return false; // 关键运算按位与结果非 0 说明权限位存在 return (rolePerms permission) ! 0; }, // 初始化时统一校验页面访问权限 initPage() { const pagePermMap { pet.html: PERMISSIONS.VIEW_PET, index2.html: PERMISSIONS.VIEW_USER, Message.html: PERMISSIONS.VIEW_MESSAGE, user2.html: PERMISSIONS.EDIT_USER }; const currentPage window.location.pathname.split(/).pop(); const requiredPerm pagePermMap[currentPage]; if (requiredPerm !this.hasPermission(requiredPerm)) { // 无权限时统一跳转到首页并给出提示 alert(当前账号无权访问该页面); window.location.href index.html; } } };页面则在created钩子中调用校验方法// 每个受保护页面的 created 钩子 new Vue({ el: #app, created() { common.initPage(); // 无权限则自动跳转 // 后续正常的数据加载逻辑 this.fetchPetList(); } });位运算方案的优势在于权限判断的时间复杂度是 O(1)多条权限的合并用|判断用代码极其简洁。例如判断用户是否同时拥有查看和编辑宠物权限// 同时判断两个权限 if (common.hasPermission(PERMISSIONS.VIEW_PET) common.hasPermission(PERMISSIONS.EDIT_PET)) { // 显示新增宠物按钮 }对于这种权限数量不超过 30 种的场景位运算既不需要数据库表也不需要复杂的嵌套判断一行代码就能完成校验。如果将来权限数量超过 30 种或需要细粒度的数据级控制比如只能查看自己创建的宠物再考虑引入动态路由和接口鉴权不迟。这套基于 HTML、CSS、JavaScript 的源码已经把静态页面的模块拆分、公共方法封装、数据模拟请求都做了一遍剩下的就是按业务需要往这个骨架上填肉。本文还有配套的精品资源点击获取
返回列表