bug修护

dev
huwei 2026-07-07 15:58:05 +08:00
parent 4950605bff
commit 862b434777
14 changed files with 13879 additions and 55 deletions

View File

@ -10,7 +10,7 @@ module.exports = {
javaGitBranch: "<branch-name>", javaGitBranch: "<branch-name>",
// 本地联调 safetyEval-servicecontext-path: /safetyEval默认端口 8095 // 本地联调 safetyEval-servicecontext-path: /safetyEval默认端口 8095
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
API_HOST: "http://192.168.0.152", API_HOST: "http://127.0.0.1",
}, },
production: { production: {
// 应用后端分支名称,部署上线需要 // 应用后端分支名称,部署上线需要

13664
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,30 @@ function parseAttachmentUrls(urls, defaultName = "附件.pdf") {
if (!urls) { if (!urls) {
return []; return [];
} }
return String(urls) 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(",") .split(",")
.map((url) => url.trim()) .map((url) => url.trim())
.filter(Boolean) .filter(Boolean)

View File

@ -1,4 +1,5 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import { apiPostDelete, safeAction } from "../enterpriseInfo/http";
export const staffChangeLogStaffList = declareRequest( export const staffChangeLogStaffList = declareRequest(
"staffChangeLogLoading", "staffChangeLogLoading",
@ -14,7 +15,7 @@ export const staffChangeLogList = declareRequest(
export const staffChangeLogRemove = declareRequest( export const staffChangeLogRemove = declareRequest(
"staffChangeLogLoading", "staffChangeLogLoading",
"Post > @/safetyEval/org-personnel-change/delete", safeAction(async ({ id }) => apiPostDelete("/safetyEval/org-personnel-change/delete", id)),
); );
export const staffResignationAudit = declareRequest( export const staffResignationAudit = declareRequest(

View File

@ -1,4 +1,5 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import { apiPost, apiPostDelete, safeAction } from "../enterpriseInfo/http";
export const staffInfoList = declareRequest( export const staffInfoList = declareRequest(
"staffInfoLoading", "staffInfoLoading",
@ -24,10 +25,13 @@ export const staffInfoEdit = declareRequest(
export const staffInfoRemove = declareRequest( export const staffInfoRemove = declareRequest(
"staffInfoLoading", "staffInfoLoading",
"Post > @/safetyEval/org-personnel/delete", safeAction(async ({ id }) => apiPostDelete("/safetyEval/org-personnel/delete", id)),
); );
export const staffInfoResetPassword = declareRequest( export const staffInfoResetPassword = declareRequest(
"staffInfoLoading", "staffInfoLoading",
"Get > /safetyEval/org-personnel/reset-password", safeAction(async ({ id }) => {
const url = `/safetyEval/org-personnel/reset-password?id=${encodeURIComponent(id)}`;
return apiPost(url, {});
}),
); );

View File

@ -1,18 +1,24 @@
import { Image, Modal } from "antd"; import { Image, Modal } from "antd";
function resolveUrl(raw) {
if (!raw) return "";
const u = String(raw);
if (/^https?:\/\//i.test(u)) return u;
const base = window.fileUrl || "";
return base ? `${base}${u}` : u;
}
export function getFilePreviewType(url = "", fileName = "") { export function getFilePreviewType(url = "", fileName = "") {
const hint = `${fileName || ""} ${url || ""}`.toLowerCase(); const hint = `${fileName || ""} ${url || ""}`.toLowerCase();
if (/\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(hint)) { if (/\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(hint)) {
return "image"; return "image";
} }
if (/\.pdf(\?|#|$)/i.test(hint)) { return "other";
return "pdf";
}
return "unknown";
} }
export function FilePreviewContent({ url, fileName, style }) { export function FilePreviewContent({ url, fileName, style }) {
if (!url) { const resolved = resolveUrl(url);
if (!resolved) {
return ( return (
<div style={{ <div style={{
minHeight: 120, minHeight: 120,
@ -29,24 +35,16 @@ export function FilePreviewContent({ url, fileName, style }) {
); );
} }
const type = getFilePreviewType(url, fileName); const type = getFilePreviewType(resolved, fileName);
if (type === "image") { if (type === "image") {
return <Image src={url} alt={fileName || "预览"} style={{ maxHeight: 480, ...style }} />; return <Image src={resolved} alt={fileName || "预览"} style={{ maxHeight: 480, ...style }} />;
}
if (type === "pdf") {
return (
<iframe
title={fileName || "PDF 预览"}
src={url}
style={{ width: "100%", height: 480, border: "none", ...style }}
/>
);
} }
return ( return (
<div style={{ <div style={{
minHeight: 120, minHeight: 120,
display: "flex", display: "flex",
flexDirection: "column",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
background: "#fafafa", background: "#fafafa",
@ -54,7 +52,8 @@ export function FilePreviewContent({ url, fileName, style }) {
...style, ...style,
}} }}
> >
暂不支持在线预览请使用新窗口打开 <span style={{ marginBottom: 8 }}>此文件类型不支持内嵌预览</span>
<span>请点击下方新窗口打开按钮在浏览器中查看</span>
</div> </div>
); );
} }
@ -67,6 +66,9 @@ export default function FilePreviewModal({
width = 720, width = 720,
onCancel, onCancel,
}) { }) {
const resolved = resolveUrl(url);
const isImageFile = resolved && /\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(resolved.toLowerCase());
return ( return (
<Modal <Modal
open={open} open={open}
@ -74,11 +76,11 @@ export default function FilePreviewModal({
title={title} title={title}
width={width} width={width}
cancelText="关闭" cancelText="关闭"
okText={url ? "新窗口打开" : "关闭"} okText={resolved ? (isImageFile ? "新窗口打开" : "重新打开") : "关闭"}
onCancel={onCancel} onCancel={onCancel}
onOk={() => { onOk={() => {
if (url) { if (resolved) {
window.open(url, "_blank"); window.open(resolved, "_blank");
} }
onCancel?.(); onCancel?.();
}} }}
@ -98,7 +100,7 @@ export default function FilePreviewModal({
{fileName} {fileName}
</div> </div>
)} )}
<FilePreviewContent url={url} fileName={fileName} /> <FilePreviewContent url={resolved} fileName={fileName} />
</div> </div>
)} )}
</Modal> </Modal>

View File

@ -4,20 +4,29 @@ import { Button, Image } from "antd";
const isImage = (url) => const isImage = (url) =>
/\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || ""); /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
function resolveUrl(raw) {
if (!raw) return "";
const u = String(raw);
if (/^https?:\/\//i.test(u)) return u;
const base = window.fileUrl || "";
return base ? `${base}${u}` : u;
}
export default function PreviewUrlButton({ url, children = "预览", ...btnProps }) { export default function PreviewUrlButton({ url, children = "预览", ...btnProps }) {
const [previewImage, setPreviewImage] = useState(""); const [previewImage, setPreviewImage] = useState("");
const resolved = resolveUrl(url);
return ( return (
<> <>
<Button <Button
type="link" type="link"
size="small" size="small"
disabled={!url} disabled={!resolved}
onClick={() => { onClick={() => {
if (isImage(url)) { if (isImage(resolved)) {
setPreviewImage(url); setPreviewImage(resolved);
} else { } else {
window.open(url); window.open(resolved, "_blank");
} }
}} }}
{...btnProps} {...btnProps}

View File

@ -72,7 +72,12 @@ function PersonnelChangePage(props) {
okText: "是", okText: "是",
cancelText: "否", cancelText: "否",
onOk: async () => { onOk: async () => {
const res = await props.staffInfoRemove?.({ id: record.staffId || record.id }); const id = record.id ?? record.staffId;
if (!id) {
message.error("缺少人员ID无法删除");
return;
}
const res = await props.staffInfoRemove?.({ id });
if (res?.success !== false) { if (res?.success !== false) {
message.success("删除成功"); message.success("删除成功");
handleSearch(); handleSearch();
@ -97,6 +102,10 @@ function PersonnelChangePage(props) {
}; };
const openResignView = async (record) => { const openResignView = async (record) => {
if (!record?.resignApplyId) {
message.warning("该人员暂无离职申请记录");
return;
}
try { try {
const res = await props.staffResignationInfo({ id: record.resignApplyId }); const res = await props.staffResignationInfo({ id: record.resignApplyId });
setResignInfo(res?.data || {}); setResignInfo(res?.data || {});
@ -183,7 +192,7 @@ function PersonnelChangePage(props) {
render: (_, record) => ( render: (_, record) => (
<TableAction> <TableAction>
<Button type="link" size="small" onClick={() => openResignView(record)}> <Button type="link" size="small" onClick={() => openResignView(record)}>
查看 查看离职状态
</Button> </Button>
{record.resignApplyId && Number(record.resignAuditStatus) === 0 && ( {record.resignApplyId && Number(record.resignAuditStatus) === 0 && (
<Button type="link" size="small" onClick={() => openResignAudit(record)}> <Button type="link" size="small" onClick={() => openResignAudit(record)}>

View File

@ -86,12 +86,12 @@ function StaffCertificatePage(props) {
<ControlWrapper.Input label="证书作业类别名称" placeholder="请输入证书作业类别名称" allowClear /> <ControlWrapper.Input label="证书作业类别名称" placeholder="请输入证书作业类别名称" allowClear />
</Form.Item>, </Form.Item>,
]} ]}
onReset={() => { onReset={(values) => {
router.query = { ...router.query, current: 1, size: 10 }; router.query = { ...values, current: 1, size: 10 };
handleSearch(); handleSearch();
}} }}
onFinish={() => { onFinish={(values) => {
router.query = { ...router.query, current: 1, size: 10 }; router.query = { ...values, current: 1, size: 10 };
handleSearch(); handleSearch();
}} }}
/> />

View File

@ -1,5 +1,5 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Form, Input, message, Modal, Row, Col, Select, Table, Upload } from "antd"; import { Button, DatePicker, Form, Image, Input, message, Modal, Row, Col, Select, Table, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons"; import { UploadOutlined } from "@ant-design/icons";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -250,10 +250,13 @@ function StaffFormModal({
const [deptPositionOptions, setDeptPositionOptions] = useState([]); const [deptPositionOptions, setDeptPositionOptions] = useState([]);
const [positionLoading, setPositionLoading] = useState(false); const [positionLoading, setPositionLoading] = useState(false);
const [uploadFileList, setUploadFileList] = useState([]); const [uploadFileList, setUploadFileList] = useState([]);
const [previewImage, setPreviewImage] = useState("");
const watchedDeptId = Form.useWatch("deptId", form); const watchedDeptId = Form.useWatch("deptId", form);
const wasOpenRef = useRef(false); const wasOpenRef = useRef(false);
const inflightDeptRef = useRef(null); const inflightDeptRef = useRef(null);
const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
const loadPositionsByDept = async (deptId) => { const loadPositionsByDept = async (deptId) => {
const id = deptId; const id = deptId;
if (!id) { if (!id) {
@ -287,6 +290,7 @@ function StaffFormModal({
form.resetFields(); form.resetFields();
setDeptPositionOptions([]); setDeptPositionOptions([]);
setUploadFileList([]); setUploadFileList([]);
setPreviewImage("");
form.setFieldsValue({ form.setFieldsValue({
registerEngineerFlag: 2, registerEngineerFlag: 2,
}); });
@ -295,6 +299,7 @@ function StaffFormModal({
if (!open) { if (!open) {
setDeptPositionOptions([]); setDeptPositionOptions([]);
setUploadFileList([]); setUploadFileList([]);
setPreviewImage("");
} }
wasOpenRef.current = open; wasOpenRef.current = open;
}, [open, currentId, form]); }, [open, currentId, form]);
@ -333,6 +338,7 @@ function StaffFormModal({
form.resetFields(); form.resetFields();
setDeptPositionOptions([]); setDeptPositionOptions([]);
setUploadFileList([]); setUploadFileList([]);
setPreviewImage("");
onCancel(); onCancel();
}; };
@ -364,7 +370,7 @@ function StaffFormModal({
educationLevelCode: values.educationLevelName, educationLevelCode: values.educationLevelName,
educationLevelName: values.educationLevelName, educationLevelName: values.educationLevelName,
proofMaterialUrl: Array.isArray(values.proofMaterialUrl) proofMaterialUrl: Array.isArray(values.proofMaterialUrl)
? values.proofMaterialUrl.map((f) => f.url || f.response?.url).filter(Boolean).join(",") ? values.proofMaterialUrl.map((f) => f.url || f.response?.data?.url).filter(Boolean).join(",")
: "", : "",
}; };
if (payload.birthDate && typeof payload.birthDate !== "string") { if (payload.birthDate && typeof payload.birthDate !== "string") {
@ -507,21 +513,46 @@ function StaffFormModal({
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={24}> <Col span={24}>
<Form.Item name="proofMaterialUrl" label="申报专业能力证明材料" valuePropName="fileList" getValueFromEvent={(e) => { <Form.Item
if (Array.isArray(e)) return e; name="proofMaterialUrl"
return e?.fileList; label="申报专业能力证明材料"
}}> valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) return e;
return e?.fileList;
}}
>
<Upload <Upload
action="/api/upload" action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`}
headers={{ token: sessionStorage.getItem("token") }}
maxCount={5} maxCount={5}
fileList={uploadFileList} fileList={uploadFileList}
onChange={(info) => { onChange={(info) => {
setUploadFileList(info.fileList); setUploadFileList(info.fileList);
}} }}
onPreview={(file) => {
const src = file.url || file.response?.data?.url || file.thumbUrl;
if (!src) return;
if (isImage(src)) {
setPreviewImage(src);
} else {
window.open(src, "_blank");
}
}}
> >
<Button icon={<UploadOutlined />}>上传文件</Button> <Button icon={<UploadOutlined />}>上传文件</Button>
</Upload> </Upload>
</Form.Item> </Form.Item>
<Image
style={{ display: "none" }}
preview={{
visible: !!previewImage,
src: previewImage,
onVisibleChange: (visible) => {
if (!visible) setPreviewImage("");
},
}}
/>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="currentAddress" label="现住地址"> <Form.Item name="currentAddress" label="现住地址">

View File

@ -14,6 +14,7 @@ import {
} from "antd"; } from "antd";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction"; import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { UploadOutlined } from "@ant-design/icons"; import { UploadOutlined } from "@ant-design/icons";
import PreviewUrlButton from "~/components/PreviewUrlButton";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm"; import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
@ -297,9 +298,17 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
> >
<Upload <Upload
action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`} action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`}
showUploadList={false}
maxCount={1} maxCount={1}
accept=".pdf" accept=".pdf"
showUploadList={{
showPreviewIcon: true,
showRemoveIcon: true,
showDownloadIcon: false,
}}
onPreview={() => {
const url = form.getFieldValue("reportFileUrl");
if (url) window.open(url);
}}
onChange={(info) => { onChange={(info) => {
if (info.file.status === "uploading") { if (info.file.status === "uploading") {
setUploading(true); setUploading(true);
@ -308,11 +317,19 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
if (info.file.status === "done") { if (info.file.status === "done") {
const data = info.file.response?.data; const data = info.file.response?.data;
if (data) { if (data) {
form.setFieldsValue({ reportFileUrl: data.url || data }); const url = data.url || data;
form.setFieldsValue({ reportFileUrl: url });
} else {
message.error("上传失败,未获取到文件地址");
} }
} else if (info.file.status === "error") {
message.error(`${info.file.name} 上传失败`);
} }
} }
}} }}
onRemove={() => {
form.setFieldsValue({ reportFileUrl: "" });
}}
> >
<Button loading={uploading} icon={<UploadOutlined />}> <Button loading={uploading} icon={<UploadOutlined />}>
上传文件 上传文件
@ -371,7 +388,13 @@ function ViewModal({ open, currentId, requestDetail, onCancel }) {
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="备注">{info.remark}</Descriptions.Item> <Descriptions.Item label="备注">{info.remark}</Descriptions.Item>
<Descriptions.Item label="离职通知报告"> <Descriptions.Item label="离职通知报告">
{info.reportFileUrl || "-"} {info.reportFileUrl ? (
<PreviewUrlButton url={info.reportFileUrl}>
查看文件
</PreviewUrlButton>
) : (
"-"
)}
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Modal> </Modal>

View File

@ -30,15 +30,20 @@ const menuItems = [
icon: <DashboardOutlined />, icon: <DashboardOutlined />,
children: [ children: [
{ {
key: "/safetyEval/container/Supervision/Dashboard", key: "/safetyEval/container/Institution/Dashboard",
label: "机构端首页", label: "机构端首页",
icon: <DashboardOutlined />, icon: <DashboardOutlined />,
}, },
{ {
key: "/safetyEval/container/Supervision/Cockpit", key: "/safetyEval/container/Supervision/Dashboard",
label: "监管端首页", label: "监管端首页",
icon: <PieChartOutlined />, icon: <PieChartOutlined />,
}, },
{
key: "/safetyEval/container/Supervision/Cockpit",
label: "监管端驾驶舱",
icon: <PieChartOutlined />,
},
{ {
key: "/safetyEval/container/Supervision/BasicInfo", key: "/safetyEval/container/Supervision/BasicInfo",
label: "基础信息管理", label: "基础信息管理",

View File

@ -22,6 +22,18 @@ function parseQueryId(location) {
return new URLSearchParams(search).get("id"); return new URLSearchParams(search).get("id");
} }
function resolvePreviewUrl(raw) {
if (!raw) return "";
const u = String(raw);
if (/^https?:\/\//i.test(u)) return u;
const base = window.fileUrl || "";
return base ? `${base}${u}` : u;
}
function isImageUrl(url) {
return /\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(String(url || "").toLowerCase());
}
function MaterialsTab({ materialGroups, loading, onPreview }) { function MaterialsTab({ materialGroups, loading, onPreview }) {
if (loading) { if (loading) {
return <Spin />; return <Spin />;
@ -224,7 +236,18 @@ function RegisteredOrgDetailPage(props) {
<MaterialsTab <MaterialsTab
materialGroups={materialGroups} materialGroups={materialGroups}
loading={extrasLoading} loading={extrasLoading}
onPreview={setFilePreview} onPreview={(record) => {
const url = resolvePreviewUrl(record?.previewUrl);
if (!url) {
message.warning("该材料暂无可预览的附件");
return;
}
if (isImageUrl(url)) {
setFilePreview(record);
} else {
window.open(url, "_blank");
}
}}
/> />
), ),
}, },
@ -252,7 +275,7 @@ function RegisteredOrgDetailPage(props) {
open={!!filePreview} open={!!filePreview}
title="文件预览" title="文件预览"
fileName={filePreview?.name} fileName={filePreview?.name}
url={filePreview?.previewUrl} url={resolvePreviewUrl(filePreview?.previewUrl)}
onCancel={() => setFilePreview(null)} onCancel={() => setFilePreview(null)}
/> />

View File

@ -11,23 +11,53 @@ export function resolveUploadFileId(_files) {
return DEFAULT_UPLOAD_FILE_URL; return DEFAULT_UPLOAD_FILE_URL;
} }
/** 多附件:逗号拼接 URL */ /** 多附件:序列化为 JSON 数组字符串,与后端格式保持一致 */
export function resolveUploadFileIds(files) { export function resolveUploadFileIds(files) {
if (!files?.length) { if (!files?.length) {
return undefined; return undefined;
} }
const urls = files.map((file) => file?.url || file?.response?.url).filter(Boolean); const valid = files
if (!urls.length) { .filter((file) => file?.url)
.map((file) => ({
url: file.url,
uid: file.uid || undefined,
name: file.name || file.fileName || "附件",
status: file.status || "done",
}));
if (!valid.length) {
return DEFAULT_UPLOAD_FILE_URL; return DEFAULT_UPLOAD_FILE_URL;
} }
return urls.join(","); return JSON.stringify(valid);
} }
export function parseUploadFileList(urls, defaultName = "附件.pdf") { export function parseUploadFileList(urls, defaultName = "附件.pdf") {
if (!urls) { if (!urls) {
return []; return [];
} }
return String(urls) 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(",") .split(",")
.map((url) => url.trim()) .map((url) => url.trim())
.filter(Boolean) .filter(Boolean)