@@ -894,8 +896,8 @@ export default function NotificationCenterPanel({ showToast }: NotificationCente
投递记录
优先展示可理解的状态与处理建议。发送结果不确定时必须先人工核验。
-
-
+
+
{deliveriesError ?
{deliveriesError}。没有自动重发任何通知,请稍后刷新。
: deliveriesLoading ?
{[0,1,2].map((item) =>
)}
: deliveries.length === 0 ?
当前筛选条件下没有投递记录。发送测试通知后可在这里查看状态。
: (
diff --git a/components/department-dashboard/BrandKnowledgeDashboard.test.tsx b/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
index 72e087e..b82235f 100644
--- a/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
+++ b/components/department-dashboard/BrandKnowledgeDashboard.test.tsx
@@ -1,3 +1,4 @@
+import { changeField, openSelect } from '@/test-utils/select'
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'
@@ -89,7 +90,7 @@ describe('BrandKnowledgeDashboard', () => {
fireEvent.click(screen.getByRole('button', { name: '主图规范' }))
expect(screen.getByText('主图规范暂未上传内容')).toBeInTheDocument()
- fireEvent.change(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '索菲亚' } })
+ changeField(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '索菲亚' } })
expect(screen.getByText('没有找到与“索菲亚”匹配的知识内容')).toBeInTheDocument()
})
@@ -124,7 +125,7 @@ describe('BrandKnowledgeDashboard', () => {
render(
)
expect(await screen.findByRole('heading', { level: 3, name: '升降桌主图模版-索菲亚' })).toBeInTheDocument()
- fireEvent.change(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '主图模板' } })
+ changeField(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '主图模板' } })
expect(screen.getByRole('heading', { level: 3, name: '升降桌主图模版-索菲亚' })).toBeInTheDocument()
})
@@ -277,11 +278,12 @@ describe('BrandKnowledgeDashboard', () => {
fireEvent.click(await screen.findByRole('button', { name: '编辑索菲亚图像影像规范' }))
const editDialog = screen.getByRole('dialog', { name: '编辑品牌知识' })
- fireEvent.change(within(editDialog).getByLabelText('知识标题'), { target: { value: '更新后的规范' } })
- fireEvent.change(within(editDialog).getByLabelText('知识类型'), { target: { value: '主图模板' } })
- fireEvent.change(within(editDialog).getByLabelText('关联品牌'), { target: { value: '亿家达' } })
- await within(editDialog).findByRole('option', { name: '床类' })
- fireEvent.change(within(editDialog).getByLabelText('品类'), { target: { value: '床类' } })
+ changeField(within(editDialog).getByLabelText('知识标题'), { target: { value: '更新后的规范' } })
+ changeField(within(editDialog).getByLabelText('知识类型'), { target: { value: '主图模板' } })
+ changeField(within(editDialog).getByLabelText('关联品牌'), { target: { value: '亿家达' } })
+ openSelect(within(editDialog).getByLabelText('品类'))
+ await screen.findByRole('option', { name: '床类' })
+ changeField(within(editDialog).getByLabelText('品类'), { target: { value: '床类' } })
fireEvent.click(within(editDialog).getByRole('button', { name: '保存修改' }))
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith(
'/api/department-dashboard/brand-knowledge/knowledge-1',
@@ -332,13 +334,14 @@ describe('BrandKnowledgeDashboard', () => {
'sku模板', '主图视频规范', '主图视频模板', '影像规范', '品牌VI',
])
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ openSelect(screen.getByLabelText(/品类/))
await waitFor(() => expect(screen.getByRole('option', { name: '床类' })).toBeInTheDocument())
- fireEvent.change(screen.getByLabelText(/品类/), { target: { value: '床类' } })
+ changeField(screen.getByRole('combobox', { name: /品类/ }), { target: { value: '床类' } })
const knowledgeFile = new File(['品牌资料'], '品牌手册.pdf', { type: 'application/pdf' })
const previewFile = new File(['fake image'], '预览图.png', { type: 'image/png' })
- fireEvent.change(screen.getByLabelText('选择知识文件'), { target: { files: [knowledgeFile] } })
- fireEvent.change(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
+ changeField(screen.getByLabelText('选择知识文件'), { target: { files: [knowledgeFile] } })
+ changeField(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
expect(screen.getByText('品牌手册.pdf')).toBeInTheDocument()
expect(await screen.findByText('预览图.png')).toBeInTheDocument()
expect(await screen.findByRole('img', { name: '预览图:预览图.png' })).toHaveClass('object-contain', 'object-center')
@@ -394,7 +397,7 @@ describe('BrandKnowledgeDashboard', () => {
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
const previewFile = new File(['fake image'], '预览图.png', { type: 'image/png' })
- fireEvent.change(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
+ changeField(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
expect(await screen.findByText('10 B · 单张预览')).toBeInTheDocument()
expect(screen.getByRole('status')).toHaveTextContent('图片高度不超过 3000px,已保留为单张预览')
@@ -425,8 +428,8 @@ describe('BrandKnowledgeDashboard', () => {
render(
)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
- fireEvent.change(screen.getByLabelText('选择知识文件'), {
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
@@ -458,8 +461,8 @@ describe('BrandKnowledgeDashboard', () => {
render(
)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
- fireEvent.change(screen.getByLabelText('选择知识文件'), {
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
@@ -498,8 +501,8 @@ describe('BrandKnowledgeDashboard', () => {
const firstRender = render(
)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
- fireEvent.change(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
expect(await screen.findByText(/服务器处理超时(504)/)).toBeInTheDocument()
expect(localStorage.length).toBe(1)
@@ -507,8 +510,8 @@ describe('BrandKnowledgeDashboard', () => {
render(
)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
- fireEvent.change(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
expect(within(screen.getByRole('dialog', { name: '上传知识' })).getByRole('status')).toHaveTextContent('已恢复这个文件完成的分片上传')
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
@@ -528,8 +531,8 @@ describe('BrandKnowledgeDashboard', () => {
render(
)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
- fireEvent.change(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
- fireEvent.change(screen.getByLabelText('选择知识文件'), {
+ changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
+ changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
diff --git a/components/department-dashboard/BrandKnowledgeDashboard.tsx b/components/department-dashboard/BrandKnowledgeDashboard.tsx
index 3e8523b..0c6c673 100644
--- a/components/department-dashboard/BrandKnowledgeDashboard.tsx
+++ b/components/department-dashboard/BrandKnowledgeDashboard.tsx
@@ -1,5 +1,7 @@
'use client'
+import Select from '@/components/ui/Select'
+
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import Image from 'next/image'
import Link from 'next/link'
@@ -333,9 +335,9 @@ function EditDialog({ item, onClose, onSave }: {
{error &&
{error}
}
-
-
-
+
+
+
diff --git a/components/department-dashboard/BrandKnowledgeUploadDialog.tsx b/components/department-dashboard/BrandKnowledgeUploadDialog.tsx
index 4fbd58c..17ea747 100644
--- a/components/department-dashboard/BrandKnowledgeUploadDialog.tsx
+++ b/components/department-dashboard/BrandKnowledgeUploadDialog.tsx
@@ -1,5 +1,7 @@
'use client'
+import Select from '@/components/ui/Select'
+
import Image from 'next/image'
import { useCallback, useEffect, useRef, useState, type DragEvent, type RefObject } from 'react'
import { AlertCircle, FileText, FolderOpen, ImagePlus, Upload, X } from 'lucide-react'
@@ -466,8 +468,8 @@ export default function BrandKnowledgeUploadDialog({ onClose, onUploaded, return
{isUploading && uploadProgress !== null &&
主文件上传进度{uploadProgress}%{uploadSpeedLabel ? ` · ${uploadSpeedLabel}` : ''}
}
-
-
+
+
预览图片 (可选)
{previewImage && } event.preventDefault()} onDragLeave={handlePreviewDragLeave} onDrop={(event) => { event.preventDefault(); previewDragDepthRef.current = 0; setIsPreviewDragging(false); void setPreview(event.dataTransfer.files[0]) }} className={`rounded-xl border border-dashed p-4 transition-colors ${isPreviewDragging ? 'border-[#1677ff] bg-[#e6f4ff]' : 'border-[#cbd5e1] bg-[#f8fafc]'}`}>
{ const file = event.currentTarget.files?.[0]; event.currentTarget.value = ''; void setPreview(file) }} className="sr-only" />{previewImage ?
{previewUrl ? : }
{previewImage.name}
{formatFileSize(previewImage.size)} · {previewSlices.length === 1 ? '单张预览' : previewSlices.length > 1 ? `已切为 ${previewSlices.length} 片` : previewUsesServerFallback ? '将在服务器生成预览' : '正在生成安全预览'}
:
拖放预览图片到这里,或选择图片
支持 JPG、PNG、WebP,单张不超过 50MB
}
diff --git a/components/department-dashboard/MonthlyLaunchPeriodFilter.test.tsx b/components/department-dashboard/MonthlyLaunchPeriodFilter.test.tsx
index 5857ff9..5a23dd1 100644
--- a/components/department-dashboard/MonthlyLaunchPeriodFilter.test.tsx
+++ b/components/department-dashboard/MonthlyLaunchPeriodFilter.test.tsx
@@ -1,3 +1,4 @@
+import { changeField, openSelect } from '@/test-utils/select'
import { fireEvent, render, screen } from '@testing-library/react'
import MonthlyLaunchPeriodFilter from './MonthlyLaunchPeriodFilter'
@@ -29,11 +30,12 @@ describe('MonthlyLaunchPeriodFilter', () => {
it('shows available month presets and navigates with a month query', () => {
render(
)
+ openSelect(screen.getByRole('combobox', { name: '选择统计月份' }))
expect(screen.getByRole('option', { name: '本月' })).toBeInTheDocument()
expect(screen.getByRole('option', { name: '上月' })).toBeInTheDocument()
expect(screen.getByRole('option', { name: '自定义日期' })).toBeInTheDocument()
- fireEvent.change(screen.getByRole('combobox', { name: '选择统计月份' }), {
+ changeField(screen.getByRole('combobox', { name: '选择统计月份' }), {
target: { value: '2026-07' },
})
@@ -45,8 +47,8 @@ describe('MonthlyLaunchPeriodFilter', () => {
it('submits an inclusive custom date range', () => {
render(
)
- fireEvent.change(screen.getByLabelText(/统计开始日期/), { target: { value: '2026-06-15' } })
- fireEvent.change(screen.getByLabelText(/统计结束日期/), { target: { value: '2026-06-30' } })
+ changeField(screen.getByLabelText(/统计开始日期/), { target: { value: '2026-06-15' } })
+ changeField(screen.getByLabelText(/统计结束日期/), { target: { value: '2026-06-30' } })
expect(screen.getByRole('combobox', { name: '选择统计月份' })).toHaveValue('custom')
fireEvent.submit(screen.getByRole('form', { name: '本月上新日期筛选' }))
@@ -58,8 +60,8 @@ describe('MonthlyLaunchPeriodFilter', () => {
it('disables apply when the end date is before the start date', () => {
render(
)
- fireEvent.change(screen.getByLabelText(/统计开始日期/), { target: { value: '2026-08-20' } })
- fireEvent.change(screen.getByLabelText(/统计结束日期/), { target: { value: '2026-08-10' } })
+ changeField(screen.getByLabelText(/统计开始日期/), { target: { value: '2026-08-20' } })
+ changeField(screen.getByLabelText(/统计结束日期/), { target: { value: '2026-08-10' } })
expect(screen.getByRole('button', { name: /应用筛选/ })).toBeDisabled()
expect(mockPush).not.toHaveBeenCalled()
diff --git a/components/department-dashboard/MonthlyLaunchPeriodFilter.tsx b/components/department-dashboard/MonthlyLaunchPeriodFilter.tsx
index 91db683..8075acb 100644
--- a/components/department-dashboard/MonthlyLaunchPeriodFilter.tsx
+++ b/components/department-dashboard/MonthlyLaunchPeriodFilter.tsx
@@ -1,5 +1,7 @@
'use client'
+import Select from '@/components/ui/Select'
+
import { CalendarDays, Filter } from 'lucide-react'
import { FormEvent, useEffect, useState } from 'react'
import { usePathname, useRouter } from 'next/navigation'
@@ -72,7 +74,7 @@ export default function MonthlyLaunchPeriodFilter({
>
diff --git a/components/department-dashboard/UserPermissionsDashboard.tsx b/components/department-dashboard/UserPermissionsDashboard.tsx
index 56ad227..77fa105 100644
--- a/components/department-dashboard/UserPermissionsDashboard.tsx
+++ b/components/department-dashboard/UserPermissionsDashboard.tsx
@@ -1,5 +1,7 @@
'use client'
+import Select from '@/components/ui/Select'
+
import { useCallback, useEffect, useState } from 'react'
import Link from 'next/link'
import { useSession } from 'next-auth/react'
@@ -209,7 +211,7 @@ export default function UserPermissionsDashboard() {
{extraGroups.length > 0 && (