首页 > IT基础架构 > 服务器/存储 > 客服订单详情页体验升级之路 | 得物技术
来源:得物技术
一
背景


这么信息全的页面,几乎所有客服域平台都要能直接访问查阅,一个页面如何多个系统使用,在保障客服同学使用体验的同时,还能节省开发同学维护成本。
信息量大数据源多导致页面加载慢,客服同学经常反馈卡顿,如何对页面首屏进行秒开优化,进一步提升客服同学的使用体验。
简单的改动也需要投入资源,信息模块复用率低,如何建设信息自由编排、信息模块拔插的能力,最大程度解放产研运同学生产力。
二
多入口的页面复用



/** Vue3 *//** 1. 详情接口响应后注册message事件 */onMounted(() => {/** 请求详情接口 */fetchOrderDetail(() => {/** query上打上iframe标签,用于确定注册时机 */route.query.iframeRoute && watchParentMessage()})})/** 2. 监听iframe父级携带数据变化,更新本地页面数据 */const watchParentMessage = () => {window.addEventListener('message', event => {const orderNo = event.data.data.orderNoif (event.data.type === 'ORDER_CHANGE' && orderNo) {/** 更新订单信息*/initStream(orderNo)}})}/** 3. 本地页面交互事件被远端触发,发送当前的数据给远端 */window.parent.postMessage(/** payload: 携带的数据*/{type: 'workbenchRoute',params: {/** 跳转退货详情页 */name: 'refundDetail',query: {// 携带数据},},},/** orgin: 如果想要传递给任意窗口,可以将这个参数设置为'*' ,为了安全起见,不建议设置为'*'*/'*')
/** Vue2 *//** 1. 页面初始化时注册message事件*/mounted() {window.addEventListener('message', this.callBack, false)},/** 2. 监听本地订单单号变化,将新的数据传给远端*/watch: {orderNo(newOrderNo) {/** 在iframe的contentWindow属性上挂载postMessage方法*/detailIframeRef.contentWindow.postMessage(/** payload: 携带的数据*/{type: 'ORDER_CHANGE',data: {orderNo:newOrderNo,//其他数据},},/** orgin: 如果想要传递给任意窗口,可以将这个参数设置为'*' ,为了安全起见,不建议设置为'*'*/'*',)},},/** 3. 监听远端交互和数据变化,根据交互类型做不同的本地处理 */method: {/** callBack: 远端事件被触发后,处理本地回调逻辑 */callBack(event) {try {if (event.data.type === 'workbenchRoute') {switch (event.data.params.name) {case 'orderdetail'://跳转订单详情的handlerbreakcase 'detail'://跳转工单详情的handlerbreak// 其他交互default://兜底处理}}} catch(error) {//异常处理}}3.2、远程组件通信
/** 配置webpack MF插件,将订单详情页exposes出去*/new ModuleFederationPlugin({filename: 'remoteEntry.js?[hash]',library: { type: 'window', name: 'app_ticket' },name: 'app_ticket',shared: {/** 需要共享的依赖 */},exposes: {/** 提供订单详情远程组件*/'./OrderDetail': './src/views/orderdetail/Index.tsx',},}),内容使用方
/** 1.webpack配置远端应用 */remotes: { app_ticket: getRemoteUrl('app_ticket'),},/** 2. 使用defineAsyncComponent注册组件*/'OrderDetail': defineAsyncComponent(() => import('app_ticket/OrderDetail')),/** 3. 像本地组件一样使用远程组件*/<OrderDetail orderNo={orderNo} {...props} />

三
首屏秒开优化



/** 处理promise,保证promise.all使用时相互独立 */export const handlerPromise = (api, params) => {return new Promise(resolve => {api(params).then(resolve).catch(() => resolve({ error: true }))})}/** 详情页首屏请求函数 */const fetchOrderDetail = (callback?) => {/** 使用handlerPromise封装过的promise,保证有一个报错不干扰其他接口请求 */Promise.all([quickDetail(), firstLevel1(), firstLevel2()]).then(([quickDetailData, firstLevelData1, firstLevelData2]) => {// 快详情接口!quickDetailData?.error && quickDetailHandler(quickDetailData)// 第一梯队接口调用:不依赖详情反参的首屏信息接口!firstLevelData1?.error && firstLevelHandler1(firstLevelData1)!firstLevelData2?.error && firstLevelHandler2(firstLevelData2)// 执行回调nextTick(callback)}).then(() => {// 第二梯队接口调用:依赖详情反参的首屏信息接口、非首屏接口secondLevelHandler1()secondLevelHandler2()})// 执行慢接口fetchSlowOrderDetail()}
/** 慢详情接口请求 */ const fetchSlowOrderDetail = () => { slowLoading.value = true orderApi .getDetail(params) .then(slowData => { /** 防止快速切换订单导致的数据串台问题 */ if (slowData?.topInfo?.orderNo === orderNo.value) { orderDetail.value = slowData } slowLoading.value = false }) .catch(() => { slowLoading.value = false }) }
接口协议由POST改为GET请求,GET的总耗时是POST的三分之二;Chrome下如果检测到GET请求的是静态资源,则会缓存,如果两次传输的数据相同,第二次以后耗费的时间将在10ms以内。另一方面也为后续工作台引入Service Worker技术打下基础。
新增快详情接口,将大接口中的响应耗时较高的字段整理出来,快接口不再包含这些字段。这些高耗时的字段新增字段级别的loading效果,为了避免快慢接口耗时差异较大,导致一些经验丰富的客服同学误以为快接口没返回数据的字段是空数据,但是这个loading数量不会超过3处,保持页面的整洁易读。

四
信息编排、模版插拔能力建设

schemaData : [ { label: '订单类型' text:'普通现货' children: [ { id: 'orderTypeDetail' text: '详情', show: true, toolType: 'linkBtn', /** linkBtn, primaryBtn, tag */ eventType: 'click', /** dbclick, hover*/ interactiveType: 'popover', /** modal, popover, message*/ children: [ //** popover弹出的内容 */ { label: , text: '', children: //..}] { label: , text: ''} ] }, { text: '晚到必赔', show: true toolType: 'tag', }, { text: '退运服务', show: true toolType: 'tag', }, ] }, { id: 'tradeStatus' label: '支付状态' text:'已经支付' children: [ { text: '七天风控', show: true toolType: 'tag', }, ] }],2.2、高级配置
/** 信息元素枚举*/enum INFO_ElEMENT_MAP {/** 订单类型详情按钮 */ORDERTYPE_DETAIL: 'orderTypeDetail'}/** 信息元素*/const infoElementMap = {INFO_ElEMENT_MAP.ORDERTYPE_DETAIL: {/** 绑定事件 */onClick: () => {orderTypeDetailClickHandler()},/** 绑定样式 */className: [styles.marginLeft],},/** 其他需要添加复杂交互和样式*/}

const SchemaRender = () => { //TODO 健壮性代码 return ( <div> {schemaData.length ? ( <Row> {schemaTemplate.map(item => { // 分隔符 if (item.key === TemplateKeyEnum.dividedLine) { return <Divider /> } return ( <Col span={12}> <InfoItem key={item.key} label={item.label} text={item.text} infoList={item.children.map(child => { return { text: popoverRender(child), hide: !child.show, } })} /> </Col> ) })} </Row> ) : ( <Skeleton title={false} active paragraph={{ rows: 3 }} /> )} </div> ) }



五
灰度和埋点方案
/** 获取来源区分IM、工单灰度组信息 */watch( () => props.platformCode, code => { try { switch (code) { case PLATFORM_TYPE.IM: /** 一线灰度走一线灰度接口 */ isGray.value = true break case PLATFORM_TYPE.TICKET: /** 二线灰度走二线灰度接口 */ isGray.value = true break default: isGray.value = false } catch { isGray.value = false } }, { immediate: true, }) return () => isGray.value ? ( <> <Button type="link" onClick={clickHandler} > 返回{isOld.value ? '新版' : '老版'} </Button> {isOld.value ? <OldDetail {...props} /> : <Detail {...props} />} </> ) : <OldDetail {...props} />
/** 数据上报 */const uplog = (current, last, constant, type) => {/** b: 只用考虑上一个路由是订单页面的情况 */if (constant === last) {const nowTime = getNowTime()const stayTime = nowTime - stayOrderDetailTime.currentTime/** c: 小于3s,大于30min视为无效数据 */if (stayTime > 3000 && stayTime < 1000 * 60 * 30) {orderDuLog('ORDER_STAY_TIME', {orderNo: orderNo.value,stayTime,type,})}stayOrderDetailTime.currentTime = nowTime}/** d:如果最近的路由是订单页面,则重置时间*/if (constant === current) {stayOrderDetailTime.currentTime = getNowTime()}}/** 监听路由 */watch(() => {return { name: route.name, id: route.params?.id }},throttle((currentRoute, lastRoute) => {/** 从工单出发:只用考虑上一个路由是订单页面的情况 */uplog(currentRoute?.name, lastRoute?.name, `${globalConfig.backstageCode}_orderdetail`, 'routeChange')},100,{ leading: true, trailing: false }),{deep: true,immediate: true,})



六
总结&规划
*文/昌禾
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70027824/viewspace-2967411/ ,如需转载,请注明出处,否则将追究法律责任。