fix: 显示调研草稿发布反馈

This commit is contained in:
江林铖 2026-07-30 12:09:44 +08:00
parent 89878b7880
commit 1859d03760
2 changed files with 127 additions and 4 deletions

View File

@ -104,6 +104,16 @@ function jsonResponse(data: unknown) {
} as unknown as Response
}
function jsonErrorResponse(error: string, status = 409) {
return {
ok: false,
status,
headers: { get: () => 'application/json' },
json: async () => ({ success: false, error }),
text: async () => '',
} as unknown as Response
}
describe('考核调研顶层导航保护', () => {
const originalFetch = global.fetch
@ -241,4 +251,67 @@ describe('考核调研顶层导航保护', () => {
expect.anything(),
))
})
test('我发起的草稿发布失败时展示发布进度和后端原因', async () => {
mockSearchParams = new URLSearchParams('tab=personal&workspace=created')
let resolvePublish: (response: Response) => void = () => {}
const publishResponse = new Promise<Response>((resolve) => {
resolvePublish = resolve
})
global.fetch = jest.fn(async (input, init) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: { id: 'employee-admin', displayName: '管理员' },
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.endsWith('/surveys') && (!init?.method || init.method === 'GET')) {
return jsonResponse({
summary: {
pendingCount: 0,
submittedCount: 0,
createdCount: 1,
openCreatedCount: 0,
},
pending: [],
submitted: [],
created: [{
id: 'survey-draft',
title: '2026年7月 · AI作品评审',
status: 'draft',
departmentName: '综合支持部',
dueAt: null,
counts: { targets: 1, assignments: 3, responses: 0 },
}],
})
}
if (url.endsWith('/surveys/survey-draft') && init?.method === 'PATCH') {
return publishResponse
}
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
const publishButtons = await screen.findAllByRole('button', { name: '发布调研:2026年7月 · AI作品评审' })
fireEvent.click(publishButtons[0])
expect(await screen.findAllByText('发布中...')).not.toHaveLength(0)
resolvePublish(jsonErrorResponse('视觉评分作品尚未上传完整:无邪(0/3)'))
expect(await screen.findAllByRole('alert')).not.toHaveLength(0)
expect(await screen.findAllByText('发布失败:视觉评分作品尚未上传完整:无邪(0/3)')).not.toHaveLength(0)
expect(screen.getAllByRole('link', { name: '继续上传作品' })[0]).toHaveAttribute(
'href',
'/performance-survey/survey/survey-draft',
)
expect(screen.getAllByRole('button', { name: '发布调研:2026年7月 · AI作品评审' })[0]).toBeEnabled()
})
})

View File

@ -2335,15 +2335,29 @@ function CreatedList({
resultBackHref?: string
}) {
const [busyId, setBusyId] = useState<string | null>(null)
const [actionErrors, setActionErrors] = useState<Record<string, string>>({})
const changeStatus = async (id: string, status: string) => {
const actionLabel = status === 'open' ? '发布' : '关闭'
setBusyId(id)
setActionErrors((current) => {
if (!current[id]) return current
const next = { ...current }
delete next[id]
return next
})
try {
await apiRequest(`/surveys/${id}`, {
method: 'PATCH',
body: JSON.stringify({ status }),
})
await onRefresh()
} catch (error) {
const message = error instanceof Error ? error.message : `${actionLabel}失败,请稍后重试`
setActionErrors((current) => ({
...current,
[id]: `${actionLabel}失败:${message}`,
}))
} finally {
setBusyId(null)
}
@ -2382,6 +2396,8 @@ function CreatedList({
const progressText = assignmentCount > 0
? `已提交 ${responseCount}/${assignmentCount}`
: '尚未生成评价任务'
const actionError = actionErrors[item.id]
const needsWorkItemUpload = actionError?.includes('作品尚未上传完整')
return (
<article
@ -2411,7 +2427,7 @@ function CreatedList({
aria-label={`发布调研:${item.title}`}
className="min-h-11 rounded border border-[#b8d0ff] bg-[#edf4ff] px-4 text-sm font-bold text-[#0050cb] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0050cb] focus-visible:ring-offset-2 disabled:opacity-50"
>
发布
{busyId === item.id ? '发布中...' : '发布'}
</button>
)}
{item.status !== 'closed' && item.status !== 'cancelled' && (
@ -2422,7 +2438,7 @@ function CreatedList({
aria-label={`关闭调研:${item.title}`}
className="min-h-11 rounded border border-[#c2c6d8] bg-white px-4 text-sm font-bold text-[#1a1c1c] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0050cb] focus-visible:ring-offset-2 disabled:opacity-50"
>
关闭
{busyId === item.id ? '处理中...' : '关闭'}
</button>
)}
{item.canCancel && (
@ -2446,6 +2462,22 @@ function CreatedList({
</Link>
)}
</div>
{actionError && (
<div
role="alert"
className="mt-3 rounded border border-[#f0b8b8] bg-[#fff5f5] px-3 py-2 text-[13px] font-medium leading-5 text-[#b42318]"
>
{actionError}
{needsWorkItemUpload && (
<Link
href={`/performance-survey/survey/${item.id}`}
className="ml-2 inline-flex min-h-8 items-center rounded border border-[#f0b8b8] bg-white px-2.5 font-bold text-[#b42318] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#b42318] focus-visible:ring-offset-2"
>
继续上传作品
</Link>
)}
</div>
)}
</article>
)
})}
@ -2467,6 +2499,8 @@ function CreatedList({
const targetCount = Number(item.counts?.targets || 0)
const assignmentCount = Number(item.counts?.assignments || 0)
const responseCount = Number(item.counts?.responses || 0)
const actionError = actionErrors[item.id]
const needsWorkItemUpload = actionError?.includes('作品尚未上传完整')
return (
<tr key={item.id} className="text-sm text-[#1a1c1c] hover:bg-[#fcfcfc]">
@ -2492,7 +2526,7 @@ function CreatedList({
aria-label={`发布调研:${item.title}`}
className="min-h-9 rounded border border-[#b8d0ff] bg-[#edf4ff] px-3 text-[13px] font-bold text-[#0050cb] hover:bg-[#dae8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0050cb] focus-visible:ring-offset-2 disabled:opacity-50"
>
发布
{busyId === item.id ? '发布中...' : '发布'}
</button>
)}
{item.status !== 'closed' && item.status !== 'cancelled' && (
@ -2503,7 +2537,7 @@ function CreatedList({
aria-label={`关闭调研:${item.title}`}
className="min-h-9 rounded border border-[#c2c6d8] bg-white px-3 text-[13px] font-bold text-[#1a1c1c] hover:border-[#0050cb] hover:text-[#0050cb] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0050cb] focus-visible:ring-offset-2 disabled:opacity-50"
>
关闭
{busyId === item.id ? '处理中...' : '关闭'}
</button>
)}
{item.canCancel && (
@ -2527,6 +2561,22 @@ function CreatedList({
</Link>
)}
</div>
{actionError && (
<div
role="alert"
className="mt-2 rounded border border-[#f0b8b8] bg-[#fff5f5] px-3 py-2 text-left text-[12px] font-medium leading-5 text-[#b42318]"
>
{actionError}
{needsWorkItemUpload && (
<Link
href={`/performance-survey/survey/${item.id}`}
className="ml-2 inline-flex min-h-8 items-center rounded border border-[#f0b8b8] bg-white px-2.5 font-bold text-[#b42318] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#b42318] focus-visible:ring-offset-2"
>
继续上传作品
</Link>
)}
</div>
)}
</td>
</tr>
)