1.新增套餐商品

This commit is contained in:
gyq 2024-12-06 14:41:58 +08:00
parent 96ab68f463
commit 7c27372c4d
15 changed files with 1320 additions and 15927 deletions

File diff suppressed because one or more lines are too long

View File

@ -158,3 +158,87 @@ export function tglogout(data) {
data,
});
}
/**
* 美团团购核销
* 绑定-获取绑定状态
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_state(data) {
return request_php({
method: "post",
url: "/meituan/searchstorestatus",
data,
});
}
/**
* 美团团购核销
* 绑定-获取绑定url
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_bindUrl(data) {
return request_php({
method: "post",
url: "/meituan/getuisdkurl",
data,
});
}
/**
* 美团团购核销
* 团购券-获取可用券
* @param {*} data
* @returns
*/
export function thirdPartyCoupon_list(data) {
return request_php({
method: "post",
url: "/meituan/fulfilmentcertificateprepare",
data,
});
}
/**
* 美团团购核销
* 执行核销
* @param {*} data
* @returns
*/
export function certificateprepare(data) {
return request_php({
method: "post",
url: "/meituan/certificateprepare",
data,
});
}
/**
* 美团团购核销
* 团购核销记录
* @param {*} data
* @returns
*/
export function meituan_orderlist(data) {
return request_php({
method: "post",
url: "/meituan/orderlist",
data,
});
}
/**
* 美团团购核销
* 团购撤销
* @param {*} data
* @returns
*/
export function meituan_fulfilmentcertificatecancel(data) {
return request_php({
method: "post",
url: "/meituan/fulfilmentcertificatecancel",
data,
});
}

View File

@ -194,3 +194,55 @@ export function productStock(data) {
data,
});
}
/**
* 添加临时菜
* @param {*} data
* @returns
*/
export function temporaryDishes(data) {
return request({
method: "post",
url: "/order/temporaryDishes",
data,
});
}
/**
* 商品单位列表获取
* @param {*} params
* @returns
*/
export function getUnitList(params) {
return request({
method: "get",
url: "/unit",
params,
});
}
/**
* 单品改价
* @param {*} data
* @returns
*/
export function updatePrice(data) {
return request({
method: "PUT",
url: "/order/updatePrice",
data,
});
}
/**
* 免厨打印
* @param {*} data
* @returns
*/
export function orderPrint(data) {
return request({
method: "PUT",
url: "/order/print",
data,
});
}

View File

@ -5,7 +5,7 @@ import { formatDecimal } from "@/utils/index.js";
* 打印订单小票
*/
export default (data) => {
console.log('需要打印的订单数据===',data);
// console.log("需要打印的订单数据===", data);
// console.log("data.deviceName===", data.deviceName);
let LODOP = getLodop();
LODOP.PRINT_INIT("打印小票");
@ -51,23 +51,49 @@ export default (data) => {
let table = "";
for (let item of data.carts) {
table += `
if (item.proGroupInfo) {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%" colspan="3">
<div>${item.name}</div>
</td>
<td style="font-size: 12px;width:${t2}%;">${item.totalAmount}</td>
</tr>
`;
let proGroupInfo = JSON.parse(item.proGroupInfo);
for (let item of proGroupInfo) {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%;">
<div>${item.name}</div>
<div>>${item.proName}</div>
${
item.skuName
? `<div class="sku">规格:${item.skuName}</div>`
: ""
}
</td>
<td style="font-size: 12px;width:${t2}%;">${item.salePrice}</td>
<td style="font-size: 12px;width:${t2}%;">0</td>
<td style="font-size: 12px;width:${t2}%;">${item.number}</td>
<td style="font-size: 12px;width:${t2}%;">
${item.totalAmount}
</td>
<td style="font-size: 12px;width:${t2}%;">0</td>
</tr>
`;
`;
}
} else {
table += `
<tr>
<td style="font-size: 12px;width:${t1}%;">
<div>${item.name}</div>
${item.skuName ? `<div class="sku">规格:${item.skuName}</div>` : ""}
</td>
<td style="font-size: 12px;width:${t2}%;">${item.salePrice}</td>
<td style="font-size: 12px;width:${t2}%;">${item.number}</td>
<td style="font-size: 12px;width:${t2}%;">
${item.totalAmount}
</td>
</tr>
`;
}
}
let str = `

View File

@ -208,7 +208,7 @@ defineExpose({
.drawerbox_bo_box_icon {
border-radius: 6px;
background: #2196f3;
background-color: var(--primary-color);
width: 100px;
height: 100px;
display: flex;

View File

@ -57,3 +57,42 @@ export function formatDecimal(num, decimal = 2, isInt = false) {
return parseFloat(num).toFixed(decimal);
}
}
/**
* 过滤input只能输入整数
* @param {*} value
* @returns
*/
export function inputFilterInt(value) {
if (!value) return;
return value.replace(/[^\d]/g, "");
}
/**
* 过滤input只能输入数字并且最多输入两位小数
* @param {*} value
* @returns
*/
export function inputFilterFloat(value) {
if (!value) return;
// 去除首位小数点
if (value.startsWith(".")) {
value = value.slice(1);
}
// 清除非数字和小数点(除了第一个小数点)
value = value.replace(/[^\d.]/g, "");
// 确保最多只有一个小数点
if (value.split(".").length > 2) {
value = value.split(".").slice(0, 2).join(".");
}
// 限制小数位数为两位
if (value.split(".")[1] && value.split(".")[1].length > 2) {
value = value.split(".")[0] + "." + value.split(".")[1].slice(0, 2);
}
// 限制首位只能输入一个0
if (value.startsWith("0") && value.length > 1 && value[1] === "0") {
// 如果首位是0且第二位也是0将第二个0及之后的内容清空
value = "0";
}
return value;
}

View File

@ -79,7 +79,7 @@
</template>
</el-table-column>
</el-table>
<el-table ref="douyin_table" :data="groupDetail.goods" border v-if="props.type == 2">
<el-table ref="douyin_table" :data="groupDetail.goods" border v-else>
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="title"></el-table-column>
<el-table-column label="价格" prop="amount"></el-table-column>
@ -100,7 +100,7 @@
import _ from "lodash";
import { ref } from "vue";
import icon from "@/assets/icon_scan.png";
import { groupOrderorderInfo, groupOrdergroupScan, douyinfulfilmentcertificateprepare, douyincertificateprepare } from '@/api/group'
import { groupOrderorderInfo, groupOrdergroupScan, douyinfulfilmentcertificateprepare, douyincertificateprepare, thirdPartyCoupon_list, certificateprepare } from '@/api/group'
import { useUser } from "@/store/user.js";
import BindShop from './bindShop.vue'
const BindShopRef = ref(null)
@ -173,6 +173,23 @@ async function groupOrdergroupScanHandle() {
}
}
break;
case 3:
//
{
let encrypted_codes = douyin_table.value.getSelectionRows()
if (encrypted_codes.length) {
groupDetailLoading.value = true
let arr = encrypted_codes.map(item => item.encrypted_code)
const res = await certificateprepare({
couponCode: groupDetail.value.couponCode,
num: encrypted_codes.length
})
} else {
ElMessage.error('请选择核销项目')
return
}
}
break
default:
break;
}
@ -221,6 +238,23 @@ async function submitHandle() {
}, 100)
}
break;
case 3:
{
const res = await thirdPartyCoupon_list({
shopId: store.userInfo.shopId,
code: scanCode.value
});
dialogVisible.value = false
loading.value = false
groupDetail.value = res
detailVisible.value = true
setTimeout(() => {
groupDetail.value.goods.map(item => {
douyin_table.value.toggleRowSelection(item)
})
}, 100)
}
break;
default:
break;
}

View File

@ -19,7 +19,7 @@
@click="resetHandle">重置</el-button>
</div>
</div>
<el-button type="warning" :icon="FullScreen" @click="scanGroupRef.show()">核销团购券</el-button>
<el-button type="warning" :icon="FullScreen" @click="showScanModalHandle">核销团购券</el-button>
</div>
<div class="tab_container">
<el-table :data="tableData.list" height="540px" v-loading="tableData.loading"
@ -101,6 +101,24 @@
</template>
</el-table-column>
</el-table>
<el-table height="540px" :data="tableData.list" v-loading="tableData.loading"
v-if="tableData.type == 3">
<el-table-column label="名称" prop="dealTitle"></el-table-column>
<el-table-column label="总金额" prop="couponBuyPrice" width="100">
<template v-slot="scope">
<span style="color: var(--primary-color);">{{ scope.row.couponBuyPrice }}</span>
</template>
</el-table-column>
<el-table-column label="状态" prop="couponStatusDesc" width="150"></el-table-column>
<el-table-column label="使用时间" prop="couponUseTime" width="200"></el-table-column>
<el-table-column label="操作" prop="douyinCodeGoods" width="100">
<template v-slot="scope">
<el-button type="danger" size="small" @click="cacelMeittuanHandle(scope.row)">
撤销
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
@ -112,11 +130,22 @@
<scanGroup ref="scanGroupRef" :title="typeList.find(item => item.value == tableData.type).label"
:type="tableData.type" @succcess="groupOrderlistAjax" />
<refundDialog ref="refundDialogRef" @success="groupOrderlistAjax" />
<el-dialog v-model="showMeituanUrlModal" title="注意">
<span style="font-size: 18px;">您的店铺还未绑定美团请绑定后操作</span>
<template #footer>
<div class="dialog-footer" style="padding: 0 15px 15px;">
<el-button @click="showMeituanUrlModal = false">取消</el-button>
<el-button type="primary" @click="openMeituan">
去绑定
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { groupOrderlist, douyinorderlist, douyinfulfilmentcertificatecancel } from '@/api/group'
import { groupOrderlist, douyinorderlist, douyinfulfilmentcertificatecancel, thirdPartyCoupon_state, thirdPartyCoupon_bindUrl, meituan_orderlist, meituan_fulfilmentcertificatecancel } from '@/api/group'
import { Search, RefreshRight, FullScreen } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { ref, onMounted, reactive } from 'vue'
@ -124,6 +153,7 @@ import scanGroup from './components/scanGroup.vue'
import refundDialog from './components/refundDialog.vue'
import { useUser } from "@/store/user.js"
import BindShop from './components/bindShop.vue'
import { shell } from 'electron'
const store = useUser()
import { useGlobal } from '@/store/global.js'
@ -144,7 +174,7 @@ function typeStatus(t) {
const tableData = reactive({
resetLoading: false,
proName: '',
type: 2,
type: 3,
status: '',
loading: false,
list: [],
@ -171,6 +201,10 @@ const typeList = reactive([
{
value: 2,
label: '抖音'
},
{
value: 3,
label: '美团'
}
])
@ -229,6 +263,10 @@ function typeChange(e) {
case 2:
statusList.value = [...dmStatus]
break;
case 3:
statusList.value = [...dmStatus]
thirdPartyCoupon_state_ajax()
break;
default:
break;
}
@ -238,6 +276,54 @@ function typeChange(e) {
groupOrderlistAjax()
}
//
const meituanStatus = ref(false)
async function thirdPartyCoupon_state_ajax() {
try {
const res = await thirdPartyCoupon_state({
shopId: store.userInfo.shopId
})
if (res.status == 0) {
meituanStatus.value = false
showMeituanUrlModal.value = true
thirdPartyCoupon_bindUrl_ajax()
} else {
meituanStatus.value = true
}
} catch (error) {
console.log(error);
}
}
//
const meituanURL = ref('')
const showMeituanUrlModal = ref(false)
async function thirdPartyCoupon_bindUrl_ajax() {
try {
const res = await thirdPartyCoupon_bindUrl({
shopId: store.userInfo.shopId
})
meituanURL.value = res
} catch (error) {
console.log(error);
}
}
//
function openMeituan() {
showMeituanUrlModal.value = false
shell.openExternal(meituanURL.value);
}
function showScanModalHandle() {
//
if (tableData.type == 3 && !meituanStatus.value) {
showMeituanUrlModal.value = true
return
}
scanGroupRef.value.show()
}
//
function statusFilter(t) {
return originStatus.find(item => item.value == t)?.label
@ -268,6 +354,21 @@ function cacelDouyinHandle(item) {
}).catch(() => { })
}
//
function cacelMeittuanHandle(item) {
ElMessageBox.confirm(
'是否撤销该团购?',
'注意').then(async () => {
try {
await meituan_fulfilmentcertificatecancel({ couponCode: item.couponCode })
ElMessage.success('撤销成功')
groupOrderlistAjax()
} catch (error) {
console.log(error);
}
}).catch(() => { })
}
//
async function groupOrderlistAjax() {
try {
@ -300,6 +401,19 @@ async function groupOrderlistAjax() {
tableData.list = res.list
tableData.total = res.count
break;
case 3:
//
res = await meituan_orderlist({
page: tableData.page,
// status: tableData.status,
// d_order_id: tableData.proName,
date: ''
})
tableData.resetLoading = false
tableData.loading = false
tableData.list = res.list
tableData.total = res.count
break;
default:
break;
}

View File

@ -6,7 +6,7 @@
</el-icon>
</div>
<div class="item number" @click="props.item.id && takeFoodCodeRef.show()">
<el-text class="num">{{ props.item.number || 1 }}</el-text>
<el-text class="num">{{ formatDecimal(props.item.number || 1, 2, true) }}</el-text>
</div>
<div class="item" @click="numberChange('add')">
<el-icon class="icon add">
@ -19,6 +19,12 @@
</el-icon>
<el-text class="t">规格</el-text>
</div>
<div class="item" @click="showDiscountModalHandle">
<el-icon class="icon">
<PriceTag />
</el-icon>
<el-text class="t">打折</el-text>
</div>
<div class="item" :class="{ disabled: props.item.isGift == 'true' }" @click="giftPackHandle('isGift')">
<el-icon class="icon">
<ShoppingBag />
@ -32,6 +38,12 @@
</el-icon>
<el-text class="t">打包</el-text>
</div>
<div class="item" :class="{ disabled: props.item.isPrint == 0 }" @click="kitchenPrint">
<el-icon class="icon">
<DishDot />
</el-icon>
<el-text class="t">免厨</el-text>
</div>
<div class="item" @click="props.item.id && emit('delete', props.item)">
<el-icon class="icon">
<Delete />
@ -54,17 +66,51 @@
<takeFoodCode ref="takeFoodCodeRef" title="修改商品数量" placeholder="请输入商品数量" @success="updateNumber" />
<!-- 购物车选择规格 -->
<skuModal ref="skuModalRef" @success="skuConfirm" />
<!-- 单品打折 -->
<el-dialog v-model="showDiscountModal" title="单品打折" @open="resetDiscountForm = { ...discountForm }"
@closed="discountModalClose">
<div class="dialog">
<div class="el-popover__title content">
<el-form ref="discountFormRef" :model="discountForm" :rules="discountFormRules" label-width="100px"
label-position="left">
<el-form-item label="价格更改" prop="amount">
<el-input v-model="discountForm.amount" placeholder="减8.88元请输入8.88" @input="priceInput">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="更改原因">
<el-input v-model="discountForm.note" type="textarea" placeholder="请输入自定义备注" />
<div class="remark_list">
<div class="item" v-for="item in noteList" :key="item" @click="addNote(item)">
{{ item }}
</div>
</div>
</el-form-item>
</el-form>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showDiscountModal = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="discountFormLoading"
@click="discountFormSubmit">确认</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import takeFoodCode from '@/components/takeFoodCode.vue'
import skuModal from '@/components/skuModal.vue'
import { useShop } from '@/store/shop.js'
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
import { updatePrice, orderPrint } from '@/api/product.js'
const shopStore = useShop()
console.log('---------')
console.log(shopStore)
const props = defineProps({
item: {
type: Object,
@ -126,6 +172,114 @@ function skuConfirm(e) {
if (!props.item.id) return
emit('confirm', e)
}
/**单品打折 start */
const showDiscountModal = ref(false)
const resetDiscountForm = ref({})
const discountFormRef = ref(null)
const discountFormLoading = ref(false)
const discountForm = ref({
masterId: '',
cartId: '',
amount: '',
note: '',
shopId: ''
})
function validateAmount(rule, value, callback) {
if (value == '') {
callback(new Error('请输入折扣价格'))
} else if (value <= 0) {
callback(new Error('输入价格有误'))
} else {
callback()
}
}
const discountFormRules = ref({
amount: [
{
required: true,
validator: validateAmount,
trigger: 'blur',
}
]
})
const noteList = ref([
'顾客投诉质量...',
'友情打折',
'临时活动',
])
//
function showDiscountModalHandle() {
if (props.item.id) {
showDiscountModal.value = true
}
}
//
function priceInput(e) {
setTimeout(() => {
discountForm.value.amount = inputFilterFloat(e)
}, 50)
}
//
function discountModalClose() {
discountForm.value = { ...resetDiscountForm.value }
discountFormRef.value.resetFields()
}
//
function addNote(str) {
if (!discountForm.value.note.length) {
discountForm.value.note += str
} else {
discountForm.value.note += `${str}`
}
}
//
function discountFormSubmit() {
discountFormRef.value.validate(async valid => {
try {
if (valid) {
discountFormLoading.value = true
discountForm.value.masterId = props.item.masterId
discountForm.value.cartId = props.item.id
discountForm.value.shopId = props.item.shopId
await updatePrice(discountForm.value)
discountFormLoading.value = false
showDiscountModal.value = false
ElMessage.success('操作成功')
emit('confirm', { isTemporary: true })
}
} catch (error) {
discountFormLoading.value = false
console.log(error);
}
})
}
/**单品打折 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 */
</script>
<style scoped lang="scss">
@ -133,7 +287,7 @@ function skuConfirm(e) {
padding: 10px;
display: flex;
flex-direction: column;
gap: 16px;
gap: 10px;
.item {
width: 70px;
@ -178,4 +332,33 @@ function skuConfirm(e) {
}
}
}
.dialog {
.content {
padding-bottom: 20px;
}
.footer_wrap {
display: flex;
gap: 20px;
.btn {
flex: 1;
}
}
}
.remark_list {
display: flex;
gap: 10px;
margin-top: 10px;
.item {
padding: 0 10px;
border: 1px solid #ddd;
color: #999;
border-radius: 4px;
}
}
</style>

View File

@ -26,8 +26,12 @@
</el-popover>
</div>
<div class="search_wrap">
<el-button :type="showEditor ? 'warning' : ''" @click="showEditorChange">{{ showEditor ? '关闭编辑' : '编辑'
}}</el-button>
<div class="left">
<el-button :type="showEditor ? 'warning' : ''" @click="showEditorChange">
{{ showEditor ? '关闭编辑' : '编辑' }}
</el-button>
<el-button type="warning" icon="Food" @click="showTemporaryDish = true">临时菜</el-button>
</div>
<div class="right">
<div class="input">
<el-input placeholder="请输入商品名称查询" v-model="commdityName" clearable @focus="
@ -66,6 +70,7 @@
<div class="sell_out" v-if="item.isPauseSale == 1">
<img class="sell_out_icon" src="../../../assets/icon_xq.png">
</div>
<div class="weight" v-if="item.type == 'weigh'">称重</div>
</div>
<div class="name"><el-text line-clamp="1">{{ item.name }}</el-text></div>
<div class="item_empty" v-if="shopListType == 'text'"></div>
@ -160,6 +165,61 @@
</div>
</div>
</el-dialog>
<!-- 添加临时菜 -->
<el-dialog v-model="showTemporaryDish" title="添加临时菜" top="3vh" @open="showTemporaryDishOpen"
@closed="showTemporaryDishClosed">
<div class="dialog">
<div class="el-popover__title content">
<el-form ref="temporaryFormRef" :model="temporaryForm" :rules="temporaryFormRules" label-width="100px"
label-position="left">
<el-form-item label="菜品名称" prop="name">
<el-input v-model="temporaryForm.name" placeholder="请输入菜品名称" />
</el-form-item>
<el-form-item label="菜品分类" prop="categoryId">
<el-select v-model="temporaryForm.categoryId" placeholder="请选择菜品分类">
<el-option v-for="item in temporaryCategorys" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="temporaryForm.price" placeholder="请输入价格" @input="priceInput">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-select v-model="temporaryForm.unit" placeholder="请选择单位">
<el-option v-for="item in units" :key="item.id" :label="item.name"
:value="item.name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="下单数量">
<el-input-number v-model="temporaryForm.num" :min="1" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="temporaryForm.note" type="textarea" placeholder="请输入自定义备注" />
<div class="remark_list">
<div class="item" v-for="item in noteList" :key="item" @click="addNote(item)">
{{ item }}
</div>
</div>
</el-form-item>
</el-form>
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showTemporaryDish = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="temporaryFormLoading"
@click="temporaryFormSubmit">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 称重商品弹窗 -->
<WeightModal ref="WeightModalRef" @success="skuConfirm" />
<!-- 套餐商品弹窗 -->
<GroupModal ref="GroupModalRef" @success="skuConfirm" />
</template>
<script setup>
@ -169,12 +229,15 @@ import { onMounted, ref } from 'vue'
import _ from 'lodash'
import useStorage from "@/utils/useStorage";
import skuModal from '@/components/skuModal.vue'
import { queryCategory, queryNewCommodityInfo, queryProductSku, productStatus, productStock } from '@/api/product'
import WeightModal from './weightModal.vue'
import GroupModal from './groupModal.vue'
import { queryCategory, queryNewCommodityInfo, queryProductSku, productStatus, productStock, getUnitList, temporaryDishes } from '@/api/product'
import { useUser } from "@/store/user.js"
import { Swiper, SwiperSlide } from 'swiper/vue'
import "swiper/swiper-bundle.css";
import { staffPermission } from '@/api/user.js'
import { useGlobal } from '@/store/global.js'
import { inputFilterFloat } from '@/utils/index.js'
const global = useGlobal()
@ -213,6 +276,136 @@ const inputChange = _.debounce(function () {
searchHandle()
}, 500)
/** 添加临时菜 start */
const temporaryCategorys = ref([]) //
const showTemporaryDish = ref(false) //
const units = ref([]) //
const temporaryFormRef = ref(null)
const resetTemporaryForm = ref({})
const temporaryForm = ref({
masterId: '',
shopId: '',
tableId: '',
name: '',
categoryId: '',
price: '',
unit: '',
num: 1,
note: '',
vipUserId: ''
})
const temporaryFormLoading = ref(false)
const noteList = ref([
'免葱',
'免香菜',
'不要辣',
])
function priceInput(e) {
setTimeout(() => {
temporaryForm.value.price = inputFilterFloat(e)
}, 50)
}
//
function addNote(str) {
if (!temporaryForm.value.note.length) {
temporaryForm.value.note += str
} else {
temporaryForm.value.note += `${str}`
}
}
const temporaryFormRules = ref({
name: [
{
required: true,
message: '请输入菜品名称',
trigger: 'blur',
}
],
categoryId: [
{
required: true,
message: '请选择菜品分类',
trigger: 'change',
}
],
price: [
{
required: true,
message: '请输入价格',
trigger: 'blur',
}
],
unit: [
{
required: true,
message: '请选择单位',
trigger: 'change',
}
],
})
//
function showTemporaryDishOpen() {
resetTemporaryForm.value = { ...temporaryForm.value }
}
//
function showTemporaryDishClosed() {
temporaryForm.value = { ...resetTemporaryForm.value }
temporaryFormRef.value.resetFields()
}
//
async function getUnitListAjax() {
try {
const res = await getUnitList({
shopId: store.userInfo.shopId,
page: 1,
size: 100,
name: ''
})
units.value = res.records
} catch (error) {
console.log(error);
}
}
//
function temporaryFormSubmit() {
temporaryFormRef.value.validate(async valid => {
try {
if (valid) {
temporaryFormLoading.value = true
temporaryForm.value.masterId = props.masterId
temporaryForm.value.shopId = store.userInfo.shopId
temporaryForm.value.tableId = global.tableInfo.qrcode
await temporaryDishes(temporaryForm.value)
temporaryFormLoading.value = false
showTemporaryDish.value = false
ElMessage.success('添加成功')
emit('success', { isTemporary: true })
}
} catch (error) {
temporaryFormLoading.value = false
console.log(error);
}
})
}
/** 添加临时菜 end */
/** 套餐 start */
const GroupModalRef = ref(null)
/** 套餐 end */
//
const searchLoading = ref(false)
function searchHandle() {
@ -258,6 +451,7 @@ async function showEditorChange() {
}
// sku
const WeightModalRef = ref(null)
function showSkuHandle(item) {
if (showEditor.value) {
if (item.isPauseSale == 1) {
@ -288,11 +482,22 @@ function showSkuHandle(item) {
})
return
}
if (item.typeEnum == 'sku') {
//
skuModalRef.value.show({ ...item })
} else {
//
if (item.type == 'normal') {
if (item.typeEnum == 'sku') {
//
skuModalRef.value.show({ ...item })
} else {
//
loading.value = true
emit('loading')
queryProductSkuAjax(item)
}
} else if (item.type == 'weigh') {
WeightModalRef.value.show(item)
} else if (item.type == 'package' && item.groupType == 1) {
GroupModalRef.value.show(item)
} else if (item.type == 'package' && item.groupType == 0) {
//
loading.value = true
emit('loading')
queryProductSkuAjax(item)
@ -373,6 +578,7 @@ async function queryCategoryAjax() {
page: 1,
pageSize: 100
})
temporaryCategorys.value = [...res.list]
categorys.value = res.list
categorys.value.unshift({
name: '全部',
@ -666,6 +872,7 @@ defineExpose({
onMounted(async () => {
localUpdateShopListType()
getUnitListAjax()
await updateCategoryActive()
await queryCategoryAjax()
})
@ -785,6 +992,10 @@ onMounted(async () => {
justify-content: space-between;
padding: var(--el-font-size-base);
.left {
display: flex;
}
.right {
display: flex;
gap: 10px;
@ -872,6 +1083,17 @@ onMounted(async () => {
height: 60%;
position: relative;
.weight {
position: absolute;
left: 5px;
bottom: 5px;
color: #fff;
font-size: 12px;
padding: 2px 6px;
background-color: var(--el-color-danger);
border-radius: 4px;
}
.el_img {
width: 100%;
height: 100%;
@ -962,4 +1184,17 @@ onMounted(async () => {
}
}
}
.remark_list {
display: flex;
gap: 10px;
margin-top: 10px;
.item {
padding: 0 10px;
border: 1px solid #ddd;
color: #999;
border-radius: 4px;
}
}
</style>

View File

@ -0,0 +1,206 @@
<!-- 称重商品组件 -->
<template>
<el-dialog title="可选套餐" width="70%" :close-on-click-modal="false" v-model="dialogVisible" top="10vh">
<div class="row" v-for="(item, index) in goodsItem.proGroupVo" :key="index">
<div class="title_wrap">
<div class="item">规格组名{{ item.title }}</div>
<div class="item"
v-html="`本组菜品<span style='color: var(--el-color-danger)'>${item.count}</span>选<span style='color: var(--el-color-danger)'>${item.number}</span>`">
</div>
</div>
<div class="error">
<span v-if="item.isError">错误请按规格组选择菜品</span>
</div>
<el-table border :data="item.goods" ref="tabRefs" @select="selectChange($event, index)"
@select-all="selectChange($event, index)">
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="proName"></el-table-column>
<el-table-column label="规格" prop="skuName"></el-table-column>
<el-table-column label="价格" prop="price">
<template v-slot="scope">
{{ formatDecimal(scope.row.price) }}
</template>
</el-table-column>
<el-table-column label="数量" prop="number">
<template v-slot="scope">
{{ `${scope.row.number}${scope.row.unitName || ''}` }}
</template>
</el-table-column>
</el-table>
</div>
<div class="footer">
<el-button style="width: 100%" @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :disabled="disabled" @click="confirmHandle">
{{ disabled ? '请选择菜品' : '确认' }}
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
const dialogVisible = ref(false);
const number = ref("");
const goodsItem = ref({})
const emit = defineEmits(["success"]);
const tabRefs = ref([])
function show(item) {
disabled.value = true
dialogVisible.value = true;
goodsItem.value = { ...item }
goodsItem.value.proGroupVo.map(item => {
item.isError = false
})
setTimeout(() => {
tabRefs.value.map(item => {
item.clearSelection()
})
}, 100);
}
//
function selectChange($event, index) {
let item = goodsItem.value.proGroupVo[index]
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
item.isError = true
} else {
item.isError = false
}
let flags = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
flags.push({ flag: false })
} else {
flags.push({ flag: true })
}
})
const arr = flags.find(item => !item.flag)
if (arr != undefined && !arr.flag) {
disabled.value = true
return
}
disabled.value = false
}
//
const disabled = ref(true)
function confirmHandle() {
let flags = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
if (selectNum.length != item.number) {
flags.push({ flag: false })
} else {
flags.push({ flag: true })
}
})
const arr = flags.find(item => !item.flag)
if (arr != undefined && !arr.flag) {
disabled.value = true
return
}
disabled.value = false
let goodIds = []
goodsItem.value.proGroupVo.map((item, index) => {
let selectNum = tabRefs.value[index].getSelectionRows()
goodIds.push(selectNum)
})
//
emit("success", {
...goodsItem.value,
productId: goodsItem.value.id,
groupProductIdList: goodIds.flat().map(item => item.proId)
});
dialogVisible.value = false;
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.keybord_wrap {
padding: var(--el-font-size-base) 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
gap: var(--el-font-size-base);
:deep(.el-button--large) {
height: 50px;
}
}
.input_wrap {
display: flex;
align-items: center;
gap: 20px;
.item {
display: flex;
flex-direction: column;
gap: 10px;
}
}
.price_item {
font-size: 18px;
font-weight: bold;
color: var(--el-color-danger);
padding: 15px 0;
border-top: 1px solid #ececec;
}
.footer {
display: flex;
}
.row {
margin-bottom: 20px;
.title_wrap {
display: flex;
gap: 30px;
font-size: 16px;
// padding-bottom: 10px;
.item {
span {
margin: 0 4px;
font-weight: bold;
color: var(--el-color-danger);
}
}
}
.error {
height: 20px;
color: var(--el-color-danger);
font-size: 12px;
}
}
</style>

View File

@ -278,7 +278,8 @@ async function printOrderLable() {
number: item.num,
skuName: item.productSkuName,
salePrice: formatDecimal(item.price),
totalAmount: formatDecimal(item.num * item.price)
totalAmount: formatDecimal(item.num * item.price),
proGroupInfo: item.proGroupInfo
}
)
})

View File

@ -0,0 +1,131 @@
<!-- 称重商品组件 -->
<template>
<el-dialog title="称重商品" width="400" :close-on-click-modal="false" v-model="dialogVisible" top="10vh"
@closed="reset">
<div class="input_wrap">
<div class="item">
<div class="title">单价</div>
<el-button type="primary" plain>{{ goodsItem.lowPrice }}/{{ goodsItem.unitName }}</el-button>
</div>
<div class="item">
<div class="title">重量</div>
<el-input v-model="number" readonly placeholder="请输入">
<template #append>{{ goodsItem.unitName }}</template>
</el-input>
</div>
</div>
<div class="keybord_wrap">
<div v-for="item in 9" :key="item">
<el-button plain type="info" style="width: 100%" @click="inputHandle(item)">{{ item }}</el-button>
</div>
<div>
<el-button plain type="info" style="width: 100%" @click="inputHandle('.')">.</el-button>
</div>
<div>
<el-button plain type="info" style="width: 100%" @click="inputHandle(0)">0</el-button>
</div>
<div>
<el-button plain type="info" icon="CloseBold" style="width: 100%" @click="delHandle"></el-button>
</div>
</div>
<div class="price_item">
{{ formatDecimal(goodsItem.lowPrice * number) }}
</div>
<div class="footer">
<el-button style="width: 100%" :loading="loading" @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" style="width: 100%" :loading="loading" :disabled="number <= 0"
@click="confirmHandle">
确认
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { inputFilterFloat, formatDecimal } from '@/utils/index.js'
const dialogVisible = ref(false);
const number = ref("");
const goodsItem = ref({})
const emit = defineEmits(["success"]);
function show(item) {
dialogVisible.value = true;
goodsItem.value = { ...item }
}
function reset() {
goodsItem.value = {}
number.value = ''
}
//
function inputHandle(n) {
// number.value += n;
number.value = inputFilterFloat(number.value += n)
}
//
function delHandle() {
if (!number.value) return;
number.value = number.value.substring(0, number.value.length - 1);
}
const loading = ref(false)
//
function confirmHandle() {
if (!number.value) return
goodsItem.value.productId = goodsItem.value.id
goodsItem.value.number = number.value
emit("success", goodsItem.value);
dialogVisible.value = false;
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.keybord_wrap {
padding: var(--el-font-size-base) 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
gap: var(--el-font-size-base);
:deep(.el-button--large) {
height: 50px;
}
}
.input_wrap {
display: flex;
align-items: center;
gap: 20px;
.item {
display: flex;
flex-direction: column;
gap: 10px;
}
}
.price_item {
font-size: 18px;
font-weight: bold;
color: var(--el-color-danger);
padding: 15px 0;
border-top: 1px solid #ececec;
}
.footer {
display: flex;
}
</style>

View File

@ -51,7 +51,11 @@
@click="selectCartItemHandle(item, index, i)">
<div class="name_wrap">
<span>{{ item.name }}</span>
<span>{{ item.salePrice }}</span>
<div class="price">
<span :class="{ dis: item.discountSaleAmount }">{{ item.salePrice }}</span>
<span v-if="item.discountSaleAmount">
{{ formatDecimal(item.salePrice - item.discountSaleAmount, 2, true) }}</span>
</div>
</div>
<div class="sku_list" v-if="item.skuName">
<div class="tag" v-for="item in item.skuName.split(',')">
@ -60,21 +64,26 @@
</div>
<div class="num">
<div class="left">
<div class="icon_item" v-if="item.isGift == 'true'" @click="giftPackHandle('isGift', item)">
<el-icon class="icon">
<ShoppingBag />
</el-icon>
<div class="icon_item zen" v-if="item.isGift == 'true'" @click="giftPackHandle('isGift', item)">
<span class="t"></span>
</div>
<div class="icon_item" v-if="item.isPack == 'true'" @click="giftPackHandle('isPack', item)">
<el-icon class="icon" style="color: var(--primary-color)">
<Box />
</el-icon>
<div class="icon_item bao" v-if="item.isPack == 'true'" @click="giftPackHandle('isPack', item)">
<span class="t"></span>
</div>
<div class="icon_item" v-if="item.status == 'return'">
<span class="t">已退</span>
<div class="icon_item tui" v-if="item.status == 'return'">
<span class="t">退</span>
</div>
<div class="icon_item lin" v-if="item.isTemporary == 1">
<span class="t"></span>
</div>
<div class="icon_item zhe" v-if="item.discountSaleAmount">
<span class="t"></span>
</div>
<div class="icon_item chu" v-if="item.isPrint == 0">
<span class="t">免厨打印</span>
</div>
</div>
<el-text class="t">X{{ item.number }}</el-text>
<el-text class="t">X{{ formatDecimal(item.number, 2, true) }}</el-text>
</div>
</div>
</template>
@ -402,27 +411,43 @@ function selectCartItemHandle(row, index, i) {
}
//
async function addCart(params, type = "add") {
async function addCart(params = {}, type = "add") {
console.log(params);
try {
cartLoading.value = true;
const res = await createCart({
productId: params.productId,
masterId: masterId.value,
tableId: global.tableInfo.qrcode || '',
vipUserId: global.orderMemberInfo.id || '',
shopId: store.userInfo.shopId,
skuId: type == "add" ? params.id : params.skuId,
number: params.number || 1,
isPack: params.isPack || "false",
isGift: params.isGift || "false",
cartId: type == "add" ? "" : params.id,
uuid: params.uuid || store.userInfo.uuid,
type: type,
});
cartLoading.value = false;
masterId.value = res;
goodsRef.value.updateData();
queryCartAjax();
if (params.isTemporary) {
await createCodeAjax()
cartLoading.value = false;
} else {
let skuId = ''
if (params.skuList && params.skuList.length) {
skuId = params.skuList[0].id
} else {
skuId = type == "add" ? params.id : params.skuId
}
const res = await createCart({
productId: params.productId,
masterId: masterId.value,
tableId: global.tableInfo.qrcode || '',
vipUserId: global.orderMemberInfo.id || '',
shopId: store.userInfo.shopId,
// skuId: type == "add" ? params.id : params.skuId,
skuId: skuId,
number: params.number || 1,
isPack: params.isPack || "false",
isGift: params.isGift || "false",
cartId: type == "add" ? "" : params.id,
uuid: params.uuid || store.userInfo.uuid,
type: type,
groupProductIdList: params.groupProductIdList || []
});
cartLoading.value = false;
masterId.value = res;
goodsRef.value.updateData();
queryCartAjax();
}
} catch (error) {
console.log(error);
cartLoading.value = false;
@ -498,7 +523,6 @@ async function addTableNum() {
//
async function createCodeAjax(type = "0") {
console.log(1111)
try {
// if (!process.env.VITE_DEV_SERVER_URL) {
// masterId.value = '#20'
@ -661,6 +685,13 @@ onMounted(() => {
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 {
@ -686,21 +717,51 @@ onMounted(() => {
.left {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
padding-right: 10px;
.icon_item {
$size: 30px;
width: $size;
$size: 20px;
height: $size;
border-radius: 4px;
padding: 0 6px;
border-radius: 2px;
background-color: #e2e2e2;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
color: #fff;
font-size: 10px;
.t {
font-size: 10px;
color: #888;
&.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;
}
}
}

View File

@ -122,13 +122,24 @@
</div>
<div class="orderbox_right_list_item" style="margin-top: 20px"
v-for="(item, index) in orderDetaildata.detailList" :key="index">
<div>{{ item.productName }} {{ item.productSkuName }}</div>
<div style="text-align: center">{{ item.num }}</div>
<div style="text-align: center">{{ item.price }}</div>
<div v-if="item.status == 'refund'">
<span style="border: 2px solid red; color: red; padding: 4px 2px">已退</span>
<div class="orderbox_right_list_item_row" style="display:flex;">
<div>{{ item.productName }} {{ item.productSkuName }}</div>
<div style="text-align: center">{{ item.num }}</div>
<div style="text-align: center">{{ item.price }}</div>
<div v-if="item.status == 'refund'">
<span style="border: 2px solid red; color: red; padding: 4px 2px">已退</span>
</div>
<div v-else>{{ item.priceAmount }}</div>
</div>
<div class="pro" v-if="item.proGroupInfo" v-for="(val, idx) in JSON.parse(item.proGroupInfo)" :key="idx">
<div>
<span>>{{ val.proName }}</span>
<span v-if="val.skuName">规格{{ val.skuName }}</span>
</div>
<div>{{ val.number }}</div>
<div>0</div>
<div>0</div>
</div>
<div v-else>{{ item.priceAmount }}</div>
</div>
<div :style="{ height: reforderboxrightbuttonheight + 'px' }"></div>
</div>
@ -513,7 +524,8 @@ const print = lodash.throttle(
number: item.num,
skuName: item.productSkuName,
salePrice: formatDecimal(item.price),
totalAmount: formatDecimal(item.num * item.price)
totalAmount: formatDecimal(item.num * item.price),
proGroupInfo: item.proGroupInfo
}
)
})
@ -1098,30 +1110,66 @@ onMounted(() => {
}
.orderbox_right_list_item {
display: flex;
font-size: 14px;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
div:nth-child(1) {
text-align: left;
width: 45%;
}
div:nth-child(2) {
width: 15%;
.pro {
display: flex;
font-size: 14px;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
div:nth-child(1) {
text-align: left;
width: 45%;
display: flex;
flex-direction: column;
}
div:nth-child(2) {
width: 15%;
display: flex;
justify-content: center;
}
div:nth-child(3) {
width: 15%;
display: flex;
justify-content: center;
}
div:nth-child(4) {
width: 25%;
display: flex;
justify-content: flex-end;
}
}
div:nth-child(3) {
width: 15%;
.orderbox_right_list_item_row {
display: flex;
font-size: 14px;
justify-content: space-between;
align-items: center;
}
padding-bottom: 10px;
div:nth-child(4) {
text-align: right;
width: 25%;
div:nth-child(1) {
text-align: left;
width: 45%;
}
div:nth-child(2) {
width: 15%;
align-items: center;
}
div:nth-child(3) {
width: 15%;
align-items: center;
}
div:nth-child(4) {
text-align: right;
width: 25%;
}
}
}