feat(goods, order): 完善预约与订单详情功能

- 预约规则区域的说明中补充可预约时段
- 价格区域的标题和说明改为根据商品类型动态展示
- 优化订单详情中配送信息的展示,仅在实物订单中显示
- 核销记录新增搜索功能,支持按商品名称和核销人筛选
- 精简订单列表搜索表单,移除不必要的字段,界面更简洁
This commit is contained in:
pikachu1995@126.com
2026-10-02 15:28:25 +08:00
parent 326ccea1b4
commit 4c232284f8
5 changed files with 130 additions and 96 deletions

View File

@@ -159,7 +159,7 @@
</el-form-item>
</div>
<!-- 时段划分挂到价格库存区,单实例避免 v-model 互相覆盖 -->
<!-- 时段划分挂到预约规则区,单实例避免 v-model 互相覆盖 -->
<Teleport
v-if="!divisionTarget || divisionReady"
:to="divisionTarget || 'body'"

View File

@@ -225,7 +225,7 @@
<span class="goods-section-index">{{ sectionNo("appointment") }}</span>
<div class="goods-section-hd-text">
<h3>预约规则</h3>
<p>服务模式、可约日期与预约政策</p>
<p>服务模式、可约日期、时段与预约政策</p>
</div>
</div>
<div class="goods-section-bd">
@@ -237,19 +237,23 @@
:sku-info="skuInfo"
division-target="#appointment-slot-mount"
/>
<div class="goods-submodule">
<div class="goods-submodule-hd">时段划分</div>
<div id="appointment-slot-mount"></div>
</div>
</div>
</div>
<div id="goods-section-price" class="goods-section" data-section="price">
<div class="goods-section-hd">
<span class="goods-section-index">{{ sectionNo("price") }}</span>
<div class="goods-section-hd-text">
<h3>价格库存</h3>
<p>销售模式、时段、规格与库存</p>
<h3>{{ priceSectionTitle }}</h3>
<p>{{ priceSectionDesc }}</p>
</div>
</div>
<div class="goods-section-bd">
<div class="form-item-view">
<el-form-item class="form-item-view-el" label="销售模式" prop="salesModel">
<el-form-item v-if="showSalesModel" class="form-item-view-el" label="销售模式" prop="salesModel">
<el-radio-group
v-if="!isVirtualLikeGoods"
v-model="baseInfoForm.salesModel"
@@ -336,11 +340,14 @@
</div>
</div>
</el-form-item>
<div v-if="isAppointmentGoods" class="goods-submodule">
<div class="goods-submodule-hd">时段划分</div>
<!-- AppointmentPublishPanel Teleport 挂载点 -->
<div id="appointment-slot-mount"></div>
</div>
<el-alert
v-if="isECouponGoods"
class="mb_10"
type="info"
show-icon
:closable="false"
title="卡密库存不在这里填写。商品保存后,到商品列表的「卡池管理」导入卡密。"
/>
<div class="layout" style="width: 100%">
<el-collapse v-model="open_panel">
<el-collapse-item title="自定义规格项" name="1">
@@ -569,7 +576,7 @@
<span class="goods-section-index">{{ sectionNo("other") }}</span>
<div class="goods-section-hd-text">
<h3>其他信息</h3>
<p>上架状态、运费模板与店内分类</p>
<p>{{ otherSectionDesc }}</p>
</div>
</div>
<div class="goods-section-bd">
@@ -715,6 +722,24 @@ export default {
needsLogistics() {
return this.baseInfoForm.goodsType === "PHYSICAL_GOODS";
},
/** 预约、卡券固定零售,不展示销售模式切换 */
showSalesModel() {
return !this.isECouponGoods && !this.isAppointmentGoods;
},
priceSectionTitle() {
if (this.isAppointmentGoods) return "规格与可约数量";
if (this.isECouponGoods) return "规格价格";
return "价格库存";
},
priceSectionDesc() {
if (this.isAppointmentGoods) return "规格、售价与各时段可预约数量";
if (this.isECouponGoods) return "规格与售价,库存由卡池管理";
return "销售模式、规格与库存";
},
otherSectionDesc() {
if (this.needsLogistics) return "上架状态、运费模板与店内分类";
return "上架状态与店内分类";
},
sectionTabs() {
const tabs = [
{ name: "basic", label: "基础信息" },
@@ -1568,7 +1593,11 @@ export default {
this.wholesaleData = response.result.wholesaleList;
}
if (response.result.salesModel === "WHOLESALE") {
if (this.isECouponGoods || this.isAppointmentGoods) {
this.baseInfoForm.salesModel = "RETAIL";
}
if (response.result.salesModel === "WHOLESALE" && this.showSalesModel) {
this.baseInfoForm.weight = response.result.skuList[0].weight;
}
@@ -2228,11 +2257,11 @@ export default {
}
if (this.isAppointmentGoods) {
pushData.push({
title: "预约规则",
title: "可预约数量",
slot: "appointmentCapacity",
minWidth: 160,
});
} else {
} else if (!this.isECouponGoods) {
pushData.push({
title: "库存",
slot: "quantity",
@@ -2569,7 +2598,7 @@ export default {
}
if (!this.$refs.appointmentPanel?.validateDivision()) {
this.submitLoading = false;
this.jumpToTab("price");
this.jumpToTab("appointment");
return;
}
Object.assign(submit, this.$refs.appointmentPanel.buildSubmitPayload());

View File

@@ -4,9 +4,9 @@
<el-card style="height: 60px">
<div>
<el-button v-if="allowOperation.editPrice" type="primary" @click="modifyPrice">调整价格</el-button>
<el-button v-if="allowOperation.editConsignee" type="primary" @click="editAddress">修改收货地址</el-button>
<el-button v-if="showDelivery && allowOperation.editConsignee" type="primary" @click="editAddress">修改收货地址</el-button>
<el-button
v-if="allowOperation.showLogistics || orderPackages.length > 0"
v-if="showDelivery && (allowOperation.showLogistics || orderPackages.length > 0)"
type="primary"
@click="checkLogistics"
>查看物流</el-button>
@@ -14,22 +14,22 @@
<el-button v-if="canOrderTake" type="primary" @click="orderTake">
{{ isAppointmentOrder ? "预约核销" : "订单核销" }}
</el-button>
<el-button v-if="canShip" type="primary" @click="openFulfill()">发货</el-button>
<el-button v-if="showDelivery && canShip" type="primary" @click="openFulfill()">发货</el-button>
<el-button type="primary" @click="modifyRemark">添加备注</el-button>
<el-button
v-if="allowOperation.showLogistics && logisticsType === 'SHUNFENG'"
v-if="showDelivery && allowOperation.showLogistics && logisticsType === 'SHUNFENG'"
type="primary"
plain
@click="sfPrint"
>下载面单</el-button>
<el-button
v-if="canShip && logisticsType !== 'SHUNFENG'"
v-if="showDelivery && canShip && logisticsType !== 'SHUNFENG'"
type="primary"
plain
@click="toPrint"
>打印电子面单</el-button>
<el-button
v-if="!isNonPhysicalOrder"
v-if="showDelivery"
type="primary"
plain
style="float: right"
@@ -68,7 +68,7 @@
{{ orderInfo.order.createTime }}
</div>
</div>
<div class="div-item" v-if="orderInfo.order.verificationCode && !isAppointmentOrder">
<div class="div-item" v-if="orderInfo.order.verificationCode && !isAppointmentOrder && !isECouponOrder">
<div class="div-item-left">核验码:</div>
<div class="div-item-right">{{ orderInfo.order.verificationCode }}</div>
</div>
@@ -96,7 +96,7 @@
</div>
</div>
</div>
<div class="order-info-col">
<div class="order-info-col" v-if="showInvoice">
<div class="div-item" v-if="orderInfo.order.needReceipt == false">
<div class="div-item-left">发票信息:</div>
<div class="div-item-right">暂无发票信息</div>
@@ -148,7 +148,7 @@
</div>
</div>
<div class="order-info-col">
<div class="div-item" v-if="!isECouponOrder && !isAppointmentOrder && orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
<div class="div-item" v-if="showDelivery && orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
<div class="div-item-left">收货信息:</div>
<div class="div-item-right">
{{ orderInfo.order.consigneeName }}
@@ -157,7 +157,7 @@
{{ orderInfo.order.consigneeDetail }}
</div>
</div>
<div class="div-item" v-if="orderInfo.order.deliveryMethod == 'SELF_PICK_UP'">
<div class="div-item" v-if="showDelivery && orderInfo.order.deliveryMethod == 'SELF_PICK_UP'">
<div class="div-item-left">自提信息:</div>
<div class="div-item-right">
{{ orderInfo.order.storeAddressPath }}
@@ -176,61 +176,7 @@
<div class="div-item-right">{{ orderInfo.order.remark }}</div>
</div>
<!-- <div class="div-item" v-if="orderInfo.order.needReceipt == false">
<div class="div-item-left">发票信息:</div>
<div class="div-item-right">暂无发票信息</div>
</div> -->
<!-- <div class="div-item" v-if="orderInfo.order.needReceipt == true">
<div class="div-item-left">发票抬头:</div>
<div class="div-item-right">
{{
orderInfo.receipt && orderInfo.receipt.receiptTitle ? orderInfo.receipt.receiptTitle : "暂无"
}}
</div>
</div>
<div class="div-item"
v-if="orderInfo.order.needReceipt == true && orderInfo.receipt && orderInfo.receipt.taxpayerId">
<div class="div-item-left">发票税号:</div>
<div class="div-item-right">
{{ orderInfo.receipt && orderInfo.receipt.taxpayerId ? orderInfo.receipt.taxpayerId : "暂无" }}
</div>
</div>
<div class="div-item" v-if="orderInfo.order.needReceipt == true">
<div class="div-item-left">发票内容:</div>
<div class="div-item-right">
{{
orderInfo.receipt && orderInfo.receipt.receiptContent
? orderInfo.receipt.receiptContent
: "暂无"
}}
</div>
</div>
<div class="div-item" v-if="orderInfo.order.needReceipt == true">
<div class="div-item-left">发票金额:</div>
<div class="div-item-right">
{{
$filters.unitPrice(
orderInfo.receipt && orderInfo.receipt.receiptPrice
? orderInfo.receipt.receiptPrice
: "暂无",
"¥"
)
}}
</div>
</div>
<div class="div-item" v-if="orderInfo.order.needReceipt == true">
<div class="div-item-left">是否开票:</div>
<div class="div-item-right">
{{ orderInfo.receipt ? (orderInfo.receipt.receiptStatus == 0 ? "未开" : "已开") : "空" }}
</div>
</div> -->
<div class="div-item" v-if="!isNonPhysicalOrder">
<div class="div-item" v-if="showDelivery">
<div class="div-item-left">配送方式:</div>
<div class="div-item-right">
{{ orderInfo.deliveryMethodValue }}
@@ -239,7 +185,7 @@
</div>
</div>
</el-card>
<el-card v-if="orderPackages.length" class="mt_10 order-goods-card">
<el-card v-if="showDelivery && orderPackages.length" class="mt_10 order-goods-card">
<el-divider content-position="left">发货记录</el-divider>
<el-table :data="orderPackages" border style="width: 100%">
<el-table-column label="发货方式" width="120">
@@ -434,7 +380,7 @@
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}:</a><span class="op-split">|</span>
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ $filters.unitPrice(item.discountPrice) }}</span>
</li> -->
<li v-if="!isECouponOrder">
<li v-if="showDelivery">
<span class="label">运费:</span>
<span class="txt">{{
$filters.unitPrice(orderInfo.order.freightPrice, "¥")
@@ -1098,6 +1044,17 @@ export default {
isNonPhysicalOrder() {
return this.isVirtualOrder || this.isECouponOrder || this.isAppointmentOrder;
},
/** 实物订单才展示收货、配送、运费和发货记录 */
showDelivery() {
return !this.isNonPhysicalOrder;
},
/** 实物订单始终展示发票;卡密、预约、虚拟仅在买家索取发票时展示 */
showInvoice() {
const order = this.orderInfo && this.orderInfo.order;
if (!order) return false;
if (order.needReceipt == true) return true;
return this.showDelivery;
},
isAppointmentOrder() {
const t = this.orderInfo?.order?.orderType;
// 详情未回时用路由 query 兜底

View File

@@ -11,16 +11,13 @@
<el-form-item label="关键字" prop="keywords">
<el-input
v-model="searchForm.keywords"
placeholder="商品名称/收货人/手机号/店铺名称"
placeholder="商品名称/收货人/手机号"
clearable
/>
</el-form-item>
<el-form-item label="订单号" prop="orderSn">
<el-input v-model="searchForm.orderSn" placeholder="请输入订单号" clearable />
</el-form-item>
<el-form-item label="商品名称" prop="goodsName">
<el-input v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable />
</el-form-item>
<el-form-item label="商品类型" prop="goodsType">
<el-select v-model="searchForm.goodsType" placeholder="请选择商品类型" clearable>
<el-option label="实物商品" value="PHYSICAL_GOODS" />
@@ -29,16 +26,12 @@
<el-option label="预约商品" value="APPOINTMENT_GOODS" />
</el-select>
</el-form-item>
<el-form-item label="收货人" prop="shipName">
<el-input v-model="searchForm.shipName" placeholder="请输入收货人姓名" clearable />
</el-form-item>
<el-form-item label="订单类型" prop="orderType">
<el-select v-model="searchForm.orderPromotionType" placeholder="请选择" clearable>
<el-option label="普通订单" value="NORMAL" />
<el-option label="拼团订单" value="PINTUAN" />
<el-option label="赠品订单" value="GIFT" />
<el-option label="积分订单" value="POINTS" />
<el-option label="砍价订单" value="KANJIA" />
</el-select>
</el-form-item>
<el-form-item label="支付方式" prop="paymentMethod">
@@ -205,9 +198,7 @@ export default {
orderType: "",
orderSn: "",
keywords: "",
goodsName: "",
goodsType: "",
shipName: "",
orderStatus: "",
paymentMethod: "",
orderPromotionType: "",
@@ -460,8 +451,7 @@ export default {
this.getDataList();
},
getOrderNumData() {
// orderNum 接口仅查 li_order;keywords/goodsName 会引用 oi 字段。goodsType 用 EXISTS,可以带上。
const { orderStatus, keywords, goodsName, ...searchParams } = this.searchForm;
const { orderStatus, pageNumber, pageSize, sort, order, ...searchParams } = this.searchForm;
API_Order.getOrderNum(searchParams)
.then((res) => {
if (res.success) {

View File

@@ -20,6 +20,37 @@
</el-form>
</el-tab-pane>
<el-tab-pane label="核销记录" name="records">
<el-form class="record-search" @submit.prevent @keyup.enter="searchRecords">
<el-form-item label="商品名称">
<el-input
v-model="recordQuery.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="核销员">
<el-input
v-model="recordQuery.verifierName"
placeholder="请输入核销员"
clearable
style="width: 160px"
/>
</el-form-item>
<el-form-item label="核销时间">
<el-date-picker
v-model="verifiedRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchRecords">搜索</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="records" border style="width: 100%">
<el-table-column prop="code" label="核销码" min-width="120" />
<el-table-column prop="orderSn" label="订单编号" min-width="200" show-overflow-tooltip />
@@ -104,6 +135,11 @@ export default {
total: 0,
pageNumber: 1,
pageSize: 20,
verifiedRange: null,
recordQuery: {
goodsName: "",
verifierName: "",
},
};
},
methods: {
@@ -175,12 +211,24 @@ export default {
this.pageNumber = 1;
this.loadRecords();
},
searchRecords() {
this.pageNumber = 1;
this.loadRecords();
},
loadRecords() {
this.loading = true;
getVerificationRecords({
const range = this.verifiedRange;
const params = {
pageNumber: this.pageNumber,
pageSize: this.pageSize,
})
goodsName: (this.recordQuery.goodsName || "").trim(),
verifierName: (this.recordQuery.verifierName || "").trim(),
};
if (range && range.length === 2) {
params.startTime = range[0];
params.endTime = range[1];
}
getVerificationRecords(params)
.then((res) => {
this.loading = false;
if (res.success) {
@@ -203,6 +251,16 @@ export default {
gap: 16px;
padding: 12px 0 4px;
}
.record-search {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
margin-bottom: 12px;
}
.record-search :deep(.el-form-item) {
margin-bottom: 0;
}
.preview-meta {
display: flex;
gap: 24px;