|
|
@@ -3,12 +3,6 @@
|
|
|
<!-- 筛选:点击搜索后生效(与翻页联动) -->
|
|
|
<div class="filter-bar">
|
|
|
<div class="filter-row">
|
|
|
- <span class="filter-label">姓名</span>
|
|
|
- <el-input v-model="searchForm.name" placeholder="请输入" clearable style="width: 160px" />
|
|
|
- <span class="filter-label">状态</span>
|
|
|
- <el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 160px">
|
|
|
- <el-option v-for="item in statusOptions" :key="String(item.value)" :label="item.label" :value="item.value" />
|
|
|
- </el-select>
|
|
|
<span class="filter-label">预订日期</span>
|
|
|
<el-date-picker
|
|
|
v-model="searchForm.dateRange"
|
|
|
@@ -17,7 +11,7 @@
|
|
|
start-placeholder="请选择"
|
|
|
end-placeholder="请选择"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
- style="width: 260px"
|
|
|
+ style="width: 160px"
|
|
|
/>
|
|
|
<el-button type="primary" @click="handleSearch"> 搜索 </el-button>
|
|
|
<el-button @click="handleReset"> 重置 </el-button>
|
|
|
@@ -31,7 +25,7 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
<template #statusText="{ row }">
|
|
|
- <el-tag :type="getStatusTagType(row.status)" size="small">
|
|
|
+ <el-tag :type="getStatusTagType(row.orderStatus)" size="small">
|
|
|
{{ row.statusText }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
@@ -41,35 +35,37 @@
|
|
|
</el-tooltip>
|
|
|
<span v-else>—</span>
|
|
|
</template>
|
|
|
+ <!-- 操作按钮与商家端 1.vue 一致:orderStatus 0待支付 1待使用 2已完成 3已过期 4已取消 5已关闭 6退款中 7已退款 8商家预订 -->
|
|
|
<template #operation="scope">
|
|
|
- <template v-if="scope.row.status === 0 || scope.row.status === '0'">
|
|
|
+ <template v-if="isOrderStatus(scope.row, 1)">
|
|
|
<el-button link type="primary" @click="handleCancel(scope.row)"> 取消 </el-button>
|
|
|
</template>
|
|
|
- <template v-else-if="scope.row.status === 1 || scope.row.status === '1'">
|
|
|
- <el-button link type="primary" @click="handleAddTime(scope.row)"> 加时 </el-button>
|
|
|
+ <template v-else-if="isOrderStatus(scope.row, 7)">
|
|
|
<el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
</template>
|
|
|
- <template v-else-if="scope.row.status === 2 || scope.row.status === '2'">
|
|
|
- <el-button v-if="scope.row.canRefund !== false" link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
+ <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="scope.row.status === 3 || scope.row.status === '3'">
|
|
|
- <el-button link type="primary" @click="handleViewReason(scope.row)"> 查看原因 </el-button>
|
|
|
+ <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="scope.row.status === 4 || scope.row.status === '4'">
|
|
|
- <el-button v-if="scope.row.canRefund !== false" link type="primary" @click="handleRefund(scope.row)"> 退款 </el-button>
|
|
|
+ <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="scope.row.status === 5 || scope.row.status === '5'">
|
|
|
- <el-button link type="primary" @click="handleViewReason(scope.row)"> 查看原因 </el-button>
|
|
|
- </template>
|
|
|
- <template v-else-if="scope.row.status === 6 || scope.row.status === '6'">
|
|
|
- <el-button link type="primary" @click="handleViewReason(scope.row)"> 查看原因 </el-button>
|
|
|
+ <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>
|
|
|
- <el-button link type="primary" @click="handleDelete(scope.row)"> 删除 </el-button>
|
|
|
+ <template v-else-if="isOrderStatus(scope.row, 2) && !isMoreThanThreeHoursAfterEnd(scope.row)">
|
|
|
+ <el-button link type="primary" @click="handleAddTime(scope.row)"> 加时 </el-button>
|
|
|
</template>
|
|
|
</template>
|
|
|
</ProTable>
|
|
|
@@ -86,13 +82,6 @@
|
|
|
<el-button type="primary" :loading="addTimeLoading" @click="confirmAddTime"> 确定 </el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
-
|
|
|
- <!-- 查看原因弹窗 -->
|
|
|
- <el-dialog v-model="reasonVisible" title="查看原因" width="400px" append-to-body>
|
|
|
- <p class="reason-text">
|
|
|
- {{ currentReason || "—" }}
|
|
|
- </p>
|
|
|
- </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -122,8 +111,18 @@ export interface ReservationRow {
|
|
|
amount: string | number;
|
|
|
customerName: string;
|
|
|
phone: string;
|
|
|
+ /** 兼容旧字段展示/筛选 */
|
|
|
status: number | string;
|
|
|
statusText: string;
|
|
|
+ /** 与商家端 1.vue 一致:订单状态 */
|
|
|
+ orderStatus: number;
|
|
|
+ /** 0 免费 1 付费(退款按钮用) */
|
|
|
+ orderCostType: number;
|
|
|
+ /** 与 1.vue item.reason 一致:空=用户取消,非空=商家取消等 */
|
|
|
+ reason: string;
|
|
|
+ reservationDateRaw: string;
|
|
|
+ timeSlotRaw: string;
|
|
|
+ endTimeRaw?: string | Date;
|
|
|
remark: string;
|
|
|
cancelReason?: string;
|
|
|
refundReason?: string;
|
|
|
@@ -145,26 +144,43 @@ const columns: ColumnProps<ReservationRow>[] = [
|
|
|
{ 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: 170, align: "center" }
|
|
|
+ { prop: "operation", label: "操作", fixed: "right", width: 200, align: "center" }
|
|
|
];
|
|
|
|
|
|
+/** 与商家端 1.vue 筛选、列表 status 一致(orderStatus) */
|
|
|
const statusOptions = [
|
|
|
{ label: "全部", value: "" },
|
|
|
- { label: "待使用", value: 0 },
|
|
|
- { label: "已完成", value: 1 },
|
|
|
- { label: "退款", value: 5 }
|
|
|
+ { label: "待使用", value: 1 },
|
|
|
+ { label: "已完成", value: 2 },
|
|
|
+ { label: "退款", value: 6 } // 退款 tab:6退款中(接口若合并 7 需后端支持)
|
|
|
];
|
|
|
|
|
|
-const STATUS_MAP: Record<number, string> = {
|
|
|
- 0: "待使用",
|
|
|
- 1: "已完成",
|
|
|
- 2: "用户取消",
|
|
|
- 3: "商家取消",
|
|
|
- 4: "已过期",
|
|
|
- 5: "退款中",
|
|
|
- 6: "退款成功"
|
|
|
+/** orderStatus 文案,与 1.vue ORDER_STATUS_TEXT 一致 */
|
|
|
+const ORDER_STATUS_TEXT: Record<number, string> = {
|
|
|
+ 0: "待支付",
|
|
|
+ 1: "待使用",
|
|
|
+ 2: "已完成",
|
|
|
+ 3: "已过期",
|
|
|
+ 4: "已取消",
|
|
|
+ 5: "已关闭",
|
|
|
+ 6: "退款中",
|
|
|
+ 7: "已退款",
|
|
|
+ 8: "商家预订"
|
|
|
+};
|
|
|
+
|
|
|
+/** 旧版 Web 列表 status → orderStatus(无 orderStatus 字段时的兜底) */
|
|
|
+const LEGACY_STATUS_TO_ORDER: Record<number, number> = {
|
|
|
+ 0: 1, // 待使用
|
|
|
+ 1: 2, // 已完成
|
|
|
+ 2: 4, // 用户取消 → 已取消
|
|
|
+ 3: 4, // 商家取消 → 已取消
|
|
|
+ 4: 3, // 已过期
|
|
|
+ 5: 6, // 退款中
|
|
|
+ 6: 7 // 退款成功 → 已退款
|
|
|
};
|
|
|
|
|
|
+const THREE_HOURS_MS = 3 * 60 * 60 * 1000;
|
|
|
+
|
|
|
const initParam = reactive({
|
|
|
storeId: localGet("geeker-user")?.userInfo?.storeId ?? localGet("createdId") ?? ""
|
|
|
});
|
|
|
@@ -188,23 +204,60 @@ const addTimeLoading = ref(false);
|
|
|
const addTimeForm = reactive({ minutes: 30 });
|
|
|
const currentAddTimeRow = ref<ReservationRow | null>(null);
|
|
|
|
|
|
-const reasonVisible = ref(false);
|
|
|
-const currentReason = ref("");
|
|
|
-
|
|
|
function getStoreId(): number | string | null {
|
|
|
return localGet("geeker-user")?.userInfo?.storeId ?? localGet("createdId") ?? null;
|
|
|
}
|
|
|
|
|
|
-function getStatusTagType(status: number | string): "primary" | "success" | "warning" | "danger" | "info" {
|
|
|
- const s = Number(status);
|
|
|
- if (s === 0) return "primary";
|
|
|
- if (s === 1) return "success";
|
|
|
- if (s === 2 || s === 3 || s === 4) return "info";
|
|
|
- if (s === 5) return "warning";
|
|
|
- if (s === 6) return "success";
|
|
|
+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 === 3 || s === 4 || s === 5) return "info";
|
|
|
return "info";
|
|
|
}
|
|
|
|
|
|
+function isOrderStatus(row: ReservationRow, n: number) {
|
|
|
+ return Number(row.orderStatus) === n;
|
|
|
+}
|
|
|
+
|
|
|
+function orderCostTypeIsPaid(row: ReservationRow) {
|
|
|
+ return Number(row.orderCostType) === 1;
|
|
|
+}
|
|
|
+
|
|
|
+/** 与 1.vue 一致:reason === '' 时视为用户取消(付费可退款+删除) */
|
|
|
+function cancelReasonIsEmpty(row: ReservationRow) {
|
|
|
+ return String(row.reason ?? "").trim() === "";
|
|
|
+}
|
|
|
+
|
|
|
+function parseRowEndDateTime(row: ReservationRow): Date | null {
|
|
|
+ const et = row.endTimeRaw;
|
|
|
+ if (et != null && et !== "") {
|
|
|
+ const d = et instanceof Date ? et : new Date(et as string);
|
|
|
+ if (!isNaN(d.getTime())) return d;
|
|
|
+ }
|
|
|
+ const date = String(row.reservationDateRaw || "")
|
|
|
+ .trim()
|
|
|
+ .replace(/\//g, "-");
|
|
|
+ const slot = String(row.timeSlotRaw || "").trim();
|
|
|
+ const parts = slot.split("-");
|
|
|
+ const endTimeStr = parts.length > 1 ? parts[parts.length - 1].trim() : "";
|
|
|
+ if (date && endTimeStr) {
|
|
|
+ const normalized = endTimeStr.length <= 5 ? `${date} ${endTimeStr}:00` : `${date} ${endTimeStr}`;
|
|
|
+ const parsed = new Date(normalized.replace(/-/g, "/"));
|
|
|
+ if (!isNaN(parsed.getTime())) return parsed;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+/** 结束时间已过且当前时间超过结束 3 小时(与 1.vue 一致) */
|
|
|
+function isMoreThanThreeHoursAfterEnd(row: ReservationRow) {
|
|
|
+ const end = parseRowEndDateTime(row);
|
|
|
+ if (!end) return false;
|
|
|
+ return Date.now() > end.getTime() + THREE_HOURS_MS;
|
|
|
+}
|
|
|
+
|
|
|
function formatDateCol(val: string): string {
|
|
|
if (!val) return "—";
|
|
|
const d = new Date(val);
|
|
|
@@ -223,11 +276,39 @@ function getWeekDay(val: string): string {
|
|
|
}
|
|
|
|
|
|
function mapReservationRow(item: any): ReservationRow {
|
|
|
- const status = item.status ?? item.reservationStatus ?? 0;
|
|
|
const date = item.reservationDate ?? item.bookingDate ?? item.date ?? "";
|
|
|
const timeStart = item.startTime ?? item.bookingStartTime ?? "";
|
|
|
- const timeEnd = item.endTime ?? item.bookingEndTime ?? "";
|
|
|
- const customerName = item.name ?? item.userName ?? item.contactName ?? "—";
|
|
|
+ const timeEnd = item.bookingEndTime ?? item.endTime ?? "";
|
|
|
+ const customerName =
|
|
|
+ item.customerName != null && String(item.customerName).trim() !== ""
|
|
|
+ ? String(item.customerName).trim()
|
|
|
+ : (item.name ?? item.userName ?? item.contactName ?? "—");
|
|
|
+
|
|
|
+ const hasOrderStatusField =
|
|
|
+ (item.orderStatus != null && item.orderStatus !== "") || (item.statusType != null && item.statusType !== "");
|
|
|
+ const legacySt = Number(item.status ?? item.reservationStatus ?? NaN);
|
|
|
+
|
|
|
+ let orderStatus: number;
|
|
|
+ if (item.orderStatus != null && item.orderStatus !== "") {
|
|
|
+ orderStatus = Number(item.orderStatus);
|
|
|
+ } else if (item.statusType != null && item.statusType !== "") {
|
|
|
+ orderStatus = Number(item.statusType);
|
|
|
+ } else if (Number.isFinite(legacySt) && LEGACY_STATUS_TO_ORDER[legacySt] !== undefined) {
|
|
|
+ orderStatus = LEGACY_STATUS_TO_ORDER[legacySt];
|
|
|
+ } else if (Number.isFinite(legacySt)) {
|
|
|
+ orderStatus = legacySt;
|
|
|
+ } else {
|
|
|
+ orderStatus = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ let reason = String(item.reason ?? item.cancelReason ?? "").trim();
|
|
|
+ if (!hasOrderStatusField && legacySt === 2) reason = "";
|
|
|
+ if (!hasOrderStatusField && legacySt === 3 && !reason) reason = "商家取消";
|
|
|
+
|
|
|
+ const orderCostType = Number(item.orderCostType ?? item.costType ?? item.reservationCostType ?? 0);
|
|
|
+
|
|
|
+ const timeSlotRaw = item.timeSlot ?? (timeStart && timeEnd ? `${String(timeStart).trim()}-${String(timeEnd).trim()}` : "");
|
|
|
+
|
|
|
return {
|
|
|
id: item.id,
|
|
|
date: formatDateCol(date),
|
|
|
@@ -236,11 +317,23 @@ function mapReservationRow(item: any): ReservationRow {
|
|
|
peopleCount: item.peopleCount ?? item.personCount ?? item.guestCount ?? "—",
|
|
|
tableNo: item.tableNo ?? item.tableNumber ?? item.tableNumbers ?? "—",
|
|
|
timeRange: timeStart && timeEnd ? `${timeStart}-${timeEnd}` : (item.timeRange ?? "—"),
|
|
|
- amount: item.amount != null && item.amount !== "" ? item.amount : "—",
|
|
|
+ amount:
|
|
|
+ item.depositAmount != null && item.depositAmount !== ""
|
|
|
+ ? item.depositAmount
|
|
|
+ : item.amount != null && item.amount !== ""
|
|
|
+ ? item.amount
|
|
|
+ : "—",
|
|
|
customerName,
|
|
|
phone: item.phone ?? item.mobile ?? item.contactPhone ?? "—",
|
|
|
- status,
|
|
|
- statusText: STATUS_MAP[Number(status)] ?? item.statusText ?? "—",
|
|
|
+ status: orderStatus,
|
|
|
+ orderStatus,
|
|
|
+ orderCostType,
|
|
|
+ reason,
|
|
|
+ reservationDateRaw: String(date).trim(),
|
|
|
+ timeSlotRaw,
|
|
|
+ endTimeRaw: item.endTime ?? item.bookingEndTime ?? item.reservationEndTime,
|
|
|
+ statusText:
|
|
|
+ (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,
|
|
|
@@ -390,11 +483,6 @@ function handleRefund(row: ReservationRow) {
|
|
|
})
|
|
|
.catch(() => {});
|
|
|
}
|
|
|
-
|
|
|
-function handleViewReason(row: ReservationRow) {
|
|
|
- currentReason.value = row.cancelReason || row.refundReason || "暂无原因说明";
|
|
|
- reasonVisible.value = true;
|
|
|
-}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@@ -435,9 +523,4 @@ function handleViewReason(row: ReservationRow) {
|
|
|
white-space: nowrap;
|
|
|
cursor: default;
|
|
|
}
|
|
|
-.reason-text {
|
|
|
- margin: 0;
|
|
|
- color: #606266;
|
|
|
- word-break: break-all;
|
|
|
-}
|
|
|
</style>
|