菜单添加页面模板管理页面,增加一个纯图平铺模板,优化预览手机屏效果
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import type { EditorProps } from "../../types"
|
||||
|
||||
export function CarPageEditor({
|
||||
content,
|
||||
canEdit,
|
||||
onUpdate,
|
||||
workflowConfig,
|
||||
newImageUrl,
|
||||
onNewImageUrlChange,
|
||||
onAddImage,
|
||||
onRemoveImage,
|
||||
}: EditorProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">标题</label>
|
||||
<Input value={content.title} disabled={!canEdit} onChange={(e) => onUpdate({ title: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">副标题</label>
|
||||
<Input value={content.subtitle} disabled={!canEdit} onChange={(e) => onUpdate({ subtitle: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">卖点(每行一条)</label>
|
||||
<textarea
|
||||
className="min-h-24 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
||||
value={content.highlights}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdate({ highlights: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图文描述(轻量编排)</label>
|
||||
<textarea
|
||||
className="min-h-28 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
||||
value={content.description}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdate({ description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">按钮文案</label>
|
||||
<Input value={content.ctaText} disabled={!canEdit} onChange={(e) => onUpdate({ ctaText: e.target.value })} />
|
||||
</div>
|
||||
|
||||
{workflowConfig.publishStrategy === "scheduled" && (
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">预约发布时间</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
disabled={!canEdit}
|
||||
value={content.scheduledPublishAt}
|
||||
onChange={(e) => onUpdate({ scheduledPublishAt: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图片素材(多图)</label>
|
||||
<div className="flex gap-2">
|
||||
<Input value={newImageUrl ?? ""} disabled={!canEdit} onChange={(e) => onNewImageUrlChange?.(e.target.value)} placeholder="粘贴图片 URL" />
|
||||
<Button variant="outline" disabled={!canEdit} onClick={() => onAddImage?.()}>新增</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{content.imageUrls.map((url, idx) => (
|
||||
<div key={`${url}-${idx}`} className="rounded-lg border p-2">
|
||||
<img src={url} alt={`${content.sourceCarName}-${idx}`} className="aspect-video w-full rounded object-cover" />
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<span className="truncate text-[11px] text-muted-foreground">{url}</span>
|
||||
<Button size="sm" variant="ghost" disabled={!canEdit} onClick={() => onRemoveImage?.(idx)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { ChevronLeft, MoreHorizontal, X } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import type { PreviewProps } from "../../types"
|
||||
|
||||
export function CarPagePreview({ content, workflowConfig }: PreviewProps) {
|
||||
const previewHeroImage = content.imageUrls[0]
|
||||
const highlights = content.highlights
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
return (
|
||||
<div className="flex h-[780px] w-[360px] flex-col overflow-hidden rounded-[28px] border border-gray-200 bg-white shadow-2xl">
|
||||
<div className="flex h-11 items-end justify-between bg-black px-8 pb-2 text-[15px] font-semibold tracking-tight text-white">
|
||||
<span>9:41</span>
|
||||
<div className="flex items-center gap-1.5 pb-1">
|
||||
<svg width="17" height="11" viewBox="0 0 17 11" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M0 8.35L2.83 8.35L2.83 10.5H0V8.35ZM4.25 6.2H7.08V10.5H4.25V6.2ZM8.5 4.05H11.33V10.5H8.5V4.05ZM12.75 1.9H15.58V10.5H12.75V1.9Z" fill="white" />
|
||||
</svg>
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M8 12L16 3.10931C11.5768 -0.493103 4.42323 -0.493103 0 3.10931L8 12Z" fill="white" />
|
||||
</svg>
|
||||
<div className="relative h-[11.33px] w-[24.33px] rounded-[3px] border border-white/35 p-[1px]">
|
||||
<div className="h-full w-[18px] rounded-[1px] bg-white" />
|
||||
<div className="absolute -right-[3px] top-[3px] h-[5px] w-[2px] rounded-r-[1px] bg-white/35" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header className="flex h-14 items-center justify-between bg-black/85 px-4 backdrop-blur-md">
|
||||
<button className="p-2 text-white/90" type="button" aria-label="返回">
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<div className="h-7" />
|
||||
<div className="flex h-8 items-center rounded-full border border-white/10 bg-black/40 px-1">
|
||||
<button className="border-r border-white/10 px-2 text-white/90" type="button" aria-label="更多">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="px-2 text-white/90" type="button" aria-label="关闭">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="min-h-0 flex-1 overflow-y-auto bg-white [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
<div className="relative">
|
||||
{previewHeroImage ? (
|
||||
<img src={previewHeroImage} alt={content.title} className="h-48 w-full object-cover" />
|
||||
) : (
|
||||
<div className="flex h-48 w-full items-center justify-center bg-gray-100 text-xs text-gray-500">请先添加图片素材</div>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/65 to-transparent" />
|
||||
<div className="absolute left-4 right-4 bottom-3 text-white">
|
||||
<p className="text-lg font-bold tracking-tight">{content.title}</p>
|
||||
<p className="text-xs opacity-85">{content.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="mb-3 grid grid-cols-3 gap-2">
|
||||
{content.imageUrls.slice(0, 3).map((url, idx) => (
|
||||
<img key={`${url}-${idx}`} src={url} alt={`thumb-${idx}`} className="h-16 w-full rounded object-cover" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{highlights.map((item) => (
|
||||
<div key={item} className="inline-flex mr-2 mb-1 rounded-full border px-2 py-0.5 text-[10px] text-gray-600">
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="mt-3 whitespace-pre-line text-sm leading-6 text-gray-600">{content.description}</p>
|
||||
|
||||
{workflowConfig.publishStrategy === "scheduled" && content.scheduledPublishAt && (
|
||||
<div className="mt-3 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700">
|
||||
预约发布时间:{content.scheduledPublishAt.replace("T", " ")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
|
||||
<div className="grid gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">姓名</label>
|
||||
<Input placeholder="请输入您的姓名" disabled />
|
||||
</div>
|
||||
<div className="grid gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">手机号</label>
|
||||
<Input placeholder="请输入手机号" disabled />
|
||||
</div>
|
||||
<div className="grid gap-1">
|
||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">意向城市</label>
|
||||
<div className="flex gap-2">
|
||||
<select className="h-8 flex-1 rounded-lg border border-input bg-white px-2 text-sm text-gray-500" disabled>
|
||||
<option>请选择城市</option>
|
||||
</select>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => alert("获取位置功能为原型占位提示")}
|
||||
>
|
||||
获取位置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="flex items-start gap-2 text-[11px] leading-5 text-gray-600">
|
||||
<input type="checkbox" className="mt-0.5" />
|
||||
<span>
|
||||
我已阅读并同意《个人信息保护政策》,授权奥迪及授权经销商与我联系。信息提交后将直接进入企业 CRM,本平台不留存潜客数据。
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Button className="mt-4 w-full bg-audi-black hover:bg-audi-dark-gray text-white" disabled>
|
||||
{content.ctaText || "立即预约试驾"}
|
||||
</Button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import type { EditorProps } from "../../types"
|
||||
|
||||
export function PureImagePageEditor({
|
||||
content,
|
||||
canEdit,
|
||||
onUpdate,
|
||||
newImageUrl,
|
||||
onNewImageUrlChange,
|
||||
onAddImage,
|
||||
onRemoveImage,
|
||||
}: EditorProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">标题</label>
|
||||
<Input value={content.title} disabled={!canEdit} onChange={(e) => onUpdate({ title: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">按钮方案</label>
|
||||
<Input value={content.ctaText} disabled={!canEdit} onChange={(e) => onUpdate({ ctaText: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图片素材(多图)</label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={newImageUrl ?? ""}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onNewImageUrlChange?.(e.target.value)}
|
||||
placeholder="粘贴图片 URL"
|
||||
/>
|
||||
<Button variant="outline" disabled={!canEdit} onClick={() => onAddImage?.()}>
|
||||
新增
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{content.imageUrls.map((url, idx) => (
|
||||
<div key={`${url}-${idx}`} className="rounded-lg border p-2">
|
||||
<img src={url} alt={`${content.title}-${idx}`} className="aspect-video w-full rounded object-cover" />
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<span className="truncate text-[11px] text-muted-foreground">{url}</span>
|
||||
<Button size="sm" variant="ghost" disabled={!canEdit} onClick={() => onRemoveImage?.(idx)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ChevronLeft, MoreHorizontal, X } from "lucide-react"
|
||||
import type { PreviewProps } from "../../types"
|
||||
|
||||
export function PureImagePagePreview({ content }: PreviewProps) {
|
||||
const imageUrls = content.imageUrls.filter(Boolean)
|
||||
|
||||
return (
|
||||
<div className="flex h-[780px] w-[360px] flex-col overflow-hidden rounded-[28px] border border-gray-200 bg-black text-white shadow-2xl">
|
||||
<div className="flex h-11 items-end justify-between px-8 pb-2 text-[15px] font-semibold tracking-tight">
|
||||
<span>9:41</span>
|
||||
<div className="flex items-center gap-1.5 pb-1">
|
||||
<svg width="17" height="11" viewBox="0 0 17 11" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M0 8.35L2.83 8.35L2.83 10.5H0V8.35ZM4.25 6.2H7.08V10.5H4.25V6.2ZM8.5 4.05H11.33V10.5H8.5V4.05ZM12.75 1.9H15.58V10.5H12.75V1.9Z" fill="white" />
|
||||
</svg>
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M8 12L16 3.10931C11.5768 -0.493103 4.42323 -0.493103 0 3.10931L8 12Z" fill="white" />
|
||||
</svg>
|
||||
<div className="relative h-[11.33px] w-[24.33px] rounded-[3px] border border-white/35 p-[1px]">
|
||||
<div className="h-full w-[18px] rounded-[1px] bg-white" />
|
||||
<div className="absolute -right-[3px] top-[3px] h-[5px] w-[2px] rounded-r-[1px] bg-white/35" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header className="flex h-14 items-center justify-between bg-black/85 px-4 backdrop-blur-md">
|
||||
<button className="p-2 text-white/90" type="button" aria-label="返回">
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<h1 className="text-lg font-medium tracking-wide">{content.title || "E7X详情"}</h1>
|
||||
<div className="flex h-8 items-center rounded-full border border-white/10 bg-black/40 px-1">
|
||||
<button className="border-r border-white/10 px-2 text-white/90" type="button" aria-label="更多">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="px-2 text-white/90" type="button" aria-label="关闭">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="min-h-0 flex-1 overflow-y-auto bg-black [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
{imageUrls.length > 0 ? (
|
||||
imageUrls.map((url, idx) => (
|
||||
<img
|
||||
key={`${url}-${idx}`}
|
||||
src={url}
|
||||
alt={`${content.title || "E7X详情"}-${idx + 1}`}
|
||||
className="block w-full"
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex min-h-[480px] items-center justify-center bg-neutral-900 text-sm text-white/60">
|
||||
请先添加图片素材
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<footer className="shrink-0 bg-black px-5 py-3.5">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full border border-white bg-transparent py-2.5 text-sm font-medium text-white"
|
||||
>
|
||||
{content.ctaText || "预约体验"}
|
||||
</button>
|
||||
<div className="mt-3 flex justify-center">
|
||||
<div className="h-1 w-24 rounded-full bg-white/20" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import type { EditorProps } from "../../types"
|
||||
import { createTopicSectionsFromImages } from "./helpers"
|
||||
|
||||
export function TopicPageEditor({
|
||||
content,
|
||||
canEdit,
|
||||
onUpdate,
|
||||
topicSections: topicSectionsProp,
|
||||
onUpdateTopicSection,
|
||||
onAddTopicSection,
|
||||
onRemoveTopicSection,
|
||||
}: EditorProps) {
|
||||
const topicSections = topicSectionsProp
|
||||
?? ((content.topicSections && content.topicSections.length > 0)
|
||||
? content.topicSections
|
||||
: createTopicSectionsFromImages(content.imageUrls))
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">导航标题</label>
|
||||
<Input
|
||||
value={content.topicNavTitle ?? `${content.sourceCarName || "车型"}详情`}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdate({ topicNavTitle: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">主视觉标题</label>
|
||||
<Input
|
||||
value={content.topicHeroTitle ?? "先锋设计"}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdate({ topicHeroTitle: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">底部按钮文案</label>
|
||||
<Input value={content.ctaText} disabled={!canEdit} onChange={(e) => onUpdate({ ctaText: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">专题模块(多个图文)</label>
|
||||
<Button size="sm" variant="outline" disabled={!canEdit || topicSections.length >= 6} onClick={() => onAddTopicSection?.()}>
|
||||
新增模块
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{topicSections.map((section, index) => (
|
||||
<div key={section.id} className="rounded-lg border p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-semibold text-muted-foreground">模块 {index + 1}</p>
|
||||
<Button size="sm" variant="ghost" disabled={!canEdit || topicSections.length <= 1} onClick={() => onRemoveTopicSection?.(section.id)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
value={section.title}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdateTopicSection?.(section.id, { title: e.target.value })}
|
||||
placeholder="模块标题"
|
||||
/>
|
||||
<Input
|
||||
value={section.imageUrl}
|
||||
disabled={!canEdit}
|
||||
onChange={(e) => onUpdateTopicSection?.(section.id, { imageUrl: e.target.value })}
|
||||
placeholder="模块图片 URL"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ChevronLeft, MoreHorizontal, X } from "lucide-react"
|
||||
import type { PreviewProps } from "../../types"
|
||||
import { createTopicSectionsFromImages } from "./helpers"
|
||||
|
||||
export function TopicPagePreview({ content }: PreviewProps) {
|
||||
const topicSections = (content.topicSections && content.topicSections.length > 0)
|
||||
? content.topicSections
|
||||
: createTopicSectionsFromImages(content.imageUrls)
|
||||
|
||||
return (
|
||||
<div className="relative flex h-[780px] w-[360px] flex-col overflow-hidden rounded-[28px] border border-gray-200 bg-white text-[#1a1a1a] shadow-2xl">
|
||||
<div className="flex h-11 items-end justify-between bg-white px-8 pb-2 text-[15px] font-semibold tracking-tight text-[#1a1a1a]">
|
||||
<span>9:41</span>
|
||||
<div className="flex items-center gap-1.5 pb-1">
|
||||
<svg width="17" height="11" viewBox="0 0 17 11" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M0 8.35L2.83 8.35L2.83 10.5H0V8.35ZM4.25 6.2H7.08V10.5H4.25V6.2ZM8.5 4.05H11.33V10.5H8.5V4.05ZM12.75 1.9H15.58V10.5H12.75V1.9Z" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M8 12L16 3.10931C11.5768 -0.493103 4.42323 -0.493103 0 3.10931L8 12Z" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<div className="relative h-[11.33px] w-[24.33px] rounded-[3px] border border-black/35 p-[1px]">
|
||||
<div className="h-full w-[18px] rounded-[1px] bg-[#1a1a1a]" />
|
||||
<div className="absolute -right-[3px] top-[3px] h-[5px] w-[2px] rounded-r-[1px] bg-black/35" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header className="flex h-14 items-center justify-between bg-white px-4">
|
||||
<button className="p-2 text-[#1a1a1a]" type="button" aria-label="返回">
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<h1 className="text-lg font-medium tracking-wide">{content.topicNavTitle || `${content.sourceCarName || "车型"}详情`}</h1>
|
||||
<div className="flex h-8 items-center rounded-full border border-gray-200 bg-gray-50 px-1">
|
||||
<button className="border-r border-gray-200 px-2 text-gray-500" type="button" aria-label="更多">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="px-2 text-gray-500" type="button" aria-label="关闭">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="min-h-0 flex-1 overflow-y-auto pb-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
<section className="pt-4 pb-3 text-center">
|
||||
<h2 className="mb-1 text-[28px] font-bold tracking-tight">{content.topicHeroTitle || "先锋设计"}</h2>
|
||||
<div className="w-12 h-[3px] bg-[#d51c2a] mx-auto" />
|
||||
</section>
|
||||
|
||||
<div className="space-y-2 px-0">
|
||||
{topicSections.map((section) => (
|
||||
<section key={section.id} className="bg-[#f8f9fb] rounded-t-[32px] overflow-hidden">
|
||||
<div className="aspect-[16/9] relative overflow-hidden">
|
||||
{section.imageUrl ? (
|
||||
<img
|
||||
src={section.imageUrl}
|
||||
alt={section.title}
|
||||
className="w-full h-full object-cover mix-blend-multiply"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-xs text-gray-500 bg-gray-100">请添加模块图片</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="px-5 py-1.5">
|
||||
<h3 className="text-[19px] font-normal text-gray-700">{section.title}</h3>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="shrink-0 border-t border-gray-100 bg-white/80 px-4 pt-2 pb-4 backdrop-blur-md">
|
||||
<button className="w-full rounded-md bg-[#1c1f23] py-3 text-[15px] font-medium text-white">
|
||||
{content.ctaText || "预约体验"}
|
||||
</button>
|
||||
<div className="mx-auto mt-3 h-1 w-24 rounded-full bg-black/80" />
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { MiniAppContent, SourceSnapshot, TopicSection } from "../../types"
|
||||
|
||||
export function createTopicSectionsFromImages(images: string[]): TopicSection[] {
|
||||
const picks = images.slice(0, 2)
|
||||
const fallback = ["/images/cars/a7-1.jpg", "/images/cars/a7-2.jpg"]
|
||||
const selected = picks.length > 0 ? picks : fallback
|
||||
|
||||
return selected.map((imageUrl, index) => ({
|
||||
id: `topic-section-${index + 1}`,
|
||||
title: index === 0 ? "RS竞速套件" : "宽体低趴 超长轴距",
|
||||
imageUrl,
|
||||
specLeftLabel: "长",
|
||||
specRightLabel: "宽",
|
||||
}))
|
||||
}
|
||||
|
||||
export function buildTopicPatch(sourceCarName: string, snapshot?: SourceSnapshot): Pick<MiniAppContent, "topicNavTitle" | "topicHeroTitle" | "topicSections"> {
|
||||
const hero = snapshot?.topicHeroTitle ?? snapshot?.subtitle ?? "先锋设计"
|
||||
return {
|
||||
topicNavTitle: snapshot?.topicNavTitle ?? `${sourceCarName || "车型"}详情`,
|
||||
topicHeroTitle: hero,
|
||||
topicSections: snapshot?.topicSections ?? createTopicSectionsFromImages(snapshot?.imageUrls ?? []),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user