v0.0.1-final
tangjie 2026-07-15 14:26:05 +08:00
parent 4cb916cf5c
commit 1d5d508292
13 changed files with 662 additions and 111 deletions

View File

@ -35,4 +35,10 @@ export const equipInfoRemove = declareRequest(
export const equipInfoToggleStatus = declareRequest(
"equipInfoLoading",
"Post > @/safetyEval/org-equipment/modify",
);
export const queryDeviceTypeOptions = declareRequest(
"equipInfoLoading",
"Get > /safetyEval/org-business-scope-device-ref",
"deviceTypeOptions: [] | res.data || []",
);

View File

@ -1,5 +1,5 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import { apiPostDelete, safeAction } from "../../utils/enterpriseInfo/http";
export const staffChangeLogStaffList = declareRequest(
"staffChangeLogLoading",
@ -13,10 +13,7 @@ export const staffChangeLogList = declareRequest(
"staffChangeLogChangeList: [] | res.data || [] & staffChangeLogChangeTotal: 0 | res.total || 0",
);
export const staffChangeLogRemove = declareRequest(
"staffChangeLogLoading",
safeAction(async ({ id }) => apiPostDelete("/safetyEval/org-personnel-change/delete", id)),
);
export const staffResignationAudit = declareRequest(
"staffChangeLogLoading",

View File

@ -158,6 +158,12 @@ export default function StaffViewModal({
<Descriptions.Item label="是否注册安全工程师">
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="是否技术负责人">
{info.technicalDirectorFlag === true ? "是" : info.technicalDirectorFlag === false ? "否" : "-"}
</Descriptions.Item>
<Descriptions.Item label="是否过程控制负责人">
{info.processControlLeaderFlag === true ? "是" : info.processControlLeaderFlag === false ? "否" : "-"}
</Descriptions.Item>
<Descriptions.Item label="身份证号">
{info.idCardNo}
</Descriptions.Item>

View File

@ -25,6 +25,7 @@ import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_EQUIP_INFO } from "~/enumerate/namespace";
import { positiveNumberRule } from "~/utils/validators";
import { toEquipPageQuery } from "../equipPageQuery";
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
const { router } = tools;
const DUAL_CHANNEL_MAP = { 1: "是", 2: "否" };
@ -84,7 +85,6 @@ function EquipInfoPage(props) {
message.success("操作成功");
handleSearch();
} else {
}
},
});
@ -119,29 +119,14 @@ function EquipInfoPage(props) {
maxLength={30}
/>
</Form.Item>,
<Form.Item key="instrumentType" name="instrumentType">
<Form.Item key="industryCode" name="industryCode">
<ControlWrapper.Select
label="仪器类型"
label="业务范围"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="分析仪器">分析仪器</Select.Option>
<Select.Option value="检测仪器">检测仪器</Select.Option>
<Select.Option value="采样设备">采样设备</Select.Option>
<Select.Option value="物理仪器">物理仪器</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="deviceType" name="deviceType">
<ControlWrapper.Select
label="设备类型"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="固定式">固定式</Select.Option>
<Select.Option value="便携式">便携式</Select.Option>
</ControlWrapper.Select>
options={QUALIFICATION_INDUSTRY_OPTIONS}
/>
</Form.Item>,
]}
onReset={(value) => {
@ -162,9 +147,16 @@ function EquipInfoPage(props) {
width: 200,
ellipsis: true,
},
{ title: "设备型号", dataIndex: "deviceModel", ellipsis: true, },
{ title: "仪器类型", dataIndex: "instrumentTypeName" },
{ title: "设备类型", dataIndex: "deviceTypeName" },
{ title: "设备型号", dataIndex: "deviceModel", ellipsis: true },
{
title: "业务范围",
dataIndex: "industryCode",
render: (v) =>
QUALIFICATION_INDUSTRY_OPTIONS.find((o) => o.value === v)
?.label ??
(v || "-"),
},
{ title: "设备类型", dataIndex: "deviceTypeCode" },
{
title: "厂家",
dataIndex: "manufacturer",
@ -231,7 +223,7 @@ function EquipInfoPage(props) {
},
]}
dataSource={dataSource}
scroll={{ y: props.scrollY, x: 1600 }}
scroll={{ y: props.scrollY, x: 1600 }}
loading={loading}
pagination={{
total,
@ -254,6 +246,7 @@ function EquipInfoPage(props) {
equipInfoGet={props.equipInfoGet}
equipInfoAdd={props.equipInfoAdd}
equipInfoEdit={props.equipInfoEdit}
queryDeviceTypeOptions={props.queryDeviceTypeOptions}
onCancel={() => {
setFormModalOpen(false);
setCurrentId("");
@ -282,12 +275,46 @@ function EquipFormModal({
equipInfoGet,
equipInfoAdd,
equipInfoEdit,
queryDeviceTypeOptions,
onCancel,
onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [deviceTypeOptions, setDeviceTypeOptions] = useState([]);
const [deviceTypeLoading, setDeviceTypeLoading] = useState(false);
const industryCode = Form.useWatch("industryCode", form);
useEffect(() => {
if (!industryCode) {
setDeviceTypeOptions([]);
form.resetFields(["deviceTypeCode"]);
return;
}
let cancelled = false;
setDeviceTypeLoading(true);
queryDeviceTypeOptions({ industryCode, pageSize: 999 })
.then((res) => {
if (cancelled) return;
const list = res?.data || [];
const unique = Array.from(
new Map(list.map((item) => [item.deviceCode, item])).values(),
);
setDeviceTypeOptions(
unique.map((item) => ({
label: item.deviceName,
value: item.deviceCode,
})),
);
})
.finally(() => {
if (!cancelled) setDeviceTypeLoading(false);
});
return () => {
cancelled = true;
};
}, [industryCode]);
useEffect(() => {
if (!open) return;
@ -328,7 +355,6 @@ function EquipFormModal({
handleCancel();
onSuccess();
} else {
}
} finally {
setSubmitting(false);
@ -368,28 +394,32 @@ function EquipFormModal({
</Col>
<Col span={12}>
<Form.Item
name="instrumentTypeName"
label="仪器分类"
rules={[{ required: true, message: "请选择仪器分类" }]}
name="industryCode"
label="业务范围"
rules={[{ required: true, message: "请选择业务范围" }]}
>
<Select placeholder="请选择仪器分类">
<Select.Option value="分析仪器">分析仪器</Select.Option>
<Select.Option value="检测仪器">检测仪器</Select.Option>
<Select.Option value="采样设备">采样设备</Select.Option>
<Select.Option value="物理仪器">物理仪器</Select.Option>
</Select>
<Select
placeholder="请选择业务范围"
options={QUALIFICATION_INDUSTRY_OPTIONS}
allowClear
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="deviceTypeName"
name="deviceTypeCode"
label="设备类型"
rules={[{ required: true, message: "请选择设备类型" }]}
>
<Select placeholder="请选择设备类型">
<Select.Option value="固定式">固定式</Select.Option>
<Select.Option value="便携式">便携式</Select.Option>
</Select>
<Select
placeholder="请先选择业务范围"
loading={deviceTypeLoading}
options={deviceTypeOptions}
disabled={!industryCode}
allowClear
showSearch
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
/>
</Form.Item>
</Col>
<Col span={12}>
@ -403,7 +433,11 @@ function EquipFormModal({
</Col>
<Col span={12}>
<Form.Item name="flowDesc" label="设备流量说明">
<Input placeholder="请输入设备流量说明" maxLength={100} showCount />
<Input
placeholder="请输入设备流量说明"
maxLength={100}
showCount
/>
</Form.Item>
</Col>
<Col span={12}>
@ -413,7 +447,6 @@ function EquipFormModal({
rules={[positiveNumberRule("最小流量", false)]}
>
<InputNumber
style={{ width: "100%" }}
placeholder="请输入最小流量"
/>
@ -426,7 +459,6 @@ function EquipFormModal({
rules={[positiveNumberRule("最大流量", false)]}
>
<InputNumber
style={{ width: "100%" }}
placeholder="请输入最大流量"
/>
@ -517,11 +549,14 @@ function EquipViewModal({ open, currentId, equipInfoGet, onCancel }) {
<Descriptions.Item label="设备型号">
{info.deviceModel}
</Descriptions.Item>
<Descriptions.Item label="仪器分类">
{info.instrumentTypeName}
<Descriptions.Item label="业务范围">
{QUALIFICATION_INDUSTRY_OPTIONS.find(
(o) => o.value === info.industryCode,
)?.label ??
(info.industryCode || "-")}
</Descriptions.Item>
<Descriptions.Item label="设备类型">
{info.deviceTypeName}
{info.deviceTypeCode || "-"}
</Descriptions.Item>
<Descriptions.Item label="厂家">{info.manufacturer}</Descriptions.Item>
<Descriptions.Item label="设备流量说明" span={2}>

View File

@ -710,6 +710,16 @@ function StaffFormModal({
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="technicalDirectorFlag" label="是否技术负责人">
<Select placeholder="请选择" allowClear options={[{ label: "是", value: true }, { label: "否", value: false }]} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="processControlLeaderFlag" label="是否过程控制负责人">
<Select placeholder="请选择" allowClear options={[{ label: "是", value: true }, { label: "否", value: false }]} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="publications"

View File

@ -106,11 +106,7 @@ const menuItems = [
label: "备案变更管理",
icon: <SwapOutlined />,
},
{
key: "/safetyEval/container/QualificationReview/QualMonitor",
label: "资质保持监控",
icon: <BarChartOutlined />,
},
],
},
]
@ -179,6 +175,23 @@ const menuItems = [
},
],
},
{
key: "/safetyEval/container/MonitorManage",
label: "监督管理",
icon: <SafetyCertificateOutlined />,
children: [
{
key: "/safetyEval/container/MonitorManage/QualMonitor",
label: "资质保持监控",
icon: <BarChartOutlined />,
},
{
key: "/safetyEval/container/MonitorManage/RiskCenter",
label: "风险预警中心",
icon: <ExperimentOutlined />,
},
],
},
{
key: "/safetyEval/container/test",
label: "测试",

View File

@ -308,7 +308,7 @@ const QualMonitor = (props) => {
{
label: "条件预警机构",
value: stats.warning,
hint: "人员比例不满足要求",
hint: "人员比例、属地材料待核验",
color: "#d97706",
iconBg: "#fef3c7",
iconColor: "#d97706",
@ -335,10 +335,8 @@ const QualMonitor = (props) => {
{/* 资质条件来源说明 */}
<div className="source-note">
<strong>资质条件来源</strong>
依据安全评价机构资质条件将机构备案信息拆为固定资产不少于 800
万元工作场所不少于 1000 平方米档案室不少于 100 平方米
专职技术人员数量及比例技术负责人/过程控制负责人内部制度法定代表人承诺公开网站三年重大违法失信记录注册地属地满足情况等字段
<strong>页面定位</strong>
资质保持监控是监管信息看板展示机构具备哪些资质资质条件是否保持每个结论来自哪些字段系统命中异常后自动生成风险消息发函整改机构反馈复核闭环等办理流程全部在风险预警中心完成
</div>
{/* 搜索表单 */}

View File

@ -0,0 +1,399 @@
import React, { useState } from "react";
import { Button, Form, Table, Tag } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import { tools } from "@cqsjjb/jjb-common-lib";
import "./index.less";
const { router } = tools;
/** 预警大类配置 */
const RISK_TYPE_MAP = {
qual: { label: "资质预警", color: "error" },
proj: { label: "项目预警", color: "warning" },
};
/** 处理阶段配置 */
const STAGE_MAP = {
untreated: { label: "未处理", color: "error" },
sent: { label: "已发机构", color: "warning" },
review: { label: "待复核", color: "processing" },
closed: { label: "已闭环", color: "success" },
};
/** 机构端状态配置 */
const ORG_STATUS_MAP = {
unsent: { label: "未发送", color: "error" },
unread: { label: "机构未读", color: "warning" },
processing: { label: "处理中", color: "processing" },
submitted: { label: "已提交材料", color: "processing" },
done: { label: "整改完成", color: "success" },
};
/** 预警子类选项 */
const RISK_SUBTYPE_OPTIONS = [
{ label: "资质条件保持异常", value: "qual_condition" },
{ label: "人员比例异常", value: "qual_person_rate" },
{ label: "属地材料待核验", value: "qual_area" },
{ label: "项目启动超期", value: "proj_start" },
{ label: "关键节点超期", value: "proj_node" },
{ label: "项目人员异常", value: "proj_person" },
{ label: "从业告知异常", value: "proj_notify" },
{ label: "现场勘查异常", value: "proj_site" },
{ label: "过程控制/归档异常", value: "proj_archive" },
];
/** 项目预警规则提示 */
const PROJ_RULES = [
"项目启动超期",
"关键节点超期",
"项目人员异常",
"从业告知异常",
"现场勘查异常",
"过程控制/归档异常",
];
/** mock 数据 */
const MOCK_DATA = [
{
id: "W-QUAL-2026-012",
type: "qual",
subtype: "qual_condition",
target: "重庆渝安风险评估中心",
sources: ["财务材料.固定资产", "场所核验.建筑面积", "人员库.专职人数"],
content: "固定资产、场所面积、档案室面积和专职人员数量不满足保持条件。",
stage: "untreated",
orgStatus: "unsent",
},
{
id: "W-QUAL-2026-011",
type: "qual",
subtype: "qual_person_rate",
target: "重庆恒安安全评价有限公司",
sources: ["人员库.职称等级", "人员库.注册安全工程师"],
content: "高级职称比例 26%,合计比例 59%,低于资质保持条件。",
stage: "sent",
orgStatus: "unread",
},
{
id: "W-QUAL-2026-013",
type: "qual",
subtype: "qual_area",
target: "北京中安评价中心重庆分公司",
sources: ["本市社保.缴纳人数", "场所核验.租赁合同"],
content: "机构已提交本地场所和人员说明,等待监管复核。",
stage: "review",
orgStatus: "submitted",
},
{
id: "W-PROJ-2026-021",
type: "proj",
subtype: "proj_start",
target: "华安化工年度安全现状评价",
sources: ["客户信息.评价到期日", "项目库.新建时间"],
content: "评价周期已到期 14 天,系统内尚未建立新一轮评价项目。",
stage: "untreated",
orgStatus: "unsent",
},
{
id: "W-PROJ-2026-018",
type: "proj",
subtype: "proj_node",
target: "安环检测年度安全现状评价",
sources: ["项目计划.节点期限", "流程记录.当前节点"],
content: "项目停留在过程控制环节 12 天,已超过计划完成时间。",
stage: "sent",
orgStatus: "processing",
},
{
id: "W-PROJ-2026-015",
type: "proj",
subtype: "proj_person",
target: "鼎信建设安全验收评价",
sources: ["项目组.项目负责人", "人员库.在职状态"],
content: "项目负责人已提交离职申请,项目尚未完成负责人变更。",
stage: "review",
orgStatus: "submitted",
},
{
id: "W-PROJ-2026-009",
type: "proj",
subtype: "proj_site",
target: "华安化工园区安全预评价",
sources: ["现场勘查.签到签退", "项目组.参与人员"],
content: "现场勘查仅有签到记录,无签退记录和现场照片。",
stage: "closed",
orgStatus: "done",
},
];
const RiskCenter = () => {
const [form] = Form.useForm();
const [data] = useState(MOCK_DATA);
/** 统计卡片数据 */
const stats = {
qualRisk: data.filter((i) => i.type === "qual").length,
projRisk: data.filter((i) => i.type === "proj").length,
pendingFeedback: data.filter((i) => i.orgStatus === "submitted").length,
todayClosed: data.filter((i) => i.stage === "closed").length,
};
const handleSearch = () => {
// 接口搜索
};
const handleReset = () => {
form.resetFields();
};
const columns = [
{
title: "预警编号",
dataIndex: "id",
width: 160,
},
{
title: "预警大类",
dataIndex: "type",
width: 100,
render: (type) => {
const cfg = RISK_TYPE_MAP[type];
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
},
},
{
title: "预警子类",
dataIndex: "subtype",
width: 140,
render: (subtype) => {
const opt = RISK_SUBTYPE_OPTIONS.find((o) => o.value === subtype);
return opt?.label || "--";
},
},
{
title: "风险对象",
dataIndex: "target",
width: 200,
},
{
title: "来源字段",
dataIndex: "sources",
width: 280,
render: (sources) => (
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
{(sources || []).map((s, i) => (
<span key={i} className="source-pill">
{s}
</span>
))}
</div>
),
},
{
title: "触发内容",
dataIndex: "content",
width: 320,
},
{
title: "处理阶段",
dataIndex: "stage",
width: 100,
render: (stage) => {
const cfg = STAGE_MAP[stage];
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
},
},
{
title: "机构端状态",
dataIndex: "orgStatus",
width: 110,
render: (status) => {
const cfg = ORG_STATUS_MAP[status];
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : "--";
},
},
{
title: "监管操作",
width: 100,
fixed: "right",
render: () => (
<Button type="primary" size="small">
处理
</Button>
),
},
];
return (
<PageLayout title="风险预警中心">
<div className="risk-center">
{/* 统计卡片 */}
<div className="stat-grid cols-4">
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">资质预警</div>
<div className="stat-value stat-value--high">
{stats.qualRisk}
</div>
<div className="stat-hint">
条件保持人员比例属地材料
</div>
</div>
<div
className="stat-icon"
style={{ background: "#fee2e2", color: "#dc2626" }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">项目预警</div>
<div className="stat-value stat-value--mid">
{stats.projRisk}
</div>
<div className="stat-hint">
进度人员告知勘查质控
</div>
</div>
<div
className="stat-icon"
style={{ background: "#fef3c7", color: "#d97706" }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">待机构反馈</div>
<div className="stat-value stat-value--mid">
{stats.pendingFeedback}
</div>
<div className="stat-hint">整改说明或证明材料</div>
</div>
<div
className="stat-icon"
style={{ background: "#dbeafe", color: "#2563eb" }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">今日闭环</div>
<div className="stat-value stat-value--ok">
{stats.todayClosed}
</div>
<div className="stat-hint">监管复核已完成</div>
</div>
<div
className="stat-icon"
style={{ background: "#d1fae5", color: "#059669" }}
>
</div>
</div>
</div>
{/* 项目预警规则提示 */}
<div className="source-note">
<strong>项目预警规则</strong>
{PROJ_RULES.map((rule) => (
<span key={rule} className="source-pill">
{rule}
</span>
))}
</div>
{/* 搜索表单 */}
<SearchForm
style={{ marginBottom: 12 }}
form={form}
formLine={[
<Form.Item key="keyword" name="keyword">
<ControlWrapper.Input
label="关键词"
placeholder="机构/项目/人员/预警编号"
allowClear
/>
</Form.Item>,
<Form.Item key="riskType" name="riskType">
<ControlWrapper.Select
label="预警大类"
placeholder="全部"
allowClear
style={{ width: "100%" }}
options={[
{ label: "资质预警", value: "qual" },
{ label: "项目预警", value: "proj" },
]}
/>
</Form.Item>,
<Form.Item key="subtype" name="subtype">
<ControlWrapper.Select
label="预警子类"
placeholder="全部"
allowClear
style={{ width: "100%" }}
options={RISK_SUBTYPE_OPTIONS}
/>
</Form.Item>,
<Form.Item key="stage" name="stage">
<ControlWrapper.Select
label="处理阶段"
placeholder="全部"
allowClear
style={{ width: "100%" }}
options={[
{ label: "未处理", value: "untreated" },
{ label: "已发机构", value: "sent" },
{ label: "待复核", value: "review" },
{ label: "已闭环", value: "closed" },
]}
/>
</Form.Item>,
]}
onReset={handleReset}
onFinish={handleSearch}
/>
{/* 工具条 */}
<div className="risk-toolbar">
<div className="risk-toolbar-left">
<span className="risk-toolbar-desc">
资质预警沿用资质保持监控结果项目预警由项目计划人员从业告知现场记录和审核归档数据触发
</span>
</div>
<div className="risk-toolbar-right">
<Button className="btn-ghost" size="small">
导出预警
</Button>
</div>
</div>
{/* 数据表格 */}
<Table
rowKey="id"
columns={columns}
dataSource={data}
scroll={{ x: 1600 }}
pagination={{
total: data.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current || 1,
pageSize: router.query.size || 10,
onChange: (page, size) => {
router.query = { ...router.query, current: page, size };
},
}}
/>
</div>
</PageLayout>
);
};
export default RiskCenter;

View File

@ -0,0 +1,125 @@
.risk-center {
// 统计卡片网格
.stat-grid.cols-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 10px;
}
// 统计卡片
.stat-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
display: flex;
justify-content: space-between;
.stat-info {
.stat-label {
font-size: 0.82rem;
color: #64748b;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
line-height: 1.15;
margin-top: 6px;
}
.stat-value--high {
color: #dc2626;
}
.stat-value--mid {
color: #d97706;
}
.stat-value--ok {
color: #059669;
}
.stat-hint {
font-size: 0.75rem;
color: #64748b;
margin-top: 4px;
}
}
.stat-icon {
width: 38px;
height: 38px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
flex-shrink: 0;
}
}
// 来源说明/规则提示
.source-note {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 8px 14px;
font-size: 0.82rem;
color: #64748b;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
strong {
color: #1e293b;
}
}
// 来源字段药丸
.source-pill {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 2px 7px;
border-radius: 999px;
background: #f1f5f9;
color: #475569;
font-size: 0.68rem;
white-space: nowrap;
}
// 工具条
.risk-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.risk-toolbar-left {
.risk-toolbar-desc {
font-size: 0.85rem;
color: #64748b;
}
}
.risk-toolbar-right {
display: flex;
gap: 8px;
}
}
.btn-ghost {
background: transparent;
color: #64748b;
border: 1px solid #e2e8f0;
&:hover {
background: #f1f5f9;
color: #1e293b;
}
}
}

View File

@ -0,0 +1,12 @@
import React from "react";
function MonitorManage(props) {
return (
props.children
);
}
export default MonitorManage;

View File

@ -3,46 +3,7 @@
import { asId, isIdFieldName, normalizeQueryIds } from "./idUtil";
import { withOrgId } from "./orgContext";
function parseAttachmentUrls(urls, defaultName = "附件.pdf") {
if (!urls) {
return [];
}
const raw = String(urls).trim();
// JSON 数组字符串:如 '[{"url":"...","uid":"...","name":"...","status":"done"}]'
if (raw.startsWith("[") && raw.endsWith("]")) {
try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
return parsed
.filter((item) => item?.url)
.map((item) => ({
name: item.name || `${defaultName.replace(".pdf", "")}1`,
fileName:
item.fileName ||
item.name ||
`${defaultName.replace(".pdf", "")}1`,
url: item.url,
uid: item.uid || undefined,
status: item.status || "done",
}));
}
} catch (_) {
// JSON 解析失败,回退到逗号分隔处理
}
}
// 旧格式:逗号分隔的 URL 字符串
return raw
.split(",")
.map((url) => url.trim())
.filter(Boolean)
.map((url, index) => ({
name: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
fileName: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
url,
}));
}
/** 分页查询参数:子表 org_id → org_info.id机构信息管理 getInfo 不走此方法 */
export function toPageQuery(params = {}, fieldMap = {}) {

View File

@ -64,18 +64,7 @@ export async function apiPostDelete(path, id, options = {}) {
}
}
export function safePageResult(mapper) {
return async (params) => {
try {
const res = await mapper(params);
return res;
}
catch (err) {
console.warn("[enterpriseInfo/safePageResult]", err);
return { success: false, data: [], totalCount: 0 };
}
};
}
export function safeAction(mapper) {
return async (params) => {