From f207ba94591aa41fe283be6abc439c9906136bc6 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 28 Oct 2026 15:45:15 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=92=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shengnuo-channel-config.template.yml | 2 + .../supermarket/smHotelChannelDept/index.vue | 307 +++++++++++++++--- .../smHotelDownstreamPushLog/index.vue | 12 +- 3 files changed, 273 insertions(+), 48 deletions(-) diff --git a/src/views/supermarket/smHotelChannelConfig/shengnuo-channel-config.template.yml b/src/views/supermarket/smHotelChannelConfig/shengnuo-channel-config.template.yml index f84c36b3..dc273dcb 100644 --- a/src/views/supermarket/smHotelChannelConfig/shengnuo-channel-config.template.yml +++ b/src/views/supermarket/smHotelChannelConfig/shengnuo-channel-config.template.yml @@ -34,6 +34,8 @@ daily-days: 15 resource-push-join-schedule: true # 推送流水:fail_only=只记失败;sample=失败+成功抽样;all=全记 +# 说明:房型对不上/计划失败等「部分未推上」会单独落 push_partial(不受 fail_only 限制), +# 在 PC「下游推送日志」筛动作「部分未推上」或结果「跳过/部分」,点详情看 unmatchedRooms / planErrors push-log-mode: fail_only # sample 模式下,单次任务最多再记多少条成功样例 diff --git a/src/views/supermarket/smHotelChannelDept/index.vue b/src/views/supermarket/smHotelChannelDept/index.vue index 2c944d29..4bb6092f 100644 --- a/src/views/supermarket/smHotelChannelDept/index.vue +++ b/src/views/supermarket/smHotelChannelDept/index.vue @@ -211,26 +211,30 @@ - - - +
+
+ 选择酒店 + 已选 {{ form.hotelIdList.length }} 家(弹窗内可筛选、多选) + + 清空 + +
+
+ + {{ selectedHotelLabel(id) }} + +
+
请点击「选择酒店」打开列表勾选
+
@@ -248,6 +252,112 @@ + + + + + + + + + + + + + + + + + + + + + + + 查询 + + 重置 + + +
+ 本页勾选会并入已选;已选 {{ hotelPickerDraftIds.length }} 家 + (打开弹窗时带入表单已选 {{ form.hotelIdList.length }} 家) +
+ + + + + + + + + + + + +
+ +
+ +
+ @@ -278,9 +388,26 @@ const saving = ref(false) const districtPath = ref("") const districtCascaderKey = ref(0) const downstreamPartnerOptions = ref([]) +/** 已选酒店展示缓存 id -> hotel */ const hotelOptions = ref([]) -const hotelSearching = ref(false) const pushingPartner = ref(null) +const hotelPickerVisible = ref(false) +const hotelPickerLoading = ref(false) +const hotelPickerTableRef = ref() +const hotelPickerRows = ref([]) +const hotelPickerPage = ref(1) +const hotelPickerSize = ref(20) +const hotelPickerTotal = ref(0) +const hotelPickerDraftIds = ref([]) +const hotelPickerDraftMap = ref>(new Map()) +const hotelPickerQuery = reactive({ + hotelName: "", + hotelCode: "", + sheetName: "", + upstreamStatus: "", +}) +/** 避免 selection-change 在程序勾选时清空草稿 */ +const hotelPickerSyncing = ref(false) const form = reactive({ id: undefined, direction: "upstream", @@ -324,6 +451,121 @@ const hotelOptionLabel = (h: any) => { return `${name}(${code})` } +const dataSourceLabel = (v?: string) => { + const hit = (sm_hotel_data_source.value || []).find((d: any) => String(d.value) === String(v)) + return hit?.label || v || "-" +} + +const selectedHotelLabel = (id: string) => { + const h = hotelOptions.value.find((x) => String(x.id) === String(id)) + return h ? hotelOptionLabel(h) : String(id) +} + +const removeSelectedHotel = (id: string) => { + form.hotelIdList = (form.hotelIdList || []).filter((x: string) => String(x) !== String(id)) + formRef.value?.validateField?.("hotelIdList") +} + +const clearSelectedHotels = () => { + form.hotelIdList = [] + formRef.value?.validateField?.("hotelIdList") +} + +const openHotelPicker = () => { + hotelPickerDraftIds.value = [...(form.hotelIdList || []).map(String)] + hotelPickerDraftMap.value = new Map( + hotelOptions.value + .filter((h) => hotelPickerDraftIds.value.includes(String(h.id))) + .map((h) => [String(h.id), h]), + ) + hotelPickerQuery.hotelName = "" + hotelPickerQuery.hotelCode = "" + hotelPickerQuery.sheetName = "" + hotelPickerQuery.upstreamStatus = "" + hotelPickerPage.value = 1 + hotelPickerVisible.value = true +} + +const onHotelPickerOpened = () => { + loadHotelPicker(1) +} + +const resetHotelPickerQuery = () => { + hotelPickerQuery.hotelName = "" + hotelPickerQuery.hotelCode = "" + hotelPickerQuery.sheetName = "" + hotelPickerQuery.upstreamStatus = "" + loadHotelPicker(1) +} + +const loadHotelPicker = async (page = 1) => { + hotelPickerLoading.value = true + hotelPickerPage.value = page + try { + const res: any = await fetchHotelList({ + current: page, + size: hotelPickerSize.value, + hotelName: hotelPickerQuery.hotelName || undefined, + hotelCode: hotelPickerQuery.hotelCode || undefined, + sheetName: hotelPickerQuery.sheetName || undefined, + upstreamStatus: hotelPickerQuery.upstreamStatus || undefined, + }) + hotelPickerRows.value = res?.data?.records || [] + hotelPickerTotal.value = Number(res?.data?.total || 0) + await nextTick() + syncHotelPickerSelection() + } catch (e: any) { + hotelPickerRows.value = [] + hotelPickerTotal.value = 0 + useMessage().error(e?.msg || e?.message || "加载酒店失败") + } finally { + hotelPickerLoading.value = false + } +} + +const syncHotelPickerSelection = () => { + const table = hotelPickerTableRef.value + if (!table) return + hotelPickerSyncing.value = true + try { + table.clearSelection?.() + for (const row of hotelPickerRows.value) { + if (row?.id != null && hotelPickerDraftIds.value.includes(String(row.id))) { + table.toggleRowSelection?.(row, true) + } + } + } finally { + nextTick(() => { + hotelPickerSyncing.value = false + }) + } +} + +const onHotelPickerSelectionChange = (rows: any[]) => { + if (hotelPickerSyncing.value) return + const pageIds = new Set(hotelPickerRows.value.map((r) => String(r.id))) + // 先去掉本页未勾选的,再并入本页勾选的(跨页保留) + hotelPickerDraftIds.value = hotelPickerDraftIds.value.filter((id) => !pageIds.has(id)) + for (const row of rows || []) { + const id = String(row.id) + if (!hotelPickerDraftIds.value.includes(id)) { + hotelPickerDraftIds.value.push(id) + } + hotelPickerDraftMap.value.set(id, row) + } +} + +const confirmHotelPicker = () => { + form.hotelIdList = [...hotelPickerDraftIds.value] + const map = new Map() + ;[...hotelOptions.value, ...hotelPickerDraftMap.value.values()].forEach((h) => { + if (h?.id != null) map.set(String(h.id), h) + }) + hotelOptions.value = form.hotelIdList.map((id: string) => map.get(String(id)) || { id, hotelName: id }) + hotelPickerVisible.value = false + formRef.value?.validateField?.("hotelIdList") +} + const loadDownstreamPartners = async () => { try { const [cfgRes, appRes]: any[] = await Promise.all([ @@ -395,27 +637,6 @@ const handlePushScope = async (row: any) => { } } -const searchHotels = async (keyword: string) => { - hotelSearching.value = true - try { - const res: any = await fetchHotelList({ - current: 1, - size: 30, - hotelName: keyword || undefined, - }) - const records = res?.data?.records || [] - const map = new Map() - ;[...hotelOptions.value, ...records].forEach((h) => { - if (h?.id != null) map.set(String(h.id), h) - }) - hotelOptions.value = Array.from(map.values()) - } catch { - // keep existing options - } finally { - hotelSearching.value = false - } -} - const ensureHotelOptions = async (ids: string[]) => { const missing = ids.filter((id) => !hotelOptions.value.some((h) => String(h.id) === id)) for (const id of missing) { @@ -570,7 +791,7 @@ const openForm = async (row?: any) => { if (form.hotelIdList.length) { await ensureHotelOptions(form.hotelIdList) } else { - await searchHotels("") + hotelOptions.value = [] } if (form.direction === "downstream" && form.scopeType === "dept" && form.districtAdcode != null) { try { diff --git a/src/views/supermarket/smHotelDownstreamPushLog/index.vue b/src/views/supermarket/smHotelDownstreamPushLog/index.vue index 24d54bb7..67a09e2e 100644 --- a/src/views/supermarket/smHotelDownstreamPushLog/index.vue +++ b/src/views/supermarket/smHotelDownstreamPushLog/index.vue @@ -6,7 +6,7 @@ :closable="false" show-icon class="mb-3" - title="资源推送默认只落失败(YAML:push-log-mode=fail_only)。慧订/环启打进我方的校验、下单、取消会全部落库,动作用「入站-」筛选,点详情看请求/响应。下架/无产品/无日历记「预检跳过」。「重试」只适用于资源推送。" + title="资源推送默认只落失败(YAML:push-log-mode=fail_only)。圣诺房型对不上/计划失败会记「部分未推上」(push_partial),点详情看缺口清单。下架/无产品/无日历记「预检跳过」。「重试」只适用于资源推送。" /> @@ -32,15 +32,16 @@ +
- + - + @@ -291,6 +292,7 @@ const actionLabel = (a?: string) => { product_sync: "产品同步", daily_sync: "日历同步", push_skip: "预检跳过", + push_partial: "部分未推上", order_query: "订单查询", order_detail: "订单详情", inbound_check: "入站-校验", @@ -305,13 +307,13 @@ const actionLabel = (a?: string) => { const successLabel = (s?: string) => { if (s === "1") return "成功" - if (s === "2") return "跳过" + if (s === "2") return "跳过/部分" return "失败" } const successTagType = (s?: string) => { if (s === "1") return "success" - if (s === "2") return "info" + if (s === "2") return "warning" return "danger" }