feat(ms): 管理端材料库排序依据/分组 + 健康档标签
- Material 类型新增 ypt/health_tier 字段 - material.ts search() 查询参数补 group_by_tier - material-card 新增排序依据 Select(手动排序/综合 Ypt/五种污染物释放量)与按健康档分组 Switch, 健康档 Tag 列,分组时按档打分区标题 - 顶部排序依据 Select 为唯一排序权威来源:移除旧筛选区排序下拉对 searchParams 第三参数的驱动, 避免其静默覆盖新排序请求(对齐用户端 iapip-web 同一修复) - 经核查 ms 端不存在手动拖动/位置输入重排 sort_order 的既有功能,故无需像 web 端那样额外加 sortKey==='sort_order' && !groupByTier 门控 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCShKevM5prWZhp1kk1iGh
This commit is contained in:
parent
c7134f4332
commit
fb033ada28
|
|
@ -0,0 +1,298 @@
|
|||
export type AnyObj = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type Arrayable<T> = T | T[]
|
||||
|
||||
/**
|
||||
* Server Types
|
||||
*/
|
||||
|
||||
export type Admin = {
|
||||
id: string
|
||||
name: string
|
||||
password: string
|
||||
username: string
|
||||
status: number
|
||||
type: ADMIN_TYPE
|
||||
authz_hist_mgmt: number
|
||||
authz_tmpl_mgmt: number
|
||||
authz_mtrl_mgmt: number
|
||||
authz_audit_mgmt: number
|
||||
authz_acct_mgmt: number
|
||||
authz_sys_cfg: number
|
||||
creator_id: string
|
||||
creator_name: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export enum ADMIN_TYPE {
|
||||
SUPER_ADMIN = 1,
|
||||
ADMIN = 2
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
name: string
|
||||
password: string
|
||||
username: string
|
||||
status: number
|
||||
creator_id: string
|
||||
creator_name: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type AdminCert = {
|
||||
id: string
|
||||
name: string
|
||||
username: string
|
||||
status: number
|
||||
type: ADMIN_TYPE
|
||||
authz_hist_mgmt: number
|
||||
authz_tmpl_mgmt: number
|
||||
authz_mtrl_mgmt: number
|
||||
authz_audit_mgmt: number
|
||||
authz_acct_mgmt: number
|
||||
authz_sys_cfg: number
|
||||
ip: string
|
||||
login_at: Date
|
||||
expire_at: Date
|
||||
iat?: number
|
||||
exp?: number
|
||||
}
|
||||
|
||||
export type Creator = {
|
||||
id: string
|
||||
name: string
|
||||
username: string
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
id: string
|
||||
name: string
|
||||
category?: string
|
||||
province?: string
|
||||
city?: string
|
||||
climatic_zone?: string
|
||||
constr_area?: number
|
||||
usage_area?: number
|
||||
spaces?: Space[]
|
||||
status?: number
|
||||
display?: number
|
||||
predicted_at?: string
|
||||
score?: number
|
||||
grade?: number
|
||||
collectors?: { user_id: string; project_id: string }[]
|
||||
from?: FROM_TYPE
|
||||
creator_id?: string
|
||||
creator_name: string
|
||||
creator?: Creator
|
||||
related_user?: Creator
|
||||
related_admin?: Creator
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type Space = {
|
||||
id: string
|
||||
project_id: string
|
||||
name: string
|
||||
category?: string
|
||||
layout: number
|
||||
height?: number
|
||||
area?: number
|
||||
volume?: number
|
||||
env_temp?: number
|
||||
env_hum?: number
|
||||
env_vent_rate?: number
|
||||
apc_lmt_std_id?: string
|
||||
apc_lmt_std?: ApcLimitStandard
|
||||
materials?: Material[]
|
||||
methanal_c?: number
|
||||
tvoc_c?: number
|
||||
benzene_c?: number
|
||||
toluene_c?: number
|
||||
p_xylene_c?: number
|
||||
methanal_c_exceed?: number
|
||||
tvoc_c_exceed?: number
|
||||
benzene_c_exceed?: number
|
||||
toluene_c_exceed?: number
|
||||
p_xylene_c_exceed?: number
|
||||
creator_id: string
|
||||
creator_name: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type Material = {
|
||||
id?: number
|
||||
material_id: string
|
||||
project_id?: string
|
||||
space_id?: string
|
||||
name?: string
|
||||
category?: string
|
||||
brand?: string
|
||||
factory?: string
|
||||
spec?: string
|
||||
eco_level?: ECO_LEVEL
|
||||
unit?: MTRL_UNIT
|
||||
qty?: number
|
||||
display?: number
|
||||
ypt?: number | null
|
||||
health_tier?: 'A' | 'B' | 'C' | null
|
||||
collectors?: { user_id: string; project_id: string }[]
|
||||
methanal?: number
|
||||
methanal_min_be?: number
|
||||
methanal_be_area?: number
|
||||
methanal_be_roc?: number
|
||||
tvoc?: number
|
||||
tvoc_min_be?: number
|
||||
tvoc_be_area?: number
|
||||
tvoc_be_roc?: number
|
||||
benzene?: number
|
||||
benzene_min_be?: number
|
||||
benzene_be_area?: number
|
||||
benzene_be_roc?: number
|
||||
toluene?: number
|
||||
toluene_min_be?: number
|
||||
toluene_be_area?: number
|
||||
toluene_be_roc?: number
|
||||
p_xylene?: number
|
||||
p_xylene_min_be?: number
|
||||
p_xylene_be_area?: number
|
||||
p_xylene_be_roc?: number
|
||||
methanal_cr?: number
|
||||
tvoc_cr?: number
|
||||
benzene_cr?: number
|
||||
toluene_cr?: number
|
||||
p_xylene_cr?: number
|
||||
methanal_cr_exceed?: number
|
||||
tvoc_cr_exceed?: number
|
||||
benzene_cr_exceed?: number
|
||||
toluene_cr_exceed?: number
|
||||
p_xylene_cr_exceed?: number
|
||||
is_methanal_ps?: number
|
||||
is_tvoc_ps?: number
|
||||
is_benzene_ps?: number
|
||||
is_toluene_ps?: number
|
||||
is_p_xylene_ps?: number
|
||||
from?: FROM_TYPE
|
||||
user_id: string
|
||||
user_name: string
|
||||
creator_id?: string
|
||||
creator_name: string
|
||||
creator?: Creator
|
||||
related_user?: Creator
|
||||
related_admin?: Creator
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type AuditRequest = {
|
||||
id: string
|
||||
status: AUDIT_REQ_STATUS
|
||||
source_id?: string
|
||||
source_creator_id?: string
|
||||
source: AUDIT_REQ_SRC
|
||||
type: AUDIT_REQ_TYPE
|
||||
desc?: string
|
||||
remark?: string
|
||||
details: AuditRequestDetails[]
|
||||
auditor_id?: string
|
||||
auditor_name?: string
|
||||
auditor?: Creator
|
||||
audited_at?: string
|
||||
creator_id?: string
|
||||
creator_name: string
|
||||
creator?: Creator
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type AuditRequestDetails = {
|
||||
id: number
|
||||
audit_request_id: number
|
||||
content: AnyObj
|
||||
}
|
||||
|
||||
export enum ECO_LEVEL {
|
||||
E0 = 'E0',
|
||||
E1 = 'E1',
|
||||
E2 = 'E2'
|
||||
}
|
||||
|
||||
export enum MTRL_UNIT {
|
||||
AREA = 'AREA',
|
||||
LEN = 'LEN'
|
||||
}
|
||||
|
||||
export enum FROM_TYPE {
|
||||
CREATE = 'CREATE',
|
||||
IMPORT = 'IMPORT',
|
||||
COLLECT = 'COLLECT'
|
||||
}
|
||||
|
||||
export enum AUDIT_REQ_SRC {
|
||||
PUB_TEMP = 'PUB_TEMP',
|
||||
SELF_TEMP = 'SELF_TEMP',
|
||||
PUB_MTRL = 'PUB_MTRL',
|
||||
SELF_MTRL = 'SELF_MTRL',
|
||||
HIS_PROJ = 'HIS_PROJ'
|
||||
}
|
||||
|
||||
export enum AUDIT_REQ_TYPE {
|
||||
CREATE = 'CREATE',
|
||||
IMPORT = 'IMPORT',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
COLLECT = 'COLLECT',
|
||||
EXPORT = 'EXPORT'
|
||||
}
|
||||
|
||||
export enum AUDIT_REQ_STATUS {
|
||||
REJECT = -1,
|
||||
HOLD = 0,
|
||||
PASS = 1
|
||||
}
|
||||
|
||||
export type ProjectSpaceCategory = {
|
||||
pry_no_1: number
|
||||
project_category: string
|
||||
space_categories: {
|
||||
pry_no_2: number
|
||||
space_category: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export type MaterialCategory = {
|
||||
pry_no_1: number
|
||||
first_category: string
|
||||
second_categories: {
|
||||
pry_no_2: number
|
||||
second_category: string
|
||||
unit?: MTRL_UNIT
|
||||
}[]
|
||||
}
|
||||
|
||||
export type CityInformation = {
|
||||
pry_no_1: number
|
||||
province: string
|
||||
cities: {
|
||||
pry_no_2: number
|
||||
city: string
|
||||
climatic_zone?: string
|
||||
env_temp: number
|
||||
env_hum: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export type ApcLimitStandard = {
|
||||
id: string
|
||||
methanal_c_lmt: number
|
||||
tvoc_c_lmt: number
|
||||
benzene_c_lmt: number
|
||||
toluene_c_lmt: number
|
||||
p_xylene_c_lmt: number
|
||||
}
|
||||
|
|
@ -0,0 +1,546 @@
|
|||
import { DATA_TYPE } from '@/common/constants'
|
||||
import { AnyObj, ECO_LEVEL, Material } from '@/common/types'
|
||||
import { searchParams } from '@/common/utils'
|
||||
import api from '@/services/api'
|
||||
import {
|
||||
ActionType,
|
||||
LightFilter,
|
||||
ProFormCascader,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProTable
|
||||
} from '@ant-design/pro-components'
|
||||
import { Badge, Button, Form, Modal, Select, Switch, Tag } from 'antd'
|
||||
import { useRef, useState } from 'react'
|
||||
import OperateMaterial from '../apply-request-modal/operate-material'
|
||||
import ImportMaterialsModal from '../import-file-modal/materials'
|
||||
import MaterialModalForm from '../material-modal-form'
|
||||
import PageCard from '../page-card'
|
||||
|
||||
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 [openCreateModal, setOpenCreateModal] = useState(false)
|
||||
const [details, setDetails] = useState<Material>()
|
||||
const [selected, setSelected] = useState<Material[] | undefined>()
|
||||
const [content, setContent] = useState<AnyObj>()
|
||||
// 材料排序依据(默认综合 Ypt 升序)与是否按健康档分组显示
|
||||
const [sortKey, setSortKey] = useState<string>('ypt')
|
||||
const [groupByTier, setGroupByTier] = useState<boolean>(false)
|
||||
const page = (
|
||||
<PageCard
|
||||
title={title}
|
||||
defActTabKey={!modal ? DATA_TYPE.PUBLIC : undefined}
|
||||
tabItems={
|
||||
!modal
|
||||
? [
|
||||
{
|
||||
key: DATA_TYPE.PUBLIC,
|
||||
label: '公共库'
|
||||
},
|
||||
{
|
||||
key: DATA_TYPE.PRIVATE,
|
||||
label: '自建库'
|
||||
}
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
onTabChange={!modal ? (key) => setTab(key as DATA_TYPE) : undefined}
|
||||
extra={
|
||||
!modal && tab === DATA_TYPE.PUBLIC
|
||||
? [
|
||||
<ImportMaterialsModal
|
||||
key="import-modal"
|
||||
title="导入公共材料"
|
||||
trigger={<Button>导入材料</Button>}
|
||||
/>,
|
||||
<>
|
||||
<Button type="primary" onClick={() => setOpenCreateModal(true)}>
|
||||
新建材料
|
||||
</Button>
|
||||
<MaterialModalForm
|
||||
key="create-modal-form"
|
||||
title="新建公共材料"
|
||||
displayStatus
|
||||
displayDetails
|
||||
open={openCreateModal}
|
||||
onCancel={() => setOpenCreateModal(false)}
|
||||
onFinish={async (data) => setContent(data)}
|
||||
/>
|
||||
</>
|
||||
]
|
||||
: []
|
||||
}
|
||||
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="材料名称" />
|
||||
<ProFormCascader
|
||||
name="category"
|
||||
label="材料类别"
|
||||
fieldProps={{
|
||||
placeholder: '',
|
||||
multiple: true,
|
||||
showSearch: true
|
||||
}}
|
||||
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.map((v: string[]) => v.join('/')) })}
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
{tab === DATA_TYPE.PRIVATE ? (
|
||||
<ProFormText name="creator_name" label="创建用户" />
|
||||
) : (
|
||||
<ProFormSelect
|
||||
name="display"
|
||||
label="状态"
|
||||
options={[
|
||||
{ label: '展示', value: 1 },
|
||||
{ label: '隐藏', value: 0 }
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<ProFormSelect
|
||||
name="sort"
|
||||
label="排序"
|
||||
options={[
|
||||
{
|
||||
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: 'sort_order', label: '手动排序' },
|
||||
{ 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}
|
||||
scroll={modal ? { y: 'calc(100vh - 400px)' } : { y: 'calc(100vh - 360px)' }}
|
||||
rowSelection={
|
||||
modal
|
||||
? {
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: selected?.map((m) => m.material_id),
|
||||
// onSelect: (mtrl, slct) => {
|
||||
// setSelected((pre) => {
|
||||
// if (slct) {
|
||||
// const nxt = pre ? [...pre] : []
|
||||
// nxt.push(mtrl)
|
||||
// return nxt
|
||||
// } else {
|
||||
// return pre?.filter((m) => m.material_id !== mtrl.material_id)
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
onChange: (_, mtrls) => setSelected(mtrls),
|
||||
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]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})}
|
||||
rowKey={(data) => data.material_id}
|
||||
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: '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_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: 'display',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
hideInTable: modal || tab === DATA_TYPE.PRIVATE,
|
||||
render: (_, { display }) =>
|
||||
display === 1 ? (
|
||||
<Badge status="success" text="展示" />
|
||||
) : (
|
||||
<Badge status="error" text="隐藏" />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '创建用户',
|
||||
dataIndex: 'creator_name',
|
||||
align: 'center',
|
||||
hideInTable: modal || tab === DATA_TYPE.PUBLIC,
|
||||
renderText: (_, { creator }) => creator?.username
|
||||
},
|
||||
{
|
||||
title: '最近更新时间',
|
||||
dataIndex: 'updated_at',
|
||||
align: 'center',
|
||||
width: 160,
|
||||
renderText: (dt) => new Date(dt).toLocaleString('zh-CN')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: (_, item) => (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={(e) => {
|
||||
setDetails(item)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
{!modal && tab === DATA_TYPE.PUBLIC ? (
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={(e) => {
|
||||
setContent({ delete_id: item.material_id })
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
) : !modal && tab === DATA_TYPE.PRIVATE ? (
|
||||
<Button
|
||||
type="link"
|
||||
style={{ padding: 0 }}
|
||||
onClick={(e) => {
|
||||
setContent({ collect_id: item.material_id, creator_id: item.creator_id })
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
收录
|
||||
</Button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]}
|
||||
params={{ type: tab, conditions: filterConditions, sortKey, groupByTier }}
|
||||
request={async ({ type, conditions, sortKey: skParam, groupByTier: gbtParam, ...other }) => {
|
||||
try {
|
||||
// 旧筛选区「排序」下拉已被顶部「排序依据」Select 取代:
|
||||
// 从 conditions 里剔除 sort,不让它随 otherConditions 传给后端,
|
||||
// 也不再把它转成 searchParams() 的第三个参数(那会覆盖 query.sort_key/sort_val,
|
||||
// 导致新 Select 选的排序字段被静默忽略)。'category' 仍保留为纯前端排序。
|
||||
const { sort, ...otherConditions } = conditions ?? {}
|
||||
const sortByCategory = sort === 'category'
|
||||
const data = await api.material.search(
|
||||
type,
|
||||
searchParams(
|
||||
{
|
||||
...otherConditions,
|
||||
...other,
|
||||
display: modal ? 1 : undefined,
|
||||
with_cltrs: 1,
|
||||
// 顶部「排序依据」Select 始终是排序字段的唯一来源,不会被旧筛选区排序覆盖
|
||||
sort_key: skParam,
|
||||
sort_val: 'asc',
|
||||
group_by_tier: gbtParam ? 1 : 0
|
||||
},
|
||||
false
|
||||
)
|
||||
)
|
||||
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
|
||||
}
|
||||
}
|
||||
}}
|
||||
pagination={
|
||||
!modal
|
||||
? {
|
||||
position: ['bottomRight'],
|
||||
defaultPageSize: 10,
|
||||
showSizeChanger: true
|
||||
}
|
||||
: false
|
||||
}
|
||||
/>
|
||||
<MaterialModalForm
|
||||
title={`${tab === DATA_TYPE.PUBLIC ? '公共' : '自建'}材料详情`}
|
||||
initialValues={details}
|
||||
displayId
|
||||
displayCreator={!modal && tab === DATA_TYPE.PRIVATE}
|
||||
displayStatus={!modal && tab === DATA_TYPE.PUBLIC}
|
||||
displayDetails
|
||||
readonly={modal || tab === DATA_TYPE.PRIVATE}
|
||||
open={details !== undefined}
|
||||
submitText="保存"
|
||||
onFinish={tab === DATA_TYPE.PUBLIC ? async (data) => setContent(data) : undefined}
|
||||
onCancel={() => setDetails(undefined)}
|
||||
/>
|
||||
</PageCard>
|
||||
)
|
||||
return modal ? (
|
||||
<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={() => {
|
||||
setFilterConditions(undefined)
|
||||
filterForm.resetFields()
|
||||
setSelected(undefined)
|
||||
}}
|
||||
onCancel={modalProps?.onCancel}
|
||||
>
|
||||
{page}
|
||||
</Modal>
|
||||
) : (
|
||||
<>
|
||||
{page}
|
||||
<OperateMaterial
|
||||
type={tab}
|
||||
content={content}
|
||||
open={content !== undefined}
|
||||
onCancel={() => setContent(undefined)}
|
||||
onFinish={() => {
|
||||
setOpenCreateModal(false)
|
||||
setDetails(undefined)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialCard
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import { DATA_TYPE, HTTP_HEADER } from '@/common/constants'
|
||||
import { AnyObj, Arrayable, ECO_LEVEL, FROM_TYPE, MTRL_UNIT, Material } from '@/common/types'
|
||||
import { getLocalToken } from '@/common/utils'
|
||||
import { request } from '@umijs/max'
|
||||
|
||||
export async function createPub(
|
||||
body: {
|
||||
name?: string
|
||||
category?: string
|
||||
brand?: string
|
||||
factory?: string
|
||||
spec?: string
|
||||
eco_level?: ECO_LEVEL
|
||||
display?: number
|
||||
methanal_min_be?: number
|
||||
methanal_be_area?: number
|
||||
methanal_be_roc?: number
|
||||
tvoc_min_be?: number
|
||||
tvoc_be_area?: number
|
||||
tvoc_be_roc?: number
|
||||
benzene_min_be?: number
|
||||
benzene_be_area?: number
|
||||
benzene_be_roc?: number
|
||||
toluene_min_be?: number
|
||||
toluene_be_area?: number
|
||||
toluene_be_roc?: number
|
||||
p_xylene_min_be?: number
|
||||
p_xylene_be_area?: number
|
||||
p_xylene_be_roc?: number
|
||||
from?: FROM_TYPE
|
||||
},
|
||||
options?: AnyObj
|
||||
) {
|
||||
return request<Material>(`/api/mtrl/pub`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function collectSelf(
|
||||
body: {
|
||||
material_id: string
|
||||
creator_id: string
|
||||
display?: number
|
||||
},
|
||||
options?: AnyObj
|
||||
) {
|
||||
return request<Material>(`/api/mtrl/pub/self`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function patchPub(
|
||||
query: {
|
||||
material_id: string
|
||||
},
|
||||
body: {
|
||||
name?: string
|
||||
category?: string
|
||||
brand?: string
|
||||
factory?: string
|
||||
spec?: string
|
||||
eco_level?: ECO_LEVEL
|
||||
display?: number
|
||||
methanal_min_be?: number
|
||||
methanal_be_area?: number
|
||||
methanal_be_roc?: number
|
||||
tvoc_min_be?: number
|
||||
tvoc_be_area?: number
|
||||
tvoc_be_roc?: number
|
||||
benzene_min_be?: number
|
||||
benzene_be_area?: number
|
||||
benzene_be_roc?: number
|
||||
toluene_min_be?: number
|
||||
toluene_be_area?: number
|
||||
toluene_be_roc?: number
|
||||
p_xylene_min_be?: number
|
||||
p_xylene_be_area?: number
|
||||
p_xylene_be_roc?: number
|
||||
},
|
||||
options?: AnyObj
|
||||
) {
|
||||
return request<Material>(`/api/mtrl/pub`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: query,
|
||||
data: body,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function removePub(
|
||||
query: {
|
||||
material_id: Arrayable<string>
|
||||
},
|
||||
options?: AnyObj
|
||||
) {
|
||||
return request<{ count: number }>('/api/mtrl/pub', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: query,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
|
||||
export async function search(
|
||||
type: DATA_TYPE,
|
||||
query?: {
|
||||
material_id?: Arrayable<string>
|
||||
name?: Arrayable<string>
|
||||
category?: Arrayable<string>
|
||||
brand?: Arrayable<string>
|
||||
factory?: Arrayable<string>
|
||||
spec?: Arrayable<string>
|
||||
eco_level?: Arrayable<ECO_LEVEL>
|
||||
unit?: Arrayable<MTRL_UNIT>
|
||||
display?: number
|
||||
collected?: number
|
||||
creator_id?: Arrayable<string>
|
||||
creator_name?: Arrayable<string>
|
||||
with_cltrs?: number
|
||||
sort_key?: Arrayable<string>
|
||||
sort_val?: Arrayable<string>
|
||||
group_by_tier?: 0 | 1
|
||||
page?: number
|
||||
size?: number
|
||||
},
|
||||
options?: AnyObj
|
||||
) {
|
||||
if (type === DATA_TYPE.PRIVATE) {
|
||||
delete query?.display
|
||||
delete query?.collected
|
||||
delete query?.with_cltrs
|
||||
}
|
||||
return request<Material[]>(`/api/mtrl/${type}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
[HTTP_HEADER.AUTHORIZATION]: getLocalToken()
|
||||
},
|
||||
params: query,
|
||||
...(options || {})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue