| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777 |
- <template>
- <div class="table-box appointment-info">
- <!-- 筛选:点击搜索后生效(与翻页联动);列表参数 reservationUserName、orderStatus -->
- <div class="filter-bar">
- <div class="filter-row">
- <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" :pagination="false">
- <template #tableHeader>
- <div class="table-header">
- <!-- <el-button type="primary" @click="handleNew"> 新建 </el-button> -->
- </div>
- </template>
- <template #statusText="{ row }">
- <el-tag :type="getStatusTagType(row.orderStatus)" size="small">
- {{ row.statusText }}
- </el-tag>
- </template>
- <template #remark="{ row }">
- <el-tooltip v-if="row.remark" :content="row.remark" placement="top" :show-after="200">
- <span class="remark-cell">{{ row.remark }}</span>
- </el-tooltip>
- <span v-else>—</span>
- </template>
- <!-- 操作按钮与 group_merchant scheduledInfo.vue 一致(orderStatus 判断顺序一致) -->
- <template #operation="scope">
- <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="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>
- <el-button @click="addTimeVisible = false"> 取消 </el-button>
- <el-button type="primary" :loading="addTimeLoading" @click="confirmAddTime"> 确定 </el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive } from "vue";
- import { ElMessage, ElMessageBox } from "element-plus";
- import ProTable from "@/components/ProTable/index.vue";
- import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
- import {
- reservationList,
- reservationAddTime,
- reservationCancel,
- reservationDelete,
- reservationRefund
- } from "@/api/modules/scheduledService";
- import { localGet } from "@/utils";
- export interface ReservationRow {
- id: number | string;
- /** 与商家端加时接口一致,缺省时用 id */
- reservationId?: number | string;
- _seq?: number;
- date: string;
- weekDay: string;
- location: string;
- peopleCount: string | number;
- tableNo: string;
- timeRange: string;
- 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;
- canRefund?: boolean;
- }
- const proTable = ref<ProTableInstance>();
- const columns: ColumnProps<ReservationRow>[] = [
- { prop: "_seq", label: "序号", width: 60, align: "center" },
- { prop: "date", label: "日期", width: 100, align: "center" },
- { prop: "weekDay", label: "星期", width: 70, align: "center" },
- { prop: "location", label: "位置", minWidth: 90, showOverflowTooltip: true },
- { prop: "peopleCount", label: "人数", width: 70, align: "center" },
- { prop: "tableNo", label: "桌号", minWidth: 90, showOverflowTooltip: true },
- { prop: "timeRange", label: "时间", width: 120, 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: 240, align: "center" }
- ];
- /**
- * 筛选状态:商家取消 / 用户取消 均为 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: 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 一致 */
- 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") ?? ""
- });
- /** 已生效的查询条件(点击搜索后写入;接口字段 reservationUserName、orderStatus) */
- const listFilter = reactive({
- reservationUserName: "",
- orderStatus: "" as number | string,
- startDate: "",
- endDate: ""
- });
- const searchForm = reactive({
- reservationUserName: "",
- orderStatus: "" as number | string,
- dateRange: [] as string[]
- });
- const addTimeVisible = ref(false);
- const addTimeLoading = ref(false);
- 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 === 2) 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";
- }
- 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 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 !== "") {
- 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);
- if (isNaN(d.getTime())) return String(val);
- const m = d.getMonth() + 1;
- const day = d.getDate();
- return `${String(m).padStart(2, "0")}月${String(day).padStart(2, "0")}日`;
- }
- function getWeekDay(val: string): string {
- if (!val) return "—";
- const d = new Date(val);
- if (isNaN(d.getTime())) return "—";
- const week = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
- return week[d.getDay()];
- }
- function mapReservationRow(item: any): ReservationRow {
- const date = item.reservationDate ?? item.bookingDate ?? item.date ?? "";
- const timeStart = item.startTime ?? item.bookingStartTime ?? "";
- 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,
- reservationId: item.reservationId ?? item.id,
- date: formatDateCol(date),
- weekDay: item.weekDay ?? getWeekDay(date),
- location: item.locationName ?? item.categoryName ?? item.location ?? "—",
- peopleCount: item.peopleCount ?? item.personCount ?? item.guestCount ?? "—",
- tableNo: item.tableNo ?? item.tableNumber ?? item.tableNumbers ?? "—",
- timeRange: timeStart && timeEnd ? `${timeStart}-${timeEnd}` : (item.timeRange ?? "—"),
- amount:
- item.depositAmount != null && item.depositAmount !== ""
- ? item.depositAmount
- : item.amount != null && item.amount !== ""
- ? item.amount
- : "—",
- customerName,
- phone: item.phone ?? item.mobile ?? item.contactPhone ?? "—",
- status: orderStatus,
- orderStatus,
- orderCostType,
- reason,
- reservationDateRaw: String(date).trim(),
- timeSlotRaw,
- endTimeRaw: item.endTime ?? item.bookingEndTime ?? item.reservationEndTime,
- /** scheduledInfo:orderStatus==4 时 reason 空=用户取消,非空=商家取消 */
- 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,
- canRefund: item.canRefund
- };
- }
- /** ProTable / useTable 约定:返回 { data: { list, total } } */
- async function getTableList(params: any) {
- const storeId = params.storeId ?? initParam.storeId;
- if (!storeId) {
- return { data: { list: [] as ReservationRow[], total: 0 } };
- }
- const pageNum = params.pageNum ?? 1;
- const pageSize = params.pageSize ?? 10;
- const req: Record<string, any> = {
- storeId: Number(storeId),
- pageNum,
- pageSize
- };
- 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.dateFrom = listFilter.startDate;
- req.dateTo = listFilter.endDate;
- }
- try {
- const res: any = await reservationList(req);
- const body = res?.data ?? res;
- const inner = body?.data ?? body;
- const list = inner?.list ?? inner?.records ?? (Array.isArray(inner) ? inner : []);
- const arr = Array.isArray(list) ? list : [];
- 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
- }));
- 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 } };
- }
- }
- function handleSearch() {
- listFilter.reservationUserName = searchForm.reservationUserName;
- listFilter.orderStatus = searchForm.orderStatus;
- if (searchForm.dateRange?.length === 2) {
- listFilter.startDate = searchForm.dateRange[0];
- listFilter.endDate = searchForm.dateRange[1];
- } else {
- listFilter.startDate = "";
- listFilter.endDate = "";
- }
- proTable.value?.getTableList();
- }
- function handleReset() {
- searchForm.reservationUserName = "";
- searchForm.orderStatus = "";
- searchForm.dateRange = [];
- listFilter.reservationUserName = "";
- listFilter.orderStatus = "";
- listFilter.startDate = "";
- listFilter.endDate = "";
- proTable.value?.getTableList();
- }
- function handleNew() {
- ElMessage.info("新建预约功能可在此处跳转或打开弹窗");
- }
- function handleCancel(row: ReservationRow) {
- 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.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 {
- const apiRes: any = await reservationAddTime({
- reservationId,
- addTimeMinutes: addTimeMinutesNum,
- addTimeStart
- });
- ElMessage.success(apiRes?.msg || "加时成功");
- addTimeVisible.value = false;
- resetAddTimeDialog();
- proTable.value?.getTableList();
- } catch (e: any) {
- // 业务 code≠200 时 indexApi 响应拦截器已提示 msg,避免重复 Toast
- if (typeof e?.code !== "number") {
- ElMessage.error(e?.msg || e?.data?.msg || e?.message || "加时失败");
- }
- } finally {
- addTimeLoading.value = false;
- }
- }
- function handleDelete(row: ReservationRow) {
- ElMessageBox.confirm("确认删除该预约记录?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- })
- .then(async () => {
- try {
- await reservationDelete({ id: row.id });
- ElMessage.success("删除成功");
- proTable.value?.getTableList();
- } catch (e: any) {
- ElMessage.error(e?.message || "删除失败");
- }
- })
- .catch(() => {});
- }
- function handleRefund(row: ReservationRow) {
- ElMessageBox.confirm("确认对该预约发起退款?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- })
- .then(async () => {
- try {
- await reservationRefund({ id: row.id });
- ElMessage.success("退款申请已提交");
- proTable.value?.getTableList();
- } catch (e: any) {
- ElMessage.error(e?.message || "退款失败");
- }
- })
- .catch(() => {});
- }
- </script>
- <style scoped lang="scss">
- .appointment-info {
- padding: 0;
- }
- .filter-bar {
- padding: 0 4px;
- margin-bottom: 12px;
- }
- .filter-row {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- align-items: center;
- }
- .filter-label {
- font-size: 14px;
- color: #606266;
- white-space: nowrap;
- }
- .table-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- padding: 12px 0;
- }
- .tip-text {
- font-size: 12px;
- color: #909399;
- }
- .remark-cell {
- display: inline-block;
- max-width: 120px;
- overflow: hidden;
- text-overflow: ellipsis;
- 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>
|