酒店
This commit is contained in:
1 parent
44f03f7f20
commit
a0c259ed46
6 files changed
+289
-52
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# 网站主标题
|
||||
VITE_GLOBAL_TITLE= '后勤管理系统'
|
||||
VITE_GLOBAL_TITLE= '后台管理系统'
|
||||
|
||||
# footer
|
||||
VITE_FOOTER_TITLE= '©2024 bsd.com'
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import request from "/@/utils/request"
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry/page",
|
||||
method: "get",
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
export function listEnabled() {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry/listEnabled",
|
||||
method: "get",
|
||||
})
|
||||
}
|
||||
|
||||
export function enabledDeptIds() {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry/enabledDeptIds",
|
||||
method: "get",
|
||||
})
|
||||
}
|
||||
|
||||
/** 整表替换可见部门;deptIds 空数组 = 全员隐藏 */
|
||||
export function replaceDepts(data: { deptIds: Array<string | number>; remark?: string }) {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry/replaceDepts",
|
||||
method: "post",
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function addObj(obj?: Object) {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry",
|
||||
method: "post",
|
||||
data: obj,
|
||||
})
|
||||
}
|
||||
|
||||
export function putObj(obj?: Object) {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry",
|
||||
method: "put",
|
||||
data: obj,
|
||||
})
|
||||
}
|
||||
|
||||
export function delObjs(ids?: Object) {
|
||||
return request({
|
||||
url: "/supermarket/smHotelAppEntry",
|
||||
method: "delete",
|
||||
data: ids,
|
||||
})
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export function rejectOrder(id: string | number, reason?: string) {
|
||||
return request({
|
||||
url: `/supermarket/smHotelOrder/reject/${id}`,
|
||||
method: "post",
|
||||
data: { reason: reason || "人工拒绝接单" },
|
||||
data: { reason: reason || "酒店暂无法确认预订,已为您取消并全额退款" },
|
||||
timeout: 120000,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-alert type="info" :closable="false" show-icon class="mb-3">
|
||||
<template #title>
|
||||
App「出行」入口控制:默认全员不显示底部「出行」Tab 与订单页「出行」选项。勾选部门后,该部门及其子部门用户登录后可见。
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-card shadow="never" class="mb-3">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium">可见部门</span>
|
||||
<div>
|
||||
<el-button @click="loadAll" :loading="loading">刷新</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-auth="'supermarket_smHotelAppEntry_edit'"
|
||||
:loading="saving"
|
||||
@click="onSave"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="100px" class="max-w-720px">
|
||||
<el-form-item label="选择部门">
|
||||
<el-tree-select
|
||||
v-model="checkedDeptIds"
|
||||
:data="deptOptions"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
node-key="id"
|
||||
multiple
|
||||
show-checkbox
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
check-strictly
|
||||
default-expand-all
|
||||
placeholder="不选 = 全员隐藏出行"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="remark" clearable placeholder="可选,如:仅总部试点开放" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前规则">
|
||||
<div class="text-13px text-gray-600 leading-6">
|
||||
<template v-if="!checkedDeptIds.length">
|
||||
未勾选任何部门 → <b>所有用户看不到出行</b>
|
||||
</template>
|
||||
<template v-else>
|
||||
已选 {{ checkedDeptIds.length }} 个部门(含其下子部门用户可见)
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span class="font-medium">已启用清单</span>
|
||||
</template>
|
||||
<el-table :data="enabledList" v-loading="loading" border>
|
||||
<el-table-column prop="deptId" label="部门ID" width="140" />
|
||||
<el-table-column prop="deptName" label="部门名称" min-width="180" />
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
v-auth="'supermarket_smHotelAppEntry_edit'"
|
||||
@click="onRemove(row)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { deptTree } from "/@/api/admin/dept"
|
||||
import * as AppEntryApi from "/@/api/supermarket/smHotelAppEntry"
|
||||
|
||||
defineOptions({ name: "SmHotelAppEntry" })
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const deptOptions = ref<any[]>([])
|
||||
const checkedDeptIds = ref<Array<string | number>>([])
|
||||
const enabledList = ref<any[]>([])
|
||||
const remark = ref("")
|
||||
|
||||
const normalizeTreeIds = (nodes: any[]): any[] => {
|
||||
if (!Array.isArray(nodes)) return []
|
||||
return nodes.map((n) => {
|
||||
const id = n?.id ?? n?.deptId
|
||||
return {
|
||||
...n,
|
||||
id,
|
||||
name: n?.name ?? n?.deptName,
|
||||
children: n?.children?.length ? normalizeTreeIds(n.children) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
const res: any = await deptTree()
|
||||
deptOptions.value = normalizeTreeIds(res?.data || [])
|
||||
} catch {
|
||||
deptOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadAll = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await loadDeptTree()
|
||||
const [idsRes, listRes]: any[] = await Promise.all([AppEntryApi.enabledDeptIds(), AppEntryApi.listEnabled()])
|
||||
const ids = idsRes?.data || idsRes || []
|
||||
checkedDeptIds.value = Array.isArray(ids) ? ids.map((x: any) => (typeof x === "number" ? x : Number(x) || x)) : []
|
||||
enabledList.value = listRes?.data || listRes || []
|
||||
const firstRemark = enabledList.value.find((r) => r?.remark)?.remark
|
||||
if (firstRemark) remark.value = String(firstRemark)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
await AppEntryApi.replaceDepts({
|
||||
deptIds: checkedDeptIds.value || [],
|
||||
remark: remark.value || undefined,
|
||||
})
|
||||
message.success("已保存:未勾选部门的用户将看不到出行入口")
|
||||
await loadAll()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onRemove = async (row: any) => {
|
||||
if (!row?.id) return
|
||||
await message.confirm(`确认移除部门「${row.deptName || row.deptId}」的出行可见?`)
|
||||
await AppEntryApi.delObjs([row.id])
|
||||
message.success("已移除")
|
||||
await loadAll()
|
||||
}
|
||||
|
||||
onMounted(() => loadAll())
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb-3"
|
||||
title="上游:选渠道→归属部门。下游两种授权:①按部门(+可选区域)②按指定酒店。伙伴编码在「对接配置」维护一次,这里下拉选择。"
|
||||
title="上游:选渠道→归属部门。下游:同一伙伴可同时配「指定酒店」+「按部门」多条规则,生效时取并集(不是互斥)。协议下游可全量推送;开放应用(如小如)配好范围即可,对方主动拉数。"
|
||||
/>
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
@@ -120,7 +120,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" v-auth="'supermarket_smHotelChannelDept_edit'" @click="openForm(row)">
|
||||
编辑
|
||||
@@ -128,6 +128,15 @@
|
||||
<el-button
|
||||
v-if="row.direction === 'downstream' && row.status === '1'"
|
||||
text
|
||||
type="success"
|
||||
:loading="previewingPartner === row.channel"
|
||||
@click="handlePreviewScope(row)"
|
||||
>
|
||||
生效范围
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.direction === 'downstream' && row.status === '1' && canPushPartner(row.channel)"
|
||||
text
|
||||
type="warning"
|
||||
v-auth="'supermarket_smHotel_sync'"
|
||||
:loading="pushingPartner === row.channel"
|
||||
@@ -391,6 +400,7 @@ const downstreamPartnerOptions = ref<any[]>([])
|
||||
/** 已选酒店展示缓存 id -> hotel */
|
||||
const hotelOptions = ref<any[]>([])
|
||||
const pushingPartner = ref<string | null>(null)
|
||||
const previewingPartner = ref<string | null>(null)
|
||||
const hotelPickerVisible = ref(false)
|
||||
const hotelPickerLoading = ref(false)
|
||||
const hotelPickerTableRef = ref()
|
||||
@@ -602,10 +612,48 @@ const loadDownstreamPartners = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 开放应用(protocol=open)是拉模式,不展示也不执行「全量推送」 */
|
||||
const partnerMeta = (channel: string) =>
|
||||
downstreamPartnerOptions.value.find(
|
||||
(p) => String(p.channel || "").toLowerCase() === String(channel || "").toLowerCase(),
|
||||
)
|
||||
|
||||
const canPushPartner = (channel: string) => {
|
||||
const meta = partnerMeta(channel)
|
||||
if (!meta) return true
|
||||
return String(meta.protocol || "").toLowerCase() !== "open"
|
||||
}
|
||||
|
||||
/** 查看同一伙伴全部启用规则并集后的上架酒店数(指定酒店 ∪ 按部门) */
|
||||
const handlePreviewScope = async (row: any) => {
|
||||
const partner = row?.channel
|
||||
if (!partner || row.direction !== "downstream") return
|
||||
previewingPartner.value = partner
|
||||
try {
|
||||
const preview: any = await previewPushScope(partner)
|
||||
const count = Number(preview?.data?.hotelCount || 0)
|
||||
const raw = Number(preview?.data?.rawHotelCount ?? count)
|
||||
useMessage().success(
|
||||
`伙伴「${partner}」生效范围(指定酒店∪按部门并集)约 ${count} 家上架酒店`
|
||||
+ (raw !== count ? `(原始命中 ${raw},已排除下架/不可推)` : ""),
|
||||
)
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || err?.message || "查询生效范围失败")
|
||||
} finally {
|
||||
previewingPartner.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 按该下游全部启用范围汇总酒店并异步推送(不限当前这一行映射) */
|
||||
const handlePushScope = async (row: any) => {
|
||||
const partner = row?.channel
|
||||
if (!partner || row.direction !== "downstream") return
|
||||
if (!canPushPartner(partner)) {
|
||||
useMessage().warning(
|
||||
`「${partner}」是开放应用,对方主动拉数即可,无需全量推送。请确认本页已配好授权范围。`,
|
||||
)
|
||||
return
|
||||
}
|
||||
pushingPartner.value = partner
|
||||
try {
|
||||
const preview: any = await previewPushScope(partner)
|
||||
|
||||
@@ -244,15 +244,6 @@
|
||||
>
|
||||
拒绝取消
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRefund(scope.row)"
|
||||
text
|
||||
type="danger"
|
||||
v-auth="'supermarket_smHotelOrder_cancel'"
|
||||
@click="handleRefund(scope.row)"
|
||||
>
|
||||
退款
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canCancel(scope.row)"
|
||||
text
|
||||
@@ -530,15 +521,6 @@
|
||||
>
|
||||
拒绝取消
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRefund(detailOrder)"
|
||||
type="danger"
|
||||
v-auth="'supermarket_smHotelOrder_cancel'"
|
||||
:loading="detailActing"
|
||||
@click="handleRefund(detailOrder)"
|
||||
>
|
||||
退款
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canCancel(detailOrder)"
|
||||
type="primary"
|
||||
@@ -607,7 +589,6 @@ import {
|
||||
confirmCancelOrder,
|
||||
rejectCancelOrder,
|
||||
compensateOrder,
|
||||
refundOrder,
|
||||
listOrderSourceOptions,
|
||||
} from "/@/api/supermarket/smHotelOrder"
|
||||
import {
|
||||
@@ -957,17 +938,26 @@ const confirmPull = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const canRefund = (row: any) =>
|
||||
row?.payStatus === "PAID" &&
|
||||
row?.orderStatus !== "PENDING_CONFIRM" &&
|
||||
row?.orderStatus !== "PENDING_CANCEL"
|
||||
/** 当天已晚于离店日:不再展示取消,防误触历史单 */
|
||||
const isPastCheckout = (row: any) => {
|
||||
const raw = row?.checkOutDate
|
||||
if (!raw) return false
|
||||
const out = String(raw).slice(0, 10)
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(out)) return false
|
||||
const today = new Date()
|
||||
const y = today.getFullYear()
|
||||
const m = String(today.getMonth() + 1).padStart(2, "0")
|
||||
const d = String(today.getDate()).padStart(2, "0")
|
||||
return `${y}-${m}-${d}` > out
|
||||
}
|
||||
|
||||
const canCancel = (row: any) =>
|
||||
row?.orderStatus !== "CANCELLED" &&
|
||||
row?.orderStatus !== "PENDING_CONFIRM" &&
|
||||
row?.orderStatus !== "PENDING_CANCEL" &&
|
||||
row?.payStatus !== "PAID" &&
|
||||
row?.payStatus !== "REFUNDED"
|
||||
row?.payStatus !== "REFUNDED" &&
|
||||
!isPastCheckout(row)
|
||||
|
||||
const canCompensate = (row: any) =>
|
||||
(row?.compensateStatus === "WAITING" || row?.compensateStatus === "NEED_MANUAL") &&
|
||||
@@ -1137,7 +1127,7 @@ const handleRejectLocal = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(
|
||||
paid
|
||||
? `拒绝订单 ${row.channelNo}?将取消预订、释放库存并原路退款 ${formatAmount(row.sellAmount)}。`
|
||||
? `拒绝订单 ${row.channelNo}?将取消预订、释放库存,并通知下游退款(退款由小如处理)。`
|
||||
: `拒绝订单 ${row.channelNo}?将取消预订并释放库存。`,
|
||||
)
|
||||
} catch {
|
||||
@@ -1146,7 +1136,7 @@ const handleRejectLocal = async (row: any) => {
|
||||
detailActing.value = true
|
||||
try {
|
||||
await rejectOrder(row.id)
|
||||
useMessage().success(paid ? "已拒绝并退款" : "已拒绝")
|
||||
useMessage().success(paid ? "已拒绝,已通知下游退款" : "已拒绝")
|
||||
await refreshAfterAction()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || err?.message || "拒绝失败")
|
||||
@@ -1160,7 +1150,7 @@ const handleConfirmCancelLocal = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(
|
||||
paid
|
||||
? `同意取消 ${row.channelNo}?请先核对取消规则。将关单、释库存并原路退款 ${formatAmount(row.sellAmount)},并回写下游。\n${cancelRuleDisplay(row) || "(无规则文案)"}`
|
||||
? `同意取消 ${row.channelNo}?请先核对取消规则。将关单、释库存,并通知下游退款(退款由小如处理)。\n${cancelRuleDisplay(row) || "(无规则文案)"}`
|
||||
: `同意取消 ${row.channelNo}?请先核对取消规则。将关单并回写下游。\n${cancelRuleDisplay(row) || "(无规则文案)"}`,
|
||||
)
|
||||
} catch {
|
||||
@@ -1169,7 +1159,7 @@ const handleConfirmCancelLocal = async (row: any) => {
|
||||
detailActing.value = true
|
||||
try {
|
||||
await confirmCancelOrder(row.id)
|
||||
useMessage().success(paid ? "已同意取消并退款" : "已同意取消")
|
||||
useMessage().success(paid ? "已同意取消,已通知下游退款" : "已同意取消")
|
||||
await refreshAfterAction()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || err?.message || "操作失败")
|
||||
@@ -1196,26 +1186,6 @@ const handleRejectCancelLocal = async (row: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefund = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(
|
||||
`确认对订单 ${row.channelNo} 原路退款 ${formatAmount(row.sellAmount)}?将关闭预订并退回支付。`,
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
detailActing.value = true
|
||||
try {
|
||||
await refundOrder(row.id)
|
||||
useMessage().success("退款已受理")
|
||||
await refreshAfterAction()
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || err?.message || "退款失败")
|
||||
} finally {
|
||||
detailActing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCompensate = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(`对 ${row.channelNo} 执行补偿查询?`)
|
||||
|
||||
Reference in new issue
Block a user