优化小票打印

This commit is contained in:
gyq 2025-03-13 18:54:10 +08:00
parent cfe9f7bb36
commit 745b8675ea
8 changed files with 207 additions and 167 deletions

View File

@ -1,4 +1,5 @@
import _ from "lodash";
import dayjs from "dayjs";
import { defineStore } from "pinia";
import { productPage, categoryList } from "@/api/product_new.js";
import { historyOrder } from "@/api/order.js";
@ -6,6 +7,7 @@ import { useUser } from "@/store/user.js";
import { useSocket } from "@/store/socket.js";
import useStorage from "@/utils/useStorage.js";
import { formatDecimal } from "@/utils/index.js";
import { ElMessage } from "element-plus";
// 商品store + 购物车store
export const useGoods = defineStore("goods", {
@ -45,8 +47,58 @@ export const useGoods = defineStore("goods", {
orderListInfo: "", // 历史订单信息
cartType: "cart", // cart order
cartOrderItem: "",
pendingList: useStorage.get("pendingList") || [],
}),
actions: {
// 恢复挂单
async recoverPending(item) {
let socket = useSocket();
if (this.cartList.length || this.orderList.length) {
this.pendingCart();
}
if (item.orderId) {
await this.historyOrderAjax(item.tableCode);
}
let pendingList = useStorage.get("pendingList");
let index = pendingList.findIndex(
(val) => val.tableCode == item.tableCode
);
pendingList.splice(index, 1);
useStorage.set("pendingList", pendingList);
this.pendingList = useStorage.get("pendingList");
useStorage.set("tableCode", item.tableCode);
socket.cartInit();
},
// 开始挂单
pendingCart() {
let cart = this.cartList;
let order = this.orderList;
if (cart.length || order.length) {
let pendingList = useStorage.get("pendingList") || [];
pendingList.push({
tableCode: cart[0].table_code || this.orderListInfo.tableCode,
productName: [
...cart.map((item) => item.product_name),
...order
.map((item) => item.goods)
.flat()
.map((item) => item.product_name),
].join("、"),
orderId: order.length && this.orderListInfo.id,
totalAmount: this.cartInfo.totalAmount,
pendingAt: dayjs().format("YYYY-MM-DD HH:mm:ss"),
});
useStorage.set("pendingList", pendingList);
this.pendingList = useStorage.get("pendingList");
useStorage.del("tableCode");
}
},
// 选中订单中的商品
selectOrderItem(index = null, i) {
this.orderList.map((item) => {
@ -220,7 +272,12 @@ export const useGoods = defineStore("goods", {
async getCartList(arr) {
const store = useUser();
let numCount = 0;
let packCount = 0;
arr.map((val, index) => {
numCount += +val.number;
packCount += +val.pack_number;
val = this.completeGoodsInfo(val);
val.active = false;
if (!this.isCartInit && index == 0) {
@ -228,6 +285,12 @@ export const useGoods = defineStore("goods", {
}
});
if (packCount > 0 && packCount == numCount) {
this.allSelected = 1;
} else {
this.allSelected = 0;
}
this.cartList = arr;
this.isCartInit = true;
@ -267,7 +330,7 @@ export const useGoods = defineStore("goods", {
product_id: params.productId || "",
sku_id: params.id || "",
number: params.number || 1,
pack_number: params.is_pack || 0,
pack_number: this.allSelected ? params.number : 0,
is_gift: params.is_gift || 0,
is_temporary: params.is_temporary || 0,
discount_sale_amount: params.discount_sale_amount || 0,

View File

@ -119,6 +119,10 @@ export const useSocket = defineStore("socket", {
// 清空购物车
goodsStore.successClearCart();
break;
case "batch":
// 打包
this.cartInit();
break;
default:
break;
}
@ -127,37 +131,12 @@ export const useSocket = defineStore("socket", {
}
} else if (data.data_type == "order") {
// 收到订单消息,打印订单小票
// this.orderList.push(data.data);
// this.startPrintInterval();
if (!this.orderList.some((el) => el == data.data)) {
// 防止重复打印
this.orderList.push(data.data);
this.startPrintInterval();
}
}
// if (data.data_type == "cart" && data.operate_type == "init") {
// // 购物车消息
// goodsStore.getCartList(data.data);
// } else if (data.data_type == "order") {
// // 接收订单消息,打印小票
// if (this.log) console.log("接收消息", data);
// this.ws.send(
// JSON.stringify({
// type: "send",
// orderNo: data.orderInfo.orderNo,
// })
// );
// // 接收订单消息,打印小票
// // printBill(data)
// // 打印标签小票
// if (!this.orderList.some((el) => el == data.orderInfo.orderNo)) {
// // console.log("打印", data);
// printStore.labelPrint(data);
// printStore.pushReceiptData(data);
// this.orderList.push(data.orderInfo.orderNo);
// if (this.orderList.length > 30) {
// this.orderList.splice(0, 1);
// }
// }
// } else if (data.data_type == "heartbeat") {
// if (this.log) console.log("接收心跳");
// }
});
this.ws.addEventListener("error", () => {
@ -190,14 +169,15 @@ export const useSocket = defineStore("socket", {
const printStore = usePrint();
if (this.orderListTimer !== null) return;
this.orderListTimer = setInterval(async () => {
console.log("隔2秒执行===", this.orderList);
try {
if (!this.orderList.length) {
clearInterval(this.orderListTimer);
this.orderListTimer = null;
} else {
const orderInfo = await getOrderByIdAjax(this.orderList[0]);
printStore.pushReceiptData(commOrderPrintData(orderInfo));
if (orderInfo.status == "done" && orderInfo.platformType != "PC") {
printStore.pushReceiptData(commOrderPrintData(orderInfo));
}
this.orderList.splice(0, 1);
}
} catch (error) {

View File

@ -68,12 +68,12 @@
</el-icon>
<el-text class="t">删除</el-text>
</div>
<!-- <div class="item" @click="props.item.id && emit('pending', props.item)">
<div class="item" @click="pendingOrderHandle">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div> -->
</div>
<div class="item" @click="tableMergingRef.show()">
<el-icon class="icon">
<EditPen />
@ -143,12 +143,12 @@
</el-icon>
<el-text class="t">退菜</el-text>
</div>
<!-- <div class="item" @click="props.item.id && emit('pending', props.item)">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div> -->
<div class="item" @click="pendingOrderHandle">
<el-icon class="icon">
<Sell />
</el-icon>
<el-text class="t">挂单</el-text>
</div>
<div class="item" @click="tableMergingRef.show()">
<el-icon class="icon">
<EditPen />
@ -254,14 +254,13 @@ import { ElMessage } from 'element-plus'
import takeFoodCode from '@/components/takeFoodCode.vue'
import TableMerging from './tableMerging.vue'
import skuModal from '@/components/skuModal.vue'
import { useShop } from '@/store/shop.js'
import { useGoods } from '@/store/goods.js'
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
import { updatePrice, orderPrint } from '@/api/product.js'
import { refundOrder } from '@/api/order.js'
import { useSocket } from '@/store/socket.js'
const shopStore = useShop()
const goodsStore = useGoods()
const socket = useSocket()
const tableMergingRef = ref(null)
@ -282,6 +281,18 @@ const returnForm = ref({
num: 1
})
//
function pendingOrderHandle() {
let cart = goodsStore.cartList;
let order = goodsStore.orderList;
if (cart.length || order.length) {
goodsStore.pendingCart()
goodsStore.successClearCart();
socket.cartInit();
ElMessage.success('挂单成功')
}
}
// 退
async function returnOrderItemHandle() {
@ -348,18 +359,20 @@ function packHandle() {
//
function giftPackHandle(key) {
if (!goodsStore.cartList[goodsStore.cartActiveIndex].id) return
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if (item && item.id) {
if (key == 'is_gift' && goodsStore.cartList[goodsStore.cartActiveIndex] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex].discount_sale_amount = 0
}
if (key == 'is_gift' && goodsStore.cartList[goodsStore.cartActiveIndex] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex].discount_sale_amount = 0
if (goodsStore.cartList[goodsStore.cartActiveIndex][key] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 1
} else {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 0
}
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex] }, 'edit')
}
if (goodsStore.cartList[goodsStore.cartActiveIndex][key] == 0) {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 1
} else {
goodsStore.cartList[goodsStore.cartActiveIndex][key] = 0
}
goodsStore.operateCart({ ...goodsStore.cartList[goodsStore.cartActiveIndex] }, 'edit')
}
//
@ -467,9 +480,10 @@ const noteList = ref([
//
function showDiscountModalHandle() {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if ((item && !item.id) || item.is_temporary || item.is_gift) return
//
showDiscountModal.value = true
if ((item && item.id) && (!item.is_temporary || !item.is_gift)) {
//
showDiscountModal.value = true
}
}
//
@ -519,24 +533,10 @@ function discountFormSubmit() {
}
/**单品打折 end */
/**免厨打印 start */
async function kitchenPrint() {
try {
const res = await orderPrint({
isPrint: props.item.isPrint ? 0 : 1,
shopId: props.item.shopId,
cartId: props.item.id
})
emit('confirm', { isTemporary: true })
} catch (error) {
console.log(error);
}
}
/**免厨打印 end */
//
function deleteHandle() {
if (goodsStore.cartList[goodsStore.cartActiveIndex].id) {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]
if (item && item.id) {
goodsStore.deleteCartItem()
}
}
@ -605,7 +605,7 @@ function packFormSubmit() {
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
gap: 8px;
.item {
width: 70px;

View File

@ -1,7 +1,7 @@
<template>
<el-dialog title="恢复挂起的订单" width="800" v-model="dialogVisible">
<div class="pending_carts">
<div class="item" v-for="(item, index) in cartList" :key="index" @click="select(item)">
<div class="item" v-for="(item, index) in goodsStore.pendingList" :key="index" @click="select(item)">
<div class="time">
<el-icon class="icon">
<Clock />
@ -9,12 +9,12 @@
<span>{{ dayjs(item.pendingAt).format('HH:mm') }}</span>
</div>
<div class="info">
<span class="name">{{ item.productName }}</span>
<span class="p">{{ item.totalAmount }}</span>
<span class="name">{{ item.tableCode }}{{ item.productName }}</span>
<span class="p">{{ formatDecimal(item.totalAmount) }}</span>
</div>
</div>
<div class="empty">
<el-empty description="暂无挂单" v-if="!cartList.length" />
<el-empty description="暂无挂单" v-if="!goodsStore.pendingList.length" />
</div>
</div>
</el-dialog>
@ -23,40 +23,20 @@
<script setup>
import { ref } from 'vue'
import { dayjs } from 'element-plus'
import { getCartList } from "@/api/product";
import { useUser } from "@/store/user"
import { useGoods } from '@/store/goods.js'
import { formatDecimal } from '@/utils/index.js'
const emit = defineEmits(['select'])
const store = useUser()
const goodsStore = useGoods()
const dialogVisible = ref(false)
const loading = ref(false)
const cartList = ref([])
//
function select(item) {
emit('select', item)
async function select(item) {
await goodsStore.recoverPending(item)
dialogVisible.value = false
}
function show() {
dialogVisible.value = true
getCartListAjax()
}
//
async function getCartListAjax() {
try {
loading.value = true
const res = await getCartList({
shopId: store.userInfo.shopId
})
cartList.value = res
loading.value = false
} catch (error) {
console.log(error)
}
}
defineExpose({

View File

@ -1,6 +1,6 @@
<!-- 合并/转桌 -->
<template>
<el-dialog title="转桌/并桌" width="700px" v-model="visible" @closed="onClose" top="10vh">
<el-dialog title="转桌/并桌" width="700px" v-model="visible" @closed="onClose" top="3vh">
<div class="scroll_y">
<el-form :model="form" ref="formRef" :rules="rules" label-position="top">
<el-form-item label="转入台桌" prop="targetTableId">
@ -15,15 +15,24 @@
<el-radio :value="true" border>并桌并台会将全部购物车商品转入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="转入商品" prop="cartIds" v-if="!form.isFull">
<div v-for="item in props.data" style="width: 100%;">
<div>{{ `${item.placeNum}次下单` }}</div>
<el-table ref="tableRefs" :data="item.info" border>
<el-form-item label="购物车商品" v-if="!form.isFull">
<el-table ref="cartTableRefs" :data="goodsStore.cartList" border stripe>
<el-table-column type="selection" align="center" width="50px"></el-table-column>
<el-table-column label="名称" prop="product_name"></el-table-column>
<el-table-column label="数量" prop="number"></el-table-column>
<el-table-column label="规格" prop="sku_name"></el-table-column>
<el-table-column label="价格" prop="lowPrice"></el-table-column>
</el-table>
</el-form-item>
<el-form-item label="已下单商品" prop="cartIds" v-if="!form.isFull">
<div v-for="item in goodsStore.orderList" style="width: 100%;">
<div>{{ `${item.orderNum}次下单` }}</div>
<el-table ref="orderTableRefs" :data="item.goods" border stripe>
<el-table-column type="selection" align="center" width="50px"></el-table-column>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="名称" prop="product_name"></el-table-column>
<el-table-column label="数量" prop="number"></el-table-column>
<el-table-column label="规格" prop="skuName"></el-table-column>
<el-table-column label="价格" prop="salePrice"></el-table-column>
<el-table-column label="规格" prop="sku_name"></el-table-column>
<el-table-column label="价格" prop="lowPrice"></el-table-column>
</el-table>
</div>
</el-form-item>
@ -41,29 +50,20 @@
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { shopTable } from '@/api/account.js'
import { queryShopTable } from '@/api/table.js'
import { onMounted, ref } from 'vue'
import { orderSwitcht } from '@/api/product.js'
import { useGoods } from '@/store/goods.js'
import { useUser } from "@/store/user.js"
import { useGlobal } from '@/store/global.js'
import { ElMessage } from 'element-plus'
const store = useUser()
const global = useGlobal()
const visible = ref(false)
const props = reactive({
data: []
})
import { shopTable } from "@/api/account.js";
const emits = defineEmits(['success'])
const tableRefs = ref([])
const list = ref([])
const store = useUser()
const goodsStore = useGoods()
const visible = ref(false)
const cartTableRefs = ref(null)
const orderTableRefs = ref(null)
const loading = ref(false)
const formRef = ref(null)
const resetForm = ref({})
@ -111,10 +111,8 @@ const tableList = ref([])
async function queryShopTableAjax() {
try {
const res = await shopTable({
areaId: store.userInfo.shopId,
tableCode: '',
status: 'using',
name: ''
isBind: true
})
tableList.value = res.records
} catch (error) {
@ -144,7 +142,7 @@ function confirmHandle() {
loading.value = false
//
global.setOrderTable(tableList.value.find(item => item.qrcode == form.value.targetTableId))
//
visible.value = false
@ -162,8 +160,7 @@ function onClose() {
formRef.value.resetFields()
}
function show(data) {
props.data = data
function show() {
visible.value = true
queryShopTableAjax()
}
@ -181,7 +178,7 @@ onMounted(() => {
$btmH: 50px;
.scroll_y {
height: 50vh;
height: 68vh;
overflow-y: auto;
padding-bottom: $btmH;
}

View File

@ -6,7 +6,7 @@
<el-icon class="icon">
<TakeawayBox />
</el-icon>
<el-text class="t">({{ pendingCartNum }})</el-text>
<el-text class="t">({{ goodsStore.pendingList.length }})</el-text>
</div>
<div class="number" @click="SelectVipUserRef.show()">
<div class="left">
@ -106,7 +106,8 @@
<div class="top">
<div class="num-wrap">
<div class="num_wrap_top">
<div class="left" @click="allSelectedHandle" v-if="store.shopInfo.eatModel.includes('take-out')">
<div class="left" @click="allSelectedHandle"
v-if="store.shopInfo && store.shopInfo.eatModel.includes('take-out')">
<div class="selected">
<div class="selected_round" v-if="!goodsStore.allSelected"></div>
<el-icon class="icon" v-else>
@ -118,7 +119,7 @@
<div class="left" v-else></div>
<div class="right">
<el-text>
{{ goodsStore.cartInfo.total }}
{{ formatDecimal(goodsStore.cartInfo.total, 2, true) }}
</el-text>{{ formatDecimal(goodsStore.cartInfo.totalAmount || 0) }}
</div>
</div>
@ -167,7 +168,7 @@
<!-- 快捷收银 -->
<fastCashier ref="fastCashierRef" type="0" />
<!-- 挂起订单 -->
<pendingCartModal ref="pendingCartModalRef" @select="pendingCartHandle" />
<pendingCartModal ref="pendingCartModalRef" />
<!-- 检查版本升级 -->
<updateDialog />
<!-- 选择会员 -->
@ -195,13 +196,12 @@ import { createOrder } from '@/api/order.js'
import goods from "@/views/home/components/goods.vue";
import member from "@/views/member/index.vue";
import { useUser } from '@/store/user.js'
import { useSocket } from '@/store/socket.js'
const SelectVipUserRef = ref(null)
const goodsStore = useGoods()
const global = useGlobal()
const socket = useSocket()
const membershow = ref(false);
const store = useUser();
const remarkRef = ref(null);
@ -320,18 +320,15 @@ const allSelectedHandle = async () => {
} else {
goodsStore.allSelected = 1
}
if (goodsStore.cartList.length) {
goodsStore.operateCart({
table_code: goodsStore.cartList[0].table_code,
is_pack: goodsStore.allSelected
}, 'batch')
}
};
//
function selectCartItemHandle(index) {
goodsStore.selectCartItemHandle(index)
}
// /
function clearMember() {
global.setOrderMember({})
global.setOrderTable({})
}
// /
function showTableMerging() {

View File

@ -1,5 +1,5 @@
<template>
<el-drawer v-model="isShow" direction="rtl" size="60%">
<el-drawer v-model="isShow" direction="rtl" size="70%">
<template #header>
<h4>订单号{{ item.orderNo }}</h4>
</template>
@ -24,9 +24,10 @@
<el-radio-button label="自定义" :value="3" />
</el-radio-group>
<div class="amount">
<el-input v-model="customAmount" v-if="refundType == 3" style="width: 250px;"
<el-input v-model="customAmount" v-if="refundType == 3" style="width: 370px;height: 42px;"
placeholder="请输入退款金额" @input="inputChange">
<template #prepend></template>
<template #append>最多可退{{ formatDecimal(item.payAmount - item.refundAmount, 2) }}</template>
</el-input>
<template v-else>
退款金额{{ formatDecimal(refundType == 1 ? item.originAmount - item.refundAmount : amount) }}
@ -97,10 +98,10 @@
</el-table>
</template>
<div class="ipt">
<el-input type="textarea" v-model="remark" placeholder="请输入退单原因" />
<el-input type="textarea" rows="4" v-model="remark" placeholder="请输入退单原因" />
</div>
<div class="remark_tag">
<div class="item" v-for="(item, index) in remarkTagList" :key="index" @click="remark = item">
<div class="item" v-for="(item, index) in remarkTagList" :key="index" @click="addRmarkHandle(item)">
{{ item }}
</div>
</div>
@ -394,13 +395,22 @@ function refundTypeChange(val) {
function inputChange(n) {
setTimeout(() => {
if (n > item.value.payAmount - item.value.refundAmount) {
customAmount.value = formatDecimal(item.value.payAmount - item.value.refundAmount, 2, true)
customAmount.value = formatDecimal(item.value.payAmount - item.value.refundAmount, 2)
} else {
customAmount.value = inputFilterFloat(n)
}
}, 100)
}
//
function addRmarkHandle(item) {
if (remark.value.length) {
remark.value += `${item}`
} else {
remark.value = item
}
}
defineExpose({
show
})

View File

@ -45,10 +45,15 @@
</div>
</div>
</div>
<div class="empty">
<el-empty description="空空如也~" v-if="!tableList.length" />
<div class="empty" v-if="!tableList.length">
<el-empty description="空空如也~" />
</div>
</div>
<div class="pagination">
<el-pagination background v-model:current-page="query.page" :pager-count="5"
layout=" pager, jumper, total" :total="query.total"
@current-change="shopTableAjax"></el-pagination>
</div>
</div>
</div>
<div class="right_card card">
@ -61,17 +66,11 @@
</template>
<script setup>
import { queryShopArea, queryShopTable } from '@/api/table'
import { shopArea, shopTable } from "@/api/account.js";
import countCard from '@/views/table/components/countCard.vue'
import tableInfo from '@/views/table/components/tableInfo.vue'
import { ref, onMounted } from 'vue'
import { useUser } from "@/store/user.js"
import { dayjs } from 'element-plus'
const store = useUser()
const tabActive = ref(0)
const tabAreas = ref([
@ -93,6 +92,11 @@ const tabAreas = ref([
// }
])
const query = ref({
page: 1,
size: 12,
total: 0
})
const loading = ref(false)
//
const areaList = ref([])
@ -158,12 +162,15 @@ async function shopTableAjax() {
try {
loading.value = true
const res = await shopTable({
page: query.value.page,
size: query.value.size,
areaId: area.value,
tableCode: '',
name: '',
status: tabAreas.value[tabActive.value].type,
})
tableList.value = res.records
query.value.total = +res.totalRow
setTimeout(() => {
loading.value = false
}, 500)
@ -180,6 +187,12 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.pagination {
display: flex;
justify-content: flex-end;
padding-top: var(--el-font-size-base);
}
.cart_wrap {
flex: 2;
}
@ -244,7 +257,7 @@ onMounted(() => {
}
.overflow_y {
height: calc(100vh - 160px);
height: calc(100vh - 220px);
overflow-y: auto;
}
@ -252,7 +265,7 @@ onMounted(() => {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: auto;
gap: var(--el-font-size-base);
gap: 10px;
.item {
$closedColor: #aeb8c9;
@ -317,7 +330,7 @@ onMounted(() => {
}
.tab_cont {
height: 120px;
height: 112px;
display: flex;
align-items: center;
justify-content: center;