--- title: Hi运动商城 date: 2017-12-28 tags: - Vue - Webpack - TypeScript sidebarDepth: 2 categories: 项目 --- # hiyd_h5_Wx 文档整理参与者请写下你的大名 > 文档整理(2019/12) 整理人 杜文华 ## 项目地址 - svn://svn.webdev.ouj.com/biz/hiyd_shop/hiyd_app_wx ## 项目域名 | 环境 | 域名 | | -------- | -------------------------------------------------------- | | 正式环境 | [http://wx-shop.hiyd.com](http://wx-shop.hiyd.com) | | 测试环境 | [http://test-wx-shop.hiyd.com](http://test-wx-shop.hiyd.com) | | 预发环境 | [http://new-wx-shop.hiyd.com](http://new-wx-shop.hiyd.com) | ## 技术选型 > 嵌入微信公众号中 1. 当时手上有两种方案可以选(1)Vue2.6x(可选 composition-api) + TypeScript (2) React16.x + TypeScript 2. 针对当时的情况最后采用的是方案一 原因有: 1. Vue 相对 React 来说概念比较少,上手容易,Api 也是简洁明了,对国人友好 2. React 对 TypeScript 的支持相当友好,但是 Vue3.0 使用 TypeScript 重写了全部逻辑,所以到 3.0 才对 TypeScript 支持友好。不过 Vue2.6x 可以通过库的引入实现对 Vue3.0 的新特性的支持 3. Vue 的兼容性问题相对 React 来说较少,直接可以支持到 IE9 了,但是 React 需要引入额外的库,增加开销 4. 对 Vue3.0 的 beta 版不太熟悉,而且因为是 beta 可能还有很多坑要排。所以采用的是 2.6x,在无法解决的时候可以用以前的 2.x 的写法去实现 5. 为什么选择 TypeScript,(1)类型检查系统对 IDE 友好,相对 JS 来说更加多提示(2)TypeScript 的类型系统可以在编写的时候发现百分之 70%的问题避免不必要的问题发生(3)对于习惯弱类型的 JS 来讲,TypeScript 是一种折磨很容易就报错,但是不失为一种减少错误率的最有效办法 6. 还得针对团队的现状,人员的问题,还有大家的技术栈来最后决定方案一 ## 最终的技术栈 > TypeScript + Vue-Cli3.x + Vue2.6x(配合 Composition-api 支持部分 3.0 的新特性) + Vuex + Vue-Router + scss + fetch(原生 fetch) + Mint-UI + Jest ## 项目架构 ```Bash ... - dist - node_modules - public - src - api --- 业务逻辑 - assets - components --- 公共组件(也可以称为纯UI组件) - router - routes --- 路由表 - store --- 公共数据流 - styles - views - tests --- 这里的tests不是jest,是在页面上测试的 - activity --- activity写具体的活动名字 是以活动为单位 (exam:redPacket ) - ... - components ---- 针对该View的组件(业务组件) - ... - tests ---- jest单元测试(可针对某些特殊的进行测试) - ... ---- 其他配置文件 ... ``` - Vue 部分(Vue,Vue-router,Vuex)是表现层 - Service/Api 是业务层 - Utils 是基础设施层 (2020/3/18 注:后续新增活动可以以活动为单位,进行项目架构。例如:红包活动直接一个文件夹,在分出来页面和组件等,单独一个整体存在。假如后续不需要也可以不需要侵入性的修改,包括其他在其他页面侵入式修改的组件都应该在活动里面编写,保持模块中的共同做法) ## Vue 组件(View 的编写规则) - 一个页面一个 View - 一个 View 中如果有组件可以单独提取就放在该 View 里面的 component 文件夹 - 如果是公共组件就放在 /src/components 里面 - 修改了组件库样式的 代码 写在 component.scss - 如果是组件就单文件把所有逻辑写在一个文件里面 - 当然你也可以不使用 composition-api,用你想用的可以组织代码的方法(mixin,slot) (tips:当时接触的时候,没有去详细了解写法。可以尝试的是,将 Script 部分的逻辑抽离到一个 ts 文件中,ts 文件里写一个 composition 函数,返回你需要的逻辑,2020/2/10 注) (tips:在 composition 的函数中,返回的 reative 的 state 要用 toRefs 包裹,这样可以防止丢失响应式。在解构或者是扩展运算符的操作情况下就会出现这样的问题,2020/3/9 注) ## 路由 ```JavaScript shop shop主页 order 订单页 category 分类 marketDetail 中间部分推广的详细页 productDetail 所有产品的详细页 orderDetails 订单详细 express 查看快递的详细页 paySuccess 支付成功页 search 搜索页 shopcar 购物车 confirmOrder 确认支付 address 地址管理 login H5的专属登录 index (本来以为tab栏也是H5做的,但是后来是安卓做的,所以就不用了) // redPacket 新加的路由 newUser 新人用户专享路由 ticket 可使用券 shareActivity 分享活动 // 测试的路由 test 付款的测试 test2 登录的测试 ``` ## 特殊说明一下 公众号的测试可能有点复杂,需要每次更改都需要打包到测试环境才能测试。因为微信公众号只能识别线上的地址,本地开发配置 host 的地址,微信不能识别 ## 巨坑 ### 项目适配环节(flexable + postcss-pxtorem) 1. 尝试一:postcss-pxtorem + 仅仅计算转换后的数值 结果: 跟设计稿相差甚远,最好不要仅仅转换数值 2. 尝试二:postcss-pxtorem + 修改后的公式(px2rem = px / 750 /640 /1.5 / rem) 结果: 看上去可能好一点,实际上不太行还是得重新弄 3. 最终尝试:postcss-pxtorem + flexable 原理: 因为 pxtorem 转换后的是根据 rem 进行修改的。配合 flexable 进行修改 rem 的数值到达屏幕适配的目的 ### cookie 环节 1. 后端的接口只有 POST 和 GET 之分,所以可以随意传递 GET 和 POST 类型 2. Fetch 是中 GET,OPTIONS 是没有 Type 的。 3. Fetch 的 Cookie 有分同源和非同源 4. 写入 cookie 的时候,请务必配好你的 host 不然写一万遍写不进去(千万别 172 开头) 5. 使用 Fetch 或者是 ajax 的时候 记得要默认加上携带 Cookie ### 公众号登录逻辑 ```Bash // 这里特殊说明一下,都是通过cookie进行保存信息的,但是后端一开始是写不进去的,最后是前端进行写入cookie的操作。后面看的时候需要小心这里 // 登录逻辑分三步 login - blank - shop/other 1. login 逻辑是 调用weixin/login接口,然后获取到对应的信息之后,跳转到blank 2. blank是一个中间页面。只是用来写入cookie的操作。 3. 最后写完cookie之后就看 是跳详情页 还是 shop页了 ``` ### 订单部分 1. 在订单支付页的时候,是没有生成订单的,是直接提交 productID 和 number 将数据封装一层后返回显示,所以没有 orderId,不能直接使用 Order 进行查询 这里有一个问题就是当用户点击地址的时候,假如是用 router 的 params 传递的话,就回丢失这部分数据,所以这里才用了 Vuex,跨页面后还保存这部分数据,但是 仅仅保存 productId 和 number 等信息,请求还是在 confirmOrder 这个页面中(假如去地址后再回来,就会请求两次,但是是封装数据不会操作数据库,所以没有问题) ### 地址管理 1. 这里的地址管理得有标志标识是从哪里来的,在页面切换的时候得用 replace,不能用 push。不然点击多次的效果就是来回在地址和地址列表中来回切换,影响用户体验 ### 商品详情页(认真看下逻辑,只是我个人的逻辑,假如有更好的也可以替换) 1. ajax 请求过后,将数据调整为合适的数据结构在进行赋值 2. 当商品的规格有一种的时候,直接加入购物车,直接购买,无需选择数量 3. 当商品有两种规格的时候,没有默认选择的规格,其次还得优先计算是否存在库存,没有的需要提示 ### 前端原生复制跟安卓原生复制的问题 1. 前端原生复制在某些机上无法兼容(华为跟魅族之类的),所以在复制微信号这种复制的情况下,是通过调安卓原生 Api 进行复制的。这里说明的是并不是前端原生不能复制,只是兼容性问题舍弃了。做法仍有保留。 ### 支付订单 1. 会有一个再次重新选择支付的方式。这里之前是没有存在的,因为后端的支付接口问题,过期时间可能有问题的情况下,重新选择支付方式可以重新生成支付的订单。 2 假如出现 totalFree 问题,这个是后端计算的问题,跟我们没什么关系 ### 蒙层移动的问题 1. 某些机型不能通过设置 body 的 overflow:hidden 来阻止蒙层移动,需要在父节点同时设置才能解决。(这个是奇哥手机) ### 分类页面返回的问题 1. 在分类这个页面中,点击进入商品详情页的时候,返回的时候因为缓存的原因会停留在某个 tab 上。但是在移动端中并不能保存当前的高度快照(原理跟首页一样,但是我重新请求了一次,导致会返回到最高处,这样不会存在更新数据的问题),但是就要手动计算一次高度 ## 仍然存在的 bug(能接受范围) ### 首页的缓存问题 1. 因为首页做了缓存的缘故,所以后退的时候不会刷新页面。这样也会导致,假如在购物车中的商品添加了,回首页的时候不能及时反应这个更新。缓存状态并且没有重新请求,所以不会有数据的更新。(这里有一个取舍问题,要用户体验还是要数据的及时更新的问题) ### 首页 tab 导航栏的问题 1. 首页的 tab 是用插件(vue-awesome-swiper)做的,这个插件是基于 Swiper 的,想要找的 Api 就去 Swiper 上找。这里有一个问题就是,当 tab 已经是 fixed 固定在视图的最上方的时候,假如进去了详情页再返回时,会因为缓存的原因停留在选中的 tab 上。但是也会因为这个 fixed,点击其他 tab 的时候,激活 swpier 插件使得 swpier 返回到最初的地方。(可能是 fixed 跟 transform 无法兼容的问题) 2. 这里做的尝试有(1)进去详情页之前先记录当前的 transform 的距离,然后返回的时候动态加上。但是又会因为激活的原因返回到第一个 tab。(2)假如是点击的时候每次进行改变,这样也会跟原来的 transform 冲突,返回的位置也会不一样。 3. 最终找到了官网上的一个方法,可以应对这个场景。但是会有一个问题就是会出现一个过渡的 300ms 的过渡动画。 ### 无内容充满整个屏幕 1. 这里有全面屏和非全面屏的问题。因为要适配这两种屏幕,所以我动态计算了高度。然后通过计算当前的高度和文档的高度,进行取舍。但是又因为有头部的原因,需要把头部剪去。但是这里会出现因为适配之前已经计算好了 rem 的原因。剪去的高度不一。在不同的屏幕上也会出现问题。这里主要是适配了全面屏。非全面屏这里,会出现高度溢出的状况(暂时能想到这种解决方法) 2. (3.9 新增)通过 flex 布局 + min-height:100vh 实现。min-height 的意思是最小值是 100vh。1vh = 屏幕的 1%。 ## 分开内嵌页跟公众号的 bug ### 2020.3.6 发现的 bug 1. 选择三级联动的时候,键盘会跟随唤醒,挡住三级联动的选择(IOS) 两种方法解决:(1)用 div 模拟成 input 框的样子,自然就不会有键盘的唤醒了(好像有点违背原来的想法)(2)将输入框设置成 readonly 属性就可以不唤醒键盘 2. 选择框 focus 的时候,键盘唤醒的同时会将页面整体向上调整高度适应键盘的整个高度。当键盘 blur 的时候,高度没有返回,导致整个页面拉长 在 focus 的时候记录当前的 scrollElement 的高度,在 blur 还原 ```JavaScript // IOS 上 键盘弹回的操作 handleSaveScrollTop() { if (document.scrollingElement) { // data 记录当前的scrollTop 感觉得hack 因为可能有些没有这个对象 data.scrollTop = document.scrollingElement.scrollTop; } }, handleChangeScrollTop() { if (document.scrollingElement) { // data 记录当前的scrollTop 感觉得hack 因为可能有些没有这个对象 document.scrollingElement.scrollTo(0, data.scrollTop); } }, ``` 3. H5 内嵌的 cookie 是由前端写入的 当时后端无法写入 Cookie,不知道是什么问题。然后前端写 cookie 也有一个问题,信息可能会被盗取的可能,毕竟没有写入 httpOnly ```JavaScript export function setCookie(name: string, value: string) { const expiresDays = 10; // 过期时间是10天 let date = new Date(); date.setTime(date.getTime() + expiresDays * 24 * 3600 * 1000); // 这里的document.cookie 直接等于不用加号 document.cookie = name + '=' + escape(value) + // 记得这里的分号 ,少一个分号就写不进去 ';expires=' + // 这里的时间要转换一下 date.toUTCString() + // 域名也得写正确 ';domain=hiyd.com;path=/'; } ``` ### 2020.3.7 发现的 bug 1. 微信支付的尝试(巨坑,巨坑,巨坑) 路由 hash 的问题,微信可能会屏蔽 hash 后面的值,导致后面的 url 可能会对签名有问题。可以做的方法是跳到另外一个页面在做。 (1)请求完接口后,让后端去签名然后支付完再回来前端 (2)hash 问题直接换 hash 就行了,用 history(需要后端代理一下 ) WeixinJSBridge.invoke: (1)刚开始发现的是参数不对,那个 timestamp 转成字符串后某些机型就可以了 (2)这种方法在安卓上行不通,某些机型偶尔可以支付一下,但是大部分都不行。可能是因为 WeixinJSBridge 的问题。一直无法监听 onWeiXinBridgeReady 方法 WXSDK: 这种方法就是后端返回一堆东西,前端直接调用微信的 sdk(这种方法不知道会不会有风险,但是只有这种办法暂时能兼容安卓和 IOS) ```JavaScript // 巨坑之 timestamp 大小写问题 wx.config({ appId: res.data.config.appId, timestamp: res.data.config.timestamp, nonceStr: res.data.config.nonceStr, signature: res.data.config.signature, jsApiList: ['chooseWXPay'], }); wx.ready(() => { wx.chooseWXPay({ nonceStr: res.data.platform_data.nonceStr, package: res.data.platform_data.package, paySign: res.data.platform_data.paySign, signType: res.data.platform_data.signType, timestamp: `${res.data.platform_data.timestamp}`, success: ret => { state.success = true; }, }); ``` ### 2020.3.9 发现的 bug 1. 微信支付报(appid 和 openId 不匹配) 因为刚开始使用的是别的地方的 appid,突然换一个公众号的时候,session_id 仍然存在,保留了之前的登录信息,所以 appid 也就不匹配了 解决方法是清除缓存就可以了。 2. 优化了 nav 中的吸顶操作 (1)在谷歌浏览器中调试刚好的高度,然后通过这个高度进行判断(存在的问题是,全面屏跟普通屏不能适配) (2)后来通过`document.documentElment.scrollTop`来动态计算高度,也是不太准 (3)最后是通过 offsetTop 和 getBoundingClientRect 来适配机型。 ```JavaScript // 最终方案 window.addEventListener('scroll', function() { if (nav.getBoundingClientRect()) { data.positionFixed = nav.getBoundingClientRect().top < 0; } else { let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; let offsetTop = getOffset(nav, 'top'); data.positionFixed = scrollTop > offsetTop; } }); (2020/3/10注:此类做法要用一个新的空的div来包裹,防止抖动的现象) ``` ### 2020.3.12/13 发现的 bug 1. 慧慧手机(6s plus)首次打开商城空白的情况 ```Bash (定位1)首先定位是否是IOS的问题 当时 嘉智家里人安卓测试了没问题, 我通过各位同学的测试后,发现确实安卓基本都通过,由此可以暂时确定IOS有问题 (定位2)后来让慧慧家里的人测试,还有IOS的朋友,发现IOS也没问题,锁定是否是6s plus的问题 (定位3)最后第二天找了其他三台6s plus测试,最终结果如下 微信版本都是 最新的 6s plus 13.1 有问题 6s plus 13.2 没问题 6s plus 12 没问题 6s plus 10 没问题 最终锁定就是慧慧手机的问题 最终解决方法是(不管是安卓还是IOS空白的解决方法) 3. 用户双金home键,关闭微信重新打开微信 4. 在空白页面点击右上角的”刷新“ 5. 注销微信账号,然后关闭微信,重新登录微信 6. 等待一段时间,重新点击公众号登录 据某位IOS初级开发说:就是把账号也退出了,再等到微信自己把东西都清干净 这里有一个问题:(1)是否微信内部打开网页都不能打开,(2)是否仅仅是自己的应用无法打开(3)是否外面的问题 ``` 这里假如出现空白搜到的几种问题 (1) JS 内部报错,导致无法继续下去(出现一次路由栈爆了) (2) 微信浏览器或者是自身浏览器缓存机制的问题(IOS 的) (3) 可能是浏览器兼容性问题,某些属性无法使用导致 空白屏 (2020/3/13) 当晚确定的问题是 安卓手机跟 IOS 手机 判断没有 cookie 的返回值不一样,导致路由无法进入 然后页面空白 ```JavaScript function getCookieParam(name: string, str: string = `${document.cookie};`) { // 这里IOS的问题 是 result === null 而不是undefined 而且 某些苹果里面会存在一个 device-token的 这种手机 就会报错 ... return result === undefined || result === null ? '' : unescape((result as any)[1]); ... } ``` 2. 路由栈爆了导致页面空白 当判断是否是登录的情况,是判断 cookie 是否存在,但是我的方法里面是默认 document.cookie 是存在的,当浏览器清除了以后,document.cookie 就不存在了,所以方法报错,导致路由栈爆了。 还有第二个爆栈的原因,就是应该将'/login'的路由剔除,不然会重复也会爆栈 3. 公众号链接外部访问最终的调整(最终拟定的方案) ```Bash 问题1. 是复制的时候 相当于分享的操作的时候。头部的返回都得返回到首页 之前有做vuex的全局管理,进入的时候就记录一下,上面全局头部进行判断的时候就可以返回了 问题2. 当不是login进去的时候,都得检查是否是有登陆 不然就会跳转 到 登录进行 登录一系列操作 这里的问题是,假如是详情页进去的话,还得将首次进入的url保存。然后登录完之后拿出来跳到该url中 这里我使用了sessionStorge,保证的是用户只有打开的时候生效,离开就不生效的做法 问题3 最后需要关注的是公众号内部的问题。马上关注的链接打开之后,是缺少关注按钮的。所以最后用了二维码代替 问题4 假如想要直接从外部浏览器跳微信浏览器,这里有一个最直接的侵入式操作,就是使用微信的协议。但是这个协议需要花钱 慎重考虑 ``` 4. clipboard.js 复制的问题 问题 1. 需要点击两次才能有反应(仅存在于请求后端的时候,返回才做操作) 方案一: 这里有一个 hack 的方法,就是 root.\$nextick 在去复制操作。但是好像没有效果 方案二: 有一个 mouseteer 的方法,并且仅执行一次,来 hack 问题 2. 点击次数越多,复制次数递增 方案一: 每次生成之后就销毁再生成 问题 3. 原生复制在浏览器上面是无法使用的,被迫无奈使用了这个库,谁知道还是有问题的。后面有更好的想法可以替换 5. 某个商品点击加入购物车,或者是立即购买无效 问题: 首先是从分享进去就无法点击了,然后在看到那个商品无法点击的时候,就推断报错了。但是没有反应,后面发现报了 product_id 为空 是因为那件商品已经下架了。但是没有下架的提醒,所以报错了导致无法点击 6. 分享的问题 描述:分享存在两种不同的状态,是否存在登录态。登录态通过 cookie 判断(这里保证 cookie 是存在的),分享的时候还得记录当前的 url,在没有登录的时候需要登录流程 过完之后,再回来记录的 url。但是这个 url 不能用 sessionStorage 来存储,某系机型因为这个原生 Api 不能存储导致报错。最后还是选择了 localStorage ### 2020.3 后半段的红包的记录 > 描述一下因为换了个后端 所以有一些数据返回的类型还有值 都要发生变化。可能得要重新封装一层数据才能使用,所以要认真看一下 redPacket - 新增头部的问题 头部有一个公共头部 就是添加返回的那个,还有一些零零散散自己的头部这些都要针对性的调整。调整的时候因为是点击之后就没了 tips,而且又因为不同的 state 控制的,所以就得放到全局里边去了。(header.vue/ tipsHeader) 这里还没有完成,还需要增加对应的接口返回知道是否已经关注了公众号如果没有关注的才需要显示,已经关注了就不需要在显示了 - 复制的问题 cliboard.js 这个东西需要两次点击才可以,原因是在点击的时候 DOM 还没有更新。或者说 DOM 节点还不存在。所以第二次点击的时候 DOM 才会存在。这个时候很简单 nextTick 在处理就可以。那为什么 SetTimout 之后不行呢?其实我在怀疑是不是 nextTick 又降为用 SetTimout 了。(也有 hack 的手法但是不能作为一种解决方案) - 使用券的这个问题 这个把使用券弹窗跟页面分离,通信成本增加。这里主要是父组件请求(查看减少价格的接口)还是子组件查看。按钮是在子组件的 尝试 1:子组件进行请求。这里需要的参数有 goodsId 跟 takesId,goodsId 直接从父组件拿到,takeId 是动态更新的,子组件选中后就更新一次,但是这里有一个问题子组件只知道当前的 takeId,但是他不知道外面还有多少选中的 takeId,所以还得让父组件通过 props 传进来后,watch。这里就有一个问题了。到底是 watch 先还是赋值先的问题了。经过实践是 watch 先赋值后(这里的赋值是自定义事件,可能排在 watch 的后面吧)。所以效果就是每次点击都是拿的上一次的值....尝试失败 尝试 2:父组件请求。父组件将请求封装成方法传递给子组件,然后子组件直接点击触发。搞定。逻辑上也说得通。因为请求的结果是父组件样式的修改,所以返回的数据可以直接作用于父组件,而不用继续透传 props 修改子组件的样式了。 这里的使用券还有一个问题就是要封装数据(请仔细看 confirmMessage.vue) ## 4 月中旬的 bug 1. 出现了 index.html 中的那段话("We're sorry but hiyd_h5 doesn't work properly without JavaScript enabled. Please enable it to continue"),这里是指 JavaScript 没法执行下去了。通常都是设置了 History 路由没有映射到,或者接口没有请求到 解决方法:ngnix 没有代理的话就代理一下。接口没有请求到就得看是否 baseUrl 的问题 2. nice_price 不会存入数据库的,这是一个内存字段。注意:只有红包的逻辑,逻辑包括什么(红包商品,该商品的下单,使用红包的时候等等),相关的价格全部使用这个字段,其他时间都可以用原本的 privilege_price,在使用之前一定要判断sell_price_rule参数这个参数就是是否使用ruleId的 3. 花了几天重构了一下商品详情页,整体感觉比较舒服,也很明了,一些函数调用方面看参数都应该知道是什么情况。但是因为拆分了组件的原因。所以可能通信成本增加,本来重构过后的通信就难以理解,这部分可能还得继续考虑该如何进行下一步的优化(productDetail) ## 4月22后的bug 1. 字体大小影响了布局的问题。因为有些老年人需要放大字体,然后设置了微信的字体大小后,打开微信的浏览器样式就会崩,我们可以使用weixin提供的bridge来实现重新调整fontsize的大小(@/utils/ajustFont.js) 2. 公众号头部没有完成只是隐藏了。后续如果加上的话把样式隐藏的加起来就好 3. 解决了首页商品缓存的问题,在首页实现一个路由钩子,在返回的时候将一个标志update置为true,进去首页的时候置为false,通过update透传props进nav组件,通知nav进行数据更新。(涉及几个问题 v-for问题,钩子触发的问题)