政策下载功能开发
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled

This commit is contained in:
冯辉
2026-07-08 21:32:44 +08:00
parent 0e17534c55
commit 0390807e6a
7 changed files with 127 additions and 4 deletions

View File

@@ -17,8 +17,8 @@ export default {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
'/api/': {
// 要代理的地址
target: localBackendUrl,
// target: 'http://39.98.44.136:6024/api/shihezi/', // 线上环境
// target: localBackendUrl, // 本地环境
target: 'http://39.98.44.136:6024/api/shihezi/', // 线上环境
// target: 'http://wykj.cdwsx.com/api',// 后端
// target: 'http://ks.zhaopinzao8dian.com/api/ks',
// 配置了这个可以从 http 代理到 https

View File

@@ -18,7 +18,7 @@ import {
} from '@ant-design/icons';
import { history } from '@umijs/max';
import JobPortalHeader from '@/components/JobPortalHeader';
import { getPortalPolicyList } from '@/services/cms/policyInfo';
import { getPortalPolicyList, exportPolicy } from '@/services/cms/policyInfo';
import { getDictValueEnum } from '@/services/system/dict';
import type { DictValueEnumObj } from '@/components/DictTag';
import './index.less';
@@ -34,7 +34,9 @@ const PolicyListPage: React.FC = () => {
const [searchValue, setSearchValue] = useState('');
const [keyword, setKeyword] = useState('');
const [selectedPolicyTag, setSelectedPolicyTag] = useState<string>('__recommended__');
const [selectedPolicyCategory, setSelectedPolicyCategory] = useState<string>('');
const [userTypeEnum, setUserTypeEnum] = useState<DictValueEnumObj>({});
const [policyCategoryEnum, setPolicyCategoryEnum] = useState<DictValueEnumObj>({});
/** 从 localStorage 读取当前用户的 personnel type */
const currentUserType = (() => {
@@ -56,6 +58,9 @@ const PolicyListPage: React.FC = () => {
setSelectedPolicyTag('');
}
});
getDictValueEnum('policy_category', false, true).then((data) => {
setPolicyCategoryEnum(data);
});
}, []);
/** 将字典枚举转为标签数组,按 value 排序 */
@@ -63,6 +68,11 @@ const PolicyListPage: React.FC = () => {
.map((item) => ({ label: item.label, value: item.value }))
.sort((a, b) => Number(a.value) - Number(b.value));
/** 将政策分类字典转为标签数组 */
const policyCategoryTags = Object.values(policyCategoryEnum)
.map((item) => ({ label: item.label, value: String(item.value) }))
.sort((a, b) => Number(a.value) - Number(b.value));
/** 将内部标记转为实际 API 参数 */
const resolvePolicyTag = (tag: string): string | undefined => {
if (tag === '__recommended__') {
@@ -79,6 +89,7 @@ const PolicyListPage: React.FC = () => {
pageSize,
searchValue: keyword || undefined,
policyTag: resolvePolicyTag(selectedPolicyTag),
policyCategory: selectedPolicyCategory || undefined,
});
if (response?.code === 200) {
setList(response.rows || []);
@@ -92,7 +103,7 @@ const PolicyListPage: React.FC = () => {
} finally {
setLoading(false);
}
}, [pageNum, pageSize, keyword, selectedPolicyTag]);
}, [pageNum, pageSize, keyword, selectedPolicyTag, selectedPolicyCategory]);
useEffect(() => {
fetchList();
@@ -113,6 +124,16 @@ const PolicyListPage: React.FC = () => {
}
};
/** 点击政策分类标签:选中/取消选中 */
const handlePolicyCategoryClick = (tagValue: string) => {
setPageNum(1);
if (selectedPolicyCategory === tagValue) {
setSelectedPolicyCategory('');
} else {
setSelectedPolicyCategory(tagValue);
}
};
const handleItemClick = (item: API.PolicyInfo.PolicyInfoItem) => {
history.push(`/job-portal/policy/detail?id=${item.id}`);
};
@@ -131,6 +152,31 @@ const PolicyListPage: React.FC = () => {
document.body.removeChild(link);
};
/** 导出政策 */
const handleExport = async (e: React.MouseEvent, item: API.PolicyInfo.PolicyInfoItem) => {
e.stopPropagation();
try {
const blob = await exportPolicy(item.id);
// 如果后端返回 JSON 错误(如 401blob 可能仍是 JSON 格式
if (blob.type === 'application/json') {
const text = await blob.text();
const err = JSON.parse(text);
message.error(err.msg || '导出失败');
return;
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${item.zcmc || '政策'}.docx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (err: any) {
message.error(err?.message || '导出失败');
}
};
return (
<div className="policy-page">
<JobPortalHeader showSearch={false} showHotJobs={false} />
@@ -188,6 +234,24 @@ const PolicyListPage: React.FC = () => {
</div>
</div>
)}
{policyCategoryTags.length > 0 && (
<div className="policy-tag-filter">
<span className="policy-tag-filter__label"></span>
<div className="policy-tag-filter__tags">
{policyCategoryTags.map((tag) => (
<Tag
key={tag.value}
className={`policy-tag-filter__item ${
selectedPolicyCategory === tag.value ? 'policy-tag-filter__item--active' : ''
}`}
onClick={() => handlePolicyCategoryClick(tag.value)}
>
{tag.label}
</Tag>
))}
</div>
</div>
)}
{list.length > 0 ? (
<>
<List
@@ -219,6 +283,14 @@ const PolicyListPage: React.FC = () => {
)}
</div>
</div>
<span
className="policy-download-btn policy-export-btn"
title="导出政策"
onClick={(e) => handleExport(e, item)}
>
<DownloadOutlined />
<span className="policy-download-btn__text"></span>
</span>
{item.fileUrl && (
<span
className="policy-download-btn"

View File

@@ -19,6 +19,7 @@ const DetailModal: React.FC<DetailModalProps> = ({ open, data, onCancel }) => {
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="政策名称" span={2}>{data?.zcmc}</Descriptions.Item>
<Descriptions.Item label="政策类型">{data?.zclx}</Descriptions.Item>
<Descriptions.Item label="政策分类">{data?.policyCategory || '-'}</Descriptions.Item>
<Descriptions.Item label="政策级别">{data?.zcLevel}</Descriptions.Item>
<Descriptions.Item label="发文单位">{data?.sourceUnit}</Descriptions.Item>
<Descriptions.Item label="受理单位">{data?.acceptUnit}</Descriptions.Item>

View File

@@ -23,11 +23,15 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const [userTypeEnum, setUserTypeEnum] = useState<DictValueEnumObj>({});
const [policyCategoryEnum, setPolicyCategoryEnum] = useState<DictValueEnumObj>({});
useEffect(() => {
getDictValueEnum('user_type', false, true).then((data) => {
setUserTypeEnum(data);
});
getDictValueEnum('policy_category', false, true).then((data) => {
setPolicyCategoryEnum(data);
});
}, []);
useEffect(() => {
@@ -59,6 +63,13 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
}
}, [open, values, form]);
// 字典选项加载完成后,重新回填 policyCategory 确保 Select 正确显示
useEffect(() => {
if (open && values?.policyCategory && Object.keys(policyCategoryEnum).length > 0) {
form.setFieldsValue({ policyCategory: values.policyCategory });
}
}, [open, policyCategoryEnum, values, form]);
const handleUpload = async (file: File) => {
setUploading(true);
try {
@@ -113,6 +124,11 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
value: item.value,
}));
const policyCategoryOptions = Object.values(policyCategoryEnum).map((item) => ({
label: item.label,
value: item.value,
}));
return (
<Modal
title={values ? '编辑政策' : '新增政策'}
@@ -139,6 +155,15 @@ const EditModal: React.FC<EditModalProps> = ({ open, values, onCancel, onSubmit
<Input placeholder="请输入政策级别" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="policyCategory" label="政策分类">
<Select
allowClear
placeholder="请选择政策分类"
options={policyCategoryOptions}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="policyTag" label="人员类型">
<Select

View File

@@ -24,11 +24,15 @@ const PolicyMgmt: React.FC = () => {
const [currentRow, setCurrentRow] = useState<API.PolicyInfo.PolicyInfoItem>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [userTypeEnum, setUserTypeEnum] = useState<DictValueEnumObj>({});
const [policyCategoryEnum, setPolicyCategoryEnum] = useState<DictValueEnumObj>({});
useEffect(() => {
getDictValueEnum('user_type', false, true).then((data) => {
setUserTypeEnum(data);
});
getDictValueEnum('policy_category', false, true).then((data) => {
setPolicyCategoryEnum(data);
});
}, []);
const handleDelete = async (ids: string) => {
@@ -103,6 +107,16 @@ const PolicyMgmt: React.FC = () => {
);
},
},
{
title: '政策分类',
dataIndex: 'policyCategory',
hideInSearch: true,
width: 120,
render: (_, record) => {
if (!record.policyCategory) return '-';
return <DictTag enums={policyCategoryEnum} value={record.policyCategory} />;
},
},
{
title: '发文单位',
dataIndex: 'sourceUnit',

View File

@@ -71,3 +71,12 @@ export async function uploadPolicyFile(file: File) {
data: formData,
});
}
// 导出/下载政策
export async function exportPolicy(id: number | string) {
return request('/api/cms/policyInfo/exportUtil', {
method: 'GET',
params: { id },
responseType: 'blob',
});
}

View File

@@ -25,6 +25,7 @@ declare namespace API.PolicyInfo {
export interface PolicyInfoItem {
id: number;
policyTag?: string;
policyCategory?: string;
zcmc: string;
zclx: string;
zcLevel: string;
@@ -51,5 +52,6 @@ declare namespace API.PolicyInfo {
pageSize?: number;
searchValue?: string;
policyTag?: string;
policyCategory?: string;
}
}