|
|
@@ -1,27 +1,40 @@
|
|
|
<template>
|
|
|
<div class="table-box appointment-info">
|
|
|
- <!-- 筛选:点击搜索后生效(与翻页联动) -->
|
|
|
+ <!-- 筛选:点击搜索后生效(与翻页联动);列表参数 reservationUserName、orderStatus -->
|
|
|
<div class="filter-bar">
|
|
|
<div class="filter-row">
|
|
|
- <span class="filter-label">预订日期</span>
|
|
|
- <el-date-picker
|
|
|
- v-model="searchForm.dateRange"
|
|
|
- type="daterange"
|
|
|
- range-separator="至"
|
|
|
- start-placeholder="请选择"
|
|
|
- end-placeholder="请选择"
|
|
|
- value-format="YYYY-MM-DD"
|
|
|
- style="width: 160px"
|
|
|
- />
|
|
|
+ <span class="filter-label">姓名</span>
|
|
|
+ <el-input v-model="searchForm.reservationUserName" placeholder="请输入姓名" clearable style="width: 160px" />
|
|
|
+ <span class="filter-label">状态</span>
|
|
|
+ <el-select v-model="searchForm.orderStatus" placeholder="请选择" clearable style="width: 180px">
|
|
|
+ <el-option
|
|
|
+ v-for="opt in statusOptions"
|
|
|
+ :key="`${opt.label}-${String(opt.value)}`"
|
|
|
+ :label="opt.label"
|
|
|
+ :value="opt.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <div style="width: 600px">
|
|
|
+ <span class="filter-label">预订日期</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="searchForm.dateRange"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="请选择"
|
|
|
+ end-placeholder="请选择"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 80%; margin-left: 10px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
<el-button type="primary" @click="handleSearch"> 搜索 </el-button>
|
|
|
<el-button @click="handleReset"> 重置 </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam">
|
|
|
+ <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :pagination="false">
|
|
|
<template #tableHeader>
|
|
|
<div class="table-header">
|
|
|
- <el-button type="primary" @click="handleNew"> 新建 </el-button>
|
|
|
+ <!-- <el-button type="primary" @click="handleNew"> 新建 </el-button> -->
|
|
|
</div>
|
|
|
</template>
|
|
|
<template #statusText="{ row }">
|
|
|
@@ -35,46 +48,116 @@
|
|
|
</el-tooltip>
|
|
|
<span v-else>—</span>
|
|
|
</template>
|
|
|
- <!-- 操作按钮与商家端 1.vue 一致:orderStatus 0待支付 1待使用 2已完成 3已过期 4已取消 5已关闭 6退款中 7已退款 8商家预订 -->
|
|
|
+ <!-- 操作按钮与 group_merchant scheduledInfo.vue 一致(orderStatus 判断顺序一致) -->
|
|
|
<template #operation="scope">
|
|
|
- <template v-if="isOrderStatus(scope.row, 1)">
|
|
|
- <el-button link type="primary" @click="handleCancel(scope.row)"> 取消 </el-button>
|
|
|
- </template>
|
|
|
- <template v-else-if="isOrderStatus(scope.row, 7)">
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
- </template>
|
|
|
- <template
|
|
|
- v-else-if="
|
|
|
- (isOrderStatus(scope.row, 2) || isOrderStatus(scope.row, 7) || isOrderStatus(scope.row, 3)) &&
|
|
|
- isMoreThanThreeHoursAfterEnd(scope.row)
|
|
|
- "
|
|
|
- >
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
- </template>
|
|
|
- <template
|
|
|
- v-else-if="isOrderStatus(scope.row, 3) && !isMoreThanThreeHoursAfterEnd(scope.row) && orderCostTypeIsPaid(scope.row)"
|
|
|
- >
|
|
|
- <el-button link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
- </template>
|
|
|
- <template v-else-if="isOrderStatus(scope.row, 4) && !orderCostTypeIsPaid(scope.row)">
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
- </template>
|
|
|
- <template v-else-if="orderCostTypeIsPaid(scope.row) && isOrderStatus(scope.row, 4) && cancelReasonIsEmpty(scope.row)">
|
|
|
- <el-button link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
- </template>
|
|
|
- <template v-else-if="isOrderStatus(scope.row, 2) && !isMoreThanThreeHoursAfterEnd(scope.row)">
|
|
|
- <el-button link type="primary" @click="handleAddTime(scope.row)"> 加时 </el-button>
|
|
|
- </template>
|
|
|
+ <div class="op-actions">
|
|
|
+ <el-button v-if="hasReasonText(scope.row)" link type="primary" @click="openReasonDialog(scope.row)">
|
|
|
+ 查看原因
|
|
|
+ </el-button>
|
|
|
+ <template v-if="isOrderStatus(scope.row, 1)">
|
|
|
+ <el-button link type="primary" @click="handleCancel(scope.row)"> 取消 </el-button>
|
|
|
+ </template>
|
|
|
+ <template
|
|
|
+ v-else-if="
|
|
|
+ (isOrderStatus(scope.row, 2) || isOrderStatus(scope.row, 7) || isOrderStatus(scope.row, 3)) &&
|
|
|
+ isMoreThanThreeHoursAfterEnd(scope.row)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template
|
|
|
+ v-else-if="
|
|
|
+ (isOrderStatus(scope.row, 2) || isOrderStatus(scope.row, 7) || isOrderStatus(scope.row, 3)) &&
|
|
|
+ !isMoreThanThreeHoursAfterEnd(scope.row)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="isOrderStatus(scope.row, 7) && !isMoreThanThreeHoursAfterEnd(scope.row)">
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template
|
|
|
+ v-else-if="isOrderStatus(scope.row, 3) && !isMoreThanThreeHoursAfterEnd(scope.row) && orderCostTypeIsPaid(scope.row)"
|
|
|
+ >
|
|
|
+ <el-button link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="isOrderStatus(scope.row, 4) && !orderCostTypeIsPaid(scope.row)">
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="orderCostTypeIsPaid(scope.row) && isOrderStatus(scope.row, 4) && cancelReasonIsEmpty(scope.row)">
|
|
|
+ <el-button link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
+ <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="isOrderStatus(scope.row, 2) && !isMoreThanThreeHoursAfterEnd(scope.row)">
|
|
|
+ <el-button link type="primary" @click="handleAddTime(scope.row)"> 加时 </el-button>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</ProTable>
|
|
|
|
|
|
- <!-- 加时弹窗 -->
|
|
|
- <el-dialog v-model="addTimeVisible" title="加时" width="400px" append-to-body>
|
|
|
- <el-form :model="addTimeForm" label-width="80px">
|
|
|
- <el-form-item label="加时时长">
|
|
|
- <el-input-number v-model="addTimeForm.minutes" :min="1" :max="480" placeholder="分钟" style="width: 100%" />
|
|
|
+ <!-- 查看原因 -->
|
|
|
+ <el-dialog v-model="reasonDialogVisible" title="原因" width="420px" append-to-body destroy-on-close>
|
|
|
+ <div class="reason-dialog-body">取消原因:</div>
|
|
|
+ <div class="reason-dialog-body">
|
|
|
+ {{ reasonDialogText || "—" }}
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button type="primary" @click="reasonDialogVisible = false"> 知道了 </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 取消原因(与 group_merchant scheduledInfo:reservationId + cancelReason) -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="cancelReasonVisible"
|
|
|
+ title="取消原因"
|
|
|
+ width="480px"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ class="cancel-reason-dialog"
|
|
|
+ @closed="resetCancelReasonDialog"
|
|
|
+ >
|
|
|
+ <el-form label-position="top" class="cancel-reason-form">
|
|
|
+ <el-form-item label="取消原因" required>
|
|
|
+ <el-input
|
|
|
+ v-model="cancelReasonText"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ maxlength="500"
|
|
|
+ show-word-limit
|
|
|
+ placeholder="请输入取消原因"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="cancelReasonVisible = false"> 取消 </el-button>
|
|
|
+ <el-button type="primary" :loading="cancelReasonLoading" @click="confirmCancelReason"> 确定 </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 加时弹窗(与 group_merchant scheduledInfo:1-99 分钟、reservationId + addTimeMinutes + addTimeStart) -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="addTimeVisible"
|
|
|
+ title="加时"
|
|
|
+ width="480px"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ class="add-time-dialog"
|
|
|
+ @closed="resetAddTimeDialog"
|
|
|
+ >
|
|
|
+ <el-form :model="addTimeForm" label-position="top" require-asterisk-position="right" class="add-time-form">
|
|
|
+ <el-form-item label="加时时长" required>
|
|
|
+ <el-input
|
|
|
+ :model-value="addTimeForm.minutesInput"
|
|
|
+ placeholder="请输入"
|
|
|
+ clearable
|
|
|
+ maxlength="2"
|
|
|
+ @update:model-value="onAddTimeMinutesInput"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <span class="add-time-append-unit">分钟</span>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
@@ -101,6 +184,8 @@ import { localGet } from "@/utils";
|
|
|
|
|
|
export interface ReservationRow {
|
|
|
id: number | string;
|
|
|
+ /** 与商家端加时接口一致,缺省时用 id */
|
|
|
+ reservationId?: number | string;
|
|
|
_seq?: number;
|
|
|
date: string;
|
|
|
weekDay: string;
|
|
|
@@ -139,20 +224,28 @@ const columns: ColumnProps<ReservationRow>[] = [
|
|
|
{ prop: "peopleCount", label: "人数", width: 70, align: "center" },
|
|
|
{ prop: "tableNo", label: "桌号", minWidth: 90, showOverflowTooltip: true },
|
|
|
{ prop: "timeRange", label: "时间", width: 120, align: "center" },
|
|
|
- { prop: "amount", label: "预订金额(元)", width: 110, align: "center" },
|
|
|
{ prop: "customerName", label: "姓名", width: 100, showOverflowTooltip: true },
|
|
|
{ prop: "phone", label: "电话", width: 120, showOverflowTooltip: true },
|
|
|
{ prop: "statusText", label: "状态", width: 96, align: "center" },
|
|
|
{ prop: "remark", label: "备注", minWidth: 100, showOverflowTooltip: true },
|
|
|
- { prop: "operation", label: "操作", fixed: "right", width: 200, align: "center" }
|
|
|
+ { prop: "operation", label: "操作", fixed: "right", width: 240, align: "center" }
|
|
|
];
|
|
|
|
|
|
-/** 与商家端 1.vue 筛选、列表 status 一致(orderStatus) */
|
|
|
-const statusOptions = [
|
|
|
+/**
|
|
|
+ * 筛选状态:商家取消 / 用户取消 均为 orderStatus=4,列表展示区分见 mapReservationRow(reason 空=用户取消)
|
|
|
+ * 接口仅支持 orderStatus 时,请求 4 后在本地按 reason 再筛一层
|
|
|
+ */
|
|
|
+const STATUS_FILTER_MERCHANT_CANCEL = "merchant_cancel";
|
|
|
+const STATUS_FILTER_USER_CANCEL = "user_cancel";
|
|
|
+
|
|
|
+const statusOptions: { label: string; value: number | string }[] = [
|
|
|
{ label: "全部", value: "" },
|
|
|
{ label: "待使用", value: 1 },
|
|
|
- { label: "已完成", value: 2 },
|
|
|
- { label: "退款", value: 6 } // 退款 tab:6退款中(接口若合并 7 需后端支持)
|
|
|
+ { label: "退款成功", value: 7 },
|
|
|
+ { label: "商家取消", value: STATUS_FILTER_MERCHANT_CANCEL },
|
|
|
+ { label: "用户取消", value: STATUS_FILTER_USER_CANCEL },
|
|
|
+ { label: "已过期", value: 3 },
|
|
|
+ { label: "已完成", value: 2 }
|
|
|
];
|
|
|
|
|
|
/** orderStatus 文案,与 1.vue ORDER_STATUS_TEXT 一致 */
|
|
|
@@ -185,36 +278,42 @@ const initParam = reactive({
|
|
|
storeId: localGet("geeker-user")?.userInfo?.storeId ?? localGet("createdId") ?? ""
|
|
|
});
|
|
|
|
|
|
-/** 已生效的查询条件(点击搜索后写入) */
|
|
|
+/** 已生效的查询条件(点击搜索后写入;接口字段 reservationUserName、orderStatus) */
|
|
|
const listFilter = reactive({
|
|
|
- name: "",
|
|
|
- status: "" as number | string,
|
|
|
+ reservationUserName: "",
|
|
|
+ orderStatus: "" as number | string,
|
|
|
startDate: "",
|
|
|
endDate: ""
|
|
|
});
|
|
|
|
|
|
const searchForm = reactive({
|
|
|
- name: "",
|
|
|
- status: "" as number | string,
|
|
|
+ reservationUserName: "",
|
|
|
+ orderStatus: "" as number | string,
|
|
|
dateRange: [] as string[]
|
|
|
});
|
|
|
|
|
|
const addTimeVisible = ref(false);
|
|
|
const addTimeLoading = ref(false);
|
|
|
-const addTimeForm = reactive({ minutes: 30 });
|
|
|
+const addTimeForm = reactive({ minutesInput: "" });
|
|
|
const currentAddTimeRow = ref<ReservationRow | null>(null);
|
|
|
|
|
|
+/** 取消预约:原因弹窗(对齐 scheduledInfo) */
|
|
|
+const cancelReasonVisible = ref(false);
|
|
|
+const cancelReasonLoading = ref(false);
|
|
|
+const cancelReasonText = ref("");
|
|
|
+const cancelCurrentRow = ref<ReservationRow | null>(null);
|
|
|
+
|
|
|
function getStoreId(): number | string | null {
|
|
|
return localGet("geeker-user")?.userInfo?.storeId ?? localGet("createdId") ?? null;
|
|
|
}
|
|
|
|
|
|
+/** 与 scheduledInfo normalizeReservationItem:pending(0,1,8) | done(2) | refund(6,7) | closed(3,4,5) */
|
|
|
function getStatusTagType(orderStatus: number | string): "primary" | "success" | "warning" | "danger" | "info" {
|
|
|
const s = Number(orderStatus);
|
|
|
- if (s === 0 || s === 1 || s === 8) return "primary";
|
|
|
if (s === 2) return "success";
|
|
|
- if (s === 6) return "warning";
|
|
|
- if (s === 7) return "success";
|
|
|
+ if (s === 6 || s === 7) return "warning";
|
|
|
if (s === 3 || s === 4 || s === 5) return "info";
|
|
|
+ if (s === 0 || s === 1 || s === 8) return "primary";
|
|
|
return "info";
|
|
|
}
|
|
|
|
|
|
@@ -231,6 +330,18 @@ function cancelReasonIsEmpty(row: ReservationRow) {
|
|
|
return String(row.reason ?? "").trim() === "";
|
|
|
}
|
|
|
|
|
|
+function hasReasonText(row: ReservationRow) {
|
|
|
+ return String(row.reason ?? "").trim() !== "";
|
|
|
+}
|
|
|
+
|
|
|
+const reasonDialogVisible = ref(false);
|
|
|
+const reasonDialogText = ref("");
|
|
|
+
|
|
|
+function openReasonDialog(row: ReservationRow) {
|
|
|
+ reasonDialogText.value = String(row.reason ?? "").trim() || "—";
|
|
|
+ reasonDialogVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
function parseRowEndDateTime(row: ReservationRow): Date | null {
|
|
|
const et = row.endTimeRaw;
|
|
|
if (et != null && et !== "") {
|
|
|
@@ -311,6 +422,7 @@ function mapReservationRow(item: any): ReservationRow {
|
|
|
|
|
|
return {
|
|
|
id: item.id,
|
|
|
+ reservationId: item.reservationId ?? item.id,
|
|
|
date: formatDateCol(date),
|
|
|
weekDay: item.weekDay ?? getWeekDay(date),
|
|
|
location: item.locationName ?? item.categoryName ?? item.location ?? "—",
|
|
|
@@ -332,8 +444,16 @@ function mapReservationRow(item: any): ReservationRow {
|
|
|
reservationDateRaw: String(date).trim(),
|
|
|
timeSlotRaw,
|
|
|
endTimeRaw: item.endTime ?? item.bookingEndTime ?? item.reservationEndTime,
|
|
|
+ /** scheduledInfo:orderStatus==4 时 reason 空=用户取消,非空=商家取消 */
|
|
|
statusText:
|
|
|
- (item.orderStatusText && String(item.orderStatusText).trim()) || ORDER_STATUS_TEXT[orderStatus] || item.statusText || "—",
|
|
|
+ orderStatus === 4
|
|
|
+ ? reason === ""
|
|
|
+ ? "用户取消"
|
|
|
+ : "商家取消"
|
|
|
+ : (item.orderStatusText && String(item.orderStatusText).trim()) ||
|
|
|
+ ORDER_STATUS_TEXT[orderStatus] ||
|
|
|
+ item.statusText ||
|
|
|
+ "—",
|
|
|
remark: item.remark ?? item.remarkDesc ?? "",
|
|
|
cancelReason: item.cancelReason ?? item.reason ?? "",
|
|
|
refundReason: item.refundReason,
|
|
|
@@ -354,13 +474,20 @@ async function getTableList(params: any) {
|
|
|
pageNum,
|
|
|
pageSize
|
|
|
};
|
|
|
- if (listFilter.name?.trim()) req.name = listFilter.name.trim();
|
|
|
- if (listFilter.status !== undefined && listFilter.status !== "" && listFilter.status !== null) {
|
|
|
- req.status = listFilter.status;
|
|
|
+ if (listFilter.reservationUserName?.trim()) {
|
|
|
+ req.reservationUserName = listFilter.reservationUserName.trim();
|
|
|
+ }
|
|
|
+ const sf = listFilter.orderStatus;
|
|
|
+ if (sf !== undefined && sf !== "" && sf !== null) {
|
|
|
+ if (sf === STATUS_FILTER_USER_CANCEL || sf === STATUS_FILTER_MERCHANT_CANCEL) {
|
|
|
+ req.orderStatus = 4;
|
|
|
+ } else {
|
|
|
+ req.orderStatus = sf;
|
|
|
+ }
|
|
|
}
|
|
|
if (listFilter.startDate && listFilter.endDate) {
|
|
|
- req.startDate = listFilter.startDate;
|
|
|
- req.endDate = listFilter.endDate;
|
|
|
+ req.dateFrom = listFilter.startDate;
|
|
|
+ req.dateTo = listFilter.endDate;
|
|
|
}
|
|
|
try {
|
|
|
const res: any = await reservationList(req);
|
|
|
@@ -368,12 +495,19 @@ async function getTableList(params: any) {
|
|
|
const inner = body?.data ?? body;
|
|
|
const list = inner?.list ?? inner?.records ?? (Array.isArray(inner) ? inner : []);
|
|
|
const arr = Array.isArray(list) ? list : [];
|
|
|
- const total = inner?.total ?? body?.total ?? res?.total ?? 0;
|
|
|
- const rows = arr.map((item: any, i: number) => ({
|
|
|
- ...mapReservationRow(item),
|
|
|
+ const apiTotal = Number(inner?.total ?? body?.total ?? res?.total ?? 0) || 0;
|
|
|
+ let rows = arr.map((item: any) => mapReservationRow(item));
|
|
|
+ if (sf === STATUS_FILTER_USER_CANCEL) {
|
|
|
+ rows = rows.filter(r => r.orderStatus === 4 && cancelReasonIsEmpty(r));
|
|
|
+ } else if (sf === STATUS_FILTER_MERCHANT_CANCEL) {
|
|
|
+ rows = rows.filter(r => r.orderStatus === 4 && !cancelReasonIsEmpty(r));
|
|
|
+ }
|
|
|
+ const finalRows = rows.map((r, i) => ({
|
|
|
+ ...r,
|
|
|
_seq: (pageNum - 1) * pageSize + i + 1
|
|
|
}));
|
|
|
- return { data: { list: rows, total: Number(total) || 0 } };
|
|
|
+ const total = sf === STATUS_FILTER_USER_CANCEL || sf === STATUS_FILTER_MERCHANT_CANCEL ? finalRows.length : apiTotal;
|
|
|
+ return { data: { list: finalRows, total } };
|
|
|
} catch (e: any) {
|
|
|
ElMessage.error(e?.message || "加载失败");
|
|
|
return { data: { list: [] as ReservationRow[], total: 0 } };
|
|
|
@@ -381,8 +515,8 @@ async function getTableList(params: any) {
|
|
|
}
|
|
|
|
|
|
function handleSearch() {
|
|
|
- listFilter.name = searchForm.name;
|
|
|
- listFilter.status = searchForm.status;
|
|
|
+ listFilter.reservationUserName = searchForm.reservationUserName;
|
|
|
+ listFilter.orderStatus = searchForm.orderStatus;
|
|
|
if (searchForm.dateRange?.length === 2) {
|
|
|
listFilter.startDate = searchForm.dateRange[0];
|
|
|
listFilter.endDate = searchForm.dateRange[1];
|
|
|
@@ -394,11 +528,11 @@ function handleSearch() {
|
|
|
}
|
|
|
|
|
|
function handleReset() {
|
|
|
- searchForm.name = "";
|
|
|
- searchForm.status = "";
|
|
|
+ searchForm.reservationUserName = "";
|
|
|
+ searchForm.orderStatus = "";
|
|
|
searchForm.dateRange = [];
|
|
|
- listFilter.name = "";
|
|
|
- listFilter.status = "";
|
|
|
+ listFilter.reservationUserName = "";
|
|
|
+ listFilter.orderStatus = "";
|
|
|
listFilter.startDate = "";
|
|
|
listFilter.endDate = "";
|
|
|
proTable.value?.getTableList();
|
|
|
@@ -409,40 +543,104 @@ function handleNew() {
|
|
|
}
|
|
|
|
|
|
function handleCancel(row: ReservationRow) {
|
|
|
- ElMessageBox.confirm("确认取消该预约?", "提示", {
|
|
|
- confirmButtonText: "确定",
|
|
|
- cancelButtonText: "取消",
|
|
|
- type: "warning"
|
|
|
- })
|
|
|
- .then(async () => {
|
|
|
- try {
|
|
|
- await reservationCancel({ id: row.id });
|
|
|
- ElMessage.success("已取消");
|
|
|
- proTable.value?.getTableList();
|
|
|
- } catch (e: any) {
|
|
|
- ElMessage.error(e?.message || "取消失败");
|
|
|
- }
|
|
|
- })
|
|
|
- .catch(() => {});
|
|
|
+ cancelCurrentRow.value = row;
|
|
|
+ cancelReasonText.value = "";
|
|
|
+ cancelReasonVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function resetCancelReasonDialog() {
|
|
|
+ cancelCurrentRow.value = null;
|
|
|
+ cancelReasonText.value = "";
|
|
|
+}
|
|
|
+
|
|
|
+async function confirmCancelReason() {
|
|
|
+ const reason = (cancelReasonText.value || "").trim();
|
|
|
+ if (!reason) {
|
|
|
+ ElMessage.warning("请输入取消原因");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const row = cancelCurrentRow.value;
|
|
|
+ const reservationId = row?.reservationId ?? row?.id;
|
|
|
+ if (reservationId == null || reservationId === "") {
|
|
|
+ ElMessage.warning("缺少预订ID");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ cancelReasonLoading.value = true;
|
|
|
+ try {
|
|
|
+ const apiRes: any = await reservationCancel({ reservationId, cancelReason: reason });
|
|
|
+ ElMessage.success(apiRes?.msg || "已取消");
|
|
|
+ cancelReasonVisible.value = false;
|
|
|
+ resetCancelReasonDialog();
|
|
|
+ proTable.value?.getTableList();
|
|
|
+ } catch (e: any) {
|
|
|
+ if (typeof e?.code !== "number") {
|
|
|
+ ElMessage.error(e?.msg || e?.data?.msg || e?.message || "取消失败");
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ cancelReasonLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 与 scheduledInfo confirmAddTime:当前时间 yyyy-MM-dd HH:mm */
|
|
|
+function formatAddTimeStartFromNow(): string {
|
|
|
+ const now = new Date();
|
|
|
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
|
+}
|
|
|
+
|
|
|
+/** 与 scheduledInfo onAddTimeMinutesInput:最多 2 位数字 1-99 */
|
|
|
+function onAddTimeMinutesInput(val: string | number) {
|
|
|
+ const v = String(val ?? "")
|
|
|
+ .replace(/\D/g, "")
|
|
|
+ .slice(0, 2);
|
|
|
+ addTimeForm.minutesInput = v;
|
|
|
+}
|
|
|
+
|
|
|
+function resetAddTimeDialog() {
|
|
|
+ currentAddTimeRow.value = null;
|
|
|
+ addTimeForm.minutesInput = "";
|
|
|
}
|
|
|
|
|
|
function handleAddTime(row: ReservationRow) {
|
|
|
currentAddTimeRow.value = row;
|
|
|
- addTimeForm.minutes = 30;
|
|
|
+ addTimeForm.minutesInput = "";
|
|
|
addTimeVisible.value = true;
|
|
|
}
|
|
|
|
|
|
async function confirmAddTime() {
|
|
|
const row = currentAddTimeRow.value;
|
|
|
if (!row) return;
|
|
|
+ const raw = String(addTimeForm.minutesInput ?? "").trim();
|
|
|
+ if (!raw) {
|
|
|
+ ElMessage.warning("请输入加时时长");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const addTimeMinutesNum = parseInt(raw, 10);
|
|
|
+ if (isNaN(addTimeMinutesNum) || addTimeMinutesNum < 1 || addTimeMinutesNum > 99) {
|
|
|
+ ElMessage.warning("加时时长为1-99的整数");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const reservationId = row.reservationId ?? row.id;
|
|
|
+ if (reservationId == null || reservationId === "") {
|
|
|
+ ElMessage.warning("缺少预订ID");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const addTimeStart = formatAddTimeStartFromNow();
|
|
|
addTimeLoading.value = true;
|
|
|
try {
|
|
|
- await reservationAddTime({ id: row.id, addMinutes: addTimeForm.minutes });
|
|
|
- ElMessage.success("加时成功");
|
|
|
+ const apiRes: any = await reservationAddTime({
|
|
|
+ reservationId,
|
|
|
+ addTimeMinutes: addTimeMinutesNum,
|
|
|
+ addTimeStart
|
|
|
+ });
|
|
|
+ ElMessage.success(apiRes?.msg || "加时成功");
|
|
|
addTimeVisible.value = false;
|
|
|
+ resetAddTimeDialog();
|
|
|
proTable.value?.getTableList();
|
|
|
} catch (e: any) {
|
|
|
- ElMessage.error(e?.message || "加时失败");
|
|
|
+ // 业务 code≠200 时 indexApi 响应拦截器已提示 msg,避免重复 Toast
|
|
|
+ if (typeof e?.code !== "number") {
|
|
|
+ ElMessage.error(e?.msg || e?.data?.msg || e?.message || "加时失败");
|
|
|
+ }
|
|
|
} finally {
|
|
|
addTimeLoading.value = false;
|
|
|
}
|
|
|
@@ -523,4 +721,57 @@ function handleRefund(row: ReservationRow) {
|
|
|
white-space: nowrap;
|
|
|
cursor: default;
|
|
|
}
|
|
|
+.op-actions {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px 8px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.reason-dialog-body {
|
|
|
+ padding: 8px 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+ color: #303133;
|
|
|
+ word-break: break-word;
|
|
|
+ white-space: pre-wrap;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<!-- append-to-body 时弹窗挂到 body,需非 scoped 命中 -->
|
|
|
+<style lang="scss">
|
|
|
+.add-time-dialog.el-dialog .el-dialog__body {
|
|
|
+ padding-top: 8px;
|
|
|
+}
|
|
|
+.add-time-form {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.add-time-form .el-form-item {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.add-time-form .el-form-item__label {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+.add-time-form .el-input-group {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.add-time-form .el-input__wrapper {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.add-time-append-unit {
|
|
|
+ padding: 0 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+.cancel-reason-dialog.el-dialog .el-dialog__body {
|
|
|
+ padding-top: 8px;
|
|
|
+}
|
|
|
+.cancel-reason-form {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.cancel-reason-form .el-form-item {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
</style>
|