Commit ace4a71c authored by Xuguangxing's avatar Xuguangxing

feat: 处理h5流程

parent b603184d
# vue-standard-h5-project GROUP-BUY
基于vue-cli 3.0整合的针对前端业务的vue移动端项目模板 important:
1、vuex中 state的showMiniappGuide用于处理中断h5流程
## Quick start \ No newline at end of file
你可以直接clone这个项目,作为项目的基础,一切都是准备好的。
```bash
git clone http://git.quantgroup.cn/zhiwei.guo/vue-standard-h5-project.git
cd vue-standard-h5-project
npm install && npm run serve
```
## Feature
- **对项目结构进行细微优化**。对各资源模块进行归类、细分;编写、维护、查找更便利。
- **数据请求策略化**。对axios封装,统一请求配置,可配置针对不同业务接口返回参数的处理。
- **内置样式处理方案**。支持less编译,并内置变量和mixin,轻松开启样式编写、复用、修改等。
- **集成组件库**。可通过cherry-ui快速开发功能,配置全局主题风格。
- **集成 Prettier & Eslint**。检测代码潜在问题的同时,统一团队代码规范。
- **集成git hooks**。只允许提交符合规范的代码,保持代码库干净整洁。
- **打包资源自动上传cdn**。项目代码上传cdn,加快访问速度。
## Directory
```bash
├── README.md // 项目说明
├── babel.config.js // babel配置
├── .browserslistrc // 目标浏览器配置
├── .editorconfig // 编辑器统一编码规范配置
├── .eslintignore // eslint忽略清单
├── .eslintrc.js // eslint配置
├── .gitignore // git忽略清单
├── .prettierrc.js // prettier配置
├── .sentryclirc.js // sentry配置
├── jsconfig.json // vsc js配置
├── vue.config.js // vue-cli3主配置
├── package-lock.json // package.json锁定
├── yarn.lock // package.json锁定-yarn
├── package.json // npm配置
├── postcss.config.js // postcss配置
├── .env.production // 生产环境变量配置
├── .env.development // 开发环境变量配置
├── .env.test // 测试环境变量配置
├── public // 公用文件,不经过webpack处理
│ ├── fixIosTitle.html // ios兼容修改title
│ ├── index.html
│ └── logo.png
└── src
├── main.js // 主入口
├── App.vue // 主页面
├── api // 接口层
│ └── common.js // 原则:按接口或业务区分不同文件夹
├── assets // lottie动画、image、svg等资源
│ ├── images // image图标库
│ │ └── pay // 不同业务对应不同文件夹
│ │ └── xhk.png
│ ├── logo-txt.png
│ ├── logo.png
│ ├── lottie // lottie动画库
│ │ └── red-card-popup.json
│ └── svg // svg图标库
│ └── ufo.svg
├── components // 业务公用组件、原子组件
│ ├── LoginForm.vue
│ ├── NetError.vue
│ ├── PayWaitLayer.vue
│ ├── SvgIcon.vue // svg公共组件
│ └── lottie.vue // lottie动画公共组件
├── config // 配置层,包括常量配置、接口配置
│ ├── constant.js // 常量
│ ├── dev.config.js // 本地开发环境配置
│ ├── index.js // 聚合
│ └── prod.config.js // 生产/测试环境配置
├── mixins // 公共mixins
│ ├── formValidate.mixin.js
│ └── inputFix.mixin.js
├── router // 路由层
│ ├── index.js // router功能配置、路由监听
│ └── routes.js // 所有路由
├── service // 工具层
│ ├── cherry-ui.js // ui库按需引入
│ ├── cookie.js // cookies操作
│ ├── http.js // axios二次封装
│ ├── init.service.js // 路由鉴权、修改title
│ ├── localstorage.js // localstorage操作
│ ├── pay.js // 支付
│ ├── sa.js // 神策
│ ├── svg.js // 生成
│ ├── utils.js // 工具库
│ ├── validation.js // 校验规则
│ └── vconsole.js
├── store // vuex数据
│ └── index.js
├── style //所有less资源
│ ├── iconfont.less // iconfont
│ ├── index.less // 聚合
│ ├── mixins.less // mixins
│ ├── reset.less // 兼容各浏览器
│ ├── theme.less //ui库主题定制
│ └── var.less // less变量和function等
└── views // UI层(原则:轻page,重component)
└── Home
├── Demo // 业务
│ ├── index.less // 业务样式(少可以不分离,但建议分离)
│ └── index.vue // 业务page
└── modules // 业务功能组件
└── Child.vue
```
{ {
"name": "vcc-spider-center-ui", "name": "group-buy-ui",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "vcc-spider-center-ui",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@better-scroll/core": "^2.0.0-beta.6", "@better-scroll/core": "^2.0.0-beta.6",
...@@ -23,7 +22,8 @@ ...@@ -23,7 +22,8 @@
"vue": "2.6.11", "vue": "2.6.11",
"vue-awesome-swiper": "3.1.3", "vue-awesome-swiper": "3.1.3",
"vue-router": "^3.2.0", "vue-router": "^3.2.0",
"vuex": "^3.4.0" "vuex": "^3.4.0",
"weixin-js-sdk": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import NetError from '@/components/NetError'; import NetError from '@/components/NetError';
import { isApp, isWxMp } from '@/service/validation.service';
import store from '@/store';
export default { export default {
name: 'App', name: 'App',
components: { components: {
...@@ -25,7 +28,8 @@ export default { ...@@ -25,7 +28,8 @@ export default {
...mapState({ ...mapState({
title: state => state.pay.title, title: state => state.pay.title,
header: state => state.pay.header, header: state => state.pay.header,
loading: state => state.pay.loading loading: state => state.pay.loading,
showMiniappGuide: state => state.pay.showMiniappGuide
}) })
}, },
watch: { watch: {
...@@ -36,6 +40,12 @@ export default { ...@@ -36,6 +40,12 @@ export default {
document.body.className = val ? 'has-header' : ''; document.body.className = val ? 'has-header' : '';
} }
} }
},
$route() {
store.dispatch('change_show_mini_app_guide', {
bool: !isApp && !isWxMp ? true : false,
pointer: this
});
} }
}, },
methods: { methods: {
......
...@@ -27,5 +27,11 @@ export default { ...@@ -27,5 +27,11 @@ export default {
return http.get(`http://yapi.quantgroups.com/mock/479/goods/getAvator`, data, { return http.get(`http://yapi.quantgroups.com/mock/479/goods/getAvator`, data, {
hideLoading: true hideLoading: true
}); });
},
getWxConfig(url = window.location.href.split('#')[0]) {
return http.post(`${talosHost}/api/kdsp/wx/mp/getJsapiSign`, {
url: url,
appId: 'wx2f44c7fe7b08458d'
});
} }
}; };
<template>
<wx-open-launch-weapp
class="launch-btn"
username="gh_a976018bfb9e"
:path="jumpUrl"
@launch="launch"
@error="launchError"
>
<script type="text/wxtag-template">
<style>.btn {height: 96px}</style>
<div class="btn">打开小程序</div>
</script>
</wx-open-launch-weapp>
</template>
<script>
import wx from 'weixin-js-sdk';
import groupBuy from '@/api/groupBuy';
export default {
name: 'Weapp',
props: {
registeredObj: {
type: Object,
default: () => ({
key: '',
value: {}
})
},
jumpUrl: String
},
data() {
return {};
},
created() {
this.wxConfig();
},
methods: {
async wxConfig() {
const res = await groupBuy.getWxConfig();
wx.config({
debug: false,
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'], // 必填,需要使用的JS接口列表
openTagList: ['wx-open-launch-weapp'],
...res
});
},
launch() {
console.log('launch');
},
launchError() {
console.log('launchError');
}
}
};
</script>
<style lang="less">
.launch-btn {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
opacity: 0;
z-index: 100;
left: 0;
}
</style>
export default {
MP_URL_MAP: {
home: '/pages/index/index',
webview: '/pages/webview/webview',
middleWay: '/pages/webview/middleWay',
goodDetail: '/pages/product/goodDetail',
landPage: '/pages/landing/goods',
searchPage: '/pages/search/index'
}
};
/*
* @Description: h5 与 mp 交互
* @Date: 2021-04-29 15:19:37
* @LastEditors: gzw
* @LastEditTime: 2021-06-07 19:30:02
*/
import qs from 'qs';
import commonCfg from '../config/common.setting';
const { MP_URL_MAP } = commonCfg;
class Mp {
constructor() {
this.callbackQueue = {};
this.wx = require('weixin-js-sdk');
}
// 小程序中postMessage在特定时机(小程序后退、组件销毁、分享)触发; 如果需要立即触发,需要navigateTo跳转
run(data, callback, immediate = false) {
const { wx } = this;
console.log(data.event);
if (immediate) {
const url = `${MP_URL_MAP.middleWay}${qs.stringify(data, {
encode: true,
addQueryPrefix: true
})}`;
if (callback) this.callbackQueue[data.event] = callback;
wx.miniProgram.navigateTo({
url,
success: () => {
this.bindListener();
}
});
} else {
wx.miniProgram.postMessage({ data });
}
}
getToken(callback = () => {}) {
this.run({ event: 'getToken' }, callback, true);
}
closeBrowser() {
const { wx } = this;
wx.miniProgram.navigateBack();
}
openNewUrl(data = {}) {
const { wx } = this;
const { newUrl } = data;
console.log(newUrl);
if (/(https|http):\/\//gi.test(newUrl)) {
// window.location.href = newUrl;
wx.miniProgram.navigateTo({
url: `${MP_URL_MAP['webview']}?url=${encodeURIComponent(JSON.stringify(newUrl))}`
});
} else if (/xyqb:\/\//gi.test(newUrl)) {
let urlTag = '';
if (/goodsdetail|goodsDetail/gi.test(newUrl)) {
urlTag = 'goodDetail';
} else if (/goodsList/gi.test(newUrl)) {
urlTag = 'landPage';
} else if (/discover|homepage/gi.test(newUrl)) {
urlTag = 'home';
} else {
urlTag = 'home';
}
if (urlTag) {
const url = `${MP_URL_MAP[urlTag]}?${newUrl.split('?')[1]}`;
wx.miniProgram[urlTag === 'home' ? 'reLaunch' : 'navigateTo']({ url });
}
} else {
wx.miniProgram[data.type || 'navigateTo']({
url: newUrl
});
}
}
getHashData(that, currentHref) {
const hashArr = decodeURIComponent(currentHref).split('#/');
const lastHashStr = hashArr[hashArr.length - 1];
const hashData = qs.parse(lastHashStr);
this.callbackQueue[hashData.event](hashData.data);
this.unBindListener();
}
// TODO hashchange使用非匿名函数,获取location是异步的
bindListener() {
window.addEventListener(
'hashchange',
() => {
this.getHashData(this, window.location.href);
},
false
);
}
// TODO
unBindListener() {
window.removeEventListener(
'hashchange',
() => {
this.getHashData(this, window.location.href);
},
false
);
}
}
export default Mp;
import * as types from './type'; import * as types from './type';
const state = { const state = {
header: true, header: true,
title: '支付中心', title: '支付中心',
loading: false, loading: false,
meta: {}, meta: {},
keepAliveMap: [] keepAliveMap: [],
showMiniappGuide: false
}; };
// getters // getters
...@@ -27,6 +27,9 @@ const actions = { ...@@ -27,6 +27,9 @@ const actions = {
}, },
add_keep_alive({ commit }, name) { add_keep_alive({ commit }, name) {
commit(types.ADD_KEEP_ALIVE, name); commit(types.ADD_KEEP_ALIVE, name);
},
change_show_mini_app_guide({ commit }, obj) {
commit(types.CHANGE_SHOW_MINI_APP_GUIDE, obj);
} }
}; };
...@@ -64,6 +67,20 @@ const mutations = { ...@@ -64,6 +67,20 @@ const mutations = {
}, },
[types.CLEAR_KEEP_ALIVE](state) { [types.CLEAR_KEEP_ALIVE](state) {
state.keepAliveMap = []; state.keepAliveMap = [];
},
[types.CHANGE_SHOW_MINI_APP_GUIDE](state, { bool, pointer }) {
state.showMiniappGuide = bool;
if (bool) {
// 如果非app webview并且非小程序webview,直接拦截提示,到小程序操作
pointer.$dialog({
message: '请在微信小程序中参与此活动哦~',
showCancelButton: false,
confirmButtonText: '打开微信小程序',
onConfirm: () => {
// todo 跳转到小程序
}
});
}
} }
}; };
......
...@@ -2,7 +2,7 @@ export const CHANGE_HEADER = 'CHANGE_HEADER'; ...@@ -2,7 +2,7 @@ export const CHANGE_HEADER = 'CHANGE_HEADER';
export const CHANGE_TITLE = 'CHANGE_TITLE'; export const CHANGE_TITLE = 'CHANGE_TITLE';
export const CHANGE_META = 'CHANGE_META'; export const CHANGE_META = 'CHANGE_META';
export const CHANGE_LOADING = 'CHANGE_LOADING'; export const CHANGE_LOADING = 'CHANGE_LOADING';
export const CHANGE_SHOW_MINI_APP_GUIDE = 'CHANGE_SHOW_MINI_APP_GUIDE';
export const ADD_KEEP_ALIVE = 'ADD_KEEP_ALIVE'; export const ADD_KEEP_ALIVE = 'ADD_KEEP_ALIVE';
export const DEL_KEEP_ALIVE = 'DEL_KEEP_ALIVE'; export const DEL_KEEP_ALIVE = 'DEL_KEEP_ALIVE';
export const CLEAR_KEEP_ALIVE = 'CLEAR_KEEP_ALIVE'; export const CLEAR_KEEP_ALIVE = 'CLEAR_KEEP_ALIVE';
<template> <template>
<div class="goods-bottom"> <div class="goods-bottom">
<template v-if="status == 1"> <template v-if="status == 1">
<cr-button class="mutiplie" plain type="primary" shape="circle" :disabled="disabled" <cr-button
class="mutiplie"
plain
type="primary"
shape="circle"
:disabled="disabled"
@click="createGroup"
>自己做团长</cr-button >自己做团长</cr-button
> >
<cr-button <cr-button
...@@ -10,13 +16,13 @@ ...@@ -10,13 +16,13 @@
:disabled="disabled" :disabled="disabled"
type="primary" type="primary"
block block
@click="buy" @click="joinGroup"
> >
立即参团 立即参团
</cr-button> </cr-button>
</template> </template>
<template v-if="status == 2"> <template v-if="status == 2">
<cr-button shape="circle" :disabled="disabled" type="primary" block @click="buy"> <cr-button shape="circle" :disabled="disabled" type="primary" block @click="createGroup">
立即开团 立即开团
</cr-button> </cr-button>
</template> </template>
...@@ -54,8 +60,24 @@ export default { ...@@ -54,8 +60,24 @@ export default {
} }
}, },
methods: { methods: {
buy() { checkLogin() {
const vccToken = localStorage.get('vccToken');
return vccToken ? true : false;
},
joinGroup() {
// 参团
if (!this.checkLogin()) {
// todo
return;
}
this.$emit('buy'); this.$emit('buy');
},
createGroup() {
// 开团
if (!this.checkLogin()) {
// todo
return;
}
} }
} }
}; };
......
...@@ -163,7 +163,12 @@ ...@@ -163,7 +163,12 @@
</div> </div>
</div> </div>
</cr-popup> </cr-popup>
<bottom-nav type="shoppingCar" :disabled="false" @buy="goVccOrDetail" /> <bottom-nav
v-if="!showMiniappGuide"
type="shoppingCar"
:disabled="false"
@buy="goVccOrDetail"
/>
</div> </div>
</template> </template>
<script> <script>
...@@ -177,6 +182,7 @@ import avatorGroup from '@/components/avatorGroup'; ...@@ -177,6 +182,7 @@ import avatorGroup from '@/components/avatorGroup';
import successInfo from '@/components/groupBuySuccessInfo'; import successInfo from '@/components/groupBuySuccessInfo';
import rules from '@/components/rules'; import rules from '@/components/rules';
import groupDescInfo from './components/groupDescInfo'; import groupDescInfo from './components/groupDescInfo';
import { mapState } from 'vuex';
export default { export default {
// eslint-disable-next-line vue/name-property-casing // eslint-disable-next-line vue/name-property-casing
name: 'goodDetail', name: 'goodDetail',
...@@ -227,7 +233,10 @@ export default { ...@@ -227,7 +233,10 @@ export default {
computed: { computed: {
minCount() { minCount() {
return this.detailInfo.stock ? 1 : 0; return this.detailInfo.stock ? 1 : 0;
} },
...mapState({
showMiniappGuide: state => state.pay.showMiniappGuide
})
}, },
created() { created() {
this.hasLogin = window.localStorage.getItem('vccToken') != 'null' ? true : false; this.hasLogin = window.localStorage.getItem('vccToken') != 'null' ? true : false;
...@@ -430,6 +439,7 @@ export default { ...@@ -430,6 +439,7 @@ export default {
this.swiperCurrent = e.detail.current; this.swiperCurrent = e.detail.current;
}, },
handleParamsClick(eventType, name) { handleParamsClick(eventType, name) {
if (this.showMiniappGuide) return;
this.currentPopupType = eventType; this.currentPopupType = eventType;
this.currentPopupName = name; this.currentPopupName = name;
switch (eventType) { switch (eventType) {
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
</template> </template>
<!-- 底部 --> <!-- 底部 -->
<bottom-nav <bottom-nav
v-if="orderInfo.shopSkuList.length" v-if="orderInfo.shopSkuList.length && !showMiniappGuide"
type="submitOrder" type="submitOrder"
:info="orderInfo.calcFeeInfo" :info="orderInfo.calcFeeInfo"
@buy="handleBuy" @buy="handleBuy"
...@@ -60,6 +60,8 @@ ...@@ -60,6 +60,8 @@
</template> </template>
<script> <script>
import Bridge from '@qg/js-bridge';
import MpBridge from '@/service/mp';
import order from '@/api/order.api'; import order from '@/api/order.api';
import bottomNav from '@/components/bottomNav'; import bottomNav from '@/components/bottomNav';
import couponList from '@/components/coupon-list'; import couponList from '@/components/coupon-list';
...@@ -69,6 +71,8 @@ import historyRecordMixins from '@/mixins/historyRecord.mixins'; ...@@ -69,6 +71,8 @@ import historyRecordMixins from '@/mixins/historyRecord.mixins';
import FeeInfo from './components/feeInfo.vue'; import FeeInfo from './components/feeInfo.vue';
import { DISCOUNT, FREIGHT, CASH, REDUCTION, HAS_DISCOUNT } from '@/constants/order'; import { DISCOUNT, FREIGHT, CASH, REDUCTION, HAS_DISCOUNT } from '@/constants/order';
import cookies from '@/service/cookieStorage.service'; import cookies from '@/service/cookieStorage.service';
import { isApp, isWxMp } from '@/service/validation.service';
import { mapState } from 'vuex';
export default { export default {
name: 'CreateOrder', name: 'CreateOrder',
components: { components: {
...@@ -80,6 +84,8 @@ export default { ...@@ -80,6 +84,8 @@ export default {
mixins: [historyRecordMixins], mixins: [historyRecordMixins],
data() { data() {
return { return {
nativeBridge: null,
// 以下是原有data
query: {}, query: {},
prevCanAdd: true, prevCanAdd: true,
isOutBuy: false, isOutBuy: false,
...@@ -98,12 +104,19 @@ export default { ...@@ -98,12 +104,19 @@ export default {
} }
}; };
}, },
computed: {
...mapState
},
mounted() { mounted() {
this.init(); this.init();
}, },
activated() { activated() {
this.init(); this.init();
}, },
created() {
if (isApp) this.nativeBridge = new Bridge();
else if (isWxMp) this.nativeBridge = new MpBridge();
},
methods: { methods: {
init() { init() {
const orderData = localStorage.get('orderData') || {}; const orderData = localStorage.get('orderData') || {};
...@@ -404,12 +417,23 @@ export default { ...@@ -404,12 +417,23 @@ export default {
async orderSubmit(params) { async orderSubmit(params) {
const [data] = await order.orderSubmit(params); const [data] = await order.orderSubmit(params);
if (data && data.orderNo) { if (data && data.orderNo) {
// this.$track.registeredEvents('H5_2BConfirmPageSubmitOrderBtnClick', {
// order_no: data.orderNo,
// order_fee: params.totalFee
// });
cookies.set('skuID', params.skuList[0].skuNo); cookies.set('skuID', params.skuList[0].skuNo);
this.$router.push({ name: 'pay', query: { orderNo: data.orderNo } }); if (isWxMp) {
this.nativeBridge.openNewUrl({
newUrl: `/pages/pay/index?orderNo=${data.orderNo}`
});
} else if (isApp) {
this.nativeBridge.openNewUrl({
newUrl: `/pay?orderNo=${data.orderNo}`
});
} else {
this.$dialog({
message: '请在App或小程序中参与活动~',
title: '',
showCancelButton: false,
confirmButtonText: '我知道了'
});
}
} }
} }
} }
......
...@@ -124,7 +124,7 @@ import { ...@@ -124,7 +124,7 @@ import {
import { import {
pay, pay,
prepay, prepay,
getCoupon, // getCoupon,
ocrFaceId, ocrFaceId,
queryPayInfo, queryPayInfo,
kaGetNextUrl, kaGetNextUrl,
...@@ -230,7 +230,7 @@ export default { ...@@ -230,7 +230,7 @@ export default {
const vccToken = localStorage.get('vccToken'); const vccToken = localStorage.get('vccToken');
Current_Url = `${window.location.origin}/payWaiting?vccToken=${vccToken}&orderNo=${this.orderNo}`; Current_Url = `${window.location.origin}/payWaiting?vccToken=${vccToken}&orderNo=${this.orderNo}`;
this.queryPayInfo(); this.queryPayInfo();
this.getCouponList(this.orderNo); // this.getCouponList(this.orderNo);
// this.$track.registeredEvents('H5_2B_CashierPageExposure'); // this.$track.registeredEvents('H5_2B_CashierPageExposure');
}, },
methods: { methods: {
...@@ -375,22 +375,22 @@ export default { ...@@ -375,22 +375,22 @@ export default {
} }
}, },
/* 获取优惠券信息 */ /* 获取优惠券信息 */
async getCouponList(orderNo) { // async getCouponList(orderNo) {
const [data] = await getCoupon({ orderNo: orderNo }); // const [data] = await getCoupon({ orderNo: orderNo });
this.payCouponList = []; // this.payCouponList = [];
if (data && data.coupons) { // if (data && data.coupons) {
data.coupons.forEach(item => { // data.coupons.forEach(item => {
// 享花券 // // 享花券
if (item.couponCategory === 21) { // if (item.couponCategory === 21) {
this.payCouponList.push({ // this.payCouponList.push({
...item, // ...item,
id: item.pickupId, // id: item.pickupId,
pickupAble: 1 // pickupAble: 1
}); // });
} // }
}); // });
} // }
}, // },
nextAction: throttle(function() { nextAction: throttle(function() {
const trackParams = { const trackParams = {
order_id: this.orderNo, order_id: this.orderNo,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment