Commit 375c57f1 authored by FE-安焕焕's avatar FE-安焕焕 👣

Merge branch 'pay' into 'master'

Pay

See merge request !2
parents 36904b4b dc05b25f
import config from '@/config';
import http from '@/service/httpDecorator';
import { encryption } from '@/service/encrypt';
const { talosHost } = config;
export default {
......@@ -22,6 +23,6 @@ export default {
},
// 订单创建
orderCreate(data) {
return http.get(`${talosHost}/api/kdsp/order-info/e/vmSubmit`, data);
return http.post(`${talosHost}/api/kdsp/order-info/e/vmSubmit`, { data: encryption(data) });
}
};
import request from '@/service/httpDecorator';
import config from '@/config';
const { talosApi } = config;
import { encryption } from '@/service/encrypt';
const { talosHost } = config;
const queryPayInfo = function(data) {
return request.post(`${talosApi}/open/checkout`, data);
return request.post(`${talosHost}/open/checkout`, data);
};
const prepay = function(data) {
console.log('prepay-param', data);
return request.post(`${talosApi}/open/checkout/prepay`, data);
return request.post(`${talosHost}/open/checkout/prepay`, data);
};
const pay = function(data) {
return request.post(`${talosApi}/open/checkout/pay`, data, {
needScDeviceId: true
});
return request.post(
`${talosHost}/open/checkout/pay`,
{ data: encryption(data) },
{
needScDeviceId: true
}
);
};
const queryPayStatus = function(data) {
return request.post(`${talosApi}/open/checkout/pay_status/query`, data);
return request.post(`${talosHost}/open/checkout/pay_status/query`, data);
};
const sendSms = function(data) {
return request.post(`${talosApi}/open/checkout/send_sms`, data);
return request.post(`${talosHost}/open/checkout/send_sms`, data);
};
const desSalt = function() {
return request.get(`${talosApi}/vcc/account/salt`);
return request.get(`${talosHost}/vcc/account/salt`);
};
const h5AppyUrl = function() {
return request.get(`${talosApi}/vcc/xyqb_mall/app_url`);
return request.get(`${talosHost}/vcc/xyqb_mall/app_url`);
};
const getCoupon = function(params) {
return request.post(`${talosApi}/open/checkout/pay_coupon_list`, params);
return request.post(`${talosHost}/open/checkout/pay_coupon_list`, params);
};
const ocrFaceId = function(params) {
return request.post(`${talosApi}open/checkout/ocr_faceId`, params);
return request.post(`${talosHost}open/checkout/ocr_faceId`, params);
};
const getGoodsList = function(data) {
return request.get(`${talosHost}/vcc/xyqb/recommend/goods-list`, data, {
needScDeviceId: true,
hideLoading: true
});
};
export {
pay,
prepay,
sendSms,
queryPayInfo,
queryPayStatus,
desSalt,
h5AppyUrl,
getCoupon,
ocrFaceId
ocrFaceId,
queryPayInfo,
getGoodsList,
queryPayStatus
};
......@@ -8,11 +8,13 @@ export default {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/config');
},
// VIP充值中心SKU列表
getSkuList() {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/recharge-list');
getSkuList(spuNos, type) {
return http.get(
`${talosHost}/api/kdsp/virtual/recharge-center/recharge-list?spuNos=${spuNos}&type=${type}`
);
},
// 查询归属地
getPhoneHome() {
return http.get(talosHost + '/api/kdsp/virtual/recharge-center/phone-home');
getPhoneHome(phoneNo) {
return http.get(`${talosHost}/api/kdsp/virtual/recharge-center/phone-home?phoneNo=${phoneNo}`);
}
};
src/assets/images/addicon.png

1.06 KB | W: | H:

src/assets/images/addicon.png

465 Bytes | W: | H:

src/assets/images/addicon.png
src/assets/images/addicon.png
src/assets/images/addicon.png
src/assets/images/addicon.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/images/paying.png

4.36 KB | W: | H:

src/assets/images/paying.png

1.13 KB | W: | H:

src/assets/images/paying.png
src/assets/images/paying.png
src/assets/images/paying.png
src/assets/images/paying.png
  • 2-up
  • Swipe
  • Onion skin
<template>
<div class="reco">
<cr-list
v-model="loading"
:immediate-check="false"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="list">
<div class="left">
<items :data="leftList" />
</div>
<div class="right">
<items :data="rightList" />
</div>
</div>
</cr-list>
</div>
</template>
<script>
import Items from './RecoGoodsItem';
import { getGoodsList } from '@/api/pay.api';
export default {
components: {
Items
},
// props: {
// list: Array
// },
data() {
return {
finished: true,
loading: false,
list: []
};
},
computed: {
leftList: function() {
return this.list.filter((item, index) => {
if (index % 2 === 0) {
return item;
}
});
},
rightList: function() {
return this.list.filter((item, index) => index % 2);
}
},
methods: {
async onLoad() {
this.loading = true;
const [data = {}] = await getGoodsList({ pageNo: 1, pageSize: 10 });
this.list = [...this.list, ...data?.goodsList];
this.loading = false;
}
}
};
</script>
<style lang="less" scoped>
.reco {
width: 100%;
// height: 100%;
overflow: auto;
margin: 12px 0 70px 0;
background-color: #f7f7f7;
padding-bottom: 10px;
.list {
height: 100%;
overflow: auto;
clear: both;
}
.left {
float: left;
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
padding: 0 5px 0 10px;
box-sizing: border-box;
}
.right {
float: right;
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
padding-right: 10px;
box-sizing: border-box;
padding: 0 10px 0 5px;
}
@{deep} .cr-list__finished-text {
font-size: 14px;
}
}
</style>
<template>
<div class="goods">
<template v-for="item in data">
<div
v-if="item.goods"
:key="item.goodsId"
:class="['card']"
@click="toDetail(item.goods.jumpUrl)"
>
<div class="card__top">
<div class="card__head">
<cr-image
height="190px"
width="190px"
class="card__head__image"
object-fit="contain"
:src="item.goods.goodsImage"
/>
</div>
<p class="card__name">
<cr-image
v-if="item.goods.goodsTypeImage"
width="0.64rem"
height="auto"
class="add-title-label"
:src="item.goods.goodsTypeImage"
/>
{{ item.goods.goodsName }}
</p>
<p v-if="item.goods.serviceTypeList" class="card__service">
"{{ item.goods.serviceTypeList[0] }}"
</p>
<div class="card__tag">
<div v-for="(tag, index) in item.goods.tagList" :key="index" class="card__tag-wrap">
<span v-if="tag.type == 2" :key="index" class="card__tag-normal">{{ tag.name }}</span>
<img v-if="tag.type == 1" class="card__tag-icon" :src="tag.icon" alt="" />
</div>
</div>
</div>
<div class="card__bottom">
<div class="price">
<p>
<span class="price__icon">¥</span>
<span class="price__text">{{
item.goods.goodsSalePrice && item.goods.goodsSalePrice.replace('', '')
}}</span>
</p>
<p class="sale">已售{{ item.goods.saleCount }}</p>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
props: {
data: [Object, Array]
},
methods: {
toDetail(url) {
const data = {
event: 'openNewUrl',
data: {
newUrl: url // 需要打开的新链接
}
};
this.util.openNewUrl(data);
}
}
};
</script>
<style lang="less" scoped>
.add-title-label {
vertical-align: middle;
width: 24px;
margin-top: -2px;
}
.goods {
height: 100%;
}
.card {
font-size: 0;
background-color: #fff;
border-radius: 6px;
margin-bottom: 10px;
display: flex;
flex-direction: column;
position: relative;
justify-content: space-between;
&__head {
height: 173px;
border-radius: 6px;
overflow: hidden;
&__image {
height: 100%;
width: 100%;
}
}
&__name {
font-size: 13px;
line-height: 18px;
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
/*! autoprefixer: off */
-webkit-box-orient: vertical;
/*! autoprefixer: on */
margin: 8px 5px 0px 14px;
text-align: left;
.shop-tag {
vertical-align: middle;
margin-top: -2px;
}
}
&__service {
font-size: 13px;
color: #e1a069;
margin: 5px 14px;
}
&__tag {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
margin: 3px 5px 0 14px;
&-wrap {
margin-bottom: 5px;
}
&-text {
color: #e1a069;
font-size: 13px;
}
&-normal {
display: inline-block;
font-size: 10px;
box-sizing: border-box;
border: 1px solid #ff5a4b;
color: #ff5a4b;
padding: 0px 4px;
border-radius: 3px;
margin-right: 5px;
line-height: 14px;
}
&-icon {
height: 15px;
margin-right: 5px;
}
&-warn {
color: #ff4b00;
border: 1px solid #ff4b00;
}
}
&__bottom {
padding: 5px 5px 6px 14px;
.activity {
display: flex;
align-items: bottom;
&__free {
height: 14px;
margin-right: 3px;
}
}
.price__raw {
float: left;
color: #999;
font-size: 10px;
line-height: 14px;
text-decoration: line-through;
}
.sale {
color: #999;
font-size: 10px;
height: 20px;
display: flex;
align-items: flex-end;
margin-left: 5px;
}
.price {
display: flex;
align-items: center;
&__icon {
font-size: 12px;
color: #ec3333;
}
&__text {
font-size: 18px;
line-height: 25px;
color: #ec3333;
}
}
}
&::after {
clear: both;
}
}
</style>
......@@ -14,11 +14,6 @@ let protocol = window.location.protocol;
let payHost = protocol + '//mapi-qa.liangkebang.net/pay';
let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址
<<<<<<< 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';
export default { talosApi, operatorHost, payHost, shenceHost, test: true };
export default { talosHost, operatorHost, payHost, shenceHost, test: true };
export default [
{
path: '/',
redirect: '/pay'
redirect: '/home'
},
{
path: '/home',
name: 'home',
meta: {
title: '充值中心'
},
component: () => import('../views/home')
},
{
path: '/pay',
......@@ -9,7 +17,7 @@ export default [
meta: {
title: '收银台'
},
component: () => import('../pay')
component: () => import('../views/pay')
},
{
path: '/payFail',
......@@ -18,7 +26,7 @@ export default [
meta: {
title: '支付失败'
},
component: () => import('../pay/payResult')
component: () => import('../views/pay/payResult')
},
{
path: '/paySuccess',
......@@ -28,7 +36,7 @@ export default [
title: '支付成功',
success: true
},
component: () => import('../pay/payResult')
component: () => import('../views/pay/payResult')
},
{
path: '/vipLife',
......@@ -53,28 +61,15 @@ export default [
title: '订单详情'
},
component: () => import('../views/orderDetail')
},{
},
{
path: '/payWaiting',
name: 'payWaiting',
meta: {
title: '支付中',
success: true
},
component: () => import('../pay/payWaiting')
},
{
path: '/demo',
alias: ['/demo-page'],
name: 'demo-page',
back: false,
meta: {
title: 'DEMO',
has: {
header: true,
footer: true
}
},
component: () => import('../views/demo')
component: () => import('../views/pay/payWaiting')
},
{
path: '/error',
......
......@@ -27,14 +27,11 @@ import {
Sticky,
Tab,
Tabs,
<<<<<<< HEAD
Empty
=======
Empty,
CountDown,
PwdField,
AuthcodeField,
CouponList
>>>>>>> 支付开发
} from '@qg/cherry-ui';
import DialogFn from '@qg/cherry-ui/src/dialog/func';
// import "@qg/cherry-ui/dist/cherry.css";
......@@ -64,14 +61,11 @@ Vue.use(Loading);
Vue.use(List);
Vue.use(Tab);
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 = Object.create(_proto);
......
export const APP_ID = '102';
export const PUBLIC_KEY = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCGf4wnNJXHXM54wsmxTwhpiPaAp27zM
3QsrWu1+GOMoCDmAEOb2jYjyNhwBIeV9eY0fwoK+MfWBywbonypyXs1j5l/mTmFRQ8IZY
+xZF0t01cornoMpKJncoNiwqc9OnsiwGPqzIs/iLalBonppqhWSn0g99vFS2qgr0WnOWl
qLQIDAQAB
-----END PUBLIC KEY-----
`;
export const PRIVATE_KEY = `
-----BEGIN RSA PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIC46noXi7VhxDMCQNK3x
k7uVQICaIwhQ6Ye5c5XYaM9IgUhFUioQZ3llelM3oAnYqLuAZMU0POcNqm8qmr7I3fc8A
SZZKm5XUm6i/PI++xT+5c9zhHHsHumoSoTJ/bpS8xNhDFz/X2JQTIn6izFtBBnsMJRx5K
aJWXYebk1RyCVAgMBAAECgYA27bGxE+ccKXINykJbKOzItc80ok52raMuejTaTlNt0yJ3
SdzJOnN1q4jDG3g++4+Nsz6cwt8/dUOmPsoCCNTjMDUg40Cl8NopPMXvo/INwd2vJSEFr
JyC629pl3N7E18Iqjdt8jMy/zEMSDMPh7+NDtSDQqfQpeT0w31/Q1qdwQJBALtk/xIIPN
/zyCZcAIKtXvp0FrHslEL55q01kV6bwhyfAnyz+GcGJwxPWOKJ1FRCnzjOZRc3ygekMZU
WGLjszDkCQQCv2Qo/q97pjw4QwxsTwuuQKlLhAuBr2W0eTsnou9tk3i7PJX/xQXlccp0C
FRWAe3xdpYtapUiMJ8zpKkw+PC89AkBdn9UMRk9buKmL+LVMlJ/6U5uvIzrjx4UnjrblZ
L5znEIa2bPGjT1fGhmXfTM3Md3o/L1m/zmR3cfj65lIcw6JAkB0uf6qmz0CDnrIt6pOqN
HTRbT0NgOgs5hSSPyQJ7sPrsilqn/ONqcHrfD5A+PdAJtiUlQ5nIOWvYiwseogWbUFAkB
y6/cB1HblHJzuyJwaAW9vLNmbqeKW5DhU2QG7Jp1uUgk4GcopDAgFWlAjbQcBCGDyStHH
YxINn+qpKviRkeFf
-----END RSA PRIVATE KEY-----
`;
......@@ -4,13 +4,12 @@
* @LastEditors: gzw
* @LastEditTime: 2021-01-29 19:06:27
*/
// perf forge.js较大,后期需要替换为其他库,以减小体积
// const forge = require('../utils/forge.min');
import { cipher as AES, util as UTIL, pki as PKI, md as SHA1 } from 'node-forge';
import uuidv1 from 'uuid/v1';
import { parseTime } from './utils.service';
import { APP_ID, PUBLIC_KEY, PRIVATE_KEY } from './encrypt.config';
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: 数据加密
......@@ -18,21 +17,21 @@ import { desSalt } from '@/api/pay.api';
* @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
// };
// }
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: 数据加密
......@@ -56,25 +55,25 @@ export async function encryptByDESModeEBC(message) {
* @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);
// }
function encryptDataByAes(txt, key, iv) {
const cipher = AES.createCipher('AES-CBC', key);
cipher.start({ iv });
cipher.update(UTIL.createBuffer(txt, 'utf8'));
cipher.finish();
const ciphertext = cipher.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);
// }
function encryptDataByPb(txt) {
const publicKey = PKI.publicKeyFromPem(PUBLIC_KEY);
const pbData = publicKey.encrypt(txt);
return buffer2Base64(pbData);
}
/**
* @description: RSA私钥+SHA1生成签名
......@@ -82,51 +81,51 @@ export async function encryptByDESModeEBC(message) {
* @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
// };
// }
function generateSign(txt) {
const timestamp = parseTime('');
const nonce = generateNonce();
const privateKey = PKI.privateKeyFromPem(PRIVATE_KEY);
const md = SHA1.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);
// }
function buffer2Base64(buf) {
return UTIL.encode64(buf);
}
/**
* @description: 生成nonce(uuid)
* 规则:以当前时间的uuid作为name,以随机生成的uuid作为namespace,生成最终的uuid
* @return {String} 生成的uuid
*/
// function generateNonce() {
// return uuidv1();
// }
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;
// }
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,5 @@ const http = new HttpRequest(
function(loadingState) {
store.dispatch('change_loading', loadingState);
}
).getInstance();
);
export default http;
......@@ -314,12 +314,32 @@ export function idNoFormat(value) {
export function dateFormat(value) {
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 phoneFormat(mobile) {
/* 将手机号格式化 */
if (!mobile) return;
let value = mobile.replace(/\D/g, '').substr(0, 11); // 不允许输入非数字字符,超过11位数字截取前11位
let len = value.length;
if (len > 3 && len < 8) {
value = value.replace(/^(\d{3})/g, '$1 ');
} else if (len >= 8) {
value = value.replace(/^(\d{3})(\d{4})/g, '$1 $2 ');
}
return value;
}
export function checkPhoneFormat(mobile) {
console.log(mobile);
/* 校验手机号格式 */
if (!mobile) return;
const reg = /^1([3-9][0-9]|4[5,7]|5[0,1,2,3,5,6,7,8,9]|6[2,5,6,7]|7[0,1,7,8]|8[0-9]|9[1,8,9])\d{8}$/;
return reg.test(mobile);
}
export function phoneTrim(mobile) {
if (!mobile) return;
return mobile.replace(/\s/g, '');
}
export function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
>>>>>>> 支付开发
}
<template>
<p class="center-phone">
<span v-if="rechargeTel" class="center-phone-location"
>{{ isDefaultphone ? '默认号码' : '未知号码'
}}{{ phoneNoHome ? `(${phoneNoHome})` : '' }}</span
>
<cr-field
v-model="rechargeTel"
type="tel"
class="center-phone-field"
placeholder="请输入手机号码"
@input="changeTelFormat"
@blur="searchPhoneNoHome"
/>
</p>
</template>
<script>
import api from '@/api/recharge.api';
import { phoneFormat, phoneTrim, checkPhoneFormat } from '@/service/utils.service';
const { getPhoneHome } = api;
export default {
props: {
userPhoneInfo: Object
},
data() {
return {
phoneNoHome: null,
rechargeTel: null
};
},
computed: {
isDefaultphone: function() {
return this.phoneNo === this.userPhoneInfo?.phoneNo;
},
phoneNo: function() {
return phoneTrim(this.rechargeTel);
}
},
watch: {
userPhoneInfo: function(val) {
this.phoneNoHome = val?.phoneNoHome;
this.changeTelFormat(val?.phoneNo);
}
},
created() {},
methods: {
changeTelFormat(phone) {
this.phoneNoHome = '';
this.rechargeTel = phoneFormat(phone);
},
async searchPhoneNoHome() {
if (!this.phoneNo) return;
if (!checkPhoneFormat(this.phoneNo)) {
this.$toast.fail('请您输入正确的手机号');
this.$emit('input', { phoneNo: '', phoneNoHome: this.phoneNoHome });
return;
}
const [data] = await getPhoneHome(this.phoneNo);
this.phoneNoHome = data?.phoneHome;
this.$emit('input', { phoneNo: this.phoneNo, phoneNoHome: this.phoneNoHome });
}
}
};
</script>
<style lang="less">
.center {
&-phone {
border-bottom: @border-width-base solid @gray-2;
.cr-cell__title {
display: none;
}
.cr-cell {
.text-24();
color: @black;
padding: @padding-xs 0;
}
&-location {
.text-12();
color: @gray-5;
}
&-field {
text-indent: -2px;
}
}
}
</style>
<template>
<cr-tabs
v-model="rechargeType"
background="#ffffff"
class="center-recharge"
@change="changeRechargeType"
>
<cr-tab title="话费充值" :title-style="tabItemStyle" name="recharge">
<PhoneRechargeList
:list="rechargeList.recharge"
:info="selectedRechargeInfo"
:three-col="true"
/>
</cr-tab>
<cr-tab title="话费慢冲" :title-style="tabItemStyle" name="slowRecharge">
<PhoneRechargeList
:list="rechargeList.slowRecharge"
:info="selectedRechargeInfo"
:three-col="true"
/></cr-tab>
</cr-tabs>
</template>
<script>
import api from '@/api/recharge.api';
import PhoneRechargeList from '../../vipLife/components/SkuList.vue';
const { getSkuList } = api;
export default {
components: {
PhoneRechargeList
},
props: { phoneRecharge: Object },
data() {
return {
rechargeType: 'recharge',
tabItemStyle: {
width: '75px',
flex: 'none'
},
selectedRechargeInfo: {},
rechargeList: {
recharge: [],
slowRecharge: []
}
};
},
watch: {
phoneRecharge: function() {
this.changeRechargeType('recharge');
}
},
methods: {
async changeRechargeType(name) {
const [data] = await getSkuList(
this.phoneRecharge[`${name}SpuNo`],
this.phoneRecharge[`${name}Type`]
);
this.rechargeList[name] = data?.rechargeList || [];
},
handleSkuSelected(item) {
this.selectedRechargeInfo = item;
this.$emit('selectedRecharge', item);
}
}
};
</script>
<style lang="less">
.center-recharge {
height: 300px;
margin-top: @padding-xs;
margin-bottom: @padding-xs;
}
</style>
<template>
<div class="top">
<div class="top-link">
<a href="#" class="top-link-title top-link-title-first" @click="goOrderList">充值订单</a>
<a
href="https://www.sobot.com/chat/h5/v2/index.html?sysnum=84ed0ad93caa47b0a9d1600824546b35&source=2"
class="top-link-title"
>联系客服</a
>
</div>
<div class="top-tabs">
<p class="top-tabs-title">娱乐生活</p>
<div class="top-tabs-recharge">
<div v-for="life in vipLife" :key="life.type" class="recharge" @click="goVipLife(life)">
<cr-image width="34px" height="34px" :src="life.icon" />
<p class="recharge-name">{{ life.name }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
value: Array
},
data() {
return {
vipLife: this.value
};
},
watch: {
value: function(val) {
this.vipLife = val;
}
},
methods: {
goVipLife(spu) {
this.$router.push({
name: 'vipLife',
query: {
spuType: spu.type
}
});
},
goOrderList() {
this.$router.push({ name: 'orderList' });
}
}
};
</script>
<style lang="less">
.top {
width: 100%;
height: 132px;
padding: 10px;
background-size: 100%;
box-sizing: border-box;
background-repeat: no-repeat;
background: url('~@/assets/images/ellipse.png');
.recharge {
width: 55px;
height: 55px;
text-align: center;
&-name {
.text-12();
color: @gray-5;
}
}
&-link {
text-align: right;
&-title {
.text-14();
width: 64px;
color: @white;
padding: 0 @padding-xs;
&-first {
border-right: 1px solid @white;
}
}
}
&-tabs {
height: 108px;
width: 351px;
margin: auto;
overflow: hidden;
position: relative;
background: @white;
box-sizing: border-box;
border-radius: @border-radius-md;
padding: @padding-xs @padding-sm;
top: @padding-md;
&-title {
.text-14();
color: @black;
font-weight: bold;
}
&-recharge {
display: flex;
overflow-x: auto;
overflow-y: hidden;
margin-top: @padding-sm;
justify-content: space-between;
}
}
}
</style>
.home {
height: 100%;
.center {
width: 351px;
height: 451px;
box-sizing: border-box;
background: @white;
border-radius: 8px;
padding: 14px;
margin: auto;
margin-top: 50px;
.button{
.text-18();
}
}
}
\ No newline at end of file
<template>
<div class="home">
<RechargeTop v-model="vipLife" />
<div class="center">
<RechargeInput v-model="rechargePhoneInfo" :user-phone-info="userPhoneInfo" />
<RechargeList :phone-recharge="phoneRecharge" @selectedRecharge="selectedRecharge" />
<cr-button
block
type="primary"
shape="circle"
class="button"
:disabled="!disabled"
@click="goOrder"
>
{{ selectedRechargeInfo.salePrice ? `¥${selectedRechargeInfo.salePrice}` : '' }}立即充值
</cr-button>
</div>
</div>
</template>
<script>
import api from '@/api/recharge.api';
import orderApi from '@/api/order.api';
import RechargeTop from './components/RechargeTop.vue';
import RechargeList from './components/RechargeList.vue';
import RechargeInput from './components/RechargeInput.vue';
import localStorage from '@/service/localStorage.service';
const { getSpuList } = api;
export default {
components: {
RechargeTop,
RechargeList,
RechargeInput
},
data() {
return {
vipLife: [],
rechargeAmount: 0,
phoneRecharge: {},
userPhoneInfo: {},
rechargeType: null,
rechargePhoneInfo: {},
selectedRechargeInfo: {}
};
},
computed: {
disabled: function() {
return this.rechargePhoneInfo.phoneNo && this.selectedRechargeInfo.salePrice;
}
},
created() {},
async mounted() {
const [data] = await getSpuList();
if (data) {
this.vipLife = data.vipLife || [];
this.phoneRecharge = data.phoneRecharge || {};
this.userPhoneInfo = data.userPhoneInfo || {};
this.rechargePhoneInfo = data.userPhoneInfo || {};
localStorage.set('phoneNo', data.userPhoneInfo.phoneNo);
}
},
methods: {
selectedRecharge(item) {
this.selectedRechargeInfo = item;
},
async goOrder() {
const { skuNo, salePrice } = this.selectedRechargeInfo;
const [res] = await orderApi.orderCreate({
totalFee: salePrice,
registrationLocation: this.rechargePhoneInfo.phoneNoHome,
virtualRechargeType: this.phoneRecharge[`${this.rechargeType}Type`],
skuList: [
{
skuNo,
count: 1
}
]
});
res && this.$router.push({ path: '/pay', query: { orderNo: res.orderNo } });
}
}
};
</script>
<style lang="less">
@import './index';
</style>
......@@ -132,7 +132,7 @@ export default {
margin-top: -12px;
left: 50%;
margin-left: -12px;
background: url('../../assets/images/addicon.png') no-repeat;
background: url('../../../assets/images/addicon.png') no-repeat;
background-size: 100%;
}
}
......
......@@ -5,7 +5,7 @@
<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>
<p class="phone">已发送至 {{ getPhone() }}17611682272</p>
<cr-authcode-field
span-size="20px"
type="number"
......@@ -30,6 +30,7 @@
<script>
// import { registeredEvents } from '@/utils/sa';
import localStorage from '@/service/localStorage.service';
import { sendSms as sendSmsApi } from '@/api/pay.api';
export default {
props: {
......@@ -85,9 +86,9 @@ export default {
}
},
getPhone() {
// const phone = uni.getStorageSync('phone');
// const phoneS = phone.replace(/(\d{3})\d*(\d{4})/, '+86 $1 **** $2');
// return phoneS;
const phone = localStorage.get('phoneNo');
const phoneS = phone.replace(/(\d{3})\d*(\d{4})/, '+86 $1 **** $2');
return phoneS;
},
sendSa() {
// if (!this.numberArr.length && !this.isDel) {
......@@ -129,7 +130,7 @@ export default {
font-size: 16px;
}
&-des {
padding: 10px 0 0 8px;
padding: 17px 0 0 8px;
display: flex;
justify-content: flex-start;
font-size: 12px;
......@@ -139,6 +140,7 @@ export default {
margin: auto;
}
.phone {
padding-left: 8px;
margin-top: 8px;
color: #323233;
font-size: 14px;
......
.app {
.pay {
// width: 100%;
padding: @padding-xs;
}
......
<template>
<div class="app">
<div class="pay">
<!-- 支付倒计时 -->
<div class="price-box">
<p class="price">{{ displayInfo.orderAmt }}100</p>
......
<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 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>
<p class="tips">
您可在“我的-我的订单”中查看详情
</p>
<div class="actions">
<cr-button shape="circle" class="actions__back" :plain="true" type="primary" @click="goHome"
>返回首页</cr-button
<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
>
<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>
<RecoGoods />
</div>
</template>
<script>
import { registeredEvents } from '@/service/sa.service';
import RecoGoods from '@/components/RecoGoods.vue';
export default {
components: {},
components: { RecoGoods },
data() {
return {
money: '00.00',
......
<template>
<div class="card">
<div class="info">
<cr-image width="63px" height="63px" class="info__image" src="../assets/images/paying.png" />
<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>
......@@ -19,7 +24,7 @@
</template>
<script>
import { registeredEvents } from '@/service/sa.service';
import { queryPayStatus } from '../api/pay.api';
import { queryPayStatus } from '@/api/pay.api';
export default {
components: {},
data() {
......
......@@ -6,12 +6,13 @@
class="Vl__sku-item"
:class="{
cheap: item.price - item.salePrice > 0,
active: props.info.skuNo === item.skuNo
active: props.info.skuNo === item.skuNo,
nohhird: (index + 1) % 3 !== 0
}"
@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-name">{{ props.threeCol ? item.salePrice : item.skuName }}</div>
<div class="Vl__sku-price">售价{{ props.threeCol ? item.price : item.salePrice }}</div>
<div class="Vl__sku-tag">优惠</div>
</div>
</div>
......@@ -39,6 +40,7 @@ export default {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
position: relative;
z-index: 1;
&.disabled::before {
......@@ -53,13 +55,20 @@ export default {
background: rgba(255, 255, 255, 0.6);
}
&.three-col {
height: 250px;
justify-content: flex-start;
overflow: auto;
.nohhird {
margin-right: 4px;
}
.Vl__sku-item {
width: 103px;
}
}
&-item {
width: 162px;
width: 159px;
height: 72px;
box-sizing: border-box;
border-radius: @border-radius-sm - 2;
border: 1px solid @grey-border;
display: flex;
......
<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-tab v-for="(item, index) in spuData" :key="index" :title="item.name" :name="+item.type" />
</cr-tabs>
<spu-list :list="spuList" :info="spuInfo" />
<div class="Vl__panel">
......@@ -50,7 +50,7 @@ export default {
return this.spuInfo.rechargeAccountType !== 2 && !this.account;
},
spuList() {
return this.spuData[this.currentTab] ? this.spuData[this.currentTab].itemList : [];
return this.spuData[this.currentTab - 1] ? this.spuData[this.currentTab - 1].itemList : [];
},
tips() {
return tipsData[this.spuInfo.spuNo];
......@@ -102,6 +102,7 @@ export default {
totalFee: salePrice,
orderCouponIds: '',
freightCouponIds: '',
virtualRechargeType: this.spuInfo.rechargeAccountType,
skuList: [
{
skuNo,
......
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