feat: 新增路线详情关联管家顾问及相关功能
详细变更如下: - 更新 .gitignore 文件,添加 pnpm-store 忽略规则 - 新增数据库迁移脚本,为 DetailRecord 添加可空的 conciergeAdvisorId 字段用于关联管家顾问 - 完善 Admin 后台玩法详情编辑器,支持选择关联的管家顾问并校验合法性 - 公共 API 支持返回已启用的管家顾问完整数据,不在详情表中冗余存储管家资料 - 小程序端新增详情页联系管家入口、个人页最近浏览历史功能 - 更新所有相关文档与测试用例,修复下拉选择框的 z-index 样式问题
This commit is contained in:
1 parent
e7b8c5bf43
commit
b7a81964c9
34 files changed
+739
-63
No files matched your search
@@ -28,6 +28,7 @@
|
||||
price }}万</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="showConcierge"
|
||||
class="tap-feedback flex items-center justify-center m-0 h-11 rounded-full bg-[#e96635] px-4 text-[13px] font-semibold text-white shadow-[0_6px_16px_rgba(233,102,53,0.22)]"
|
||||
@click="$emit('concierge')">联系管家</view>
|
||||
</view>
|
||||
@@ -38,10 +39,13 @@
|
||||
<script setup lang="ts">
|
||||
import { formatWan } from "@/lib/data";
|
||||
|
||||
defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
price: number;
|
||||
favorite: boolean;
|
||||
}>();
|
||||
showConcierge?: boolean;
|
||||
}>(), {
|
||||
showConcierge: true,
|
||||
});
|
||||
|
||||
defineEmits<{
|
||||
call: [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { playCategories, type PlayRoute } from "../../play/components/playData";
|
||||
import type { PublicDetail } from "@/lib/types";
|
||||
import { normalizeConciergeAdvisor } from "../../concierge/components/conciergeTypes";
|
||||
|
||||
export type DetailPresentation = PublicDetail;
|
||||
|
||||
@@ -30,6 +31,7 @@ function createRouteFallback(route: PlayRoute): DetailPresentation {
|
||||
"最终行程以出行日期、人数和资源确认结果为准。",
|
||||
],
|
||||
gallery: Array.from(new Set([route.image, ...sharedGallery])).slice(0, 5),
|
||||
conciergeAdvisor: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +77,7 @@ export function normalizeDetailPresentation(
|
||||
const subtitle = cleanText(source.subtitle) || fallback?.subtitle || "";
|
||||
const intro = cleanText(source.intro) || fallback?.intro || "";
|
||||
const gallery = cleanGallery(source.gallery);
|
||||
const conciergeAdvisor = normalizeConciergeAdvisor(source.conciergeAdvisor);
|
||||
|
||||
if (!key || !eyebrow || !duration || !title || !subtitle || !intro) {
|
||||
return fallback;
|
||||
@@ -92,5 +95,6 @@ export function normalizeDetailPresentation(
|
||||
excluded: cleanList(source.excluded).length ? cleanList(source.excluded) : fallback?.excluded ?? [],
|
||||
notes: cleanList(source.notes).length ? cleanList(source.notes) : fallback?.notes ?? [],
|
||||
gallery: gallery.length ? gallery : fallback?.gallery ?? [],
|
||||
conciergeAdvisor,
|
||||
};
|
||||
}
|
||||
@@ -42,7 +42,13 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<DetailActionBar :price="1.68" :favorite="false" @call="goCall" @concierge="goConcierge" />
|
||||
<DetailActionBar
|
||||
:price="1.68"
|
||||
:favorite="false"
|
||||
:show-concierge="Boolean(presentation?.conciergeAdvisor)"
|
||||
@call="goCall"
|
||||
@concierge="goConcierge"
|
||||
/>
|
||||
|
||||
<ConciergeContactSheet :open="Boolean(selectedAdvisor)" :advisor="selectedAdvisor" @close="handleConciergeClosed" />
|
||||
</view>
|
||||
@@ -54,6 +60,7 @@ import { onLoad, onShareAppMessage } from "@dcloudio/uni-app";
|
||||
import ConciergeContactSheet from "@/components/ConciergeContactSheet.vue";
|
||||
import { fetchPublicDetail } from "@/lib/api";
|
||||
import { goBack } from "@/lib/navigation";
|
||||
import { rememberRecentlyViewed } from "@/lib/recentlyViewed";
|
||||
import { SUPPORT_PHONE } from "@/lib/types";
|
||||
import type { ConciergeAdvisor } from "../concierge/components/conciergeTypes";
|
||||
import DetailActionBar from "./components/DetailActionBar.vue";
|
||||
@@ -74,17 +81,6 @@ const usingFallback = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const selectedAdvisor = ref<ConciergeAdvisor | null>(null);
|
||||
|
||||
const conciergeAdvisor: ConciergeAdvisor = {
|
||||
avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
|
||||
name: "Amanda",
|
||||
role: "SENIOR TRAVEL ADVISOR",
|
||||
qrImage: "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
|
||||
details: [
|
||||
{ icon: "calendar", label: "服务经验:8年" },
|
||||
{ icon: "navigate", label: "擅长领域:瀑降 / 探洞" },
|
||||
],
|
||||
};
|
||||
|
||||
const heroImage = computed(() => presentation.value?.gallery[0] ?? "");
|
||||
const priceParagraphs = computed(() => ["最终价格会根据出行日期、人数和资源确认结果生成。"]);
|
||||
|
||||
@@ -125,16 +121,31 @@ async function loadDetail() {
|
||||
usingFallback.value = Boolean(fallback);
|
||||
errorMessage.value = cause instanceof Error ? cause.message : "路线详情接口暂时不可用。";
|
||||
} finally {
|
||||
rememberCurrentDetail();
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function rememberCurrentDetail() {
|
||||
const current = presentation.value;
|
||||
if (!current || !routeId.value) return;
|
||||
|
||||
rememberRecentlyViewed({
|
||||
type: "wanfa-route",
|
||||
id: routeId.value,
|
||||
title: current.title,
|
||||
image: current.gallery[0] ?? "",
|
||||
visitedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
function goCall() {
|
||||
uni.makePhoneCall({ phoneNumber: SUPPORT_PHONE });
|
||||
}
|
||||
|
||||
function goConcierge() {
|
||||
selectedAdvisor.value = conciergeAdvisor;
|
||||
const advisor = presentation.value?.conciergeAdvisor;
|
||||
if (advisor) selectedAdvisor.value = advisor;
|
||||
}
|
||||
|
||||
function handleConciergeClosed() {
|
||||
|
||||
Reference in new issue
Block a user