This commit is contained in:
2026-01-05 13:53:12 +08:00
33 changed files with 1407 additions and 280 deletions

View File

@@ -0,0 +1,5 @@
<template>
<div>
<FastMenuConfig></FastMenuConfig>
</div>
</template>

View File

@@ -56,7 +56,7 @@
</el-icon>
</div>
<div class="info">
<div class="m">{{ payCount.totalAmount || 0 }}</div>
<div class="m">{{ multiplyAndFormat(payCount.totalAmount || 0) }}</div>
<div class="t">总金额</div>
</div>
</div>
@@ -67,7 +67,7 @@
</el-icon>
</div>
<div class="info">
<div class="m">{{ payCount.refundAmount || 0 }}</div>
<div class="m">{{ multiplyAndFormat(payCount.refundAmount || 0) }}</div>
<div class="t">退款金额</div>
</div>
</div>
@@ -100,7 +100,7 @@
</el-icon>
</div>
<div class="info">
<div class="m">{{ payCount.totalAmount - payCount.refundAmount || 0 }}</div>
<div class="m">{{ multiplyAndFormat((payCount.totalAmount - payCount.refundAmount) || 0) }}</div>
<div class="t">实际销售额总和</div>
</div>
</div>
@@ -111,7 +111,7 @@
</el-icon>
</div>
<div class="info">
<div class="m">{{ payCount.saleCount - payCount.refundCount || 0 }}</div>
<div class="m">{{ multiplyAndFormat((payCount.saleCount - payCount.refundCount) || 0) }}</div>
<div class="t">实际销量总和</div>
</div>
</div>
@@ -152,10 +152,10 @@
<template v-slot="scope">{{ scope.row.refund }}</template>
</el-table-column>
<el-table-column label="实际销量" prop="refundCount">
<template v-slot="scope"> {{ scope.row.saleCount - scope.row.refundCount }} </template>
<template v-slot="scope"> {{ multiplyAndFormat(scope.row.saleCount - scope.row.refundCount) }} </template>
</el-table-column>
<el-table-column label="实际销售额" prop="refundCount">
<template v-slot="scope"> {{ scope.row.saleAmount - scope.row.refundAmount }} </template>
<template v-slot="scope"> {{ multiplyAndFormat(scope.row.saleAmount - scope.row.refundAmount) }} </template>
</el-table-column>
</el-table>
<el-table :data="tableData.data" v-loading="tableData.loading" v-if="orderType == 2">
@@ -165,17 +165,17 @@
<el-table-column label="商品名称" prop="productName"></el-table-column>
<el-table-column label="销量" prop="saleCount" sortable></el-table-column>
<el-table-column label="销售金额" prop="saleAmount" sortable>
<template v-slot="scope">{{ scope.row.saleAmount }}</template>
<template v-slot="scope">{{ multiplyAndFormat(scope.row.saleAmount) }}</template>
</el-table-column>
<el-table-column label="退单量" prop="refundCount" sortable></el-table-column>
<el-table-column label="退款金额" prop="refundAmount" sortable>
<template v-slot="scope">{{ scope.row.refundAmount }}</template>
<template v-slot="scope">{{ multiplyAndFormat(scope.row.refundAmount) }}</template>
</el-table-column>
<el-table-column label="实际销量" prop="refundCount" sortable>
<template v-slot="scope"> {{ scope.row.saleCount - scope.row.refundCount }} </template>
<template v-slot="scope"> {{ multiplyAndFormat(scope.row.saleCount - scope.row.refundCount) }} </template>
</el-table-column>
<el-table-column label="实际销售额" prop="refundCount" sortable>
<template v-slot="scope"> {{ scope.row.saleAmount - scope.row.refundAmount }} </template>
<template v-slot="scope"> {{ multiplyAndFormat(scope.row.saleAmount - scope.row.refundAmount) }} </template>
</el-table-column>
</el-table>
</div>
@@ -193,12 +193,13 @@ import saleSummaryApi from "@/api/order/sale-summary";
import categoryApi from "@/api/product/productclassification";
import ShopApi from "@/api/account/shop";
import dayjs from "dayjs";
import { downloadFile } from "@/utils/index";
import { downloadFile, multiplyAndFormat } from "@/utils/index";
import { formatDateRange } from './utils/index.js'
export default {
data() {
return {
multiplyAndFormat,
timeValue: "today",
resetQuery: null,
orderType: "2",
@@ -314,7 +315,8 @@ export default {
endDate: this.query.createdAt[1],
categoryId: this.query.prodCategoryId,
productName: this.query.productName,
shopId: this.shopId
shopId: this.shopId,
rangeType: this.timeValue,
});
downloadFile(file, "数据", "xlsx");
} catch (error) {

View File

@@ -187,7 +187,8 @@ export default {
const file = await tableSummaryApi.export({
beginDate: this.query.createdAt[0],
endDate: this.query.createdAt[1],
shopId: this.shopId
shopId: this.shopId,
rangeType: this.timeValue,
});
downloadFile(file, "数据", "xlsx");
this.downloadLoading = false;

View File

@@ -12,7 +12,15 @@
</el-select>
</el-form-item>
<el-form-item label="耗材价格" prop="price">
<el-input-number v-model="form.price" placeholder="请输入耗材价格"></el-input-number>
<div class="center">
<el-input-number v-model="form.price" placeholder="请输入" @change="onPriceChange"></el-input-number>
<span>{{ form.conUnit }}</span>
<div class="ipt">
第二单位价格
<el-input-number v-model="twoPrice" placeholder="请输入" @change="onTwoPriceChange"></el-input-number>
<span>{{ form.conUnitTwo }}</span>
</div>
</div>
</el-form-item>
<!-- <el-form-item label="库存">
<el-input-number v-model="form.stockNumber" placeholder="请输入库存值"></el-input-number>
@@ -23,7 +31,7 @@
<el-form-item label="单位" prop="conUnit">
<el-input v-model="form.conUnit" placeholder="请输入单位"></el-input>
</el-form-item>
<el-alert class="u-m-t-10 u-m-b-10" title="提示" description="换算值为第单位*第二单位转换数量=第单位" type="warning" show-icon
<el-alert class="u-m-t-10 u-m-b-10" title="提示" description="换算值为第单位*第二单位转换数量=第单位" type="warning" show-icon
:closable="false"></el-alert>
<el-form-item label="预警值">
<el-input-number v-model="form.conWarning" placeholder="请输入耗材预警值"></el-input-number>
@@ -50,9 +58,11 @@
<script setup>
import { ref, reactive, computed } from "vue";
import consApi from "@/api/product/cons";
import consGroupApi from "@/api/product/cons-group";
import { ElMessage } from "element-plus";
const consGroups = ref([]);
const rules = {
conName: [{ required: true, message: "请输入耗材名称", trigger: "blur" }],
@@ -94,6 +104,34 @@ const form = reactive({
...basicForm,
});
// 第二单位价格为计算字段:第一单位价格 * 第二单位转换数量
const twoPrice = computed({
get() {
const p = Number(form.price || 0);
const c = Number(form.conUnitTwoConvert || 0);
const val = c ? p * c : 0;
return parseFloat(val.toFixed(4));
},
set(val) {
const c = Number(form.conUnitTwoConvert || 0);
if (!c || !isFinite(Number(val))) return;
// 修改第二单位价格时,反算第一单位价格并保留四位小数
form.price = parseFloat((Number(val) / c).toFixed(4));
},
});
function onPriceChange(val) {
if (!isFinite(Number(val))) return;
form.price = parseFloat(Number(val).toFixed(4));
}
function onTwoPriceChange(val) {
if (!isFinite(Number(val))) return;
const c = Number(form.conUnitTwoConvert || 0);
if (!c) return;
form.price = parseFloat((Number(val) / c).toFixed(4));
}
const show = ref(false);
let dialogtitle = ref("");
function open(item) {
@@ -171,4 +209,18 @@ defineExpose({
open,
close,
});
</script>
</script>
<style scoped lang="scss">
.center {
display: flex;
align-items: center;
.ipt {
display: flex;
align-items: center;
gap: 5px;
margin-left: 28px;
}
}
</style>

View File

@@ -280,9 +280,9 @@ export default {
return price;
}
if (row.conUnitTwo && row.unit == row.conUnitTwo) {
console.log(price / row.conUnitTwoConvert);
console.log(new Decimal(price).div(new Decimal(row.conUnitTwoConvert)));
return new Decimal(price).div(new Decimal(row.conUnitTwoConvert));
// console.log(price / row.conUnitTwoConvert);
// console.log(new Decimal(price).div(new Decimal(row.conUnitTwoConvert)));
return new Decimal(price).mul(new Decimal(row.conUnitTwoConvert));
}
return price;
},
@@ -294,7 +294,7 @@ export default {
return number;
}
if (row.conUnitTwo && row.unit == row.conUnitTwo) {
return number * row.conUnitTwoConvert;
return number / row.conUnitTwoConvert;
}
return number;
},

View File

@@ -14,7 +14,9 @@ const contentConfig: IContentConfig = {
pageSizes: [10, 20, 30, 50],
},
indexAction: function (params) {
return Api.getList({});
return Api.getList({
...params,
});
},
deleteAction: function (id) {
return Api.delete(id);
@@ -79,7 +81,7 @@ const contentConfig: IContentConfig = {
fixed: "right",
width: 280,
templet: "tool",
operat: [{ text: "编辑", icon: 'edit', name: "edit"}, { text: "删除", icon: 'delete', type: 'danger', name: "delete"}],
operat: [{ text: "编辑", icon: 'edit', name: "edit" }, { text: "删除", icon: 'delete', type: 'danger', name: "delete" }],
},
],
};

View File

@@ -336,7 +336,8 @@ async function confirmRefundHandle(form) {
} catch (error) {
console.log(error);
}
tableData.loading = false
getTableData()
wareCountAjax()
}
// 审核退款操作

View File

@@ -34,6 +34,7 @@ function returnPrice(skuList) {
const emits = defineEmits(['success'])
function selectItem(item) {
emits('success', {
id: item.id,
coverImg: item.coverImg,
name: item.name,
price: returnPrice(item.skuList)

View File

@@ -42,7 +42,7 @@ const form = ref({
watch(() => form.value.isEnable, (newValue, OldValue) => {
if (loading.value == false) {
if (newValue == 0) {
ElMessageBox.confirm('关闭商品拼团所有未支付的订单都将自动取消,是否确定关闭?', '注意', {
ElMessageBox.confirm('关闭商品拼团所有未成团的订单都将自动取消,是否确定关闭?', '注意', {
confirmButtonText: '确认关闭',
type: 'danger'
}).then(() => {

View File

@@ -56,8 +56,13 @@
<el-table-column label="操作" width="150">
<template v-slot="scope">
<template v-if="scope.row.shopId === shopInfo.id">
<el-button link type="primary" @click="addGroupGoodsRef.show(scope.row)">编辑</el-button>
<el-button type="danger" link @click="delHandle(scope.row)">删除</el-button>
<template v-if="scope.row.onlineStatus">
<el-button link type="primary" disabled>下架后编辑/删除</el-button>
</template>
<template v-else>
<el-button link type="primary" @click="addGroupGoodsRef.show(scope.row)">编辑</el-button>
<el-button link type="danger" @click="delHandle(scope.row)">删除</el-button>
</template>
</template>
<template v-else>
<el-button link type="primary" disabled>无权操作</el-button>

View File

@@ -14,14 +14,22 @@ const contentConfig: IContentConfig = {
pageSize: 10,
pageSizes: [10, 20, 30, 50],
},
indexAction: function (params) {
async indexAction(params) {
console.log(params);
if (params.createAt) {
params.startTime = params.createAt[0];
params.endTime = params.createAt[1];
delete params.createAt;
}
return OrderApi.getList(params);
const res = await OrderApi.getList(params)
console.log('OrderApi.getList', res);
res.records.map(item => {
item.payLoading = false
item.printStatus = JSON.parse(item.printStatus || '[]')
})
return res;
},
indexActionData: {},
// deleteAction: OrderApi.delete,
@@ -93,7 +101,14 @@ const contentConfig: IContentConfig = {
templet: "custom",
slotName: "options",
},
{
label: "打印状态",
align: "center",
prop: "printStatus",
width: 120,
templet: "custom",
slotName: "printStatus",
},
{
label: "状态",
align: "center",
@@ -116,7 +131,7 @@ const contentConfig: IContentConfig = {
label: "操作",
align: "center",
fixed: "right",
width: 150,
width: 180,
templet: "custom",
slotName: "operate",
},

View File

@@ -51,6 +51,13 @@
</div>
</template>
<!-- 打印状态 -->
<template #printStatus="scope">
<span v-if="scope.row.printStatus.length > 0" style="color: var(--el-color-danger)">
打印失败{{scope.row.printStatus.map(item => item.name).join('、')}}
</span>
</template>
<template #options="scope">
{{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }}
</template>
@@ -65,6 +72,7 @@
</template>
<template #operate="scope">
<div>
<el-button link @click="printOrderHandle(scope.row)">打印</el-button>
<el-button link @click="showdetail(scope.row)">详情</el-button>
<el-button v-if="scope.row.status == 'done'" link>开票</el-button>
<el-button v-if="scope.row.status == 'unpaid'" type="primary" @click="toPayOrder(scope.row)">
@@ -148,6 +156,19 @@ onMounted(() => {
}
});
// 打印订单
async function printOrderHandle(order: getListResponse) {
try {
await orderApi.printOrder({
id: order.id,
type: 0
});
ElMessage.success("打印成功");
} catch (error) {
ElMessage.error("打印失败,请稍后重试");
}
}
// 新增
async function handleAddClick() {
addModalRef.value?.setModalVisible();

View File

@@ -2,26 +2,14 @@
<div class="app-container">
<!-- 列表 -->
<!-- 搜索 -->
<page-search
ref="searchRef"
:search-config="searchConfig"
@query-click="newHandleQueryClick"
@reset-click="handleResetClick2"
/>
<page-search ref="searchRef" :search-config="searchConfig" @query-click="newHandleQueryClick"
@reset-click="handleResetClick2" />
<!-- 顶部数据 -->
<Statistics :data="gongjiData"></Statistics>
<!-- 列表 -->
<page-content
ref="contentRef"
:content-config="contentConfig"
@add-click="handleAddClick"
@edit-click="handleEditClick"
@export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
>
<page-content ref="contentRef" :content-config="contentConfig" @add-click="handleAddClick"
@edit-click="handleEditClick" @export-click="handleExportClick" @search-click="handleSearchClick"
@toolbar-click="handleToolbarClick" @operat-click="handleOperatClick" @filter-change="handleFilterChange">
<!-- <template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
@@ -34,20 +22,11 @@
<DictLabel v-model="scope.row[scope.prop]" code="gender" />
</template>
<template #shangjia="scope">
<el-switch
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchChange(scope.row)"
></el-switch>
<el-switch v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchChange(scope.row)"></el-switch>
</template>
<template #isStock="scope">
<el-switch
disabled
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
></el-switch>
<el-switch disabled v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"></el-switch>
</template>
<template #kucunedit="scope">
@@ -60,29 +39,16 @@
</template>
<template #tuikuantuihui="scope">
<el-switch
v-if="!scope.row.productId"
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchhaocai(scope.row)"
></el-switch>
<el-switch v-if="!scope.row.productId" v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchhaocai(scope.row)"></el-switch>
</template>
<template #sellOut="scope">
<el-switch
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchChangeTwo(scope.row)"
></el-switch>
<el-switch v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchChangeTwo(scope.row)"></el-switch>
</template>
<template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text>
<copy-button
v-if="scope.row[scope.prop]"
:text="scope.row[scope.prop]"
style="margin-left: 2px"
/>
<copy-button v-if="scope.row[scope.prop]" :text="scope.row[scope.prop]" style="margin-left: 2px" />
</template>
<template #consumables="scope">
<template v-if="scope.row.type != null">
@@ -105,11 +71,7 @@
</page-modal>
<!-- 编辑 -->
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<page-modal ref="editModalRef" :modal-config="editModalConfig" @submit-click="handleSubmitClick">
<template #gender="scope">
<Dict v-model="scope.formData[scope.prop]" code="gender" v-bind="scope.attrs" />
</template>
@@ -156,32 +118,24 @@
<el-table-column label="序号" type="index" width="60"></el-table-column>
<el-table-column label="耗材名称" prop="consInfoId">
<template v-slot="scope">
<el-select
v-model="scope.row.consInfoId"
reserve-keyword
placeholder="请输入关键词"
@change="selectionChange($event, scope.row)"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.conName"
:value="item.id"
></el-option>
<el-select v-model="scope.row.consInfoId" reserve-keyword placeholder="请输入关键词"
@change="selectionChange($event, scope.row)">
<el-option v-for="item in options" :key="item.id" :label="item.conName" :value="item.id"></el-option>
</el-select>
<!-- <div class="tips" v-if="scope.row.stockNumber">库存{{ scope.row.stockNumber }}</div> -->
</template>
</el-table-column>
<el-table-column label="单位" prop="conUnit">
<template v-slot="scope">
<el-select v-model="scope.row.conUnit" reserve-keyword placeholder="请输入关键词">
<el-input v-model="scope.row.conUnit" readonly disabled placeholder="请选择耗材"></el-input>
<!-- <el-select v-model="scope.row.conUnit" reserve-keyword placeholder="请输入关键词">
<el-option
v-for="item in returnConUnits(scope.row.consInfoId)"
:key="item"
:label="item"
:value="item"
></el-option>
</el-select>
</el-select> -->
</template>
</el-table-column>
<el-table-column label="使用数量" prop="surplusStock" min-width="150px">
@@ -192,20 +146,13 @@
<el-table-column label="操作" width="100">
<template v-slot="scope">
<div class="table_btn_wrap">
<div
class="btn sub"
v-if="haocaiData.consList.length > 1"
@click="haocaiData.consList.splice(scope.$index, 1)"
>
<div class="btn sub" v-if="haocaiData.consList.length > 1"
@click="haocaiData.consList.splice(scope.$index, 1)">
<el-icon>
<RemoveFilled />
</el-icon>
</div>
<div
class="btn add"
v-if="scope.$index == haocaiData.consList.length - 1"
@click="createItem(scope.row)"
>
<div class="btn add" v-if="scope.$index == haocaiData.consList.length - 1" @click="createItem(scope.row)">
<el-icon>
<CirclePlusFilled />
</el-icon>

View File

@@ -222,7 +222,6 @@
<el-form-item label="库存数量">
<el-input-number v-model="ruleForm.stockNumber" :min="0" />
</el-form-item>
<el-form-item label="打包费" prop="delivery">
<div style="display: block;">
<el-input-number v-model="ruleForm.packFee" controls-position="right"
@@ -230,6 +229,42 @@
<div style="color: #999;">单份商品打包费店铺开启外卖模式下该数据才生效</div>
</div>
</el-form-item>
<el-form-item label="关联推荐商品">
<div class="column">
<div class="row">
<div class="center">
<el-button type="primary" @click="selecProductDialogRef?.show()"
:disabled="ruleForm.relatedRecommendJson.length >= goodsListMax">添加商品</el-button>
<div class="tips">设置商品后用户可以在商品详情页中看到推荐商品可拖动调整顺序最多设置{{ goodsListMax }}个商品</div>
</div>
</div>
<div class="row">
<div id="goods_table_drag">
<el-table :data="ruleForm.relatedRecommendJson" border stripe style="width: 500px;" row-key="id">
<!-- 拖拽列 -->
<el-table-column label="排序" width="60">
<template v-slot="scope">
<div class="drag-handle"></div>
</template>
</el-table-column>
<el-table-column label="商品图片" prop="coverImg" width="90">
<template v-slot="scope">
<el-image :src="scope.row.coverImg" style="width: 50px;height: 50px;border-radius: 4px;"
fit="cover"></el-image>
</template>
</el-table-column>
<el-table-column label="商品名称" prop="name"></el-table-column>
<el-table-column label="操作" width="100">
<template v-slot="scope">
<el-button link type="danger"
@click="ruleForm.relatedRecommendJson.splice(scope.$index, 1)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)">确定</el-button>
<el-button @click="resetForm(ruleFormRef)">取消</el-button>
@@ -256,11 +291,13 @@
</span>
</template>
</el-dialog>
<!-- 选择商品弹窗 -->
<selecProductDialog ref="selecProductDialogRef" @success="selecProductSuccess" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { reactive, ref, onMounted, nextTick } from "vue";
import type { FormInstance, FormRules } from "element-plus";
// 规格属性
import SpecificationAttribute from "./SpecificationAttribute.vue";
@@ -272,7 +309,69 @@ import UserAPI4 from "@/api/product/specificationsconfig";
import shopList from "@/components/mycomponents/shopList.vue";
import AddImg from "@/components/mycomponents/addImg.vue";
import { useRouter } from "vue-router";
import Sortable from "sortablejs";
import { useTagsViewStore } from "@/store";
import selecProductDialog from "@/views/marketing_center/group_booking/components/selecProductDialog.vue";
const selecProductDialogRef = ref(null)
const goodsListMax = ref(9)
onMounted(() => {
// Sortable 需要在 el-table 渲染 tbody 后初始化,尝试多次以确保 DOM 可用
const initSortable = async () => {
await nextTick();
let attempts = 0;
const maxAttempts = 8;
const tryInit = () => {
const el = document.querySelector("#goods_table_drag .el-table__body-wrapper tbody");
if (!el) {
attempts++;
if (attempts < maxAttempts) {
setTimeout(tryInit, 80);
}
return;
}
// 已找到表体,初始化 Sortable
new Sortable(el as HTMLElement, {
animation: 150,
handle: ".drag-handle",
ghostClass: "sortable-ghost",
chosenClass: "sortable-chosen",
onEnd: (e) => {
if (e.oldIndex == null || e.newIndex == null) return;
if (e.oldIndex === e.newIndex) return;
const from = e.oldIndex;
const to = e.newIndex;
const item = ruleForm.relatedRecommendJson.splice(from, 1)[0];
ruleForm.relatedRecommendJson.splice(to, 0, item);
// 触发响应式更新
ruleForm.relatedRecommendJson = ruleForm.relatedRecommendJson.slice();
console.log("排序后的数据", ruleForm.relatedRecommendJson);
},
});
};
tryInit();
};
initSortable();
});
// 已选择的商品
function selecProductSuccess(res: any) {
if (res.id == ruleForm.id) {
ElMessage.error("不能选择当前商品");
return;
}
let obj = ruleForm.relatedRecommendJson.find((item) => item.id == res.id);
if (obj && obj.id) {
ElMessage.error("该商品已选择");
return;
}
ruleForm.relatedRecommendJson.push({ ...res });
}
const tagsViewStore = useTagsViewStore();
const router = useRouter();
let list = ref<any[]>([
@@ -338,6 +437,7 @@ interface datasForm {
addGroupIndex: any;
}
interface RuleForm {
id: number | string;
name: string;
shortTitle: string;
unitId: string;
@@ -362,9 +462,11 @@ interface RuleForm {
sort: Number;
proGroupVo: any[];
selectSpecInfo: any;
relatedRecommendJson: any[];
}
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive<RuleForm>({
id: '',
//商品名称
name: "",
//商品介绍
@@ -418,6 +520,7 @@ const ruleForm = reactive<RuleForm>({
// 排序值
sort: 1,
selectSpecInfo: {},
relatedRecommendJson: [] // 关联推荐商品
});
const rules = reactive<FormRules<RuleForm>>({
name: [{ required: true, message: "请输入商品名称", trigger: "blur" }],
@@ -915,6 +1018,27 @@ const resetForm = (formEl: FormInstance | undefined) => {
margin-bottom: 20px;
}
.drag-handle {
cursor: move;
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
background: #f0f0f0;
}
/* SortableJS classes */
.sortable-ghost {
opacity: 0.6;
}
.sortable-chosen {
background: #f5f7ff !important;
}
.el-table__body-wrapper tbody tr {
transition: transform 150ms ease, background-color 150ms ease;
}
.showStyle:hover>.buttonstyle {
display: block;
}
@@ -942,4 +1066,26 @@ const resetForm = (formEl: FormInstance | undefined) => {
top: -10px;
z-index: 10;
}
</style>
.column {
flex: 1;
display: flex;
flex-direction: column;
.row {
flex: 1;
margin-bottom: 10px;
}
}
.center {
display: flex;
align-items: center;
gap: 10px;
.tips {
color: #999;
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<FastMenuConfig></FastMenuConfig>
</div>
</template>

View File

@@ -0,0 +1,113 @@
<!-- 客服设置 -->
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100" label-position="right">
<el-form-item label="企业ID" prop="weworkId">
<el-input v-model="form.weworkId" placeholder="请输入企业ID" style="width: 400px;"></el-input>
</el-form-item>
<el-form-item label="接入链接" prop="weworkUrl">
<el-input v-model="form.weworkUrl" placeholder="请输入接入链接" style="width: 400px;"></el-input>
</el-form-item>
<el-form-item>
<div class="detail">
<h3>如何获取企业</h3>
<p>1.登录企业微信管理后台(<el-link type="primary" target="_blank"
href="https://work.weixin.qq.com">work.weixin.qq.com</el-link>)</p>
<p>2.获取企业ID登录后进入我的企业>>企业信息>>企业ID</p>
<el-image :src="imgs[0]" style="width: 600px;height: auto;" :preview-src-list="imgs"
:initial-index="0"></el-image>
<h3>如何获取接入链接</h3>
<p>1. 登录企业微信管理后台(<el-link type="primary" target="_blank"
href="https://work.weixin.qq.com">work.weixin.qq.com</el-link>)
</p>
<p>2.找到微信客服应用路径应用管理>> 应用管理>>应用>>微信客服</p>
<el-image :src="imgs[1]" style="width: 600px;height: auto;" :preview-src-list="imgs"
:initial-index="1"></el-image>
<p>3.进入微信客服找到创建账号按钮点击后进入页面完成创建具体的接待时间接待人员等商家可自行配置</p>
<el-image :src="imgs[2]" style="width: 600px;height: auto;" :preview-src-list="imgs"
:initial-index="2"></el-image>
<p>4.创建成功后再次点击刚刚创建的账号进入后复制接入链接粘贴过来即可特别说明账号可选用已创建的账号</p>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="submitHandle">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import ShopApi from "@/api/account/shop";
const imgs = [
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/06f236d656be479284833a166a5b98c1.png",
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/96f3c5c4c1f940c390eef901bcd11a14.png",
"https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/5dbbd3a7d31c4bef8af57c6679421dd5.png",
];
const formRef = ref();
const loading = ref(false);
const form = ref({
id: '',
weworkId: "",
weworkUrl: "",
});
const rules = {
weworkId: [{ required: true, message: "请输入企业ID", trigger: "blur" }],
weworkUrl: [{ required: true, message: "请输入接入链接", trigger: "blur" }],
};
// 提交表单
function submitHandle() {
formRef.value.validate(async (valid) => {
if (valid) {
try {
loading.value = true;
await ShopApi.edit(form.value);
ElNotification({
title: "成功",
message: "保存成功",
type: "success",
});
} catch (error) {
console.log(error);
}
loading.value = false;
} else {
return false;
}
});
}
// 获取店铺信息
async function getShopInfo() {
try {
const res = await ShopApi.get()
form.value.id = res.id;
form.value.weworkId = res.weworkId;
form.value.weworkUrl = res.weworkUrl;
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getShopInfo();
})
</script>
<style scoped lang="scss">
.detail {
p {
margin-bottom: 10px;
}
.img {
width: 600px;
border-radius: 4px;
}
}
</style>

View File

@@ -3,25 +3,14 @@
<div>
<el-form ref="form" :model="form" :rules="rules" label-width="160px" label-position="left">
<el-form-item label="门店名称" prop="shopName">
<el-input
v-model.trim="form.shopName"
placeholder="请输入门店名称"
style="width: 500px"
></el-input>
<el-input v-model.trim="form.shopName" placeholder="请输入门店名称" style="width: 500px"></el-input>
</el-form-item>
<el-form-item label="连锁店扩展店名">
<el-input
v-model.trim="form.chainName"
placeholder="请输入连锁店扩展店名"
style="width: 500px"
></el-input>
<el-input v-model.trim="form.chainName" placeholder="请输入连锁店扩展店名" style="width: 500px"></el-input>
</el-form-item>
<el-form-item label="门店logo">
<div class="img_box">
<single-image-upload
style="width: 80px; height: 80px"
v-model="form.logo"
></single-image-upload>
<single-image-upload style="width: 80px; height: 80px" v-model="form.logo"></single-image-upload>
</div>
</el-form-item>
<!-- <el-form-item label="门店照片">
@@ -38,22 +27,14 @@
<el-form-item label="门店收款码">
<div class="img_box">
<canvas ref="canvas" id="QRCode_header" style="width: 80px; height: 80px"></canvas>
<el-button
size="small"
plain
v-if="form.paymentQrcode"
@click="downloadCanvas(form.paymentQrcode)"
>
<el-button size="small" plain v-if="form.paymentQrcode" @click="downloadCanvas(form.paymentQrcode)">
下载
</el-button>
</div>
</el-form-item>
<el-form-item label="微信二维码">
<div class="img_box">
<single-image-upload
style="width: 80px; height: 80px"
v-model="form.shopQrcode"
></single-image-upload>
<single-image-upload style="width: 80px; height: 80px" v-model="form.shopQrcode"></single-image-upload>
</div>
</el-form-item>
<!-- <el-form-item label="店铺小程序码">
@@ -84,11 +65,7 @@
</el-radio-group>
</el-form-item> -->
<el-form-item label="联系电话" prop="phone">
<el-input
v-model.trim="form.phone"
placeholder="请输入联系电话"
style="width: 500px"
></el-input>
<el-input v-model.trim="form.phone" placeholder="请输入联系电话" style="width: 500px"></el-input>
</el-form-item>
<!-- <el-form-item label="外卖起送金额">
<el-input-number v-model="form.takeaway_money" placeholder="0.00" controls-position="right"
@@ -114,62 +91,30 @@
<div style="color: #999">准确的定位便于用户导航到店铺</div>
</el-form-item>
<el-form-item label="门店详细地址">
<el-input
type="textarea"
v-model.trim="form.address"
placeholder="请输入门店详细地址"
style="width: 500px"
></el-input>
<el-input type="textarea" v-model.trim="form.address" placeholder="请输入门店详细地址" style="width: 500px"></el-input>
</el-form-item>
<el-form-item label="营业时间">
<div class="u-flex gap-2" style="width: 50%">
<el-select v-model="form.businessStartDay" placeholder="周几开始">
<el-option
:value="item.label"
:label="item.label"
v-for="item in weeks"
:key="item.value"
></el-option>
<el-option :value="item.label" :label="item.label" v-for="item in weeks" :key="item.value"></el-option>
</el-select>
<el-select v-model="form.businessEndDay" placeholder="周几结束">
<el-option
:value="item.label"
:label="item.label"
v-for="item in weeks"
:key="item.value"
></el-option>
<el-option :value="item.label" :label="item.label" v-for="item in weeks" :key="item.value"></el-option>
</el-select>
<el-time-picker
placeholder="起始时间"
v-model="startTime"
:picker-options="{
selectableRange: '00:00:00 - 23:59:59',
format: 'HH:mm',
}"
format="HH:mm"
value-format="HH:mm"
></el-time-picker>
<el-time-picker
placeholder="结束时间"
v-model="endTime"
:picker-options="{
selectableRange: '00:00:00 - 23:59:59',
}"
format="HH:mm"
value-format="HH:mm"
></el-time-picker>
<el-time-picker placeholder="起始时间" v-model="startTime" :picker-options="{
selectableRange: '00:00:00 - 23:59:59',
format: 'HH:mm',
}" format="HH:mm" value-format="HH:mm"></el-time-picker>
<el-time-picker placeholder="结束时间" v-model="endTime" :picker-options="{
selectableRange: '00:00:00 - 23:59:59',
}" format="HH:mm" value-format="HH:mm"></el-time-picker>
</div>
</el-form-item>
<el-form-item label="桌位费/位/元">
<el-input-number :disabled="!!form.isTableFee" v-model="form.tableFee" :min="0" />
<!-- <el-checkbox v-model="form.isTableFee" :label="1">免餐位费</el-checkbox> -->
<el-switch
class="u-m-l-10"
v-model.trim="form.isTableFee"
:active-value="1"
:inactive-value="0"
active-text="免餐位费"
></el-switch>
<el-switch class="u-m-l-10" v-model.trim="form.isTableFee" :active-value="1" :inactive-value="0"
active-text="免餐位费"></el-switch>
</el-form-item>
<!-- <el-form-item label="是否开启8折活动">
<el-switch v-model.trim="form.isOpenYhq" active-value="true" inactive-value="false"></el-switch>
@@ -203,41 +148,24 @@
</el-time-picker>
</el-form-item> -->
<el-form-item label="店铺简介">
<el-input
type="textarea"
v-model.trim="form.detail"
placeholder="请输入店铺简介"
style="width: 500px"
></el-input>
<el-input type="textarea" v-model.trim="form.detail" placeholder="请输入店铺简介" style="width: 500px"></el-input>
</el-form-item>
<el-form-item label="台桌预订短信">
<el-input
type="textarea"
v-model.trim="form.bookingSms"
placeholder="请输入台桌预订短信"
style="width: 500px"
></el-input>
<el-input type="textarea" v-model.trim="form.bookingSms" placeholder="请输入台桌预订短信"
style="width: 500px"></el-input>
</el-form-item>
<el-form-item label="电子围栏" prop="isOrderFence">
<div class="column">
<div class="center" style="display: flex; align-items: center; gap: 14px">
<el-switch
v-model="form.isOrderFence"
:active-value="1"
:inactive-value="0"
></el-switch>
<el-switch v-model="form.isOrderFence" :active-value="1" :inactive-value="0"></el-switch>
<div class="tips" style="font-size: 14px; color: #999">
开启后用户只能在店铺附近xx公里内点餐
</div>
</div>
<div class="center" v-if="form.isOrderFence == 1">
<el-input
v-model="form.orderFenceDistance"
placeholder="请输入"
@input="(e) => (form.orderFenceDistance = filterNumberInput(e))"
style="width: 250px"
input-style="text-align: center;"
>
<el-input v-model="form.orderFenceDistance" placeholder="请输入"
@input="(e) => (form.orderFenceDistance = filterNumberInput(e))" style="width: 250px"
input-style="text-align: center;">
<template #prepend>限制</template>
<template #append>公里</template>
</el-input>
@@ -247,29 +175,24 @@
<el-form-item label="上菜时间(分钟)" prop="isServeTimeControl">
<div class="column">
<div class="center" style="display: flex; align-items: center; gap: 14px">
<el-switch
v-model="form.isServeTimeControl"
:active-value="1"
:inactive-value="0"
></el-switch>
<el-switch v-model="form.isServeTimeControl" :active-value="1" :inactive-value="0"></el-switch>
<div class="tips" style="font-size: 14px; color: #999">
起菜到上菜的时间间隔开启后会有超时提示
</div>
</div>
<div class="center" v-if="form.isServeTimeControl == 1">
<el-input
v-model="form.serveTime"
placeholder="请输入"
@input="(e) => (form.serveTime = filterNumberInput(e))"
style="width: 250px"
input-style="text-align: center;"
>
<el-input v-model="form.serveTime" placeholder="请输入"
@input="(e) => (form.serveTime = filterNumberInput(e))" style="width: 250px"
input-style="text-align: center;">
<template #prepend>限制</template>
<template #append>分钟</template>
</el-input>
</div>
</div>
</el-form-item>
<el-form-item label="数签子功能">
<el-switch v-model="form.isCountStick" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio :value="1">营业中</el-radio>
@@ -285,24 +208,11 @@
</el-form>
</div>
<ChooseAddress ref="refChooseAddress" @choose="chooseAddressConfirm"></ChooseAddress>
<el-dialog
v-model="showUpload"
:close-on-click-modal="false"
append-to-body
width="500px"
@close="showUpload = false"
>
<el-upload
:before-remove="handleBeforeRemove"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
:headers="headers"
:action="qiNiuUploadApi"
:limit="1"
list-type="picture"
class="upload-demo"
>
<el-dialog v-model="showUpload" :close-on-click-modal="false" append-to-body width="500px"
@close="showUpload = false">
<el-upload :before-remove="handleBeforeRemove" :on-success="handleSuccess" :on-error="handleError"
:file-list="fileList" :headers="headers" :action="qiNiuUploadApi" :limit="1" list-type="picture"
class="upload-demo">
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<div style="display: block" class="el-upload__tip">请勿上传违法文件且文件不超过15M</div>
@@ -549,7 +459,7 @@ export default {
handleBeforeRemove(file, fileList) {
for (let i = 0; i < this.files.length; i++) {
if (this.files[i].uid === file.uid) {
crudQiNiu.del([this.files[i].id]).then((res) => {});
crudQiNiu.del([this.files[i].id]).then((res) => { });
return true;
}
}

View File

@@ -6,11 +6,13 @@
<el-tab-pane label="安全设置" name="4"></el-tab-pane>
<el-tab-pane label="跳转小程序" name="6" v-if="shopId == 1"></el-tab-pane>
<el-tab-pane label="通知中心" name="7"></el-tab-pane>
<el-tab-pane label="客服设置" name="8"></el-tab-pane>
</el-tabs>
<shopInfo v-if="activeName == 1" />
<safe v-if="activeName == 4" />
<notifications v-if="activeName == 7" />
<!-- <goxcx v-if="activeName == 6" />-->
<contactSetting v-if="activeName == 8" />
</div>
</div>
</template>
@@ -19,6 +21,7 @@
import shopInfo from "./components/shopInfo.vue";
import safe from "./components/safe.vue";
import notifications from "./components/notifications.vue";
import contactSetting from "./components/contactSetting.vue";
const state = reactive({
activeName: "1",

View File

@@ -4,22 +4,38 @@
<template v-if="carts.list && carts.list.length >= 1">
<!-- 当前购物车 -->
<div v-for="(item, index) in carts.list" :key="index">
<carts-item :item="item" :useVipPrice="carts.useVipPrice" @changeNumber="changeNumber"
:selCart="carts.selCart" @itemClick="itemClick(item)" @editNote="editNote"></carts-item>
<carts-item
:item="item"
:useVipPrice="carts.useVipPrice"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
</div>
</template>
<!-- 赠菜 -->
<div class="cart-title" v-if="carts.giftList.length > 0"><span>以下是优惠菜品</span></div>
<div v-for="(item, index) in carts.giftList" :key="index">
<carts-item :item="item" @changeNumber="changeNumber" :useVipPrice="carts.useVipPrice" :selCart="carts.selCart"
@itemClick="itemClick(item)" @editNote="editNote"></carts-item>
<carts-item
:item="item"
@changeNumber="changeNumber"
:useVipPrice="carts.useVipPrice"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
</div>
<el-empty :image-size="60" v-if="carts.isEmpty" description="点餐列表为空" />
<!-- 打包费 -->
<template v-if="carts.packNum > 0">
<div class="cart-title"><span>打包费</span></div>
<extra-fee name="打包费" :number="carts.packNum" :price="carts.orderCostSummary.packFee"></extra-fee>
<extra-fee
name="打包费"
:number="carts.packNum"
:price="carts.orderCostSummary.packFee"
></extra-fee>
</template>
<!-- 餐位费 -->
<template v-if="perpole >= 1 && carts.dinnerType == 'dine-in'">
@@ -40,9 +56,17 @@
</div>
<div v-for="(detaiItem, index) in item" :key="index">
<carts-item :useVipPrice="carts.useVipPrice" :canChangeNumber="false" isOld :dinerType="dinerType"
:item="detaiItem" @changeNumber="changeNumber" :selCart="carts.selCart" @itemClick="itemClick(detaiItem)"
@editNote="editNote"></carts-item>
<carts-item
:useVipPrice="carts.useVipPrice"
:canChangeNumber="false"
isOld
:dinerType="dinerType"
:item="detaiItem"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(detaiItem)"
@editNote="editNote"
></carts-item>
</div>
</template>
@@ -50,9 +74,14 @@
</div>
<div class="bottom">
<div class="u-flex u-row-right">
<el-tooltip placement="top" effect="light" popper-class="youhui-tips" :popper-options="{
'background-color': '#fff',
}">
<el-tooltip
placement="top"
effect="light"
popper-class="youhui-tips"
:popper-options="{
'background-color': '#fff',
}"
>
<template #content>
<div class="u-flex color-000 u-font-14 u-row-between">
<span class="font-bold">会员优惠</span>
@@ -74,20 +103,36 @@
</div>
<div class="u-flex u-row-between">
<el-link type="primary">打印制作单</el-link>
<!-- <el-link type="primary">打印制作单</el-link> -->
<div></div>
<div>
<span class="totalNumber">{{ customTruncateToTwoDecimals(carts.totalNumber) }}</span>
<span class="totalPrice">{{ customTruncateToTwoDecimals(carts.payMoney) }}</span>
</div>
</div>
<div class="btn-group" v-if="isXianFuKuan">
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('wx-aiplay')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('wx-aiplay')"
>
微信/支付宝
</el-button>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('cash')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('cash')"
>
现金
</el-button>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('more-pay')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('more-pay')"
>
更多支付
</el-button>
</div>
@@ -100,20 +145,31 @@
<el-button type="primary" size="large" :disabled="disabledMorePay" @click="createOrder('to-pay')">
去结账
</el-button> -->
<el-button type="primary" size="large"
<el-button
type="primary"
size="large"
:disabled="carts.list.length == 0 || carts.oldOrder.detailMap.length == 0"
@click="createOrder('only-create')">
@click="createOrder('only-create')"
>
仅下单
</el-button>
<el-button type="primary" size="large"
<el-button
type="primary"
size="large"
:disabled="carts.list.length == 0 && isEmptyObject(carts.oldOrder.detailMap)"
@click="createOrder('to-pay')">
@click="createOrder('to-pay')"
>
去结账
</el-button>
</template>
<template v-else>
<el-button type="default" size="large" @click="hideOrder()">加菜/返回</el-button>
<el-button type="primary" size="large" :disabled="!carts.isLinkFinshed" @click="createOrder('to-pay')">
<el-button
type="primary"
size="large"
:disabled="!carts.isLinkFinshed"
@click="createOrder('to-pay')"
>
立即支付
</el-button>
</template>
@@ -131,7 +187,7 @@ import { useUserStore } from "@/store/modules/user";
function isEmptyObject(obj) {
// 步骤1排除null和非对象类型
if (obj === null || typeof obj !== 'object') {
if (obj === null || typeof obj !== "object") {
return false;
}
// 步骤2排除数组数组也是对象需单独判断
@@ -313,4 +369,4 @@ defineExpose({
margin-left: 10px;
}
}
</style>
</style>

View File

@@ -13,7 +13,7 @@
</p>
<p class="u-font-14 color-666 u-m-t-10">
<span class="money">余额{{ carts.vipUser.amount }}</span>
<span class="score u-m-l-10">积分{{ carts.vipUser.accountPoints }}</span>
<span class="score u-m-l-10">积分{{ carts.vipUser.pointBalance }}</span>
</p>
</div>
</div>
@@ -46,7 +46,8 @@
</el-radio-group>
<el-input-number class="u-m-l-10" v-if="score.sel != -1" v-model="usePointsNumber" step-strictly
:step="pointsRes.equivalentPoints" placeholder="请输入积分抵扣数量" :min="pointsRes.minDeductionPoints"
:max="pointsRes.maxUsablePoints" @change="pointsToMoney"></el-input-number>
:max="pointsRes.maxUsablePoints" :disabled="!pointsRes.usable"
@change="pointsToMoney"></el-input-number>
</div>
<p class="u-font-14 color-666 u-m-t-10" v-if="pointsRes.unusableReason && !pointsRes.usable">
<span class="color-red">*</span>
@@ -398,8 +399,16 @@ watch(
);
//002-获取订单可用积分及抵扣金额(支付页面使用)
const pointsRes = ref({ usable: true, maxUsablePoints: 0, minDeductionPoints: 0 });
const usePointsNumber = ref(0);
const pointsRes = ref({
usable: false,
equivalentPoints: 0, // 积分换算比例 eg: 20 积分 = 1 元
maxDeductionRatio: 0, // 下单最高抵扣比例(小数)
minPaymentAmount: 0, // 下单实付抵扣门槛(元)
maxUsablePoints: 0,
minDeductionPoints: 0,
unusableReason: "",
});
const usePointsNumber = ref(0); // 输入的积分数量(用户填写)
//积分可抵扣最大金额
const scoreMaxMoney = computed(() => {
return (
@@ -421,22 +430,129 @@ async function pointsInit() {
if (!carts.vipUser.id) {
return;
}
const res = await PointsApi.calcOrderUsablePoints({
const { pointsConfig, pointsUser } = await PointsApi.calcOrderUsablePoints({
shopUserId: carts.vipUser.id,
orderAmount: scoreMaxMoney.value,
});
// 不修改 accountPoints为余额将积分保存到 pointBalance 字段
carts.vipUser.pointBalance = pointsUser && pointsUser.id ? pointsUser.pointBalance : 0;
// 保险取值
const eq = pointsConfig?.equivalentPoints || 0;
const maxRatio = pointsConfig?.maxDeductionRatio || 0;
const minPay = pointsConfig?.minPaymentAmount || 0;
// 计算当前订单可抵扣金额上限(元)
let finalPay = Number(carts.orderCostSummary.finalPayAmount) || 0;
const res = {
usable: true,
equivalentPoints: eq,
maxDeductionRatio: maxRatio,
minPaymentAmount: minPay,
maxUsablePoints: 0,
minDeductionPoints: pointsConfig?.minDeductionPoints || eq,
unusableReason: "",
};
// 如果订单实付低于最小使用门槛,则不可用
if (finalPay <= 0 || (minPay > 0 && finalPay < minPay)) {
res.usable = false;
res.unusableReason = `订单实付金额低于 ${minPay} 元,无法使用积分抵扣`;
} else if (eq <= 0) {
res.usable = false;
res.unusableReason = `积分换算比例配置错误,无法使用积分抵扣`;
} else {
// 计算基于比例限制的最大抵扣金额(元)
let maxByRatio = finalPay * maxRatio;
// 保证抵扣后剩余金额 >= minPaymentAmount
if (minPay > 0) {
const allowed = finalPay - minPay;
if (allowed <= 0) {
res.usable = false;
res.unusableReason = `抵扣后实付金额必须大于等于 ${minPay} 元,当前不可使用积分`;
} else {
maxByRatio = Math.min(maxByRatio, allowed);
}
}
if (res.usable) {
// 可用积分上限(向下取整为 eq 的倍数)
const maxByMoney = Math.floor(maxByRatio * eq);
const userPoints = carts.vipUser.pointBalance || 0;
res.maxUsablePoints = Math.min(userPoints, maxByMoney);
// 最小抵扣积分为配置值或等于换算比
res.minDeductionPoints = pointsConfig?.minDeductionPoints || eq;
if (res.maxUsablePoints < res.minDeductionPoints) {
res.usable = false;
res.unusableReason = `可用积分不足,至少需要 ${res.minDeductionPoints} 积分才可抵扣`;
}
}
}
pointsRes.value = res;
carts.pointDeductionRule.pointsPerYuan = res.equivalentPoints;
if (score.sel == -1) {
// 未选择使用积分
return res;
}
// 如果可用则默认填充可用最大值,否则清零
usePointsNumber.value = res.usable ? res.maxUsablePoints : 0;
if (!res.usable) score.sel = -1;
return res;
}
// 将输入的积分数转换为抵扣金额并写回 carts.orderCostSummary
function pointsToMoney(val) {
const cfg = pointsRes.value;
if (!cfg.usable || cfg.equivalentPoints <= 0) {
usePointsNumber.value = 0;
carts.orderCostSummary.pointUsed = 0;
carts.orderCostSummary.pointDeductionAmount = 0;
return;
}
usePointsNumber.value = res.usable ? res.maxUsablePoints : 0;
if (res.usable) {
} else {
score.sel = -1;
// 确保为整数积分
let pts = parseInt(usePointsNumber.value || 0, 10);
if (isNaN(pts) || pts <= 0) {
pts = 0;
}
return res;
// 限制最大值
if (cfg.maxUsablePoints && pts > cfg.maxUsablePoints) {
pts = cfg.maxUsablePoints;
}
// 计算抵扣金额(元),向下保留两位
const money = new BigNumber(pts).div(cfg.equivalentPoints).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber();
// 再次校验不超过允许的最大抵扣金额(基于比例或门槛)
let finalPay = Number(carts.orderCostSummary.finalPayAmount) || 0;
let maxByRatio = finalPay * cfg.maxDeductionRatio;
if (cfg.minPaymentAmount > 0) {
const allowed = finalPay - cfg.minPaymentAmount;
if (allowed <= 0) {
usePointsNumber.value = 0;
carts.orderCostSummary.pointUsed = 0;
carts.orderCostSummary.pointDeductionAmount = 0;
return;
}
maxByRatio = Math.min(maxByRatio, allowed);
}
const maxAllowedMoney = new BigNumber(maxByRatio).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber();
if (money > maxAllowedMoney) {
// 调整积分到允许的最大金额对应的积分
const allowedPts = Math.floor(maxAllowedMoney * cfg.equivalentPoints);
pts = Math.min(allowedPts, cfg.maxUsablePoints);
}
usePointsNumber.value = pts;
const finalMoney = new BigNumber(pts).div(cfg.equivalentPoints).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber();
carts.orderCostSummary.pointUsed = pts;
carts.orderCostSummary.pointDeductionAmount = finalMoney;
}
const emits = defineEmits(["chooseUser", "paysuccess"]);
@@ -693,6 +809,10 @@ watch(
onMounted(() => {
carts.payParamsInit();
getPaytype();
if (carts.vipUser.id) {
pointsInit();
}
});
defineExpose({
nowPayClick,