fix: 优化验收操作和图片去重
This commit is contained in:
@@ -44,10 +44,15 @@ function normalizePreviewItems(files: UploadedFile[], imageUrls: string[]) {
|
|||||||
mediumUrl: file.mediumUrl || file.url,
|
mediumUrl: file.mediumUrl || file.url,
|
||||||
}))
|
}))
|
||||||
.filter((item) => Boolean(item.url))
|
.filter((item) => Boolean(item.url))
|
||||||
const fileUrlSet = new Set(normalizedFiles.map((item) => item.url))
|
const fileUrlSet = new Set(normalizedFiles.map((item) => getImageIdentity(item.url)))
|
||||||
const normalizedUrls = imageUrls
|
const normalizedUrls = imageUrls
|
||||||
.map((url) => String(url || '').trim())
|
.map((url) => String(url || '').trim())
|
||||||
.filter((url) => url && !fileUrlSet.has(url))
|
.filter((url) => url && !fileUrlSet.has(getImageIdentity(url)))
|
||||||
.map((url) => ({ url, thumbnailUrl: url, mediumUrl: url }))
|
.map((url) => ({ url, thumbnailUrl: url, mediumUrl: url }))
|
||||||
return [...normalizedFiles, ...normalizedUrls]
|
return [...normalizedFiles, ...normalizedUrls]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 忽略临时签名参数,避免同一存储对象重复显示。 */
|
||||||
|
function getImageIdentity(value: string) {
|
||||||
|
return String(value || '').trim().split(/[?#]/, 1)[0]
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tag,
|
Tag,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import type { TableColumnsType } from 'antd'
|
import type { TableColumnsType } from 'antd'
|
||||||
@@ -167,20 +168,7 @@ export default function WorkerOrdersPage() {
|
|||||||
const source = hasSubmittedAcceptance(order)
|
const source = hasSubmittedAcceptance(order)
|
||||||
? getWorkerAcceptance(order)
|
? getWorkerAcceptance(order)
|
||||||
: getWorkerDraft(order)
|
: getWorkerDraft(order)
|
||||||
setAcceptanceFiles([
|
setAcceptanceFiles(getUniqueAcceptanceFiles(source))
|
||||||
...getAcceptanceFilesFromSource(source),
|
|
||||||
...(Array.isArray(source.imageUrls) ? source.imageUrls : []).map(
|
|
||||||
(url): UploadedFile => ({
|
|
||||||
objectKey: '',
|
|
||||||
url: String(url || ''),
|
|
||||||
thumbnailUrl: String(url || ''),
|
|
||||||
mediumUrl: String(url || ''),
|
|
||||||
filename: '图片',
|
|
||||||
contentType: '',
|
|
||||||
size: 0,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
form.setFieldsValue({ note: String(source.note || '') })
|
form.setFieldsValue({ note: String(source.note || '') })
|
||||||
setDetailOrder(null)
|
setDetailOrder(null)
|
||||||
}
|
}
|
||||||
@@ -438,27 +426,33 @@ export default function WorkerOrdersPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 260,
|
width: 190,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<Space wrap>
|
<div className="worker-order-actions">
|
||||||
<Button icon={<EyeOutlined />} onClick={() => setDetailOrder(row)}>
|
<Space size={8}>
|
||||||
详情
|
<Tooltip title="查看详情">
|
||||||
</Button>
|
<Button
|
||||||
|
aria-label="查看详情"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => setDetailOrder(row)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
{canEditAcceptanceImages(row) ? (
|
{canEditAcceptanceImages(row) ? (
|
||||||
<Button
|
<Button
|
||||||
icon={<PictureOutlined />}
|
icon={<PictureOutlined />}
|
||||||
type={canSubmitAcceptance(row) ? 'primary' : 'default'}
|
type={canSubmitAcceptance(row) ? 'primary' : 'default'}
|
||||||
onClick={() => openAcceptanceModal(row)}
|
onClick={() => openAcceptanceModal(row)}
|
||||||
>
|
>
|
||||||
{hasSubmittedAcceptance(row) ? '补充图片' : '上传图片 / 提交验收'}
|
{hasSubmittedAcceptance(row) ? '补充图片' : '验收'}
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
{canCancelOrder(row) ? (
|
|
||||||
<Button danger onClick={() => openCancelModal(row)}>
|
|
||||||
取消接单
|
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
|
{canCancelOrder(row) ? (
|
||||||
|
<Button type="link" danger size="small" onClick={() => openCancelModal(row)}>
|
||||||
|
取消接单
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -740,7 +734,7 @@ export default function WorkerOrdersPage() {
|
|||||||
editingOrder
|
editingOrder
|
||||||
? hasSubmittedAcceptance(editingOrder)
|
? hasSubmittedAcceptance(editingOrder)
|
||||||
? '补充验收图片'
|
? '补充验收图片'
|
||||||
: '提交验收'
|
: '验收图片'
|
||||||
: '验收图片'
|
: '验收图片'
|
||||||
}
|
}
|
||||||
open={Boolean(editingOrder)}
|
open={Boolean(editingOrder)}
|
||||||
@@ -762,10 +756,7 @@ export default function WorkerOrdersPage() {
|
|||||||
>
|
>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={() => void saveAcceptanceImages(form.getFieldsValue())}>
|
||||||
type="primary"
|
|
||||||
onClick={() => void saveAcceptanceImages(form.getFieldsValue())}
|
|
||||||
>
|
|
||||||
{hasSubmittedAcceptance(editingOrder) ? '保存修改' : '保存暂存'}
|
{hasSubmittedAcceptance(editingOrder) ? '保存修改' : '保存暂存'}
|
||||||
</Button>
|
</Button>
|
||||||
{canSubmitAcceptance(editingOrder) ? (
|
{canSubmitAcceptance(editingOrder) ? (
|
||||||
@@ -778,15 +769,7 @@ export default function WorkerOrdersPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" onFinish={submitAcceptance}>
|
<Form form={form} layout="vertical" onFinish={submitAcceptance}>
|
||||||
<Form.Item
|
<Form.Item label="完成说明" name="note">
|
||||||
label="完成说明"
|
|
||||||
name="note"
|
|
||||||
extra={
|
|
||||||
editingOrder && !hasSubmittedAcceptance(editingOrder)
|
|
||||||
? '图片和说明可先「保存暂存」,之后随时继续补充,不会丢失;确认完成后再「提交验收」。'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="可补充代练结果、注意事项或截图说明。"
|
placeholder="可补充代练结果、注意事项或截图说明。"
|
||||||
@@ -979,6 +962,38 @@ function getAcceptanceFilesFromSource(source: WorkerAcceptance): UploadedFile[]
|
|||||||
return Array.isArray(source.files) ? source.files : []
|
return Array.isArray(source.files) ? source.files : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 合并草稿兼容字段,并按固定图片地址去重,避免同一图片重复回填。 */
|
||||||
|
function getUniqueAcceptanceFiles(source: WorkerAcceptance): UploadedFile[] {
|
||||||
|
const files = [
|
||||||
|
...getAcceptanceFilesFromSource(source),
|
||||||
|
...(Array.isArray(source.imageUrls) ? source.imageUrls : []).map(
|
||||||
|
(url): UploadedFile => ({
|
||||||
|
objectKey: '',
|
||||||
|
url: String(url || ''),
|
||||||
|
thumbnailUrl: String(url || ''),
|
||||||
|
mediumUrl: String(url || ''),
|
||||||
|
filename: '图片',
|
||||||
|
contentType: '',
|
||||||
|
size: 0,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
const identities = new Set<string>()
|
||||||
|
return files.filter((file) => {
|
||||||
|
const identity = getImageIdentity(file.url || file.mediumUrl || file.thumbnailUrl || file.objectKey)
|
||||||
|
if (!identity || identities.has(identity)) return false
|
||||||
|
identities.add(identity)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 去除临时签名参数,确保同一个对象的不同访问链接能匹配。 */
|
||||||
|
function getImageIdentity(value: string): string {
|
||||||
|
const url = String(value || '').trim()
|
||||||
|
if (!url) return ''
|
||||||
|
return url.split(/[?#]/, 1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
function getAcceptanceFiles(row: WorkOrder): UploadedFile[] {
|
function getAcceptanceFiles(row: WorkOrder): UploadedFile[] {
|
||||||
return getAcceptanceFilesFromSource(getWorkerAcceptance(row))
|
return getAcceptanceFilesFromSource(getWorkerAcceptance(row))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1519,6 +1519,17 @@ select {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.worker-order-actions {
|
||||||
|
display: grid;
|
||||||
|
justify-items: start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-order-actions .ant-btn-link {
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.worker-order-detail-value {
|
.worker-order-detail-value {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
|||||||
Reference in New Issue
Block a user