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
@@ -19,27 +19,31 @@ function normalizeText(value: unknown) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export function normalizeConciergeAdvisor(value: unknown): ConciergeAdvisor | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
const avatar = normalizeText(value.avatar);
|
||||
const name = normalizeText(value.name);
|
||||
const role = normalizeText(value.role);
|
||||
const qrImage = normalizeText(value.qrImage);
|
||||
if (!avatar || !name || !role || !qrImage) return null;
|
||||
|
||||
const details = Array.isArray(value.details)
|
||||
? value.details.flatMap((detail) => {
|
||||
if (!isRecord(detail)) return [];
|
||||
const icon = normalizeText(detail.icon);
|
||||
const label = normalizeText(detail.label);
|
||||
return icon && label ? [{ icon, label }] : [];
|
||||
})
|
||||
: [];
|
||||
|
||||
return { avatar, name, role, details, qrImage };
|
||||
}
|
||||
|
||||
export function normalizeConciergeAdvisors(value: unknown): ConciergeAdvisor[] {
|
||||
if (!isRecord(value) || !Array.isArray(value.advisors)) return [];
|
||||
|
||||
return value.advisors.flatMap((item) => {
|
||||
if (!isRecord(item)) return [];
|
||||
|
||||
const avatar = normalizeText(item.avatar);
|
||||
const name = normalizeText(item.name);
|
||||
const role = normalizeText(item.role);
|
||||
const qrImage = normalizeText(item.qrImage);
|
||||
if (!avatar || !name || !role || !qrImage) return [];
|
||||
|
||||
const details = Array.isArray(item.details)
|
||||
? item.details.flatMap((detail) => {
|
||||
if (!isRecord(detail)) return [];
|
||||
const icon = normalizeText(detail.icon);
|
||||
const label = normalizeText(detail.label);
|
||||
return icon && label ? [{ icon, label }] : [];
|
||||
})
|
||||
: [];
|
||||
|
||||
return [{ avatar, name, role, details, qrImage }];
|
||||
const advisor = normalizeConciergeAdvisor(item);
|
||||
return advisor ? [advisor] : [];
|
||||
});
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="flex h-screen flex-col overflow-hidden bg-[#f5f2ed]">
|
||||
<view class="relative h-[52%] min-h-[300px] overflow-hidden rounded-b-[24px] bg-[#211e1c]">
|
||||
<view class="min-h-screen bg-[#f5f2ed]">
|
||||
<view class="relative h-[32vh] min-h-[230px] max-h-[320px] overflow-hidden rounded-b-[24px] bg-[#211e1c]">
|
||||
<image class="absolute inset-0 h-full w-full opacity-75"
|
||||
src="https://images.unsplash.com/photo-1774623703220-b3b5e6d3cedf?auto=format&fit=crop&fm=jpg&q=80&w=1200&h=1800"
|
||||
mode="aspectFill" />
|
||||
@@ -17,7 +17,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="flex min-h-0 flex-1 flex-col items-stretch px-6 pb-[calc(12px+env(safe-area-inset-bottom))] pt-6">
|
||||
class="px-6 pb-[calc(28px+env(safe-area-inset-bottom))] pt-6">
|
||||
<view
|
||||
class="w-full rounded-[22px] border border-[#ded6cd] bg-white/95 px-4 py-4 shadow-[0_12px_30px_rgba(80,58,45,0.08)]">
|
||||
<view class="flex items-center gap-3">
|
||||
@@ -48,33 +48,83 @@
|
||||
<text v-else-if="avatarUrl" class="mt-3 block text-[11px] leading-5 text-[#9a9189]">头像仅保存在当前设备</text>
|
||||
<text v-else class="mt-3 block text-[11px] leading-5 text-[#9a9189]">微信头像可在授权后显示</text>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="m-0 mt-5 flex min-h-[58px] w-full items-center gap-3 rounded-[16px] bg-[#a45a3d] px-4 text-left text-white shadow-[0_8px_20px_rgba(164,90,61,0.16)]"
|
||||
@click="emit('open-concierge')"
|
||||
>
|
||||
<view class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/15">
|
||||
<uni-icons type="headphones" :size="18" color="#fffaf5" />
|
||||
</view>
|
||||
<view class="min-w-0 flex-1">
|
||||
<text class="block text-[14px] font-semibold">联系管家</text>
|
||||
<text class="mt-1 block text-[10px] leading-4 text-white/75">获取适合你的出行建议</text>
|
||||
</view>
|
||||
<uni-icons type="right" :size="16" color="#fffaf5" />
|
||||
</button>
|
||||
|
||||
<MineRecentViews
|
||||
:items="props.recentViews"
|
||||
@select="(item) => emit('select-recent', item)"
|
||||
@explore="emit('open-play')"
|
||||
/>
|
||||
|
||||
<view class="mt-6 rounded-[16px] border border-[#e2d9d0] bg-white px-4 py-4 shadow-[0_8px_20px_rgba(80,58,45,0.04)]">
|
||||
<text class="block text-[14px] font-semibold text-[#2b2724]">账号操作</text>
|
||||
<text class="mt-1 block text-[11px] leading-5 text-[#9a9189]">退出登录会清除本机最近浏览记录</text>
|
||||
<button
|
||||
class="m-0 mt-4 flex min-h-[42px] w-full items-center justify-center rounded-full border border-[#d9c7bb] bg-[#fffaf5] px-4 text-[12px] font-medium tracking-[0.04em] text-[#8c4f39]"
|
||||
@click="emit('logout')"
|
||||
>
|
||||
<uni-icons type="forward" :size="16" color="#8c4f39" />
|
||||
<text class="ml-1">退出登录</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import type { RecentlyViewedItem } from "@/lib/recentlyViewed";
|
||||
import MineRecentViews from "./MineRecentViews.vue";
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
customerLine: string;
|
||||
customerName?: string;
|
||||
avatarUrl?: string;
|
||||
showAvatarAction?: boolean;
|
||||
recentViews?: RecentlyViewedItem[];
|
||||
}>(),
|
||||
{
|
||||
customerName: "万趣会员",
|
||||
avatarUrl: "",
|
||||
showAvatarAction: false,
|
||||
recentViews: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
"request-avatar": [];
|
||||
"open-concierge": [];
|
||||
"open-play": [];
|
||||
"select-recent": [item: RecentlyViewedItem];
|
||||
logout: [];
|
||||
}>();
|
||||
|
||||
const menuButtonTop = ref<number | null>(null);
|
||||
const brandPositionStyle = computed(() =>
|
||||
menuButtonTop.value === null ? {} : { top: `${menuButtonTop.value}px` },
|
||||
);
|
||||
|
||||
function syncMenuButtonPosition() {
|
||||
try {
|
||||
menuButtonTop.value = uni.getMenuButtonBoundingClientRect().top;
|
||||
} catch {
|
||||
menuButtonTop.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(syncMenuButtonPosition);
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view class="mt-6">
|
||||
<view class="mb-3 flex items-end justify-between">
|
||||
<view>
|
||||
<text class="block text-[18px] font-semibold tracking-[0.02em] text-[#24211f]">继续探索</text>
|
||||
<text class="mt-1 block text-[11px] leading-5 text-[#9a9189]">最近浏览的内容</text>
|
||||
</view>
|
||||
<text v-if="items.length" class="text-[11px] text-[#b18a76]">{{ items.length }} 条记录</text>
|
||||
</view>
|
||||
|
||||
<view v-if="items.length" class="overflow-hidden rounded-[16px] border border-[#e2d9d0] bg-white shadow-[0_8px_20px_rgba(80,58,45,0.05)]">
|
||||
<button
|
||||
v-for="(item, index) in items"
|
||||
:key="`${item.type}-${item.id}`"
|
||||
class="m-0 flex w-full items-center gap-3 px-3 py-3 text-left"
|
||||
:class="index < items.length - 1 ? 'border-b border-[#eee7e0]' : ''"
|
||||
@click="emit('select', item)"
|
||||
>
|
||||
<view class="h-14 w-16 shrink-0 overflow-hidden rounded-[10px] bg-[#f1ece6]">
|
||||
<image v-if="item.image" class="h-full w-full" :src="item.image" mode="aspectFill" />
|
||||
<view v-else class="flex h-full w-full items-center justify-center">
|
||||
<uni-icons type="image" :size="20" color="#b8aaa0" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="min-w-0 flex-1">
|
||||
<text class="block truncate text-[14px] font-medium text-[#2b2724]">{{ item.title }}</text>
|
||||
<text class="mt-1 block text-[10px] leading-4 text-[#a19890]">{{ typeLabel(item.type) }}</text>
|
||||
</view>
|
||||
<uni-icons type="right" :size="15" color="#b3a79e" />
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-else class="rounded-[16px] border border-dashed border-[#d8cec5] bg-white/70 px-5 py-6 text-center">
|
||||
<view class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-[#f4eee8]">
|
||||
<uni-icons type="eye" :size="20" color="#a45a3d" />
|
||||
</view>
|
||||
<text class="mt-3 block text-[14px] font-medium text-[#4a413a]">暂无浏览记录</text>
|
||||
<text class="mt-1 block text-[11px] leading-5 text-[#9a9189]">去看看玩法,收藏下一段旅程</text>
|
||||
<button
|
||||
class="m-0 mx-auto mt-4 flex min-h-[36px] items-center justify-center rounded-full bg-[#a45a3d] px-5 text-[12px] font-medium text-white"
|
||||
@click="emit('explore')"
|
||||
>
|
||||
去看看玩法
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RecentlyViewedItem, RecentlyViewedType } from "@/lib/recentlyViewed";
|
||||
|
||||
defineProps<{
|
||||
items: RecentlyViewedItem[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [item: RecentlyViewedItem];
|
||||
explore: [];
|
||||
}>();
|
||||
|
||||
function typeLabel(type: RecentlyViewedType) {
|
||||
if (type === "wanfa-route") return "玩法路线";
|
||||
if (type === "team-building") return "团队共创";
|
||||
return "客片案例";
|
||||
}
|
||||
</script>
|
||||
@@ -8,7 +8,12 @@
|
||||
:customer-name="wechatProfile?.nickName || '万趣会员'"
|
||||
:avatar-url="wechatProfile?.avatarUrl"
|
||||
:show-avatar-action="isWechatMiniProgram"
|
||||
@request-avatar="loadWechatProfile" />
|
||||
:recent-views="recentViews"
|
||||
@request-avatar="loadWechatProfile"
|
||||
@open-concierge="goConcierge"
|
||||
@open-play="goPlay"
|
||||
@select-recent="openRecent"
|
||||
@logout="logout" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -17,9 +22,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { getAuthSession, loginWithPhoneCode } from "@/lib/auth";
|
||||
import { clearAuthSession, getAuthSession, loginWithPhoneCode } from "@/lib/auth";
|
||||
import {
|
||||
clearRecentlyViewed,
|
||||
getRecentlyViewed,
|
||||
type RecentlyViewedItem,
|
||||
} from "@/lib/recentlyViewed";
|
||||
import {
|
||||
goConcierge,
|
||||
goPlay,
|
||||
goTeamBuildingDetail,
|
||||
goWanfaRouteDetail,
|
||||
goWildArchiveDetail,
|
||||
} from "@/lib/navigation";
|
||||
import { loadAppData } from "@/lib/store";
|
||||
import {
|
||||
clearWechatProfile,
|
||||
getStoredWechatProfile,
|
||||
requestWechatProfile,
|
||||
saveWechatProfile,
|
||||
@@ -34,9 +52,11 @@ const wechatProfile = ref(getStoredWechatProfile());
|
||||
const isWechatMiniProgram = ref(false);
|
||||
const isLoggingIn = ref(false);
|
||||
const isLoadingWechatProfile = ref(false);
|
||||
const recentViews = ref<RecentlyViewedItem[]>([]);
|
||||
onShow(() => {
|
||||
session.value = getAuthSession();
|
||||
wechatProfile.value = getStoredWechatProfile();
|
||||
recentViews.value = getRecentlyViewed();
|
||||
try {
|
||||
isWechatMiniProgram.value = uni.getSystemInfoSync().uniPlatform === "mp-weixin";
|
||||
} catch {
|
||||
@@ -101,4 +121,27 @@ async function loadWechatProfile() {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function openRecent(item: RecentlyViewedItem) {
|
||||
if (item.type === "wanfa-route") {
|
||||
goWanfaRouteDetail(item.id);
|
||||
return;
|
||||
}
|
||||
if (item.type === "team-building") {
|
||||
goTeamBuildingDetail(item.id);
|
||||
return;
|
||||
}
|
||||
goWildArchiveDetail(item.id);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
clearAuthSession();
|
||||
clearWechatProfile();
|
||||
clearRecentlyViewed();
|
||||
session.value = null;
|
||||
wechatProfile.value = null;
|
||||
recentViews.value = [];
|
||||
customerLine.value = "登录后管理头像与手机号,继续探索旅程。";
|
||||
uni.showToast({ title: "已退出登录", icon: "none" });
|
||||
}
|
||||
</script>
|
||||
@@ -42,6 +42,7 @@ import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { fetchPublicTeamBuildingDetail } from "@/lib/api";
|
||||
import { goBack } from "@/lib/navigation";
|
||||
import { rememberRecentlyViewed } from "@/lib/recentlyViewed";
|
||||
import {
|
||||
homeTeamBuildingFallback,
|
||||
normalizeHomeTeamBuildingDetail,
|
||||
@@ -63,10 +64,24 @@ async function loadDetail(teamBuildingId: string) {
|
||||
detail.value = fallback ? { ...fallback, detailParagraphs: [...fallback.detailParagraphs] } : null;
|
||||
if (detail.value) fallbackNotice.value = "接口暂不可用,当前展示模拟数据";
|
||||
} finally {
|
||||
rememberCurrentDetail(teamBuildingId);
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function rememberCurrentDetail(teamBuildingId: string) {
|
||||
const current = detail.value;
|
||||
if (!current || !teamBuildingId) return;
|
||||
|
||||
rememberRecentlyViewed({
|
||||
type: "team-building",
|
||||
id: teamBuildingId,
|
||||
title: current.title,
|
||||
image: current.image,
|
||||
visitedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
onLoad((query) => {
|
||||
const teamBuildingId = typeof query?.id === "string" ? query.id : "";
|
||||
if (!teamBuildingId) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { fetchPublicWildArchiveDetail } from "@/lib/api";
|
||||
import { goBack } from "@/lib/navigation";
|
||||
import { rememberRecentlyViewed } from "@/lib/recentlyViewed";
|
||||
import { homeWildArchiveFallback, normalizeHomeWildArchiveDetail, type HomeWildArchive } from "@/pages/home/components/homeWildArchivesData";
|
||||
|
||||
const detail = ref<HomeWildArchive | null>(null);
|
||||
@@ -50,10 +51,24 @@ async function loadDetail(archiveId: string) {
|
||||
detail.value = fallback ? { ...fallback, images: [...fallback.images] } : null;
|
||||
if (detail.value) fallbackNotice.value = "接口暂不可用,当前展示模拟数据";
|
||||
} finally {
|
||||
rememberCurrentDetail(archiveId);
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function rememberCurrentDetail(archiveId: string) {
|
||||
const current = detail.value;
|
||||
if (!current || !archiveId) return;
|
||||
|
||||
rememberRecentlyViewed({
|
||||
type: "wild-archive",
|
||||
id: archiveId,
|
||||
title: current.title,
|
||||
image: current.image,
|
||||
visitedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
onLoad((query) => {
|
||||
const archiveId = typeof query?.id === "string" ? query.id : "";
|
||||
if (!archiveId) {
|
||||
|
||||
Reference in new issue
Block a user