| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- /*
- * @Author: code4eat awesomedema@gmail.com
- * @Date: 2023-03-03 11:30:33
- * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-02-28 15:18:53
- * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
- import { createFromIconfontCN } from '@ant-design/icons';
- import { ModalForm, ProFormDependency, ProFormInstance, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
- import { ActionType, ProColumns } from '@ant-design/pro-table';
- import { Input, message, Popconfirm, Switch, Tooltip } from 'antd';
- import { useEffect, useRef, useState } from 'react';
- import 'moment/locale/zh-cn';
- import { addData, applyBatchItemValueReq, delData, editData, generateTableDataReq, getCurrentHospAlldeps, getTableList } from './service';
- import './style.less';
- import React from 'react';
- import generateTableData from './generateTableData';
- import { useModel } from 'umi';
- import KCTable from '@/components/kcTable';
- const IconFont = createFromIconfontCN({
- scriptUrl: '',
- });
- //填报周期
- export const DATAFILL_PERIODTYPE = [
- { label: '月度', value: 1 },
- { label: '季度', value: 2 },
- { label: '年度', value: 3 },
- { label: '变动时', value: 4 },
- ];
- //填报主体类型
- export const DATAFILL_MAINTYPE = [
- { label: '职能部门', value: 1 },
- { label: '相关科室', value: 2 },
- ];
- //填报维度
- export const DATAFILL_DIMENSIONTYPE = [
- { label: '按全院 ', value: 1 },
- { label: '按科室', value: 2 },
- ];
- type Data = {
- // 键为科室/部门编码,保持字符串类型
- [key: string]: string;
- };
- type LabelValue = {
- label: string;
- // value 需保持为字符串编码,避免多选时因 NaN 造成同值冲突
- value: string;
- };
- // 全局简单缓存,避免每次打开不同弹窗都重复请求
- let cachedDeptOptions: LabelValue[] | null = null;
- export function transformToLabelValue(data: Data): LabelValue[] {
- const result: LabelValue[] = [];
- for (const key in data) {
- if (data.hasOwnProperty(key)) {
- result.push({
- label: data[key],
- value: key,
- });
- }
- }
- return result;
- }
- export const searchTree = (tree: any[], searchTerm: string) => {
- const searchTermLower = searchTerm.toLowerCase();
- function searchNode(node: any) {
- const matchedChildren: any[] = [];
- if (node.children && node.children.length > 0) {
- node.children.forEach((child: any) => {
- const matchedChild = searchNode(child);
- if (matchedChild) {
- matchedChildren.push(matchedChild);
- }
- });
- }
- if (node.responsibilityName.toLowerCase().includes(searchTermLower) || (node.responsibilityCode && node.responsibilityCode.toLowerCase().includes(searchTermLower))) {
- return {
- ...node,
- children: matchedChildren.length > 0 ? matchedChildren : node.children,
- };
- } else if (matchedChildren.length > 0) {
- return {
- ...node,
- children: matchedChildren,
- };
- }
- return null;
- }
- return tree.map((rootNode) => searchNode(rootNode)).filter((node) => node !== null);
- };
- const defaultYear = new Date().getFullYear();
- let table_columns: any[] = [];
- let total_tableDataSource: any[] = [];
- export default function FillingMana() {
- const { initialState, setInitialState } = useModel('@@initialState');
- const [tableDataFilterParams, set_tableDataFilterParams] = useState<{
- current: number;
- pageSize: number;
- name?: string;
- }>({
- current: 1,
- pageSize: 10
- });
- const [tableDataSearchKeywords, set_tableDataSearchKeywords] = useState<string>('');
- const tableRef = useRef<ActionType>();
- const detailTableRef = useRef<ActionType>();
- const formRef = useRef<ProFormInstance>();
- const [tableDataSource, set_tableDataSource] = useState<any[]>([]);
- const [tableColumns, set_tableColumns] = useState<ProColumns[]>([]);
- const [currentEditRow, set_currentEditRow] = useState<any>(undefined);
- const [actionType, set_actionType] = useState<'NORMAL' | 'DETAIL' | undefined>('NORMAL');
- const [currentEditTableRow, set_currentEditTableRow] = useState<any>(undefined);
- const [currentYear, set_currentYear] = useState(defaultYear);
- const [isFirstLoad, setIsFirstLoad] = useState(true);
- const tableColumns_default: ProColumns[] = [
- {
- title: '操作',
- key: 'option',
- fixed: 'right',
- width: 80,
- valueType: 'option',
- render: (_: any, record: any) => {
- return (
- <a
- key={'edit'}
- onClick={() => {
- set_currentEditTableRow(record);
- }}
- >
- 编辑
- </a>
- );
- },
- },
- ];
- const columns: ProColumns[] = [
- {
- title: '填报项目',
- ellipsis: true,
- dataIndex: 'name',
- },
- {
- title: '填报周期',
- ellipsis: true,
- dataIndex: 'periodType',
- renderText(code, record, index, action) {
- const item = DATAFILL_PERIODTYPE.find((item) => item.value === code);
- return item ? item.label : '';
- },
- },
- {
- title: '填报维度',
- ellipsis: true,
- dataIndex: 'dimensionType',
- renderText(code, record, index, action) {
- const item = DATAFILL_DIMENSIONTYPE.find((item) => item.value === code);
- return item ? item.label : '';
- },
- },
- {
- title: '填报主体类型',
- ellipsis: true,
- dataIndex: 'type',
- renderText(code, record, index, action) {
- const item = DATAFILL_MAINTYPE.find((item) => item.value === code);
- return item ? item.label : '';
- },
- },
- {
- title: '填报主体',
- ellipsis: true,
- dataIndex: 'typeName',
- renderText(_, record) {
- // 根据维度类型从 fillDeptList 提取名称并用逗号拼接
- const list = Array.isArray(record?.fillDeptList) ? record.fillDeptList : [];
- const names = list
- .map((item: any) => {
- if (record?.dimensionType === 2) {
- return item?.dimensionTypeName ?? item?.departmentName ?? '';
- }
- // 默认按全院/维度类型为 1 时取 departmentName
- return item?.departmentName ?? item?.dimensionTypeName ?? '';
- })
- .filter((s: string) => s);
- return names.join(',');
- },
- },
- {
- title: '填报单位',
- ellipsis: true,
- dataIndex: 'unit',
- },
- {
- title: '填报说明',
- ellipsis: true,
- dataIndex: 'description',
- },
- {
- title: '启用',
- width: 60,
- ellipsis: true,
- dataIndex: 'status',
- renderText(num, record) {
- return (
- <Switch
- size="small"
- checked={num}
- onChange={(bool) =>
- updateTable(
- {
- ...record,
- status: bool ? 1 : 0,
- fillDeptList: record?.fillDeptList.map((a: any) => ({ value: a.departmentCode, label: a.departmentName })),
- relateDeptList: record?.relateDeptList.map((a: any) => ({ value: a.departmentCode, label: a.departmentName })),
- },
- 'EDIT',
- )
- }
- />
- );
- },
- },
- {
- title: '操作',
- key: 'option',
- fixed: 'right',
- width: 120,
- valueType: 'option',
- render: (_: any, record: any) => {
- const { applyStatus } = record;
- return [
- <a
- key={'detail'}
- onClick={() => {
- set_currentEditRow(record);
- }}
- >
- 详情
- </a>,
- <UpDataActBtn record={record} type={'EDIT'} />,
- <Popconfirm title="是否确认删除?" key="del" onConfirm={() => delTableData(record)}>
- <a>删除</a>
- </Popconfirm>,
- ];
- },
- },
- ];
- const getGenerateTableData = async () => {
- const resp = await generateTableDataReq({ fillItemCode: currentEditRow?.code, dataYear: currentYear });
- if (resp) {
- const { itemTitles, itemDatas } = resp;
- const { columns, dataSource } = generateTableData(itemTitles, itemDatas, [0]);
- table_columns = columns;
- total_tableDataSource = dataSource;
- set_tableColumns(columns);
- set_tableDataSource(dataSource);
- }
- };
- const getTableData = async (params: any) => {
- const { current = 1, pageSize = 10, name } = params;
- const resp = await getTableList({
- current,
- pageSize,
- name
- });
- if (resp) {
- return {
- data: resp.list,
- success: true,
- total: resp.totalCount
- };
- }
- return {
- data: [],
- success: false,
- total: 0
- };
- };
- const delTableData = async (record: any) => {
- const resp = await delData(record.code);
- if (resp) {
- tableRef.current?.reload();
- }
- };
- const updateTable = async (data: any, type: 'ADD' | 'EDIT') => {
- try {
- const result = {
- name: data.name,
- type: data.type,
- dimensionType: data.type == 2 ? 2 : data.dimensionType,
- periodType: data.periodType,
- unit: data.unit,
- status: type == 'ADD' ? null : data.status,
- description: data.description,
- fillDeptList:
- data.fillDeptList instanceof Array
- ? data.fillDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label }))
- : [{ departmentCode: data.fillDeptList.value, departmentName: data.fillDeptList.label }],
- relateDeptList: data.type != 2 && data.dimensionType != 1 && data.relateDeptList ? data.relateDeptList.map((a: any) => ({ departmentCode: a.value, departmentName: a.label })) : [],
- };
- if (type == 'ADD') {
- const resp = await addData(result);
- if (resp) {
- tableRef.current?.reload();
- return true;
- }
- }
- if (type == 'EDIT') {
- const resp = await editData({ code: data.code, ...result });
- if (resp) {
- tableRef.current?.reload();
- }
- return true;
- }
- } catch (error) {
- console.log({ 'updateTable error': error });
- }
- return false;
- };
- const handleTableChange = (pagination: any) => {
- set_tableDataFilterParams({
- ...tableDataFilterParams,
- current: pagination.current,
- pageSize: pagination.pageSize
- });
- }
- const tableDataSearchHandle = (paramName: string) => {
- set_tableDataFilterParams({
- ...tableDataFilterParams,
- current: 1,
- [`${paramName}`]: tableDataSearchKeywords
- });
- }
- const detailtableDataSearchHandle = (paramName: string) => {
- const result = total_tableDataSource.filter((a: any) => a[`${paramName}`].indexOf(tableDataSearchKeywords) != -1);
- set_tableDataSource(result);
- };
- const UpDataActBtn = ({ record, type, sideHandle }: { record: any; type: 'EDIT' | 'ADD'; sideHandle?: { [key: string]: any } }) => {
- const actType = type;
- const ref = React.createRef<{ save: any }>();
- const [maintype, set_maintype] = useState(undefined);
- const [dimensionType, set_dimensionType] = useState(undefined);
- // 本地选项缓存,避免每次搜索触发远程过滤
- const [deptOptions, set_deptOptions] = useState<LabelValue[]>([]);
- const { commitResult } = sideHandle ? sideHandle : {};
- useEffect(() => {
- if (type !== undefined) {
- formRef.current?.setFieldValue('fillDeptList', []); // 清空填报主体
- }
- }, [maintype]);
- useEffect(() => {
- if (dimensionType !== undefined) {
- formRef.current?.setFieldValue('relateDeptList', []); // 清空填相关科室
- }
- }, [dimensionType]);
- // 弹窗打开时才加载选项,并使用文件级缓存
- const ensureDeptOptions = async () => {
- if (deptOptions && deptOptions.length > 0) return;
- if (cachedDeptOptions && cachedDeptOptions.length > 0) {
- set_deptOptions(cachedDeptOptions);
- return;
- }
- const depLists = await getCurrentHospAlldeps();
- const opts = depLists ? transformToLabelValue(depLists) : [];
- cachedDeptOptions = opts;
- set_deptOptions(opts);
- };
- return (
- <ModalForm
- title={`${type == 'EDIT' ? '编辑' : '新增'}`}
- width={352}
- formRef={formRef}
- initialValues={
- type == 'EDIT'
- ? {
- ...record,
- // 使用字符串编码,避免多选时因 NaN 造成同值冲突
- fillDeptList: record?.fillDeptList?.map((a: any) => ({ label: a.departmentName, value: a.departmentCode })) || [],
- relateDeptList: record?.relateDeptList?.map((a: any) => ({ label: a.departmentName, value: a.departmentCode })) || [],
- }
- : { periodType: 1, type: 1, dimensionType: 1 }
- }
- trigger={
- type == 'EDIT' ? (
- <a key="edit" onClick={() => ensureDeptOptions()}>编辑</a>
- ) : (
- <a className="add" onClick={() => ensureDeptOptions()}>新增</a>
- )
- }
- onFinish={(val) => {
- commitResult && commitResult();
- return updateTable(type == 'EDIT' ? { ...val, status: record.status } : { ...val }, type);
- }}
- modalProps={{ destroyOnClose: true }}
- colProps={{ span: 24 }}
- grid
- >
- {
- <>
- <ProFormText
- name="name"
- label="填报项目:"
- placeholder="请输入"
- // disabled={type == 'EDIT'}
- rules={[{ required: true, message: '填报项目不能为空!' }]}
- />
- <ProFormText name="code" label="填报编码:" hidden={type == 'ADD'} disabled={type == 'EDIT'} />
- <ProFormRadio.Group label="填报周期:" name="periodType" disabled={type == 'EDIT' && record.limitFlag} options={DATAFILL_PERIODTYPE} rules={[{ required: true }]} />
- <ProFormRadio.Group
- label="填报主体类型:"
- name="type"
- disabled={type == 'EDIT' && record.limitFlag}
- options={DATAFILL_MAINTYPE}
- rules={[{ required: true }]}
- fieldProps={{
- onChange(e) {
- set_maintype(e.target.value);
- },
- }}
- />
- <ProFormDependency name={['type']}>
- {({ type }) => {
- return (
- <>
- <ProFormSelect
- label="填报主体:"
- name="fillDeptList"
- disabled={actType == 'EDIT' && record.limitFlag}
- options={deptOptions}
- fieldProps={
- type === 2
- ? {
- mode: 'multiple', // 禁止自由录入,避免检索词生成新项
- maxTagCount: 'responsive',
- labelInValue: true,
- size: 'small',
- // 开启检索(按名称与编码匹配)
- showSearch: true,
- optionFilterProp: 'label',
- filterOption: (input: string, option: any) => {
- const label: string = (option?.label ?? '').toString();
- const value: string = (option?.value ?? '').toString();
- const kw = input.toLowerCase();
- return label.toLowerCase().includes(kw) || value.toLowerCase().includes(kw);
- },
- }
- : {
- labelInValue: true,
- size: 'small',
- // 开启检索(按名称与编码匹配)
- showSearch: true,
- optionFilterProp: 'label',
- filterOption: (input: string, option: any) => {
- const label: string = (option?.label ?? '').toString();
- const value: string = (option?.value ?? '').toString();
- const kw = input.toLowerCase();
- return label.toLowerCase().includes(kw) || value.toLowerCase().includes(kw);
- },
- }
- }
- rules={[{ required: true, message: '填报主体不能为空!' }]}
- />
- {type != 2 && (
- <>
- <ProFormRadio.Group label="填报维度:" name="dimensionType" disabled={type == 'EDIT' && record.limitFlag} options={DATAFILL_DIMENSIONTYPE} rules={[{ required: true }]} />
- <ProFormDependency name={['dimensionType']}>
- {({ dimensionType }) => {
- return (
- dimensionType == 2 && (
- <ProFormSelect
- label="相关科室:"
- disabled={type == 'EDIT' && record.limitFlag}
- name={'relateDeptList'}
- options={deptOptions}
- fieldProps={{
- mode: 'multiple', // 禁止自由录入
- maxTagCount: 'responsive',
- labelInValue: true,
- size: 'small',
- // 开启检索(按名称与编码匹配)
- showSearch: true,
- optionFilterProp: 'label',
- filterOption: (input: string, option: any) => {
- const label: string = (option?.label ?? '').toString();
- const value: string = (option?.value ?? '').toString();
- const kw = input.toLowerCase();
- return label.toLowerCase().includes(kw) || value.toLowerCase().includes(kw);
- },
- }}
- rules={[{ required: true, message: '相关科室不能为空!' }]}
- />
- )
- );
- }}
- </ProFormDependency>
- </>
- )}
- </>
- );
- }}
- </ProFormDependency>
- <ProFormText name="unit" label="填报值单位:" placeholder="请输入" rules={[{ required: true, message: '填报值单位不能为空!' }]} />
- <ProFormTextArea
- name="description"
- label="填报说明:"
- placeholder="请输入"
- fieldProps={{
- rows: 4,
- }}
- rules={[{ required: false }]}
- />
- </>
- }
- </ModalForm>
- );
- };
- useEffect(() => {
- if (!isFirstLoad) {
- getGenerateTableData();
- } else {
- setIsFirstLoad(false); // 标记首次加载已完成
- }
- }, [currentYear]);
- useEffect(() => {
- if (currentEditRow) {
- set_actionType('DETAIL');
- getGenerateTableData();
- } else {
- set_actionType('NORMAL');
- }
- }, [currentEditRow]);
- return (
- <div className="FillingMana">
- {actionType == 'DETAIL' && (
- <div className="FillingMana-detail">
- <div className="FillingMana-detail-header">
- <div className="FillingMana-detail-header-title">
- <div className="backBtn" onClick={() => set_currentEditRow(undefined)}>
- <IconFont style={{ fontSize: 15 }} type={'iconfanhui'} />
- </div>
- <span>{currentEditRow?.name}</span>
- </div>
- <div className="FillingMana-detail-header-title-sub">
- <span>
- 填报周期:
- {currentEditRow &&
- `${DATAFILL_PERIODTYPE.find((item) => item.value === currentEditRow.periodType) ? DATAFILL_PERIODTYPE.find((item) => item.value === currentEditRow.periodType)?.label : ''}`}
- </span>
- <span>
- 填报维度:
- {currentEditRow &&
- `${
- DATAFILL_DIMENSIONTYPE.find((item) => item.value === currentEditRow.dimensionType) ? DATAFILL_DIMENSIONTYPE.find((item) => item.value === currentEditRow.dimensionType)?.label : ''
- }`}
- </span>
- <span>填报单位:{currentEditRow?.unit}</span>
- </div>
- </div>
- <div className="FillingMana-detail-content">
- <div className="toolBar">
- <div className="filter">
- <div className="filterItem">
- <span className="label" style={{ whiteSpace: 'nowrap' }}>
- {' '}
- 检索:
- </span>
- <Input
- placeholder={'填报科室'}
- allowClear
- autoComplete="off"
- suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => detailtableDataSearchHandle('dimensionName')} />}
- onChange={(e) => {
- set_tableDataSearchKeywords(e.target.value);
- if (e.target.value.length == 0) {
- set_tableDataSource(total_tableDataSource);
- }
- }}
- onPressEnter={(e: any) => {
- const result = total_tableDataSource.filter((a: any) => a.dimensionName.indexOf(e.target.value) != -1);
- set_tableDataSource(result);
- }}
- />
- </div>
- </div>
- <div className="btnGroup">
- {(currentEditRow?.periodType == 1 || currentEditRow?.periodType == 2) && (
- <div className="changeBtn">
- <div className="actBtn" onClick={() => set_currentYear(currentYear - 1)}>
- <IconFont type={'iconshuangzuo'} />
- </div>
- <span style={{ padding: '0 8px' }}>{currentYear}</span>
- <div className="actBtn" onClick={() => set_currentYear(currentYear + 1)}>
- <IconFont type={'iconshuangyou'} />
- </div>
- </div>
- )}
- </div>
- </div>
- <KCTable
- newVer
- actionRef={detailTableRef}
- columns={[...tableColumns, ...tableColumns_default]}
- scroll={{ y: 'calc(100vh - 298px)', x: `${tableColumns.length * 120}px` }}
- rowKey="id"
- dataSource={tableDataSource}
- pagination={false}
- editable={{
- editableKeys: currentEditTableRow ? [currentEditTableRow.id] : [],
- actionRender: (row, config, defaultDom) => [defaultDom.save, defaultDom.cancel],
- onSave: async (key, record, originRow, newLineConfig) => {
- let unEditableColum: any = [];
- const editableColumn = table_columns.filter((a: any) => {
- !a.editable && unEditableColum.push(a);
- return a.editable;
- });
- const result = editableColumn.map((a: any) => ({
- fillItemCode: record[`${unEditableColum[0].dataIndex}_fillItemCode`],
- dimensionCode: record[`${unEditableColum[0].dataIndex}_dimensionCode`],
- periodCode: a.key,
- value: record[`${a.key}`] ?? null,
- dataYear: currentYear,
- }));
- const resp = await applyBatchItemValueReq(result);
- if (resp) {
- set_currentEditTableRow(undefined);
- getGenerateTableData();
- }
- return Promise.resolve(true);
- },
- onCancel: (key, record, originRow, newLineConfig) => {
- set_currentEditTableRow(undefined);
- return Promise.resolve(true);
- },
- }}
- />
- </div>
- </div>
- )}
- {actionType != 'DETAIL' && (
- <div style={{ padding: 16, background: '#fff', borderRadius: 4 }}>
- <div className="toolBar">
- <div className="filter">
- <div className="filterItem">
- <span className="label" style={{ whiteSpace: 'nowrap' }}>
- {' '}
- 检索:
- </span>
- <Input
- placeholder={'填报项目'}
- allowClear
- autoComplete="off"
- suffix={<IconFont type="iconsousuo" style={{ color: '#99A6BF' }} onClick={() => tableDataSearchHandle('name')} />}
- onChange={(e) => {
- set_tableDataSearchKeywords(e.target.value);
- if (e.target.value.length == 0) {
- set_tableDataFilterParams({
- ...tableDataFilterParams,
- current: 1,
- name: '',
- });
- }
- }}
- onPressEnter={(e) => {
- set_tableDataFilterParams({
- ...tableDataFilterParams,
- current: 1,
- name: (e.target as HTMLInputElement).value,
- });
- }}
- />
- </div>
- </div>
- <div className="btnGroup">
- <UpDataActBtn record={undefined} type={'ADD'} />
- </div>
- </div>
- <KCTable
- columns={columns}
- scroll={{ y: `calc(100vh - 250px)` }}
- rowKey="id"
- newVer
- params={tableDataFilterParams}
- request={(params) => getTableData(params)}
- onChange={handleTableChange}
- />
- </div>
- )}
- </div>
- );
- }
|