mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-09-21 20:32:03 +08:00
feat(appointment): 预约商品发布改为可视化配置,并接入表单设计器
商家端补齐预约规则、时段库存与系统表单设计;运营端增加预约工单与订单设置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24
manager/src/api/appointment.js
Normal file
24
manager/src/api/appointment.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 运营端预约服务 API
|
||||
*/
|
||||
import { getRequest, postRequestWithNoForm, putRequestWithNoForm } from "@/libs/axios";
|
||||
|
||||
export const getManagerWorkOrderPage = (params) =>
|
||||
getRequest("/appointment/work-orders", params);
|
||||
|
||||
export const getManagerRescheduleLogs = (params) =>
|
||||
getRequest("/appointment/reschedule-logs", params);
|
||||
|
||||
export const getAppointmentOrderSettings = () => getRequest("/appointment/settings");
|
||||
|
||||
export const saveAppointmentOrderSettings = (params) =>
|
||||
putRequestWithNoForm("/appointment/settings", params);
|
||||
|
||||
export const platformRefund = (orderSn, params) =>
|
||||
postRequestWithNoForm(`/appointment/orders/${orderSn}/platform-refund`, params);
|
||||
|
||||
export const noShowRefund = (orderSn, params) =>
|
||||
postRequestWithNoForm(`/appointment/orders/${orderSn}/no-show-refund`, params);
|
||||
|
||||
export const getAppointmentOrderDetail = (orderSn) =>
|
||||
getRequest(`/appointment/orders/${orderSn}`);
|
||||
@@ -5,14 +5,20 @@
|
||||
* @date 2026-08-02
|
||||
*/
|
||||
export const E_COUPON_GOODS_TYPE = "E_COUPON";
|
||||
export const APPOINTMENT_GOODS_TYPE = "APPOINTMENT_GOODS";
|
||||
|
||||
export function isECoupon(goodsType) {
|
||||
return goodsType === E_COUPON_GOODS_TYPE;
|
||||
}
|
||||
|
||||
export function isAppointmentGoods(goodsType) {
|
||||
return goodsType === APPOINTMENT_GOODS_TYPE;
|
||||
}
|
||||
|
||||
export function goodsTypeLabel(goodsType) {
|
||||
const map = {
|
||||
E_COUPON: "电子卡券",
|
||||
APPOINTMENT_GOODS: "预约商品",
|
||||
VIRTUAL_GOODS: "虚拟商品",
|
||||
PHYSICAL_GOODS: "实物商品",
|
||||
};
|
||||
@@ -21,6 +27,7 @@ export function goodsTypeLabel(goodsType) {
|
||||
|
||||
export function goodsTypeTagType(goodsType) {
|
||||
if (goodsType === E_COUPON_GOODS_TYPE) return "warning";
|
||||
if (goodsType === APPOINTMENT_GOODS_TYPE) return "success";
|
||||
if (goodsType === "VIRTUAL_GOODS") return "info";
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -128,6 +128,18 @@ export const otherRouter = {
|
||||
name: "order-detail",
|
||||
component: () => import("@/views/order/order/orderDetail.vue")
|
||||
},
|
||||
{
|
||||
path: "appointment/work-orders",
|
||||
title: "预约工单",
|
||||
name: "manager-appointment-work-orders",
|
||||
component: () => import("@/views/appointment/workOrderList.vue")
|
||||
},
|
||||
{
|
||||
path: "appointment/order-settings",
|
||||
title: "预约订单设置",
|
||||
name: "manager-appointment-order-settings",
|
||||
component: () => import("@/views/appointment/orderSettings.vue")
|
||||
},
|
||||
{
|
||||
path: "after-order-detail",
|
||||
title: "售后单详情",
|
||||
|
||||
76
manager/src/views/appointment/orderSettings.vue
Normal file
76
manager/src/views/appointment/orderSettings.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form :model="form" label-width="220px" style="max-width: 760px" v-loading="loading">
|
||||
<el-form-item label="服务开始前提醒买家(小时)">
|
||||
<el-input-number v-model="form.serviceStartRemindMemberHours" :min="0" :max="168" />
|
||||
<span class="hint">0 表示关闭</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="逾期未核销自动完结(小时)">
|
||||
<el-input-number v-model="form.inStoreNoShowAutoCompleteHours" :min="0" :max="720" />
|
||||
<span class="hint">0 表示关闭</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||||
<el-button @click="loadData">刷新</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppointmentOrderSettings, saveAppointmentOrderSettings } from "@/api/appointment";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
export default {
|
||||
name: "appointmentOrderSettings",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
form: {
|
||||
serviceStartRemindMemberHours: 2,
|
||||
inStoreNoShowAutoCompleteHours: 24,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
getAppointmentOrderSettings()
|
||||
.then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.form = { ...this.form, ...res.result };
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleSave() {
|
||||
this.saving = true;
|
||||
saveAppointmentOrderSettings(this.form)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("保存成功");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hint {
|
||||
margin-left: 12px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
80
manager/src/views/appointment/workOrderList.vue
Normal file
80
manager/src/views/appointment/workOrderList.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<el-card>
|
||||
<el-form :model="searchForm" inline @keyup.enter="handleSearch">
|
||||
<el-form-item label="订单编号">
|
||||
<el-input v-model="searchForm.orderSn" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工单编号">
|
||||
<el-input v-model="searchForm.workOrderSn" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="mt_10">
|
||||
<el-table v-loading="loading" :data="data" border>
|
||||
<el-table-column prop="workOrderSn" label="工单编号" min-width="180" />
|
||||
<el-table-column prop="orderSn" label="订单编号" min-width="180" />
|
||||
<el-table-column prop="storeName" label="店铺" min-width="140" />
|
||||
<el-table-column label="服务方式" width="100">
|
||||
<template #default="{ row }">{{ row.serviceMode === 'ON_SITE' ? '上门' : '到店' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="appointmentTime" label="预约时间" min-width="170" />
|
||||
<el-table-column prop="serviceStatus" label="状态" width="120" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="mt_10"
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="getData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getManagerWorkOrderPage } from "@/api/appointment";
|
||||
|
||||
export default {
|
||||
name: "managerAppointmentWorkOrders",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
orderSn: "",
|
||||
workOrderSn: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
getManagerWorkOrderPage(this.searchForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.data = res.result?.records || [];
|
||||
this.total = res.result?.total || 0;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -55,6 +55,7 @@
|
||||
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
||||
<!-- E_COUPON:卡密商品;平台无卡池管理权限(S-04) -->
|
||||
<el-option label="电子卡券" value="E_COUPON" />
|
||||
<el-option label="预约商品" value="APPOINTMENT_GOODS" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品分组" prop="groupId">
|
||||
@@ -388,6 +389,7 @@ export default {
|
||||
if (v === "PHYSICAL_GOODS") return "实物商品";
|
||||
if (v === "VIRTUAL_GOODS") return "虚拟商品";
|
||||
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
|
||||
if (v === "APPOINTMENT_GOODS") return "预约商品";
|
||||
return v || "—";
|
||||
},
|
||||
marketEnableText(v) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<el-button v-if="allowOperation.editPrice" @click="modifyPrice">调整价格</el-button>
|
||||
<el-button v-if="allowOperation.editConsignee" plain type="primary" @click="editAddress">修改收货地址</el-button>
|
||||
<el-button v-if="allowOperation.cancel" plain type="warning" @click="orderCancel">订单取消</el-button>
|
||||
<el-button v-if="isAppointmentOrder && canPlatformRefund" plain type="danger" @click="openPlatformRefund">平台退款</el-button>
|
||||
<el-button v-if="isAppointmentOrder && canNoShowRefund" plain type="danger" @click="openNoShowRefund">爽约退款</el-button>
|
||||
<el-button v-if="orderInfo.order.orderStatus === 'UNPAID'" type="primary" @click="confirmPrice">收款</el-button>
|
||||
<el-button plain @click="orderLog">订单日志</el-button>
|
||||
<el-button v-if="!isNonPhysicalOrder" plain type="primary" style="float:right;" @click="printOrder">打印发货单</el-button>
|
||||
@@ -37,6 +39,20 @@
|
||||
{{ orderInfo.order.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="div-item" v-if="isAppointmentOrder && appointmentSlotText">
|
||||
<div class="div-item-left">预约时段:</div>
|
||||
<div class="div-item-right">{{ appointmentSlotText }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="div-item"
|
||||
v-for="(rows, gi) in appointmentFormGroups"
|
||||
:key="'form-' + gi"
|
||||
>
|
||||
<div class="div-item-left">{{ appointmentFormGroups.length > 1 ? `补充信息${gi + 1}:` : '补充信息:' }}</div>
|
||||
<div class="div-item-right">
|
||||
<div v-for="row in rows" :key="row.fieldKey">{{ row.fieldLabel }}:{{ row.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 30%; float: left; margin-left: 20px">
|
||||
<div class="div-item" v-if="orderInfo.order.needReceipt == false">
|
||||
@@ -463,11 +479,48 @@
|
||||
|
||||
|
||||
<multipleMap ref="map" @callback="selectedRegion" />
|
||||
|
||||
<el-dialog v-model="platformRefundModal" title="平台预约退款" width="520px">
|
||||
<el-form :model="platformRefundForm" label-width="100px">
|
||||
<el-form-item label="退款类型">
|
||||
<el-radio-group v-model="platformRefundForm.refundType">
|
||||
<el-radio label="UNSERVED">未服务</el-radio>
|
||||
<el-radio label="SERVED">已服务</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="退款金额">
|
||||
<el-input-number v-model="platformRefundForm.refundAmount" :min="0.01" :precision="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="退款原因">
|
||||
<el-input v-model="platformRefundForm.reason" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="platformRefundModal = false">取消</el-button>
|
||||
<el-button type="primary" :loading="refundLoading" @click="submitPlatformRefund">确认退款</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="noShowRefundModal" title="上门爽约退款" width="520px">
|
||||
<el-form :model="noShowRefundForm" label-width="100px">
|
||||
<el-form-item label="退款金额">
|
||||
<el-input-number v-model="noShowRefundForm.refundAmount" :min="0.01" :precision="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="退款原因">
|
||||
<el-input v-model="noShowRefundForm.reason" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="noShowRefundModal = false">取消</el-button>
|
||||
<el-button type="primary" :loading="refundLoading" @click="submitNoShowRefund">确认退款</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as API_Order from "@/api/order";
|
||||
import { platformRefund, noShowRefund, getAppointmentOrderDetail } from "@/api/appointment";
|
||||
import * as RegExp from "@/libs/RegExp.js";
|
||||
|
||||
import multipleMap from "@/components/map/multiple-map";
|
||||
@@ -494,7 +547,28 @@ export default {
|
||||
return t === "VIRTUAL" || this.$route.query.orderType === "VIRTUAL";
|
||||
},
|
||||
isNonPhysicalOrder() {
|
||||
return this.isVirtualOrder || this.isECouponOrder;
|
||||
return this.isVirtualOrder || this.isECouponOrder || this.isAppointmentOrder;
|
||||
},
|
||||
isAppointmentOrder() {
|
||||
const t = this.orderInfo?.order?.orderType;
|
||||
return t === "APPOINTMENT" || this.$route.query.orderType === "APPOINTMENT";
|
||||
},
|
||||
appointmentSlotText() {
|
||||
const v = this.appointmentExt || {};
|
||||
if (!v.slotDate) return "";
|
||||
const date = String(v.slotDate).slice(0, 10);
|
||||
const slot = v.slotStartTime && v.slotEndTime ? `${v.slotStartTime}-${v.slotEndTime}` : "";
|
||||
return [date, slot].filter(Boolean).join(" ");
|
||||
},
|
||||
appointmentFormGroups() {
|
||||
return this.parseFormAnswerGroups(this.appointmentExt?.formAnswerJson);
|
||||
},
|
||||
canPlatformRefund() {
|
||||
const order = this.orderInfo?.order;
|
||||
return order && order.payStatus === "PAID" && order.orderStatus !== "CANCELLED";
|
||||
},
|
||||
canNoShowRefund() {
|
||||
return this.canPlatformRefund;
|
||||
},
|
||||
ecouponCardKeyDelivered() {
|
||||
return (this.data || []).some((item) => item.cardKeyDelivered);
|
||||
@@ -529,6 +603,7 @@ export default {
|
||||
priceDetailDTO: {},
|
||||
},
|
||||
},
|
||||
appointmentExt: {},
|
||||
modal: false, //弹出调整价格框
|
||||
searchForm: {
|
||||
pageNumber: 1, // 当前页数
|
||||
@@ -568,6 +643,18 @@ export default {
|
||||
},
|
||||
addressModal: false, //弹出修改收件信息框
|
||||
printModal: false,
|
||||
platformRefundModal: false,
|
||||
noShowRefundModal: false,
|
||||
refundLoading: false,
|
||||
platformRefundForm: {
|
||||
refundType: "UNSERVED",
|
||||
refundAmount: 0,
|
||||
reason: "",
|
||||
},
|
||||
noShowRefundForm: {
|
||||
refundAmount: 0,
|
||||
reason: "",
|
||||
},
|
||||
//收件地址表单
|
||||
addressForm: {
|
||||
consigneeName: "",
|
||||
@@ -690,9 +777,51 @@ export default {
|
||||
this.typeList = JSON.parse(JSON.stringify(res.result.order.priceDetailDTO.discountPriceDetail));
|
||||
this.getContentPrice()
|
||||
this.getOrderPrice()
|
||||
if (this.isAppointmentOrder) {
|
||||
this.loadAppointmentExt();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
loadAppointmentExt() {
|
||||
getAppointmentOrderDetail(this.sn)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.appointmentExt = res.result || {};
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.appointmentExt = {};
|
||||
});
|
||||
},
|
||||
parseFormAnswerGroups(formAnswerJson) {
|
||||
if (!formAnswerJson) return [];
|
||||
let data = formAnswerJson;
|
||||
if (typeof data === "string") {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
const fields = Array.isArray(data?.fields) ? data.fields : [];
|
||||
const answers = Array.isArray(data?.answers) ? data.answers : [];
|
||||
return answers
|
||||
.map((group) => {
|
||||
const map = group && typeof group === "object" && !Array.isArray(group) ? group : {};
|
||||
const source = fields.length
|
||||
? fields
|
||||
: Object.keys(map).map((key) => ({ fieldKey: key, fieldLabel: key }));
|
||||
return source
|
||||
.map((field) => ({
|
||||
fieldKey: field.fieldKey,
|
||||
fieldLabel: field.fieldLabel || field.fieldKey,
|
||||
value: map[field.fieldKey] ?? "",
|
||||
}))
|
||||
.filter((row) => row.value !== "" && row.value != null);
|
||||
})
|
||||
.filter((rows) => rows.length);
|
||||
},
|
||||
modifyPrice () {
|
||||
//默认要修改的金额为订单总金额
|
||||
this.modifyPriceForm.price = this.orderInfo.order.flowPrice;
|
||||
@@ -744,6 +873,57 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
openPlatformRefund() {
|
||||
this.platformRefundForm = {
|
||||
refundType: "UNSERVED",
|
||||
refundAmount: this.orderInfo?.order?.flowPrice || 0,
|
||||
reason: "",
|
||||
};
|
||||
this.platformRefundModal = true;
|
||||
},
|
||||
submitPlatformRefund() {
|
||||
if (!this.platformRefundForm.reason) {
|
||||
ElMessage.warning("请填写退款原因");
|
||||
return;
|
||||
}
|
||||
this.refundLoading = true;
|
||||
platformRefund(this.sn, this.platformRefundForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("退款成功");
|
||||
this.platformRefundModal = false;
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.refundLoading = false;
|
||||
});
|
||||
},
|
||||
openNoShowRefund() {
|
||||
this.noShowRefundForm = {
|
||||
refundAmount: this.orderInfo?.order?.flowPrice || 0,
|
||||
reason: "",
|
||||
};
|
||||
this.noShowRefundModal = true;
|
||||
},
|
||||
submitNoShowRefund() {
|
||||
if (!this.noShowRefundForm.reason) {
|
||||
ElMessage.warning("请填写退款原因");
|
||||
return;
|
||||
}
|
||||
this.refundLoading = true;
|
||||
noShowRefund(this.sn, this.noShowRefundForm)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("退款成功");
|
||||
this.noShowRefundModal = false;
|
||||
this.getDataList();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.refundLoading = false;
|
||||
});
|
||||
},
|
||||
//订单日志
|
||||
orderLog () {
|
||||
this.orderLogModal = true;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<el-form-item label="收货人" prop="shipName">
|
||||
<el-input v-model="searchForm.shipName" placeholder="请输入收货人姓名" clearable style="width: 240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单类型" prop="orderType">
|
||||
<el-form-item label="促销类型" prop="orderPromotionType">
|
||||
<el-select v-model="searchForm.orderPromotionType" placeholder="请选择" clearable style="width: 240px">
|
||||
<el-option label="普通订单" value="NORMAL" />
|
||||
<el-option label="拼团订单" value="PINTUAN" />
|
||||
@@ -38,6 +38,11 @@
|
||||
<el-option label="砍价订单" value="KANJIA" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单类型" prop="orderType">
|
||||
<el-select v-model="searchForm.orderType" placeholder="请选择" clearable style="width: 240px">
|
||||
<el-option label="预约商品" value="APPOINTMENT" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="支付方式" prop="paymentMethod">
|
||||
<el-select v-model="searchForm.paymentMethod" placeholder="请选择支付方式" clearable style="width: 240px">
|
||||
<el-option label="微信支付" value="WECHAT" />
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<el-input type="number" v-model="formValidate.autoEvaluation">
|
||||
<template #append>天</template>
|
||||
</el-input>
|
||||
<span class="desc">订单发货后,多少天后自动好评</span>
|
||||
<span class="desc">订单完成后,多少天后自动好评,预约订单同样适用</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="已完成订单允许退单" prop="closeAfterSale">
|
||||
<el-input type="number" v-model="formValidate.closeAfterSale">
|
||||
|
||||
Reference in New Issue
Block a user