Commit 36904b4b authored by FE-安焕焕's avatar FE-安焕焕 👣

Merge branch 'pay' into 'master'

Pay

See merge request !1
parents b2b17557 c3371c4c
...@@ -1133,9 +1133,9 @@ ...@@ -1133,9 +1133,9 @@
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==" "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
}, },
"@qg/cherry-ui": { "@qg/cherry-ui": {
"version": "2.20.22", "version": "2.20.23",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcherry-ui/-/cherry-ui-2.20.22.tgz", "resolved": "http://npmprivate.quantgroups.com/@qg%2fcherry-ui/-/cherry-ui-2.20.23.tgz",
"integrity": "sha512-uuhHwat5SUUMUkPM56TK89o49EyXExUjQSW5pdPr0vQ6QpJRUnT+TsOzIxEGyBKQOFHsAIyqWSP2L1CvPWiIHg==", "integrity": "sha512-wDS1ag1Jk8dbXVVH+dtg2PZfTYHMHOA88fojWQjZMtZC5+cNbrFtpK/DlITyKJrNvhEQWHQ7dm59Bh6oG4U3Tw==",
"requires": { "requires": {
"@popperjs/core": "^2.5.4", "@popperjs/core": "^2.5.4",
"vue-lazyload": "^1.3.3", "vue-lazyload": "^1.3.3",
...@@ -1159,9 +1159,9 @@ ...@@ -1159,9 +1159,9 @@
} }
}, },
"@qg/ui-request": { "@qg/ui-request": {
"version": "0.0.5", "version": "0.0.7",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fui-request/-/ui-request-0.0.5.tgz", "resolved": "http://npmprivate.quantgroups.com/@qg%2fui-request/-/ui-request-0.0.7.tgz",
"integrity": "sha512-6RAAMxEzud18N0WQk5CR5vnrQ+FvBdejK+Ig5HAEyBMbFJ6sE1EfuccfYqx7qQ3CizHL58VpIa9pM+zhhdztFw==", "integrity": "sha512-C6qxG0HMskn0KDc9TDfmYf2ysPcD8ewEGQCpRv505A89oW28qTA8FpGHEVGWEO20ZJ9I6KAmfMkNaPxIZXHgKg==",
"requires": { "requires": {
"axios": "^0.19.2" "axios": "^0.19.2"
} }
......
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
}; };
</script> </script>
<style lang="less"> <style lang="less">
@import './style/var.less';
.app { .app {
user-select: none; user-select: none;
height: 100%; height: 100%;
...@@ -74,4 +75,24 @@ export default { ...@@ -74,4 +75,24 @@ export default {
} }
} }
} }
/*边框*/
.b-b:after,
.b-t:after {
position: absolute;
z-index: 3;
left: 0;
right: 0;
height: 0;
content: '';
transform: scaleY(0.5);
border-bottom: 1px solid @border-color-base;
}
.b-b:after {
bottom: 0;
}
.b-t:after {
top: 0;
}
</style> </style>
import config from '@/config';
import http from '@/service/httpDecorator';
const { talosHost } = config;
export default {
// 订单列表
orderList(data) {
// TODO 测试
return http.get(`${talosHost}/api/kdsp/order-info/virtual-recharge/list`, { params: data });
},
// 用户订单确认收货接口
orderConfirm(data) {
return http.post(`${talosHost}/api/kdsp/order-info/receipt/confirm`, data);
},
// 用户订单取消接口【未付款】
orderCancel(data) {
return http.post(`${talosHost}/api/kdsp/order-info/cancel`, data);
},
// 订单详情查询接口
orderDetail(data) {
return http.get(`${talosHost}/api/kdsp/order-info/virtual-recharge/detail`, data);
},
// 订单创建
orderCreate(data) {
return http.get(`${talosHost}/api/kdsp/order-info/e/vmSubmit`, data);
}
};
import request from '@/service/httpDecorator';
import config from '@/config';
const { talosApi } = config;
const queryPayInfo = function(data) {
return request.post(`${talosApi}/open/checkout`, data);
};
const prepay = function(data) {
console.log('prepay-param', data);
return request.post(`${talosApi}/open/checkout/prepay`, data);
};
const pay = function(data) {
return request.post(`${talosApi}/open/checkout/pay`, data, {
needScDeviceId: true
});
};
const queryPayStatus = function(data) {
return request.post(`${talosApi}/open/checkout/pay_status/query`, data);
};
const sendSms = function(data) {
return request.post(`${talosApi}/open/checkout/send_sms`, data);
};
const desSalt = function() {
return request.get(`${talosApi}/vcc/account/salt`);
};
const h5AppyUrl = function() {
return request.get(`${talosApi}/vcc/xyqb_mall/app_url`);
};
const getCoupon = function(params) {
return request.post(`${talosApi}/open/checkout/pay_coupon_list`, params);
};
const ocrFaceId = function(params) {
return request.post(`${talosApi}open/checkout/ocr_faceId`, params);
};
export {
pay,
prepay,
sendSms,
queryPayInfo,
queryPayStatus,
desSalt,
h5AppyUrl,
getCoupon,
ocrFaceId
};
import config from '@/config';
import http from '@/service/httpDecorator';
const { talosHost } = config;
export default {
// 虚拟充值中心配置文件
getSpuList() {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/config');
},
// VIP充值中心SKU列表
getSkuList() {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/recharge-list');
},
// 查询归属地
getPhoneHome() {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/phone-home');
}
};
export default {
101: `<p>充值类商品售出<strong>后无法进行退换</strong>,非充值用户请谨慎购买。</p>`,
102: `<p>充值类商品售出<strong>后无法进行退换</strong>,非充值用户请谨慎购买。</p>`
};
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Description: * @Description:
* @Date: 2021-03-31 19:59:17 * @Date: 2021-03-31 19:59:17
* @LastEditors: gzw * @LastEditors: gzw
* @LastEditTime: 2021-03-31 19:59:17 * @LastEditTime: 2021-07-01 17:02:56
*/ */
/* /*
* @Description: * @Description:
...@@ -14,12 +14,11 @@ let protocol = window.location.protocol; ...@@ -14,12 +14,11 @@ let protocol = window.location.protocol;
let payHost = protocol + '//mapi-qa.liangkebang.net/pay'; let payHost = protocol + '//mapi-qa.liangkebang.net/pay';
let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址 let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址
let talosHost = 'https://talos-vcc2.liangkebang.net'; // 电商分期测试环境服务地址 <<<<<<< HEAD
let talosHost = 'http://yapi.quantgroups.com/mock/351'; // 电商分期测试环境服务地址
// let talosHost = 'https://talos-vcc2.liangkebang.net'; // 电商分期测试环境服务地址
=======
let talosApi = 'http://yapi.quantgroups.com/mock/410'; // 电商分期测试环境服务地址
>>>>>>> 支付开发
let operatorHost = 'https://operator.liangkebang.com'; let operatorHost = 'https://operator.liangkebang.com';
export default { export default { talosApi, operatorHost, payHost, shenceHost, test: true };
talosHost,
operatorHost,
payHost,
test: true,
shenceHost
};
const creditPayStatusType = {
1: 'pwd',
2: 'sms'
};
const payTypeText = ['享花卡支付', '微信支付'];
const status = ['未申请', '审核中', '审核失败', '已有额度未激活', '开户成功'];
const payTypeE = [
'PD_YXMMAEC_UserClickCashierSelectXiangHuaCardPay',
'PD_YXMMAEC_UserClickCashierSelectWechatPay'
];
const codeArr = ['4034', '4035', '4036'];
// (4034, "密码错误"),
// (4035, "密码重试超限,无法验证"),
// (4036, "验证码错误");
const payStatus = [3, 5]; // 展示支付文案
const creditStatus = [1, 2]; //展示开通文案
// (1, "VCC未申请"),
// (2, "VCC审核中"),
// (3, "VCC审核失败"),
// (4, "VCC审核成功未开户(包含开户失败)"),
// (5, "已开户"),;
const ACCOUNT_NO_APPLY = 1;
const ACCOUNT_APPLY_AUDITING = 2;
const ACCOUNT_APPLY_AUDIT_FAIL = 3;
const ACCOUNT_APPLY_FAIL = 4;
const ACCOUNT_APPLY_SUCCESS = 5;
// 1信用支付 2微信支付 3支付宝支付 4信用支付_微信支付 5信用支付_支付宝支付 6虚拟支付0元
const CREDIT_PAY = 1;
const WECHAT_PAY = 2;
const ALIPAY_PAY = 3;
const CREDIT_AND_WECHAT_PAY = 4;
const CREDIT_AND_ALIPAY_PAY = 5;
// 三方支付
const IS_THIRD_PAY = method => [WECHAT_PAY, ALIPAY_PAY].indexOf(method) > -1;
//组合支付
const IS_GROUP_PAY = method => [CREDIT_AND_WECHAT_PAY, CREDIT_AND_ALIPAY_PAY].indexOf(method) > -1;
//信用支付
const IS_CREDIT_PAY = method =>
[CREDIT_AND_WECHAT_PAY, CREDIT_AND_ALIPAY_PAY, CREDIT_PAY].indexOf(method) > -1;
const PAYMENT_CODE_PAY = 1; // 支付密码
const SMS_VERIFICATION_CODE_PAY = 2; //短信验证码
const PAY_SUCCESS = 3; // 支付成功
const PAY_PAYING = 2; //支付中
function isDetentionFn() {
// 推荐开通享花卡
if (this.creditPayInfo.accountStatus !== ACCOUNT_APPLY_SUCCESS) {
this.$dialog({
message: '您有一笔免费额度可以使用,40天免息等你拿',
confirmButtonText: '立即开通',
cancelButtonText: '继续支付',
confirmButtonColor: '#EC1500',
onCancel: () => {
this.pay();
},
onConfirm: () => {
this.goApply();
}
});
} else if (
// 推荐享花卡支付
this.creditPayInfo.accountStatus === ACCOUNT_APPLY_SUCCESS &&
this.creditPayList.isGroupPay
) {
this.$dialog({
message: '使用组合支付部分金额可免洗使用40天哦!',
confirmButtonText: '组合支付',
cancelButtonText: '继续支付',
confirmButtonColor: '#EC1500',
onCancel: () => {
this.pay();
},
onConfirm: () => {
// 推荐组合支付
for (let key in this.creditPayList.payList) {
if (this.creditPayList.payList[key].isRecommend) {
const type = this.creditPayList.payList[key].payType;
this.changePayType(type, this.creditPayList.payList[key].mergePayPretreatmentInfo);
this.pay();
return;
}
}
}
});
} else if (
this.creditPayInfo.accountStatus === ACCOUNT_APPLY_SUCCESS &&
!this.creditPayList.isGroupPay
) {
this.$dialog({
message: '使用享花卡支付可免洗使用40天哦',
confirmButtonText: '享花卡支付',
cancelButtonText: '继续支付',
confirmButtonColor: '#EC1500',
onCancel: () => {
this.pay();
},
onConfirm: () => {
// 切换享花卡支付
this.changePayType(CREDIT_PAY);
this.pay();
}
});
}
}
function filterAllPayList(type, data) {
for (let item in data.payList) {
if (data.payList[item].payType === type) {
data.payList[item].isCheck = true;
continue;
}
data.payList[item].isCheck = false;
}
return data;
}
export {
status,
codeArr,
payTypeE,
payStatus,
PAY_PAYING,
CREDIT_PAY,
WECHAT_PAY,
ALIPAY_PAY,
PAY_SUCCESS,
payTypeText,
creditStatus,
IS_THIRD_PAY,
IS_GROUP_PAY,
IS_CREDIT_PAY,
isDetentionFn,
filterAllPayList,
ACCOUNT_NO_APPLY,
PAYMENT_CODE_PAY,
ACCOUNT_APPLY_FAIL,
creditPayStatusType,
CREDIT_AND_WECHAT_PAY,
CREDIT_AND_ALIPAY_PAY,
ACCOUNT_APPLY_SUCCESS,
ACCOUNT_APPLY_AUDITING,
ACCOUNT_APPLY_AUDIT_FAIL,
SMS_VERIFICATION_CODE_PAY
};
<template>
<div class="contract">
<cr-checkbox
v-model="isCheck"
shape="round"
class="contract-checkbox"
btn-size="mini"
@change="change"
/>
<p class="contract-list">
勾选同意
<span
v-for="contract in contractList"
:key="contract.contractName"
@click="goview(contract.contractVisitUrl)"
>
{{ contract.contractName }}</span
>
</p>
</div>
</template>
<script>
export default {
name: 'Contract',
props: {
contractList: Array
},
data() {
return {
isCheck: false
};
},
methods: {
goview(url) {
window.location.href = url;
},
change(e) {
this.$emit('change', e);
}
}
};
</script>
<style lang="less">
.contract {
width: 100%;
position: relative;
display: flex;
align-items: flex-start;
color: @gray-5;
.text-10;
&-checkbox {
// margin-right: 2px;
// height: 14px;
}
&-list {
display: inline-flex;
justify-content: flex-start;
flex-wrap: wrap;
color: @gray-5;
span {
color: @red;
}
}
}
</style>
<template>
<div class="pay-type-list">
<p class="type-title b-b">{{ value.title }}</p>
<payGroupItem
v-if="value.isGroupPay"
v-model="payList"
:pay-type="payType"
:disabled="disabled"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
:coupon-disabled="showCoupon && !payCouponCouldBeUsed"
/>
<template v-else>
<template v-for="(item, key) of payList">
<payCardItem
v-if="item.show"
:key="key"
:pay-type="payType"
:value="item"
:disabled="disabled"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
:coupon-disabled="showCoupon && !payCouponCouldBeUsed"
/>
</template>
</template>
</div>
</template>
<script>
import payCardItem from './PayCardItem';
import payGroupItem from './PayGroupCard.vue';
export default {
name: 'PayCard',
components: {
payCardItem,
payGroupItem
},
provide() {
return {
payCard: this
};
},
props: {
value: Object,
payType: Number,
payText: String,
disabled: Boolean,
couponInfo: Object,
riskLimit: Boolean,
showCoupon: Boolean,
payCouponCouldBeUsed: Boolean,
single: { type: Boolean, default: false },
creditPay: { type: Boolean, default: false }
},
data() {
return {
payList: this?.value?.payList || {}
};
},
mounted() {
console.log(this.value, 'value');
},
methods: {
changePayType(payType) {
// 需要初始化全部的选中状态
for (let item in this.payList) {
if (this.payList[item].payType === payType) {
this.payList[item].isCheck = true;
continue;
}
this.payList[item].isCheck = false;
}
this.$emit('changePayType', payType);
}
}
};
</script>
<style lang="less">
.pay-type-list {
background-color: #fff;
border-radius: @border-radius-sm;
margin-top: @padding-sm;
.type-title {
height: 48px;
.text-16;
padding-left: @padding-md;
position: relative;
line-height: 48px;
}
}
</style>
<template>
<div class="payCardItem">
<div :class="['type-item', { 'b-t': !isGroupPay }]">
<div class="type-item-content">
<div class="type-item-content-info">
<cr-image :src="value.icon" class="icon" mode="aspectFit" width="39" height="39" />
<div class="content-info-con">
<div class="content-info-tit">
<p class="content-info-tit">{{ value.name }}</p>
<p v-if="value.tagName" class="content-info-tit_tag">{{ value.tagName }}</p>
</div>
<p>{{ value.accountStatusDesc }}</p>
</div>
<p v-if="isGroupPay && value.mergePayPretreatmentInfo" class="content-info-amount">
{{ value.mergePayPretreatmentInfo && value.mergePayPretreatmentInfo.creditPayAmt }}
</p>
</div>
<div
v-if="showCoupon && value.payType === 1"
class="couponDes"
:class="{ disable: couponDisabled }"
@click.stop="openCouponModal"
>
{{ couponInfo.name || '未选择优惠券' }}
<cr-icon type="arrow" class="selectArrow" size="15px" />
</div>
</div>
<cr-checkbox
v-if="!isGroupPay"
v-model="value.isCheck"
shape="round"
class="type-item-checkbox"
checked-color="#EC1500"
:disabled="disabled || value.disabled"
@click.native="changePayType(value)"
/>
</div>
<p v-if="riskLimit && value.riskManagementDesc && !disabled" class="limitDes">
{{ value.riskManagementDesc }}
</p>
</div>
</template>
<script>
// import { registeredEvents } from '@/utils/sa';
const payTypeE = [
'PD_YXMMAEC_UserClickCashierSelectXiangHuaCardPay',
'PD_YXMMAEC_UserClickCashierSelectWechatPay'
];
const payTypeMiniAppE = ['c_cashierselectxianghuacardpay', 'c_cashierselectwechatpay'];
export default {
name: 'PayCardItem',
inject: ['payCard', 'pay'],
props: {
value: Object,
payType: Number,
disabled: Boolean,
couponInfo: Object,
riskLimit: Boolean,
showCoupon: Boolean,
isGroupPay: Boolean,
couponDisabled: Boolean
},
methods: {
changePayType({ payType }) {
if (this.disabled || this.value.disabled) {
return;
}
this.$emit('click');
this.pay.changePayType(payType);
},
openCouponModal() {
if (this.couponDisabled) return;
this.pay.openCouponModal(this.pay.orderNo);
}
}
};
</script>
<style lang="less" scoped>
.payCardItem {
width: 100%;
padding-bottom: 2px;
}
.couponDes {
margin-left: 50px;
display: inline-block;
color: @text-color-red;
.text-12;
border: @border-width-base solid @text-color-light;
border-radius: 3px;
padding: 0 0 0 3px;
&.disable {
color: @font-color-disabled;
border-color: @font-color-disabled;
}
text {
color: @text-color-red;
.text-12;
&.disable {
color: @font-color-disabled;
}
}
}
.limitDes {
color: #ed6a0c;
background: #fffbe8;
.text-12;
padding: 10px 8px;
margin: 8px;
border-radius: 3px;
}
.type-item {
width: 100%;
// height: 134rpx;
padding: 12px 8px 12px 12px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&-content {
width: 100%;
}
&-content-info {
width: 100%;
display: flex;
// width: 308px;
align-items: center;
}
&-checkbox {
width: 18px;
}
}
.icon {
width: 39px;
height: 39px;
margin-right: @padding-sm;
}
.content-info-tit {
font-size: 15px;
color: @font-color-dark;
margin-bottom: 2px;
display: flex;
align-items: center;
&_tag {
display: inline-flex;
align-items: center;
height: 16px;
border: @border-width-base solid @text-color-light;
border-radius: 3px;
font-size: 11px;
color: @text-color-light;
margin-left: @padding-unit;
padding: 0 2px;
}
}
.content-info-con {
flex: 1;
display: flex;
flex-direction: column;
.text-12;
color: @font-color-light;
}
.content-info-amount {
&::before {
content: '¥';
}
.text-15;
color: @black;
}
.selectArrow {
vertical-align: text-top;
}
</style>
<template>
<div>
<div class="groupCard">
<PayCardItem
:is-group-pay="true"
:value="creditPayInfo"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
/>
<p class="dashed">
<cr-checkbox
v-model="thirdPayInfo.isCheck"
shape="round"
checked-color="#EC1500"
:disabled="disabled || thirdPayInfo.disabled"
class="dashed-checkbox"
@click.native="changePayType(thirdPayInfo)"
/>
</p>
<PayCardItem :is-group-pay="true" :value="thirdPayInfo" />
</div>
<p class="group-more b-t" @click="openMore">更多支付组合<cr-icon type="arrow" size="15px" /></p>
<cr-popup v-model="morePopup" round position="bottom" :style="{ height: '30%' }" closeable>
<p class="more-title">更换支付组合</p>
<PayCardItem
v-for="item in thirdPayList"
:key="item.payType"
:value="item"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
@click="morePopup = false"
/>
</cr-popup>
</div>
</template>
<script>
// import { registeredEvents } from '@/utils/sa';
import PayCardItem from './PayCardItem.vue';
export default {
name: 'PayGroupCard',
inject: ['payCard', 'pay'],
components: {
PayCardItem
},
props: {
value: Object,
payType: Number,
disabled: Boolean,
couponInfo: Object,
showCoupon: Boolean,
riskLimit: Boolean,
couponDisabled: Boolean
},
data() {
return {
morePopup: false,
thirdPayList: [],
creditPayInfo: this.value.creditPayInfo || {}
};
},
computed: {
thirdPayInfo: function() {
let temp = {};
for (const key in this.value) {
if (this.value[key].isRecommend || this.value[key].isCheck) {
temp = { ...this.value[key] };
}
}
return temp;
}
},
mounted() {
for (const key in this.value) {
if (this.value[key].isGroupPay) {
this.thirdPayList.push(this.value[key]);
}
}
},
methods: {
changePayType({ payType, mergePayPretreatmentInfo }) {
if (this.disabled || this.creditPayInfo.disabled) {
return;
}
this.payCard.changePayType(payType, mergePayPretreatmentInfo);
},
openCouponModal() {
if (this.couponDisabled) return;
this.pay.openCouponModal(this.pay.orderNo);
},
openMore() {
this.morePopup = true;
}
}
};
</script>
<style lang="less">
.groupCard {
width: 320px;
}
.group-more {
.text-13;
width: 335px;
height: 36px;
margin: auto;
display: flex;
color: #ec1500;
position: relative;
align-items: center;
justify-content: center;
}
.cr-popup--close-top-right {
z-index: 2;
}
.dashed {
width: 309px;
margin: auto;
position: relative;
border-bottom: 1px dashed #dcdcdc;
&-checkbox {
width: 18px;
position: absolute;
right: -22px;
top: -8px;
}
&::after {
content: '';
width: 24px;
height: 28px;
position: absolute;
margin-top: -12px;
left: 50%;
margin-left: -12px;
background: url('../../assets/images/addicon.png') no-repeat;
background-size: 100%;
}
}
.more-title {
.text-16;
display: flex;
justify-content: center;
align-items: center;
height: 48px;
color: @black;
position: relative;
}
</style>
<template>
<cr-overlay :show="value" @click="closeModal">
<div class="sms-modal">
<div class="sms">
<p class="sms-icon"><cr-icon type="cross" color="#999999" @click="closeModal" /></p>
<p class="sms-title">请输入短信验证码</p>
<p class="sms-des">为保证您账户安全,此笔交易需要短信验证</p>
<p class="sms-des phone">已发送至 {{ getPhone() }}17611682272</p>
<cr-authcode-field
span-size="20px"
type="number"
border-type="bottom"
:number="6"
height="40px"
span-color="#000"
input-color="#000"
input-size="20px"
class="sms-input"
:success="success"
/>
</div>
<div slot="footer" class="sms-action">
<p v-if="errorInfo" class="sms-error">{{ errorInfo }}</p>
<p v-if="!send" class="sms-set" @click="sendSms()">获取验证码</p>
<p v-else class="sms-set grey">{{ time }}s后重新获取验证码</p>
</div>
</div>
</cr-overlay>
</template>
<script>
// import { registeredEvents } from '@/utils/sa';
import { sendSms as sendSmsApi } from '@/api/pay.api';
export default {
props: {
value: {
type: Boolean,
default: true
},
errorInfo: String
},
data() {
return {
send: false,
time: 60,
timer: null
};
},
computed: {},
watch: {
value: function(val) {
console.log(val);
if (val) {
this.sendSms(1);
}
},
errorInfo: function() {}
},
onShow() {},
onLoad() {},
onReady() {},
methods: {
closeModal() {
this.clearTimer();
this.$emit('close');
},
success(smsCode) {
this.$emit('submit', smsCode);
},
async sendSms(val) {
// 页面摧毁清掉定时器
if (this.send) return;
if (!val) {
// registeredEvents('PD_YXMMACP_UserClickResendCaptcha');
}
const [, error] = await sendSmsApi({ orderNo: this.orderNo, flowOrderNo: this.flowOrderNo });
if (!error) {
this.send = true;
this.timer = setInterval(() => {
--this.time;
if (!this.time) {
this.clearTimer();
}
}, 1000);
}
},
getPhone() {
// const phone = uni.getStorageSync('phone');
// const phoneS = phone.replace(/(\d{3})\d*(\d{4})/, '+86 $1 **** $2');
// return phoneS;
},
sendSa() {
// if (!this.numberArr.length && !this.isDel) {
// registeredEvents('PD_YXMMACP_UserClickAgainVerificationCodeInputbox');
// wx.reportAnalytics('c_againverificationcodeinputbox', {});
// }
},
clearTimer() {
clearInterval(this.timer);
this.send = false;
this.time = 60;
}
}
};
</script>
<style lang="less">
.sms-modal {
box-sizing: border-box;
position: absolute;
left: 50%;
top: 26%;
margin-left: -150px;
padding: 12px 12px 24px;
width: 300px;
border-radius: 16px;
background: #fff;
}
.sms {
&-icon {
width: 100%;
height: 20px;
line-height: 20px;
text-align: right;
}
&-title {
text-align: center;
width: 100%;
font-size: 16px;
}
&-des {
padding: 10px 0 0 8px;
display: flex;
justify-content: flex-start;
font-size: 12px;
color: #999999;
}
&-input {
margin: auto;
}
.phone {
margin-top: 8px;
color: #323233;
font-size: 14px;
}
&-sms {
margin-top: 4px;
font-size: 30px;
color: #333333;
&:before {
content: '¥';
.text-12;
}
}
&-input {
margin: 20px 0;
}
}
.sms-action {
width: 100%;
display: flex;
justify-content: space-between;
p {
font-size: 14px;
color: #666666;
}
.sms-error {
flex: 1;
color: #ee0a24;
}
.sms-set {
flex: 1;
text-align: right;
}
.grey {
color: #999999;
}
}
</style>
.app {
// width: 100%;
padding: @padding-xs;
}
.price-box {
background-color: @white;
height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.text-13;
color: @black;
border-radius: @border-radius-sm;
.price{
.text-30;
color: @font-color-red;
margin-top: @padding-xs;
&:before{
content: '¥';
.text-14;
font-weight: bold;
}
}
.time{
display: flex;
align-items: center;
margin-top: @padding-unit;
}
}
.payBtn{
width: 100%;
position: fixed;
padding: @padding-xs @padding-lg;
box-sizing: border-box;
z-index: 99;
bottom: 0;
left:0;
background-color:@white;
button{
width: 100%;
color: @white;
font-size: @line-height-sm;
border-radius: @border-radius-lx;
}
button[disabled] {
color: rgba(255,255,255,.6);
}
&-primary {
@primary-bg();
}
}
.btn{
height:50px;
width: 100%;
position: fixed;
padding: 0 @padding-lg;
box-sizing: border-box;
bottom: 0;
left:0;
background-color:@white;
display: flex;
align-items: center;
justify-content: center;
button{
flex:1;
color: @white;
font-size: @line-height-sm;
border-radius: @border-radius-lx;
}
button[disabled] {
color: rgba(255,255,255,.6);
}
&-primary {
@primary-bg();
}
&-default{
border: 1px solid @cherry-color-error;
color: @cherry-color-error !important;
margin-right: @padding-xs;
background-color: @white !important;
}
}
.placeholder{
width: 100%;
height: 100px;
}
\ No newline at end of file
This diff is collapsed.
<template>
<div v-if="isSuccess" class="card">
<div class="info">
<cr-image
width="100px"
height="100px"
class="info__image"
src="https://img.lkbang.net/xcx/pay-success.png"
/>
<div class="info__desc">
<p class="info__text">订单支付成功!</p>
<p class="info__text info__money">实付¥{{ money }}</p>
<p v-if="freeAmount > 0" class="info__text info__free">已优惠¥{{ freeAmount }}</p>
</div>
</div>
<p class="tips">
您可在“我的-我的订单”中查看详情
</p>
<div class="actions">
<cr-button shape="circle" class="actions__back" :plain="true" type="primary" @click="goHome"
>返回首页</cr-button
>
<cr-button shape="circle" type="primary" @click="goOrderDetail">查看订单</cr-button>
</div>
</div>
<div v-else class="card">
<div class="info">
<cr-image
width="100px"
height="100px"
class="info__image"
src="https://img.lkbang.net/xcx/pay-fail.png"
/>
<p class="info__text">订单支付失败!</p>
</div>
<p class="tips">
{{ reason }}
</p>
<cr-button type="primary" shape="circle" class="info_button" @click="goPay">重新支付</cr-button>
</div>
</template>
<script>
import { registeredEvents } from '@/service/sa.service';
export default {
components: {},
data() {
return {
money: '00.00',
orderNo: null,
isSuccess: null,
freeAmount: null
};
},
created() {
const { amount, orderNo, freeAmount, reason } = this.$route.query;
const { success } = this.$route.meta;
console.log(this.$route.meta);
this.money = amount;
this.orderNo = orderNo;
this.reason = reason || '';
this.isSuccess = success || false;
this.freeAmount = freeAmount;
},
methods: {
goHome() {
// 去页面
},
goOrderDetail() {
registeredEvents('PD_YXMMAEC_UserClickCashierCheckOrderBtn', {
order_id: this.orderNo
});
// 去订单详情页面
},
goPay() {
registeredEvents('PD_YXMMAEC_UserClickCashierPaymentAgainBtn', {
order_id: this.orderNo
});
this.$router.push({ name: 'pay', query: { order: this.orderNo } });
}
}
};
</script>
<style lang="less" scoped>
.card {
margin: @padding-sm;
background-color: @white;
border-radius: @border-radius-sm;
padding: 28px @padding-sm @padding-sm @padding-sm;
.info {
display: flex;
justify-content: center;
align-items: center;
&__image {
width: 100px;
height: 100px;
}
&__text {
.text-16;
margin-left: @padding-md;
}
&__desc {
display: flex;
flex-direction: column;
}
&__money {
margin-top: @padding-xs;
}
&__free {
.text-13;
margin-top: @padding-xs;
color: @font-color-light;
}
}
.tips {
.text-12;
text-align: center;
margin-top: @padding-lg;
color: @font-color-light;
}
.actions {
display: flex;
flex-direction: row;
margin-top: @padding-lg;
justify-content: space-between;
&__back {
color: @cherry-color-error;
border: 1px solid @cherry-color-error;
}
&__order {
@primary-bg();
margin-left: @padding-sm;
}
button {
// flex: 1;
.text-17;
width: 161px;
}
}
.info_button {
width: 100%;
}
}
</style>
<template>
<div class="card">
<div class="info">
<cr-image width="63px" height="63px" class="info__image" src="../assets/images/paying.png" />
<div class="info__desc">
<p class="info__text">{{ time }}s)支付中...</p>
</div>
</div>
<p class="tips">
努力返回支付结果中,请留心查看!
</p>
<div class="actions">
<cr-button class="actions__back" :plain="true" type="primary" shape="circle" @click="goHome"
>返回首页</cr-button
>
<cr-button shape="circle" type="primary" @click="goOrderDetail">查看订单</cr-button>
</div>
</div>
</template>
<script>
import { registeredEvents } from '@/service/sa.service';
import { queryPayStatus } from '../api/pay.api';
export default {
components: {},
data() {
return {
orderNo: null,
timer: null,
time: 10
};
},
created() {
const { orderNo } = this.$route.query;
this.orderNo = orderNo;
},
mounted() {
this.timer = setInterval(() => {
this.time -= 1;
if (this.time % 2 === 0) {
queryPayStatus({ orderNo: this.orderNo });
}
if (this.time < 1) {
clearInterval(this.timer);
}
}, 2000);
},
methods: {
goHome() {
// 去页面
},
goOrderDetail() {
registeredEvents('PD_YXMMAEC_UserClickCashierCheckOrderBtn', {
order_id: this.orderNo
});
// 去订单详情页面
},
goPay() {
registeredEvents('PD_YXMMAEC_UserClickCashierPaymentAgainBtn', {
order_id: this.orderNo
});
this.$router.push({ name: 'pay', query: { order: this.orderNo } });
}
}
};
</script>
<style lang="less" scoped>
.card {
margin: @padding-sm;
background-color: @white;
border-radius: @border-radius-sm;
padding: 28px @padding-sm @padding-sm @padding-sm;
.info {
display: flex;
justify-content: center;
align-items: center;
&__image {
width: 100px;
height: 100px;
}
&__text {
.text-16;
margin-left: @padding-md;
}
&__desc {
display: flex;
flex-direction: column;
}
&__money {
margin-top: @padding-xs;
}
&__free {
.text-13;
margin-top: @padding-xs;
color: @font-color-light;
}
}
.tips {
.text-12;
text-align: center;
margin-top: @padding-lg;
color: @font-color-light;
}
.actions {
display: flex;
flex-direction: row;
margin-top: @padding-lg;
justify-content: space-between;
&__back {
color: @cherry-color-error;
border: 1px solid @cherry-color-error;
}
&__order {
@primary-bg();
margin-left: @padding-sm;
}
button {
.text-17;
width: 161px;
}
}
.info_button {
width: 100%;
}
}
</style>
export default [ export default [
{ {
path: '/', path: '/',
redirect: '/error' redirect: '/pay'
},
{
path: '/pay',
name: 'pay',
meta: {
title: '收银台'
},
component: () => import('../pay')
},
{
path: '/payFail',
name: 'payFail',
alias: ['/payFail'],
meta: {
title: '支付失败'
},
component: () => import('../pay/payResult')
},
{
path: '/paySuccess',
name: 'paySuccess',
alias: ['/paySuccess'], // 有问题🤨
meta: {
title: '支付成功',
success: true
},
component: () => import('../pay/payResult')
},
{
path: '/vipLife',
name: 'vipLife',
meta: {
title: '充值中心'
},
component: () => import('../views/vipLife')
},
{
path: '/orderList',
name: 'orderList',
meta: {
title: '我的订单'
},
component: () => import('../views/orderList')
},
{
path: '/orderDetail',
name: 'orderDetail',
meta: {
title: '订单详情'
},
component: () => import('../views/orderDetail')
},{
path: '/payWaiting',
name: 'payWaiting',
meta: {
title: '支付中',
success: true
},
component: () => import('../pay/payWaiting')
}, },
{ {
path: '/demo', path: '/demo',
......
...@@ -7,6 +7,8 @@ import { ...@@ -7,6 +7,8 @@ import {
Icon, Icon,
Cell, Cell,
CellGroup, CellGroup,
Radio,
RadioGroup,
Row, Row,
Col, Col,
Dialog, Dialog,
...@@ -24,7 +26,15 @@ import { ...@@ -24,7 +26,15 @@ import {
Form, Form,
Sticky, Sticky,
Tab, Tab,
Tabs Tabs,
<<<<<<< HEAD
Empty
=======
CountDown,
PwdField,
AuthcodeField,
CouponList
>>>>>>> 支付开发
} from '@qg/cherry-ui'; } from '@qg/cherry-ui';
import DialogFn from '@qg/cherry-ui/src/dialog/func'; import DialogFn from '@qg/cherry-ui/src/dialog/func';
// import "@qg/cherry-ui/dist/cherry.css"; // import "@qg/cherry-ui/dist/cherry.css";
...@@ -33,6 +43,8 @@ Vue.use(Button); ...@@ -33,6 +43,8 @@ Vue.use(Button);
Vue.use(Image); Vue.use(Image);
Vue.use(Cell); Vue.use(Cell);
Vue.use(CellGroup); Vue.use(CellGroup);
Vue.use(Radio);
Vue.use(RadioGroup);
Vue.use(Row); Vue.use(Row);
Vue.use(Col); Vue.use(Col);
Vue.use(Popup); Vue.use(Popup);
...@@ -52,6 +64,14 @@ Vue.use(Loading); ...@@ -52,6 +64,14 @@ Vue.use(Loading);
Vue.use(List); Vue.use(List);
Vue.use(Tab); Vue.use(Tab);
Vue.use(Tabs); Vue.use(Tabs);
<<<<<<< HEAD
Vue.use(Empty);
=======
Vue.use(CountDown);
Vue.use(PwdField);
Vue.use(AuthcodeField);
Vue.use(CouponList);
>>>>>>> 支付开发
// const _proto = Vue.prototype; // const _proto = Vue.prototype;
// const proto = Object.create(_proto); // const proto = Object.create(_proto);
......
This diff is collapsed.
/*
* @Description: 数据加密,aes加密数据主体,rsa加密aes密钥,aes密钥前端自己存储
* @Date: 2020-12-08 11:08:28
* @LastEditors: gzw
* @LastEditTime: 2021-01-29 19:06:27
*/
// perf forge.js较大,后期需要替换为其他库,以减小体积
// const forge = require('../utils/forge.min');
const CryptoJS = require('./crypto.min');
import { desSalt } from '@/api/pay.api';
// import uuidv1 from 'uuid/v1';
// import { parseTime } from './index';
// import { APP_ID, PUBLIC_KEY, PRIVATE_KEY } from '@/config/encrypt.config';
/**
* @description: 数据加密
* @param {String} data 数据源
* @return {String} 加密后的数据base64
*/
// export function encryption(data = '') {
// if (!data) return null;
// const key = generateRandomStr(16);
// const iv = key; // 后台约定iv与key一致
// const plaintext = typeof data === 'object' ? JSON.stringify(data) : data;
// const body = encryptDataByAes(plaintext, key, iv); // AES加密数据
// const encryptKey = encryptDataByPb(key); // RSA公钥加密AES密钥
// const signData = generateSign(plaintext);
// return {
// appId: APP_ID,
// body,
// encryptKey,
// ...signData
// };
// }
/**
* @description: 数据加密
* @message {String} message 数据源
* @return {String} 加密后的数据16进制
*/
export async function encryptByDESModeEBC(message) {
const [{ payPwdSalt }] = await desSalt();
var keyHex = CryptoJS.enc.Utf8.parse(payPwdSalt);
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString().toLocaleUpperCase();
}
/**
* @description: AES加密数据,默认CBC, Pki#cs7
* @param {String} txt 数据源
* @param {String} key 密钥,16位字符串
* @param {String} iv 初始化向量
* @return {String} 加密后的数据base64
*/
// function encryptDataByAes(txt, key, iv) {
// const cipherInstance = forge.cipher.createCipher('AES-CBC', key);
// cipherInstance.start({ iv });
// cipherInstance.update(forge.util.createBuffer(txt, 'utf8'));
// cipherInstance.finish();
// const ciphertext = cipherInstance.output.getBytes();
// return buffer2Base64(ciphertext);
// }
/**
* @description: 使用RSA公钥加密数据
* @param {String} txt 数据源
* @return {String} 加密后的数据base64
*/
// function encryptDataByPb(txt) {
// const publicKey = forge.pki.publicKeyFromPem(PUBLIC_KEY);
// const pbData = publicKey.encrypt(txt);
// return buffer2Base64(pbData);
// }
/**
* @description: RSA私钥+SHA1生成签名
* 签名组成结构nonce+appid+timestamp+body
* @param {String} txt 数据源
* @return {Object} 生成的sign数据,时间戳、Nonce
*/
// function generateSign(txt) {
// const timestamp = parseTime('');
// const nonce = generateNonce();
// const privateKey = forge.pki.privateKeyFromPem(PRIVATE_KEY);
// const md = forge.md.sha1.create();
// md.update(nonce + APP_ID + timestamp + txt, 'utf8');
// let sign = privateKey.sign(md);
// sign = buffer2Base64(sign);
// return {
// timestamp,
// nonce,
// sign
// };
// }
/**
* @description: buffer转base64
* @param {Buffer} buf buffer源数据
* @return {String} base64 字符串
*/
// function buffer2Base64(buf) {
// return forge.util.encode64(buf);
// }
/**
* @description: 生成nonce(uuid)
* 规则:以当前时间的uuid作为name,以随机生成的uuid作为namespace,生成最终的uuid
* @return {String} 生成的uuid
*/
// function generateNonce() {
// return uuidv1();
// }
/**
* @description: 生成随机字符串
* @param {Number} n 位数
* @return {String} 生成的字符串
*/
// function generateRandomStr(n) {
// const len = n || 32;
// const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789';
// const maxPos = chars.length;
// let pwd = '';
// for (let i = 0; i < len; i++) {
// pwd += chars.charAt(Math.floor(Math.random() * maxPos));
// }
// return pwd;
// }
...@@ -10,6 +10,6 @@ const http = new HttpRequest( ...@@ -10,6 +10,6 @@ const http = new HttpRequest(
function(loadingState) { function(loadingState) {
store.dispatch('change_loading', loadingState); store.dispatch('change_loading', loadingState);
} }
); ).getInstance();
export default http; export default http;
/*
* @Description: 支付微信h5, jsapi, 第三方收银台,跳转)
* @Date: 2020-07-28 15:03:52
* @LastEditors: gzw
* @LastEditTime: 2020-10-19 11:33:04
*/
import qs from 'qs';
import { isApp as isXyqb } from '@/service/validation.service';
/**
* payByWeixinJsapi
* @description: 微信通过jsapi支付
* 可以在微信浏览器调起支付
* @param {type}
* @return:
*/
function payByWeixinJsapi(info = {}, callback) {
function onBridgeReady() {
// eslint-disable-next-line no-undef
WeixinJSBridge.invoke('getBrandWCPayRequest', info, function(res) {
console.log(res);
if (res.err_msg == 'get_brand_wcpay_request:ok') {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
callback('ok');
} else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
// 支付取消
callback('cancel');
} else {
// 支付失败
callback('fail');
}
});
}
if (typeof WeixinJSBridge == 'undefined') {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
}
/**
* payByWeixinH5
* @description: 微信通过H5支付
* 可以在浏览器调起微信支付
* 在羊小咩(信用钱包)app中调起微信支付,会先唤起微信,原来的页面会自动跳转到redirect_url设定的地址
* @param {type}
* @return:
*/
function payByWeixinH5(info) {
if (!info.url) return;
if (!info.params) {
info.params = {};
}
info.params.isXyqb = isXyqb ? 1 : 0;
info.params.isWxH5 = 1;
const currentPath = encodeURIComponent(
window.location.origin +
'/payWaiting' +
qs.stringify(info.params, { encode: true, addQueryPrefix: true })
);
// window.location.href = `${info.url}&redirect_url=${currentPath}`;
const nextPage = document.createElement('a');
nextPage.setAttribute('href', `${info.url}&redirect_url=${currentPath}`);
nextPage.click();
}
/**
* payByALIH5
* @description: 支付宝支付
* @param {type}
* @return:
*/
function payByALIH5(info) {
if (!info.order_string) return;
console.log('ali:', info.order_string);
const aliWrap = document.createElement('div');
aliWrap.id = 'ALIWEB_WRAP';
aliWrap.setAttribute('id', 'ALIWEB_WRAP');
aliWrap.innerHTML = info.order_string;
document.body.appendChild(aliWrap);
document.forms[0].submit();
setTimeout(() => {
const ALIWEB_WRAP = document.getElementById('ALIWEB_WRAP');
if (ALIWEB_WRAP != null) ALIWEB_WRAP.parentNode.removeChild(ALIWEB_WRAP);
}, 500);
}
/**
* payByThirdPartyCashier
* @description: 第三方收银台
* @param {type}
* @return:
*/
function payByThirdPartyCashier(info) {
if (!info.url) return;
info.params.third = 1;
const currentPath = encodeURIComponent(
window.location.origin +
'/payWaiting' +
qs.stringify(info.params, { encode: true, addQueryPrefix: true })
);
const nextPage = document.createElement('a');
nextPage.setAttribute('href', `${info.url}&redirect_uri=${currentPath}`);
nextPage.click();
// window.location.href = `${info.url}&redirect_uri=${currentPath}`;
}
/**
* @description: 支付方式判断, 返回promise
* NATIVE=原生扫码支付.,APP=ap支付,,JSAPI=公众号支付/小程序支付,,MWEB=H5支付.,MICROPAY=刷卡支付,默认JSAPI
* @param {String} type 支付方式,THIRD -> 第三方,MWEB -> H5支付, JSAPI -> jsapi支付,默认支付方式,THIRD
* @param {Object/String} payInfo 支付信息
* @return {Promise} 回调
*/
export function payByWay(type = 'THIRD', payInfo) {
return new Promise((resolve, reject) => {
if (type === 'JSAPI' && !payInfo.url) {
payByWeixinJsapi(payInfo, function(e) {
if (e === 'ok') {
resolve();
} else {
reject();
}
});
} else {
if (payInfo.url.indexOf('tenpay') > -1) {
payByWeixinH5(payInfo);
reject();
} else if (type === 'ALIWEB') {
payByALIH5(payInfo);
resolve();
} else {
payByThirdPartyCashier(payInfo);
reject();
}
// resolve();
}
});
}
...@@ -310,6 +310,16 @@ export function idNoFormat(value) { ...@@ -310,6 +310,16 @@ export function idNoFormat(value) {
return $1 + ' ' + $2 + ' ' + $3; return $1 + ' ' + $2 + ' ' + $3;
}); });
} }
export function dateFormat(value) { export function dateFormat(value) {
return value.replace(/(\d{4})(\d{2})(\d{2})/, '$1.$2.$3'); return value.replace(/(\d{4})(\d{2})(\d{2})/, '$1.$2.$3');
} }
<<<<<<< HEAD
export function phoneFormat(value) {
return value.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
=======
export function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
>>>>>>> 支付开发
}
...@@ -102,3 +102,11 @@ strong { ...@@ -102,3 +102,11 @@ strong {
border-bottom: 0.026667rem solid #f2f3f5; border-bottom: 0.026667rem solid #f2f3f5;
transform: scaleY(0.5); transform: scaleY(0.5);
} }
.Vl__panel-tips {
color: @text-grey;
min-height: 143px;
p {
.text-14();
margin-bottom: @padding-xs;
}
}
\ No newline at end of file
// 覆盖cherry-ui样式 // 覆盖cherry-ui样式
@import "./var.less"; @import "./var.less";
@button-border-width: 0; @button-border-width: 1px;
@button-default-height: 37px; @button-default-height: 37px;
@button-default-line-height: 37px; @button-default-line-height: 37px;
@button-default-font-size: 16px; @button-default-font-size: 16px;
...@@ -25,9 +25,10 @@ ...@@ -25,9 +25,10 @@
@cell-group-title-color: @orange; @cell-group-title-color: @orange;
@cell-group-title-padding: @padding-xs @padding-lg @padding-xs; @cell-group-title-padding: @padding-xs @padding-lg @padding-xs;
@cell-group-title-font-size: 12px; @cell-group-title-font-size: 12px;
@cell-border-color: @grey-border; @cell-border-color: @gray-2;
@cell-right-icon-color: @gray-3; @cell-right-icon-color: @gray-3;
@cell-icon-size: 16px; @cell-icon-size: 16px;
@cell-clear-color: @gray-4;
@field-label-width: 75px; @field-label-width: 75px;
@dialog-width: 290px; @dialog-width: 290px;
...@@ -41,5 +42,4 @@ ...@@ -41,5 +42,4 @@
@picker-toolbar-padding: @padding-unit - 2 @padding-md; @picker-toolbar-padding: @padding-unit - 2 @padding-md;
@picker-font-size: 14px; @picker-font-size: 14px;
@loading-color: @white; @tabs-nav-background-color: @background-color;
@loading-text-color: @white; \ No newline at end of file
\ No newline at end of file
...@@ -11,10 +11,26 @@ ...@@ -11,10 +11,26 @@
@red-dark: #ee0a24; @red-dark: #ee0a24;
@orange: #faab0c; @orange: #faab0c;
@text-color-red:#E81800;
@text-color-light: #FF5A4B;
@font-color-disabled: #C0C4CC;
@font-color-dark: #333333;
@font-color-light: #909399;
@font-color-red: #F23E33;
@cherry-color-error: #dd524d;
@grey-border: #f2f3f5; @grey-border: #f2f3f5;
// Gradient Colors // Gradient Colors
@gradient-red: linear-gradient(269deg, #ff5d00 12%, #ff1900 86%); @gradient-red: linear-gradient(269deg, #ff5d00 12%, #ff1900 86%);
@gradient-pink: linear-gradient(180deg, #fff7f0 0%, #ffe4dc 100%); @gradient-pink: linear-gradient(180deg, #fff7f0 0%, #ffe4dc 100%);
@primary-bg: {
background-image: linear-gradient(269deg, #FF4B00 12%, #FF7705 86%);
background-image: linear-gradient(269deg, #FF5D00 12%, #FF1900 86%);
}
// Component Colors // Component Colors
@text-color: @black; @text-color: @black;
...@@ -35,6 +51,8 @@ ...@@ -35,6 +51,8 @@
@border-radius-lx: 20px; @border-radius-lx: 20px;
@border-radius-max: 999px; @border-radius-max: 999px;
@border-color-base: #E4E7ED;
// Padding // Padding
@padding-unit: 4px; @padding-unit: 4px;
@padding-xs: @padding-unit * 2; @padding-xs: @padding-unit * 2;
...@@ -44,9 +62,9 @@ ...@@ -44,9 +62,9 @@
@padding-xl: @padding-unit * 8; @padding-xl: @padding-unit * 8;
// Font // Font
@font-size-list: 10,11, 12, 13, 14, 16, 17, 18, 20, 26, 28, 30, 52; @font-size-list: 10,11, 12, 13, 14, 15, 16, 17, 18, 20, 24, 26, 28, 30, 52;
.generate-text(12); .generate-text(14);
.generate-text(@n, @i: 1) when (@i =< @n) { .generate-text(@n, @i: 1) when (@i =< @n) {
@font: extract(@font-size-list, @i); @font: extract(@font-size-list, @i);
.text-@{font} { .text-@{font} {
......
@import '../../style/var.less';
.page {
padding: @page-padding-lg @page-padding-lg 0;
height: calc(100% - @page-padding-lg);
}
.banner {
position: relative;
display: flex;
justify-content: center;
background-image: @gradient-red;
background-repeat: no-repeat;
margin: -1 * @page-padding-lg -1 * @page-padding-lg 48px -1 * @page-padding-lg;
padding: @padding-md @padding-lg 0;
}
.card {
&__icon {
height: 200px;
width: 200px;
margin: 0 auto;
display: block;
}
&__info {
padding: 20px 0;
color: #fff;
font-size: 28px;
text-align: center;
}
}
<template>
<div class="page">
<div class="banner">
<div class="card">
<p class="card__info">{{ list.length }}</p>
</div>
</div>
<svg-icon icon-class="ufo" class="card__icon" />
</div>
</template>
<script>
import demoApi from '@/api/demo.auth';
export default {
data() {
return {
list: []
};
},
mounted() {
this.getList();
},
methods: {
async getList() {
const res = await demoApi.recommendLike();
this.list = res;
}
}
};
</script>
<style lang="less" src="./index.less" scoped></style>
@bottom-height: 60px;
.order-detail {
padding-bottom: @bottom-height;
}
.Od {
&__item {
background-color: @white;
border-radius: @border-radius-sm;
margin: @padding-sm 0;
padding: 0 @padding-xs;
}
&__status {
padding-top: 8px;
padding-bottom: 8px;
display: flex;
justify-content: center;
align-items: center;
&-desc {
margin-right: 10px;
width: 142px;
}
&-statusTxt {
.text-16();
color: @text-color;
}
&-txt {
.text-12();
color: @text-grey;
}
&-img {
width: 100px;
height: 100px;
}
}
&__price {
color: @text-grey;
.text-13();
&-statistic {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
span:last-child {
color: @text-color;
}
}
&-bold {
font-weight: @font-weight-bold - 100;
color: @red-light !important;
.text-15();
}
}
&__info {
&-item {
padding: 8px 0;
color: @text-grey;
.text-13();
span {
color: @gray-5;
}
}
}
&__bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: calc(100% - @padding-sm * 2);
background-color: @white;
padding: 0 @padding-sm;
height: @bottom-height;
display: flex;
align-items: center;
justify-content: flex-end;
.cr-button {
margin-left: @padding-xs;
}
}
}
\ No newline at end of file
<template>
<div class="page order-detail">
<div class="Od__item Od__status">
<div class="Od__status-desc">
<div class="Od__status-statusTxt">{{ orderInfo.orderStatusInfo.orderStatusText }}</div>
<div class="Od__status-txt">{{ orderInfo.orderStatusInfo.text }}</div>
</div>
<cr-image
width="2.666667rem"
height="2.666667rem"
class="Od__status-img"
:src="orderStatus"
/>
</div>
<div class="Od__item Od__list">
<list-item :list="orderInfo.skuList" :show-copy="true" />
</div>
<div class="Od__item Od__price">
<!-- 活动优惠金额 -->
<div v-if="orderInfo.feeInfo.activityDeductionFee" class="Od__price-statistic">
<span>活动优惠</span>
<span>¥{{ orderInfo.feeInfo.activityDeductionFee || 0 }}</span>
</div>
<!-- 优惠券抵扣金额 -->
<div v-if="orderInfo.feeInfo.couponDeductionFee" class="Od__price-statistic">
<span>优惠</span>
<span>¥{{ orderInfo.feeInfo.couponDeductionFee || 0 }}</span>
</div>
<!-- 运费优惠券抵扣金额 -->
<div v-if="orderInfo.feeInfo.freightDeductionFee" class="Od__price-statistic">
<span>运费优惠</span>
<span>¥{{ orderInfo.feeInfo.freightDeductionFee || 0 }}</span>
</div>
<div class="Od__price-statistic">
<span>运费</span>
<!-- 订单运费总金额 -->
<span>¥{{ orderInfo.feeInfo.totalFreightFee || 0 }}</span>
</div>
<div class="Od__price-statistic">
<span v-if="orderInfo.orderStatusInfo.orderStatus == 11">待付款</span>
<span v-else>实付款</span>
<!-- 订单总金额 -->
<span class="Od__price-bold">¥{{ orderInfo.feeInfo.totalFee || 0 }}</span>
</div>
</div>
<div class="Od__item Od__info">
<div class="Od__info-item">
订单编号:<span>{{ orderInfo.orderDetail.orderNo || '' }}</span>
</div>
<div class="Od__info-item">
下单时间:<span>{{ orderInfo.orderDetail.orderTime || '' }}</span>
</div>
<div v-if="orderInfo.orderDetail.payTime" class="Od__info-item">
支付时间:<span>{{ orderInfo.orderDetail.payTime || '' }}</span>
</div>
<div v-if="orderInfo.payDetail && orderInfo.payDetail.aliPayAmt" class="Od__info-item">
支付宝支付金额:<span>{{ orderInfo.payDetail.aliPayAmt || '' }}</span>
</div>
<div v-if="orderInfo.payDetail && orderInfo.payDetail.wxPayAmt" class="Od__info-item">
微信支付金额:<span>{{ orderInfo.payDetail.wxPayAmt || '' }}</span>
</div>
<div v-if="orderInfo.orderDetail.payType" class="Od__info-item">
支付方式:<span>{{ payType }}</span>
</div>
</div>
<div class="Od__bottom">
<cr-button
v-if="orderInfo.orderStatusInfo.orderStatus !== 41"
size="small"
plain
type="default"
shape="circle"
@click="contractCS"
>
联系客服
</cr-button>
<cr-button
v-if="orderInfo.orderStatusInfo.orderStatus === 11"
size="small"
plain
type="primary"
shape="circle"
@click="openCancelPopup"
>
取消订单
</cr-button>
<cr-button
v-if="orderInfo.orderStatusInfo.orderStatus === 11"
size="small"
plain
type="primary"
shape="circle"
@click="toPay"
>
付款</cr-button
>
<cr-button
v-if="orderInfo.orderStatusInfo.orderStatus === 21"
size="small"
plain
type="primary"
shape="circle"
@click="orderNotify"
>
提醒发货
</cr-button>
<cr-button
v-if="orderInfo.orderStatusInfo.orderStatus === 31"
size="small"
plain
type="primary"
shape="circle"
@click="toGoods"
>
再次购买
</cr-button>
</div>
<cancel-popup v-model="showCancelPopup" :order-info="currentOrder" />
</div>
</template>
<script>
import ListItem from '../orderList/components/ListItem.vue';
import CancelPopup from '../orderList/components/CancelPopup.vue';
import orderApi from '@/api/order.api';
import img11 from '@/assets/images/order/11.png';
import img21 from '@/assets/images/order/21.png';
import img41 from '@/assets/images/order/41.png';
import img51 from '@/assets/images/order/51.png';
const orderStatusImgs = {
11: img11,
21: img21,
41: img41,
51: img51,
61: img51,
62: img51
};
export default {
name: 'OrderDetail',
components: {
ListItem,
CancelPopup
},
data() {
return {
orderInfo: {
feeInfo: {},
orderDetail: {},
orderStatusInfo: {},
receiverInfo: {},
skuList: [],
payDetail: {},
orderNo: ''
},
showCancelPopup: false
};
},
computed: {
orderStatus() {
return orderStatusImgs[this.orderInfo.orderStatusInfo.orderStatus] || '';
},
currentOrder() {
return {
orderNo: this.orderNo
};
},
payType() {
return this.orderInfo.orderDetail.payType === 1 ? '微信' : '支付宝';
}
},
mounted() {
this.orderNo = this.$route.query.orderNo;
this.getDetail();
},
methods: {
async getDetail() {
const [res] = await orderApi.orderDetail({ orderNo: this.orderNo });
this.orderInfo = res;
},
contractCS() {
window.location.href =
'https://www.sobot.com/chat/h5/v2/index.html?sysnum=84ed0ad93caa47b0a9d1600824546b35&source=2';
},
openCancelPopup() {
this.showCancelPopup = true;
},
toPay() {
this.$router.push({ path: '/pay', query: { orderNo: this.currentOrder.orderNo } });
},
toGoods() {
this.$router.push({ path: '/goods' });
},
orderNotify() {
this.$toast.success('已通知卖家');
},
copyPwd(item) {
this.$toast.success('已复制');
console.log(item);
}
}
};
</script>
<style lang="less" src="./index.less" scoped></style>
<template>
<cr-popup
v-model="show"
closeable
round
position="bottom"
get-container="body"
class="cancel-popup"
>
<div class="Cp__head">
<div class="Cp__head-title">请选择原因</div>
<div class="Cp__head-desc">订单一旦取消,无法恢复,金额/积分将原路返还</div>
</div>
<cr-radio-group v-model="reasonType" class="Cp__list-wrap">
<cr-cell-group class="Cp__list">
<cr-cell
v-for="(item, index) in reasonList"
:key="index"
:title="item.cancelReason"
@click="onCellClick(item.cancelReasonType)"
>
<template #right-icon>
<cr-radio ref="checkboxes" :name="item.cancelReasonType" class="Cp__list-radio" />
</template>
</cr-cell>
</cr-cell-group>
</cr-radio-group>
<div class="Cp__button">
<cr-button block type="primary" shape="circle" @click="handleRadioSubmit">提交</cr-button>
</div>
</cr-popup>
</template>
<script>
const EVENT_INPUT = 'input';
import orderApi from '@/api/order.api';
export default {
name: 'CancelPopup',
props: {
value: Boolean,
orderInfo: {
type: Object,
default: () => {}
}
},
data() {
return {
show: false,
reasonType: '',
reasonList: [
{
cancelReasonType: 1,
cancelReason: '收货地址填错了'
},
{
cancelReasonType: 2,
cancelReason: '忘记支付密码/余额不足'
},
{
cancelReasonType: 3,
cancelReason: '无法正常支付'
},
{
cancelReasonType: 4,
cancelReason: '不想买了'
},
{
cancelReasonType: 5,
cancelReason: '其他原因'
}
]
};
},
computed: {
reason() {
return this.reasonList.find(item => item.cancelReasonType === this.reasonList);
}
},
watch: {
value(val) {
this.show = val;
}
},
methods: {
onCellClick(name) {
this.reasonType = name;
},
async handleRadioSubmit() {
const [res] = await orderApi.orderCancel({
orderNo: this.orderInfo.orderNo,
...this.reason
});
if (res) {
this.$toast('已取消');
this.$emit(EVENT_INPUT, false);
}
}
}
};
</script>
<style lang="less" scoped>
.order-list {
padding: 0 @padding-sm;
}
.Cp {
&__head {
text-align: center;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 @padding-md;
&-title {
.text-16();
color: @text-color;
font-weight: bold;
}
&-desc {
.text-13();
color: @text-grey;
}
}
&__list {
flex: 1;
&-radio {
margin-right: 0;
}
}
&__button {
border-top: 1px solid @grey-border;
padding: @padding-sm - 2 @padding-xs;
}
}
</style>
<template>
<div class="order-list">
<cr-list
v-if="list.length"
v-model="listLoading"
:finished="listFinished"
finished-text="没有更多了"
@load="onLoad"
>
<div v-for="(item, index) in list" :key="index" class="Ol__item" @click="toDetail(item)">
<div class="Ol__head">
<span class="Ol__head-title">当前订单状态</span>
<span class="Ol__head-state">{{ item.orderStatusText }}</span>
</div>
<div class="Ol__body">
<list-item :list="item.skuList" />
</div>
<div class="Ol__foot">
<div class="Ol__foot-settle">
<span>总价¥{{ item.totalFee }}</span>
<span>优惠¥{{ item.reduceFee }}</span>
<span>
实付款<strong>¥{{ item.originalFee }}</strong>
</span>
</div>
<div class="Ol__foot-btns">
<cr-button
v-if="item.orderStatus == 11"
size="small"
plain
type="default"
shape="circle"
@click="onOptionClick(item, 'cancel')"
>
取消订单
</cr-button>
<cr-button
v-if="item.orderStatus == 11"
size="small"
plain
type="primary"
shape="circle"
@click="onOptionClick(item, 'pay')"
>
付款
</cr-button>
<cr-button
v-if="item.orderStatus == 21"
size="small"
plain
type="default"
shape="circle"
@click="onOptionClick(item, 'notify')"
>
提醒发货
</cr-button>
<cr-button
v-if="item.orderStatus == 31"
size="small"
plain
type="primary"
shape="circle"
@click="onOptionClick(item, 'again')"
>
再次购买
</cr-button>
</div>
</div>
</div>
</cr-list>
<div v-else class="Ol__noData">
<cr-empty
image="https://img.lkbang.net/xcx/empty@2x.png"
image-size="4rem"
description="暂无订单~"
>
<cr-button size="small" plain type="primary" shape="circle" @click="toHome">
返回充值中心
</cr-button>
</cr-empty>
</div>
</div>
</template>
<script>
import ListItem from './ListItem.vue';
const EVENT_LOADING = 'load';
const EVENT_CLICK = 'option-click';
export default {
name: 'OrderSkuList',
components: {
ListItem
},
props: {
list: {
type: Array,
default: () => []
},
loading: Boolean,
finished: Boolean
},
data() {
return {
listLoading: false,
listFinished: false
};
},
watch: {
loading(val) {
this.listLoading = val;
},
finished(val) {
this.listFinished = val;
}
},
methods: {
toDetail(order) {
this.$router.push({ path: '/orderDetail', query: { orderNo: order.orderNo } });
},
onLoad() {
this.$emit(EVENT_LOADING);
},
onOptionClick(orderInfo, eventType) {
this.$emit(EVENT_CLICK, { orderInfo, eventType });
},
toHome() {
this.$router.replace({ path: '/home' });
}
}
};
</script>
<style lang="less" scoped>
.order-list {
padding: 0 @padding-sm;
.cr-empty {
text-align: center;
}
@{deep} .cr-empty__image {
height: auto !important;
}
}
.Ol {
&__item {
.text-12();
background-color: @white;
border-radius: @border-radius-md;
margin-bottom: @padding-sm;
}
&__head {
padding: @padding-sm @padding-xs;
border-bottom: 1px solid @grey-border;
display: flex;
justify-content: space-between;
&-title {
color: @text-grey;
}
&-state {
color: @red;
}
}
&__foot {
border-top: 1px solid @grey-border;
padding: @padding-sm - 2 @padding-xs;
&-settle {
text-align: right;
color: @text-grey;
span {
vertical-align: baseline;
&:last-child {
color: @text-color;
.text-13();
strong {
.text-14();
margin-left: @padding-unit;
}
}
}
}
&-btns {
text-align: right;
margin-top: @padding-sm - 2;
.cr-button {
margin-left: @padding-unit;
}
}
}
&__noData {
.cr-button {
background: transparent;
}
}
}
</style>
<template functional>
<div class="Ol__body">
<div v-for="(it, idx) in props.list" :key="idx" class="Ol__body-item">
<cr-image :src="it.imageUrl" width="2.266667rem" height="2.266667rem" class="Ol__body-img" />
<div class="Ol__body-content">
<div class="Ol__body-row">
<span class="Ol__body-title">
{{ it.skuName }}
</span>
<span v-if="it.salePrice" class="Ol__body-salePrice">¥{{ it.salePrice }}</span>
</div>
<div v-if="it.count" class="Ol__body-row">
<span class="Ol__body-count">{{ it.count }}</span>
</div>
<div class="Ol__body-skus">
<div v-if="it.virtualChargeAttrs.account" class="Ol__body-sku">
<span>充值帐户:</span>
<span class="Ol__body-val">{{ it.virtualChargeAttrs.account }}</span>
</div>
<div v-if="it.virtualChargeAttrs.registrationLocation" class="Ol__body-sku">
<span>归属地:</span>
<span class="Ol__body-val">{{ it.virtualChargeAttrs.account }}</span>
</div>
<div v-if="it.virtualChargeAttrs.cardNo" class="Ol__body-sku">
<span>卡号:</span>
<span class="Ol__body-val">{{ it.virtualChargeAttrs.cardNo }}</span>
</div>
<div v-if="it.virtualChargeAttrs.cardPassword" class="Ol__body-sku">
<span>卡密:</span>
<span class="Ol__body-val">{{ it.virtualChargeAttrs.cardPassword }}</span>
<a
v-if="props.showCopy"
href="javascript:;"
@click="parent.copyPwd(it.virtualChargeAttrs)"
>
复制
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ListItem',
props: {
list: {
type: Array,
default: () => []
},
showCopy: Boolean
}
};
</script>
<style lang="less" scoped>
.Ol {
&__body {
&-item {
padding: @padding-sm - 2 @padding-xs;
display: flex;
border-bottom: 1px solid @grey-border;
&:last-child {
border-bottom: 0;
}
}
&-img {
width: 85px;
height: 85px;
}
&-content {
margin-left: @padding-sm - 2;
flex: 1;
}
&-row {
display: flex;
justify-content: space-between;
}
&-title {
.text-13();
color: @text-color;
flex: 1;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
&-salePrice {
.text-13();
color: @text-color;
width: 78px;
text-align: right;
}
&-count {
.text-12();
margin-left: auto;
color: @text-grey;
}
&-sku {
.text-12();
display: flex;
align-items: center;
color: @text-grey;
a {
.text-12();
margin-left: auto;
color: @red;
}
}
&-val {
color: @gray-5 !important;
}
}
}
</style>
.cr-tabs {
margin-top: 0 !important;
}
\ No newline at end of file
<template>
<div class="page page__nopad">
<cr-tabs v-model="currentTab" sticky :offset-top="isApp ? 0 : 48" @change="handleTabChange">
<cr-tab v-for="(item, index) in navList" :key="index" :title="item.title" :name="index">
<list
:list="item.list"
:loading="item.loading"
:finished="item.finished"
@load="handleLoad"
@option-click="handleOptionClick"
/>
</cr-tab>
</cr-tabs>
<cancel-popup v-model="showCancelPopup" :order-info="currentOrder" />
</div>
</template>
<script>
import orderApi from '@/api/order.api';
import List from './components/List';
import { isApp } from '@/service/validation.service';
import CancelPopup from './components/CancelPopup';
const commonParams = {
loading: false,
finished: false,
page: 1,
pageSize: 10,
list: []
};
export default {
name: 'OrderList',
components: {
List,
CancelPopup
},
data() {
return {
isApp,
showCancelPopup: false,
currentTab: 0,
navList: [
{
state: 0,
title: '全部',
...JSON.parse(JSON.stringify(commonParams))
},
{
state: 1,
title: '待付款',
...JSON.parse(JSON.stringify(commonParams))
},
{
state: 2,
title: '待发货',
...JSON.parse(JSON.stringify(commonParams))
},
{
state: 3,
title: '待收货',
...JSON.parse(JSON.stringify(commonParams))
},
{
state: 4,
title: '已完成',
...JSON.parse(JSON.stringify(commonParams))
}
],
currentOrder: {}
};
},
mounted() {
this.getList();
},
methods: {
handleTabChange(name) {
this.currentTab = name;
this.getList();
},
handleLoad() {
this.getList();
},
handleOptionClick(info) {
this.currentOrder = info.orderInfo || {};
switch (info.eventType) {
case 'cancel':
this.orderCancelPopup();
break;
case 'pay':
this.toPay();
break;
case 'notify':
this.orderNotify();
break;
case 'again':
this.toGoods();
break;
default:
this.currentItems = {};
break;
}
},
orderCancelPopup() {
this.showCancelPopup = true;
},
toPay() {
this.$router.push({ path: '/pay', query: { orderNo: this.currentOrder.orderNo } });
},
toGoods() {
this.$router.push({ path: '/goods' });
},
orderNotify() {
this.$toast.success('已通知卖家');
},
setNavListData(key, val) {
this.$set(this.navList[this.currentTab], key, val);
},
async getList() {
let { finished, page, pageSize, list, state: orderStatus } = this.navList[this.currentTab];
if (finished) return;
this.setNavListData('loading', true);
const [res] = await orderApi.orderList({
page,
pageSize,
orderStatus
});
if (res) {
this.setNavListData('loading', false);
list = [...list, ...res.orderList];
this.setNavListData('list', list);
if (!res.hasNext) {
this.setNavListData('finished', true);
} else {
page++;
this.setNavListData('page', page);
}
}
}
}
};
</script>
<style lang="less" src="./index.less" scoped></style>
<template>
<div class="Vl__account">
<cr-field
v-model="accountMask"
:placeholder="`请输入${info.name || ''}账号`"
clearable
@focus="inputBlur = false"
@blur="inputBlur = true"
>
<template #button>
<cr-image v-if="info.icon" :src="info.icon" width="0.64rem" height="0.64rem" />
</template>
</cr-field>
<div class="Vl__list" :class="{ show: !inputBlur }">
<div
v-for="(item, index) in list"
:key="index"
class="Vl__list-item"
@click="handleSelectPhone(item)"
>
<span class="phone">{{ phoneFormat(item.phone) }}</span>
<span class="phone-home">{{ item.home }}</span>
<span v-if="index === 0" class="current">上次充值</span>
</div>
</div>
</div>
</template>
<script>
import rechargeApi from '@/api/recharge.api';
import { phoneFormat } from '@/service/utils.service';
export default {
name: 'AccountInput',
props: {
value: String,
info: {
type: Object,
default: () => {}
}
},
data: function() {
return {
inputBlur: true,
showList: false,
list: [
{
phone: '17165445433',
home: '北京 移动'
},
{
phone: '17165345433',
home: '北京 移动'
},
{
phone: '17165345435',
home: '北京 移动'
}
]
};
},
computed: {
accountMask: {
get() {
return this.phoneFormat(this.value);
},
set(val) {
this.$emit('input', val.replace(/\s/g, ''));
// this.getPhoneHome();
}
}
},
methods: {
phoneFormat,
handleSelectPhone(item) {
this.$emit('input', item.phone);
},
async getPhoneHome() {
const [res] = await rechargeApi({ phoneNo: this.value });
this.phoneHome = res.phoneHome;
}
}
};
</script>
<style lang="less" scoped>
.Vl {
&__account {
position: relative;
@{deep} .cr-field {
padding: @padding-xs + 2 0;
&--control-in {
.text-24();
}
&::after {
right: 0;
left: 0;
}
}
}
&__list {
display: none;
position: absolute;
top: 50px;
left: -@padding-lg;
right: -@padding-lg;
z-index: 3;
background-color: @white;
box-shadow: 0px 7px 12px 0px rgba(0, 0, 0, 0.1);
&.show {
display: block;
}
&-item {
display: flex;
align-items: center;
padding: @padding-lg / 2 @padding-lg;
.phone {
color: @text-color;
.text-14();
margin-right: @padding-xs;
}
.phone-home {
color: @text-color;
.text-12();
max-width: 180px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex-shrink: 0;
overflow: hidden;
}
.current {
color: @text-grey;
.text-10();
margin-left: auto;
}
}
}
}
</style>
<template functional>
<div class="Vl__sku" :class="{ disabled: props.disabled, 'three-col': props.threeCol }">
<div
v-for="(item, index) in props.list"
:key="index"
class="Vl__sku-item"
:class="{
cheap: item.price - item.salePrice > 0,
active: props.info.skuNo === item.skuNo
}"
@click="parent.handleSkuSelected(item, index)"
>
<div class="Vl__sku-name">{{ item.skuName }}</div>
<div class="Vl__sku-price">{{ item.salePrice }}</div>
<div class="Vl__sku-tag">优惠</div>
</div>
</div>
</template>
<script>
export default {
name: 'SkuList',
props: {
threeCol: Boolean,
list: {
type: Array,
default: () => []
},
info: {
type: Object,
default: () => {}
},
disabled: Boolean
}
};
</script>
<style lang="less" scoped>
.Vl {
&__sku {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
position: relative;
z-index: 1;
&.disabled::before {
content: ' ';
z-index: 2;
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.6);
}
&.three-col {
.Vl__sku-item {
width: 103px;
}
}
&-item {
width: 162px;
height: 72px;
border-radius: @border-radius-sm - 2;
border: 1px solid @grey-border;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
margin-bottom: @padding-xs;
transition: all 0.1s linear;
&.cheap {
.Vl__sku-tag {
display: block;
color: @white;
background: linear-gradient(269deg, #ff5d00 12%, #ff1900 86%);
}
}
&.active {
background: #fff5f5;
border: 1px solid @red;
.Vl__sku-name {
color: @red;
}
.Vl__sku-price {
color: @red;
}
}
}
&-name {
color: @text-color;
.text-17();
font-weight: @font-weight-bold - 100;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex-shrink: 0;
overflow: hidden;
}
&-price {
color: @gray-5;
.text-12();
}
&-tag {
background: @gray-2;
.text-10();
border-radius: 0 @border-radius-sm - 2 0 @border-radius-sm - 2;
padding: 0 @padding-unit;
position: absolute;
top: 0;
right: 0;
display: none;
}
}
}
</style>
<template functional>
<div class="Vl__spu">
<div
v-for="(item, index) in props.list"
:key="index"
class="Vl__spu-item"
:class="{ 'Vl__spu-item_active': props.info.spuNo === item.spuNo }"
@click="parent.handleSpuSelected(item, index)"
>
<cr-image :src="item.icon" height="1.093333rem" width="1.093333rem" class="Vl__spu-icon" />
<div class="Vl__spu-name">{{ item.name }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'SpuList',
props: {
list: {
type: Array,
default: () => []
},
info: {
type: Object,
default: () => {}
}
}
};
</script>
<style lang="less" scoped>
.Vl {
&__spu {
padding: @padding-sm 0;
display: flex;
align-items: center;
overflow: auto;
&::before,
&::after {
content: ' ';
flex-shrink: 0;
display: block;
width: @padding-sm;
height: 97px;
}
&::after {
width: @padding-unit;
}
&-item {
height: 97px;
width: 95px;
border-radius: @border-radius-md;
background-color: @white;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-right: @padding-xs;
transition: all 0.2s linear;
&_active {
box-shadow: 0px 2px 12px 0px rgba(100, 101, 102, 0.12);
transform: scale(1.03);
}
}
&-name {
.text-14();
color: @text-grey;
padding: 0 @padding-sm;
margin-top: @padding-xs;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex-shrink: 0;
overflow: hidden;
}
}
}
</style>
@bottom-height: 60px;
.Vl {
&__spu {
padding: @padding-sm 0;
display: flex;
align-items: center;
overflow: auto;
&::before, &::after {
content: ' ';
flex-shrink: 0;
display: block;
width: @padding-sm;
height: 97px;
}
&::after {
width: @padding-unit;
}
&-item {
height: 97px;
width: 95px;
border-radius: @border-radius-md;
background-color: @white;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-right: @padding-xs;
transition: all .2s linear;
&_active {
box-shadow: 0px 2px 12px 0px rgba(100, 101, 102, 0.12);
transform: scale(1.03);
}
}
&-name {
.text-14();
color: @text-grey;
padding: 0 @padding-sm;
margin-top: @padding-xs;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
flex-shrink: 0;
overflow: hidden;
}
}
&__sku {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
position: relative;
z-index: 1;
&.disabled::before {
content: ' ';
z-index: 2;
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.6);
}
&-item {
width: 162px;
height: 72px;
border-radius: @border-radius-sm - 2;
border: 1px solid @grey-border;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
margin-bottom: @padding-xs;
transition: all .1s linear;
&.cheap {
.Vl__sku-tag {
display: block;
color: @white;
background: linear-gradient(269deg, #FF5D00 12%, #FF1900 86%);
}
}
&.active {
background: #FFF5F5;
border: 1px solid @red;
.Vl__sku-name {
color: @red;
}
.Vl__sku-price {
color: @red;
}
}
}
&-name {
color: @text-color;
.text-17();
font-weight: @font-weight-bold - 100;
}
&-price {
color: @gray-5;
.text-12();
}
&-tag {
background: @gray-2;
.text-10();
border-radius: 0 @border-radius-sm - 2 0 @border-radius-sm - 2;
padding: 0 @padding-unit;
position: absolute;
top: 0;
right: 0;
display: none;
}
}
&__panel {
background-color: @white;
border-radius: @border-radius-lx @border-radius-lx 0 0;
padding: @padding-lg @padding-lg @bottom-height;
min-height: 340px;
&-placeholder {
margin-bottom: -@padding-lg;
}
&-title {
color: @text-color;
.text-16();
font-weight: @font-weight-bold - 100;
margin: @padding-lg + @padding-unit 0 @padding-sm;
}
}
&__bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 2;
right: 0;
width: calc(100% - @padding-sm * 2);
background-color: @white;
border-top: 1px solid @grey-border;
padding: 0 @padding-sm;
height: 60px;
display: flex;
align-items: center;
justify-content: flex-end;
}
}
\ No newline at end of file
<template>
<div class="page page__nopad">
<cr-tabs v-model="currentTab" @change="handleTabChange">
<cr-tab v-for="(item, index) in spuData" :key="index" :title="item.name" :name="index" />
</cr-tabs>
<spu-list :list="spuList" :info="spuInfo" />
<div class="Vl__panel">
<account-input v-if="spuInfo.rechargeAccountType !== 2" v-model="account" :info="spuInfo" />
<div v-else class="Vl__panel-placeholder" />
<div class="Vl__panel-title">充值类型</div>
<sku-list :list="skuList" :info="skuInfo" />
<template v-if="tips">
<div class="Vl__panel-title">温馨提示</div>
<div class="Vl__panel-tips" v-html="tips" />
</template>
</div>
<div class="Vl__bottom">
<cr-button type="primary" block shape="circle" :disabled="disabled" @click="goOrder">
立即充值
</cr-button>
</div>
</div>
</template>
<script>
import rechargeApi from '@/api/recharge.api';
import orderApi from '@/api/order.api';
import tipsData from '@/api/tips';
import SpuList from './components/SpuList.vue';
import SkuList from './components/SkuList.vue';
import AccountInput from './components/AccountInput.vue';
export default {
name: 'VipLife',
components: {
SpuList,
SkuList,
AccountInput
},
data() {
return {
account: '',
currentTab: 0,
spuData: [],
spuInfo: {},
skuInfo: {},
skuList: []
};
},
computed: {
disabled() {
return this.spuInfo.rechargeAccountType !== 2 && !this.account;
},
spuList() {
return this.spuData[this.currentTab] ? this.spuData[this.currentTab].itemList : [];
},
tips() {
return tipsData[this.spuInfo.spuNo];
}
},
mounted() {
this.currentTab = +this.$route.query.spuType || 0;
this.$nextTick(() => {
this.getList();
});
},
methods: {
handleTabChange(name) {
this.currentTab = name;
this.spuInfo = this.spuData[name].itemList[0];
this.getSkuList();
},
handleSpuSelected(item, index) {
this.spuInfo = item;
this.spuInfo.index = index;
this.getSkuList();
},
handleSkuSelected(item, index) {
this.skuInfo = item;
this.skuInfo.index = index;
},
async getList() {
const [res] = await rechargeApi.getSpuList();
if (res) {
this.spuData = res.vipLife;
this.spuInfo = this.spuData[this.currentTab].itemList[0];
this.getSkuList();
}
},
async getSkuList() {
this.skuList = [];
const { spuNo: spuNos, type } = this.spuInfo;
const [res] = await rechargeApi.getSkuList({ spuNos, type });
if (res) {
this.skuList = res.rechargeList;
}
},
async goOrder() {
if (!this.account && this.spuInfo.rechargeAccountType !== 2)
return this.$toast.fail('请填写账号!');
if (!this.skuInfo.skuNo) return this.$toast.fail('请选择类型!');
const { skuNo, salePrice } = this.skuInfo;
const [res] = await orderApi.orderCreate({
totalFee: salePrice,
orderCouponIds: '',
freightCouponIds: '',
skuList: [
{
skuNo,
count: 1
}
]
});
res && this.$router.push({ path: '/pay', query: { orderNo: res.orderNo } });
}
}
};
</script>
<style lang="less" src="./index.less" scoped></style>
...@@ -140,6 +140,7 @@ module.exports = { ...@@ -140,6 +140,7 @@ module.exports = {
proxy: 'http://kdsp-vcc.liangkebang.net' proxy: 'http://kdsp-vcc.liangkebang.net'
}, },
css: { css: {
extract: false,
loaderOptions: { loaderOptions: {
less: { less: {
// 若使用 less-loader@5,请移除 lessOptions 这一级,直接配置选项。 // 若使用 less-loader@5,请移除 lessOptions 这一级,直接配置选项。
......
This diff is collapsed.
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