硅谷甄选运营平台
1.项目初始化
模板路由配置
一级路由搭建
2.登录模块
登录静态页面搭建
element plus组件库使用
登录验证
注意要配置生产环境,解决跨域问题,才能成功访问到接口
登录时间判断
注意Date静态方法与new Date()调用的实例方法区别
实例方法: 需要通过new Date()
实例化一个日期对象后调用,操作的是具体的日期对象。
常见实例方法:
getFullYear()
: 获取年份。getMonth()
: 获取月份(0-11,0表示一月)。getDate()
: 获取日期(1-31)。getDay()
: 获取星期几(0-6,0表示星期日)。getHours()
: 获取小时(0-23)。getMinutes()
: 获取分钟(0-59)。getSeconds()
: 获取秒(0-59)。toString()
: 返回日期对象的字符串表示。
静态方法: 不需要实例化,可以直接通过Date
构造函数调用,通常用于全局时间相关的操作。
常见静态方法:
Date.now()
: 返回自1970年1月1日午夜以来的毫秒数。Date.parse()
: 解析一个日期字符串,返回该日期的时间戳(从1970年1月1日午夜开始的毫秒数)。Date.UTC()
: 根据提供的年份、月份、日期等参数,返回该日期的UTC时间戳。
登录模块表单校验
位el-form添加:model绑定收集的数据,ref打标签,便于后面调用组件实例身上的validate方法校验表单,:rules传入校验规则
自定义表单校验
向rules的validator传入函数,函数传入的三个参数
rule: any,
校验配置对象
value: any,
表单文本内容
callback: any
,回调函数,满足条件放行通过,否则抛出错误
1 |
|
3.主页面(layout)
主页面静态页面搭建
注意calc()和scss全局变量灵活控制样式
利用组件递归动态生成导航菜单
当有动态路由时,把路由交由pinia管理
将动态路由存储在Pinia(或其他状态管理工具)中的做法在大型Vue 3项目中比较常见,尤其是在需要根据用户权限动态生成或控制路由的情况下。将动态路由存储在Pinia中是为了更好地管理权限、动态添加路由、实现状态持久化,以及解耦业务逻辑。这种做法对于需要根据用户权限动态生成路由的应用非常有效。这么做有几个原因:
用户权限管理
在一些应用中,不同用户有不同的权限,访问的路由也有所不同。将动态路由存储在Pinia中,可以根据用户登录后的权限数据来动态生成可访问的路由表。
- 示例: 用户登录后,后端返回用户的权限信息。前端根据这些权限信息生成动态路由,并存储在Pinia中,方便在路由守卫或其他组件中访问和控制。
1 |
|
路由动态添加
当应用加载时,可以根据用户的权限或其他条件,动态地将路由添加到Vue Router中。将这些路由放在Pinia中可以在应用的任何地方方便地管理和修改路由。
- 示例: 应用初始化时,根据用户角色动态添加路由:
1 |
|
状态管理与持久化
通过Pinia管理动态路由,可以与其他应用状态一起进行集中管理和持久化。比如,当用户刷新页面时,动态路由依然可以从Pinia中恢复,不需要重新向后端请求权限信息来生成路由。
- 示例: 结合持久化插件将动态路由存储在本地,以避免页面刷新后路由信息丢失:
1 |
|
写样式时一个注意点
发现加上了scoped但是父组件中的样式还是会影响子组件的样式
动态创建菜单时注意点
当菜单项有且仅有一个子路由时,使用了el-menu-item
来呈现该子路由,而不是使用el-sub-menu
。这么做的原因:
简化用户导航:如果父菜单项只有一个子路由,那么这个父菜单项和子路由基本上是绑定在一起的,导航到父菜单项时通常直接会进入这个子路由页面。因此,将唯一的子路由直接呈现为一级菜单项,可以简化用户的点击操作,使导航更加直观。
避免冗余点击:当父菜单项只有一个子路由时,如果使用el-sub-menu
,用户点击父菜单项后还需要再点击子菜单项,这种情况就显得不必要和冗余。直接将唯一的子路由呈现为一级菜单项,可以减少用户的点击次数,提升用户体验。
UI/UX体验一致:在用户看来,一个只包含一个子菜单的父菜单项如果使用了el-sub-menu
,可能会让他们感到多余或困惑。直接将子菜单项提升为主菜单项,可以提供一致且简洁的用户体验。
路由与视图的自然映射:在某些设计中,一个父路由与其唯一的子路由可能代表同一个页面或功能。此时,子路由实际上代表的是主路由的具体视图,因此直接呈现为一级菜单更加合理。
可能出现的内存溢出问题
如果把menuRoutes放在组件内部,则会引发内存溢出
使用el-icon结合component(is属性对应相应meta里对应的图标)实现菜单图标
el-menu下还要包裹一层template结构
因为 el-menu
是整个菜单的容器,它只渲染一次,而 v-for
是用于循环渲染子元素的指令
1 |
|
要循环渲染的是子元素而非el-menu
el-menu
作为容器: el-menu
是一个整体的菜单容器,不应被循环渲染。它内部的菜单项才是需要循环渲染的部分。直接在 el-menu
上使用 v-for
会导致整个菜单容器被多次渲染,而不是循环渲染每个菜单项。
循环的目标是子元素: v-for
的目标是循环生成多个子元素(如 el-menu-item
或 el-sub-menu
),而不是循环生成多个 el-menu
容器。因此,v-for
应该放在一个用于包裹子元素的 <template>
或直接在子元素标签上。
保持结构清晰: 把 v-for
放在合适的位置,可以保持代码结构清晰,避免误解和潜在的渲染问题。直接把 v-for
放在 el-menu
上,可能会使代码混乱且不符合逻辑。
搭建路由时的注意点
使用编程式路由导航,配置路由时要写完整路径
注意给路由命名不能重复,否则后面的路由会顶掉前面的路由使其不能匹配
注意menu组件里不能包含el-menu,否则递归使用组件时el-sub-menu下也是从el-menu开始的结构而非el-menu-item,会导致菜单项与父级平级,看起来没有向里的缩进量。
还要注意搭建路由时要考虑清楚,/home之所以是/的子路由,是因为home组件要展示在LayOut组件中,/screen会直接跳走为另一个页面所以与/平级,其他/acl和/product都是挂载的LayOut组件,所以其子路由自然在LayOut组件中展示(理解好嵌套路由和组件之间的关系)
其他问题
关于整个页面出现滚动条,发现是Logo组件未限定高度
添加LayOut组件一级路由切换子路由时的动画
1 |
|
el-menu-item的icon不要放到插槽中,否则折叠菜单后文字和icon都消失了,但el-sub-menu的icon放到插槽里
1 |
|
回忆动态绑定样式(3种方式)
以前觉得样式尽量写到css里,但在真实开发中感觉行内样式也挺香的,特别是只用给span这种普普通通的元素加点margin啥的
注意折叠后不仅宽度要变,可能元素的定位也要发生变化
注意注意,解构store对象要用storeToRefs包裹,否则解构出的数据失去响应式!!!(当然后面还是觉得xxxstore.xxx比较香)
一个很奇怪的地方,切换菜单图标用refs解构成功,用storeTorefs有问题,说不能读取undefined上的value,是因为会忽略store中的方法和非响应式数据
学到了新的route的api
1 |
|
刷新业务实现
点击刷新按钮:
1.更改存放layout配置仓库中isRefresh的值(值是多少不重要,只要变化就好)
2.监测到isRefresh修改后,卸载content部分展示组件再重新挂载( v-if
实现卸载与挂载的切换) (注意v-if
是在渲染出来的组件身上)
1 |
|
全屏业务实现
原生实现:
(1)Element.requestFullscreen():用于发出异步请求使元素进入全屏模式;
(2)Document.exitFullscreen() :用于让当前文档退出全屏模式;
插件实现:
screenfull.request(); // 全屏
screenfull.exit(); // 退出全屏
screenfull.toggle(); // 全屏切换
获取用户信息与token理解
token就是每个用户的唯一标识,当用户登录成功后,服务器端返回token,之后再向服务器发请求都需携带token
1 |
|
注意仓库中数据大部分要是响应式的,否则可能修改无效
路由鉴权
注意新建的ts文件实现某个业务要记得在main.ts中引入
注意路由鉴权文件中的router是整个项目的路由器,而非用useRouter()创建的路由器对象,原来学习路由守卫时是直接写在src/router/index.ts
里的,现在只是把这部分提到新的地方src/permission.ts
1 |
|
注意在组件以外的地方使用小仓库要记得引入大仓库并传入
4.品牌管理模块
注意写接口时老师用的箭头函数,只有一行时可以省略()和return,但我写的普通函数要记得加return
在前端定义品牌的 TypeScript 数据类型时,id
属性可有可无是因为品牌的状态不同:
- 已有品牌:对于已经存在的品牌,
id
是数据库中唯一标识该品牌的字段,因此它是必有的,用来区分和操作具体的品牌记录。 - 新增品牌:在创建新品牌时,
id
通常由后端生成。前端在发送请求前,新增的品牌是没有id
的,因为此时品牌还未被存储到数据库中,因此id
属性可以为空或不存在。新品牌数据在发送到后端后,后端会生成一个id
并返回给前端。
TypeScript 类型定义示例
在 TypeScript 中,你可以使用可选属性 (?
) 来定义 id
可以有也可以没有:
1 |
|
这里 id?
表示 id
属性是可选的,只有在已有品牌时才会存在,而在新增品牌时可以不定义这个属性。
什么时候使用接口,什么时候使用类型别名?
接口:更适合用于定义对象的结构,尤其是需要扩展或被类实现时。
类型别名:更适合用于定义复杂的类型组合,如联合类型、交叉类型或需要表达更复杂的类型逻辑时。
如果你主要是定义对象的形状,尤其是需要扩展或实现时,推荐使用接口。
如果你需要更灵活地组合类型或定义复杂的类型逻辑,推荐使用类型别名。
定义ts类型
响应式数据
1 |
|
普通数据
1 |
|
分页器组件
虽然数据改变了,但分页器组件渲染有问题
给分页器组件添加一个key,每次数据改变后更新key
1 |
|
记得新增和修改品牌向服务器发请求时要带data,不然服务器拿不到更新后的数据
表单校验
form上打标识、model指定数据收集在哪、rules指定校验规则
1 |
|
rules中自定义校验规则
1 |
|
1 |
|
在合适时机调用 formRef.value.validate()校验所有表单项
1 |
|
注意初次的formRef.value为undefined,可以用ts的问号写法或者下一次DOM更新时(nextTick)清除上一次表单校验提示错误信息
注意get,delete请求时会在url上携带id等数据,不用传递额外数据;而post,put请求往往会额外传递data数据告诉服务器需添加或修改的数据
注意删除品牌时传的当前页码:为什么要等于1?因为此时trademarkArr是还没有更新的,服务器那边已经修改了,但这边展示的数据还没有更新,要重新请求,所以这里的trademarkArr长度也是没有更新的,=1的时候实际上这页已经没有数据了,要返回上一页
1 |
|
5.属性管理模块
注意el-select框v-model收集选中的分类的id,但id初始值为’‘(空字符串),如果设为0最开始会展示在select框上
细节:一级分类的选中值变化时,要清空二级,三级分类的id和三级分类数组(二级分类数组不用请,已经获取了新的数据了);二级分类选中值变化时,清空三级分类id即可
使用storeTorefs和不使用各有优劣,使用了看起来比较简洁,但是每次就要.value了;不使用的话看起来比较复杂但不容易出问题
注意收集用户的数据只收集新增一个数据项应具有的属性,id等由服务器返回的数据通通没有,收集的数据可以用在新增和修改中;修改时将收集到用户上传的数据与当前要修改的完整数据项使用Object.assign合并成新的数据项
注意vue开发工具常有延迟,可通过页面来观察展示的数据,记得要给el-table传入data属性指定表格的数据
注意el-table-column插槽中可以传入row,即为当前的数据项,可以通过v-model将用户输入保存到当前数据项(类比v-for的item)
1 |
|
注意每次新增前要先清上一次用户输入的数据再拿到需要的数据
注意input聚焦(.focus())这样的操作一般都要放在nextTick中
对一堆input来说,每次让新添加上来的input聚焦,即所有input的最后一个(可以为这些input的ref传入函数,把他们存到数组中,方便拿到每一个input组件实例)
1 |
|
注意Object.assign()是浅拷贝,新、旧数据指向同一个对象,并未开辟新的存储空间
,会出bug,比如修改属性后点击取消也会修改原来row里的属性值,但服务器那边的数据并没有被修改。可以使用lodash解决,也可以Object.assign(a,JSON.parse(JSON.stringfy(b))),把原来的对象先转成JSON字符串,再转为JSON对象,在堆内存中开辟新的空间
1 |
|
发现了一个影响用户体验的地方,原来我把属性数组、请求获取属性的方法放在了category小仓库里,导致每次重新获取属性比较慢,会出现几秒的暂无数据页面,但把这两个数据放到attribute组件里就好了
发现使用categoryStore.$reset后路由组件渲染出问题了,显示找不到父节点?
6.SPU管理模块
el-table-column上的prop属性可以直接写源数据中的字段名称(能找到数组中每个对象中的字段)
注意,分页器初始每页显示的条数必须要在page-sizes数组中,不然page-sizes上会显示一个初始值
可以通过给form设置label-width值使每个表单项对齐
如何在父组件中拿到子组件的实例对象:用ref,在切换场景我用的v-show,所以子组件是被挂载了,只是未显示
接口崩了的处理办法,可以再配置swagger上的接口地址,再封装一个request.ts
v-model既可以收集又可以更新
数组的map方法,map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。map() 方法按照原始数组元素顺序依次处理元素。注意: map() 不会对空数组进行检测。注意: map() 不会改变原始数组。
1 |
|
当数据比较多时,分散在各个接口中时,可以先声明几个变量来存储数据,最后整合交给服务器
注意row传进来的是整个一行的对象(完整的对象)
注意filter与every的结合使用,可以过滤出符合某些条件的元素
要想Select框拿到数据需要在select上写v-model收集要拿到的数据,并在option上设置value拿到每一项对应的数据,要收集多个数据时可使用模板字符串,后面再对拿到的数据做处理如split
注意字符串split后是个数组,解构要用[]
注意对空、重复属性值的检查可以放在将新的属性值添加到原数组之前(此时不用剔除自己,因为还没收集到),也可以放到之后(此时要剔除自己,收集到了,且还要将原数组中空、重复的这个属性值去掉)
因为要收集用户输入,所以一定是在失去焦点之后才做判断
JS中的短路逻辑
在 imgUrl: (item.response && item.response.data) || item.imgUrl
这个代码中:
- 当
item.response
为真,并且item.response.data
也存在时,表达式返回item.response.data
的值。 - 当
item.response
为假(如null
或undefined
)时,整个item.response && item.response.data
表达式返回null
或undefined
,此时通过||
运算符,会使用右边的值item.imgUrl
作为imgUrl
的值。
不能直接写成 item.response.data || item.imgUrl
的原因:
- 防止访问未定义属性的错误:
- 如果
item.response
不存在(即为undefined
或null
),那么item.response.data
的访问会导致错误。 item.response && item.response.data
通过短路求值,首先检查item.response
是否存在。如果不存在,就不会继续访问item.response.data
,从而避免了潜在的错误。
- 如果
- 处理
item.response
为null
或undefined
的情况:- 使用
item.response && item.response.data
可以优雅地处理item.response
为null
或undefined
的情况,返回null
或undefined
,然后通过||
运算符使用item.imgUrl
作为默认值。
- 使用
注意接口定义ts数据类型要和老师完全一致
注意区分插槽传的参数$index(当前第几行)和v-for里面传的index(v-for遍历生成的索引)不是一个东西
1 |
|
注意新增SPU在清空上一次数据时,不仅要清空spuParams,还要清空其他用来收集SPU数据的变量,如imgList,attrList(最开始页面展示的就是这里面的数据)
数组的reduce方法
1 |
|
参数解释
- callback: 一个函数,用于执行每个数组元素的累加计算。它有四个参数:
accumulator
(累计器):上一次调用回调函数时返回的累积值,或者是初始值initialValue
(如果提供了的话)。currentValue
(当前值):数组中正在处理的当前元素。currentIndex
(当前索引):当前元素在数组中的索引。如果提供了initialValue
,则索引为0
,否则为1
。array
(数组):调用reduce
方法的数组。
- initialValue (可选): 作为第一次调用
callback
时accumulator
的初始值。如果没有提供初始值,数组的第一个元素将被作为初始值,并从第二个元素开始执行回调函数。
返回值
reduce
方法返回累计计算的结果。
先开始把sku的attrId,valueId收集到spu的attr对象身上,最后使用Reduce方法把这个字段切割并封装成对象赋值给skuParams
遍历取反的思想
1 |
|
7.用户管理模块
使input两端不能输入空格,可使用v-model.trim(输入的内容中间输入的空格不会去除)
清楚上一次表单校验提示的错误信息,可以在抽屉打开时进行,利用element plus提供的事件
1 |
|
trigger:’change’会有问题,点击取消,上次输入的信息变成空串也会触发
表单校验中v-if和v-show的问题
发现表单校验一个神奇的地方,修改用户是只展示用户名和用户昵称的,添加用户的话还要输入用户密码,所以采用v-show/v-if控制用户密码el-input显示与隐藏。但是用v-show的话密码输入框还是会挂载的,只是不展示,所以表单校验还会校验密码,就会出问题;但用v-if的话,密码输入框会被卸载,也就不会校验密码了
在 Vue 中,v-if
和 v-show
的工作机制不同,这也是为什么在你的示例中,使用 v-if
时不会触发校验,而使用 v-show
时会触发校验的原因。
总结:
当使用
v-if="!userParams.id"
时,如果userParams.id
存在,表单项<el-form-item>
及其子元素根本就不会被渲染到页面上,因此表单验证也不会触发。当使用
v-show="!userParams.id"
时,即使userParams.id
存在,表单项<el-form-item>
及其子元素仍然存在于 DOM 中,只是被隐藏了。因此,表单验证仍然会执行。**使用
v-if
**:适用于需要在特定条件下完全移除表单项及其验证的场景。**使用
v-show
**:适用于需要在表单项可见或不可见时,依然保持表单项及其验证的场景。
每次修改完用户让浏览器强制刷新一次,因为可能修改的是自己的用户,那么就要重新获取用户信息并让用户重新登录(这里的逻辑写在了路由守卫里)
修改了login下的index.vue和permission.ts
注意展示和数据,可以将数据加工成需要的样子但可能比较复杂,展示时在模板里的js表达式也能得到相应效果
数组api的灵活使用(map,reduce)
1 |
|
注意删除加了气泡确认框后,把原来绑在delete按钮上的回调要换到气泡框的@confirm上
搜索用户业务
在获取用户列表时将输入的用户名作为query参数传给接口,来获取相应用户
注意搜索后不仅要更新当前用户列表,还要更新新返回的数据条数,当然这个新增业务可以直接添加在获取用户列表中
事件回调直接绑定异步函数可能出现问题
为何重置按钮直接绑定getUser请求会显示无网络呢,但把它写一个回调来调getUser却能正常使用?
当在渲染过程中引发了一个异步操作时,可能导致浏览器认为网络请求出错,尤其是在开发环境或某些浏览器中更容易出现这种情况
确保 getUser
的执行是在点击事件的处理过程中被调用,而不是直接作为事件处理函数返回值。这样做有几个好处:
- 避免 Vue 的异步错误处理问题:包装在回调中的异步函数调用,可以避免 Vue 在处理异步操作时可能产生的错误。
- 更清晰的代码结构:回调函数使代码更易读,并且可以在回调中加入额外的逻辑(如条件判断、额外操作等)。
可能的原因:
- 异步操作的返回值:直接绑定异步函数会将返回的
Promise
暴露给 Vue 的事件处理机制,可能引发不可预见的行为。 - 生命周期冲突:Vue 可能在某些时候因组件状态或渲染的原因,无法正确处理直接绑定的异步函数
想要el-input组件在按下回车后调用搜索的回调,需要在el-form上阻止按下enter(提交表单)的默认行为(会刷新页面)
1 |
|
分配权限
勾选菜单其实只要勾选叶子节点就可以了,利用递归(把这个业务封装成一个函数,然后在函数中再次调用,即为递归)
reduce在递归中的使用?prev对累加递归调用函数的结果未可知,感觉不是一个prev了啊,每次的初始值如果赋空值的话结果根本保存不下来的,所以初始的空数组要在函数中传,而递归中传的是prev
1 |
|
初始时选中的是从数据中过滤出来的,后续被选中的权限要用计算得到
注意要分配角色、权限也需要拿到id,也要Object.assign()合并params和row
注意搜索和重置清空搜索框的时机
1 |
|
注意模板里用ref不用.value
el-table的树形,默认展开使用expand-row-keys要是字符串类型
1 |
|
注意对话框等结构不要写到表格的结构中
注意追加新的菜单,还要收集pid和level,才能知道是给谁追加几级菜单
权限管理->需设置异步路由(需满足条件才能访问),原来的全是常量路由
popover里面嵌套颜色选择器color-picker会出现的问题,颜色选择器上的事件会冒泡到popover上,而且stop没有用,可能因为是原生事件?解决方法:1.手动控制popover的显示与隐藏,其上的visible属性。2.或者在取色器上增加一个:teleported=”false”
(解释:
- 默认
teleport
行为:Element Plus
中的el-popover
和el-color-picker
都使用了teleport
技术,默认会将弹出的内容(如颜色选择器)移动到body
的末尾。这样可以避免由于 z-index 或父元素样式导致的显示问题。 - 事件处理问题:当弹出的内容被
teleport
移动到body
外部时,事件冒泡和触发机制可能不如预期,尤其是点击时。因为点击颜色选择器时,弹出层和触发层在不同的 DOM 层次中,事件可能没有被正确捕获和处理。 - **
teleported="false"
**:当你将teleported
设置为false
时,弹出层内容会被渲染在原本组件的 DOM 结构内部,不再移到body
外。这样可以确保事件冒泡和 DOM 层次的操作保持一致,从而避免popover
弹出框由于错误的事件处理而消失。
因此,通过禁用 teleport
功能,所有的元素都保持在相同的 DOM 层次中,这就避免了因为跨层事件冒泡带来的问题。)
8.数据大屏
自适应问题:
vw,vh解决,思路简单,但计算麻烦,可同rem一起使用
scale缩放解决
1 |
|
再用js确定缩放倍数,并将content再位移到原来的位置
1 |
|
reduce有时会搞不清prev变成了什么,这时直接用foreach就好.注意这里的逻辑(这里的要保持父子关系,即不能把children过滤出来和父路由同级,不然就变成下图这样了,但是也要过滤掉不能访问的子路由)和上面勾选菜单(只要最下面的叶子节点判断勾选就好了,不用管以上的节点)的处理不一样
1 |
|
以上过滤出来的异步路由只是能够展示出来,但还没有注册
动态追加了异步路由和任意路由后,异步路由加载白屏问题:
1 |
|
注意页面需要倒计时效果,如果是用延时调用,需要在模板里调用函数获得时间,这样就可以数据更新-》模板重新渲染-》重新调用该函数-》重新执行延时调用(从而实现与定时器一样的效果)
设置按钮权限可以用自定义全局指令,只用引用一次仓库里的按钮数据
定义全局指令,要定义在函数里,然后在main.ts中引入就会执行
这里为什么要这样写?在 Axios 中,DELETE
请求的配置对象需要显式地将请求体数据放在 data
属性中。通过 { data: idList }
,你明确告诉 Axios 在发送 DELETE
请求时,将 idList
作为请求体的一部分传递到服务器端。
data
属性:用来指定请求体内容,类似于 POST
和 PUT
请求中常见的行为。
传递数据:这样后端能够接收到你指定的 idList
,处理相应的批量删除操作。
在 Axios 中发送 DELETE
请求时,通常无法像 POST
请求那样直接通过第二个参数传递请求体的数据。DELETE
方法的规范更倾向于使用 URL 路径或查询字符串传递参数,而不像 POST
请求有专门的请求体用于发送数据
1 |
|
改变页码要在请求之前,因为当前页码改变会触发current-change事件所以传入了当前页码导致不会返回首页
1 |
|