1、消防救援队 消防控制室 消防泵房 消防水源

2、监管端统计页面
master
shenzhidan 2026-02-06 10:44:56 +08:00
parent a34a7cceb1
commit 18d8e7108e
30 changed files with 3864 additions and 7 deletions

View File

@ -9,17 +9,18 @@ module.exports = {
// 应用后端分支名称,部署上线需要
javaGitBranch: "<branch-name>",
// 接口服务地址
API_HOST: "开发环境后端地址",
API_HOST: "http://192.168.20.100:30140",
// API_HOST: "http://127.0.0.1",
},
production: {
// 应用后端分支名称,部署上线需要
javaGitBranch: "<branch-name>",
// 接口服务地址
API_HOST: "",
API_HOST: "https://gbs-gateway.qhdsafety.com",
},
},
// 应用唯一标识符
appIdentifier: "唯一应用标识与后端gateway相同",
appIdentifier: "fireResource",
// 应用上下文注入全局变量
contextInject: {
// 应用Key

View File

@ -6,8 +6,8 @@
"license": "MIT",
"main": "index.js",
"scripts": {
"serve": "node node_modules/@cqsjjb/scripts/webpack.dev.server.js",
"build": "node node_modules/@cqsjjb/scripts/webpack.build.js",
"serve": "node node_modules/@cqsjjb/scripts/rspack.dev.server.js",
"build": "node node_modules/@cqsjjb/scripts/rspack.build.js",
"push": "jjb-cmd push java production",
"clean-cache": "rimraf node_modules/.cache/webpack",
"serve:development": "cross-env NODE_ENV=development npm run serve",
@ -30,12 +30,12 @@
"lodash-es": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zy-react-library": "latest"
"zy-react-library": "^1.1.41"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@babel/plugin-proposal-decorators": "^7.19.3",
"@cqsjjb/scripts": "latest",
"@cqsjjb/scripts": "2.0.0-rspack.1",
"@eslint-react/eslint-plugin": "^2.2.2",
"cross-env": "^7.0.3",
"eslint": "^9.37.0",

View File

@ -0,0 +1,26 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const controlRoomList = declareRequest(
"controlRoomLoading",
`Post >@/fireResource/controlRoom/list`,
);
export const controlRoomDetail = declareRequest(
"controlRoomLoading",
`Get >/fireResource/controlRoom/{id}`,
);
export const controlRoomAdd = declareRequest(
"controlRoomLoading",
`Post >@/fireResource/controlRoom/save`,
);
export const controlRoomUpdate = declareRequest(
"controlRoomLoading",
`Put >@/fireResource/controlRoom/edit`,
);
export const controlRoomDelete = declareRequest(
"controlRoomLoading",
`Delete >@/fireResource/controlRoom/{id}`,
);

View File

@ -0,0 +1,6 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const fireResourceStatsList = declareRequest(
"fireResourceStatsLoading",
`Post >@/fireResource/resource/list`,
);

26
src/api/pumpRoom/index.js Normal file
View File

@ -0,0 +1,26 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const pumpRoomList = declareRequest(
"pumpRoomLoading",
`Post >@/fireResource/pumpRoom/list`,
);
export const pumpRoomDetail = declareRequest(
"pumpRoomLoading",
`Get >/fireResource/pumpRoom/{id}`,
);
export const pumpRoomAdd = declareRequest(
"pumpRoomLoading",
`Post >@/fireResource/pumpRoom/save`,
);
export const pumpRoomUpdate = declareRequest(
"pumpRoomLoading",
`Put >@/fireResource/pumpRoom/edit`,
);
export const pumpRoomDelete = declareRequest(
"pumpRoomLoading",
`Delete >@/fireResource/pumpRoom/{id}`,
);

View File

@ -0,0 +1,26 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const rescueTeamList = declareRequest(
"rescueTeamLoading",
`Post > @/fireResource/rescueTeam/list`,
);
export const rescueTeamDetail = declareRequest(
"rescueTeamLoading",
`Get > /fireResource/rescueTeam/{id}`,
);
export const rescueTeamAdd = declareRequest(
"rescueTeamLoading",
`Post >@/fireResource/rescueTeam/save`,
);
export const rescueTeamUpdate = declareRequest(
"rescueTeamLoading",
`Put >@/fireResource/rescueTeam/edit`,
);
export const rescueTeamDelete = declareRequest(
"rescueTeamLoading",
`Delete >@/fireResource/rescueTeam/{id}`,
);

View File

@ -0,0 +1,26 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const waterSourceList = declareRequest(
"waterSourceLoading",
`Post >@/fireResource/waterSource/list`,
);
export const waterSourceDetail = declareRequest(
"waterSourceLoading",
`Get >/fireResource/waterSource/{id}`,
);
export const waterSourceAdd = declareRequest(
"waterSourceLoading",
`Post >@/fireResource/waterSource/save`,
);
export const waterSourceUpdate = declareRequest(
"waterSourceLoading",
`Put >@/fireResource/waterSource/edit`,
);
export const waterSourceDelete = declareRequest(
"waterSourceLoading",
`Delete >@/fireResource/waterSource/{id}`,
);

View File

@ -5,3 +5,8 @@
import { defineNamespace } from "@cqsjjb/jjb-dva-runtime";
export const NS_GLOBAL = defineNamespace("global");
export const NS_CONTROL_ROOM = defineNamespace("controlRoom");
export const NS_RESCUE_TEAM = defineNamespace("rescueTeam");
export const NS_PUMP_ROOM = defineNamespace("pumpRoom");
export const NS_WATER_SOURCE = defineNamespace("waterSource");
export const NS_FIRE_RESOURCE_STATS = defineNamespace("fireResourceStats");

View File

@ -0,0 +1,534 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Col, Form, Input, InputNumber, message, Row, Space, Table } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Upload from "zy-react-library/components/Upload";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useUploadFile from "zy-react-library/hooks/useUploadFile";
import { NS_CONTROL_ROOM } from "~/enumerate/namespace";
function Add(props) {
const query = useGetUrlQuery();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
// 设备信息列表
const [deviceList, setDeviceList] = useState([]);
// 人员信息列表
const [personnelList, setPersonnelList] = useState([]);
// 文件上传相关hooks
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
const { loading: getFileLoading, getFile } = useGetFile();
// 保存要删除的文件列表
const [deleteFiles, setDeleteFiles] = useState([]);
const getData = async () => {
setLoading(true);
try {
const { data } = await props["controlRoomDetail"]({ id: query.id });
// ⭐ 如果有业务ID查询图片文件
if (data.roomId) {
const files = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[302], // 消防点检检查合格图片
eqForeignKey: data.roomId,
});
data.roomImages = files;
}
form.setFieldsValue(data);
// 设置设备列表
if (data.devices && data.devices.length > 0) {
setDeviceList(data.devices.map((item, index) => ({ ...item, key: item.id || index })));
}
// 设置人员列表
if (data.persons && data.persons.length > 0) {
setPersonnelList(data.persons.map((item, index) => ({ ...item, key: item.id || index })));
}
}
catch (error) {
console.error("加载详情失败:", error);
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
const onFinish = async (values) => {
// 验证设备列表
if (deviceList.length === 0) {
message.warning("请至少添加一条设备信息");
return;
}
// 验证人员列表
if (personnelList.length === 0) {
message.warning("请至少添加一条人员信息");
return;
}
// 验证设备列表是否填写完整
const invalidDevice = deviceList.find(
item => !item.deviceName || !item.deviceModel || !item.deviceQty || !item.deviceLocation,
);
if (invalidDevice) {
message.warning("请完善设备信息");
return;
}
// 验证人员列表是否填写完整
const invalidPersonnel = personnelList.find(
item => !item.personName || !item.personPhone,
);
if (invalidPersonnel) {
message.warning("请完善人员信息");
return;
}
// 验证图片是否上传
if (!values.roomImages || values.roomImages.length === 0) {
message.warning("请上传消防控制室图片");
return;
}
setSubmitting(true);
try {
// ⭐ 先删除标记删除的文件
if (deleteFiles.length > 0) {
await deleteFile({ single: false, files: deleteFiles });
}
// ⭐ 上传图片文件获取业务IDroom_id
const { id: roomId } = await uploadFile({
single: false,
files: values.roomImages,
params: {
type: UPLOAD_FILE_TYPE_ENUM[302], // ⭐ 消防点检检查合格图片
foreignKey: values.roomId, // 编辑时传入已有的 roomId
},
});
const apiMethod = query.id ? props["controlRoomUpdate"] : props["controlRoomAdd"];
// 构建提交参数
const params = {
...values,
roomId, // ⭐ 使用上传返回的业务ID
// 设备列表:去掉前端添加的 key
devices: deviceList.map(({ key, ...device }) => ({
deviceName: device.deviceName,
deviceModel: device.deviceModel,
deviceQty: device.deviceQty,
deviceLocation: device.deviceLocation,
...(device.id && { id: device.id }),
})),
// 人员列表:去掉前端添加的 key
persons: personnelList.map(({ key, ...personnel }) => ({
personName: personnel.personName,
personPhone: personnel.personPhone,
dutyDesc: personnel.dutyDesc || "",
...(personnel.id && { id: personnel.id }),
})),
};
if (query.id) {
params.id = query.id;
}
await apiMethod(params);
message.success(query.id ? "修改成功" : "新增成功");
props.history.goBack();
}
catch (error) {
console.error("提交失败:", error);
message.error(query.id ? "修改失败" : "新增失败");
}
finally {
setSubmitting(false);
}
};
// 设备列表相关操作
const addDevice = () => {
const newDevice = {
key: Date.now(),
deviceName: "",
deviceModel: "",
deviceQty: null,
deviceLocation: "",
};
setDeviceList([...deviceList, newDevice]);
};
const removeDevice = (key) => {
setDeviceList(deviceList.filter(item => item.key !== key));
};
const updateDevice = (key, field, value) => {
setDeviceList(
deviceList.map(item => (item.key === key ? { ...item, [field]: value } : item)),
);
};
// 人员列表相关操作
const addPersonnel = () => {
const newPersonnel = {
key: Date.now(),
personName: "",
personPhone: "",
dutyDesc: "",
};
setPersonnelList([...personnelList, newPersonnel]);
};
const removePersonnel = (key) => {
setPersonnelList(personnelList.filter(item => item.key !== key));
};
const updatePersonnel = (key, field, value) => {
setPersonnelList(
personnelList.map(item => (item.key === key ? { ...item, [field]: value } : item)),
);
};
// 设备表格列定义
const deviceColumns = [
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备名称
</>),
dataIndex: "deviceName",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备名称"
onChange={e => updateDevice(record.key, "deviceName", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备型号
</>),
dataIndex: "deviceModel",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备型号"
onChange={e => updateDevice(record.key, "deviceModel", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备数量
</>),
dataIndex: "deviceQty",
render: (text, record) => (
<InputNumber
value={text}
placeholder="请输入设备数量"
min={1}
style={{ width: "100%" }}
onChange={value => updateDevice(record.key, "deviceQty", value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备位置
</>),
dataIndex: "deviceLocation",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备位置"
onChange={e => updateDevice(record.key, "deviceLocation", e.target.value)}
/>
),
},
{
title: "操作",
width: 80,
align: "center",
render: (_, record) => (
<Button type="link" danger onClick={() => removeDevice(record.key)}>
删除
</Button>
),
},
];
// 人员表格列定义
const personnelColumns = [
{
title: (<>
<span style={{ color: "red" }}>*</span>
人员姓名
</>),
dataIndex: "personName",
render: (text, record) => (
<Input
value={text}
placeholder="请输入人员姓名"
onChange={e => updatePersonnel(record.key, "personName", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
人员手机号
</>),
dataIndex: "personPhone",
render: (text, record) => (
<Input
value={text}
placeholder="请输入人员手机号"
maxLength={11}
onChange={e => updatePersonnel(record.key, "personPhone", e.target.value)}
/>
),
},
{
title: "人员值班情况",
dataIndex: "dutyDesc",
render: (text, record) => (
<Input
value={text}
placeholder="请输入人员值班情况"
onChange={e => updatePersonnel(record.key, "dutyDesc", e.target.value)}
/>
),
},
{
title: "操作",
width: 80,
align: "center",
render: (_, record) => (
<Button type="link" danger onClick={() => removePersonnel(record.key)}>
删除
</Button>
),
},
];
return (
<Page
headerTitle={query.id ? "编辑消防控制室" : "新增消防控制室"}
loading={loading || uploadFileLoading || deleteFileLoading || getFileLoading}
isShowFooter={false}
>
<Form
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={onFinish}
style={{ maxWidth: 1200, marginTop: 24 }}
>
<Card title="基本信息" style={{ marginBottom: 24 }}>
{/* 添加隐藏字段保存 roomId */}
<Form.Item name="roomId" hidden>
<Input />
</Form.Item>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="消防控制室名称"
name="roomName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入消防控制室名称" },
{ max: 100, message: "消防控制室名称不能超过100个字符" },
]}
>
<Input placeholder="请输入消防控制室名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="消防控制室状态"
name="roomStatus"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请选择消防控制室状态" }]}
>
<DictionarySelect
dictValue="fire_resource_contro_root_type"
placeholder="请选择"
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="负责人"
name="principalName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入负责人" },
{ max: 50, message: "负责人不能超过50个字符" },
]}
>
<Input placeholder="请输入负责人" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="负责人手机号"
name="principalPhone"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入负责人手机号" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" },
]}
>
<Input placeholder="请输入负责人手机号" maxLength={11} />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="经度"
name="lng"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入经度" }]}
>
<InputNumber
placeholder="请输入经度"
style={{ width: "100%" }}
min={-180}
max={180}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="纬度"
name="lat"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入纬度" }]}
>
<InputNumber
placeholder="请输入纬度"
style={{ width: "100%" }}
min={-90}
max={90}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<Form.Item
label="消防控制室图片"
name="roomImages"
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
rules={[{ required: true, message: "请上传消防控制室图片" }]}
extra="默认上传4个目前支持jpg、jpeg、png格式单张图片不超过5mb"
>
<Upload
fileType="image"
maxCount={4}
maxSize={5}
accept=".jpg,.jpeg,.png"
onGetRemoveFile={(file) => {
setDeleteFiles([...deleteFiles, file]);
}}
/>
</Form.Item>
</Col>
</Row>
</Card>
<Card
title="设备信息"
extra={(
<Button type="primary" onClick={addDevice}>
新增
</Button>
)}
style={{ marginBottom: 24 }}
>
<Table
columns={deviceColumns}
dataSource={deviceList}
pagination={false}
rowKey="key"
locale={{ emptyText: "暂无设备信息,请点击新增按钮添加" }}
/>
</Card>
<Card
title="人员信息"
extra={(
<Button type="primary" onClick={addPersonnel}>
新增
</Button>
)}
style={{ marginBottom: 24 }}
>
<Table
columns={personnelColumns}
dataSource={personnelList}
pagination={false}
rowKey="key"
locale={{ emptyText: "暂无人员信息,请点击新增按钮添加" }}
/>
</Card>
<Form.Item wrapperCol={{ span: 24 }} style={{ textAlign: "center" }}>
<Space size={16}>
<Button onClick={() => props.history.goBack()}>
取消
</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
提交
</Button>
</Space>
</Form.Item>
</Form>
</Page>
);
}
export default Connect([NS_CONTROL_ROOM], true)(Permission(Add));

View File

@ -0,0 +1,184 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Table from "zy-react-library/components/Table";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import { NS_CONTROL_ROOM } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const query = useGetUrlQuery();
const { tableProps, getData } = useTable(props["controlRoomList"], { form });
const [deleting, setDeleting] = useState(false);
const [statusDict, setStatusDict] = useState([]);
const { getDictionary } = useDictionary();
useEffect(() => {
if (props.location?.state?.refresh) {
getData();
props.history.replace({ ...props.location, state: {} });
}
}, [props.location?.state?.refresh]);
useEffect(() => {
if (query.eqCorpId) {
form.setFieldsValue({ eqCorpId: query.eqCorpId });
getData();
}
}, [query.eqCorpId]);
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_contro_root_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
const getStatusLabel = (record) => {
if (record.roomStatusName)
return record.roomStatusName;
const match = statusDict.find(item => item.dictValue === record.roomStatus);
return match?.dictLabel || record.roomStatus || "-";
};
const onDelete = (id) => {
Modal.confirm({
title: "确认删除",
content: "删除后不可恢复,确认继续?",
okText: "确认",
cancelText: "取消",
okButtonProps: { danger: true, loading: deleting },
onOk: async () => {
if (!props["controlRoomDelete"]) {
message.warning("未接入 controlRoomDelete effect");
return;
}
setDeleting(true);
try {
await props["controlRoomDelete"]({ id: String(id) });
message.success("已删除");
getData();
}
finally {
setDeleting(false);
}
},
});
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{
name: "likeRoomName",
label: "消防控制室名称:",
span: 4,
formItemProps: { style: { marginRight: 16 } },
},
{
name: "eqRoomStatus",
label: "消防控制室状态:",
render: (<DictionarySelect dictValue="fire_resource_contro_root_type" />),
span: 4,
formItemProps: { style: { marginLeft: 16 } },
},
{ name: "eqCorpId", onlyForLabel: true },
]}
form={form}
onFinish={getData}
/>
<Table
toolBarRender={() => (
<Space>
{/* {props.permission("xfkzs-btn-add") && ( */}
<Button
type="primary"
onClick={() => {
props.history.push("./add");
}}
>
新增
</Button>
{/* )} */}
</Space>
)}
columns={[
// { title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "消防控制室名称", dataIndex: "roomName", ellipsis: true, width: 140 },
{
title: "消防控制室状态",
width: 140,
align: "center",
render: (_, record) => getStatusLabel(record),
},
{ title: "负责人", dataIndex: "principalName", width: 120, align: "center" },
{ title: "负责人手机号", dataIndex: "principalPhone", width: 140, align: "center" },
{ title: "设备数", dataIndex: "deviceCount", width: 100, align: "center" },
{ title: "人员数", dataIndex: "personCount", width: 100, align: "center" },
{
title: "操作",
fixed: "right",
width: 180,
render: (_, record) => (
<Space>
{/* {props.permission("xfkzs-btn-view") && ( */}
<Button
type="link"
onClick={() => {
props.history.push(`./view?id=${record.id}`);
}}
>
查看
</Button>
{/* )} */}
{/* {props.permission("xfkzs-btn-edit") && ( */}
<Button
type="link"
onClick={() => {
props.history.push(`./add?id=${record.id}`);
}}
>
编辑
</Button>
{/* )} */}
{/* {props.permission("xfkzs-btn-delete") && ( */}
<Button
type="link"
danger
onClick={() => {
onDelete(record.id);
}}
>
删除
</Button>
{/* )} */}
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_CONTROL_ROOM], true)(Permission(List));

View File

@ -0,0 +1,247 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Descriptions, Image, message, Space, Table } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_CONTROL_ROOM } from "~/enumerate/namespace";
function View(props) {
const query = useGetUrlQuery();
const [loading, setLoading] = useState(false);
const [detail, setDetail] = useState(null);
const [statusDict, setStatusDict] = useState([]);
const [statusLabel, setStatusLabel] = useState("-");
const { loading: dictLoading, getDictionary } = useDictionary();
const { loading: getFileLoading, getFile } = useGetFile();
const getData = async () => {
setLoading(true);
try {
const { data } = await props["controlRoomDetail"]({ id: query.id });
let nextDetail = data;
if (data.roomId) {
const files = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[302],
eqForeignKey: data.roomId,
});
nextDetail = { ...data, roomImages: files };
}
setDetail(nextDetail);
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, [query.id]);
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_contro_root_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
useEffect(() => {
if (!detail)
return;
const match = statusDict.find(item => item.dictValue === detail.roomStatus);
setStatusLabel(match?.dictLabel || detail.roomStatusName || detail.roomStatus || "-");
}, [detail, statusDict]);
const longitude = detail?.lng ?? detail?.longitude;
const latitude = detail?.lat ?? detail?.latitude;
const lngLatText = longitude != null && latitude != null
? `${longitude}, ${latitude}`
: (longitude ?? latitude ?? "-");
const deviceColumns = [
{
title: "序号",
width: 80,
align: "center",
render: (_, __, index) => index + 1,
},
{
title: "设备名称",
dataIndex: "deviceName",
width: 180,
align: "center",
ellipsis: true,
},
{
title: "设备型号",
dataIndex: "deviceModel",
width: 180,
align: "center",
ellipsis: true,
},
{
title: "设备数量",
dataIndex: "deviceQty",
align: "center",
width: 120,
},
{
title: "设备位置",
dataIndex: "deviceLocation",
align: "center",
ellipsis: true,
width: 240,
},
];
const personnelColumns = [
{
title: "序号",
width: 80,
align: "center",
render: (_, __, index) => index + 1,
},
{
title: "人员姓名",
dataIndex: "personName",
width: 160,
align: "center",
ellipsis: true,
},
{
title: "人员手机号",
dataIndex: "personPhone",
width: 180,
align: "center",
ellipsis: true,
},
{
title: "人员值班情况",
render: (_, record) => record.dutyDesc || "-",
align: "center",
ellipsis: true,
width: 240,
},
];
return (
<Page
headerTitle="查看消防控制室"
loading={loading || dictLoading || getFileLoading}
isShowFooter={false}
>
{detail && (
<>
<Card title="基本信息" style={{ marginBottom: 24, marginTop: 24 }}>
<Descriptions
bordered
column={2}
labelStyle={{ width: 180, backgroundColor: "#fafafa" }}
contentStyle={{ backgroundColor: "#fff" }}
items={[
{
label: "消防控制室名称",
children: detail.roomName || "-",
},
{
label: "消防控制室状态",
children: statusLabel,
},
{
label: "负责人",
children: detail.principalName || "-",
},
{
label: "负责人手机号",
children: detail.principalPhone || "-",
},
{
label: "经纬度",
children: lngLatText,
span: 2,
},
{
label: "消防控制室图片",
children: detail.roomImages && detail.roomImages.length > 0
? (
<Image.PreviewGroup>
<Space>
{detail.roomImages.map((img, index) => (
<Image
key={index}
width={100}
height={100}
src={img}
style={{ objectFit: "cover" }}
/>
))}
</Space>
</Image.PreviewGroup>
)
: (
"无"
),
span: 2,
},
]}
/>
</Card>
<Card title="设备信息" style={{ marginBottom: 24 }}>
<Table
columns={deviceColumns}
dataSource={detail.devices || []}
pagination={false}
rowKey={(record, index) => index}
locale={{ emptyText: "暂无设备信息" }}
bordered
tableLayout="fixed"
/>
</Card>
<Card title="人员信息" style={{ marginBottom: 24 }}>
<Table
columns={personnelColumns}
dataSource={detail.persons || []}
pagination={false}
rowKey={(record, index) => index}
locale={{ emptyText: "暂无人员信息" }}
bordered
tableLayout="fixed"
/>
</Card>
<div style={{ marginTop: 24, textAlign: "center" }}>
<Button
onClick={() => {
props.history.replace({
pathname: "/Container/BranchCompany/ControlRoom/list",
state: { refresh: true },
});
}}
>
返回
</Button>
</div>
</>
)}
</Page>
);
}
export default Connect([NS_CONTROL_ROOM], true)(Permission(View));

View File

@ -0,0 +1,18 @@
import { Route, Switch } from "react-router-dom";
import Add from "./Add";
import List from "./List";
import View from "./View";
function ControlRoom() {
return (
<div>
<Switch>
<Route path="/Container/BranchCompany/ControlRoom/list" component={List} />
<Route path="/Container/BranchCompany/ControlRoom/add" component={Add} />
<Route path="/Container/BranchCompany/ControlRoom/view" component={View} />
</Switch>
</div>
);
}
export default ControlRoom;

View File

@ -0,0 +1,111 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Form } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_FIRE_RESOURCE_STATS } from "~/enumerate/namespace";
function FireResourceStats(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["fireResourceStatsList"], { form });
const handleViewDetail = (record, type) => {
const routeMap = {
rescueTeam: "/container/branchCompany/rescueTeam/list",
controlRoom: "/container/branchCompany/controlRoom/list",
pumpRoom: "/container/branchCompany/pumpRoom/list",
waterSource: "/container/branchCompany/waterSource/list",
};
if (routeMap[type]) {
const corpId = record.corpId || record.companyId || "";
props.history.push(`${routeMap[type]}?eqCorpId=${corpId}`);
}
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "likeCompanyName", label: "企业名称" },
]}
form={form}
onFinish={getData}
/>
<Table
columns={[
{ title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{
title: "企业名称",
dataIndex: "companyName",
ellipsis: true,
render: text => text || "一公司",
},
{
title: "消防救援队数",
dataIndex: "rescueTeamCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "rescueTeam")}
>
{text || 0}
</a>
),
},
{
title: "消防控制室数",
dataIndex: "controlRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "controlRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防泵房数",
dataIndex: "pumpRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "pumpRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防水源数",
dataIndex: "waterSourceCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "waterSource")}
>
{text || 0}
</a>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_FIRE_RESOURCE_STATS], true)(Permission(FireResourceStats));

View File

@ -0,0 +1,414 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Col, Form, Input, InputNumber, message, Row, Space, Table } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Upload from "zy-react-library/components/Upload";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useUploadFile from "zy-react-library/hooks/useUploadFile";
import { NS_PUMP_ROOM } from "~/enumerate/namespace";
function Add(props) {
const query = useGetUrlQuery();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
// 设备信息列表
const [devices, setDeviceList] = useState([]);
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
const { loading: getFileLoading, getFile } = useGetFile();
const [deleteFiles, setDeleteFiles] = useState([]);
const getData = async () => {
setLoading(true);
try {
const { data } = await props["pumpRoomDetail"]({ id: query.id });
const roomKey = data.roomId || data.pumpRoomId || data.id;
if (roomKey) {
const files = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[302],
eqForeignKey: roomKey,
});
data.roomImages = files;
}
form.setFieldsValue(data);
// 设置设备列表
if (data.devices && data.devices.length > 0) {
setDeviceList(data.devices.map((item, index) => ({ ...item, key: index })));
}
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
const onFinish = async (values) => {
// 验证设备列表
if (devices.length === 0) {
message.warning("请至少添加一条设备信息");
return;
}
// 验证设备列表是否填写完整
const invalidDevice = devices.find(
item => !item.deviceName || !item.deviceModel || !item.deviceQty || !item.deviceLocation,
);
if (invalidDevice) {
message.warning("请完善设备信息");
return;
}
// 楠岃瘉鍥剧墖鏄惁涓婁紶
if (!values.roomImages || values.roomImages.length === 0) {
message.warning("璇蜂笂浼犳秷闃叉车鎴垮浘鐗?");
return;
}
setSubmitting(true);
try {
if (deleteFiles.length > 0) {
await deleteFile({ single: false, files: deleteFiles });
}
const { id: pumpRoomId } = await uploadFile({
single: false,
files: values.roomImages,
params: {
type: UPLOAD_FILE_TYPE_ENUM[302],
foreignKey: values.roomId || values.pumpRoomId || query.id,
},
});
const apiMethod = query.id ? props["pumpRoomUpdate"] : props["pumpRoomAdd"];
const params = {
...values,
pumpRoomId,
devices: devices.map(({ key, ...device }) => ({
deviceCode: device.deviceCode,
deviceName: device.deviceName,
deviceModel: device.deviceModel,
deviceQty: device.deviceQty,
deviceLocation: device.deviceLocation,
...(device.id && { id: device.id }),
})),
};
if (query.id) {
params.id = query.id;
}
await apiMethod(params);
message.success(query.id ? "修改成功" : "新增成功");
props.history.goBack();
}
catch (error) {
message.error(query.id ? "修改失败" : "新增失败");
}
finally {
setSubmitting(false);
}
};
// 设备列表相关操作
const addDevice = () => {
const newDevice = {
key: Date.now(),
deviceName: "",
deviceModel: "",
deviceQty: null,
deviceLocation: "",
};
setDeviceList([...devices, newDevice]);
};
const removeDevice = (key) => {
setDeviceList(devices.filter(item => item.key !== key));
};
const updateDevice = (key, field, value) => {
setDeviceList(
devices.map(item => (item.key === key ? { ...item, [field]: value } : item)),
);
};
// 设备表格列定义
const deviceColumns = [
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备编号
</>),
dataIndex: "deviceCode",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备编号"
onChange={e => updateDevice(record.key, "deviceCode", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备名称
</>),
dataIndex: "deviceName",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备名称"
onChange={e => updateDevice(record.key, "deviceName", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备型号
</>),
dataIndex: "deviceModel",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备型号"
onChange={e => updateDevice(record.key, "deviceModel", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备数量
</>),
dataIndex: "deviceQty",
render: (text, record) => (
<InputNumber
value={text}
placeholder="请输入设备数量"
min={1}
style={{ width: "100%" }}
onChange={value => updateDevice(record.key, "deviceQty", value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
设备位置
</>),
dataIndex: "deviceLocation",
render: (text, record) => (
<Input
value={text}
placeholder="请输入设备位置"
onChange={e => updateDevice(record.key, "deviceLocation", e.target.value)}
/>
),
},
{
title: "操作",
width: 80,
align: "center",
render: (_, record) => (
<Button type="link" danger onClick={() => removeDevice(record.key)}>
删除
</Button>
),
},
];
return (
<Page
headerTitle={query.id ? "编辑消防泵房" : "新增消防泵房"}
loading={loading || uploadFileLoading || deleteFileLoading || getFileLoading}
isShowFooter={false}
>
<Form
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={onFinish}
style={{ maxWidth: 1200, marginTop: 24 }}
>
<Card title="基本信息" style={{ marginBottom: 24 }}>
<Form.Item name="pumpRoomId" hidden>
<Input />
</Form.Item>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="消防泵房名称"
name="pumpRoomName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入消防泵房名称" },
{ max: 100, message: "消防泵房名称不能超过100个字符" },
]}
>
<Input placeholder="请输入消防泵房名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="消防泵房状态"
name="pumpRoomStatus"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请选择消防泵房状态" }]}
>
<DictionarySelect
dictValue="fire_resource_contro_root_type"
placeholder="请选择"
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="负责人"
name="principalName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入负责人" },
{ max: 50, message: "负责人不能超过50个字符" },
]}
>
<Input placeholder="请输入负责人" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="负责人手机号"
name="principalPhone"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入负责人手机号" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" },
]}
>
<Input placeholder="请输入负责人手机号" maxLength={11} />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="经度"
name="lng"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入经度" }]}
>
<InputNumber
placeholder="请输入经度"
style={{ width: "100%" }}
min={-180}
max={180}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="纬度"
name="lat"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入纬度" }]}
>
<InputNumber
placeholder="请输入纬度"
style={{ width: "100%" }}
min={-90}
max={90}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<Form.Item
label="消防泵房图片"
name="roomImages"
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
rules={[{ required: true, message: "请上传消防泵房图片" }]}
extra="默认上传4个目前支持jpg、jpeg、png格式单张图片不超过5mb"
>
<Upload
fileType="image"
maxCount={4}
maxSize={5}
accept=".jpg,.jpeg,.png"
onGetRemoveFile={(file) => {
setDeleteFiles([...deleteFiles, file]);
}}
/>
</Form.Item>
</Col>
</Row>
</Card>
<Card
title="设备信息"
extra={(
<Button type="primary" onClick={addDevice}>
新增
</Button>
)}
style={{ marginBottom: 24 }}
>
<Table
columns={deviceColumns}
dataSource={devices}
pagination={false}
rowKey="key"
locale={{ emptyText: "暂无设备信息,请点击新增按钮添加" }}
/>
</Card>
<Form.Item wrapperCol={{ span: 24 }} style={{ textAlign: "center" }}>
<Space size={16}>
<Button onClick={() => props.history.goBack()}>
取消
</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
提交
</Button>
</Space>
</Form.Item>
</Form>
</Page>
);
}
export default Connect([NS_PUMP_ROOM], true)(Permission(Add));

View File

@ -0,0 +1,158 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Table from "zy-react-library/components/Table";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import { NS_PUMP_ROOM } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const query = useGetUrlQuery();
const { tableProps, getData } = useTable(props["pumpRoomList"], { form });
const [deleting, setDeleting] = useState(false);
const [statusDict, setStatusDict] = useState([]);
const { getDictionary } = useDictionary();
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_contro_root_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
useEffect(() => {
if (query.eqCorpId) {
form.setFieldsValue({ eqCorpId: query.eqCorpId });
getData();
}
}, [query.eqCorpId]);
const getStatusLabel = (record) => {
if (record.roomStatusName)
return record.roomStatusName;
const match = statusDict.find(item => item.dictValue === record.pumpRoomStatus);
return match?.dictLabel || record.pumpRoomStatus || "-";
};
const onDelete = (id) => {
Modal.confirm({
title: "确认删除",
content: "删除后不可恢复,确认继续?",
okText: "确认",
cancelText: "取消",
okButtonProps: { danger: true, loading: deleting },
onOk: async () => {
if (!props["pumpRoomDelete"]) {
message.warning("未接入 pumpRoomDelete effect");
return;
}
setDeleting(true);
try {
await props["pumpRoomDelete"]({ id });
message.success("已删除");
getData();
}
finally {
setDeleting(false);
}
},
});
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "likePumpRoomName", label: "消防泵房名称" },
{
name: "eqPumpRoomStatus",
label: "消防泵房状态",
render: (<DictionarySelect dictValue="fire_resource_contro_root_type" />),
},
{ name: "eqCorpId", onlyForLabel: true },
]}
form={form}
onFinish={getData}
/>
<Table
toolBarRender={() => (
<Space>
<Button
type="primary"
onClick={() => {
props.history.push("./add");
}}
>
新增
</Button>
</Space>
)}
columns={[
// { title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "消防泵房名称", dataIndex: "pumpRoomName", ellipsis: true, width: 120 },
{
title: "消防泵房状态",
width: 140,
align: "center",
render: (_, record) => getStatusLabel(record),
},
{ title: "负责人", dataIndex: "principalName", width: 120 },
{ title: "负责人手机号", dataIndex: "principalPhone", width: 140 },
{ title: "设备数", dataIndex: "deviceCount", width: 100, align: "center" },
{
title: "操作",
fixed: "right",
width: 180,
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => {
props.history.push(`./view?id=${record.id}`);
}}
>
查看
</Button>
<Button
type="link"
onClick={() => {
props.history.push(`./add?id=${record.id}`);
}}
>
编辑
</Button>
<Button
type="link"
danger
onClick={() => onDelete(record.id)}
>
删除
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_PUMP_ROOM], true)(Permission(List));

View File

@ -0,0 +1,166 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Descriptions, Image, message, Space, Table } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_PUMP_ROOM } from "~/enumerate/namespace";
function View(props) {
const query = useGetUrlQuery();
const [loading, setLoading] = useState(false);
const [detail, setDetail] = useState(null);
const [statusDict, setStatusDict] = useState([]);
const { getDictionary } = useDictionary();
const { loading: getFileLoading, getFile } = useGetFile();
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_contro_root_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
const getStatusLabel = (record) => {
if (!record)
return "-";
if (record.roomStatusName)
return record.roomStatusName;
const match = statusDict.find(item => item.dictValue === record.pumpRoomStatus);
return match?.dictLabel || record.pumpRoomStatus || "-";
};
const getData = async () => {
setLoading(true);
try {
const { data } = await props["pumpRoomDetail"]({ id: query.id });
let nextDetail = data;
const roomKey = data.roomId || data.pumpRoomId || data.id;
if (roomKey) {
const files = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[302],
eqForeignKey: roomKey,
});
nextDetail = { ...data, roomImages: files };
}
setDetail(nextDetail);
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
// 设备表格列定义
const deviceColumns = [
{ title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "设备编号", dataIndex: "deviceCode", align: "center" },
{ title: "设备名称", dataIndex: "deviceName", align: "center" },
{ title: "设备型号", dataIndex: "deviceModel", align: "center" },
{ title: "设备数量", dataIndex: "deviceQty", align: "center" },
{ title: "设备位置", dataIndex: "deviceLocation", align: "center" },
];
return (
<Page
headerTitle="查看消防泵房"
loading={loading || getFileLoading}
isShowFooter={false}
>
{detail && (
<>
<Card title="基本信息" style={{ marginBottom: 24, marginTop: 24 }}>
<Descriptions
bordered
column={2}
labelStyle={{ width: 180, backgroundColor: "#fafafa" }}
contentStyle={{ backgroundColor: "#fff" }}
items={[
{
label: "消防泵房名称",
children: detail.pumpRoomName,
},
{
label: "消防泵房状态",
children: getStatusLabel(detail),
},
{
label: "负责人",
children: detail.principalName,
},
{
label: "负责人手机号",
children: detail.principalPhone,
},
{
label: "经度",
children: detail.lng,
},
{
label: "纬度",
children: detail.lat,
},
{
label: "消防泵房图片",
children: detail.roomImages && detail.roomImages.length > 0
? (
<Image.PreviewGroup>
<Space>
{detail.roomImages.map((img, index) => (
<Image
key={index}
width={100}
height={100}
src={img}
style={{ objectFit: "cover" }}
/>
))}
</Space>
</Image.PreviewGroup>
)
: (
"无"
),
span: 2,
},
]}
/>
</Card>
<Card title="设备信息" style={{ marginBottom: 24 }}>
<Table
columns={deviceColumns}
dataSource={detail.devices || []}
pagination={false}
rowKey={(record, index) => index}
locale={{ emptyText: "暂无设备信息" }}
/>
</Card>
<div style={{ marginTop: 24, textAlign: "center" }}>
<Button onClick={() => props.history.goBack()}>
返回
</Button>
</div>
</>
)}
</Page>
);
}
export default Connect([NS_PUMP_ROOM], true)(Permission(View));

View File

@ -0,0 +1,5 @@
function Index(props) {
return (<div>{props.children}</div>);
}
export default Index;

View File

@ -0,0 +1,407 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Col, DatePicker, Form, Input, message, Row, Space, Table } from "antd";
import dayjs from "dayjs";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_RESCUE_TEAM } from "~/enumerate/namespace";
function Add(props) {
const query = useGetUrlQuery();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
// 消防队员列表
const [rescueMembers, setRescueMembers] = useState([]);
// ⭐ 添加状态保存字典显示名称
const [teamTypeName, setTeamTypeName] = useState("");
const [regionScopeName, setRegionScopeName] = useState("");
const getData = async () => {
setLoading(true);
try {
const { data } = await props["rescueTeamDetail"]({ id: query.id });
// 处理日期字段
if (data.establishDate) {
data.establishDate = dayjs(data.establishDate);
}
form.setFieldsValue(data);
// ⭐ 设置字典显示名称
if (data.teamTypeName) {
setTeamTypeName(data.teamTypeName);
}
if (data.regionScopeName) {
setRegionScopeName(data.regionScopeName);
}
// 设置消防队员列表
if (data.rescueMembers && data.rescueMembers.length > 0) {
setRescueMembers(data.rescueMembers.map((item, index) => ({ ...item, key: index })));
}
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
const onFinish = async (values) => {
// 验证消防队员列表
if (rescueMembers.length === 0) {
message.warning("请至少添加一名消防队员");
return;
}
// 验证消防队员必填项
const invalidMember = rescueMembers.find(
item => !item.personName || !item.personPhone,
);
if (invalidMember) {
message.warning("请完善消防队员信息");
return;
}
setSubmitting(true);
try {
const apiMethod = query.id ? props["rescueTeamUpdate"] : props["rescueTeamAdd"];
// 处理日期格式和提交参数
const params = {
...values,
establishDate: values.establishDate ? values.establishDate.format("YYYY-MM-DD") : null,
// ⭐ 从状态中获取字典显示名称
teamTypeName,
regionScopeName,
// 队员数量
memberCount: rescueMembers.length,
// 队员列表:去掉前端添加的 key
rescueMembers: rescueMembers.map(({ key, ...member }) => ({
personName: member.personName,
personPhone: member.personPhone,
dutyDesc: member.dutyDesc || "",
roleCode: member.roleCode || 1,
...(member.id && { id: member.id }),
})),
};
if (query.id) {
params.id = query.id;
params.teamId = values.teamId;
}
await apiMethod(params);
message.success(query.id ? "修改成功" : "新增成功");
props.history.goBack();
}
catch (error) {
message.error(query.id ? "修改失败" : "新增失败");
}
finally {
setSubmitting(false);
}
};
// 消防队员相关操作
const addMember = () => {
const newMember = {
key: Date.now(),
personName: "",
personPhone: "",
dutyDesc: "",
roleCode: 1,
};
setRescueMembers([...rescueMembers, newMember]);
};
const removeMember = (key) => {
setRescueMembers(rescueMembers.filter(item => item.key !== key));
};
const updateMember = (key, field, value) => {
setRescueMembers(
rescueMembers.map(item => (item.key === key ? { ...item, [field]: value } : item)),
);
};
// 消防队员表格列定义
const memberColumns = [
{
title: (<>
<span style={{ color: "red" }}>*</span>
队员姓名
</>),
dataIndex: "personName",
render: (text, record) => (
<Input
value={text}
placeholder="请输入队员姓名"
onChange={e => updateMember(record.key, "personName", e.target.value)}
/>
),
},
{
title: (<>
<span style={{ color: "red" }}>*</span>
队员电话
</>),
dataIndex: "personPhone",
render: (text, record) => (
<Input
value={text}
placeholder="请输入队员电话"
maxLength={11}
onChange={e => updateMember(record.key, "personPhone", e.target.value)}
/>
),
},
{
title: "操作",
width: 80,
align: "center",
render: (_, record) => (
<Button type="link" danger onClick={() => removeMember(record.key)}>
删除
</Button>
),
},
];
return (
<Page
headerTitle={query.id ? "编辑救援队" : "新增救援队"}
loading={loading}
isShowFooter={false}
>
<Form
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={onFinish}
style={{ maxWidth: 1200, marginTop: 24 }}
>
<Card title="基本信息" style={{ marginBottom: 24 }}>
<Form.Item name="teamId" hidden>
<Input />
</Form.Item>
{/* 第一行:救援队名称、类型 */}
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="救援队名称"
name="teamName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入救援队名称" },
{ max: 100, message: "救援队名称不能超过100个字符" },
]}
>
<Input placeholder="请输入救援队名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="类型"
name="teamType"
labelCol={{ span: 12 }}
rules={[
{ required: true, message: "请选择救援队类型" },
{ max: 50, message: "救援队类型不能超过50个字符" },
]}
wrapperCol={{ span: 12 }}
>
<DictionarySelect
dictValue="fire_resource_team_type"
placeholder="请选择"
onChange={(value, option) => {
// ⭐ 监听选择事件,保存显示名称
setTeamTypeName(option?.children || option?.label || "");
}}
/>
</Form.Item>
</Col>
</Row>
{/* 第二行:队长、队长电话 */}
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="队长"
name="captainName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入队长姓名" },
{ max: 50, message: "队长姓名不能超过50个字符" },
]}
>
<Input placeholder="请输入队长姓名" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="队长电话"
name="captainPhone"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入队长电话" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" },
]}
>
<Input placeholder="请输入队长电话" maxLength={11} />
</Form.Item>
</Col>
</Row>
{/* 第三行:指挥人员、建立日期 */}
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="指挥人员"
name="commandCrew"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入指挥人员姓名" },
{ max: 50, message: "指挥人员姓名不能超过50个字符" },
]}
>
<Input placeholder="请输入指挥人员" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="建立日期"
name="establishDate"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请选择建立日期" }]}
>
<DatePicker
placeholder="请选择日期"
style={{ width: "100%" }}
format="YYYY-MM-DD"
/>
</Form.Item>
</Col>
</Row>
{/* 第四行:所属区域或范围、职责和任务范围 */}
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="所属区域或范围"
name="regionScope"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请选择救援队所属区域或范围" },
{ max: 50, message: "救援队所属区域或范围不能超过50个字符" },
]}
>
<DictionarySelect
dictValue="fire_resource_area_scope"
placeholder="请选择"
onChange={(value, option) => {
// ⭐ 监听选择事件,保存显示名称
setRegionScopeName(option?.children || option?.label || "");
}}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="职责和任务范围"
name="dutyScope"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入职责和任务范围" },
{ max: 500, message: "职责和任务范围不能超过500个字符" },
]}
>
<Input.TextArea
placeholder="请输入职责和任务范围"
rows={4}
maxLength={500}
showCount
/>
</Form.Item>
</Col>
</Row>
{/* 第五行:负责人单位或部门(单独一行) */}
<Row gutter={24}>
<Col span={24}>
<Form.Item
label="负责人单位或部门"
name="chargeOrgDept"
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
rules={[
{ required: true, message: "请输入负责人单位或部门" },
{ max: 200, message: "负责人单位或部门不能超过200个字符" },
]}
>
<Input placeholder="请输入负责人单位或部门" />
</Form.Item>
</Col>
</Row>
</Card>
<Card
title="队员信息"
extra={(
<Button type="primary" onClick={addMember}>
新增
</Button>
)}
style={{ marginBottom: 24 }}
>
<Table
columns={memberColumns}
dataSource={rescueMembers}
pagination={false}
rowKey="key"
locale={{ emptyText: "暂无队员信息,请点击新增按钮添加" }}
/>
</Card>
<Form.Item wrapperCol={{ span: 24 }} style={{ textAlign: "center" }}>
<Space size={16}>
<Button onClick={() => props.history.goBack()}>
取消
</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
提交
</Button>
</Space>
</Form.Item>
</Form>
</Page>
);
}
export default Connect([NS_RESCUE_TEAM], true)(Permission(Add));

View File

@ -0,0 +1,147 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Table from "zy-react-library/components/Table";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import { getLabelName } from "zy-react-library/utils";
import { NS_RESCUE_TEAM } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const query = useGetUrlQuery();
const { tableProps, getData } = useTable(props["rescueTeamList"], { form });
const [deleting, setDeleting] = useState(false);
useEffect(() => {
if (query.eqCorpId) {
form.setFieldsValue({ eqCorpId: query.eqCorpId });
getData();
}
}, [query.eqCorpId]);
const onDelete = (id) => {
Modal.confirm({
title: "确认删除",
content: "删除后不可恢复,确认继续?",
okText: "确认",
cancelText: "取消",
okButtonProps: { danger: true, loading: deleting },
onOk: async () => {
if (!props["rescueTeamDelete"]) {
message.warning("未接入 rescueTeamDelete effect");
return;
}
setDeleting(true);
try {
await props["rescueTeamDelete"]({ id });
message.success("已删除");
getData();
}
finally {
setDeleting(false);
}
},
});
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "likeTeamName", label: "救援队名称" },
{
name: "eqTeamType",
label: "类型",
render: (<DictionarySelect dictValue="fire_resource_team_type" />),
},
{ name: "likeChargeOrgDept", label: "负责人单位或部门" },
{ name: "likeCaptainName", label: "队长" },
{ name: "eqCorpId", onlyForLabel: true },
]}
form={form}
onFinish={getData}
/>
<Table
toolBarRender={() => (
<Space>
{/* {props.permission("jydgl-btn-add") && ( */}
<Button
type="primary"
onClick={() => {
props.history.push("./add");
}}
>
新增
</Button>
{/* )} */}
</Space>
)}
columns={[
// { title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "救援队名称", dataIndex: "teamName", ellipsis: true },
{
title: "类型",
dataIndex: "teamTypeName",
width: 140,
align: "center",
},
{ title: "负责人单位或部门", dataIndex: "chargeOrgDept", ellipsis: true },
{ title: "队长", dataIndex: "captainName", width: 120, align: "center" },
{ title: "指挥人员", dataIndex: "commandCrew", width: 120, align: "center" },
{ title: "队员人数", dataIndex: "memberCount", width: 110, align: "center" },
{
title: "操作",
fixed: "right",
width: 180,
render: (_, record) => (
<Space>
{/* {props.permission("jydgl-btn-view") && ( */}
<Button
type="link"
onClick={() => {
props.history.push(`./view?id=${record.id}`);
}}
>
查看
</Button>
{/* )} */}
{/* {props.permission("jydgl-btn-edit") && ( */}
<Button
type="link"
onClick={() => {
props.history.push(`./add?id=${record.id}`);
}}
>
编辑
</Button>
{/* )} */}
{/* {props.permission("jydgl-btn-delete") && ( */}
<Button
type="link"
danger
onClick={() => onDelete(record.id)}
>
删除
</Button>
{/* )} */}
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_RESCUE_TEAM], true)(Permission(List));

View File

@ -0,0 +1,119 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Descriptions, message, Table } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_RESCUE_TEAM } from "~/enumerate/namespace";
function View(props) {
const query = useGetUrlQuery();
const [loading, setLoading] = useState(false);
const [detail, setDetail] = useState(null);
const getData = async () => {
setLoading(true);
try {
const { data } = await props["rescueTeamDetail"]({ id: query.id });
setDetail(data);
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
// 队员表格列定义
const memberColumns = [
{ title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "队员", dataIndex: "personName" },
{ title: "队员电话", dataIndex: "personPhone" },
];
return (
<Page
headerTitle="查看救援队"
loading={loading}
isShowFooter={false}
>
{detail && (
<>
<Card title="基本信息" style={{ marginBottom: 24, marginTop: 24 }}>
<Descriptions
bordered
column={2}
contentStyle={{ backgroundColor: "#fff" }}
labelStyle={{
width: "180px",
backgroundColor: "#fafafa",
fontWeight: "normal",
}}
items={[
{
label: "救援队名称",
children: detail.teamName || "-",
},
{
label: "负责人单位或部门",
children: detail.chargeOrgDept || "-",
},
{
label: "类型",
children: detail.teamTypeName || detail.teamType || "-",
},
{
label: "职责和任务范围",
children: detail.dutyScope || "-",
},
{
label: "队长",
children: detail.captainName || "-",
},
{
label: "队长电话",
children: detail.captainPhone || "-",
},
{
label: "指挥人员",
children: detail.commandCrew || "-",
},
{
label: "建立日期",
children: detail.establishDate || "-",
},
{
label: "所属区域或范围",
children: detail.regionScopeName || detail.regionScope || "-",
},
]}
/>
</Card>
<Card title="队员信息" style={{ marginBottom: 24 }}>
<Table
columns={memberColumns}
dataSource={detail.rescueMembers || []}
pagination={false}
rowKey={(record, index) => index}
locale={{ emptyText: "暂无队员信息" }}
/>
</Card>
<div style={{ marginTop: 24, textAlign: "center" }}>
<Button onClick={() => props.history.goBack()}>
返回
</Button>
</div>
</>
)}
</Page>
);
}
export default Connect([NS_RESCUE_TEAM], true)(Permission(View));

View File

@ -0,0 +1,4 @@
function RescueTeam(props) {
return (<div>{props.children}</div>);
}
export default RescueTeam;

View File

@ -0,0 +1,285 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Col, Form, Input, InputNumber, message, Row, Space } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_WATER_SOURCE } from "~/enumerate/namespace";
function Add(props) {
const query = useGetUrlQuery();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const getData = async () => {
setLoading(true);
try {
const { data } = await props["waterSourceDetail"]({ id: query.id });
form.setFieldsValue(data);
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
const onFinish = async (values) => {
setSubmitting(true);
try {
const apiMethod = query.id ? props["waterSourceUpdate"] : props["waterSourceAdd"];
const params = query.id ? { ...values, id: query.id } : values;
await apiMethod(params);
message.success(query.id ? "修改成功" : "新增成功");
props.history.goBack();
}
catch (error) {
message.error(query.id ? "修改失败" : "新增失败");
}
finally {
setSubmitting(false);
}
};
return (
<Page
headerTitle={query.id ? "编辑消防水源" : "新增消防水源"}
loading={loading}
isShowFooter={false}
>
<Form
form={form}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={onFinish}
style={{ maxWidth: 1200, marginTop: 24 }}
>
<Card title="基本信息" style={{ marginBottom: 24 }}>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="消防水源名称"
name="waterSourceName"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入消防水源名称" },
{ max: 100, message: "消防水源名称不能超过100个字符" },
]}
>
<Input placeholder="请输入消防水源名称" />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="消防水源状态"
name="waterSourceStatus"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请选择消防水源状态" }]}
>
<DictionarySelect
dictValue="fire_resource_water_type"
placeholder="请选择"
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="经度"
name="lng"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入经度" }]}
>
<InputNumber
placeholder="请输入经度"
style={{ width: "100%" }}
min={-180}
max={180}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="纬度"
name="lat"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入纬度" }]}
>
<InputNumber
placeholder="请输入纬度"
style={{ width: "100%" }}
min={-90}
max={90}
step={0.000001}
precision={6}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="所属单位或部门"
name="belongOrgDept"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入所属单位或部门" },
{ max: 200, message: "所属单位或部门不能超过200个字符" },
]}
>
<Input placeholder="请输入所属单位或部门" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="水源位置"
name="waterLocation"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[
{ required: true, message: "请输入水源位置" },
{ max: 200, message: "水源位置不能超过200个字符" },
]}
>
<Input placeholder="请输入水源位置" />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="接口形式"
name="interfaceForm"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入接口形式" }]}
>
<Input placeholder="请输入接口形式" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="水源类型"
name="waterType"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入水源类型" }]}
>
<Input placeholder="请输入水源类型" />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="水源编号"
name="waterCode"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入水源编号" }]}
>
<Input placeholder="请输入水源编号" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="吸水口规格"
name="suctionSpec"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入吸水口规格" }]}
>
<InputNumber
placeholder="请输入吸水口规格"
style={{ width: "100%" }}
min={0}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="水源容量"
name="capacity"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入水源容量" }]}
>
<Input placeholder="请输入水源容量" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="供水能力"
name="supplyAbility"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入供水能力" }]}
>
<Input placeholder="请输入供水能力" />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="设备清单"
name="equipmentList"
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
rules={[{ required: true, message: "请输入设备清单" }]}
>
<Input placeholder="请输入设备清单" />
</Form.Item>
</Col>
</Row>
</Card>
<Form.Item wrapperCol={{ span: 24 }} style={{ textAlign: "center" }}>
<Space>
<Button onClick={() => props.history.goBack()}>
取消
</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
提交
</Button>
</Space>
</Form.Item>
</Form>
</Page>
);
}
export default Connect([NS_WATER_SOURCE], true)(Permission(Add));

View File

@ -0,0 +1,159 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Table from "zy-react-library/components/Table";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import { NS_WATER_SOURCE } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const query = useGetUrlQuery();
const { tableProps, getData } = useTable(props["waterSourceList"], { form });
const [deleting, setDeleting] = useState(false);
const [statusDict, setStatusDict] = useState([]);
const { getDictionary } = useDictionary();
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_water_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
useEffect(() => {
if (query.eqCorpId) {
form.setFieldsValue({ eqCorpId: query.eqCorpId });
getData();
}
}, [query.eqCorpId]);
const getStatusLabel = (record) => {
if (record.sourceStatusName)
return record.sourceStatusName;
const match = statusDict.find(item => item.dictValue === record.waterSourceStatus);
return match?.dictLabel || record.sourceStatus || "-";
};
const onDelete = (id) => {
Modal.confirm({
title: "确认删除",
content: "删除后不可恢复,确认继续?",
okText: "确认",
cancelText: "取消",
okButtonProps: { danger: true, loading: deleting },
onOk: async () => {
if (!props["waterSourceDelete"]) {
message.warning("未接入 waterSourceDelete effect");
return;
}
setDeleting(true);
try {
await props["waterSourceDelete"]({ id });
message.success("已删除");
getData();
}
finally {
setDeleting(false);
}
},
});
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "likeWaterSourceName", label: "消防水源名称" },
{
name: "eqWaterSourceStatus",
label: "消防水源状态",
render: (<DictionarySelect dictValue="fire_resource_water_type" />),
},
{ name: "likeBelongOrgDept", label: "所属单位或部门" },
{ name: "eqCorpId", onlyForLabel: true },
]}
form={form}
onFinish={getData}
/>
<Table
toolBarRender={() => (
<Space>
<Button
type="primary"
onClick={() => {
props.history.push("./add");
}}
>
新增
</Button>
</Space>
)}
columns={[
// { title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{ title: "消防水源名称", dataIndex: "waterSourceName", ellipsis: true },
{
title: "消防水源状态",
width: 140,
align: "center",
render: (_, record) => getStatusLabel(record),
},
{ title: "所属单位或部门", dataIndex: "belongOrgDept", ellipsis: true },
{ title: "水源位置", dataIndex: "waterLocation", ellipsis: true },
{
title: "操作",
fixed: "right",
width: 180,
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => {
props.history.push(`./view?id=${record.id}`);
}}
>
查看
</Button>
<Button
type="link"
onClick={() => {
props.history.push(`./add?id=${record.id}`);
}}
>
编辑
</Button>
<Button
type="link"
danger
onClick={() => onDelete(record.id)}
>
删除
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_WATER_SOURCE], true)(Permission(List));

View File

@ -0,0 +1,270 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Card, Col, Descriptions, message, Row } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import useDictionary from "zy-react-library/hooks/useDictionary";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_WATER_SOURCE } from "~/enumerate/namespace";
function View(props) {
const query = useGetUrlQuery();
const [loading, setLoading] = useState(false);
const [detail, setDetail] = useState(null);
const [statusDict, setStatusDict] = useState([]);
const { getDictionary } = useDictionary();
const getData = async () => {
setLoading(true);
try {
const { data } = await props["waterSourceDetail"]({ id: query.id });
setDetail(data);
}
catch (error) {
message.error("加载详情失败");
}
finally {
setLoading(false);
}
};
useEffect(() => {
query.id && getData();
}, []);
useEffect(() => {
const fetchDict = async () => {
try {
const dict = await getDictionary({ dictValue: "fire_resource_water_type" });
setStatusDict(Array.isArray(dict) ? dict : []);
}
catch (error) {
setStatusDict([]);
}
};
fetchDict();
}, []);
const getStatusLabel = (status) => {
if (!status)
return "-";
const match = statusDict.find(item => item.dictValue === status);
return match?.dictLabel || status;
};
return (
<Page
headerTitle="查看消防水源"
loading={loading}
isShowFooter={false}
>
{detail && (
<>
<Card title="基本信息" style={{ marginBottom: 24, marginTop: 24 }}>
<Row gutter={24}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "消防水源名称",
children: detail.waterSourceName || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "消防水源状态",
children: getStatusLabel(detail.waterSourceStatus),
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "经度",
children: detail.lng || "-",
},
]}
/>
</Col>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "纬度",
children: detail.lat || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "所属单位或部门",
children: detail.belongOrgDept || "-",
},
]}
/>
</Col>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "水源位置",
children: detail.waterLocation || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "接口形式",
children: detail.interfaceForm || "-",
},
]}
/>
</Col>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "水源类型",
children: detail.waterType || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "水源编号",
children: detail.waterCode || "-",
},
]}
/>
</Col>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "吸水口规格",
children: detail.suctionSpec || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "水源容量",
children: detail.capacity || "-",
},
]}
/>
</Col>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "供水能力",
children: detail.supplyAbility || "-",
},
]}
/>
</Col>
</Row>
<Row gutter={24} style={{ marginTop: 16 }}>
<Col span={12}>
<Descriptions
bordered
column={1}
labelStyle={{ width: "50%" }}
items={[
{
label: "设备清单",
children: detail.equipmentList || "-",
},
]}
/>
</Col>
</Row>
</Card>
<div style={{ marginTop: 24, textAlign: "center" }}>
<Button onClick={() => props.history.goBack()}>
返回
</Button>
</div>
</>
)}
</Page>
);
}
export default Connect([NS_WATER_SOURCE], true)(Permission(View));

View File

@ -0,0 +1,5 @@
function Index(props) {
return (<div>{props.children}</div>);
}
export default Index;

View File

@ -0,0 +1,5 @@
// 简单的路由容器
function BranchCompany(props) {
return (<div>{props.children}</div>);
}
export default BranchCompany;

View File

@ -0,0 +1,112 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Form } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_FIRE_RESOURCE_STATS } from "~/enumerate/namespace";
function FireResourceStats(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["fireResourceStatsList"], { form });
const handleViewDetail = (record, type) => {
// 跳转到对应的详情列表页面
const routeMap = {
rescueTeam: "/container/branchCompany/rescueTeam/list",
controlRoom: "/container/branchCompany/controlRoom/list",
pumpRoom: "/container/branchCompany/pumpRoom/list",
waterSource: "/container/branchCompany/waterSource/list",
};
if (routeMap[type]) {
const corpId = record.corpId || record.companyId || "";
props.history.push(`${routeMap[type]}?corpId=${corpId}`);
}
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "likeCompanyName", label: "企业名称" },
]}
form={form}
onFinish={getData}
/>
<Table
columns={[
{ title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{
title: "企业名称",
dataIndex: "companyName",
ellipsis: true,
render: (text) => text || "一公司",
},
{
title: "消防救援队数",
dataIndex: "rescueTeamCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "rescueTeam")}
>
{text || 0}
</a>
),
},
{
title: "消防控制室数",
dataIndex: "controlRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "controlRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防泵房数",
dataIndex: "pumpRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "pumpRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防水源数",
dataIndex: "waterSourceCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "waterSource")}
>
{text || 0}
</a>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_FIRE_RESOURCE_STATS], true)(Permission(FireResourceStats));

View File

@ -0,0 +1,274 @@
import {
ColumnHeightOutlined,
FullscreenOutlined,
PlusOutlined,
ReloadOutlined,
SearchOutlined,
SettingOutlined,
} from "@ant-design/icons";
import {
Button,
Card,
Form,
Input,
message,
Select,
Space,
Table,
Tooltip,
Typography,
} from "antd";
// RescueTeamPage.jsx
// 依赖antd@5
import React, { useMemo, useState } from "react";
const { Text } = Typography;
export default function RescueTeamPage() {
const [form] = Form.useForm();
// mock 数据(你接后端时直接替换为接口)
const [data, setData] = useState([
{
id: 1,
name: "救援队名称示例",
type: "专职消防队",
org: "负责单位示例",
leader: "张三",
commander: "李四",
memberCount: 5,
},
]);
const columns = useMemo(
() => [
{
title: "序号",
dataIndex: "idx",
width: 80,
align: "center",
render: (_, __, index) => index + 1,
},
{ title: "救援队名称", dataIndex: "name", ellipsis: true },
{ title: "类型", dataIndex: "type", width: 140, align: "center" },
{ title: "负责人单位或部门", dataIndex: "org", ellipsis: true },
{ title: "队长", dataIndex: "leader", width: 120, align: "center" },
{ title: "指挥人员", dataIndex: "commander", width: 120, align: "center" },
{
title: "队员人数",
dataIndex: "memberCount",
width: 110,
align: "center",
},
{
title: "操作",
key: "actions",
width: 220,
align: "center",
render: (_, record) => (
<Space size={16}>
<a
onClick={() => {
message.info(`查看:${record.name}`);
}}
>
查看
</a>
<a
onClick={() => {
message.info(`编辑:${record.name}`);
}}
>
编辑
</a>
<a
style={{ color: "#ff4d4f" }}
onClick={() => {
setData(prev => prev.filter(x => x.id !== record.id));
message.success("已删除");
}}
>
删除
</a>
</Space>
),
},
],
[setData],
);
const onSearch = async () => {
const values = await form.validateFields().catch(() => null);
if (!values)
return;
// 这里接你的查询接口values 里包含所有筛选条件
// 示例:仅做前端过滤演示
const { name, type, org, region, leader } = values;
const source = [
{
id: 1,
name: "救援队名称示例",
type: "专职消防队",
org: "负责单位示例",
leader: "张三",
commander: "李四",
memberCount: 5,
},
];
const filtered = source.filter((r) => {
const okName = !name || r.name.includes(name);
const okType = !type || r.type === type;
const okOrg = !org || r.org.includes(org);
const okLeader = !leader || r.leader.includes(leader);
// region 只是占位,后端一般按行政区划过滤
const okRegion = !region || region === "内容待提供";
return okName && okType && okOrg && okLeader && okRegion;
});
setData(filtered);
};
const onReset = () => {
form.resetFields();
// 你可以选择是否立刻拉取列表
onSearch();
};
const onCreate = () => {
message.info("新增:打开新增弹窗/跳转新增页");
};
return (
<div style={{ padding: 16, background: "#f5f7fb", minHeight: "100vh" }}>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{/* 顶部筛选 */}
<Card
bodyStyle={{ padding: 16 }}
style={{ borderRadius: 10 }}
bordered={false}
>
<Form
form={form}
layout="vertical"
initialValues={{ region: "内容待提供" }}
>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr",
gap: 12,
alignItems: "end",
}}
>
<Form.Item label="救援队名称" name="name" style={{ marginBottom: 0 }}>
<Input placeholder="" allowClear />
</Form.Item>
<Form.Item label="类型" name="type" style={{ marginBottom: 0 }}>
<Select
placeholder="请选择"
allowClear
options={[
{ value: "专职消防队", label: "专职消防队" },
{ value: "社会救援队", label: "社会救援队" },
{ value: "企业救援队", label: "企业救援队" },
]}
/>
</Form.Item>
<Form.Item
label="负责人单位或部门"
name="org"
style={{ marginBottom: 0 }}
>
<Input placeholder="" allowClear />
</Form.Item>
<Form.Item
label={(
<span>
<span style={{ color: "#ff4d4f" }}>*</span>
所属区域范围
</span>
)}
name="region"
style={{ marginBottom: 0 }}
rules={[{ required: true, message: "请选择所属区域范围" }]}
>
<Select
options={[{ value: "内容待提供", label: "内容待提供" }]}
/>
</Form.Item>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<Space>
<Button
type="primary"
icon={<SearchOutlined />}
onClick={onSearch}
>
搜索
</Button>
<Button icon={<ReloadOutlined />} onClick={onReset}>
重置
</Button>
</Space>
</div>
{/* 第二行:队长 + 留白 */}
<Form.Item label="队长" name="leader" style={{ marginBottom: 0 }}>
<Input placeholder="" allowClear />
</Form.Item>
<div />
<div />
<div />
<div />
</div>
</Form>
</Card>
{/* 列表区 */}
<Card
bodyStyle={{ padding: 0 }}
style={{ borderRadius: 10 }}
bordered={false}
>
{/* 工具条 */}
<div
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
gap: 10,
padding: "12px 16px",
borderBottom: "1px solid #f0f0f0",
}}
>
<Button type="primary" icon={<PlusOutlined />} onClick={onCreate}>
新增
</Button>
<Tooltip title="列设置/表格配置(占位)">
<Button icon={<SettingOutlined />} />
</Tooltip>
<Tooltip title="行高/密度(占位)">
<Button icon={<ColumnHeightOutlined />} />
</Tooltip>
<Tooltip title="全屏(占位)">
<Button icon={<FullscreenOutlined />} />
</Tooltip>
</div>
<Table
rowKey="id"
columns={columns}
dataSource={data}
pagination={false}
size="middle"
scroll={{ x: 1100 }}
/>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,112 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Form } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_FIRE_RESOURCE_STATS } from "~/enumerate/namespace";
function FireResourceStats(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["fireResourceStatsList"], { form });
const handleViewDetail = (record, type) => {
const routeMap = {
rescueTeam: "/container/branchCompany/rescueTeam/list",
controlRoom: "/container/branchCompany/controlRoom/list",
pumpRoom: "/container/branchCompany/pumpRoom/list",
waterSource: "/container/branchCompany/waterSource/list",
};
if (routeMap[type]) {
const corpId = record.corpId || record.companyId || "";
props.history.push(`${routeMap[type]}?eqCorpId=${corpId}`);
}
};
return (
<Page isShowAllAction={false}>
<Search
labelCol={{ span: 8 }}
options={[
{ name: "corpName", label: "企业名称" },
]}
form={form}
onFinish={getData}
/>
<Table
columns={[
// { title: "序号", width: 80, align: "center", render: (_, __, index) => index + 1 },
{
title: "企业名称",
dataIndex: "corpName",
ellipsis: true,
render: text => text || "一公司",
width: 200,
},
{
title: "消防救援队数",
dataIndex: "rescueTeamCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "rescueTeam")}
>
{text || 0}
</a>
),
},
{
title: "消防控制室数",
dataIndex: "controlRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "controlRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防泵房数",
dataIndex: "pumpRoomCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "pumpRoom")}
>
{text || 0}
</a>
),
},
{
title: "消防水源数",
dataIndex: "waterSourceCount",
width: 140,
align: "center",
render: (text, record) => (
<a
style={{ color: "#1677ff", fontWeight: 600, textDecoration: "underline" }}
onClick={() => handleViewDetail(record, "waterSource")}
>
{text || 0}
</a>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_FIRE_RESOURCE_STATS], true)(Permission(FireResourceStats));

View File

@ -0,0 +1,5 @@
function Index(props) {
return (<div>{props.children}</div>);
}
export default Index;