index.tsx 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  1. /*
  2. * @Author: code4eat awesomedema@gmail.com
  3. * @Date: 2025-09-30 00:00:00
  4. * @LastEditors: code4eat awesomedema@gmail.com
  5. * @LastEditTime: 2025-10-17 11:57:04
  6. * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/serviceEvaluate/index.tsx
  7. * @Description: 服务评价管理 - 左树右表 + 可展开选项子表
  8. */
  9. import React, { useEffect, useMemo, useState } from 'react';
  10. import './style.less';
  11. import { Button, Input, InputNumber, message, Modal, Popconfirm, Tooltip, Transfer, Tree } from 'antd';
  12. import { ProColumns } from '@ant-design/pro-table';
  13. import { ModalForm, ProFormDigit, ProFormText } from '@ant-design/pro-form';
  14. import KCTable from '@/components/kcTable';
  15. import DirectoryTree from 'antd/es/tree/DirectoryTree';
  16. import { DataNode } from 'antd/es/tree';
  17. import type { TransferDirection } from 'antd/es/transfer';
  18. import { createFromIconfontCN } from '@ant-design/icons';
  19. import expandedIcon from '../../../../../public/images/treenode_open.png';
  20. import closeIcon from '../../../../../public/images/treenode_collapse.png';
  21. import { KCInput } from '@/components/KCInput';
  22. import { getDictByDictTypeAndSysid } from '@/service/dictionary';
  23. import {
  24. getCategoryTree,
  25. addCategory,
  26. getProjectList,
  27. getOptionList,
  28. addProject,
  29. delProject,
  30. addOption,
  31. delOption,
  32. updateProjectWeight,
  33. updateOptionScore,
  34. CategoryNode,
  35. ProjectItem,
  36. OptionItem,
  37. SystemTreeNode,
  38. getSystemListForModal,
  39. addSystems,
  40. getSelectedSystemList,
  41. addServiceTypes,
  42. getServiceEvaluationItem,
  43. getEvaluationSelect,
  44. addEvaluationItem,
  45. addEvaluationSelect,
  46. editEvaluationSelect,
  47. deleteEvaluationItem,
  48. deleteEvaluationSelect,
  49. } from './service';
  50. const IconFont = createFromIconfontCN({
  51. scriptUrl: '/zhongtaiC.js',
  52. });
  53. // 子表格:选项列表
  54. const OptionsList: React.FC<{
  55. project: ProjectItem;
  56. options: OptionItem[];
  57. onAdd: (data: { optionCode: string; optionName: string; score: number }) => Promise<void>;
  58. onDelete: (optionId: string) => Promise<void>;
  59. onScoreChange: (optionId: string, score: number) => Promise<void>;
  60. }> = ({ project, options, onAdd, onDelete, onScoreChange }) => {
  61. return (
  62. <div className="SE-OptionsWrap">
  63. <div className="SE-OptionsHeader">
  64. <span className="SE-SubTitle">{project.projectName} - 选项</span>
  65. <ModalForm
  66. title="新增选项"
  67. width={520}
  68. trigger={<Button type="link">新增选项</Button>}
  69. modalProps={{ destroyOnClose: true }}
  70. onFinish={async (values) => {
  71. await onAdd(values as any);
  72. return true;
  73. }}
  74. >
  75. <ProFormText name="optionCode" label="选项代码" placeholder="请输入" rules={[{ required: true, message: '必填' }]} />
  76. <ProFormText name="optionName" label="选项名称" placeholder="请输入" rules={[{ required: true, message: '必填' }]} />
  77. <ProFormDigit name="score" label="选项得分" placeholder="请输入" min={0} max={9999} rules={[{ required: true, message: '必填' }]} />
  78. </ModalForm>
  79. </div>
  80. <div className="SE-OptionsTable">
  81. <div className="SE-OptionsRow SE-OptionsHead">
  82. <div className="col idx">序号</div>
  83. <div className="col code">选项代码</div>
  84. <div className="col name">选项名称</div>
  85. <div className="col score">选项得分</div>
  86. <div className="col act">操作</div>
  87. </div>
  88. {options && options.length > 0 ? (
  89. options.map((opt, index) => (
  90. <div className="SE-OptionsRow" key={opt.optionId}>
  91. <div className="col idx">{index + 1}</div>
  92. <div className="col code">{opt.optionCode}</div>
  93. <div className="col name">{opt.optionName}</div>
  94. <div className="col score">
  95. <InputNumber
  96. min={0}
  97. max={9999}
  98. value={opt.score}
  99. onChange={(val) => {
  100. const v = typeof val === 'number' ? val : Number(val);
  101. if (!Number.isNaN(v)) onScoreChange(opt.optionId, v);
  102. }}
  103. />
  104. </div>
  105. <div className="col act">
  106. <Popconfirm title="是否确认删除?" onConfirm={() => onDelete(opt.optionId)}>
  107. <a>删除</a>
  108. </Popconfirm>
  109. </div>
  110. </div>
  111. ))
  112. ) : (
  113. <div className="SE-Empty">暂无选项</div>
  114. )}
  115. </div>
  116. </div>
  117. );
  118. };
  119. const ServiceEvaluatePage: React.FC = () => {
  120. // 左侧分类树
  121. const [categoryTree, set_categoryTree] = useState<CategoryNode[]>([]);
  122. const [currentCategory, set_currentCategory] = useState<CategoryNode | undefined>();
  123. const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
  124. const [autoExpandParent, setAutoExpandParent] = useState(true);
  125. const [searchValue, setSearchValue] = useState('');
  126. // 主表相关
  127. const [reload, set_reload] = useState(false);
  128. const [tableParams, set_tableParams] = useState<{ current: number; pageSize: number; categoryId?: string }>({ current: 1, pageSize: 10 });
  129. const [tableKey, set_tableKey] = useState(0); // 用于强制刷新表格
  130. const [expandedRowKeys, set_expandedRowKeys] = useState<React.Key[]>([]); // 追踪展开的行
  131. // 展开区缓存选项数据,避免频繁请求
  132. const [projectOptionsMap, set_projectOptionsMap] = useState<Record<string, OptionItem[]>>({});
  133. // 项目权重编辑态(保持原交互:编辑/勾选)
  134. const [editingProjectId, set_editingProjectId] = useState<string | null>(null);
  135. const [editingProjectWeight, set_editingProjectWeight] = useState<number>(0);
  136. // 添加系统弹窗
  137. const [sysModalOpen, set_sysModalOpen] = useState(false);
  138. const [systemTree, set_systemTree] = useState<SystemTreeNode[]>([]);
  139. const [sysTargetKeys, set_sysTargetKeys] = useState<string[]>([]);
  140. const [sysSelectedKeys, set_sysSelectedKeys] = useState<string[]>([]);
  141. const [sysExpandedKeys, set_sysExpandedKeys] = useState<string[]>([]);
  142. // 服务项弹窗
  143. const [serviceModalOpen, set_serviceModalOpen] = useState(false);
  144. const [serviceDict, set_serviceDict] = useState<{ key: string; title: string }[]>([]);
  145. const [serviceSelected, set_serviceSelected] = useState<string[]>([]);
  146. const [serviceBindSystemId, set_serviceBindSystemId] = useState<string>('');
  147. const [serviceSaving, set_serviceSaving] = useState(false);
  148. // 服务节点展开行:缓存“评价项 -> 选项”数据(与 /centerSys/evaluation/getEvaluationSelect 返回结构一致)
  149. const [serviceOptionsMap, set_serviceOptionsMap] = useState<
  150. Record<string, { id: number | string; hospId?: string; itemCode: string; code: string; name: string; value: number; sort: number }[]>
  151. >({});
  152. // 评价项目弹窗(右侧“添加”)
  153. const [evalItemModalOpen, set_evalItemModalOpen] = useState(false);
  154. const [evalItemDict, set_evalItemDict] = useState<{ key: string; title: string }[]>([]);
  155. const [evalItemTargetKeys, set_evalItemTargetKeys] = useState<string[]>([]);
  156. const [evalItemSelectedKeys, set_evalItemSelectedKeys] = useState<string[]>([]);
  157. // 评价项权重编辑态
  158. const [editingEvalCode, set_editingEvalCode] = useState<string | null>(null);
  159. const [editingEvalValue, set_editingEvalValue] = useState<number>(0);
  160. // 选项弹窗(评价项 -> 选项)
  161. const [selectModalOpen, set_selectModalOpen] = useState(false);
  162. const [selectDict, set_selectDict] = useState<{ key: string; title: string }[]>([]);
  163. const [selectTargetKeys, set_selectTargetKeys] = useState<string[]>([]);
  164. const [selectSelectedKeys, set_selectSelectedKeys] = useState<string[]>([]);
  165. const [bindItemCode, set_bindItemCode] = useState<string>('');
  166. // 服务节点主表数据缓存(用于拖拽排序后保持展示顺序)
  167. const [evalItemRows, set_evalItemRows] = useState<any[]>([]);
  168. const [draggingMainKey, set_draggingMainKey] = useState<string | null>(null);
  169. const [draggingOverMainKey, set_draggingOverMainKey] = useState<string | null>(null);
  170. const [useLocalEvalOrder, set_useLocalEvalOrder] = useState<boolean>(false);
  171. // 嵌套选项拖拽的中间态
  172. const [draggingOption, set_draggingOption] = useState<{ parent: string; key: string } | null>(null);
  173. const [draggingOverOption, set_draggingOverOption] = useState<{ parent: string; key: string } | null>(null);
  174. // 工具:移动数组元素
  175. const moveItem = <T,>(list: T[], from: number, to: number): T[] => {
  176. const next = [...list];
  177. const [m] = next.splice(from, 1);
  178. next.splice(to, 0, m);
  179. return next;
  180. };
  181. // 获取左侧“已选系统”并渲染为单层树
  182. const fetchTree = async () => {
  183. const list = await getSelectedSystemList();
  184. const tree: any[] = (list || []).map((item: any) => ({
  185. id: String(item.systemId),
  186. name: String(item.systemName),
  187. nodeType: 'system',
  188. selectable: false,
  189. // 将 serviceList 映射为子节点
  190. children: (item.serviceList || []).map((s: any) => ({
  191. id: String(s.id),
  192. name: String(s.name),
  193. code: String(s.code || ''),
  194. nodeType: 'service',
  195. selectable: true,
  196. })),
  197. }));
  198. set_categoryTree(tree as any);
  199. // 默认选中第一个服务;若无服务则只展开系统,不触发右侧
  200. if (tree.length > 0) {
  201. const sys = tree[0];
  202. if (sys.children && sys.children.length > 0) {
  203. const firstChild = sys.children[0];
  204. set_currentCategory(firstChild as any);
  205. set_tableParams((p) => ({ ...p, current: 1, categoryId: firstChild.id }));
  206. set_reload(true);
  207. set_editingEvalCode(null);
  208. // 服务节点:首次加载右侧评价项
  209. if ((firstChild as any).nodeType === 'service') {
  210. await loadServiceEvalItems(String((firstChild as any).code || (firstChild as any).name));
  211. }
  212. }
  213. if (sys.children && sys.children.length > 0) {
  214. setExpandedKeys([sys.id]);
  215. setAutoExpandParent(true);
  216. }
  217. }
  218. };
  219. // 获取系统树(弹窗数据源)
  220. const fetchSystemTree = async () => {
  221. const { systemList, selectSystemList } = await getSystemListForModal();
  222. set_systemTree(systemList || []);
  223. // 默认已选
  224. set_sysTargetKeys((selectSystemList || []).map((v)=> String(v)));
  225. // 默认全部展开
  226. const allKeys: string[] = [];
  227. const walk = (list: SystemTreeNode[]) => { list.forEach(n=>{ allKeys.push(String(n.code)); if (n.children && n.children.length) walk(n.children); }); };
  228. walk(systemList || []);
  229. set_sysExpandedKeys(allKeys);
  230. };
  231. // 获取服务项字典
  232. const fetchServiceDict = async () => {
  233. // 使用中心系统ID:1547394914533380096 获取指定字典类型
  234. const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_SERVICE');
  235. const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) }));
  236. set_serviceDict(list);
  237. };
  238. // 获取评价项目字典
  239. const fetchEvaluationItemsDict = async () => {
  240. const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_ITEM');
  241. const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) }));
  242. set_evalItemDict(list);
  243. };
  244. // 获取“选项”字典
  245. const fetchEvaluationSelectDict = async () => {
  246. const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_SELECT');
  247. const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) }));
  248. set_selectDict(list);
  249. };
  250. // 拉取服务类型下的评价项(用于 dataSource 渲染 & 拖拽)
  251. const loadServiceEvalItems = async (serviceCode: string) => {
  252. const list = (await getServiceEvaluationItem(serviceCode)) || [];
  253. const normalizePercent = (v: any): number => {
  254. const n = Number(v);
  255. return Number.isFinite(n) && n >= 0 ? n : 0;
  256. };
  257. const mapped = (list || []).map((it: any, idx: number) => ({
  258. id: it.id, // 保留 id 字段,用于删除等操作
  259. itemCode: it.itemCode,
  260. itemName: it.itemName,
  261. percent: normalizePercent(it.percent),
  262. sort: it.sort ?? idx + 1,
  263. }));
  264. set_evalItemRows(mapped);
  265. set_useLocalEvalOrder(true);
  266. };
  267. // 获取表格数据:服务节点 → 评价项;系统/分类节点 → 项目列表
  268. const getTableData = async (params: any) => {
  269. set_reload(false);
  270. if (!currentCategory) return { data: [], success: true } as any;
  271. const node: any = currentCategory as any;
  272. if (node.nodeType === 'service') {
  273. // 使用 dataSource 渲染时,这里返回本地 rows,避免与 KCTable 拖拽冲突
  274. if (evalItemRows && evalItemRows.length > 0) {
  275. return { data: evalItemRows, success: true } as any;
  276. }
  277. await loadServiceEvalItems(String(node.code || node.name));
  278. return { data: evalItemRows, success: true } as any;
  279. }
  280. const resp = await getProjectList({ categoryId: currentCategory.id, ...params });
  281. return { data: resp.list || [], success: true } as any;
  282. };
  283. // 主表列定义 - 项目
  284. const projectColumns: ProColumns<ProjectItem>[] = useMemo(
  285. () => [
  286. { title: '', dataIndex: 'drag', width: 32, render: () => (<span className="SE-DragHandle" style={{ cursor:'move', display:'flex', alignItems:'center', justifyContent:'center', height:'100%' }}><IconFont type="iconliebiao" style={{ fontSize: 16, color: '#99A6BF' }} /></span>) },
  287. {
  288. title: '序号',
  289. dataIndex: 'index',
  290. width: 60,
  291. render: (_, __, index) => index + 1,
  292. },
  293. {
  294. title: '评价项目代码',
  295. dataIndex: 'projectCode',
  296. width: 100,
  297. },
  298. {
  299. title: '评价项目名称',
  300. dataIndex: 'projectName',
  301. ellipsis: true,
  302. },
  303. {
  304. title: '权重',
  305. dataIndex: 'weight',
  306. width: 120,
  307. render: (_, record) => (
  308. <InputNumber
  309. min={0}
  310. max={100}
  311. formatter={(value) => `${value}%`}
  312. parser={(value) => Number(String(value).replace('%', ''))}
  313. value={record.weight}
  314. onChange={async (val) => {
  315. const weight = typeof val === 'number' ? val : Number(val);
  316. if (Number.isNaN(weight)) return;
  317. const ok = await updateProjectWeight(record.projectId, weight);
  318. if (ok) set_reload(true);
  319. }}
  320. />
  321. ),
  322. },
  323. {
  324. title: '操作',
  325. key: 'option',
  326. width: 160,
  327. valueType: 'option',
  328. render: (_, record) => [
  329. <ModalForm
  330. key="add"
  331. title={`新增“${record.projectName}”的选项`}
  332. trigger={<a>选项</a>}
  333. width={520}
  334. onFinish={async (values) => {
  335. const ok = await addOption({ projectId: record.projectId, ...(values as any) });
  336. if (ok) {
  337. const opts = await getOptionList({ projectId: record.projectId });
  338. set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] }));
  339. }
  340. return !!ok;
  341. }}
  342. >
  343. <ProFormText name="optionCode" label="选项代码" rules={[{ required: true, message: '必填' }]} />
  344. <ProFormText name="optionName" label="选项名称" rules={[{ required: true, message: '必填' }]} />
  345. <ProFormDigit name="score" label="选项得分" min={0} max={9999} rules={[{ required: true, message: '必填' }]} />
  346. </ModalForm>,
  347. <Popconfirm key="del" title="是否确认删除?" onConfirm={async () => { const ok = await delProject(record.projectId); if (ok) set_reload(true); }}>
  348. <a>删除</a>
  349. </Popconfirm>,
  350. ],
  351. },
  352. ],
  353. [projectOptionsMap]
  354. );
  355. // 评价项表格列
  356. const evalItemColumns: ProColumns<any>[] = useMemo(
  357. () => [
  358. { title: '', dataIndex: 'drag', width: 32, render: () => (<span className="SE-DragHandle" style={{ cursor:'move', display:'flex', alignItems:'center', justifyContent:'center', height:'100%' }}><IconFont type="iconliebiao" style={{ fontSize: 16, color: '#99A6BF' }} /></span>) },
  359. { title: '序号', dataIndex: 'index', width: 60, render: (_, __, index) => index + 1 },
  360. { title: '评价项目代码', dataIndex: 'itemCode', width: 120 },
  361. { title: '评价项目名称', dataIndex: 'itemName', ellipsis: true },
  362. {
  363. title: '权重',
  364. dataIndex: 'percent',
  365. width: 120,
  366. render: (v: any) => `${(Number.isFinite(Number(v)) && Number(v) >= 0 ? Number(v) : 0)}%`,
  367. },
  368. // 移除不需要的排序列
  369. {
  370. title: '操作',
  371. valueType: 'option',
  372. width: 120,
  373. render: (_, record) => [
  374. <a key="opt" onClick={async () => {
  375. set_bindItemCode(record.itemCode);
  376. // 获取该评价项已有的选项
  377. const existingOptions = await getEvaluationSelect(record.itemCode);
  378. const existingKeys = (existingOptions || []).map((item: any) => String(item.code));
  379. set_selectTargetKeys(existingKeys);
  380. set_selectSelectedKeys([]);
  381. await fetchEvaluationSelectDict();
  382. set_selectModalOpen(true);
  383. }}>选项</a>,
  384. <Popconfirm key="del" title="是否确认删除?" onConfirm={async () => {
  385. const res = await deleteEvaluationItem(record.id);
  386. if (res !== false) {
  387. // 删除成功后,如果该项是展开状态,从展开列表中移除
  388. set_expandedRowKeys((prev) => prev.filter(k => k !== record.itemCode));
  389. // 重新加载评价项列表
  390. const current: any = currentCategory as any;
  391. if (current && current.nodeType === 'service') {
  392. await loadServiceEvalItems(String(current.code || current.name));
  393. }
  394. }
  395. }}>
  396. <a>删除</a>
  397. </Popconfirm>,
  398. ],
  399. },
  400. ],
  401. [editingEvalCode, editingEvalValue]
  402. );
  403. // 展开行渲染(分类/系统场景使用)
  404. const expandedRowRender = (record: ProjectItem) => {
  405. const options = projectOptionsMap[record.projectId] || [];
  406. return (
  407. <OptionsList
  408. project={record}
  409. options={options}
  410. onAdd={async (data) => {
  411. const ok = await addOption({ projectId: record.projectId, ...data });
  412. if (ok) {
  413. const opts = await getOptionList({ projectId: record.projectId });
  414. set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] }));
  415. }
  416. }}
  417. onDelete={async (optionId) => {
  418. const ok = await delOption(optionId);
  419. if (ok) {
  420. const opts = await getOptionList({ projectId: record.projectId });
  421. set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] }));
  422. }
  423. }}
  424. onScoreChange={async (optionId, score) => {
  425. const ok = await updateOptionScore(optionId, score);
  426. if (ok) {
  427. const opts = await getOptionList({ projectId: record.projectId });
  428. set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] }));
  429. }
  430. }}
  431. />
  432. );
  433. };
  434. // 服务节点:展开渲染(与外层保持一致的 KCTable 风格)
  435. const expandedRowRenderService = (record: any) => {
  436. const parentCode: string = record.itemCode;
  437. const rows = serviceOptionsMap[parentCode] || [];
  438. const columns: ProColumns<any>[] = [
  439. {
  440. title: '',
  441. dataIndex: 'drag',
  442. width: 32,
  443. render: (_, row: any) => (
  444. <span
  445. className="SE-DragHandle"
  446. style={{ cursor: 'move', display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}
  447. draggable
  448. onDragStart={(e) => {
  449. set_draggingOption({ parent: parentCode, key: String(row.id ?? row.code) });
  450. e.dataTransfer.effectAllowed = 'move';
  451. try { e.dataTransfer.setData('text/plain', String(row.id ?? row.code)); } catch {}
  452. }}
  453. onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
  454. onDrop={(e) => {
  455. e.preventDefault();
  456. if (!draggingOption || draggingOption.parent !== parentCode) return;
  457. const list = serviceOptionsMap[parentCode] || [];
  458. const from = list.findIndex((r: any) => String(r.id ?? r.code) === draggingOption.key);
  459. const to = list.findIndex((r: any) => String(r.id ?? r.code) === String(row.id ?? row.code));
  460. if (from === -1 || to === -1 || from === to) return;
  461. const next = moveItem(list, from, to);
  462. set_serviceOptionsMap((old) => ({ ...old, [parentCode]: next }));
  463. set_draggingOption(null);
  464. set_reload((v)=>!v);
  465. // TODO: 保存选项排序
  466. }}
  467. title="拖动排序"
  468. >
  469. <IconFont type="iconliebiao" style={{ fontSize: 16, color: '#99A6BF' }} />
  470. </span>
  471. ),
  472. },
  473. { title: '序号', dataIndex: 'index', width: 60, render: (_, __, index) => index + 1 },
  474. { title: '选项代码', dataIndex: 'code', width: 120 },
  475. { title: '选项名称', dataIndex: 'name', ellipsis: true },
  476. {
  477. title: '选项得分',
  478. dataIndex: 'value',
  479. width: 200,
  480. render: (v: any, row: any) => {
  481. const isEditing = row.__editing === true;
  482. if (!isEditing) {
  483. return (
  484. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  485. <div style={{ padding: '2px 10px', border: '1px solid #dae2f2', borderRadius: 6, minWidth: 72, textAlign: 'center', background: '#f7f9fc' }}>{v ?? 0}</div>
  486. <span
  487. onClick={() => {
  488. set_serviceOptionsMap((old) => {
  489. const list = [...(old[parentCode] || [])];
  490. const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code));
  491. if (idx > -1) list[idx] = { ...list[idx], __editing: true } as any;
  492. return { ...old, [parentCode]: list };
  493. });
  494. }}
  495. style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: 28, width: 28, border: '1px solid #dae2f2', borderRadius: 6, cursor: 'pointer' }}
  496. >
  497. <IconFont type="iconbianji" style={{ fontSize: 16, color: '#17181A' }} />
  498. </span>
  499. </div>
  500. );
  501. }
  502. return (
  503. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  504. <InputNumber
  505. min={0}
  506. max={9999}
  507. value={row.value}
  508. onChange={(val) => {
  509. const next = Number(val) || 0;
  510. set_serviceOptionsMap((old) => {
  511. const list = [...(old[parentCode] || [])];
  512. const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code));
  513. if (idx > -1) list[idx] = { ...list[idx], value: next, __editing: true } as any;
  514. return { ...old, [parentCode]: list };
  515. });
  516. }}
  517. style={{ width: 100 }}
  518. />
  519. <span
  520. onClick={async () => {
  521. // 调用接口保存选项分值
  522. const res = await editEvaluationSelect(row.id, row.value);
  523. // 响应拦截器:成功返回 data 或 true,失败返回 false
  524. // 且 POST 成功时拦截器已自动显示"操作成功!"提示
  525. if (res !== false) {
  526. // 成功,退出编辑状态
  527. set_serviceOptionsMap((old) => {
  528. const list = [...(old[parentCode] || [])];
  529. const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code));
  530. if (idx > -1) list[idx] = { ...list[idx], __editing: false } as any;
  531. return { ...old, [parentCode]: list };
  532. });
  533. }
  534. // 失败时拦截器已显示错误提示,这里只需保持编辑状态
  535. }}
  536. style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: 28, width: 28, border: '1px solid #dae2f2', borderRadius: 6, cursor: 'pointer' }}
  537. >
  538. <IconFont type="iconqueren" style={{ fontSize: 16, color: '#17181A' }} />
  539. </span>
  540. </div>
  541. );
  542. },
  543. },
  544. {
  545. title: '操作',
  546. valueType: 'option',
  547. width: 120,
  548. render: (_, row: any) => [
  549. <Popconfirm
  550. key="del"
  551. title="是否确认删除?"
  552. onConfirm={async () => {
  553. const res = await deleteEvaluationSelect(row.id);
  554. if (res !== false) {
  555. // 删除成功,重新加载该评价项的选项列表
  556. const list = await getEvaluationSelect(parentCode);
  557. set_serviceOptionsMap((old) => ({
  558. ...old,
  559. [parentCode]: (list || []) as any,
  560. }));
  561. }
  562. }}
  563. >
  564. <a>删除</a>
  565. </Popconfirm>
  566. ],
  567. },
  568. ];
  569. return (
  570. <div className="SE-ExpandWrap">
  571. <KCTable
  572. newVer
  573. columns={columns as ProColumns[]}
  574. dataSource={rows}
  575. rowKey={(r: any) => String(r.id ?? r.code)}
  576. dragSortKey="id"
  577. dragType="KC_TABLE_NESTED_ROW"
  578. pagination={false}
  579. toolBarRender={false as any}
  580. search={false as any}
  581. options={false as any}
  582. bordered={false as any}
  583. size={'small' as any}
  584. dragSort
  585. onDragSortEnd={(next)=>{
  586. set_serviceOptionsMap((old)=> ({ ...old, [parentCode]: next }));
  587. }}
  588. />
  589. </div>
  590. );
  591. };
  592. // 表格展开回调
  593. const onExpand = async (expanded: boolean, record: any) => {
  594. const node: any = currentCategory as any;
  595. const rowKey = node?.nodeType === 'service' ? record.itemCode : record.projectId;
  596. // 更新展开行状态
  597. if (expanded) {
  598. set_expandedRowKeys((prev) => [...prev, rowKey]);
  599. } else {
  600. set_expandedRowKeys((prev) => prev.filter(k => k !== rowKey));
  601. return;
  602. }
  603. // 加载数据
  604. if (node && node.nodeType === 'service') {
  605. const list = await getEvaluationSelect(record.itemCode);
  606. set_serviceOptionsMap((old: Record<string, { id: number | string; hospId?: string; itemCode: string; code: string; name: string; value: number; sort: number }[]>) => ({
  607. ...old,
  608. [record.itemCode]: (list || []) as { id: number | string; hospId?: string; itemCode: string; code: string; name: string; value: number; sort: number }[],
  609. }));
  610. } else {
  611. const opts = await getOptionList({ projectId: record.projectId });
  612. set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] }));
  613. }
  614. };
  615. // 收起所有展开行
  616. const collapseAllRows = () => {
  617. set_expandedRowKeys([]);
  618. };
  619. useEffect(() => { fetchTree(); }, []);
  620. useEffect(() => { if (sysModalOpen) fetchSystemTree(); }, [sysModalOpen]);
  621. useEffect(() => {
  622. if (currentCategory) {
  623. set_tableParams((p) => ({ ...p, current: 1, categoryId: currentCategory.id }));
  624. set_reload(true);
  625. // 切换分类时清空展开行
  626. set_expandedRowKeys([]);
  627. }
  628. }, [currentCategory]);
  629. return (
  630. <>
  631. <div className="SystemNavMana">
  632. <div className="leftTree">
  633. <div className="search" style={{display:'flex',gap:8,alignItems:'center'}}>
  634. <Input className="searchInput" placeholder="请输入" size="small" allowClear onChange={(e)=>setSearchValue(e.target.value)} />
  635. <Tooltip title="添加系统">
  636. <span className="add" onClick={()=> set_sysModalOpen(true)}>
  637. <IconFont style={{ color: '#17181A', fontSize: 12 }} type="icon-xinzeng" />
  638. </span>
  639. </Tooltip>
  640. </div>
  641. <div className="treeeWrap">
  642. {categoryTree && categoryTree.length>0 && (
  643. <DirectoryTree
  644. fieldNames={{ title:'name', key:'id' }}
  645. rootStyle={{ overflowY:'scroll', overflowX:'hidden' }}
  646. onSelect={async (keys, info)=> {
  647. const node = info.node as any;
  648. // 仅允许选择服务节点
  649. if (node.nodeType !== 'service') return;
  650. set_currentCategory(node);
  651. set_tableParams((p)=>({ ...p, current:1, categoryId: node.id }));
  652. set_reload(true);
  653. // 服务节点:拉取右侧评价项(dataSource 渲染)
  654. await loadServiceEvalItems(String(node.code || node.name));
  655. }}
  656. onExpand={(keys)=>{ setExpandedKeys(keys as React.Key[]); setAutoExpandParent(false);} }
  657. expandedKeys={expandedKeys}
  658. autoExpandParent={autoExpandParent}
  659. selectedKeys={currentCategory && (currentCategory as any).nodeType==='service' ? [currentCategory.id] : []}
  660. blockNode
  661. className="KC-DirectoryTree"
  662. icon={() => null}
  663. titleRender={(nodeData: any) => {
  664. const strTitle = nodeData.name as string;
  665. const index = strTitle.indexOf(searchValue);
  666. const beforeStr = strTitle.substring(0, index);
  667. const afterStr = strTitle.slice(index + searchValue.length);
  668. const title = index > -1 ? (
  669. <span>
  670. {beforeStr}
  671. <span className="site-tree-search-value" style={{ color: 'red', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{searchValue}</span>
  672. {afterStr}
  673. </span>
  674. ) : (
  675. <span className="strTitle">{strTitle}</span>
  676. );
  677. const isDirectory = Array.isArray(nodeData.children) && nodeData.children.length > 0;
  678. return (
  679. <div style={{display:'flex',alignItems:'center',height:32}}>
  680. <span className="node-title-area" style={{flex:1, minWidth:0}}>{title}</span>
  681. {nodeData.nodeType === 'system' && (
  682. <span
  683. className="inline-add"
  684. style={{display:'inline-flex', justifyContent:'center', alignItems:'center'}}
  685. onClick={async (e)=>{
  686. e.stopPropagation();
  687. set_serviceBindSystemId(String(nodeData.id));
  688. // 打开前重置已选,避免上次选择残留
  689. set_serviceSelected([]);
  690. await fetchServiceDict();
  691. set_serviceModalOpen(true);
  692. }}
  693. >
  694. <IconFont style={{ color: '#99A6BF', fontSize: 12 }} type="icon-xinzeng" />
  695. </span>
  696. )}
  697. </div>
  698. );
  699. }}
  700. treeData={categoryTree as unknown as DataNode[]}
  701. switcherIcon={(props:any)=> props.expanded ? (<img style={{ width:20,height:20, position:'relative', top:5 }} src={closeIcon} />) : (<img style={{ width:20,height:20, position:'relative', top:5 }} src={expandedIcon} />)}
  702. />
  703. )}
  704. </div>
  705. </div>
  706. <div className="rightContent">
  707. <div className="tableToolbar">
  708. <div className="filter">
  709. <span style={{ fontSize: 16, fontWeight: 500, color: '#17181A' }}>
  710. {currentCategory?.name || '-'}
  711. </span>
  712. </div>
  713. <div className={'btnGroup'}>
  714. {expandedRowKeys.length > 0 && (
  715. <span className="collapse" onClick={collapseAllRows}>
  716. 收起所有 ({expandedRowKeys.length})
  717. </span>
  718. )}
  719. <span className="add" onClick={async ()=>{
  720. // 获取当前已有的评价项,用于回显
  721. const current: any = currentCategory as any;
  722. if (current && current.nodeType === 'service') {
  723. const existingItems = await getServiceEvaluationItem(String(current.code || current.name));
  724. const existingKeys = (existingItems || []).map((item: any) => String(item.itemCode));
  725. set_evalItemTargetKeys(existingKeys);
  726. } else {
  727. set_evalItemTargetKeys([]);
  728. }
  729. set_evalItemSelectedKeys([]);
  730. await fetchEvaluationItemsDict();
  731. set_evalItemModalOpen(true);
  732. }}>
  733. 添加
  734. </span>
  735. </div>
  736. <ModalForm
  737. title="新增评价项目"
  738. width={520}
  739. trigger={<span style={{ display:'none' }} />}
  740. onFinish={async (values) => {
  741. if (!currentCategory) return false;
  742. const ok = await addProject({ categoryId: currentCategory.id, ...(values as any) });
  743. if (ok) set_reload(true);
  744. return !!ok;
  745. }}
  746. >
  747. <ProFormText name="projectCode" label="项目代码" placeholder="请输入" rules={[{ required: true, message: '必填' }]} />
  748. <ProFormText name="projectName" label="项目名称" placeholder="请输入" rules={[{ required: true, message: '必填' }]} />
  749. <ProFormDigit name="weight" label="权重%" min={0} max={100} placeholder="0-100" rules={[{ required: true, message: '必填' }]} />
  750. </ModalForm>
  751. </div>
  752. {currentCategory && (
  753. <KCTable
  754. key={`table-${currentCategory.id}`}
  755. newVer
  756. reload={(currentCategory as any)?.nodeType === 'service' ? false : reload}
  757. params={(currentCategory as any)?.nodeType === 'service' ? undefined : tableParams}
  758. rowKey={(((currentCategory as any)?.nodeType === 'service')
  759. ? ((r: any) => String(r?.itemCode ?? ''))
  760. : ((r: any) => String(r?.projectId ?? ''))) as any}
  761. dragSortKey={(currentCategory as any)?.nodeType === 'service' ? 'itemCode' : 'projectId'}
  762. dragType="KC_TABLE_MAIN_ROW"
  763. disabledDragKeys={expandedRowKeys}
  764. scroll={{ y: `calc(100vh - 240px)` }}
  765. columns={((currentCategory as any)?.nodeType === 'service' ? evalItemColumns : projectColumns) as ProColumns[]}
  766. request={(((currentCategory as any)?.nodeType === 'service') ? undefined : ((p: any, s: any, f: any) => getTableData(p))) as any}
  767. expandable={{
  768. expandedRowRender: (currentCategory as any)?.nodeType === 'service' ? expandedRowRenderService : expandedRowRender,
  769. onExpand,
  770. expandedRowKeys,
  771. onExpandedRowsChange: (keys: readonly React.Key[]) => set_expandedRowKeys([...keys]),
  772. expandIconColumnIndex: 1 // 展开图标显示在第2列,第1列留给拖拽手柄
  773. }}
  774. pagination={false}
  775. dragSort={(currentCategory as any)?.nodeType === 'service'}
  776. dataSource={((currentCategory as any)?.nodeType === 'service') ? evalItemRows : undefined}
  777. onDragSortEnd={(rows: any[])=>{
  778. set_evalItemRows(rows);
  779. set_useLocalEvalOrder(true);
  780. }}
  781. />
  782. )}
  783. </div>
  784. </div>
  785. <Modal
  786. title="添加系统"
  787. open={sysModalOpen}
  788. onCancel={()=> set_sysModalOpen(false)}
  789. onOk={async ()=>{
  790. const flat = (nodes: SystemTreeNode[]): SystemTreeNode[] => nodes.reduce<SystemTreeNode[]>((acc, n)=>{
  791. acc.push(n);
  792. if (n.children && n.children.length) acc.push(...flat(n.children));
  793. return acc;
  794. }, []);
  795. const all = flat(systemTree);
  796. const payload = all
  797. .filter(n => sysTargetKeys.includes(n.code) && n.type === 3)
  798. .map(n => ({ systemId: n.code, systemName: n.name }));
  799. if (payload.length === 0) {
  800. message.warning('请选择系统');
  801. return;
  802. }
  803. const ok = await addSystems(payload as any);
  804. if (ok) {
  805. set_sysModalOpen(false);
  806. // 刷新左侧“已选系统”列表
  807. set_reload(true);
  808. fetchTree();
  809. }
  810. }}
  811. width={720}
  812. destroyOnClose
  813. >
  814. <Transfer
  815. className="tree-transfer"
  816. listStyle={{ height: 540 }}
  817. dataSource={(function buildFlat(){
  818. const res: any[] = [];
  819. const walk = (list: SystemTreeNode[], parentChecked: boolean)=>{
  820. list.forEach(n=>{
  821. res.push({ key: n.code, code: n.code, name: n.name, type: n.type, node: n });
  822. if (n.children && n.children.length) walk(n.children, false);
  823. })
  824. };
  825. walk(systemTree, false);
  826. return res;
  827. })()}
  828. rowKey={(item)=> item.code}
  829. titles={[`待选项`, `已选项`]}
  830. targetKeys={sysTargetKeys}
  831. selectedKeys={sysSelectedKeys}
  832. showSearch
  833. locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }}
  834. render={(item)=> item.name}
  835. onChange={(nextTargetKeys)=> set_sysTargetKeys(nextTargetKeys as string[])}
  836. onSelectChange={(sourceSelectedKeys, targetSelectedKeys)=> set_sysSelectedKeys([...(sourceSelectedKeys as string[]), ...(targetSelectedKeys as string[])])}
  837. >
  838. {({ direction, onItemSelect, onItemSelectAll, selectedKeys })=>{
  839. if (direction === 'left') {
  840. const treeData = (function toTree(list: SystemTreeNode[]): any[] {
  841. return list.map((n: SystemTreeNode): any => ({
  842. key: n.code,
  843. title: n.name,
  844. // 不再用 children 判断是否可勾选;统一允许勾选,由 onCheck 决定加入哪些 key
  845. disabled: false,
  846. children: n.children ? (toTree(n.children) as any[]) : undefined,
  847. }));
  848. })(systemTree);
  849. const checkedKeys = [...selectedKeys, ...sysTargetKeys];
  850. return (
  851. <Tree
  852. checkable
  853. defaultExpandAll
  854. checkedKeys={checkedKeys}
  855. treeData={treeData as any}
  856. height={480}
  857. expandedKeys={sysExpandedKeys}
  858. onExpand={(keys)=> set_sysExpandedKeys(keys as string[])}
  859. onCheck={(keys, { node })=>{
  860. // 仅加入“系统叶子”:type==3,或 type==5 但无 children(后端把系统标成5时兜底)
  861. const isSystemLeafByCode = (code: string) => {
  862. const flat = (function flat(){
  863. const res: any[] = [];
  864. const walk = (list: SystemTreeNode[])=>{ list.forEach(nn=>{ res.push({ code: nn.code, type: nn.type, children: nn.children }); if(nn.children) walk(nn.children); }); };
  865. walk(systemTree); return res;
  866. })();
  867. const found = flat.find(i=> i.code===code);
  868. if (!found) return false;
  869. const noChildren = !found.children || found.children.length===0;
  870. return found.type===3 || (found.type===5 && noChildren);
  871. };
  872. const getChildCodes = (n:any): string[] => {
  873. if (!n.children || n.children.length===0) return [n.key as string];
  874. return n.children.reduce((acc:any,c:any)=> acc.concat(getChildCodes(c)), []);
  875. };
  876. const isSelect = !(node as any).checked;
  877. const candidate = (node.children && node.children.length>0) ? getChildCodes(node) : [node.key as string];
  878. const systemLeafKeys = candidate.filter((code)=> isSystemLeafByCode(code));
  879. onItemSelectAll(systemLeafKeys as string[], isSelect);
  880. }}
  881. />
  882. );
  883. }
  884. return null;
  885. }}
  886. </Transfer>
  887. </Modal>
  888. <Modal
  889. title="添加服务"
  890. open={serviceModalOpen}
  891. onCancel={()=> { set_serviceModalOpen(false); set_serviceSelected([]); set_serviceBindSystemId(''); }}
  892. confirmLoading={serviceSaving}
  893. onOk={async ()=> {
  894. if (!serviceBindSystemId) { set_serviceModalOpen(false); return; }
  895. try {
  896. set_serviceSaving(true);
  897. const ok: any = await addServiceTypes(serviceBindSystemId, serviceSelected);
  898. if (ok) {
  899. set_serviceModalOpen(false);
  900. set_serviceSelected([]);
  901. set_serviceBindSystemId('');
  902. // 刷新左侧列表
  903. set_reload(true);
  904. fetchTree();
  905. }
  906. } finally {
  907. set_serviceSaving(false);
  908. }
  909. }}
  910. width={560}
  911. destroyOnClose
  912. >
  913. <Transfer
  914. className="ServiceTransfer"
  915. style={{ width: '100%' }}
  916. titles={[`待选项`,`已选项`]}
  917. dataSource={serviceDict}
  918. rowKey={(item)=> item.key}
  919. render={(item)=> item.title}
  920. targetKeys={serviceSelected}
  921. onChange={(next)=> set_serviceSelected(next as string[])}
  922. listStyle={{ height: 460, width: 'calc(50% - 32px)' }}
  923. operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }}
  924. locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }}
  925. showSearch
  926. />
  927. </Modal>
  928. <Modal
  929. title="评价项目"
  930. open={evalItemModalOpen}
  931. onCancel={()=> { set_evalItemModalOpen(false); set_evalItemTargetKeys([]); set_evalItemSelectedKeys([]); }}
  932. onOk={async ()=> {
  933. // 依据当前选中的服务类型保存评价项目
  934. const current: any = currentCategory as any;
  935. if (!current || current.nodeType !== 'service') { set_evalItemModalOpen(false); return; }
  936. // 从字典中匹配详细信息
  937. const itemCodes = evalItemTargetKeys.map((k, idx)=>{
  938. const found = evalItemDict.find(i=> i.key === k);
  939. return { itemCode: k, itemName: found?.title || k, itemValue: 1, sort: idx + 1 };
  940. });
  941. const ok: any = await addEvaluationItem(String(current.code || current.name), itemCodes);
  942. if (ok) {
  943. set_evalItemModalOpen(false);
  944. set_evalItemTargetKeys([]);
  945. set_evalItemSelectedKeys([]);
  946. // 刷新右侧表格:重新加载评价项列表
  947. await loadServiceEvalItems(String(current.code || current.name));
  948. }
  949. }}
  950. width={560}
  951. destroyOnClose
  952. >
  953. <Transfer
  954. titles={[`待选项`,`已选项`]}
  955. dataSource={evalItemDict}
  956. rowKey={(item)=> item.key}
  957. render={(item)=> item.title}
  958. targetKeys={evalItemTargetKeys}
  959. selectedKeys={evalItemSelectedKeys}
  960. onChange={(next)=> set_evalItemTargetKeys(next as string[])}
  961. onSelectChange={(l,r)=> set_evalItemSelectedKeys([...(l as string[]), ...(r as string[])])}
  962. listStyle={{ height: 460, width: 'calc(50% - 32px)' }}
  963. operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }}
  964. showSearch
  965. locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }}
  966. />
  967. </Modal>
  968. <Modal
  969. title="添加选项"
  970. open={selectModalOpen}
  971. onCancel={()=> { set_selectModalOpen(false); set_selectTargetKeys([]); set_selectSelectedKeys([]); set_bindItemCode(''); }}
  972. onOk={async ()=> {
  973. const selectCodes = selectTargetKeys.map((k, idx)=>{
  974. const found = selectDict.find(i=> i.key === k);
  975. return { itemCode: k, itemName: found?.title || k, itemValue: 1, sort: idx + 1 };
  976. });
  977. const ok: any = await addEvaluationSelect(bindItemCode, selectCodes);
  978. if (ok !== false) {
  979. set_selectModalOpen(false);
  980. set_selectTargetKeys([]);
  981. set_selectSelectedKeys([]);
  982. // 重新加载该评价项的选项数据
  983. const list = await getEvaluationSelect(bindItemCode);
  984. set_serviceOptionsMap((old) => ({
  985. ...old,
  986. [bindItemCode]: (list || []) as any,
  987. }));
  988. set_bindItemCode('');
  989. set_reload(true);
  990. }
  991. }}
  992. width={560}
  993. destroyOnClose
  994. >
  995. <Transfer
  996. titles={[`待选项`,`已选项`]}
  997. dataSource={selectDict}
  998. rowKey={(item)=> item.key}
  999. render={(item)=> item.title}
  1000. targetKeys={selectTargetKeys}
  1001. selectedKeys={selectSelectedKeys}
  1002. onChange={(next)=> set_selectTargetKeys(next as string[])}
  1003. onSelectChange={(l,r)=> set_selectSelectedKeys([...(l as string[]), ...(r as string[])])}
  1004. listStyle={{ height: 360, width: 'calc(50% - 32px)' }}
  1005. operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }}
  1006. showSearch
  1007. locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }}
  1008. />
  1009. </Modal>
  1010. </>
  1011. );
  1012. };
  1013. export default ServiceEvaluatePage;