# gulishop-admin_0425 **Repository Path**: newsegmentfault/gulishop-admin_0425 ## Basic Information - **Project Name**: gulishop-admin_0425 - **Description**: 关于谷粒商城的后台管理项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-10-10 - **Last Updated**: 2023-12-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 说明 * 基于Vue3的电商中台管理项目 * 技术栈: TS + Vue3 + VueRouter4 + Pinia + ElementPlus * 当前为完成版 * 学习参见 文档 文件夹下的MD文件 ## 项目介绍 前台、后台、前端、后端区别 # day01 ## 项目目录介绍 > 注意: > .env.development .env.production 理解成项目的环境变量 > vite通过 import.meta.env.VITE_API_URL 拿到文件中设置的环境变量 ## 接口文档 - 权限管理: http://39.98.123.211:8170/swagger-ui.html - 商品管理:http://39.98.123.211:8510/swagger-ui.html ## 托管git vue3base地址: https://gitee.com/newsegmentfault/0425vue3base 项目地址: https://gitee.com/newsegmentfault/gulishop-admin_0425 线上地址: http://101.43.227.123:5555/home ## 修改小功能 * 登录页边距 - src/styles/index.scss * 登录页背景图 - src/views/login/index.vue * 登录按钮文字 - src/views/login/index.vue * 退出登录文字 - src/layout/components/Navbar.vue 目的: 熟悉页面的解构 * sideBar 侧边栏 - 使用了 递归组件 sidebarItem 渲染菜单栏 * navBar 顶部导航 - 修改"退出登录"的文字 * appMain 页面的主要显示区域 - 使用 componet组件 的is属性来渲染组件,is中配置哪个组件,就显示哪个组件 ## 分析登录 一打开页面看到的就是登录,所以分析登录(分析过程在store当中),发现store中的actions都是假的,需要我们自己去写整个登录过程 登录需要接口,写api文件 ## 写接口 - 限制TS数据类型 > 写登录 - 点击登录按钮写登录的时候发现需要调接口,我们去写api函数,在给stor中数据赋值得到的token的时候,发现飘红 ## axios的二次封装 - TS的类型限制 ### api函数中使用request调用post这里限制入手 第一个类型不关注,关注第二个类型,将作为pose函数的返回值的promise的结果存在,而我们在调用这个login这个接口方法的时候,会await等待这个promise的结果,最终拿到的就是`TokenModel` ```js api文件 interface TokenModel { token: string } export default { login(loginData: LoginModel) { return request.post(`/admin/acl/index/login`, loginData) }, } store actions: { async login(username: string, password: string) { try { // 这里的result结果类型由post方法的类型限制的第二个类型决定 // 数据结果由axios的二次封装决定,响应拦截器返回的数据决定 let result = await userinfoApi.login({username, password}); } catch (error) { } } } ``` ### request响应拦截器中的响应报文处理 ```js /* 定义response对象的data接口 */ interface ResponseData { code: number; data: T; message: string; } // 添加响应拦截器 service.interceptors.response.use( /* 约束一下response */ // response 拿到的是响应报文 async (response: AxiosResponse, any>) => { // 对响应数据做点什么 const res = response.data; // 响应体内容 - 就是接口返回的数据 if (res.code !== 20000 && res.code !== 200) { /* 成功数据的code值为20000/200 */ // ...todo error tootip // 统一的错误提示 return Promise.reject(service.interceptors.response); } else { return res.data; /* 返回成功响应数据中的data属性数据 */ // return res.data决定了 ---------> 调用接口返回的数据结果 } }, (error) => { // ...todo error tootip // 对响应错误做点什么 return Promise.reject(error); } ); ``` > 结论: 以后只要写api的时候,直接在 `request.post<类型>`,在请求完数据的地方.的时候就有提示了 ## 写登录 点击登录按钮,调用actions的login方法,调用接口,拿数据,存token(store和localstorage),跳转页面,跳转到路由记录的页面(或者根路径) ```js store -> actions async login (username: string, password: string) { try { let result = await userInfoApi.login({ username, password }); // console.log(result); // 存store this.token = result.token; // 存localstorage setToken(result.token); } catch (error) { ElMessage.error("登录失败,请重试") return Promise.reject(error) } }, ``` ## 路由跳转 经过路由守卫,遵循分析的登录逻辑(写在src/store/userinfo.ts) ## 获取用户信息 - 重新存储(结构发生变化) 重写了获取用户信息的接口,获取到用户信息要存两个内容到store中 * 用户基本信息 - 接口返回的数据 * 用户的菜单展示信息 - 这个展示信息是根据返回数据的权限来进行设置的,目前写的假的,在权限的学习的时候会改进 ```js store -> actions async getInfo () { try { let result = await userInfoApi.info(); this.userinfo = result; // 个人信息 - 基本信息 this.menuRoutes = staticRoutes; // 个人信息 - 菜单展示信息 - 先写死,还没到权限的地方 } catch (error) { ElMessage.error('获取个人信息失败,请重试'); return Promise.reject(error); } }, ``` ## 退出登录 点击NavBar组件中的退出登录按钮,触发actions,调用接口,清除token(清除两个地方,store和localstoreage)和个人信息 ```js store -> actions async reset () { try { await userInfoApi.logout(); // 后端去吧token作废 // 清除 localstorage 中的token removeToken(); // 清除store中token this.token = ""; // 清除个人用户信息 this.userinfo = resetUserInfo(); } catch (error) { ElMessage.error("退出登录失败,请重试") return Promise.reject(error); } }, ``` ## 渲染左侧菜单 ```html ``` 通过在SideBar中查看,发现侧边菜单栏是`sidebar-item`这个组件,通过循环出来的,那么数据来源: ​ `userInfoStore.menuRoutes` -> 在获取个人信息的时候赋值 `this.menuRoutes = staticRoutes;` -> staticRoutes 从路由routes中导出的 > 目前写死,在后期获取用户权限的地方会修改 在 `sidebar-item` 渲染的时候,当只有一个子集路由的时候,直接渲染这个子集路由作为一级菜单,如果有多个子集路由的时候,渲染二级菜单 > 拓展讲了 icon 的显示 - 自己封装全局组件 - src/components/SvgIcon/index.vue # day02 ## 品牌管理 步骤: 1. 静态页面搭建 - 不要涉及到数据,不要涉及到事件,如果想带上数据,就上分页的数据即可 2. 初始化数据展示 查 - 调用接口,拿数据,展示 * 写接口 - 先看接口文档(或者去线上调试),拿到调用接口返回的数据类型,给api文件中写接口函数的时候写 interface 类型限制 * 再去页面中调用接口,再`onmounted`中调用接口,拿到数据进行展示 * 数据给table展示-注意table-column的prop属性用来展示字段 * 自定义列 - 图片列 和 操作列 * 翻页 - 交互(每页条数改变、页码改变) 3. 交互 1. 新增 * 点击新增按钮,弹出 `dialog` 弹框,先把 `dialog` 弹框的静态展示做出来 * 表单收集数据 - 图片上传 upload组件 - 上传成功的回调中修改`tmForm`数据 * 点击保存(考虑取消),调用接口 - 写api函数,限制TS类型 2. 修改 回显数据 - 回显表格中的数据,注意深拷贝(拓展-面试题-说说拷贝) 点击保存(考虑取消),调用接口 - 书写api函数,TS类型限制 3. 删除 写api接口函数,TS限制类型 点击删除按钮,调用接口 4. 表单校验 - 满足两个条件 1. `el-form`的rules属性 2. `el-form-item`的prop 5. bug - 单独对某一个字段校验 trademark -> 3遍 # day03 分页页面 * 三级分类组件 * 下面主体内容 * 列表展示 * 编辑页面 ## 三级分类 单独封装一个三级分类组件 `CategorySelector`,spu管理模块也用到了三级分类,数据存储放到了pinia中。 ### 静态页面搭建-交互 直接整上三个下拉框,el-select v-model先不填,涉及到数据的先不动 数据放在store中,每个选中的id和拉下列表的数据 组件初始化展示的时候,先发请求拿到一级分类的数据,当选择了一级分类的数据之后,根据一级选择的id拿二级分类的数据,当二级分类选择之后,根据二级分类的id拿三级分类的数据。这里当一级分类选择发生改变的时候,清空二级分类和三级分类的相关数据;当二级发生变化的时候,清空三级分类相关数据。 当选择完三级分类的数据之后,选择到的三级分类的id存到到了store中,在页面中如何获取? 在页面中可以使用watch监听,监听到store当中数据变化## ## 平台属性页面渲染 列表展示页渲染、新增/编辑页面的渲染 ### 列表展示页渲染 静态页面搭建 - 所有涉及到数据的地方先放着 初始化数据展示 - 只有当三级分类选择之后才展示页面数据,这里使用watch监听store中的`category3Id`即可,然后发请求,拿数据,渲染 > 注意: > > 1. 发请求,拿数据,先测试接口,看返回值,拿着返回值加TS限制数据类型 > 2. 加TS限制类型的时候,注意id,有id的大概率是需要加问号的(有id的大概率是新增、编辑的时候数据中相差的id) 编辑功能在完成新增功能之后做的 删除功能最后做的 > 注意:所有删除(指的是大概率要调用删除接口的地方)的地方都需要双重确认(double confirm) ### 新增/编辑页面的渲染 静态页面展示 - 一个form表单 - 一个table表格 新增的时候没有初始化数据展示、只有编辑的时候有,编辑的时候需要回显数据 #### 新增 先测试接口,拿到保存需要什么数据,拿到之后做两件事: 1. api的书写,加TS类型限制 2. 这个数据就是保存时候我们需要收集的数据,在编辑页创建出这个数据,收集 ##### 交互*** 属性名直接v-model收集 属性值添加到表格当中,创建一个数据push到即将要收集的`attrForm.attrValueList`列表中,在表格中展示 > 注意:先把属性值写死,至于表格中和input框的交互最后单独写 点击保存调用接口,创建一条数据,创建成功需要切回主列表界面,重新获取数据 ### 主列表编辑 编辑交互,深拷贝一下主列表的数据,回显一下即可,当点击保存的时候,调用还是之前保存的接口,只不过数据中多了一个id,保存数据成功需要切回主列表界面,重新获取数据 ### 主列表删除 删除的时候加个双重确认即可,当用户点击确认调用接口删除即可 ### input和div切换展示 1. 数据控制它俩的切换,给表格的每一行数据加了一个`inputVisible`属性,是一个布尔值,用来控制input的显示和隐藏 2. 当input自动展示的时候需要自动获取焦点,失焦的时候需要切换`inputVisible`的值,展示div > 注意: > > 1 > > 当`inputVisible`数据发生变化的时候,直接获取DOM获取不到,需要使用 nextTick 等待DOM的更新,DOM更新完毕之后回执行 nextTick 的回调 > > 问: nextTick 怎么用? > > 当数据发生改变的时候,立马获取DOM是获取不到的,是用 nextTick 等待DOM更新完毕之后再回调中获取DOM即可 > > 2 > > 只要保证页面中只有一个input在展示,使用 `ref="inputRef"` 标签的ref属性获取到的永远是在展示的哪一个 input 框 >拓展: > >面试题: vue2中$set是做什么的?vue3中有没有对应vue2中$set一样的方法呢? > >在vue2当中有 $set 来设置响应式属性,例如说: > >```js >配置项data >data() { > return { > obj: { > name: '张三', // ----> 这个张三是一个响应式数据,通过 数据劫持 > } > } >} > >this.obj.age = 33; // 此时这个age不是一个响应式数据 >this.$set(obj, 'age', 33); // 此时这个age才变成了一个响应式数据 > >这里$set方法带着 obj 对象和 age 这个字符串走了一遍数据劫持(Object.defineProperty),变成了响应式 >``` > >vue3当中没有$set这个功能 > >```js > >``` > > # day04 - SPU ## 分析页面 页面上面是三级分类、下面才是主体内容,主体内容可以切换,分别有Spu列表、Spu新增/编辑、Sku新增 ### Spu列表、Spu新增/编辑、Sku新增 - 三个模块切换 新建了三个组件,通过v-model来切换状态来切换三个组件的显示和隐藏、状态值使用的是枚举 v-model 需要实现: 在vue3当中需要两个条件 1. 给子组件传 :xxx 2. 给子组件绑定事件 @update:xxx > 如果想要直接使用 v-model 指令,xxx 是 modelValue,默认可以省略 > > 使用多个v-model, xxx就是绑定的属性名,使用v-model的时候`v-model:xxx` > > 拓展: v2的 v-model 和 .sync > > v2 -> v-model -----> 1. 子组件使用 `:value` 接收数据 2.组件需要有 `@input`这个事件 > > v2-> .sync ------> 1. ` :xxx.sync="msg"` 2. @update:xxx ### SpuList * 静态搭建 直接使用 element-plus 搭建即可 * 数据初始化展示 书写Api函数、测试接口加TS限制类型、在页面初始化的时候调用,拿到数据展示页面即可 * 交互 分页、新增Spu、新增Sku、编辑Spu、查看当前Spu的Sku列表、删除Spu ### SpuForm 在SpuList点击`添加Spu`按钮切换到`新增Spu`界面 * 静态页面搭建 > 静态展示的时候,图片列表使用的是`el-upload`下的照片墙 * 初始化页面展示 测试api、书写api,加TS限制类型 新增的时候默认调用两个接口、品牌的下拉数据、销售属性的下拉数据 保存接口也测试了 编辑回显数据看了看,调用了4个接口,其中两个新增的时候会调用,剩下两个在获取当前Spu的图片列表和当前spu的销售属性列表 * 交互 收集数据,在点击保存的时候,组装数据调用接口,接口已经写好了,剩下收集数据和组装数据 直接收集的数据 - `spuName、tmId、description` 间接收集的数据 - 图片列表的收集 - 销售属性的收集 * 图片列表数据收集 明确: 收集图片的时候使用一个单独的数据进行收集 > 注意: > > 1. upload组件展示图片必须使用 name 和 url 这两个属性 > 2. 上传图片的时候,第二次选择图片成功回调获取不到 `response` 参数问题,需要把`response`在数组中暂存一下 # day05 ## 总结Spu 一、分析页面 页面分为上下两块内容:上面是三级分类、下面是主体内容 三级分类组件有现成的直接拿过来用 主体内容分为三大块:SpuList、SpuForm、SkuForm,首先要完成这三大块内容的切换,然后改成v-model的形式进行切换,同时使用了TS类型中的枚举,在父组件当中使用一个 `showStatus` 记录当前显示的是哪一个模块;注意:这里把 `v-if` 切换的代码直接放到父组件当中 接下来就是每个模块的书写 二、SpuList模块 * 首先静态页面搭建 一个按钮、一个table表格、一个分页(pagination),不要涉及数据、把边距控制好即可 * 数据初始化展示 * 书写api 首先测试接口(按理来说应该去swagger中测试接口,但是咱们的swagger不标准,直接拿线上的项目测试接口)拿到请求页面的接口是什么,返回的数据是什么,在api文件中书写这个接口,并且限制TS的类型 * 在SpuList页面(组件)中,调用接口展示数据即可 什么时候调用接口,在 `category3Id` 有值的时候调用接口,这里需要 `watch` 监听 `categoryStore.category3Id`(此时监听的时候需要加 `immediate: true`,为什么? -- 原因: 平台属性界面可以直接切换到spu界面、在平添属性中也有三级分类组件,三级分类组件的数据放在store中,可能在store中已经有值了,同时'平台属性管理'界面的watch也要加 `immediate: true` ) 拿到数据存到一个变量中,变量给表格直接展示,在请求接口的同时也可以获取到total这个属性,把分页也做了,需要的数据有 page、limit、total,这个接口中既可以拿到total这个值 * 交互 * 分页交互 翻页的回调和每页条数改变的回调,在回调中组装数据(组装 page 和 limit 数据)、发送请求,重新获取数据进行展示 * 新增Spu * 修改Spu * 删除Spu * 新增Sku * 查看Sku列表弹窗 这里除了分页之外,每个交互都是独立的一个模块,我们单独去处理 > 什么是spu? 什么是sku? > > spu可以理解为一个细分品类,sku理解成一个真实的商品 三、SpuForm - 新增Spu * 搭建静态页面 搭建静态页面的时候,就是一个form表单,在form表单中需要注意的是 图片列表 和 销售属性 的 `el-form-item` 展示 * 初始化页面展示 - 写api、调用接口拿数据 先测试线上,打开页面初始化渲染的接口有即可(新增的时候获取数据的接口就两个: 品牌下拉数据 和 销售属性下拉数据),还需要测试保存的时候接口的传参(保存时候传给后端的参数需要看一看) 测试的过程中写api、加TS类型限制 > 注意:这里要写三个接口: 品牌下拉数据 和 销售属性下拉数据 和 保存Spu * 品牌下拉 和 销售属性下拉 展示一下(就是一个 options 循环渲染一下) * 保存接口有了,就知道要收集的数据,收集的数据分为 直接收集 和 间接收集 直接收集:Spu名称、Spu品牌、Spu描述 间接收集:图片列表收集、销售属性的收集 这里 图片列表收集 和 销售属性的收集 不好做,单独拿出来讲交互 * 图片列表收集 这里使用的组件是 element-Plus 中 `upload` 组件中的 '照片墙' ,收集数据使用单独一个数据收集,这个数据要最终绑定到组件上 `v-model:file-list="spuImageList"` , 这里的 `spuImageList` 就是单独收集的数据,同时需要添加组件的回调三个:上传成功的回调、删除的回调、预览的回调,这里最重要的就是上传成功的回调(这几个回调直接从 element 中粘贴过来) 上传成功的回调中,有三个参数,第三个参数 `uploadFiles` 是我们需要关注的,他是图片列表 ```js const handlerSuccess: UploadProps['onSuccess'] = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {} ``` 这个图片列表是一个数组,每次上传成功之后,都会增加一个图片在数组的末尾(注意:只要是新增上传的图片都有response这个属性),拿到这个数组之后需要收集到 `spuImageList` 当中,此时需要符合`spuImageList` 的TS类型,需要有的属性:imgName、imgUrl、name、url,同时暂存一下 `response`,这里的url也需要从 `response` 中拿后端服务器的url 删除和预览的回调不用管 * 销售属性的收集 明确操作流程:当销售属性下拉框选择到内容点击 '添加销售属性' 按钮的时候,会把当前的销售属性添加到 table 表格中 * 下拉框需要一个单独的数据进行收集(收集的是id和name,id和name在value属性上拼接而成,格式`id:name`) * 当点击 '添加销售属性' 拿着收集到的数据,把 id 和 name 拆开,往表格中push一条数据,表格数据就是 `spuForm.value.spuSaleAttrList` ,这个数据同时是调用保存接口时候,spu中销售属性的数据 * 此时需要做两件事 把单独收集销售属性下拉的数据清空 下拉中的内容要少一个值 ------- 看到这块,把销售属性的下拉使用计算属性的出来 这里的计算属性计算的是:拿着原始下拉数据过滤在表格中已存在的数据 ------------ * 剩下销售属性值列表的交互 销售属性现在的数据格式应该是 ```js { baseSaleAttrId: number, // 从销售属性下拉选择到的选项的id saleAttrName: string, // 从销售属性下拉选择到的选项的name spuSaleAttrValueList: SaleAttrValueModel[], } ``` '销售属性值'列,展示的是销售属性中的一个属性`spuSaleAttrValueList`,这里收集的过程: 点击"新增"按钮,切换成input框、同时聚焦,在input框中输入内容、失焦收集到input框输入的内容、切换为"新增"按钮展示状态 1. 这里使用的组件直接拿到,element-Plus中tag中现成的例子 2. 然后是粘贴过来组件上绑定的数据 > 注意:组件上绑定的数据 > > 切换input显示隐藏的数据 `row.inputVisible`---- 绑定到当前 销售属性上(表个的一行数据) > > 收集input输入内容的数据 `row.inputValue`---- 绑定到当前 销售属性上(表个的一行数据) > > 这两个数据都是针对当前这一行销售属性的 3. 失焦的时候,需要对这个两个数据进行处理 * 把 row.inputValue 先push到属性值列表中 * 清空 row.inputValue * 切换 input 的显示 row.inputVisible 变成false 注意: 在失焦收集数据的时候不能有空的数据 和 重复数属性值 * 点击保存按钮,组装数据、调用接口 组装数据: * 图片数据的组装 - 图片是当都收集的,所以组装数据的时候要 收集图片列表,同时需要的字段是 imgUrl 和 imgName * 销售属性的组装 - 循环把 inputVisible 和 inputValue 这两属性干掉,这两个数据用在前端交互 * 三级分类数据如果没有也需要组装 调用接口,传递参数,发送请求 发送请求之后要做的事情: 切换页面显示状态,SpuForm组件销毁、SpuList组件创建,走wath监听重新获取数据 四、编辑Spu 编辑Spu做了两件事:回显数据、保存的时候处理数据 * 回显数据 请求接口由之前的两个变成了四个、多了两个,步骤: 1. 点击主列表的编辑按钮,拿到要编辑的那一条 spu 数据,传给父组件 2. 切换页面显示状态,展示 SpuForm 组件,父组件要把存储的 spuInfo 这条数据传入 SpuForm 组件 3. 在 onMounted 当中增加多出来的两个接口请求,判断 props 接收到的 spuInfo 数据是否有值,是否有id 如果有,证明是编辑,需要拿着 spuId 去请求 图片列表 和 销售属性 4. 请求图片列表,组装数据,请求回来的数据中没有 name 属性 和 url 属性 而这两个属性是 upload 组件预览时候需要的 所以需要组装数据,组装完毕之后赋值给单独收集图片列表的 `spuImageList` 进行展示 5. 销售属性获取到之后,直接赋值给 `spuForm.value.spuSaleAttrList` 直接在表格中展示即可 6. 回显数据已经完毕了,需要在交互的时候,单独处理 图片列表 的收集 注意: 这里的交互指的是新上传图片,此时在上传成功的回调中参数有问题: 回显的数据是没有 `response` 这个属性的,需要单独处理 * 保存的时候处理数据 1. 处理调用接口的时候调用的是 'updateSpuInfo' 接口,这里再 spu api文件中处理的 2. 当保存成功之后,切换页面显示状态,到主列表 还需要把父组件存出的 spuInfo 这个数据清空,目的是下次点击新增的时候,这个数据不会被再次携带到当前SpuForm组件 五、删除Spu 书写api函数,加 TS 类型限制,点击按钮的时候,双重确认、调用接口,删除数据,刷新页面 > 删除完数据之后,新增Spu、编辑Spu就完成了,给按钮加了限制了,限制条件加两个地方 > > 1. `添加Spu` 按钮的限制条件 - 必须有 `category3Id` 才能点击 > 2. 三级分类的禁用 - 只要不在主列表界面就需要禁用掉 三级分类 # day06 六、新增Sku * 静态页面搭建 静态展示的时候,需要注意的是 平台属性 和 销售属性 渲染,写的时候是一个 在 `` 中嵌套了一个 `el-form` * 初始化数据展示 切换当当前页的时候,给点击的 spu 添加 sku,需要把 `spuInfo` 传递过来,步骤: 1. 点击主列表中的 spu ,把 spu 的信息 给了 父组件的 spuInfo 2. 然后通过 props 传参传给 SkuForm 测试接口,调用了三个接口:平台属性获取、销售属性获取、图片列表获取 这三个接口都已经存在了 平台属性接口在 attrApi 文件中,销售属性 和 图片列表 在 spuApi 当中 直接调用接口接口,在 onMounted 中调用即可,拿到数据之后 存储一下在页面中展示即可 * 交互 整体步骤: 1. 测试点击保存,拿到api接口,和数据类型,加TS限制 2. 点击"保存"按钮,在点击的回调中,要进行收集数据,根据需要收集的数据把交互写一下 > 测试完接口之后,明确需要收集哪些数据 > > 1. spuInfo 给过来的数据 > > 2. 手动收集的数据 > > 3. 默认图片 - 图片列表中的 "isDefault" 字段拿到的 > > 4. 图片列表 - 表格前面选择到的图片 > > 5. 平台属性 > > ```js > "skuAttrValueList": [ // 平台属性收集 > { > "attrId": "106", // 平台属性id - 从下拉数据拿 > "valueId": "175" // 平添属性值id - 从下拉数据拿 > } > ] > ``` > > 6. 销售属性 > > ```js > "skuSaleAttrValueList": [ // 销售属性收集 > { > "saleAttrId": "5", // 销售属性的id - 从下拉数据拿 > "saleAttrValueId": "9" // 销售属性值的id - 从下拉数据拿 > } > ] > ``` 交互: 收集数据需要一个变量收集,用 ref 声明一个变量收集,收的数据类型的呢?需要写api接口和 TS 类型限制 > 注意:这里sku的api文件不自己写,直接粘贴过来 接下来在页面中收集这些数据,自动收集的数据直接v-model绑定上即可,手动收集 * 平台属性收集 给 `attrList` 循环渲染出来展示的下拉框,每个成员添加一个 `attrIdValueId` ,在TS类型中添加上 当点击保存的时候,把`attrList`数组循环遍历,判断有没有`attrIdValueId`值,如果有,解构出`attrId`和`valueId` push到 `skuForm.value.skuAttrValueList` * 销售属性收集 给 `saleAttrList` 循环渲染出来展示的下拉框,每个成员添加一个 `attrIdValueId` ,在TS类型中添加上 当点击保存的时候,把`saleAttrList`数组循环遍历,判断有没有`attrIdValueId`值,如果有,解构出`saleAttrId`和`saleAttrValueId` push到 `skuForm.value.skuSaleAttrValueList` * 图片收集数据分两块 * 图片选中 给表格增加一个回调 `@selection-change="handleSelectionChange"` ,每次只要选中表格行发生改变都会执行这个方法,参数就是选中的图片列表,直接收集给 `skuForm.value.skuImageList` * 默认图片 使用排他思想,所有的图片 `isDefault` 为 '0' 当前点击图片为 '1' 同时给 `skuForm.value.skuDefaultImg` 赋值上默认图片的imgUrl 页面中的展示,默认图片的列需要展示成一个 `el-tag` ,直接在模板中判断 `isDefault` 是否为'1' * 点击保存按钮 把spu需要收集的数据赋值给skuForm,调用接口,切换页面,清空父组件 supInfo 信息 # day07 七、查看Sku列表弹窗 点击"查看Sku列表"按钮,弹窗展示表格,表格中展示当前spu的所有sku列表 1. 测试接口、书写api函数、加TS类型限制(sku.ts这个api文件是粘过来的,已经有api函数了,拿过来直接用) 2. 点击"查看Sku列表"的时候,做了两件事 展示弹框 调用api函数,拿数据,展示 ## Sku管理 一、搭建静态页面 - `el-table` 和 `el-pagination` 二、初始化数据展示 测试接口,书写api函数(sku.ts这个api文件是粘过来的,已经有api函数了,拿过来直接用) 在页面调用接口,拿数据,展示即可 三、交互 * 上架商品、下架商品 * 编辑SKU(不做) * 查看SKU的详细信息 * 删除SKU 1. 上架商品、下架商品 测试api,知道参数,点击按钮的时候调用接口、实现商品的上架和下架 2. 查看SKU的详细信息 静态展示 ​ 用的 `el-drawer`抽屉 组件,`el-row` 和 `el-col` 栅格系统,`el-carousel` 走马灯 测试接口,书写api,调用接口,拿数据展示 > 注意: > > 功能:修改轮播图(走马灯)分页器的样式 > > scoped作用? > > 将样式限制在当前组件和子组件的根标签上 > > scoped如何实现将样式限制在当前组件和子组件的根标签上? > > 当style标签加上 scoped 的时候,当前组件内的标签和子组件的根标签都会加一个自定义属性`data-v-xxxxxxx ,此时 css 样式将变成一个属性选择器 > > ```css > h3[data-v-xxxxxx] { > color: red > } > ``` > > 将样式限制在当前组件和子组件的根标签上 > > > > 如何修改 element-plus 组件的样式? > > 1. 全局设置 (一般不采用,设置的是全局样式,会影响其他组件) > > 2. 深度作用选择器 > > css 深度作用选择器 > > ​ >>> > > 例子: > > ```css > .comtainer >>> .box { > color: red; > } > > >>> .box { > color: red; > } > ``` > > > > less 深度作用选择器 > > ​ /deep/ > > 例子: > > ```less > .comtainer /deep/ .box { > color: red; > } > > /deep/ .box { > color: red; > } > ``` > > > > scss 和 less 通用的深度作用选择器 > > ​ ::v-deep() > > 例子: > > ```scss > .comtainer ::v-deep(.box) { > color: red; > } > > ::v-deep(.box) { > color: red; > } > > vue2写法: > .comtainer ::v-deep .box { > color: red; > } > > ::v-deep .box { > color: red; > } > ``` 3. 删除SKU 测试接口,书写api 点击按钮,调用接口,发请求删除SKU,删除成功之后刷新页面 > 删除需要双重确认 --------------- # day07 下午 - 权限控制 # day08 上午 - 权限的数据管理 - 按钮权限 权限参考权限文档 # day08下午 - 数据可视化 参考 echart.md 文档,具体书写步骤如下: 一、准备数据 使用 mock 数据 ```js 1. npm i mockjs 2. src/mock/data.json ---> 数据 src/mock/index.ts import Mock from 'mockjs' // TS检测类型会飘红,按照提示安装类型声明,没有类型声明的话,自己在.d.ts文件中模块声明 declare module 'mockjs' import data from './data.json' Mock.mock('/mock/getEchartData', () => { return { code: 200, data, message: 'success' } }) ``` 二、书写api 复制 src/utils/request.ts 为 src/utils/mockRequest.ts 用来对mock进行请求,在 `mockRequest.ts` 文件中要把基础路径改成 `/mock` 书写api文件 三、pinia 创建一个 store 存放图表数据,需要一个 action 调用接口返回数据,把数据放到 state 中 四、搭建静态页面 * TopView 顶部分了四块内容、TodaySale、TodayOrder、TradeUser、TotalUser,发现这四块内容几乎相同,**二次封装了一个 `CommonCard` 组件**,来使用 * MiddleView * BottomView 做了TopView第一个模块: TodaySale 五、该做TodayOrde,先学习了基础的图表制作,静态页面写了个 echart 图表 1. 安装引入 ```js npm i echarts vue-echarts 安装了原生echarts 和 vue插件vue-echarts ``` 引入的时候,静态页面引入的 echart.js 文件 2. 创建一个容器,用来承载图表(注意:必须有高度) ```html
``` 3. 创建 echart 实例 ```js var chart = echart.init(document.getElementById('chart')) ``` 4. 设置配置项 ```js var option = { ... } chart.setOption(option) ``` > 配置项的说明: > > ```js > option = { > title: { // 标题 > text: '标题文本' > }, > tootip: {}, // 提示信息 > legend: { // 图例 > data: ['销量'] > }, > xAxis: { // x轴 > data: [xxx] > }, > yAxis: {}, // y轴 > series: [ > { > name: '销量', > type: 'bar', // bar 柱状图 line 线图 pie 饼图 > data: [xxx] > } > ] > } > ``` # day09 六、TodayOrde 使用 `CommonCard` 组件搭建静态页面 通过 store 把数据计算出来,放到组件中 制作图表 * 下载安装 - 之前下载过了 * 引入使用 src/plugins/echart.ts ```js import * as $echarts from 'echarts' // 引入原生echarts import type { App } from 'vue' import VueEcharts from 'vue-echarts' // 引入 vue-echarts 组件 const vueEcharts = function(app: App){ app.component('v-chart', VueEcharts) } export { $echarts, // 抛出原生的echarts vueEcharts // 抛出了 vueEcharts 函数 } ``` main.ts 使用该插件 ```js import { vueEcharts } from '@/plugins/echarts' app.use(vueEcharts) ``` * 组件中使用 * 引入原生 echarts ```js import { $echarts } from '@/plugins/echarts' // 1.引入原生echarts ``` * 准备容器 - 容器必须有宽高 ```html
``` * 初始化实例 * 设置配置项 ```js onMounted(() => { // 3. 初始化 echart 实例 chart.value = $echarts.init(divRef.value as HTMLDivElement); // 4. 设置配置项 chart.value.setOption(setOption()) }) ``` * 由于数据是异步,监听数据,当数据回来之后,再次设置 配置项 * 优化 * 响应式 在 `mounted` 中才能获取到DOM,然后给window绑定 `resize` 事件,当窗口发生变化的时候,echart 实例也进行 `resize` 操作。同时在 `UnMounted` 事件中需要解绑事件 * 对 echart 实例的 `resize` 函数进行防抖或节流 (频繁触发的事件要防抖或节流) ```js // 优化二: 对于频繁触发的回调进行防抖或节流 const chartResize = debounce(() => { chart.value?.resize() }, 200) onMounted(() => { // 优化一: 图表要根据浏览器的大小发生响应式变化 window.addEventListener('resize', chartResize) }) onUnmounted(() => { window.removeEventListener('resize', chartResize) }) ``` 七、TradeUser - 交易用户数 * 静态 * 数据 * 图 - 使用的是 v-chart 组件 ```js 组件: option 是设置配置项 autoresize 响应式 ``` 八、TotalUser - 用户累计数 * 静态 * 数据 * 图 - 使用的是 v-chart 组件 > * 需要注意,横向的这张图中,相当于是柱状图反转一下 > > 默认情况下,x轴是类目轴'category',y轴是数值轴'value' > > 而这里x轴需要变成数值轴 type: 'value', y轴变成类目轴 type: 'category' > > * tooltip触发方式 > > 默认 'item',我们使用的是 'axis' > > * 自定义柱状图的label - 具体配置项看代码 ```js const setOption = () => { return { xAxis: { show: false, // type: 'category' // x轴默认是类目轴'category' type: 'value', max: usersTotal.value, // 数值轴的最大值 min: 0 }, yAxis: { show: false, // type: 'value' // y轴默认是数值轴'value' type: 'category' }, tooltip: { trigger: 'axis' // 触发方式,变成轴线触发 }, grid: { left: 0, right: 0, bottom: 0, top: 0 }, series: { name: '用户增长量', type: 'bar', data: [usersTotal.value - usersLastMonth.value], barWidth: '30%', showBackground: true, // 显示背景色 color: 'red', // 柱状条的颜色 label: { show: true, formatter() { // label显示的内容 return '|' }, position: 'right', // 控制lable的位置 distance: 50, // 控制label距离图形的距离 color: 'red' // label的颜色 } } } } ``` 九、中间区域的显示 - MiddleView 组件 组件分为上下两部分:上面主要就是使用了一下 `el-menu`,`el-radio-group` 和 `el-radio-button`,`el-date-picker`,下方分为左右两块内容,左侧是柱状图,右侧是排行榜文本展示 左侧:直接把数据拿进来,然后使用 e-chart 画图即可,书写配置项(柱状图) 右侧:静态样式设置完毕,拿数据循环展示即可 十、BottomView 分为左右两部分: 左边是 keywordSearch 和 右边 SaleRank > 1. 后端返回的数据可能不是图表中直接拿过来用的,需要自己手动转化一下 > 2. 饼图的配置项(参照代码) # 拓展内容 git 拉取全部远端分支同步至本地 ```js git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done git fetch --all git pull --all ``` 切换分支,安装依赖运行即可看到 订单管理 、 优惠卷管理页面