feat: align brand knowledge cards with material library
This commit is contained in:
parent
9692357954
commit
61dade05ed
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import BrandKnowledgeDashboard from './BrandKnowledgeDashboard'
|
||||
import { BRAND_KNOWLEDGE_TYPE_OPTIONS } from '@/lib/brand-knowledge-file-types'
|
||||
|
||||
@ -42,6 +42,46 @@ describe('BrandKnowledgeDashboard', () => {
|
||||
expect(screen.getByText('没有找到与“索菲亚”匹配的知识内容')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows uploaded knowledge with the material-library card hierarchy', async () => {
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
success: true,
|
||||
data: [{
|
||||
id: 'knowledge-1',
|
||||
title: '索菲亚图像影像规范',
|
||||
knowledgeType: '影像规范',
|
||||
brand: '索菲亚',
|
||||
productCategoryName: '衣柜',
|
||||
fileName: '索菲亚图像影像规范.rar',
|
||||
mimeType: 'application/vnd.rar',
|
||||
fileSize: 668886630,
|
||||
createdAt: '2026-09-12T00:00:00.000Z',
|
||||
uploadedBy: '无邪',
|
||||
downloadUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/file',
|
||||
previewUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/preview',
|
||||
previewImageName: 'preview.png',
|
||||
}],
|
||||
}),
|
||||
} as Response)
|
||||
|
||||
render(<BrandKnowledgeDashboard />)
|
||||
|
||||
expect(await screen.findByRole('img', { name: '预览图:索菲亚图像影像规范' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { level: 3, name: '索菲亚图像影像规范' })).toBeInTheDocument()
|
||||
expect(screen.getByText(/637\.9 MB/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/上传人:无邪/)).toBeInTheDocument()
|
||||
expect(screen.getByText('索菲亚图像影像规范.rar')).toBeInTheDocument()
|
||||
const knowledgeList = screen.getByRole('list', { name: '品牌知识内容列表' })
|
||||
expect(within(knowledgeList).getByText('影像规范')).toBeInTheDocument()
|
||||
expect(within(knowledgeList).getByText('索菲亚')).toBeInTheDocument()
|
||||
expect(within(knowledgeList).getByText('衣柜')).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: '下载索菲亚图像影像规范' })).toHaveAttribute(
|
||||
'href',
|
||||
'/api/department-dashboard/brand-knowledge/knowledge-1/file',
|
||||
)
|
||||
})
|
||||
|
||||
it('submits a selected knowledge file to the independent API', async () => {
|
||||
const fetchMock = global.fetch as jest.Mock
|
||||
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
|
||||
|
||||
@ -12,6 +12,77 @@ type BrandKnowledgeItem = { id: string; title: string; knowledgeType: string; br
|
||||
function formatFileSize(size: number) { if (size < 1024) return `${size} B`; if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`; return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB` }
|
||||
function formatDate(value: string) { const date = new Date(value); return Number.isNaN(date.getTime()) ? value : new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).format(date) }
|
||||
|
||||
function BrandKnowledgeCard({ item }: { item: BrandKnowledgeItem }) {
|
||||
const details = [
|
||||
formatFileSize(item.fileSize),
|
||||
item.uploadedBy ? `上传人:${item.uploadedBy}` : '',
|
||||
formatDate(item.createdAt),
|
||||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<li className="min-w-0">
|
||||
<article>
|
||||
<div className="relative aspect-[4/3] overflow-hidden rounded-xl bg-[#e9edf2]">
|
||||
{item.previewUrl ? (
|
||||
<Image
|
||||
src={item.previewUrl}
|
||||
alt={`预览图:${item.title}`}
|
||||
fill
|
||||
unoptimized
|
||||
className="object-contain"
|
||||
sizes="(max-width: 767px) 100vw, (max-width: 1279px) 50vw, 33vw"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-[#66758b]">
|
||||
<FileText className="h-8 w-8 text-[#247fd3]" aria-hidden="true" />
|
||||
<span className="line-clamp-2 text-sm">暂无预览图</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<h3 className="line-clamp-2 text-base font-bold leading-6 text-[#101828]" title={item.title}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-1.5 line-clamp-2 text-xs leading-5 text-[#66758b]" title={details.join(' · ')}>
|
||||
{details.map((detail, index) => (
|
||||
<span key={detail}>
|
||||
{index > 0 && <span aria-hidden="true"> · </span>}
|
||||
{detail}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
<p className="mt-1 truncate text-xs leading-5 text-[#8a8f97]" title={item.fileName}>
|
||||
{item.fileName}
|
||||
</p>
|
||||
|
||||
<div className="mt-2 flex min-h-6 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-[#66758b]">
|
||||
<span className="font-semibold text-[#087052]">{item.knowledgeType}</span>
|
||||
{item.brand && item.brand !== '未关联品牌' && (
|
||||
<><span aria-hidden="true" className="text-[#c3cbd6]">/</span><span>{item.brand}</span></>
|
||||
)}
|
||||
{item.productCategoryName && (
|
||||
<><span aria-hidden="true" className="text-[#c3cbd6]">/</span><span>{item.productCategoryName}</span></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 border-t border-[#e2e8f0] pt-3">
|
||||
<a
|
||||
href={item.downloadUrl}
|
||||
className="inline-flex min-h-10 items-center gap-2 rounded-sm text-sm font-semibold text-[#101828] transition-colors hover:text-[#247fd3] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff] focus-visible:ring-offset-2"
|
||||
aria-label={`下载${item.title}`}
|
||||
title="下载"
|
||||
>
|
||||
<Download className="h-4 w-4" aria-hidden="true" />
|
||||
下载
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default function BrandKnowledgeDashboard() {
|
||||
const [selectedType, setSelectedType] = useState('全部')
|
||||
const [searchKeyword, setSearchKeyword] = useState('')
|
||||
@ -123,7 +194,7 @@ export default function BrandKnowledgeDashboard() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isLoading ? <div className="border-t border-[#e2e8f0] py-24 text-center text-sm text-[#66758b]" role="status">正在读取知识内容…</div> : visibleItems.length > 0 ? <ul className="mt-4 grid gap-3 md:grid-cols-2" aria-label="品牌知识内容列表">{visibleItems.map((item) => <li key={item.id} className="rounded-xl border border-[#e2e8f0] bg-white p-5 shadow-[0_6px_18px_rgba(16,24,40,0.04)]"><div className="flex items-start justify-between gap-4"><div className="flex min-w-0 gap-3"><div className="relative h-16 w-20 shrink-0 overflow-hidden rounded-lg border border-[#e2e8f0] bg-[#f8fafc]">{item.previewUrl ? <Image src={item.previewUrl} alt={`预览图:${item.title}`} fill unoptimized className="object-cover" /> : <span className="flex h-full w-full items-center justify-center text-[#247fd3]"><FileText className="h-5 w-5" aria-hidden="true" /></span>}</div><div className="min-w-0"><h3 className="truncate font-bold text-[#101828]" title={item.title}>{item.title}</h3><p className="mt-1 truncate text-sm text-[#66758b]" title={item.fileName}>{item.fileName}</p></div></div><a href={item.downloadUrl} className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-[#247fd3] hover:bg-[#eef6ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff]" aria-label={`下载${item.title}`} title="下载"><Download className="h-4 w-4" aria-hidden="true" /></a></div><div className="mt-4 flex flex-wrap items-center gap-2 text-xs text-[#66758b]"><span className="rounded-full bg-[#f0fbf6] px-2.5 py-1 font-semibold text-[#087052]">{item.knowledgeType}</span>{item.brand && item.brand !== '未关联品牌' && <span className="rounded-full bg-[#f5f7fa] px-2.5 py-1">{item.brand}</span>}{item.productCategoryName && <span className="rounded-full bg-[#fff7e8] px-2.5 py-1 text-[#9a4d08]">{item.productCategoryName}</span>}<span>{formatFileSize(item.fileSize)}</span><span>上传人:{item.uploadedBy}</span><span>{formatDate(item.createdAt)}</span></div></li>)}</ul> : <div className="border-t border-[#e2e8f0] py-24 text-center">
|
||||
{isLoading ? <div className="border-t border-[#e2e8f0] py-24 text-center text-sm text-[#66758b]" role="status">正在读取知识内容…</div> : visibleItems.length > 0 ? <ul className="mt-5 grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 xl:grid-cols-3" aria-label="品牌知识内容列表">{visibleItems.map((item) => <BrandKnowledgeCard key={item.id} item={item} />)}</ul> : <div className="border-t border-[#e2e8f0] py-24 text-center">
|
||||
<BookOpen className="mx-auto h-8 w-8 text-[#aab5c3]" aria-hidden="true" />
|
||||
<p className="mt-4 text-base font-semibold text-[#4f6077]">{emptyMessage}</p>
|
||||
<p className="mt-2 text-sm text-[#8a8f97]">点击右上角“上传知识”,即可将文件保存到品牌知识库。</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user