diff --git a/components/department-dashboard/BrandKnowledgeDashboard.test.tsx b/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
index 80776ad..24ecf2d 100644
--- a/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
+++ b/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
@@ -11,6 +11,7 @@ describe('BrandKnowledgeDashboard', () => {
beforeEach(() => {
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: jest.fn(() => 'blob:test-preview') })
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: jest.fn() })
+ Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: jest.fn().mockResolvedValue(undefined) } })
global.fetch = jest.fn().mockImplementation((input: RequestInfo | URL) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [{ id: 'category-1', name: '床类' }] } as Response)
@@ -69,13 +70,19 @@ describe('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()
+ expect(screen.getByText(/637\.90 MB/)).toBeInTheDocument()
+ expect(screen.getByText(/无邪/)).toBeInTheDocument()
+ expect(screen.getByText(/2026\.09\.12/)).toBeInTheDocument()
const knowledgeList = screen.getByRole('list', { name: '品牌知识内容列表' })
expect(within(knowledgeList).getByText('影像规范')).toBeInTheDocument()
expect(within(knowledgeList).getByText('索菲亚')).toBeInTheDocument()
expect(within(knowledgeList).getByText('衣柜')).toBeInTheDocument()
+ expect(screen.queryByText('索菲亚图像影像规范.rar')).not.toBeInTheDocument()
+ const copyIdButton = screen.getByRole('button', { name: '复制作品ID knowledge-1' })
+ expect(copyIdButton).toHaveTextContent('作品ID knowledge-1')
+ fireEvent.click(copyIdButton)
+ await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith('knowledge-1'))
+ expect(screen.getByRole('status')).toHaveTextContent('作品ID已复制')
expect(screen.getByRole('link', { name: '下载索菲亚图像影像规范' })).toHaveAttribute(
'href',
'/api/department-dashboard/brand-knowledge/knowledge-1/file',
diff --git a/components/department-dashboard/BrandKnowledgeDashboard.tsx b/components/department-dashboard/BrandKnowledgeDashboard.tsx
index 61c8e4d..95204ee 100644
--- a/components/department-dashboard/BrandKnowledgeDashboard.tsx
+++ b/components/department-dashboard/BrandKnowledgeDashboard.tsx
@@ -9,71 +9,92 @@ import { BRAND_KNOWLEDGE_TYPE_OPTIONS } from '@/lib/brand-knowledge-file-types'
const KNOWLEDGE_TYPES = ['全部', ...BRAND_KNOWLEDGE_TYPE_OPTIONS]
type BrandKnowledgeItem = { id: string; title: string; knowledgeType: string; brand: string; productCategoryName: string; fileName: string; mimeType: string; fileSize: number; createdAt: string; uploadedBy: string; downloadUrl: string; previewUrl: string | null; previewImageName: string | null }
-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 formatFileSize(size: number) { if (size < 1024) return `${size} B`; if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`; if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`; return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB` }
+function formatDate(value: string) { const date = new Date(value); if (Number.isNaN(date.getTime())) return value; const parts = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date); const values = Object.fromEntries(parts.map((part) => [part.type, part.value])); return `${values.year}.${values.month}.${values.day}` }
-function BrandKnowledgeCard({ item }: { item: BrandKnowledgeItem }) {
+function BrandKnowledgeCard({ item, onNotice }: { item: BrandKnowledgeItem; onNotice: (message: string) => void }) {
const details = [
formatFileSize(item.fileSize),
- item.uploadedBy ? `上传人:${item.uploadedBy}` : '',
+ item.uploadedBy,
formatDate(item.createdAt),
+ item.knowledgeType,
+ item.brand && item.brand !== '未关联品牌' ? item.brand : '',
+ item.productCategoryName,
].filter(Boolean)
+ const handleCopyId = async () => {
+ try {
+ await navigator.clipboard.writeText(item.id)
+ onNotice('作品ID已复制')
+ } catch {
+ const textarea = document.createElement('textarea')
+ textarea.value = item.id
+ textarea.style.position = 'fixed'
+ textarea.style.left = '-999999px'
+ document.body.appendChild(textarea)
+ textarea.select()
+ try {
+ const copied = document.execCommand('copy')
+ onNotice(copied ? '作品ID已复制' : '作品ID复制失败,请手动复制')
+ } catch {
+ onNotice('作品ID复制失败,请手动复制')
+ } finally {
+ document.body.removeChild(textarea)
+ }
+ }
+ }
+
return (
-
-
+
+
{item.previewUrl ? (
) : (
-
-
+
{item.title}
-
+
{details.map((detail, index) => (
-
+
{index > 0 && · }
{detail}
))}
-
- {item.fileName}
-
+
-
- {item.knowledgeType}
- {item.brand && item.brand !== '未关联品牌' && (
- <>/{item.brand}>
- )}
- {item.productCategoryName && (
- <>/{item.productCategoryName}>
- )}
-
-
-
+
@@ -101,7 +122,7 @@ export default function BrandKnowledgeDashboard() {
}, [])
useEffect(() => { void loadKnowledge() }, [loadKnowledge])
- const visibleItems = useMemo(() => { const keyword = searchKeyword.trim().toLowerCase(); return items.filter((item) => (selectedType === '全部' || item.knowledgeType === selectedType) && (!keyword || [item.title, item.fileName, item.brand, item.productCategoryName, item.uploadedBy].some((value) => value.toLowerCase().includes(keyword)))) }, [items, searchKeyword, selectedType])
+ const visibleItems = useMemo(() => { const keyword = searchKeyword.trim().toLowerCase(); return items.filter((item) => (selectedType === '全部' || item.knowledgeType === selectedType) && (!keyword || [item.id, item.title, item.fileName, item.brand, item.productCategoryName, item.uploadedBy].some((value) => value.toLowerCase().includes(keyword)))) }, [items, searchKeyword, selectedType])
const emptyMessage = useMemo(() => {
if (loadError) return loadError
@@ -194,7 +215,7 @@ export default function BrandKnowledgeDashboard() {
- {isLoading ?
正在读取知识内容…
: visibleItems.length > 0 ?
{visibleItems.map((item) => )}
:
+ {isLoading ?
正在读取知识内容…
: visibleItems.length > 0 ?
{visibleItems.map((item) => )}
:
{emptyMessage}
点击右上角“上传知识”,即可将文件保存到品牌知识库。