feat(web): 材料库排序依据/分组开关 + 健康档标签与分区
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCShKevM5prWZhp1kk1iGh
This commit is contained in:
parent
929a5dc809
commit
8c82d18117
|
|
@ -0,0 +1,698 @@
|
|||
import { DATA_TYPE } from '@/common/constants'
|
||||
import { ADMIN_TYPE, AnyObj, ECO_LEVEL, Material } from '@/common/types'
|
||||
import { searchParams } from '@/common/utils'
|
||||
import MaterialModalForm from '@/components/material-modal-form'
|
||||
import PageCard from '@/components/page-card'
|
||||
import api from '@/services/api'
|
||||
import {
|
||||
ActionType,
|
||||
LightFilter,
|
||||
ProFormCascader,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProTable,
|
||||
QueryFilter
|
||||
} from '@ant-design/pro-components'
|
||||
import { Button, Form, InputNumber, Modal, Popconfirm, Select, Switch, Tag, message } from 'antd'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useModel } from '@umijs/max'
|
||||
import ImportMaterialsModal from '../import-file-modal/materials'
|
||||
|
||||
const MaterialCard: React.FC<{
|
||||
title?: string
|
||||
modal?: boolean
|
||||
modalProps?: {
|
||||
open?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
defaultSelected?: Material[]
|
||||
onAdd?: (addedMtrls?: Material[]) => void
|
||||
}> = ({ title, modal, modalProps, defaultSelected, onAdd }) => {
|
||||
const [tab, setTab] = useState(DATA_TYPE.PUBLIC)
|
||||
const tableRef = useRef<ActionType>()
|
||||
const [filterForm] = Form.useForm()
|
||||
const [filterConditions, setFilterConditions] = useState<AnyObj>()
|
||||
const [details, setDetails] = useState<Partial<Material>>()
|
||||
const [selected, setSelected] = useState<Material[] | undefined>()
|
||||
// 材料排序依据(默认综合 Ypt 升序)与是否按健康档分组显示
|
||||
const [sortKey, setSortKey] = useState<string>('ypt')
|
||||
const [groupByTier, setGroupByTier] = useState<boolean>(false)
|
||||
const { initialState } = useModel('@@initialState')
|
||||
const cert = initialState?.cert
|
||||
const isAdmin = !!cert?.is_admin
|
||||
// 公共材料编辑权限:超管,或具备材料管理权限的管理员(与后端 authz 规则一致)
|
||||
const canPubMtrlMgmt =
|
||||
isAdmin &&
|
||||
(cert?.type === ADMIN_TYPE.SUPER_ADMIN ||
|
||||
(cert?.type === ADMIN_TYPE.ADMIN && cert?.authz_mtrl_mgmt === 1))
|
||||
const pageDataRef = useRef<Material[]>([])
|
||||
// 仅在「手动排序」视图下启用排序控件(避免在按其他字段排序时破坏 sort_order 语义)
|
||||
const canSort = !modal && tab === DATA_TYPE.PUBLIC && isAdmin && (!filterConditions?.sort || filterConditions?.sort === 'sort_order')
|
||||
// pageDataRef.current 为全部材料(按 sort_order 升序)。输入目标全局序号(1-based)直接定位:
|
||||
// 把材料移到目标位置后,对全部材料按新顺序连续重编号(0,1,2,...),保证全局顺序一致、不跨页错乱
|
||||
const moveSortTo = async (material_id: string, targetPos: number) => {
|
||||
const list = pageDataRef.current ?? []
|
||||
const fromIdx = list.findIndex((m) => m.material_id === material_id)
|
||||
if (fromIdx < 0) return
|
||||
let toIdx = Math.floor(targetPos) - 1
|
||||
if (Number.isNaN(toIdx)) return
|
||||
if (toIdx < 0) toIdx = 0
|
||||
if (toIdx >= list.length) toIdx = list.length - 1
|
||||
if (toIdx === fromIdx) return
|
||||
const arr = [...list]
|
||||
const [moved] = arr.splice(fromIdx, 1)
|
||||
arr.splice(toIdx, 0, moved)
|
||||
const updates: { material_id: string; sort_order: number }[] = []
|
||||
arr.forEach((m, i) => {
|
||||
if ((m.sort_order ?? 0) !== i) {
|
||||
updates.push({ material_id: m.material_id, sort_order: i })
|
||||
}
|
||||
})
|
||||
if (!updates.length) return
|
||||
try {
|
||||
await Promise.all(updates.map((u) => api.material.updatePubSort(u)))
|
||||
message.success('排序已更新')
|
||||
await tableRef?.current?.reload?.()
|
||||
} catch (err) {}
|
||||
}
|
||||
// 上下箭头:复用 moveSortTo,按全局位置上移/下移一格
|
||||
const moveSort = async (material_id: string, dir: -1 | 1) => {
|
||||
const list = pageDataRef.current ?? []
|
||||
const idx = list.findIndex((m) => m.material_id === material_id)
|
||||
if (idx < 0) return
|
||||
const ni = idx + dir
|
||||
if (ni < 0 || ni >= list.length) return
|
||||
await moveSortTo(material_id, ni + 1)
|
||||
}
|
||||
// 取材料在全部数据中的全局序号(0-based),用于排序输入框显示
|
||||
const globalIndexOf = (material_id: string) => (pageDataRef.current ?? []).findIndex((m) => m.material_id === material_id)
|
||||
const content = (
|
||||
<PageCard
|
||||
title={title}
|
||||
defActTabKey={DATA_TYPE.PUBLIC}
|
||||
tabItems={[
|
||||
{
|
||||
key: DATA_TYPE.PUBLIC,
|
||||
label: '公共库'
|
||||
},
|
||||
{
|
||||
key: DATA_TYPE.PRIVATE,
|
||||
label: '自建库'
|
||||
}
|
||||
]}
|
||||
onTabChange={(key) => setTab(key as DATA_TYPE)}
|
||||
extra={
|
||||
!modal && tab === DATA_TYPE.PRIVATE
|
||||
? [
|
||||
<ImportMaterialsModal
|
||||
key="import-modal"
|
||||
title="导入自建材料"
|
||||
trigger={<Button>导入材料</Button>}
|
||||
onSuccess={async () => {
|
||||
await tableRef?.current?.reload?.()
|
||||
}}
|
||||
/>,
|
||||
<MaterialModalForm
|
||||
key="create-modal-form"
|
||||
title="新建自建材料"
|
||||
displayDetails
|
||||
trigger={<Button type="primary">新建材料</Button>}
|
||||
onFinish={async (data) => {
|
||||
try {
|
||||
await api.material.createSelf(data)
|
||||
message.success('新建成功')
|
||||
await tableRef?.current?.reload?.()
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}}
|
||||
/>
|
||||
]
|
||||
: []
|
||||
}
|
||||
cardStyle={
|
||||
modal
|
||||
? {
|
||||
borderBottomWidth: 0
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className="flex mb-4">
|
||||
<div style={{ width: 'calc(100% - 80px)' }}>
|
||||
<LightFilter form={filterForm} onFinish={async (data) => setFilterConditions(data)}>
|
||||
<ProFormText name="material_id" label="材料ID" />
|
||||
<ProFormText name="name" label="材料名称" />
|
||||
{/* 公共库/自建库改由顶部 tab 切换,此处不再需要「类型」筛选 */}
|
||||
<ProFormCascader
|
||||
name="category"
|
||||
label="材料类别"
|
||||
fieldProps={{
|
||||
placeholder: '请选择大类/子类(可多选跨类)',
|
||||
multiple: true,
|
||||
showSearch: true,
|
||||
changeOnSelect: true,
|
||||
maxTagCount: 'responsive'
|
||||
}}
|
||||
request={async () => {
|
||||
const cats = await api.config.getMtrlCatList()
|
||||
return cats.map((item) => ({
|
||||
value: item.first_category,
|
||||
label: item.first_category,
|
||||
children: item.second_categories.map((c) => ({
|
||||
value: c.second_category,
|
||||
label: c.second_category
|
||||
}))
|
||||
}))
|
||||
}}
|
||||
transform={(val) => ({ category: (val as string[][])?.map((v: string[]) => v.join('/')) ?? undefined })}
|
||||
/>
|
||||
<ProFormText name="brand" label="材料品牌" />
|
||||
<ProFormText name="factory" label="材料厂家" />
|
||||
<ProFormText name="spec" label="材料规格" />
|
||||
<ProFormSelect
|
||||
name="eco_level"
|
||||
label="环保级别"
|
||||
options={[{ value: ECO_LEVEL.E0 }, { value: ECO_LEVEL.E1 }, { value: ECO_LEVEL.E2 }]}
|
||||
mode="multiple"
|
||||
/>
|
||||
<ProFormSelect
|
||||
name="health_level"
|
||||
label="健康等级"
|
||||
options={[{ value: 'A' }, { value: 'B' }, { value: 'C' }]}
|
||||
mode="multiple"
|
||||
/>
|
||||
{modal || tab === DATA_TYPE.PUBLIC ? (
|
||||
<ProFormSelect
|
||||
name="collected"
|
||||
label="收藏"
|
||||
options={[
|
||||
{ label: '已收藏', value: 1 },
|
||||
{ label: '未收藏', value: 0 }
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ProFormSelect
|
||||
name="sort"
|
||||
label="排序"
|
||||
options={[
|
||||
{
|
||||
label: '手动排序',
|
||||
value: 'sort_order'
|
||||
},
|
||||
{
|
||||
label: '材料类别A-Z',
|
||||
value: 'category'
|
||||
},
|
||||
{
|
||||
label: '最近更新时间由近到远',
|
||||
value: 'updated_at'
|
||||
},
|
||||
{
|
||||
label: '甲醛释放量由低到高',
|
||||
value: 'methanal_be_area'
|
||||
},
|
||||
{
|
||||
label: 'TVOC释放量由低到高',
|
||||
value: 'tvoc_be_area'
|
||||
},
|
||||
{
|
||||
label: '苯释放量由低到高',
|
||||
value: 'benzene_be_area'
|
||||
},
|
||||
{
|
||||
label: '甲苯释放量由低到高',
|
||||
value: 'toluene_be_area'
|
||||
},
|
||||
{
|
||||
label: '二甲苯释放量由低到高',
|
||||
value: 'p_xylene_be_area'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</LightFilter>
|
||||
</div>
|
||||
<div style={{ width: 80 }} className="flex justify-center">
|
||||
<Button
|
||||
key="reset"
|
||||
onClick={() => {
|
||||
setFilterConditions(undefined)
|
||||
filterForm.resetFields()
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<span>
|
||||
排序依据{' '}
|
||||
<Select
|
||||
size="small"
|
||||
style={{ width: 160 }}
|
||||
value={sortKey}
|
||||
onChange={(v) => {
|
||||
setSortKey(v)
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
options={[
|
||||
{ value: 'ypt', label: '综合 Ypt' },
|
||||
{ value: 'methanal_be_area', label: '甲醛 Yp' },
|
||||
{ value: 'tvoc_be_area', label: 'TVOC Yp' },
|
||||
{ value: 'benzene_be_area', label: '苯 Yp' },
|
||||
{ value: 'toluene_be_area', label: '甲苯 Yp' },
|
||||
{ value: 'p_xylene_be_area', label: '二甲苯 Yp' }
|
||||
]}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
按健康等级分组{' '}
|
||||
<Switch
|
||||
size="small"
|
||||
checked={groupByTier}
|
||||
onChange={(c) => {
|
||||
setGroupByTier(c)
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<ProTable<Material>
|
||||
cardProps={{
|
||||
bodyStyle: {
|
||||
padding: 0
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
search={false}
|
||||
options={false}
|
||||
actionRef={tableRef}
|
||||
rowSelection={
|
||||
modal
|
||||
? {
|
||||
type: 'checkbox',
|
||||
hideSelectAll: true,
|
||||
selectedRowKeys: selected?.map((m) => m.material_id),
|
||||
onSelect: (mtrl, isSelected) => {
|
||||
setSelected((pre) => {
|
||||
if (isSelected) {
|
||||
if ((pre ?? []).find((m) => m.material_id === mtrl.material_id)) return pre
|
||||
return [...(pre ?? []), mtrl]
|
||||
} else {
|
||||
return (pre ?? []).filter((m) => m.material_id !== mtrl.material_id)
|
||||
}
|
||||
})
|
||||
},
|
||||
getCheckboxProps: (mtrl) => ({
|
||||
disabled:
|
||||
defaultSelected &&
|
||||
defaultSelected.findIndex((m) => m.material_id === mtrl.material_id) !== -1
|
||||
})
|
||||
}
|
||||
: false
|
||||
}
|
||||
onRow={(mtrl) => ({
|
||||
onClick: () => {
|
||||
if (!defaultSelected?.find((m) => m.material_id === mtrl.material_id)) {
|
||||
setSelected((pre) => {
|
||||
if (pre) {
|
||||
if (pre.find((m) => m.material_id === mtrl.material_id)) {
|
||||
return pre.filter((m) => m.material_id !== mtrl.material_id)
|
||||
} else {
|
||||
const nxt = [...pre]
|
||||
nxt.push(mtrl)
|
||||
return nxt
|
||||
}
|
||||
} else {
|
||||
return [mtrl]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})}
|
||||
scroll={modal ? { y: 'calc(100vh - 400px)' } : undefined}
|
||||
params={{ type: tab, conditions: filterConditions, sortKey, groupByTier }}
|
||||
columns={[
|
||||
{
|
||||
title: '材料ID',
|
||||
dataIndex: 'material_id',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '材料名称',
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
render: (_, m) => {
|
||||
const tierHead = (m as Material & { __tierHead?: string }).__tierHead
|
||||
return (
|
||||
<>
|
||||
{tierHead && (
|
||||
<div style={{ fontWeight: 600, color: '#888', margin: '4px 0' }}>
|
||||
{'档位 ' + tierHead}
|
||||
</div>
|
||||
)}
|
||||
<span>{m.name}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
render: (_, { material_id }) => <Tag>{material_id.startsWith('P') ? '公共' : '自建'}</Tag>,
|
||||
hideInTable: !modal
|
||||
},
|
||||
{
|
||||
title: '材料类别',
|
||||
dataIndex: 'category',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '材料品牌',
|
||||
dataIndex: 'brand',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '材料厂家',
|
||||
dataIndex: 'factory',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '材料规格',
|
||||
dataIndex: 'spec',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '环保等级',
|
||||
dataIndex: 'eco_level',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: (_, { eco_level }) => (eco_level ? <Tag>{eco_level}</Tag> : '-')
|
||||
},
|
||||
{
|
||||
title: '健康等级',
|
||||
dataIndex: 'health_level',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: (_, { health_level }) => (health_level ? <Tag color={health_level === 'A' ? 'green' : health_level === 'B' ? 'orange' : 'red'}>{health_level}</Tag> : '-')
|
||||
},
|
||||
{
|
||||
title: '健康档',
|
||||
dataIndex: 'health_tier',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
render: (_, m) => {
|
||||
const color =
|
||||
m.health_tier === 'A' ? 'green' : m.health_tier === 'B' ? 'gold' : m.health_tier === 'C' ? 'red' : 'default'
|
||||
return <Tag color={color}>{m.health_tier ?? '—'}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最近更新时间',
|
||||
dataIndex: 'updated_at',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
renderText: (dt) => new Date(dt).toLocaleString('zh-CN'),
|
||||
search: false
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
align: 'center',
|
||||
width: !modal && tab === DATA_TYPE.PRIVATE ? 100 : canSort ? 180 : 70,
|
||||
render: (_, item, idx) => (
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={(e) => {
|
||||
setDetails(item)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
{canSort ? (() => {
|
||||
const gIdx = globalIndexOf(item.material_id)
|
||||
const total = pageDataRef.current?.length ?? 0
|
||||
return (
|
||||
<>
|
||||
<InputNumber
|
||||
key={`so-${item.material_id}-${gIdx}`}
|
||||
size="small"
|
||||
min={1}
|
||||
max={total || 1}
|
||||
defaultValue={gIdx + 1}
|
||||
style={{ width: 56 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onPressEnter={(e) => {
|
||||
const v = Number((e.target as HTMLInputElement).value)
|
||||
if (!Number.isNaN(v)) moveSortTo(item.material_id, v)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const v = Number((e.target as HTMLInputElement).value)
|
||||
if (!Number.isNaN(v)) moveSortTo(item.material_id, v)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
disabled={gIdx <= 0}
|
||||
onClick={(e) => {
|
||||
moveSort(item.material_id, -1)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
↑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
disabled={gIdx >= total - 1}
|
||||
onClick={(e) => {
|
||||
moveSort(item.material_id, 1)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
↓
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
})() : (
|
||||
''
|
||||
)}
|
||||
{!modal && tab === DATA_TYPE.PRIVATE ? (
|
||||
<Popconfirm
|
||||
title="确定删除该材料吗?"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
onConfirm={async (e) => {
|
||||
try {
|
||||
await api.material.removeSelf({
|
||||
material_id: item.material_id
|
||||
})
|
||||
message.success('删除成功')
|
||||
await tableRef?.current?.reload?.()
|
||||
e?.stopPropagation()
|
||||
} catch (err) {}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style={{ padding: 0 }}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '收藏',
|
||||
valueType: 'option',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
render: (_, { material_id, collected }) => (
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={async (e) => {
|
||||
try {
|
||||
const c = collected ?? 0 ? 0 : 1
|
||||
await api.material.collectPub({ material_id, c })
|
||||
message.success(c ? '收藏成功' : '取消成功')
|
||||
await tableRef.current?.reload?.()
|
||||
e.stopPropagation()
|
||||
} catch (err) {}
|
||||
}}
|
||||
>
|
||||
{collected ? '取消' : '收藏'}
|
||||
</Button>
|
||||
),
|
||||
hideInTable: modal || tab === DATA_TYPE.PRIVATE
|
||||
}
|
||||
]}
|
||||
request={async ({ type, conditions, sortKey: skParam, groupByTier: gbtParam, ...other }) => {
|
||||
// 弹窗与独立页面统一:都按当前 tab(type) 分别查公共库或自建库
|
||||
try {
|
||||
const { sort, ...otherConditions } = conditions ?? {}
|
||||
const sortByCategory = sort === 'category'
|
||||
const data = await api.material.search(
|
||||
type,
|
||||
searchParams(
|
||||
{
|
||||
...otherConditions,
|
||||
...other,
|
||||
display: 1,
|
||||
with_cltrs: 1,
|
||||
// 顶部「排序依据」控件的默认值(如 ypt 升序);若下方筛选区的「排序」被管理员显式选中,
|
||||
// 下面的第三个参数会覆盖此处的 sort_key/sort_val
|
||||
sort_key: skParam,
|
||||
sort_val: 'asc',
|
||||
group_by_tier: gbtParam ? 1 : 0
|
||||
},
|
||||
false,
|
||||
!sortByCategory && sort
|
||||
? {
|
||||
[sort]: sort !== 'updated_at' ? 'ascend' : 'descend'
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
)
|
||||
pageDataRef.current = data
|
||||
if (sortByCategory) {
|
||||
data.sort((a, b) => (a.category ?? '').localeCompare(b.category ?? ''))
|
||||
}
|
||||
if (gbtParam) {
|
||||
// 分组开时后端已按档排序;为每档第一条打标,供「材料名称」列渲染分区小标题
|
||||
let last: string | null | undefined = undefined
|
||||
data.forEach((m) => {
|
||||
if (m.health_tier !== last) {
|
||||
;(m as Material & { __tierHead?: string }).__tierHead = m.health_tier ?? '数据不全'
|
||||
last = m.health_tier
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false
|
||||
}
|
||||
}
|
||||
}}
|
||||
rowKey={(data) => data.material_id}
|
||||
pagination={
|
||||
!modal
|
||||
? {
|
||||
position: ['bottomRight'],
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true
|
||||
}
|
||||
: false
|
||||
}
|
||||
/>
|
||||
<MaterialModalForm
|
||||
title={
|
||||
!modal && tab === DATA_TYPE.PUBLIC && canPubMtrlMgmt
|
||||
? '编辑公共材料'
|
||||
: `${
|
||||
!modal
|
||||
? tab === DATA_TYPE.PUBLIC
|
||||
? '公共'
|
||||
: '自建'
|
||||
: details?.material_id?.startsWith('P')
|
||||
? '公共'
|
||||
: '自建'
|
||||
}材料详情`
|
||||
}
|
||||
initialValues={details}
|
||||
displayId
|
||||
displayDetails={
|
||||
!modal
|
||||
? tab === DATA_TYPE.PRIVATE || (tab === DATA_TYPE.PUBLIC && canPubMtrlMgmt)
|
||||
: !details?.material_id?.startsWith('P')
|
||||
}
|
||||
readonly={modal || (tab === DATA_TYPE.PUBLIC && !canPubMtrlMgmt)}
|
||||
open={details !== undefined}
|
||||
submitText="保存"
|
||||
onFinish={
|
||||
!modal && (tab === DATA_TYPE.PRIVATE || (tab === DATA_TYPE.PUBLIC && canPubMtrlMgmt))
|
||||
? async (values) => {
|
||||
const { material_id, ...data } = values
|
||||
// 公共材料无 unit 字段,剔除以避免后端校验失败
|
||||
delete (data as AnyObj).unit
|
||||
try {
|
||||
if (tab === DATA_TYPE.PUBLIC) {
|
||||
await api.material.patchPub({ material_id }, data)
|
||||
} else {
|
||||
await api.material.patchSelf({ material_id }, data)
|
||||
}
|
||||
message.success('修改成功')
|
||||
await tableRef?.current?.reload?.()
|
||||
setDetails(undefined)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onCancel={() => setDetails(undefined)}
|
||||
/>
|
||||
</PageCard>
|
||||
)
|
||||
return !modal ? (
|
||||
content
|
||||
) : (
|
||||
<Modal
|
||||
style={{
|
||||
top: 65
|
||||
}}
|
||||
styles={{
|
||||
content: {
|
||||
padding: 0
|
||||
},
|
||||
footer: {
|
||||
margin: 0
|
||||
}
|
||||
}}
|
||||
width="calc(100% - 100px)"
|
||||
footer={
|
||||
<div className="space-x-4 pb-4 pr-[20px]">
|
||||
<Button style={{ width: 64 }} onClick={modalProps?.onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" style={{ width: 64 }} onClick={() => onAdd?.(selected)}>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
maskClosable={false}
|
||||
destroyOnClose
|
||||
open={modalProps?.open}
|
||||
afterClose={() => {
|
||||
setTab(DATA_TYPE.PUBLIC)
|
||||
setFilterConditions(undefined)
|
||||
filterForm.resetFields()
|
||||
setSelected(undefined)
|
||||
}}
|
||||
onCancel={modalProps?.onCancel}
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialCard
|
||||
|
|
@ -211,6 +211,7 @@ export async function search(
|
|||
with_cltrs?: number
|
||||
sort_key?: Arrayable<string>
|
||||
sort_val?: Arrayable<string>
|
||||
group_by_tier?: 0 | 1
|
||||
page?: number
|
||||
size?: number
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue