Commit 9f8f6327 authored by 郭志伟's avatar 郭志伟

feat: 完成订单和会员充值开发

parent b2b17557
......@@ -1159,9 +1159,9 @@
}
},
"@qg/ui-request": {
"version": "0.0.5",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fui-request/-/ui-request-0.0.5.tgz",
"integrity": "sha512-6RAAMxEzud18N0WQk5CR5vnrQ+FvBdejK+Ig5HAEyBMbFJ6sE1EfuccfYqx7qQ3CizHL58VpIa9pM+zhhdztFw==",
"version": "0.0.7",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fui-request/-/ui-request-0.0.7.tgz",
"integrity": "sha512-C6qxG0HMskn0KDc9TDfmYf2ysPcD8ewEGQCpRv505A89oW28qTA8FpGHEVGWEO20ZJ9I6KAmfMkNaPxIZXHgKg==",
"requires": {
"axios": "^0.19.2"
}
......
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 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 @@
* @Description:
* @Date: 2021-03-31 19:59:17
* @LastEditors: gzw
* @LastEditTime: 2021-03-31 19:59:17
* @LastEditTime: 2021-07-01 17:02:56
*/
/*
* @Description:
......@@ -14,7 +14,8 @@ let protocol = window.location.protocol;
let payHost = protocol + '//mapi-qa.liangkebang.net/pay';
let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址
let talosHost = 'https://talos-vcc2.liangkebang.net'; // 电商分期测试环境服务地址
let talosHost = 'http://yapi.quantgroups.com/mock/351'; // 电商分期测试环境服务地址
// let talosHost = 'https://talos-vcc2.liangkebang.net'; // 电商分期测试环境服务地址
let operatorHost = 'https://operator.liangkebang.com';
export default {
talosHost,
......
......@@ -4,18 +4,28 @@ export default [
redirect: '/error'
},
{
path: '/demo',
alias: ['/demo-page'],
name: 'demo-page',
back: false,
path: '/vipLife',
name: 'vipLife',
meta: {
title: 'DEMO',
has: {
header: true,
footer: true
}
title: '充值中心'
},
component: () => import('../views/vipLife')
},
{
path: '/orderList',
name: 'orderList',
meta: {
title: '我的订单'
},
component: () => import('../views/orderList')
},
{
path: '/orderDetail',
name: 'orderDetail',
meta: {
title: '订单详情'
},
component: () => import('../views/demo')
component: () => import('../views/orderDetail')
},
{
path: '/error',
......
......@@ -7,6 +7,8 @@ import {
Icon,
Cell,
CellGroup,
Radio,
RadioGroup,
Row,
Col,
Dialog,
......@@ -24,7 +26,8 @@ import {
Form,
Sticky,
Tab,
Tabs
Tabs,
Empty
} from '@qg/cherry-ui';
import DialogFn from '@qg/cherry-ui/src/dialog/func';
// import "@qg/cherry-ui/dist/cherry.css";
......@@ -33,6 +36,8 @@ Vue.use(Button);
Vue.use(Image);
Vue.use(Cell);
Vue.use(CellGroup);
Vue.use(Radio);
Vue.use(RadioGroup);
Vue.use(Row);
Vue.use(Col);
Vue.use(Popup);
......@@ -52,6 +57,7 @@ Vue.use(Loading);
Vue.use(List);
Vue.use(Tab);
Vue.use(Tabs);
Vue.use(Empty);
// const _proto = Vue.prototype;
// const proto = Object.create(_proto);
......
......@@ -10,6 +10,6 @@ const http = new HttpRequest(
function(loadingState) {
store.dispatch('change_loading', loadingState);
}
);
).getInstance();
export default http;
......@@ -310,6 +310,11 @@ export function idNoFormat(value) {
return $1 + ' ' + $2 + ' ' + $3;
});
}
export function dateFormat(value) {
return value.replace(/(\d{4})(\d{2})(\d{2})/, '$1.$2.$3');
}
export function phoneFormat(value) {
return value.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
}
......@@ -102,3 +102,11 @@ strong {
border-bottom: 0.026667rem solid #f2f3f5;
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样式
@import "./var.less";
@button-border-width: 0;
@button-border-width: 1px;
@button-default-height: 37px;
@button-default-line-height: 37px;
@button-default-font-size: 16px;
......@@ -25,9 +25,10 @@
@cell-group-title-color: @orange;
@cell-group-title-padding: @padding-xs @padding-lg @padding-xs;
@cell-group-title-font-size: 12px;
@cell-border-color: @grey-border;
@cell-border-color: @gray-2;
@cell-right-icon-color: @gray-3;
@cell-icon-size: 16px;
@cell-clear-color: @gray-4;
@field-label-width: 75px;
@dialog-width: 290px;
......@@ -41,5 +42,4 @@
@picker-toolbar-padding: @padding-unit - 2 @padding-md;
@picker-font-size: 14px;
@loading-color: @white;
@loading-text-color: @white;
\ No newline at end of file
@tabs-nav-background-color: @background-color;
\ No newline at end of file
......@@ -44,9 +44,9 @@
@padding-xl: @padding-unit * 8;
// 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) {
@font: extract(@font-size-list, @i);
.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>
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