6
0
Fork 0

线下订单

develop
Jing Li 2023-11-03 13:19:55 +08:00
parent 4d933dee3b
commit 52a8296c7b
7 changed files with 725 additions and 0 deletions

View File

@ -0,0 +1,338 @@
<template>
<view>
<view class="w-full bg-white p-base mb-20rpx">
<view class="flex justify-between flex-auto">
<view>门店</view>
<view :class="{'text-hex-c0c4cc': store ? false : true }" @tap="showStoreSelect">
{{ store ? store.title : '请选择门店' }}<u-icon name="arrow-right" class="text-hex-c0c4cc"></u-icon>
</view>
</view>
</view>
<view class="bg-white my-20rpx">
<view class="flex justify-between p-20rpx border-b">
<view>订单明细</view>
<view class="text-hex-2979ff" @tap="addOrderItem"><u-icon name="plus"></u-icon></view>
</view>
<view class="flex flex-col px-20rpx pb-20rpx">
<view class="flex flex-col border-b mt-20rpx" v-for="(orderItem, index) in orderItems" :key="index">
<view class="flex flex-col">
<view class="flex items-center justify-between py-10rpx">
<view>商品分类</view>
<view>{{ orderItem.productCategory.name }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>商品总额</view>
<view>{{ orderItem.productsTotalAmount }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>优惠折扣</view>
<view>{{ orderItem.discount ? (orderItem.discount+'折') : '无' }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>应付金额</view>
<view>{{ orderItem.paymentAmount }}</view>
</view>
</view>
<view class="flex justify-end py-20rpx text-sm">
<view class="text-hex-2979ff" @tap="editOrderItem(index)"><u-icon name="edit-pen"></u-icon></view>
<view class="ml-30rpx text-hex-dd524d" @tap="deleteOrderItem(index)"><u-icon name="trash"></u-icon></view>
</view>
</view>
</view>
</view>
<view class="h-130rpx"></view>
<view class="flex items-center justify-between bg-white shadow-up fixed right-0 left-0 bottom-0 p-20rpx">
<view class="text-lg">合计:<text class="text-xl text-txSvip">{{ paymentAmount }}</text></view>
<view @tap="createOrderPreview" class="btn btn-primary rounded-full text-lg px-40rpx py-10rpx">生成订单</view>
</view>
<!-- 门店选择器 -->
<u-select
v-model="storeSelect.visible"
:list="stores"
:default-value="storeSelect.selector"
label-name="title"
value-name="id"
@confirm="onStoreSelectConfirm"
></u-select>
<!-- 新增/修改 订单明细 -->
<u-modal
title="订单明细"
v-model="orderItemModalVisible"
ref="orderItemModal"
:show-cancel-button="true"
:async-close="true"
@confirm="onOrderItemModalConfirm"
@cancel="clearOrderItemModalForm"
>
<view class="p-base text-md">
<view class="flex items-center p-26rpx border-b">
<view class="w-140rpx">商品分类</view>
<view class="flex-auto">
<view :class="{'text-hex-c0c4cc': orderItemForm.productCategory ? false : true }" @tap="showProductCategorySelect">
{{ orderItemForm.productCategory ? orderItemForm.productCategory.name : '商品分类' }}
</view>
</view>
</view>
<view class="flex items-center p-26rpx border-b">
<view class="w-140rpx">商品总额</view>
<view class="flex-auto">
<u-input v-model="orderItemForm.productsTotalAmount" placeholder="商品总额" height="42" trim />
</view>
</view>
<view class="flex items-center p-26rpx border-b">
<view class="w-140rpx">折扣</view>
<view class="flex-auto">
<u-input v-model="orderItemForm.discount" placeholder="折扣" height="42" trim />
</view>
</view>
</view>
</u-modal>
<!-- 商品分类选择器 -->
<u-select
v-model="productCategorySelect.visible"
:default-value="productCategorySelect.selector"
:list="productCategories"
label-name="name"
value-name="id"
@confirm="onProductCategoryConfirm"
></u-select>
<!-- 删除订单明细弹窗 -->
<cu-modal
v-model="deleteOrderItemModalVisible"
@confirm="onOrderItemDeleteConfirm"
confirm-color="#378264"
show-cancel-button
content="是否删除选中订单明细?"
confirmText="确认"
cancelText="取消"
>
</cu-modal>
</view>
</template>
<script>
export default {
data() {
return {
//
stores: [],
//
productCategories: [],
//
store: null,
//
storeSelect: {
visible: false,
selector: [],
},
//
orderItems: [],
//
orderItemModalVisible: false,
orderItemForm: {
productCategory: null,
productsTotalAmount: '',
discount: '',
},
orderItemIndex: -1,
productCategorySelect: {
visible: false,
selector: [],
},
deleteOrderItemModalVisible: false,
}
},
onShow() {
this.fetchStores()
this.fetchProductCategories()
},
computed: {
paymentAmount() {
return this.orderItems.reduce((paymentAmount, orderItem) => paymentAmount + Number(orderItem.paymentAmount), 0).toFixed(2)
},
},
methods: {
fetchStores() {
this.$api
.get(`/v1/store`, {
params: {
per_page: 100,
},
})
.then((res) => {
this.stores = res.data
})
.catch((err) => {
});
},
fetchProductCategories() {
this.$api
.get(`/v1/offline-product-categories`)
.then((res) => {
this.productCategories = res
})
.catch((err) => {
});
},
showStoreSelect() {
this.storeSelect.visible = true
},
onStoreSelectConfirm(v) {
if (this.stores.length === 0) {
return
}
const i = this.stores.findIndex((e) => e.id == v[0].value)
this.store = this.stores[i]
this.storeSelect.selector = [i]
},
calculatePaymentAmount(amount, discount) {
if (discount) {
return (Number(amount) * Number(discount) / 10).toFixed(2)
}
return Number(amount).toFixed(2)
},
addOrderItem() {
this.orderItemModalVisible = true
},
editOrderItem(index) {
const orderItem = this.orderItems[index]
this.orderItemForm = {
productCategory: orderItem.productCategory,
productsTotalAmount: orderItem.productsTotalAmount,
discount: orderItem.discount,
}
this.productCategorySelect.selector = [this.productCategories.findIndex((e) => e.id == orderItem.productCategory.id)]
this.orderItemIndex = index
this.orderItemModalVisible = true
},
deleteOrderItem(index) {
this.orderItemIndex = index
this.deleteOrderItemModalVisible = true
},
onOrderItemDeleteConfirm() {
this.orderItems.splice(this.orderItemIndex, 1)
},
onOrderItemModalConfirm() {
this.$refs.orderItemModal.clearLoading()
if (! this.orderItemForm.productCategory) {
return this.$u.toast('商品分类不能为空')
}
let index = this.orderItems.findIndex((e) => e.productCategory.id == this.orderItemForm.productCategory.id)
if (index !== -1 && index != this.orderItemIndex) {
return this.$u.toast('商品分类不能重复')
}
if (this.orderItemForm.productsTotalAmount === '') {
return this.$u.toast('商品总额不能为空')
} else if (isNaN(this.orderItemForm.productsTotalAmount)) {
return this.$u.toast('商品总额格式不正确')
} else if (Number(this.orderItemForm.productsTotalAmount) < 0) {
return this.$u.toast('商品总额不能小于0')
} else if (! /^([1-9]\d*|0)(\.\d{1,2})?$/.test(this.orderItemForm.productsTotalAmount)) {
return this.$u.toast('商品总额最多2位小数')
}
if (this.orderItemForm.discount !== '') {
if (isNaN(this.orderItemForm.discount)) {
return this.$u.toast('折扣格式不正确')
} else if (Number(this.orderItemForm.discount) <= 0) {
return this.$u.toast('折扣必须大于0')
} else if (Number(this.orderItemForm.discount) >= 10) {
return this.$u.toast('折扣必须小于10')
} else if (! /^([1-9]\d*|0)(\.\d{1,2})?$/.test(this.orderItemForm.discount)) {
return this.$u.toast('折扣最多2位小数')
}
}
let orderItem = {
productCategory: this.orderItemForm.productCategory,
productsTotalAmount: this.orderItemForm.productsTotalAmount,
discount: this.orderItemForm.discount,
paymentAmount: this.calculatePaymentAmount(this.orderItemForm.productsTotalAmount, this.orderItemForm.discount),
}
if (this.orderItemIndex === -1) {
this.orderItems.push(orderItem)
} else {
this.orderItems[this.orderItemIndex] = orderItem
}
this.orderItemModalVisible = false
this.clearOrderItemModalForm()
},
clearOrderItemModalForm() {
this.orderItemForm = {
productCategory: null,
productsTotalAmount: '',
discount: '',
}
this.productCategorySelect = {
visible: false,
selector: [],
}
this.orderItemIndex = -1
},
showProductCategorySelect() {
this.productCategorySelect.visible = true
},
onProductCategoryConfirm(v) {
if (this.productCategories.length === 0) {
return
}
const i = this.productCategories.findIndex((e) => e.id == v[0].value)
this.orderItemForm.productCategory = this.productCategories[i]
this.productCategorySelect.selector = [i]
},
createOrderPreview() {
if (! this.store) {
return this.$u.toast('请选择门店')
}
if (this.orderItems.length === 0) {
return this.$u.toast('订单明细不能为空')
}
const items = this.orderItems.map((e) => ({
product_category_id: e.productCategory.id,
products_total_amount: e.productsTotalAmount,
discount: e.discount,
}))
this.$api
.post(`/v1/offline-order-previews`, {
store_id: this.store.id,
items: items,
}, {
custom: {
loading: true,
},
})
.then((res) => {
this.$u.routeAuth({
url: '/pageB/offline_order/store/order_preview_qrcode',
type: 'redirectTo',
params: {
id: res.id,
},
})
})
.catch((err) => {
})
},
}
};
</script>
<style lang="scss" scoped>
.border-b {
border-bottom: 1rpx solid #e4e7ed;
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<view class="flex items-center justify-center flex-col pt-80rpx">
<image class="w-400rpx h-400rpx" :src="qrcode"></image>
<view class="mt-20rpx">请用户通过微信扫码进入小程序生成订单</view>
<view class="grid grid-cols-2 gap-x-80rpx text-36rpx mt-120rpx">
<view @click="toBack"></view>
<view @click="toUserCenter"></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
id: '',
qrcode: '',
};
},
onLoad(e) {
this.id = e.id;
this.fetchOrderPreview()
},
methods: {
fetchOrderPreview() {
this.$api.get(`/v1/offline-order-previews/${this.id}`)
.then(res => {
this.qrcode = res.qrcode
})
},
toBack() {
uni.reLaunch({
url: '/pageB/offline_order/store/order_preview'
})
},
toUserCenter() {
uni.switchTab({
url: '/pages/me/me',
});
},
},
};
</script>
<style>
page {
background: #ffffff;
}
</style>

View File

@ -0,0 +1,252 @@
<template>
<view>
<view class="bg-white mt-20rpx">
<view class="flex flex-col">
<view class="flex flex-col border-b p-20rpx" v-for="(item, index) in order.items" :key="index">
<view class="flex flex-col">
<view class="flex items-center justify-between py-10rpx">
<view>商品分类</view>
<view>{{ item.product_category.name }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>商品总额</view>
<view>{{ item.products_total_amount }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>折扣金额</view>
<view>{{ item.discount_reduction_amount > 0 ? ('-¥'+item.discount_reduction_amount) : '无' }}</view>
</view>
<view class="flex items-center justify-between py-10rpx">
<view>应付金额</view>
<view>{{ item.payment_amount }}</view>
</view>
</view>
</view>
</view>
</view>
<view class="bg-white mt-base p-20rpx text-md">
<view class="flex items-center justify-between pb-20rpx text-txBase">
<view>订单总额</view>
<view>{{ order.products_total_amount }}</view>
</view>
<view class="flex items-center justify-between pb-20rpx text-txBase">
<view>折扣金额</view>
<view :class="{'text-bgSubtitle': order.discount_reduction_amount > 0}">{{ order.discount_reduction_amount > 0 ? ('-¥'+order.discount_reduction_amount) : '无' }}</view>
</view>
<view v-if="order.payment_amount > 0" class="flex items-center justify-between pb-20rpx text-lg">
<view>会员积分抵扣<block v-if="hasRemainingPoints">{{ order.remaining_points }}</block></view>
<view v-if="hasRemainingPoints" class="flex items-center" @tap="openPointDiscountDrawer">
<view v-if="pointDiscount.amount > 0" class="text-bgSubtitle">-{{ pointDiscount.amount }}</view>
<view v-else></view>
<u-icon name="arrow-right" size="20" class="ml-10rpx text-gray-400"></u-icon>
</view>
<view v-else class="flex items-center text-gray-400">
<view>无可用</view>
<u-icon name="arrow-right" size="20" class="ml-10rpx text-gray-400"></u-icon>
</view>
</view>
<!-- -->
<view class="border-t" style="margin: 0 -20rpx;">
<view class="flex items-center justify-end px-20rpx pt-20rpx text-md">
<view class="text-txBase">实付金额</view>
<view class="text-bgSubtitle">{{ paymentAmount }}</view>
</view>
</view>
</view>
<cu-popup v-model="showPointDiscountDrawer" mode="bottom" border-radius="20" closeable close-icon-size="24">
<view>
<view class="flex p-50rpx text-x-lg items-end">积分<view class="text-md text-txBase">剩余: {{ order.remaining_points }}</view></view>
<scroll-view scroll-y="true" class="min-h-600rpx max-h-900rpx">
<view class="text-md flex justify-between px-50rpx py-base" @tap="selectPointDiscountOption(0)">
<view>暂不使用积分</view>
<u-icon name="checkmark-circle" :color="pointDiscountSelectedOption.value == 0 ? '#f43530' : '#808080'" size="40"></u-icon>
</view>
<view class="text-md flex justify-between px-50rpx py-base" @tap="selectPointDiscountOption(1)">
<view class="flex">
抵扣<view class="mx-8rpx text-bgSubtitle">{{ order.points_discount_amount }}</view>使用<view class="mx-8rpx">{{ order.available_points }}</view>积分
</view>
<u-icon name="checkmark-circle" :color="pointDiscountSelectedOption.value == 1 ? '#f43530' : '#808080'" size="40"></u-icon>
</view>
</scroll-view>
<view class="p-base">
<view class="btn bg-hex-FB4A34 h-84rpx leading-84rpx text-white" @tap="submitPointDiscount">
<text>确定</text>
</view>
</view>
</view>
</cu-popup>
<view class="h-130rpx"></view>
<view class="flex items-center justify-between bg-white shadow-up fixed right-0 left-0 bottom-0 p-20rpx">
<view class="text-lg">合计:<text class="text-xl text-txSvip">{{ paymentAmount }}</text></view>
<view @tap="createOrder" class="btn btn-primary rounded-full text-lg px-40rpx py-10rpx">立即支付</view>
</view>
</view>
</template>
<script>
import { wxpay } from '@/utils/pay.js';
export default {
data() {
return {
id: '',
order: {
items: [],
products_total_amount: '0.00',
discount_reduction_amount: '0.00',
payment_amount: '0.00',
available_points: '0.00',
points_discount_amount: '0.00',
remaining_points: '0.00',
},
//--------------------------
//
//--------------------------
pointDiscount: {
value: null,
points: 0,
amount: 0,
},
showPointDiscountDrawer: false,
pointDiscountSelectedOption: {
value: null,
points: 0,
amount: 0,
},
//
orderCreateBtnDisabled: false,
}
},
onLoad({ id }) {
this.id = id;
},
onShow() {
this.pointDiscount = {
value: null,
points: 0,
amount: 0,
}
this.fetchOrderPreview()
},
computed: {
hasRemainingPoints() {
return this.order.remaining_points > 0
},
paymentAmount() {
if (this.pointDiscount.amount > 0) {
return (Number(this.order.payment_amount) - Number(this.pointDiscount.amount)).toFixed(2)
}
return this.order.payment_amount
},
},
methods: {
fetchOrderPreview() {
this.$api
.post(`/v1/offline-orders/check`, {
order_preview_id: this.id,
})
.then((res) => {
this.order = res
})
.catch((err) => {
});
},
openPointDiscountDrawer() {
this.pointDiscountSelectedOption = this.pointDiscount
this.showPointDiscountDrawer = true
},
selectPointDiscountOption(value) {
switch (value) {
case 0:
this.pointDiscountSelectedOption = {
value: 0,
points: 0,
amount: 0,
}
break;
case 1:
this.pointDiscountSelectedOption = {
value: 1,
points: this.order.available_points,
amount: this.order.points_discount_amount,
}
break;
}
},
submitPointDiscount() {
this.pointDiscount = this.pointDiscountSelectedOption
this.showPointDiscountDrawer = false
},
async createOrder() {
if (this.orderCreateBtnDisabled) {
return
}
let order;
this.orderCreateBtnDisabled = true
try {
uni.showLoading()
order = await this.$api
.post(`/v1/offline-orders`, {
order_preview_id: this.id,
points: this.pointDiscount.points,
}, {
custom: {
toast: false,
},
});
if (order.status == 0) {
let payinfo = await this.$api.post(`/v1/offline-orders/${order.id}/pay`, {}, {
custom: {
toast: false,
},
});
wxpay(payinfo.data).then((result) => {
this.getPaymentResults(result)
})
} else if (order.status == 1) {
this.getPaymentResults('success')
}
uni.hideLoading()
} catch (err) {
uni.hideLoading()
this.$u.toast(err.message ?? '支付失败,请稍后再试')
if (order) {
setTimeout(() => {
this.getPaymentResults('error')
}, 1000);
}
} finally {
this.orderCreateBtnDisabled = false
}
},
getPaymentResults(type) {
this.$u.route({
url: '/pageB/offline_order/user/payment_results',
type: 'redirectTo',
params: {
type: type,
},
});
},
}
};
</script>
<style lang="scss" scoped>
.border-t {
border-top: 1rpx solid #e4e7ed;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<view>
<view class="w-full py-40rpx bg-white rounded-b-xs flex items-center justify-center flex-col">
<!-- 支付成功 -->
<block v-if="type == 'success'">
<image class="w-77rpx h-77rpx" src="/static/images/user/pay_sel.png" mode=""></image>
<view class="text-xl text-primary font-extrabold mt-10rpx">支付成功</view>
<view class="pb-base flex items-center mt-40rpx w-full justify-center px-150rpx">
<view class="text-lg text-primary" @tap="switchTab"></view>
</view>
</block>
<!-- 支付失败 -->
<block v-else>
<image class="w-77rpx h-77rpx" src="/static/images/user/err.png" mode=""></image>
<view class="text-xl text-hex-FF5B5F font-extrabold mt-10rpx">支付失败</view>
<view class="pb-base flex items-center mt-40rpx w-full justify-center px-150rpx">
<view class="text-lg text-primary" @tap="switchTab"></view>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
data() {
return {
type: 'success',
}
},
onLoad({
type,
}) {
this.type = type
},
methods: {
switchTab() {
uni.switchTab({
url: '/pages/index/index',
});
},
},
};
</script>
<style lang="scss"></style>

View File

@ -607,6 +607,38 @@
"navigationBarTitleText": "活动",
"enablePullDownRefresh": false
}
},
{
"path": "offline_order/store/order_preview",
"style": {
"navigationBarTitleText": "线下订单",
"enablePullDownRefresh": false,
"navigationStyle": "default"
}
},
{
"path": "offline_order/store/order_preview_qrcode",
"style": {
"navigationBarTitleText": "二维码",
"enablePullDownRefresh": false,
"navigationStyle": "default"
}
},
{
"path": "offline_order/user/order_preview",
"style": {
"navigationBarTitleText": "确认订单",
"enablePullDownRefresh": false,
"navigationStyle": "default"
}
},
{
"path": "offline_order/user/payment_results",
"style": {
"navigationBarTitleText": "支付结果",
"enablePullDownRefresh": false,
"navigationStyle": "default"
}
}
]
}

View File

@ -186,6 +186,9 @@
<view class="w-710rpx bg-hex-f08003 text-white px-base m-auto rounded-xs mt-base text-center py-26rpx text-46rpx" v-if="is_company" @tap="$u.routeAuth('/pageB/select_store/index')">
帮用户下单
</view>
<view class="w-710rpx bg-hex-f08003 text-white px-base m-auto rounded-xs mt-base text-center py-26rpx text-46rpx" v-if="is_company" @tap="$u.routeAuth('/pageB/offline_order/store/order_preview')">
线下订单
</view>
<!-- -->
<view class="w-710rpx bg-white px-base m-auto rounded-xs mt-base">
<!-- 销售端 -->

View File

@ -38,6 +38,12 @@
url: '/pageB/select_product/com_order?id=' + scene.o
})
}
// 线
else if (scene.offline_order) {
uni.reLaunch({
url: '/pageB/offline_order/user/order_preview?id=' + scene.offline_order
})
}
//
else if (scene.d) {
const desk = scene.d