Files
cashier_desktop/src/views/home/index.vue
2026-04-03 15:35:48 +08:00

937 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="content">
<div class="cart_wrap card" v-loading="cartLoading">
<div class="menu_top">
<div class="menu" @click="pendingCartModalRef.show()">
<el-icon class="icon">
<TakeawayBox />
</el-icon>
<el-text class="t">({{ goodsStore.pendingList.length }})</el-text>
</div>
<div class="number" @click="SelectVipUserRef.show()">
<div class="left">
<el-icon class="icon">
<CirclePlus />
</el-icon>
<template v-if="!goodsStore.vipUserInfo.id">
<span class="t">选择用户</span>
</template>
<template v-else>
<div class="user_info">
<!-- <el-text class="n">{{ goodsStore.vipUserInfo.nickName }}</el-text> -->
<el-text class="p">{{ formatPhoneNumber(goodsStore.vipUserInfo.phone) }}</el-text>
</div>
</template>
</div>
<div class="icon_wrap">
<div class="u_icon" v-if="!goodsStore.vipUserInfo.id">
<el-icon class="i">
<ArrowRight />
</el-icon>
</div>
<div class="u_icon" v-else @click.stop="goodsStore.clearVipUserInfo()">
<el-icon class="i">
<Close />
</el-icon>
</div>
</div>
</div>
<!-- <div class="select_user" @click="quickCashHandle">
<div class="left">
<el-icon class="icon" style="color: var(--el-color-warning);">
<WalletFilled />
</el-icon>
<el-text class="t">快捷收银</el-text>
</div>
</div> -->
<!-- <div class="select_user">
<div class="left">
<el-icon class="icon">
<UserFilled />
</el-icon>
<div class="t_wrap" :class="{ 'big_text': global.orderMemberInfo.telephone && global.tableInfo.id }">
<div class="t" v-if="global.orderMemberInfo.telephone">
会员{{ global.orderMemberInfo.telephone }}
</div>
<div class="t" v-if="global.tableInfo.id">
台桌{{ global.tableInfo.name }}
<span v-if="global.tableInfo.num">/{{ global.tableInfo.num }}</span>
</div>
</div>
</div>
<el-icon class="arrow">
<Close />
</el-icon>
</div> -->
</div>
<div class="shop_operation">
<div class="shop_list">
<div class="table_info" v-if="goodsStore.tableInfo.name">
<div class="left">
<span>台桌{{ goodsStore.tableInfo.name }}</span>
<div class="n" @click="takeFoodCodeRef.show()">
{{ goodsStore.tableInfo.num || 1 }}
<el-icon>
<EditPen />
</el-icon>
</div>
</div>
<div class="close" @click="goodsStore.selectTable()">
<el-icon class="icon">
<Close />
</el-icon>
</div>
</div>
<div>
<CartItem :item="item" :index="index" v-for="(item, index) in goodsStore.cartList" :key="item.id" />
</div>
<div class="empty" v-if="!goodsStore.cartList.length">
<el-empty description="请选择商品" />
</div>
<div class="order_list_wrap">
<div class="order_title" :class="{ border: !goodsStore.cartList.length }"
v-if="goodsStore.orderList.length">
<span class="l">历史下单</span>
<div class="del" @click="goodsStore.clearHistoryOrder()">
<el-icon class="icon">
<Delete />
</el-icon>
<span>清空历史订单</span>
</div>
</div>
<CartItem
:item="{ product_name: '客座费', number: goodsStore.tableInfo.num, salePrice: store.shopInfo.tableFee, memberPrice: store.shopInfo.tableFee }"
v-if="!store.shopInfo.isTableFee && goodsStore.tableInfo.name && !goodsStore.allSelected" />
<div class="order_list_item" v-for="(arr, index) in goodsStore.orderList" :key="index">
<div class="order_num">
<span class="l">{{ `${arr.orderNum}次下单` }}</span>
<div class="del" @click="goodsStore.deleteHistoryOrder(arr.orderNum)">
<span>删除</span>
</div>
</div>
<CartItem type="order" :border="false" :item="item" :index="index" :i="i" :key="item.id"
v-for="(item, i) in arr.goods" />
</div>
</div>
</div>
<!-- 购物车操作栏 -->
<cartOperation :item="cartListActiveItem" @confirm="" @delete="delCartHandle" @pending="pendingCart"
@merging="showTableMerging" @showPackage="e => goodsRef.showPackage(e)" />
</div>
<div class="footer">
<div class="top">
<div class="num-wrap">
<div class="num_wrap_top">
<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>
<CircleCheckFilled />
</el-icon>
</div>
<el-text class="t">外带</el-text>
</div>
<div class="left" v-else></div>
<div class="right" v-if="goodsStore.cartInfo.costSummary">
<el-text>
{{ formatDecimal(goodsStore.cartInfo.costSummary.goodsTotal || 0, 2, true) }}
</el-text>{{ formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount || 0) }}
</div>
</div>
<div class="num_wrap_btm">
{{ goodsStore.cartInfo.discountInfo }}
</div>
</div>
</div>
<div class="btm">
<el-button icon="Edit" @click="remarkRef.show()"></el-button>
<div class="button">
<div class="btn" v-if="store.shopInfo.registerType == 'after'">
<el-button type="primary" style="width: 100%;" :disabled="!goodsStore.cartList.length"
v-loading="createOrderLoading" @click="createOrderHandle(0)">
仅下单
</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;"
:disabled="!goodsStore.cartList.length && !goodsStore.orderList.length" v-loading="createOrderLoading"
@click="createOrderHandle(1)">
去结算
</el-button>
</div>
</div>
</div>
</div>
</div>
<div class="shop_manage card">
<!-- 分类/商品列表 -->
<goods ref="goodsRef" @loading="cartLoading = true" />
<!-- ©银收客 v{{ packageData.version }} -->
</div>
<!-- 无权限遮罩 -->
<div class="no_permission" v-if="!store.menus.length || (store.menus.length && !store.menus[0].state)">
无操作权限请联系管理员(´д)
</div>
<!-- 打印插件是否加载好的提示 -->
<div class="print_tip" v-if="!printStore.isPrintService"
v-loading="!printStore.isPrintService && !printStore.showPrintNotService" element-loading-text="打印服务加载中...">
<el-dialog title="注意" v-model="printStore.showPrintNotService" :modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false" top="30vh">
<span class="print_tip_title">打印服务未启动请重新加载程序或者退出后重新打开</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="printReloadHandle">重新加载</el-button>
<el-button type="primary" @click="quitAPPhandle">退出程序</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
<!-- 备注 -->
<remarkModal ref="remarkRef" @success="(e) => (remark = e)" />
<!-- 修改取餐号 -->
<takeFoodCode />
<el-drawer v-model="membershow" :with-header="true" size="90%" title="选择会员">
<member :membershow="'1'"></member>
</el-drawer>
<!-- <takeFoodCode ref="takeFoodCodeRef" title="修改取餐号" placeholder="请输入取餐号" @success="takeFoodCodeSuccess" /> -->
<!-- 结算订单 -->
<settleAccount ref="settleAccountRef" :cart="cartList" :amount="cartInfo.totalAmount" :remark="remark"
:orderInfo="orderInfo" @success="" />
<!-- 快捷收银 -->
<fastCashier ref="fastCashierRef" type="0" />
<!-- 挂起订单 -->
<pendingCartModal ref="pendingCartModalRef" />
<!-- 检查版本升级 -->
<updateDialog />
<!-- 选择会员 -->
<SelectVipUser ref="SelectVipUserRef" @success="selectUser" />
<!-- 修改就餐人数 -->
<takeFoodCode ref="takeFoodCodeRef" title="修改就餐人数" placeholder="请输入就餐人数" @success="updateSeatNum" />
</template>
<script setup>
import _ from 'lodash'
import { ref } from "vue";
import { useGlobal } from '@/store/global.js'
import SelectVipUser from "@/components/selectVipUser.vue";
import updateDialog from '@/components/updateDialog.vue'
import remarkModal from "@/components/remarkModal.vue";
import takeFoodCode from "@/components/takeFoodCode.vue";
import cartOperation from "@/views/home/components/cartOperation.vue";
import settleAccount from "@/views/home/components/settleAccount.vue";
import fastCashier from "@/views/home/components/fastCashier.vue";
import pendingCartModal from "@/views/home/components/pendingCartModal.vue";
import tableMerging from '@/views/home/components/tableMerging.vue'
import CartItem from './components/cartItem.vue'
import { formatDecimal, formatPhoneNumber, getOrderByIdAjax, commOrderPrintData } from '@/utils/index.js'
import { useGoods } from '@/store/goods.js'
import { staffPermission } from '@/api/user.js'
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'
import { usePrint } from '@/store/print.js'
import { ipcRenderer } from 'electron';
import { ElMessage } from 'element-plus'
const printStore = usePrint()
const SelectVipUserRef = ref(null)
const goodsStore = useGoods()
const socket = useSocket()
const membershow = ref(false);
const store = useUser();
const remarkRef = ref(null);
const takeFoodCodeRef = ref(null);
const goodsRef = ref(null);
const pendingCartModalRef = ref(null);
const settleAccountRef = ref(null);
const fastCashierRef = ref(null);
const tableMergingRef = ref(null)
const remark = ref("");
const cartListActive = ref(0);
const cartListActiveItem = ref({})
const cartList = ref([]);
const cartInfo = ref({});
const cartLoading = ref(false);
const orderInfo = ref({});
const createOrderLoading = ref(false);
function quitAPPhandle(params) {
ipcRenderer.send("quitHandler", "退出吧");
}
function updateSeatNum(num) {
goodsStore.tableInfo.num = num
goodsStore.calcCartInfo()
// goodsStore.operateCart({ table_code: goodsStore.tableInfo.tableCode, seat_num: num }, 'batch')
}
// 重启刷新软件
function printReloadHandle() {
location.reload()
}
// 选择会员
async function selectUser(row) {
console.log('selectUser===', row);
goodsStore.selectUser(row)
}
// 挂单量
const pendingCartNum = ref(0);
// 快捷收银
async function quickCashHandle() {
try {
await staffPermission('yun_xu_shou_kuan')
fastCashierRef.value.show()
} catch (error) {
console.log(error);
}
}
// 生成订单 t=0 先下单后结算 t=1直接结算
async function createOrderHandle(t = 0) {
try {
let placeNum = goodsStore.orderListInfo.placeNum || 0
if (goodsStore.cartList.length) {
const data = {
orderId: goodsStore.orderListInfo.id || '', // 订单id
shopId: store.shopInfo.id, // 店铺id
seatNum: goodsStore.tableInfo.num || 0, // 用餐人数
packFee: goodsStore.cartInfo.packFee, // 打包费
originAmount: goodsStore.cartInfo.costSummary.goodsOriginalAmount,
tableCode: goodsStore.cartList[0].table_code, // 台桌号
dineMode: goodsStore.allSelected ? store.shopInfo.eatModel.split(',')[1] : store.shopInfo.eatModel.split(',')[0], // 用餐方式
remark: remark.value, // 备注
placeNum: placeNum + 1, // 下单次数
waitCall: 0, // 是否叫号
userId: goodsStore.vipUserInfo.userId || '', // 会员用户id
limitRate: goodsStore.limitDiscountRes
}
createOrderLoading.value = true;
goodsStore.calcCartInfo()
const res = await createOrder(data)
if (res.id) {
// 设置订单信息
goodsStore.orderListInfo = res
if (t == 1) {
// 向其他端发送清空购物车消息
goodsStore.operateCart({ table_code: goodsStore.orderListInfo.tableCode }, "cleanup");
console.log('生成订单===', res);
settleAccountRef.value.show(t)
} else {
goodsStore.clearCart()
// 开始打印客看单,可看单需要剔除其他历史下单
getOrderByIdAjax(res.id).then(res => {
let originOrderInfo = res
originOrderInfo.detailMap = _.at(originOrderInfo.detailMap, placeNum + 1);
console.log('originOrderInfo', originOrderInfo);
printStore.pushReceiptData(commOrderPrintData({ ...originOrderInfo, isGuest: true, isBefore: true }));
}).catch(err => {
console.log(err);
})
}
// 清除购物车,更新历史订单
goodsStore.updateOrderList()
} else {
ElMessage.error('订单成功失败,请重新下单')
}
} else {
settleAccountRef.value.show(t)
}
} catch (error) {
console.log(error);
}
createOrderLoading.value = false;
}
// 恢复挂单
async function pendingCartHandle(item) {
}
// 挂单
async function pendingCart(params, status = true) {
}
// 删除购物车
async function delCartHandle(params) {
try {
cartLoading.value = true;
await delCart({
cartId: params.id,
});
cartListActiveItem.value = {}
await queryCartAjax();
cartLoading.value = false;
cartListActive.value = 0;
} catch (error) {
console.log(error);
}
cartLoading.value = false;
}
// 赠送打包操作
function giftPackHandle(key, item) {
item[key] = false;
addCart(item, "edit");
}
// 打包全选
const allSelectedHandle = async () => {
if (goodsStore.allSelected) {
goodsStore.allSelected = 0
// 取消订单的全部外带
if (goodsStore.orderList.length) {
await goodsStore.historyOrderAjax(goodsStore.orderListInfo.tableCode)
goodsStore.calcCartInfo()
}
} else {
goodsStore.allSelected = 1
// 回复订单的外带数据
if (goodsStore.orderList.length) {
goodsStore.orderList.map(item => {
item.goods.map(val => {
if (val.goods_type == 'weight') {
val.pack_number = 1
} else {
val.pack_number = val.number
}
})
})
goodsStore.calcCartInfo()
}
}
if (goodsStore.cartList.length) {
goodsStore.operateCart({
table_code: goodsStore.cartList[0].table_code,
is_pack: goodsStore.allSelected
}, 'batch')
}
};
// 显示转桌/并桌
function showTableMerging() {
let data = cartList.value.filter(item => item.placeNum)
tableMergingRef.value.show(data)
}
</script>
<style scoped lang="scss">
.print_tip {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
background-color: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
.print_tip_title {
font-size: 18px;
font-weight: bold;
color: var(--el-color-danger);
}
.dialog-footer {
padding: var(--el-font-size-base);
}
}
.content {
position: relative;
}
.no_permission {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 999;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.order_list_wrap {
.order_list_item {
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
}
.order_title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px var(--el-font-size-base);
&.border {
border-top: 1px solid #ececec;
}
.l {
color: var(--el-color-warning);
}
.del {
font-size: 12px;
display: flex;
align-items: center;
.icon {
margin-right: 4px;
position: relative;
top: 1px;
}
}
}
.order_num {
font-size: 12px;
color: #999;
padding: var(--el-font-size-base) var(--el-font-size-base) 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
}
.cart_wrap {
flex: 1.5;
height: 100%;
display: flex;
flex-direction: column;
.menu_top {
flex-shrink: 0;
display: flex;
height: var(--el-component-size-large);
.menu {
background-color: var(--el-color-warning);
color: #fff;
width: 80px;
display: flex;
align-items: center;
justify-content: center;
.icon {
font-size: var(--el-font-size-base);
position: relative;
top: 2px;
}
.t {
color: #fff;
margin-left: 4px;
font-size: var(--el-font-size-base);
}
}
.number {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--el-color-info-light-7);
.left {
display: flex;
align-items: center;
margin-left: 14px;
}
.icon_wrap {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
.u_icon {
font-size: 16px;
color: var(--el-color-primary);
.i {
display: flex;
}
}
}
.icon {
color: var(--el-color-primary);
font-size: 20px;
}
.t {
font-size: var(--el-font-size-base);
color: var(--el-color-primary);
margin-left: 6px;
position: relative;
top: -1px;
}
.user_info {
flex: 1;
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-direction: column;
.n {
width: 100%;
flex: 1;
font-size: 14px;
}
.p {
width: 83px;
flex: 1;
color: var(--el-color-primary);
font-size: 14px;
margin-left: 6px;
}
}
}
.select_user {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--el-color-info-light-8);
padding: 0 10px;
.left {
display: flex;
align-items: center;
.icon {
color: var(--el-color-primary);
font-size: 20px;
}
.t_wrap {
display: flex;
flex-direction: column;
align-items: flex-start;
&.big_text {
.t {
font-size: 12px;
}
}
}
.t {
font-size: var(--el-font-size-base);
margin-left: 4px;
}
}
.arrow {
color: #999;
font-size: 16px;
position: relative;
top: 2px;
}
}
}
.shop_operation {
flex: 1;
display: flex;
.shop_list {
flex: 1;
height: calc(100vh - 40px - 60px - 90px);
overflow-y: auto;
border-right: 1px solid #ececec;
.table_info {
height: 40px;
border-bottom: 1px solid #ececec;
font-size: 14px;
color: #999;
display: flex;
.left {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--el-font-size-base);
.n {
display: flex;
align-items: center;
gap: 2px;
}
}
.close {
width: 40px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.icon {
font-size: 16px;
}
&::after {
content: "";
height: 15px;
border-left: 1px solid #ddd;
position: absolute;
left: 0;
top: 12.5px;
}
}
}
.item {
padding: var(--el-font-size-base);
&.active {
background-color: var(--primary-color-hover);
}
&:not(:last-child) {
border-bottom: 1px solid #ececec;
}
.name_wrap {
display: flex;
justify-content: space-between;
font-size: var(--el-font-size-base);
.dis {
color: #999;
font-size: 12px;
text-decoration: line-through;
margin-right: 4px;
}
}
.sku_list {
display: flex;
flex-wrap: wrap;
padding-top: 10px;
.tag {
padding: 2px 6px;
background-color: var(--el-color-danger);
color: #fff;
margin-right: 10px;
margin-bottom: 10px;
}
}
.grooup_wrap {
padding-top: 10px;
font-size: 12px;
color: #555;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
}
.num {
padding-top: var(--el-font-size-base);
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
padding-right: 10px;
.icon_item {
$size: 20px;
height: $size;
padding: 0 6px;
border-radius: 2px;
background-color: #e2e2e2;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 10px;
&.zen {
background-color: #FFB0B1;
color: #FF4D4F;
}
&.bao {
background-color: #52C41A;
}
&.tui {
background-color: var(--el-color-danger);
}
&.lin {
background-color: var(--el-color-warning);
}
&.zhe {
background-color: var(--primary-color);
}
&.chu {
background-color: #ffe7ba;
color: #e69f1c;
}
span {
font-size: inherit;
color: inherit;
}
}
}
.t {
font-size: var(--el-font-size-base);
}
}
}
}
}
}
.footer {
padding: 10px var(--el-font-size-base);
border-top: 1px solid #ececec;
.num-wrap {
height: 40px;
display: flex;
flex-direction: column;
.num_wrap_top {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.selected {
width: 16px;
height: 16px;
display: flex;
align-items: center;
position: relative;
top: 1px;
.icon {
display: block;
font-size: var(--el-font-size-base);
color: var(--primary-color);
}
.selected_round {
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid #ddd;
}
}
.t {
margin-left: 6px;
}
}
}
.num_wrap_btm {
flex: 1;
display: flex;
justify-content: flex-end;
font-size: 12px;
color: #999;
}
}
.btm {
$h: 70px;
display: flex;
height: $h;
padding-top: 10px;
gap: var(--el-font-size-base);
.editor {
width: $h;
border: 1px solid #ececec;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #555;
font-size: var(--el-font-size-base);
}
.button {
flex: 1;
display: flex;
gap: var(--el-font-size-base);
.btn {
flex: 1;
}
.js {
.t {
color: #fff;
font-size: var(--el-font-size-base);
}
}
}
}
}
.shop_manage {
flex: 3;
margin-left: var(--el-font-size-base);
height: 100%;
}
</style>