酒店
build / task (16.x, npm, macOS-latest) (push) Canceled after 0s
build / task (16.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, npm, windows-latest) (push) Canceled after 0s
build / task (16.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (16.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (16.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (16.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, yarn, windows-latest) (push) Canceled after 0s
build / task (18.x, npm, macOS-latest) (push) Canceled after 0s
build / task (18.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, npm, windows-latest) (push) Canceled after 0s
build / task (18.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (18.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (18.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (18.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, yarn, windows-latest) (push) Canceled after 0s
build / task (20.x, npm, macOS-latest) (push) Canceled after 0s
build / task (20.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, npm, windows-latest) (push) Canceled after 0s
build / task (20.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (20.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (20.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (20.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, yarn, windows-latest) (push) Canceled after 0s
build / task (16.x, npm, macOS-latest) (push) Canceled after 0s
build / task (16.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, npm, windows-latest) (push) Canceled after 0s
build / task (16.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (16.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (16.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (16.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, yarn, windows-latest) (push) Canceled after 0s
build / task (18.x, npm, macOS-latest) (push) Canceled after 0s
build / task (18.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, npm, windows-latest) (push) Canceled after 0s
build / task (18.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (18.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (18.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (18.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, yarn, windows-latest) (push) Canceled after 0s
build / task (20.x, npm, macOS-latest) (push) Canceled after 0s
build / task (20.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, npm, windows-latest) (push) Canceled after 0s
build / task (20.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (20.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (20.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (20.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, yarn, windows-latest) (push) Canceled after 0s
This commit is contained in:
1 parent
e84e7c64a6
commit
f207ba9459
3 files changed
+273
-48
No files matched your search
@@ -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 模式下,单次任务最多再记多少条成功样例
|
||||
|
||||
@@ -211,26 +211,30 @@
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item v-if="form.direction === 'downstream' && form.scopeType === 'hotel'" label="指定酒店" prop="hotelIdList">
|
||||
<el-select
|
||||
v-model="form.hotelIdList"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:remote-method="searchHotels"
|
||||
:loading="hotelSearching"
|
||||
placeholder="输入名称/编码搜索后多选"
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="h in hotelOptions"
|
||||
:key="h.id"
|
||||
:label="hotelOptionLabel(h)"
|
||||
:value="String(h.id)"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="w-full">
|
||||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||||
<el-button type="primary" @click="openHotelPicker">选择酒店</el-button>
|
||||
<span class="text-gray-500 text-sm">已选 {{ form.hotelIdList.length }} 家(弹窗内可筛选、多选)</span>
|
||||
<el-button v-if="form.hotelIdList.length" link type="danger" @click="clearSelectedHotels">
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.hotelIdList.length"
|
||||
class="flex flex-wrap gap-1 max-h-32 overflow-auto p-2 border border-solid border-[var(--el-border-color)] rounded"
|
||||
>
|
||||
<el-tag
|
||||
v-for="id in form.hotelIdList"
|
||||
:key="id"
|
||||
closable
|
||||
class="mb-1"
|
||||
@close="removeSelectedHotel(id)"
|
||||
>
|
||||
{{ selectedHotelLabel(id) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div v-else class="text-gray-400 text-xs">请点击「选择酒店」打开列表勾选</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
@@ -248,6 +252,112 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 指定酒店:弹窗筛选 + 多选 -->
|
||||
<el-dialog
|
||||
v-model="hotelPickerVisible"
|
||||
title="选择授权酒店"
|
||||
width="960px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@opened="onHotelPickerOpened"
|
||||
>
|
||||
<el-form :inline="true" class="mb-2" @keyup.enter="loadHotelPicker(1)">
|
||||
<el-form-item label="名称">
|
||||
<el-input
|
||||
v-model="hotelPickerQuery.hotelName"
|
||||
clearable
|
||||
placeholder="酒店名称"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="编码">
|
||||
<el-input
|
||||
v-model="hotelPickerQuery.hotelCode"
|
||||
clearable
|
||||
placeholder="hotel_code"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源">
|
||||
<el-select
|
||||
v-model="hotelPickerQuery.sheetName"
|
||||
clearable
|
||||
placeholder="全部"
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in sm_hotel_data_source"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上架">
|
||||
<el-select
|
||||
v-model="hotelPickerQuery.upstreamStatus"
|
||||
clearable
|
||||
placeholder="全部"
|
||||
style="width: 110px"
|
||||
>
|
||||
<el-option label="上架" value="ACTIVE" />
|
||||
<el-option label="下架" value="OFFLINE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :loading="hotelPickerLoading" @click="loadHotelPicker(1)">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetHotelPickerQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mb-2 text-sm text-gray-500">
|
||||
本页勾选会并入已选;已选 {{ hotelPickerDraftIds.length }} 家
|
||||
(打开弹窗时带入表单已选 {{ form.hotelIdList.length }} 家)
|
||||
</div>
|
||||
<el-table
|
||||
ref="hotelPickerTableRef"
|
||||
:data="hotelPickerRows"
|
||||
border
|
||||
height="420"
|
||||
row-key="id"
|
||||
v-loading="hotelPickerLoading"
|
||||
@selection-change="onHotelPickerSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="45" reserve-selection />
|
||||
<el-table-column prop="hotelName" label="酒店名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="hotelCode" label="酒店编码" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="sheetName" label="数据来源" width="100" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ dataSourceLabel(row.sheetName) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="upstreamStatus" label="上架" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.upstreamStatus === 'OFFLINE' ? 'info' : 'success'" size="small">
|
||||
{{ row.upstreamStatus === "OFFLINE" ? "下架" : "上架" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="district" label="区域" min-width="120" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<div class="mt-3 flex justify-end">
|
||||
<el-pagination
|
||||
v-model:current-page="hotelPickerPage"
|
||||
v-model:page-size="hotelPickerSize"
|
||||
:total="hotelPickerTotal"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@current-change="(p: number) => loadHotelPicker(p)"
|
||||
@size-change="() => loadHotelPicker(1)"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="hotelPickerVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmHotelPicker">确定(已选 {{ hotelPickerDraftIds.length }})</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -278,9 +388,26 @@ const saving = ref(false)
|
||||
const districtPath = ref("")
|
||||
const districtCascaderKey = ref(0)
|
||||
const downstreamPartnerOptions = ref<any[]>([])
|
||||
/** 已选酒店展示缓存 id -> hotel */
|
||||
const hotelOptions = ref<any[]>([])
|
||||
const hotelSearching = ref(false)
|
||||
const pushingPartner = ref<string | null>(null)
|
||||
const hotelPickerVisible = ref(false)
|
||||
const hotelPickerLoading = ref(false)
|
||||
const hotelPickerTableRef = ref()
|
||||
const hotelPickerRows = ref<any[]>([])
|
||||
const hotelPickerPage = ref(1)
|
||||
const hotelPickerSize = ref(20)
|
||||
const hotelPickerTotal = ref(0)
|
||||
const hotelPickerDraftIds = ref<string[]>([])
|
||||
const hotelPickerDraftMap = ref<Map<string, any>>(new Map())
|
||||
const hotelPickerQuery = reactive({
|
||||
hotelName: "",
|
||||
hotelCode: "",
|
||||
sheetName: "",
|
||||
upstreamStatus: "",
|
||||
})
|
||||
/** 避免 selection-change 在程序勾选时清空草稿 */
|
||||
const hotelPickerSyncing = ref(false)
|
||||
const form = reactive<any>({
|
||||
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<string, any>()
|
||||
;[...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<string, any>()
|
||||
;[...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 {
|
||||
|
||||
@@ -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),点详情看缺口清单。下架/无产品/无日历记「预检跳过」。「重试」只适用于资源推送。"
|
||||
/>
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
@@ -32,15 +32,16 @@
|
||||
<el-option label="产品同步" value="product_sync" />
|
||||
<el-option label="日历同步" value="daily_sync" />
|
||||
<el-option label="预检跳过" value="push_skip" />
|
||||
<el-option label="部分未推上" value="push_partial" />
|
||||
<el-option label="订单查询" value="order_query" />
|
||||
<el-option label="订单详情" value="order_detail" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果" prop="success">
|
||||
<el-select v-model="state.queryForm.success" clearable placeholder="全部" style="width: 120px">
|
||||
<el-select v-model="state.queryForm.success" clearable placeholder="全部" style="width: 140px">
|
||||
<el-option label="成功" value="1" />
|
||||
<el-option label="失败" value="0" />
|
||||
<el-option label="跳过" value="2" />
|
||||
<el-option label="跳过/部分" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user