迁移调整

This commit is contained in:
zjf committed 2026-09-30 18:00:54 +08:00
1 parent f04d810223
commit 6b25b84ed6
11 files changed
+369 -196

No files matched your search

@@ -2,7 +2,7 @@ import request from "/@/utils/request"
export function fetchList(query?: Object) {
return request({
url: "/canteen/canteenHotel/page",
url: "/supermarket/smHotel/page",
method: "get",
params: query,
})
@@ -10,14 +10,14 @@ export function fetchList(query?: Object) {
export function getObj(id?: string) {
return request({
url: "/canteen/canteenHotel/" + id,
url: "/supermarket/smHotel/" + id,
method: "get",
})
}
export function addObj(obj?: Object) {
return request({
url: "/canteen/canteenHotel",
url: "/supermarket/smHotel",
method: "post",
data: obj,
})
@@ -25,7 +25,7 @@ export function addObj(obj?: Object) {
export function putObj(obj?: Object) {
return request({
url: "/canteen/canteenHotel",
url: "/supermarket/smHotel",
method: "put",
data: obj,
})
@@ -33,7 +33,7 @@ export function putObj(obj?: Object) {
export function delObjs(ids?: Object) {
return request({
url: "/canteen/canteenHotel",
url: "/supermarket/smHotel",
method: "delete",
data: ids,
})
@@ -42,7 +42,7 @@ export function delObjs(ids?: Object) {
/** 提交异步导入(立刻返回 taskId) */
export function importExcel(data: FormData) {
return request({
url: "/canteen/canteenHotel/import",
url: "/supermarket/smHotel/import",
method: "post",
data,
headers: { "Content-Type": "multipart/form-data" },
@@ -53,7 +53,7 @@ export function importExcel(data: FormData) {
/** 轮询导入进度 */
export function getImportStatus(taskId: string) {
return request({
url: `/canteen/canteenHotel/import/status/${taskId}`,
url: `/supermarket/smHotel/import/status/${taskId}`,
method: "get",
timeout: 30000,
})
@@ -62,7 +62,7 @@ export function getImportStatus(taskId: string) {
/** 同步上游酒店信息(智力分销) */
export function syncUpstreamHotel(id: string | number) {
return request({
url: `/canteen/canteenHotel/upstream/sync/${id}`,
url: `/supermarket/smHotel/upstream/sync/${id}`,
method: "post",
})
}
@@ -70,7 +70,7 @@ export function syncUpstreamHotel(id: string | number) {
/** 批量同步上游酒店信息 */
export function syncUpstreamHotelBatch(ids: Array<string | number>) {
return request({
url: "/canteen/canteenHotel/upstream/sync/batch",
url: "/supermarket/smHotel/upstream/sync/batch",
method: "post",
data: ids,
})
@@ -79,7 +79,7 @@ export function syncUpstreamHotelBatch(ids: Array<string | number>) {
/** 上游房型列表 */
export function upstreamRoomTypeList(data: { groupCode: string; hotelCode: string }) {
return request({
url: "/canteen/canteenHotel/upstream/roomTypeList",
url: "/supermarket/smHotel/upstream/roomTypeList",
method: "post",
data,
})
@@ -88,7 +88,7 @@ export function upstreamRoomTypeList(data: { groupCode: string; hotelCode: strin
/** 上游产品列表 */
export function upstreamProductList(data: { groupCode: string; hotelCode: string; channelCode?: string }) {
return request({
url: "/canteen/canteenHotel/upstream/productList",
url: "/supermarket/smHotel/upstream/productList",
method: "post",
data,
})
@@ -2,7 +2,7 @@ import request from "/@/utils/request"
export function fetchList(query?: Object) {
return request({
url: "/canteen/canteenHotelCallback/page",
url: "/supermarket/smHotelCallback/page",
method: "get",
params: query,
})
@@ -10,14 +10,14 @@ export function fetchList(query?: Object) {
export function getObj(id?: string | number) {
return request({
url: "/canteen/canteenHotelCallback/" + id,
url: "/supermarket/smHotelCallback/" + id,
method: "get",
})
}
export function retryTask(id: string | number) {
return request({
url: `/canteen/canteenHotelCallback/retry/${id}`,
url: `/supermarket/smHotelCallback/retry/${id}`,
method: "post",
})
}
@@ -2,22 +2,31 @@ import request from "/@/utils/request"
export function getPriceConfig() {
return request({
url: "/canteen/canteenHotel/priceConfig",
url: "/supermarket/smHotel/priceConfig",
method: "get",
})
}
export function savePriceConfig(data: Object) {
return request({
url: "/canteen/canteenHotel/priceConfig",
url: "/supermarket/smHotel/priceConfig",
method: "put",
data,
})
}
/** 按当前配置重算全部产品/日历卖价(不动订单) */
export function resyncSellPrices() {
return request({
url: "/supermarket/smHotel/priceConfig/resyncSell",
method: "post",
timeout: 300000,
})
}
export function pullFromUpstream(id: string | number) {
return request({
url: `/canteen/canteenHotel/mid/pull/${id}`,
url: `/supermarket/smHotel/mid/pull/${id}`,
method: "post",
timeout: 120000,
})
@@ -25,7 +34,7 @@ export function pullFromUpstream(id: string | number) {
export function pushToDownstream(id: string | number) {
return request({
url: `/canteen/canteenHotel/mid/push/${id}`,
url: `/supermarket/smHotel/mid/push/${id}`,
method: "post",
timeout: 120000,
})
@@ -33,7 +42,7 @@ export function pushToDownstream(id: string | number) {
export function pullAndPush(id: string | number) {
return request({
url: `/canteen/canteenHotel/mid/pullAndPush/${id}`,
url: `/supermarket/smHotel/mid/pullAndPush/${id}`,
method: "post",
timeout: 180000,
})
@@ -41,7 +50,7 @@ export function pullAndPush(id: string | number) {
export function pullAndPushBatch(ids: Array<string | number>) {
return request({
url: "/canteen/canteenHotel/mid/pullAndPush/batch",
url: "/supermarket/smHotel/mid/pullAndPush/batch",
method: "post",
data: ids,
timeout: 300000,
@@ -55,7 +64,7 @@ export function fetchAuthorizedHotels(query?: {
refresh?: boolean
}) {
return request({
url: "/canteen/canteenHotel/mid/authorizedHotels",
url: "/supermarket/smHotel/mid/authorizedHotels",
method: "get",
params: query,
timeout: 90000,
@@ -64,7 +73,7 @@ export function fetchAuthorizedHotels(query?: {
export function importAuthorizedHotels(hotels: Array<{ hotelId: string; hotelName?: string }>) {
return request({
url: "/canteen/canteenHotel/mid/authorizedHotels/import",
url: "/supermarket/smHotel/mid/authorizedHotels/import",
method: "post",
data: hotels,
})
@@ -73,7 +82,7 @@ export function importAuthorizedHotels(hotels: Array<{ hotelId: string; hotelNam
/** 同步上游酒店列表 + Redis 差集(异步,立刻返回 logId) */
export function syncHotelList(autoImport = true) {
return request({
url: "/canteen/canteenHotel/mid/syncHotelList",
url: "/supermarket/smHotel/mid/syncHotelList",
method: "post",
params: { autoImport },
timeout: 30000,
@@ -83,7 +92,7 @@ export function syncHotelList(autoImport = true) {
/** 查询单次同步进度 */
export function getSyncLog(id: string | number) {
return request({
url: `/canteen/canteenHotel/mid/syncLog/${id}`,
url: `/supermarket/smHotel/mid/syncLog/${id}`,
method: "get",
})
}
@@ -91,7 +100,7 @@ export function getSyncLog(id: string | number) {
/** 同步日志分页 */
export function fetchSyncLogPage(query?: Object) {
return request({
url: "/canteen/canteenHotel/mid/syncLog/page",
url: "/supermarket/smHotel/mid/syncLog/page",
method: "get",
params: query,
})
@@ -100,7 +109,7 @@ export function fetchSyncLogPage(query?: Object) {
/** 日更时间配置(只读) */
export function fetchSyncSchedule() {
return request({
url: "/canteen/canteenHotel/mid/syncSchedule",
url: "/supermarket/smHotel/mid/syncSchedule",
method: "get",
})
}
@@ -108,7 +117,7 @@ export function fetchSyncSchedule() {
/** 差集分页 type=new|offline */
export function fetchHotelDiff(query?: { type?: string; current?: number; size?: number }) {
return request({
url: "/canteen/canteenHotel/mid/hotelDiff",
url: "/supermarket/smHotel/mid/hotelDiff",
method: "get",
params: query,
})
@@ -117,7 +126,7 @@ export function fetchHotelDiff(query?: { type?: string; current?: number; size?:
/** 导入差集中的新增酒店 */
export function importDiffNewHotels(limit?: number) {
return request({
url: "/canteen/canteenHotel/mid/hotelDiff/importNew",
url: "/supermarket/smHotel/mid/hotelDiff/importNew",
method: "post",
params: { limit },
timeout: 180000,
@@ -127,7 +136,7 @@ export function importDiffNewHotels(limit?: number) {
/** 手动滚一天房价(异步) */
export function rollDailyPrice() {
return request({
url: "/canteen/canteenHotel/mid/rollDailyPrice",
url: "/supermarket/smHotel/mid/rollDailyPrice",
method: "post",
timeout: 30000,
})
@@ -136,17 +145,27 @@ export function rollDailyPrice() {
/** 异步拉取全部启用酒店详情(房型+产品+未来房价) */
export function pullAllActiveDetails(limit?: number) {
return request({
url: "/canteen/canteenHotel/mid/pullAllActiveDetails",
url: "/supermarket/smHotel/mid/pullAllActiveDetails",
method: "post",
params: limit != null ? { limit } : undefined,
timeout: 30000,
})
}
/** 根据同步日志失败明细,只重试失败酒店(详情/滚价) */
export function retryFailedFromLog(logId: string | number) {
return request({
url: "/supermarket/smHotel/mid/retryFailedFromLog",
method: "post",
params: { logId },
timeout: 30000,
})
}
/** 酒店详情(房型/产品/日历预览) */
export function getHotelDetail(id: string | number) {
return request({
url: `/canteen/canteenHotel/detail/${id}`,
url: `/supermarket/smHotel/detail/${id}`,
method: "get",
})
}
@@ -154,7 +173,7 @@ export function getHotelDetail(id: string | number) {
/** 酒店日历分页 */
export function fetchHotelDaily(id: string | number, query?: Object) {
return request({
url: `/canteen/canteenHotel/detail/${id}/daily`,
url: `/supermarket/smHotel/detail/${id}/daily`,
method: "get",
params: query,
})
@@ -2,7 +2,7 @@ import request from "/@/utils/request"
export function fetchList(query?: Object) {
return request({
url: "/canteen/canteenHotelOrder/page",
url: "/supermarket/smHotelOrder/page",
method: "get",
params: query,
})
@@ -10,14 +10,14 @@ export function fetchList(query?: Object) {
export function getObj(id?: string) {
return request({
url: "/canteen/canteenHotelOrder/" + id,
url: "/supermarket/smHotelOrder/" + id,
method: "get",
})
}
export function cancelOrder(id: string | number, reason?: string) {
return request({
url: `/canteen/canteenHotelOrder/cancel/${id}`,
url: `/supermarket/smHotelOrder/cancel/${id}`,
method: "post",
data: { reason: reason || "后台取消" },
})
@@ -26,7 +26,7 @@ export function cancelOrder(id: string | number, reason?: string) {
/** 后台试下单(调上游) */
export function createAdminOrder(data: Object) {
return request({
url: "/canteen/canteenHotelOrder/create",
url: "/supermarket/smHotelOrder/create",
method: "post",
data,
timeout: 120000,
@@ -35,7 +35,7 @@ export function createAdminOrder(data: Object) {
export function compensateOrder(id: string | number) {
return request({
url: `/canteen/canteenHotelOrder/compensate/${id}`,
url: `/supermarket/smHotelOrder/compensate/${id}`,
method: "post",
})
}
@@ -1,76 +0,0 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-card shadow="never" class="max-w-xl">
<template #header>
<span>酒店全局加价配置</span>
</template>
<el-alert
type="info"
:closable="false"
show-icon
class="mb-4"
title="上游底价 × (1 + 加价%/100) = 推给下游的卖价。例如上游 100 元、加价 15%,下游看到 115 元。"
/>
<el-form v-loading="loading" label-width="120px">
<el-form-item label="加价百分比">
<el-input-number v-model="form.markupPercent" :min="0" :max="500" :precision="2" :step="1" />
<span class="ml-2 text-gray-500">%</span>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="选填" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" v-auth="'canteen_canteenHotelPrice_edit'" @click="save">
保存
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="systemCanteenHotelPrice">
import { getPriceConfig, savePriceConfig } from "/@/api/canteen/canteenHotelMid"
import { useMessage } from "/@/hooks/message"
const loading = ref(false)
const saving = ref(false)
const form = reactive({
markupPercent: 0,
remark: "",
})
const load = async () => {
loading.value = true
try {
const res: any = await getPriceConfig()
const data = res?.data || {}
form.markupPercent = Number(data.markupPercent || 0)
form.remark = data.remark || ""
} catch (err: any) {
useMessage().error(err?.msg || "加载失败")
} finally {
loading.value = false
}
}
const save = async () => {
saving.value = true
try {
await savePriceConfig({
markupPercent: form.markupPercent,
remark: form.remark,
})
useMessage().success("保存成功")
load()
} catch (err: any) {
useMessage().error(err?.msg || "保存失败")
} finally {
saving.value = false
}
}
onMounted(load)
</script>
@@ -4,10 +4,6 @@
<el-descriptions v-if="hotel" :column="2" border class="mb-4">
<el-descriptions-item label="酒店名称">{{ hotel.hotelName || "-" }}</el-descriptions-item>
<el-descriptions-item label="酒店编码">{{ hotel.hotelCode || "-" }}</el-descriptions-item>
<el-descriptions-item label="集团编码">{{ hotel.groupCode || "-" }}</el-descriptions-item>
<el-descriptions-item label="外部ID">{{ hotel.sourceId || "-" }}</el-descriptions-item>
<el-descriptions-item label="外部集团ID">{{ hotel.sGroupId || "-" }}</el-descriptions-item>
<el-descriptions-item label="外部酒店ID">{{ hotel.sHotelId || "-" }}</el-descriptions-item>
<el-descriptions-item label="来源Sheet">{{ hotel.sheetName || "-" }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ hotel.remark || "-" }}</el-descriptions-item>
</el-descriptions>
@@ -50,7 +46,7 @@
<el-table-column prop="priceCode" label="价格码" min-width="100" show-overflow-tooltip />
<el-table-column label="操作" width="110" fixed="right">
<template #default="scope">
<el-button text type="primary" v-auth="'canteen_canteenHotel_edit'" @click="openOrderByProduct(scope.row)">
<el-button text type="primary" v-auth="'supermarket_smHotel_edit'" @click="openOrderByProduct(scope.row)">
试下单
</el-button>
</template>
@@ -83,7 +79,7 @@
<el-button
text
type="primary"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
:disabled="scope.row.dailyStatus !== 1 || !(scope.row.stock > 0)"
@click="openOrderByDaily(scope.row)"
>
@@ -127,7 +123,7 @@
<el-input-number v-model="orderForm.count" :min="1" :max="10" />
</el-form-item>
<el-form-item label="联系人" required>
<el-input v-model="orderForm.contactName" placeholder="姓名" />
<el-input v-model="orderForm.contactName" placeholder="中文姓名,如:张三" />
</el-form-item>
<el-form-item label="手机号" required>
<el-input v-model="orderForm.contactPhone" placeholder="手机号" />
@@ -157,7 +153,7 @@
plain
size="small"
:loading="cancelLoading"
v-auth="'canteen_canteenHotelOrder_cancel'"
v-auth="'supermarket_smHotelOrder_cancel'"
:disabled="!lastOrder.platformNo || lastOrder.orderStatus === 'CANCELLED'"
@click="handleCancelLast"
>
@@ -167,7 +163,7 @@
</div>
<template #footer>
<el-button @click="orderVisible = false">关闭</el-button>
<el-button type="primary" :loading="orderSubmitting" v-auth="'canteen_canteenHotel_edit'" @click="submitOrder">
<el-button type="primary" :loading="orderSubmitting" v-auth="'supermarket_smHotel_edit'" @click="submitOrder">
提交下单
</el-button>
</template>
@@ -175,9 +171,9 @@
</el-drawer>
</template>
<script setup lang="ts" name="CanteenHotelDetail">
import { getHotelDetail, fetchHotelDaily } from "/@/api/canteen/canteenHotelMid"
import { createAdminOrder, cancelOrder } from "/@/api/canteen/canteenHotelOrder"
<script setup lang="ts" name="SmHotelDetail">
import { getHotelDetail, fetchHotelDaily } from "/@/api/supermarket/smHotelMid"
import { createAdminOrder, cancelOrder } from "/@/api/supermarket/smHotelOrder"
import { useMessage, useMessageBox } from "/@/hooks/message"
const visible = ref(false)
@@ -207,7 +203,7 @@ const orderForm = reactive({
checkInDate: "",
checkOutDate: "",
count: 1,
contactName: "测试用户",
contactName: "张三",
contactPhone: "13800138000",
})
@@ -338,7 +334,7 @@ const openOrderByDaily = (daily: any) => {
const fillOrderForm = ( partial: Partial<typeof orderForm>) => {
Object.assign(orderForm, {
count: 1,
contactName: orderForm.contactName || "测试用户",
contactName: orderForm.contactName || "张三",
contactPhone: orderForm.contactPhone || "13800138000",
...partial,
})
@@ -8,20 +8,8 @@
@close="closeDialog"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" v-loading="loading">
<el-form-item label="外部ID" prop="sourceId">
<el-input v-model="form.sourceId" placeholder="Excel 中的 id,选填" maxlength="64" />
</el-form-item>
<el-form-item label="集团编码" prop="groupCode">
<el-input v-model="form.groupCode" placeholder="group_code" maxlength="64" />
</el-form-item>
<el-form-item label="酒店编码" prop="hotelCode">
<el-input v-model="form.hotelCode" placeholder="hotel_code" maxlength="64" />
</el-form-item>
<el-form-item label="外部集团ID" prop="sGroupId">
<el-input v-model="form.sGroupId" placeholder="s_group_id" maxlength="64" />
</el-form-item>
<el-form-item label="外部酒店ID" prop="sHotelId">
<el-input v-model="form.sHotelId" placeholder="s_hotel_id" maxlength="64" />
<el-input v-model="form.hotelCode" placeholder="hotel_code(上游酒店ID)" maxlength="64" />
</el-form-item>
<el-form-item label="酒店名称" prop="hotelName">
<el-input v-model="form.hotelName" placeholder="hotel_name" maxlength="256" />
@@ -37,10 +25,10 @@
</el-dialog>
</template>
<script setup lang="ts" name="CanteenHotelForm">
<script setup lang="ts" name="SmHotelForm">
import type { FormInstance } from "element-plus"
import { useMessage } from "/@/hooks/message"
import { addObj, putObj, getObj } from "/@/api/canteen/canteenHotel"
import { addObj, putObj, getObj } from "/@/api/supermarket/smHotel"
const emit = defineEmits(["refresh"])
@@ -50,13 +38,14 @@ const formRef = ref<FormInstance>()
const defaultForm = () => ({
id: undefined as string | undefined,
sourceId: "",
groupCode: "",
hotelCode: "",
sGroupId: "",
sHotelId: "",
hotelName: "",
remark: "",
// 库字段保留,编辑时带回避免被清空;页面不展示
sourceId: "",
groupCode: "",
sGroupId: "",
sHotelId: "",
})
const form = reactive(defaultForm())
@@ -87,13 +76,13 @@ const openDialog = async (id?: string) => {
const data = res.data
if (!data) return
form.id = data.id
form.sourceId = data.sourceId || ""
form.groupCode = data.groupCode || ""
form.hotelCode = data.hotelCode || ""
form.sGroupId = data.sGroupId || ""
form.sHotelId = data.sHotelId || ""
form.hotelName = data.hotelName || ""
form.remark = data.remark || ""
form.sourceId = data.sourceId || ""
form.groupCode = data.groupCode || ""
form.sGroupId = data.sGroupId || ""
form.sHotelId = data.sHotelId || ""
} catch (err: any) {
useMessage().error(err.msg || "加载失败")
} finally {
@@ -113,13 +102,13 @@ const submitForm = async () => {
try {
const payload = {
id: form.id,
sourceId: form.sourceId?.trim() || null,
groupCode: form.groupCode?.trim() || null,
hotelCode: form.hotelCode?.trim() || null,
sGroupId: form.sGroupId?.trim() || null,
sHotelId: form.sHotelId?.trim() || null,
hotelName: form.hotelName?.trim() || null,
remark: form.remark?.trim() || null,
sourceId: form.sourceId?.trim() || null,
groupCode: form.groupCode?.trim() || null,
sGroupId: form.sGroupId?.trim() || null,
sHotelId: form.sHotelId?.trim() || null,
}
form.id ? await putObj(payload) : await addObj(payload)
useMessage().success(form.id ? "修改成功" : "新增成功")
@@ -9,12 +9,6 @@
<el-form-item label="酒店编码" prop="hotelCode">
<el-input v-model="state.queryForm.hotelCode" placeholder="hotel_code" clearable />
</el-form-item>
<el-form-item label="集团编码" prop="groupCode">
<el-input v-model="state.queryForm.groupCode" placeholder="group_code" clearable />
</el-form-item>
<el-form-item label="外部ID" prop="sourceId">
<el-input v-model="state.queryForm.sourceId" placeholder="Excel 中的 id" clearable />
</el-form-item>
<el-form-item label="上游状态" prop="upstreamStatus">
<el-select v-model="state.queryForm.upstreamStatus" clearable placeholder="全部" style="width: 120px">
<el-option label="启用" value="ACTIVE" />
@@ -33,7 +27,7 @@
icon="folder-add"
type="primary"
class="ml10"
v-auth="'canteen_canteenHotel_add'"
v-auth="'supermarket_smHotel_add'"
@click="formDialogRef.openDialog()"
>
新 增
@@ -43,7 +37,7 @@
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'canteen_canteenHotel_del'"
v-auth="'supermarket_smHotel_del'"
@click="handleDelete(selectObjs)"
>
删除
@@ -53,7 +47,7 @@
type="primary"
class="ml10"
:loading="syncListLoading"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
@click="handleSyncHotelList"
>
同步上游酒店
@@ -63,7 +57,7 @@
icon="Document"
type="primary"
class="ml10"
v-auth="'canteen_canteenHotel_view'"
v-auth="'supermarket_smHotel_view'"
@click="openSyncLogDialog"
>
同步日志
@@ -74,7 +68,7 @@
type="primary"
class="ml10"
:loading="authHotelLoading"
v-auth="'canteen_canteenHotel_view'"
v-auth="'supermarket_smHotel_view'"
@click="openAuthorizedHotels"
>
授权酒店 / 差集
@@ -83,7 +77,7 @@
plain
icon="Upload"
class="ml10"
v-auth="'canteen_canteenHotel_import'"
v-auth="'supermarket_smHotel_import'"
@click="openImport"
>
Excel(兼容)
@@ -94,7 +88,7 @@
type="warning"
class="ml10"
:loading="syncListLoading"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
@click="handlePullAllActiveDetails"
>
拉取全部启用详情
@@ -106,7 +100,7 @@
class="ml10"
:disabled="multiple"
:loading="syncLoading"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
@click="handleSyncBatch"
>
拉取选中详情
@@ -118,7 +112,7 @@
class="ml10"
:disabled="multiple"
:loading="pushLoading"
v-auth="'canteen_canteenHotel_push'"
v-auth="'supermarket_smHotel_push'"
@click="handlePullPushBatch"
>
拉取并推下游
@@ -136,11 +130,7 @@
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="sourceId" label="外部ID" min-width="100" show-overflow-tooltip />
<el-table-column prop="groupCode" label="集团编码" min-width="100" show-overflow-tooltip />
<el-table-column prop="hotelCode" label="酒店编码" min-width="120" show-overflow-tooltip />
<el-table-column prop="sGroupId" label="外部集团ID" min-width="110" show-overflow-tooltip />
<el-table-column prop="sHotelId" label="外部酒店ID" min-width="110" show-overflow-tooltip />
<el-table-column prop="hotelName" label="酒店名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="upstreamStatus" label="上游状态" width="100">
<template #default="scope">
@@ -148,6 +138,15 @@
<el-tag v-else type="success" size="small">启用</el-tag>
</template>
</el-table-column>
<el-table-column prop="roomCount" label="房型数" width="90" align="center">
<template #default="scope">{{ scope.row.roomCount ?? 0 }}</template>
</el-table-column>
<el-table-column prop="productCount" label="产品数" width="90" align="center">
<template #default="scope">{{ scope.row.productCount ?? 0 }}</template>
</el-table-column>
<el-table-column prop="dailyCount" label="日历数" width="90" align="center">
<template #default="scope">{{ scope.row.dailyCount ?? 0 }}</template>
</el-table-column>
<el-table-column prop="sheetName" label="来源Sheet" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="380" fixed="right">
<template #default="scope">
@@ -155,7 +154,7 @@
icon="view"
text
type="primary"
v-auth="'canteen_canteenHotel_view'"
v-auth="'supermarket_smHotel_view'"
@click="detailRef.open(scope.row.id)"
>
详情
@@ -164,7 +163,7 @@
icon="edit-pen"
text
type="primary"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
@@ -173,7 +172,7 @@
icon="Refresh"
text
type="primary"
v-auth="'canteen_canteenHotel_edit'"
v-auth="'supermarket_smHotel_edit'"
:loading="syncingId === scope.row.id"
@click="handleSyncOne(scope.row.id)"
>
@@ -183,7 +182,7 @@
icon="Promotion"
text
type="primary"
v-auth="'canteen_canteenHotel_push'"
v-auth="'supermarket_smHotel_push'"
:loading="pushingId === scope.row.id"
@click="handlePullPushOne(scope.row.id)"
>
@@ -193,7 +192,7 @@
icon="delete"
text
type="primary"
v-auth="'canteen_canteenHotel_del'"
v-auth="'supermarket_smHotel_del'"
@click="handleDelete([scope.row.id])"
>
删除
@@ -246,16 +245,18 @@
:closable="false"
show-icon
class="mb-3"
title="主路径:点「同步上游酒店」强制 getHotelList,用 Redis 差集算出新增/下线,并可自动导入新增。Excel 仅兼容旧数据。详情靠「拉取选中/全部启用详情」或每日滚一天 + 回调。"
title="主路径:点「同步上游并比对」拉取 getHotelList、算差集,并自动按批循环导入全部新增(无需再手点)。「补导剩余新增」仅在自动导入中断时用。详情靠拉取详情 / 日滚价 / 回调。"
/>
<div class="mb-3 flex flex-wrap items-center gap-2">
<el-button type="primary" :loading="syncListLoading" v-auth="'canteen_canteenHotel_edit'" @click="handleSyncHotelList">
<el-button type="primary" :loading="syncListLoading" v-auth="'supermarket_smHotel_edit'" @click="handleSyncHotelList">
同步上游并比对
</el-button>
<el-tag v-if="diffSummary" type="success">新增 {{ diffSummary.newCount ?? 0 }}</el-tag>
<el-tag v-if="diffSummary" type="warning">下线 {{ diffSummary.offlineCount ?? 0 }}</el-tag>
<el-tag v-if="diffSummary" type="info">上游 {{ diffSummary.upstreamCount ?? 0 }} / 本地 {{ diffSummary.localCount ?? 0 }}</el-tag>
<el-button :loading="diffImporting" v-auth="'canteen_canteenHotel_add'" @click="handleImportDiffNew">导入全部新增</el-button>
<el-button :loading="diffImporting" v-auth="'supermarket_smHotel_add'" @click="handleImportDiffNew">
补导剩余新增
</el-button>
</div>
<el-tabs v-model="authTab" @tab-change="onAuthTabChange">
<el-tab-pane label="授权列表" name="all" />
@@ -310,7 +311,7 @@
v-if="authTab === 'all'"
type="primary"
:loading="authHotelImporting"
v-auth="'canteen_canteenHotel_add'"
v-auth="'supermarket_smHotel_add'"
@click="importSelectedAuthHotels"
>
导入选中到本地
@@ -425,14 +426,27 @@
<el-dialog v-model="failDetailVisible" title="失败明细" width="720px" destroy-on-close>
<div class="mb-2 text-sm text-gray-600">{{ failDetailSummary }}</div>
<pre class="sync-fail-detail">{{ failDetailText || "无明细" }}</pre>
<template #footer>
<div class="flex justify-end gap-2">
<el-button @click="failDetailVisible = false">关闭</el-button>
<el-button
v-if="canRetryFailed"
type="primary"
:loading="retryFailedLoading"
@click="onRetryFailed"
>
只重试这些失败酒店
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenHotel">
<script setup lang="ts" name="systemSmHotel">
import type { UploadFile, UploadInstance } from "element-plus"
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchList, delObjs, importExcel, getImportStatus } from "/@/api/canteen/canteenHotel"
import { fetchList, delObjs, importExcel, getImportStatus } from "/@/api/supermarket/smHotel"
import {
pullFromUpstream,
pullAndPush,
@@ -446,7 +460,8 @@ import {
fetchSyncLogPage,
fetchSyncSchedule,
pullAllActiveDetails,
} from "/@/api/canteen/canteenHotelMid"
retryFailedFromLog,
} from "/@/api/supermarket/smHotelMid"
import { useMessage, useMessageBox } from "/@/hooks/message"
const FormDialog = defineAsyncComponent(() => import("./form.vue"))
@@ -502,13 +517,61 @@ const syncScheduleHint = ref("日更时间可在 Nacos/配置 shangyou-hotel.hot
const failDetailVisible = ref(false)
const failDetailText = ref("")
const failDetailSummary = ref("")
const failDetailLogId = ref<string | number | null>(null)
const failDetailSyncType = ref("")
const retryFailedLoading = ref(false)
const canRetryFailed = computed(
() =>
!!failDetailLogId.value &&
!!failDetailText.value &&
(failDetailSyncType.value === "DETAIL_PULL" || failDetailSyncType.value === "PRICE_ROLL"),
)
const openFailDetail = (row: any) => {
failDetailSummary.value = row.errorMsg || `失败 ${row.failCount || 0} 家`
failDetailText.value = row.failDetail || row.errorMsg || ""
failDetailLogId.value = row.id || null
failDetailSyncType.value = row.syncType || ""
failDetailVisible.value = true
}
const onRetryFailed = async () => {
if (!failDetailLogId.value) return
try {
await useMessageBox().confirm(
failDetailSyncType.value === "PRICE_ROLL"
? "将只重试本日志失败明细中的酒店滚价(同日),确定?"
: "将只重试本日志失败明细中的酒店详情拉取,确定?",
)
} catch {
return
}
retryFailedLoading.value = true
try {
const res: any = await retryFailedFromLog(failDetailLogId.value)
const data = res?.data || {}
if (!data.logId) {
useMessage().error(res?.msg || data.message || "提交重试失败")
return
}
failDetailVisible.value = false
syncProgressVisible.value = true
syncProgressPct.value = 1
syncProgressStatus.value = ""
syncProgressText.value = res?.msg || "已提交失败重试…"
useMessage().success(res?.msg || "已提交失败重试")
pollSyncLog(data.logId)
if (syncLogVisible.value) {
loadSyncLogs(syncLogPage.value)
}
} catch (err: any) {
useMessage().error(err?.msg || err?.message || "提交重试失败")
} finally {
retryFailedLoading.value = false
}
}
const stopSyncPoll = () => {
if (syncPollTimer != null) {
clearInterval(syncPollTimer)
@@ -551,15 +614,18 @@ const applySyncLogUi = (log: any) => {
stopSyncPoll()
if (log.syncType === "DETAIL_PULL") {
useMessage().success(
`全量详情完成:成功 ${log.successCount || 0},失败 ${log.failCount || 0},跳过 ${log.skipCount || 0}`,
`${log.triggerType === "RETRY" ? "失败重试" : "全量详情"}完成:成功 ${log.successCount || 0},失败 ${log.failCount || 0},跳过 ${log.skipCount || 0}`,
)
if (log.failCount > 0 && (log.failDetail || log.errorMsg)) {
openFailDetail(log)
}
} else if (log.syncType === "PRICE_ROLL") {
useMessage().success(
`滚价完成:成功 ${log.successCount || 0},失败 ${log.failCount || 0}`,
`${log.triggerType === "RETRY" ? "滚价失败重试" : "滚价"}完成:成功 ${log.successCount || 0},失败 ${log.failCount || 0}`,
)
if (log.triggerType === "RETRY" && log.failCount > 0 && (log.failDetail || log.errorMsg)) {
openFailDetail(log)
}
} else {
useMessage().success(
`同步完成:上游 ${log.upstreamCount || 0},新增 ${log.newCount || 0},下线 ${log.offlineCount || 0},导入 ${log.imported || 0},停用 ${log.offlineMarked || 0}`,
@@ -701,7 +767,7 @@ const loadHotelDiff = async (type: string, page?: number) => {
const handleSyncHotelList = async () => {
try {
await useMessageBox().confirm(
"将异步同步上游授权酒店列表并做差集(可达十几万,建议低峰)。确定后立即返回进度条,后台继续执行;自动导入「新增」。",
"将异步同步上游授权酒店列表并做差集(可达十几万,建议低峰)。确定后立即返回进度;后台会自动按批循环导入全部新增,无需再手动导入。",
)
} catch {
return
@@ -67,12 +67,12 @@
<el-table-column prop="finishTime" label="完成时间" min-width="160" />
<el-table-column label="操作" width="140" fixed="right">
<template #default="scope">
<el-button text type="primary" v-auth="'canteen_canteenHotelCallback_view'" @click="openDetail(scope.row)">详情</el-button>
<el-button text type="primary" v-auth="'supermarket_smHotelCallback_view'" @click="openDetail(scope.row)">详情</el-button>
<el-button
v-if="scope.row.status === 'FAILED' || scope.row.status === 'SKIPPED' || scope.row.status === 'SUCCESS'"
text
type="warning"
v-auth="'canteen_canteenHotelCallback_retry'"
v-auth="'supermarket_smHotelCallback_retry'"
@click="handleRetry(scope.row)"
>
重试
@@ -106,9 +106,9 @@
</div>
</template>
<script setup lang="ts" name="systemCanteenHotelCallback">
<script setup lang="ts" name="systemSmHotelCallback">
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchList, getObj, retryTask } from "/@/api/canteen/canteenHotelCallback"
import { fetchList, getObj, retryTask } from "/@/api/supermarket/smHotelCallback"
import { useMessage, useMessageBox } from "/@/hooks/message"
const queryRef = ref()
@@ -90,7 +90,7 @@
v-if="scope.row.compensateStatus === 'WAITING' || scope.row.compensateStatus === 'NEED_MANUAL' || scope.row.orderStatus === 'PENDING_CONFIRM'"
text
type="warning"
v-auth="'canteen_canteenHotelOrder_view'"
v-auth="'supermarket_smHotelOrder_view'"
@click="handleCompensate(scope.row)"
>
补偿查询
@@ -99,7 +99,7 @@
v-if="scope.row.orderStatus !== 'CANCELLED' && scope.row.platformNo"
text
type="primary"
v-auth="'canteen_canteenHotelOrder_cancel'"
v-auth="'supermarket_smHotelOrder_cancel'"
@click="handleCancel(scope.row)"
>
取消
@@ -112,9 +112,9 @@
</div>
</template>
<script setup lang="ts" name="systemCanteenHotelOrder">
<script setup lang="ts" name="systemSmHotelOrder">
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchList, cancelOrder, compensateOrder } from "/@/api/canteen/canteenHotelOrder"
import { fetchList, cancelOrder, compensateOrder } from "/@/api/supermarket/smHotelOrder"
import { useMessage, useMessageBox } from "/@/hooks/message"
const queryRef = ref()
@@ -0,0 +1,179 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-card shadow="never" class="max-w-xl">
<template #header>
<span>酒店全局售价调整</span>
</template>
<el-alert type="info" :closable="false" show-icon class="mb-4" :title="hintTitle" />
<el-alert
type="warning"
:closable="false"
show-icon
class="mb-4"
title="保存配置不会立刻改库存卖价。需要时再点「保存并立刻全局同步」:按底价重算全部产品/日历卖价,历史订单价格不变。"
/>
<el-form v-loading="loading" label-width="120px">
<el-form-item label="调整方式">
<el-radio-group v-model="adjustMode">
<el-radio-button label="MARKUP">加价</el-radio-button>
<el-radio-button label="DISCOUNT">折扣</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="adjustMode === 'DISCOUNT' ? '折扣百分比' : '加价百分比'">
<el-input-number
v-model="adjustPercent"
:min="0"
:max="adjustMode === 'DISCOUNT' ? 99.99 : 500"
:precision="2"
:step="1"
/>
<span class="ml-2 text-gray-500">%</span>
</el-form-item>
<el-form-item label="预览">
<span class="text-gray-600">{{ previewText }}</span>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="选填" />
</el-form-item>
<el-form-item>
<el-button :loading="saving" v-auth="'supermarket_smHotelPrice_edit'" @click="saveOnly">
仅保存配置
</el-button>
<el-button
type="primary"
:loading="savingAndSync"
v-auth="'supermarket_smHotelPrice_edit'"
class="ml-2"
@click="saveAndResync"
>
保存并立刻全局同步
</el-button>
</el-form-item>
<el-form-item v-if="lastSyncMsg">
<span class="text-sm text-gray-500">{{ lastSyncMsg }}</span>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="systemSmHotelPrice">
import { getPriceConfig, savePriceConfig, resyncSellPrices } from "/@/api/supermarket/smHotelMid"
import { useMessage, useMessageBox } from "/@/hooks/message"
const loading = ref(false)
const saving = ref(false)
const savingAndSync = ref(false)
const lastSyncMsg = ref("")
/** MARKUP=加价,DISCOUNT=折扣;落库时折扣存为负数 markupPercent */
const adjustMode = ref<"MARKUP" | "DISCOUNT">("MARKUP")
const adjustPercent = ref(0)
const form = reactive({
remark: "",
})
const hintTitle = computed(() => {
if (adjustMode.value === "DISCOUNT") {
return "卖价 = 上游底价 × (1 - 折扣%/100)。例如上游 100 元、折扣 10%,下游看到 90 元。"
}
return "卖价 = 上游底价 × (1 + 加价%/100)。例如上游 100 元、加价 15%,下游看到 115 元。"
})
const previewText = computed(() => {
const p = Number(adjustPercent.value || 0)
const sample = 100
let sell = sample
if (adjustMode.value === "DISCOUNT") {
sell = sample * (1 - p / 100)
} else {
sell = sample * (1 + p / 100)
}
sell = Math.round(sell * 100) / 100
const modeLabel = adjustMode.value === "DISCOUNT" ? `折扣 ${p}%` : `加价 ${p}%`
return `若上游底价 100 元,当前${modeLabel} → 下游卖价约 ${sell} 元`
})
const toSignedPercent = () => {
const p = Number(adjustPercent.value || 0)
return adjustMode.value === "DISCOUNT" ? -Math.abs(p) : Math.abs(p)
}
const validateBeforeSave = () => {
if (adjustMode.value === "DISCOUNT" && Number(adjustPercent.value || 0) >= 100) {
useMessage().warning("折扣须小于 100%")
return false
}
return true
}
const load = async () => {
loading.value = true
try {
const res: any = await getPriceConfig()
const data = res?.data || {}
const signed = Number(data.markupPercent || 0)
if (signed < 0) {
adjustMode.value = "DISCOUNT"
adjustPercent.value = Math.abs(signed)
} else {
adjustMode.value = "MARKUP"
adjustPercent.value = Math.abs(signed)
}
form.remark = data.remark || ""
} catch (err: any) {
useMessage().error(err?.msg || "加载失败")
} finally {
loading.value = false
}
}
const doSave = async () => {
await savePriceConfig({
markupPercent: toSignedPercent(),
remark: form.remark,
})
}
const saveOnly = async () => {
if (!validateBeforeSave()) return
saving.value = true
try {
await doSave()
useMessage().success("配置已保存(尚未重算库存卖价)")
load()
} catch (err: any) {
useMessage().error(err?.msg || "保存失败")
} finally {
saving.value = false
}
}
const saveAndResync = async () => {
if (!validateBeforeSave()) return
try {
await useMessageBox().confirm(
"将先保存配置,再按底价重算全部产品与日历卖价。历史订单价格不会改动。数据量大时可能需要几十秒,是否继续?"
)
} catch {
return
}
savingAndSync.value = true
lastSyncMsg.value = ""
try {
await doSave()
const res: any = await resyncSellPrices()
const data = res?.data || {}
lastSyncMsg.value = `已同步:产品 ${data.productUpdated ?? 0} 条,日历 ${data.dailyUpdated ?? 0} 条(调整 ${data.markupPercent ?? 0}%)`
useMessage().success(lastSyncMsg.value)
load()
} catch (err: any) {
useMessage().error(err?.msg || "保存或同步失败")
} finally {
savingAndSync.value = false
}
}
onMounted(load)
</script>