Files
cashier-web/src/views/tool/table/index.vue

649 lines
17 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="app-container">
<el-tabs v-model="tableArea.tabsSel" type="card" @tab-click="tabClick">
<el-tab-pane label="全部" name="" />
<el-tab-pane
v-for="item in tableArea.tabs"
:key="item.id"
:label="item.name"
:name="`${item.id}`"
>
<template #label>
<div class="">
<span>
{{ item.name }}
</span>
<el-icon style="margin: 0 10px" @click="addEaraShow(item)">
<EditPen />
</el-icon>
<el-popconfirm title="确定删除吗?" @confirm="delArea(item.id)">
<template #reference>
<el-icon>
<Delete />
</el-icon>
</template>
</el-popconfirm>
</div>
</template>
</el-tab-pane>
</el-tabs>
<div class="">
<el-button icon="plus" @click="addEaraShow()">添加区域</el-button>
<el-button type="primary" icon="plus" @click="addTableShow()">添加台桌</el-button>
<el-button type="danger" icon="Setting" @click="clearTabDialogRef.show()">清台设置</el-button>
<el-button type="primary" icon="download" @click="showDownloadTableCode">
下载桌台码
</el-button>
<!-- <el-button type="primary" icon="download" @click="downloadShopCpde">
下载店铺码
</el-button> -->
</div>
<div class="u-flex" style="justify-content: space-between">
<div class="u-flex u-p-b-15 u-font-14 u-m-t-16">
<div v-for="(item, key) in status" :key="key" class="state u-m-r-24">
<span
class="dot"
:style="{
backgroundColor: status[key] ? status[key].type : '',
}"
/>
{{ item.label }}
</div>
</div>
<div style="color: #3f9eff; font-weight: 700; padding-right: 30px">
<span style="color: #333; font-weight: 400">未结账</span>
<span>{{ totalOrder }}</span>
<span></span>
<span>{{ totalPerson }}</span>
<span></span>
<span>¥{{ totalMoney }}</span>
</div>
</div>
<!-- 列表 -->
<div class="head-container">
<div v-loading="loading" class="table_list">
<div
v-for="item in tableList"
:key="item.id"
class="item"
:style="{
'background-color': status[item.status] ? status[item.status].type : '',
}"
>
<div class="new-top flex u-row-between">
<div class="u-flex u-flex-1 u-p-r-10">
<span class="name u-line-1" style="max-width: 50px">
{{ item.name }}
</span>
<span class="u-font-14 u-line-1" style="max-width: 100px">
{{ areaMap[item.areaId] || "" }}
</span>
</div>
<el-dropdown trigger="click" @command="tableComman($event, item)">
<el-icon color="#fff" class="cur-pointer">
<More />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">
<span>编辑</span>
</el-dropdown-item>
<el-dropdown-item command="bindTableCode" v-if="envName !== 'production'">
<span>绑定桌码</span>
</el-dropdown-item>
<el-dropdown-item command="del">
<span>删除</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="box">
<div class="top">
<div v-if="item.status == 'subscribe' && item.bookingInfo">
<div class="row row1" style="align-items: flex-start">
<span style="font-size: 14px; color: #333">
{{ item.bookingInfo.createUserName }}{{ item.bookingInfo.bookingPerson }}{{
item.bookingInfo.gender == 1 ? "先生" : "女士"
}}
</span>
<div
class="state"
style="font-size: 12px; color: #666; display: flex; align-items: center"
>
<img
style="width: 16px; height: 16px; filter: contrast(0.5)"
src="@/assets/images/perpole.png"
alt=""
/>
{{ item.bookingInfo.bookingTime.substring(11, 19) }}
</div>
</div>
<div class="row">
<span style="font-size: 14px; color: #333; margin-top: 5px">
{{ item.bookingInfo.phoneNumber }}
</span>
</div>
</div>
<div v-else class="u-font-18 font-600 total-price">
<span
v-if="item.status == 'unsettled'"
class="cur-pointer"
@click="diancanShow(item, 'isAddGoods')"
>
¥{{ item.orderAmount || 0 }}
<!-- {{ item.productNum }} -->
</span>
<!-- <span v-else class="color-fff">|</span> -->
</div>
<div class="row btn-group" style="margin-top: 10px">
<template v-if="item.status == 'subscribe'">
<el-button
type="success"
:disabled="!item.tableId || item.status === 'closed'"
@click="markStatus(item, -1)"
>
取消预约
</el-button>
</template>
<template
v-if="
item.status == 'subscribe' && item.bookingInfo && item.bookingInfo.status == 20
"
>
<el-button
type="success"
:disabled="!item.tableId || item.status === 'closed'"
@click="markStatus(item, 10)"
>
到店
</el-button>
</template>
<template
v-if="
item.status == 'idle' ||
(item.status == 'subscribe' &&
item.bookingInfo &&
item.bookingInfo.status == 10)
"
>
<el-button
type="primary"
:disabled="!item.tableCode || item.status === 'closed'"
@click="diancanShow(item)"
>
点餐
</el-button>
</template>
<template v-else-if="item.status != 'idle' && item.status != 'subscribe'">
<template v-if="item.status == 'using'">
<el-button
:disabled="!item.tableId || item.status === 'closed'"
@click="diancanShow(item, 'isAddGoods')"
>
加菜
</el-button>
<el-button
type="danger"
:disabled="!item.tableId || item.status === 'closed'"
@click="diancanShow(item, 'isPayOrder')"
>
结账
</el-button>
</template>
<template v-else-if="item.status == 'closed'">
<el-button type="info" disabled>已关台</el-button>
</template>
<template v-else-if="item.status == 'cleaning'">
<el-button
type="info"
:style="{
backgroundColor: status[item.status].type,
borderColor: status[item.status].type,
}"
>
清理中
</el-button>
</template>
<template v-else>
<el-button type="info" disabled>点餐</el-button>
</template>
</template>
</div>
</div>
<div
class="u-flex u-col-bottom bottom u-row-between color-666"
:class="{ 'opacity-0': item.status == 'closed' }"
>
<div class="u-flex u-col-center">
<img
style="width: 16px; height: 16px; filter: contrast(0.5)"
src="@/assets/images/perpole.png"
alt=""
/>
<span class="u-m-t-4 u-font-12 u-m-l-2">
{{ item.personNum || 0 }}/{{ item.maxCapacity }}
</span>
</div>
<div v-if="item.status == 'unsettled'" class="u-flex">
<img
style="width: 16px; height: 16px; filter: contrast(0.5)"
src="@/assets/images/shalou.png"
alt=""
/>
<span class="u-m-t-4 u-font-12">
{{ formatTime(item.orderCreateTime) }}
</span>
</div>
</div>
</div>
</div>
<div class="empty_wrap">
<el-empty v-if="!tableList.length" description="空空如也~" />
</div>
</div>
</div>
<!-- 弹窗 -->
<addEara ref="refAddEara" @success="areainit" />
<addTable ref="refAddTable" @success="tableInit" />
<!-- 下载桌台码 -->
<downloadTableCode ref="refDownloadTableCode" />
<!-- 绑定桌码 -->
<bindCode ref="refBindCode" @refresh="tableInit" />
<!-- 清台设置 -->
<clearTabDialog ref="clearTabDialogRef" />
</div>
</template>
<script setup>
import importData from "@/components/importData/index.vue";
import status from "./status.js";
import { useUserStore } from "@/store/modules/user";
const shopUser = useUserStore();
const router = useRouter();
import shopAreaApi from "@/api/account/shopArea";
import tableApi from "@/api/account/table";
import addEara from "./components/addEara.vue";
import addTable from "./components/addTable.vue";
import bindCode from "./components/bind-table-code.vue";
import downloadTableCode from "./components/downloadTableCode.vue";
import clearTabDialog from "./components/clearTabDialog.vue";
const clearTabDialogRef = ref(null);
const envName = import.meta.env.VITE_APP_NAME;
//绑定桌码
const refBindCode = ref();
function showBindCode(item) {
refBindCode.value.open(item);
}
//桌台二维码
const refDownloadTableCode = ref();
function showDownloadTableCode() {
refDownloadTableCode.value.show();
}
let loading = ref(false);
const nowTime = ref(new Date().getTime());
// 实时更新当前时间(循环执行)
function updateTime() {
nowTime.value = new Date().getTime();
requestAnimationFrame(updateTime);
}
updateTime(); // 启动
// 工具方法
function formatTime(str) {
if (!str) return "";
const targetTime = new Date(str).getTime();
const milliseconds = nowTime.value - targetTime;
if (milliseconds <= 0) return "已结束";
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
return `${days ? days + "天" : ""} ${hours ? hours + "时" : ""} ${minutes + "分"}`;
}
function downloadTableCpde() {}
function downloadShopCpde() {
try {
const link = document.createElement("a");
link.href = shopUser.userInfo.smallQrcode;
const fileName = shopUser.userInfo.shopName + "店铺码" + ".png";
console.log(fileName);
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.$message.success("下载成功");
} catch (error) {
console.log(error);
}
}
// 台桌
/**
* 编辑/删除
* @param command
* @param item
*/
function tableComman(command, item) {
if (command === "bindTableCode") {
showBindCode(item);
return;
}
if (command === "edit") {
return refAddTable.value.show(item);
}
if (command === "del") {
ElMessageBox.confirm("是否删除" + item.name + "台桌", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await tableApi.delete(item.id);
ElMessage.success("删除成功");
tableInit();
})
.catch(() => {});
return;
}
}
const refAddTable = ref(null);
function addTableShow(item) {
refAddTable.value.show(item);
}
const tableList = ref([]);
const tablequery = reactive({
page: 1,
size: 300,
});
async function tableInit() {
const res = await tableApi.getList({ ...tablequery, areaId: tableArea.tabsSel });
tableList.value = res.records;
}
const totalOrder = computed(() => {
return tableList.value.reduce((pre, cur) => {
if (cur.status == "unsettled") {
return pre + 1;
}
return pre;
}, 0);
});
const totalPerson = computed(() => {
return tableList.value.reduce((pre, cur) => {
if (cur.status == "unsettled") {
return pre + cur.personNum;
}
return pre;
}, 0);
});
const totalMoney = computed(() => {
return tableList.value
.reduce((pre, cur) => {
if (cur.status == "unsettled") {
return pre + cur.orderAmount;
}
return pre;
}, 0)
.toFixed(2);
});
// 区域
let areaMap = ref({});
const refAddEara = ref(null);
function addEaraShow(item) {
refAddEara.value.show(item);
}
const tabVlaue = ref("");
const tabs = ref([]);
const tableArea = reactive({
tabs: [],
tabsSel: "",
});
watch(
() => tableArea.tabsSel,
() => {
tableInit();
}
);
//区域删除
function delArea(id) {
shopAreaApi.delete(id).then((res) => {
ElMessage.success("删除成功");
areainit();
});
}
//区域列表初始化
async function areainit() {
const res = await shopAreaApi.getList();
console.log(res);
tableArea.tabs = res.records;
areaMap.value = res.records.reduce((prve, cur) => {
prve[cur.id] = cur.name;
return prve;
}, {});
}
async function diancanShow(item, key) {
if (!key) {
router.push({ path: "/Instead", query: { tableCode: item.tableCode } });
return;
}
if (key == "isAddGoods") {
router.push({ path: "/Instead", query: { id: item.orderId, key } });
}
if (key == "isPayOrder") {
router.push({ path: "/Instead", query: { id: item.orderId, key } });
}
}
const tabClick = (tab) => {
console.log(tab);
};
init();
function init() {
areainit();
tableInit();
}
onMounted(() => {});
</script>
<style lang="scss" scoped>
.el-tabs {
margin-bottom: 0;
}
</style>
<style scoped lang="scss">
.cur-pointer {
cursor: pointer;
}
.opacity-0 {
opacity: 0;
}
.icon {
margin-left: 10px;
}
:deep(.btn-group .el-button) {
width: 100%;
}
:deep(.el-dropdown-menu__item) {
line-height: 36px;
padding: 0 20px;
min-width: 60px;
text-align: center;
}
.state {
display: flex;
align-items: center;
.dot {
$size: 8px;
width: $size;
height: $size;
border-radius: 50%;
margin-right: $size;
}
}
.table_list {
display: flex;
flex-wrap: wrap;
gap: 20px;
min-height: 150px;
.empty_wrap {
flex: 1;
display: flex;
justify-content: center;
}
.btn-group {
display: flex;
gap: 10px;
}
.item {
padding: 1px;
overflow: hidden;
border: 1px solid #ddd;
display: flex;
flex-direction: column;
justify-content: space-between;
border-radius: 6px;
background-color: #1890ff;
max-width: 210px;
min-width: 190px;
&.using {
background-color: rgb(250, 85, 85);
}
&.closed {
background-color: rgb(221, 221, 221);
filter: grayscale(1);
}
.total-price {
line-height: 35px;
height: 35px;
&:hover {
text-decoration: underline;
}
}
.new-top {
height: 30px;
color: #fff;
padding: 0 12px;
}
.name {
font-size: 16px;
line-height: 30px;
margin-right: 10px;
overflow: hidden;
text-overflow: ellipsis;
}
.box {
height: 100%;
background-color: #fff;
border-radius: 3px 3px 6px 6px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.bottom {
border-top: 1px solid #f7f7fa;
padding: 6px 15px;
}
.top {
padding: 10px;
background-color: #fff;
flex: 1;
border-radius: 3px 3px 0 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
.row {
display: flex;
gap: 10px;
.tips {
font-size: 12px;
}
&.row1 {
justify-content: space-between;
font-size: 14px;
}
}
}
.btm {
border-top: 1px solid #ddd;
background-color: #efefef;
display: flex;
border-radius: 0 0 6px 6px;
.btm_item {
flex: 1;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&:hover {
cursor: pointer;
}
&:nth-child(1) {
&::before {
content: "";
height: 50%;
border-right: 1px solid #ddd;
position: absolute;
top: 25%;
right: 0;
}
}
.i {
color: #666;
}
}
}
}
}
</style>