# rabbit-client-pc **Repository Path**: linlin2018/rabbit-client-pc ## Basic Information - **Project Name**: rabbit-client-pc - **Description**: rabbit-client-pc pc端小兔鲜电商平台 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2024-07-09 - **Last Updated**: 2024-07-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # rabbit-client-pc ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ... ### 结算&支付 ##### 05-结算-添加收货地址 **实现过程**: - 准备组件AddressEdit,用于收货地址的编辑or添加,内有XtxDialog组件,用变量visibleDialog控制显示与隐藏(默认隐藏); - 在CheckoutAddress组件中的"添加收货地址"按钮上绑绑定组件,在父组件CheckoutAddress中控制子组件AddressEdit的显示与隐藏; **具体实现方法**:由于是在父组件控制子组件中元素的显示与隐藏,若用普通方法(父子组件传值),需要在父组件传值给子组件,改变子组件中控制显示与隐藏的变量值,较为繁琐;在开发中,可以在子组件中暴露一个方法,在子组件内部修改visibleDialog的值: ```javascript // 暴露方法 父组件调用该方法(组件实例上拥有open方法) const open = () => { visibleDialog.value = true } ``` 父组件`CheckoutAddress`中:“添加收货地址”按钮添加点击事件`openAddressEdit`: ```javascript // 子组件组件实例 const addressEditCom = ref(null) // 打开添加编辑/添加收货地址的组件 const openAddressEdit = () => { // 调用组件实例的方法 addressEditCom.value.open() } ``` 模板上使用子组件的地方,用ref ```html ``` 此时父组件`CheckoutAddress`就会调用子组件`AddressEdit`的实例方法`open`,从而控制子组件中对话框的显示与隐藏。 - 完成添加/修改收货地址表单布局; - 在添加收货地址表单上双向绑定`formData`,点击确认按钮之后,调用api接口,将`formData`发送给后台; **值得注意的是**,上述添加收货地址仅仅在后台实现,但是在切换地址表单上并未显示新增的收货地址数据:此时需要将新的收货地址数据`formData`传递给父组件(使用自定义事件`on-success`),在父组件`CheckoutAddress`中,将`formData`插入到收货地址列表list中,对list进行展示 ##### 06-结算-切换收货地址 - 在组件`CheckoutAddress`左侧收货地址的展示(用于展示的数据为`showAddress`):若有默认收货地址,则进行展示;若无,显示‘’您需要先添加收货地址才可以提交订单。“,若该用户的收货地址`userAddresses`不为空,第一条为默认收货地址; - 当用户的收货地址列表中有多条数据时,就涉及到了收货地址的切换: - 首先是收货地址切换对话框的布局与数据列表展示:父组件`Checkout`通过`:list="order.userAddresses"`将收货地址列表传递给子组件`CheckoutAddress`,子组件定义props,完成数据渲染; - 在子组件`CheckoutAddress`的切换对话框中点击了某条数据(`selectedAddress`),需要将该数据的id传给父组件`Checkout`,用于后续的订单提交,同时,将`selectedAddress`的值赋给`showAddress`用于展示当前收货地址。 ##### 07-结算-修改/新增收货地址 - 由于修改收货地址和增加收货地址使用同一个表单组件,且两个功能的接口几乎一样,区别是修改收货地址除要提交收货地址表单外,还要在请求路径带上收货地址的id,所以打开表单组件、和点击表单组件确定按钮时,要区分是修改还是新增收货地址; - **若是修改**:点击修改收货地址按钮时,要将当前展示的收货地址表单`showAddress`赋值给`formData`(表单双向绑定的对象),以便让表单默认展示当前收货地址;**若是新增**:要将`formData`置为空; - 修改功能较为简单,在默认展示当前收货地址的表单上进行修改,点击确定按钮,提交表单即可。 - 新增收货地址:点击新增收货地址按钮时,显示空白的表单,进行完整输入后提交表单数据给后台。值得注意的是: 在表单输入的时候是没有收货地址id的,提交数据后,后台会返回一个id,在请求成功后,要在表单数据新增id属性并赋值,最后要将新增的address通过emit()传给父组件,完成用户收货地址列表的更新和重新渲染。 ##### 08-结算-删除收货地址 - 点击切换收货地址时,除了当前收货地址外其余均可进行删除; - 具体实现思路:删除按钮绑定点击事件,传入对应收货地址id,找到list中与传入id相等的数据,`props.list.splice()`进行删除。 - 上述开发过程中**关于props简单数据类型与复杂数据类型的修改问题** 1. props值由父组件提供,子组件只是使用这些值,对于子组件不应该去修改props的值,以保证数据流的单一性和可控性; 2. 严格来说,props中简单数据类型不可以修改,复杂数据类型如对象可以直接修改,但是在实际使用中可以通过emit()来修改,实际上是子组件将要newValue传给父组件,再由父组件修改props值,从而子组件拿到最新的值,以上过程并不违背不可以修改props值的初衷,并没有破坏props生产者和消费者的关系; 3. 需要注意的是,在vue中,对于props复杂数据类型可以直接修改,但是EsLint检查会报错,需要在修改props值的位置处加上跳过EsLint检查的注释:`// eslint-disable-next-line vue/no-mutating-props` ##### 09-创建并提交订单 - 创建订单:在进入`checkout`组件时,调用API接口函数,创建订单,并将订单编号(提交订单时的唯一标识)保存在订单对象`reqParams`中; - 提交订单: 1. 在order.js中定义提交订单的函数,传入订单数据对象,将参数发给后台; 2. 在页面`checkout`中,定义点击事件submitOrder,在函数体内调用api函数,提交订单信息; 3. 并使用vue-router完成支付页面的跳转,在router.js中定义新的路由规则`1111111`,将订单id通过路由地址传递给支付页面。 ##### 10-支付页面信息展示-倒计时函数封装 - 在跳转到支付页面时,发起请求,获取订单,此时后台返回一个countdown的数据,表示最后支付时间; - 封装倒计时函数: 1. 想要实现的功能是:支付倒计时每隔1秒自动减1,且进入支付页面后台拿到数据时开启定时器,离开支付页面时必须销毁定时器,可以使用setInterval来实现; 2. 在vue中,使用use库中的useIntervalFn可以灵活控制定时器的开始和暂停; ```js import { useIntervalFn } from '@vueuse/core' const { pause, resume, isActive } = useIntervalFn(() => { /* your function */ }, 1000,false) ``` 其中:返回的方法:pause和resume分别控制定时器的开始与暂停函数; ​ useIntervalFn()第一个参数是定时器运行时的回调函数,第二个参数是每隔多少时间执行回调函数,第三个参数表示是否立即执行,用布尔值表示。 3. 倒计时实现 在根据订单id查询订单的的回调函数中,resume()开启定时器;在组件被销毁时,pause()结束定时器; ```js const { pause, resume } = useIntervalFn(() => { time.value-- console.log(time.value) if (time.value <= 0) { pause() } }, 1000, false) ``` 4. 时间格式的处理:使用dayjs库,将秒数处理为xx分xx秒的格式 `timeText.value = dayjs.unix(time.value).format('mm分ss秒')` 其中timeText是类型为字符串的ref响应式数据 5. 倒计时函数封装: 在hook文件夹下的`index.js`中定义函数`usePayTime(countdown)`并导出,函数`usePayTime`返回`start(countdown)`,用于开启定时器,并返回经过格式处理的时间字符串`timeText`,在支付页面index.vue对方法start()和变量timeText进行接收和调用,具体逻辑如下: ```js export const usePayTime = (countdown) => { // 倒计时逻辑 const time = ref(0) const timeText = ref('') const { pause, resume } = useIntervalFn(() => { time.value-- // 使用dayjs对时间进行处理,表示成xx分xx秒的形式 timeText.value = dayjs.unix(time.value).format('mm分ss秒') if (time.value <= 0) { pause() } }, 1000, false) // 开启定时器的函数 const start = (countdown) => { time.value = countdown resume() } // 组件被销毁时 停掉定时器 onUnmounted(() => { pause() }) return { start, timeText } } ``` 在index.vue中: ```js const {start,timeText} = usePayTime() // 在获取订单成功后,调用start()开启定时器 findOrderDetail(route.query.orderTd).then(data=>{ order.value = data.result // 如果倒计时还没有结束 if(data.result.countdown>-1){ start(data.result.countdown) } }) ``` ##### 11-支付功能 - 以支付宝支付为例,完成支付功能的开发,点击支付界面-支付平台-支付宝支付,使用`target=“_blank”`弹出新页面(支付宝支付页面),同时指定跳转链接为`payUrl`,其中`payUrl`的包括一以下4部分内容:项目基准地址、支付页面地址、订单id、支付完成后的回调地址,`payUrl`拼接如下: ```js const redirect = encodeURIComponent('http://localhost:8080/#/pay/callback') const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.orderId}&redirect=${redirect}` ``` - 支付成功后回跳地址对应的组件为支付成功页面,页面上需要展示已支付订单的相关信息,故在支付成功页面要发起`findOrderDetail`请求,根据路由地址上的`orderId`获取订单数据进行展示,路由地址上的`payResult`判断是否成功支付。 ##### 12-个人中心-mock数据的使用 - 在后端接口未开发完成的情况下,为了更快地完成前端业务,可以使用mock数据; - `mockjs`模拟可更快的得到较为真实的数据,且可以拦截axios的接口调用(当前端页面飞起请求时,首先匹配是否有mock地址,有的话不再匹配线上的地址),让代码实现了调用接口的逻辑且得到模拟的数据,保证业务完整度; - 最后由本地切换到真实接口时比较方便。 - 生成数据的规则详情参考 http://mockjs.com/ - mock的使用 1. 安装:`npm i mockjs` 2. 配置`src/mock/index.js` ```js import Mock from 'mockjs' // mock的配置 Mock.setup({ // 随机延时500-1000毫秒 timeout: '500-1000' }) ``` 3. 使用 `src/main.js` ```diff import 'normalize.css' import '@/assets/styles/common.less' + import './mock' + 如果后台接口开发完毕,那么只需要注释改该行代码,前端页面就会发起真正的后台请求 ``` 4. 模拟接口,拦截请求 `src/mock/index.js` ```js // 拦截请求, // 第一个参数:url,使用正则去匹配 // 第二个参数:请求方式 // 第三个参数: 生成数据的函数 Mock.mock(/\/my\/test/, 'get', () => { // 生成随机数据的逻辑 const Arr = [] for(let i = 0;i<5;i++){ Arr.push(Mock.mock({ id: '@id', name: '@ctitle(2,4)' })) } return { msg: '请求测试接口成功', result: Arr } }) ``` 5. 生成随机数据 ``` // 单个数据 Mock.mock('@integer(0,7)') // 对象数据 Mock.mock({ id: '@id', name: '@ctitle(2,4)' }) ``` - 我的收藏-mock数据的生成——如何使用mock模拟真实接口? ```js // 模拟我的收藏 我的足迹 Mock.mock(/\/member\/collect/, 'get', config => { // 生成我的收藏数据的逻辑 // 获取前端的请求参数 const queryString = config.url.split('?')[1] const queryObject = qs.parse(queryString) const arr = [] for (let i = 0; i < +queryObject.pageSize; i++) { arr.push(Mock.mock({ id: '@id', name: '@ctitle(10,20)', desc: '@ctitle(10,20)', price: '@float(100,200,2,2)', picture: `http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/clothes_goods_${Mock.mock('@integer(1,8)')}.jpg` })) } console.log(queryObject) return { msg: '获取收藏商品成功', result: { counts: 35, pageSize: +queryObject.pageSize, page: +queryObject.page, items: arr } } }) ``` 需要注意的事项: 1、**Mock.mock ( '地址' , ’请求方法' , 返回数据前的逻辑(准备数据) )** 其中,请求地址需要使用正则表达式//,并使用\将地址中的/进行转义 2、使用Nodejs中的qs模块——querystring对前端请求地址进行解析,使用qs.parse('前端请求字符串'),将字符串转换为对象 `http://pcapi-xiaotuxian-front-devtest.itheima.net/member/collect?page=1&pageSize=4&collectType=1` 使用split方法进行分割,qs.parse转换之后生成一个对象: ```js {page: '1', pageSize: '4', collectType: '1'} ``` 3、Mock.mock生成模拟数据的规则: @id:生成位移唯一id @ctitle(10,20):生成10-20字符的中文描述 @float(100,200,2,2):生成有两位小数的浮点数... 其余规则详见 http://mockjs.com/ 4、接口return的数据要与真实情况相同:包括请求结果信息msg、请求结果result ```js return { msg: '获取收藏商品成功', result: { counts: 35, pageSize: +queryObject.pageSize, page: +queryObject.page, items: arr } ``` 5、使用上述mock模拟接口数据的好处是:前端开发人员可以不依赖于后端的数据,继续实现前端逻辑,保证在后端接口没有到位的情况下继续开发;后端接口完成后,直接注释掉mock即可,实现完美过渡。 ##### 13-个人中心-菜单激活 - 个人 中心左侧菜单是多个router-link组成的单独组件,配置相关路由之后,使用vue-router的`router-link-active router-link-exact-active`控制当前匹配路由,添加相应的激活样式; - `router-link-active` 当路由路径**包含** router-link组件的to属性值,当前组件会加上它; `router-link-exact-active` 当路由路径**完全**和router-link组件的to属性值**一致**,当前组件会加上它; 在菜单栏的标签中,添加`router-link-active:active` 或`router-link-exact-active:active` 时,被激活的标签会添加相应的类; - 需要注意的是: 当路由地址切换到order/:id时,也需要激活“我的订单”标签,vue3.0 router-link-active 加不上,路由路径包含 且 需要又路由嵌套关系,故改写路由配置: ```js { path: '/member', component: MemberLayout, children: [ { path: '/member', component: MemberHome }, { path: '/member/order', component: { render: () => h() }, children: [ { path: '', component: MemberOrder }, { path: ':id', component: MemberOrderDetail } ] } // { path: '/member/order', component: MemberOrder }, // { path: '/member/order/:id', component: MemberOrderDetail } ] } ``` 官方写法是 ![1617681965675](https://zhoushugang.gitee.io/erabbit-client-pc-document/assets/img/1617681965675.b627548e.png) 对于h函数里的RouterView,import之后ESLint仍报错,故写成组件的形式