TableEdit.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. import React, { useEffect } from "react";
  2. import { EditableProTable, ProColumns } from "@ant-design/pro-components";
  3. import type { ColumnItem } from "@/type";
  4. import { createColumn } from "@/utils";
  5. import { DataType } from "@/enum";
  6. import { DATA_TYPE_OPTIONS } from "@/constants";
  7. import {
  8. Button,
  9. Input,
  10. InputNumber,
  11. message,
  12. Switch,
  13. Tooltip,
  14. Upload,
  15. UploadFile,
  16. } from "antd";
  17. import LangInput from "./LangInput";
  18. import { validateColumnCode } from "@/utils/validator";
  19. import VariableModal from "./VariableModal";
  20. import ColumnVariableModal from "./ColumnVariableModal";
  21. import { FormInstance } from "antd/lib";
  22. import {
  23. DownloadOutlined,
  24. InfoCircleOutlined,
  25. UploadOutlined,
  26. } from "@ant-design/icons";
  27. import { parseExcel } from "@/utils/parseExcel";
  28. import ImportResultModal from "./ImportResultModal";
  29. export default function TableEdit(props: {
  30. tableId?: string;
  31. data: any[];
  32. modelId?: string;
  33. onChange?: (data: readonly ColumnItem[]) => void;
  34. }) {
  35. const [editableKeys, setEditableRowKeys] = React.useState<React.Key[]>([]);
  36. const [dataSource, setDataSource] = React.useState<readonly ColumnItem[]>(
  37. props.data
  38. );
  39. const boxRef = React.useRef<HTMLDivElement>(null);
  40. const importResultRef = React.useRef<{
  41. open: (data: any[], columns: readonly ColumnItem[]) => void;
  42. }>();
  43. useEffect(() => {
  44. props.onChange?.(dataSource);
  45. }, [dataSource]);
  46. const DefaultValueComp = ({
  47. value,
  48. onChange,
  49. }: {
  50. value?: string;
  51. onChange?: (value: string) => void;
  52. }) => {
  53. return (
  54. <div className="flex gap-2px">
  55. <Input
  56. placeholder="默认值"
  57. value={value}
  58. onChange={(e) => {
  59. onChange?.(e.target.value);
  60. }}
  61. />
  62. <div>
  63. <VariableModal
  64. trigger={
  65. <Button
  66. size="small"
  67. style={{ width: 40, fontSize: 12 }}
  68. type="text"
  69. >
  70. +变量
  71. </Button>
  72. }
  73. onOk={(val) => onChange?.(val)}
  74. />
  75. <ColumnVariableModal
  76. trigger={
  77. <Button
  78. size="small"
  79. style={{ width: 40, fontSize: 12 }}
  80. type="text"
  81. >
  82. +字段
  83. </Button>
  84. }
  85. onOk={(val) => onChange?.(val)}
  86. />
  87. </div>
  88. </div>
  89. );
  90. };
  91. const LengthComp = ({
  92. value,
  93. onChange,
  94. model,
  95. form,
  96. rowKey,
  97. }: {
  98. value?: string;
  99. onChange?: (value: string) => void;
  100. model: ColumnItem;
  101. form: FormInstance;
  102. rowKey?: React.Key | React.Key[];
  103. }) => {
  104. return (
  105. <span className="flex gap-2px">
  106. <InputNumber
  107. min={0}
  108. placeholder="总长度"
  109. defaultValue={model.precision}
  110. onChange={(value) =>
  111. form.setFieldValue([rowKey || "", "precision"], value)
  112. }
  113. />
  114. <InputNumber
  115. min={0}
  116. placeholder="小数位数"
  117. defaultValue={model.scale}
  118. onChange={(value) =>
  119. form.setFieldValue([rowKey || "", "scale"], value)
  120. }
  121. />
  122. </span>
  123. );
  124. };
  125. const columns: ProColumns[] = [
  126. {
  127. title: "字段代码",
  128. dataIndex: "schemaName",
  129. valueType: "text",
  130. width: 150,
  131. formItemProps: {
  132. rules: [
  133. {
  134. required: true,
  135. message: "请输入字段名称",
  136. },
  137. {
  138. max: 50,
  139. message: "字段名称不能超过50个字符",
  140. },
  141. validateColumnCode,
  142. ],
  143. },
  144. },
  145. {
  146. title: "字段名称",
  147. dataIndex: "langName",
  148. valueType: "text",
  149. width: 150,
  150. render: (_dom, entity) => {
  151. return (
  152. entity.langNameList?.find(
  153. (item: Record<string, string>) => item.name === "zh-CN"
  154. )?.value || "-"
  155. );
  156. },
  157. renderFormItem: (_schema, config, form) => {
  158. const model = config.record;
  159. const rowKey = config.recordKey;
  160. console.log(model);
  161. return (
  162. <span>
  163. <LangInput
  164. style={{ width: 150 }}
  165. value={model.langNameList}
  166. onChange={(langValue, key) => {
  167. form.setFieldValue([rowKey || "", "langNameList"], langValue);
  168. form.setFieldValue([rowKey || "", "langName"], "");
  169. }}
  170. />
  171. </span>
  172. );
  173. },
  174. },
  175. {
  176. title: "类型",
  177. dataIndex: "type",
  178. valueType: "select",
  179. width: 120,
  180. fieldProps: (form, { rowKey }) => {
  181. return {
  182. options: DATA_TYPE_OPTIONS,
  183. onChange: () => {
  184. form.setFieldValue([rowKey || "", "maxLength"], undefined);
  185. form.setFieldValue([rowKey || "", "scale"], undefined);
  186. form.setFieldValue([rowKey || "", "precision"], undefined);
  187. },
  188. };
  189. },
  190. formItemProps: {
  191. rules: [
  192. {
  193. required: true,
  194. message: "请选择类型",
  195. },
  196. ],
  197. },
  198. },
  199. {
  200. title: "长度",
  201. dataIndex: "maxLength",
  202. valueType: "digit",
  203. width: 120,
  204. fieldProps: {
  205. precision: 0,
  206. },
  207. render: (text, record) => {
  208. return record.type === DataType.Decimal
  209. ? `${record.precision},${record.scale}`
  210. : text;
  211. },
  212. renderFormItem: (_schema, config, form) => {
  213. const model = config.record;
  214. const rowKey = config.recordKey;
  215. return model.type === DataType.Nvarchar ? (
  216. <InputNumber min={0} max={4000} placeholder="字符长度" />
  217. ) : model.type === DataType.Decimal ? (
  218. <LengthComp model={model} form={form} rowKey={rowKey} />
  219. ) : (
  220. "-"
  221. );
  222. },
  223. },
  224. {
  225. title: "必填",
  226. dataIndex: "isRequired",
  227. valueType: "switch",
  228. render: (text, record) => {
  229. return <Switch disabled checked={record.isRequired} />;
  230. },
  231. width: 80,
  232. },
  233. {
  234. title: "唯一",
  235. dataIndex: "isUnique",
  236. valueType: "switch",
  237. render: (text, record) => {
  238. return <Switch disabled checked={record.isUnique} />;
  239. },
  240. width: 80,
  241. },
  242. {
  243. title: "默认值",
  244. dataIndex: "defaultValue",
  245. valueType: "text",
  246. width: 150,
  247. renderFormItem() {
  248. return <DefaultValueComp />;
  249. },
  250. },
  251. {
  252. title: "字符集",
  253. dataIndex: "chartset",
  254. valueType: "text",
  255. width: 120,
  256. renderFormItem: (_schema, config) => {
  257. return config.record.type === DataType.Nvarchar ? (
  258. <Input placeholder="字符集" />
  259. ) : null;
  260. },
  261. },
  262. {
  263. title: "内容",
  264. dataIndex: "whereInputContent",
  265. valueType: "text",
  266. width: 120,
  267. renderFormItem: (_schema, config) => {
  268. return config.record.type === DataType.Nvarchar ? (
  269. <Input.TextArea placeholder="内容..." />
  270. ) : null;
  271. },
  272. },
  273. {
  274. title: "描述",
  275. dataIndex: "memo",
  276. valueType: "textarea",
  277. width: 120,
  278. renderFormItem: () => {
  279. return <Input.TextArea placeholder="描述..." />;
  280. },
  281. },
  282. {
  283. title: "预定义字段",
  284. dataIndex: "isPreDefined",
  285. renderText: (text, record) => {
  286. return record.isPreDefined ? "是" : "否";
  287. },
  288. },
  289. {
  290. title: "操作",
  291. valueType: "option",
  292. width: 120,
  293. render: (_text, record, _, action) =>
  294. record.isPreDefined
  295. ? []
  296. : [
  297. <a
  298. key="editable"
  299. onClick={() => {
  300. action?.startEditable?.(record.id);
  301. }}
  302. >
  303. 编辑
  304. </a>,
  305. <a
  306. key="delete"
  307. onClick={() => {
  308. setDataSource(
  309. dataSource.filter((item) => item.id !== record.id)
  310. );
  311. }}
  312. >
  313. 删除
  314. </a>,
  315. ],
  316. },
  317. ];
  318. const handleAdd = () => {
  319. return createColumn(props?.tableId, dataSource.length + 1);
  320. };
  321. // 上传字段模版文件
  322. const handleUpload = (file: UploadFile) => {
  323. message.loading("正在解析文件...", 0);
  324. parseExcel<any>(file)
  325. .then((res) => {
  326. console.log("加载数据:", res);
  327. const list = res?.["表单字段"];
  328. message.destroy();
  329. if (!list || !list.length) {
  330. message.warning("当前文件无字段数据,请检查");
  331. } else {
  332. importResultRef.current?.open(list, dataSource);
  333. }
  334. })
  335. .catch((err) => {
  336. console.error("加载数据失败:", err);
  337. message.error("文件解析失败");
  338. message.destroy();
  339. });
  340. };
  341. const handleChangeColumn = (list: ColumnItem[]) => {
  342. setDataSource(list);
  343. };
  344. return (
  345. <div className="w-full h-full overflow-auto" ref={boxRef}>
  346. <ImportResultModal
  347. ref={importResultRef}
  348. tableId={props?.tableId}
  349. onChange={handleChangeColumn}
  350. />
  351. <EditableProTable
  352. columns={columns}
  353. rowKey="id"
  354. value={dataSource}
  355. onChange={setDataSource}
  356. recordCreatorProps={{
  357. record: handleAdd,
  358. }}
  359. editable={{
  360. type: "multiple",
  361. editableKeys,
  362. onChange: setEditableRowKeys,
  363. }}
  364. toolBarRender={() => [
  365. <Tooltip
  366. key="info"
  367. title={
  368. <div>
  369. <p>填写规范:</p>
  370. <p>
  371. 1、字段名第一位必须为大写字母,之后的对象可以字母大小写,数字或下划线;
  372. </p>
  373. <p>2、Nvarchar类型长度最大不可以超过4000;</p>
  374. <p>
  375. 3、Decimal类型精度的填写方式:总精度,有效小数位,eg: 18,6;
  376. </p>
  377. <p>4、Int、Datetime等类型长度需填写为0;</p>
  378. </div>
  379. }
  380. >
  381. <InfoCircleOutlined />
  382. </Tooltip>,
  383. <Button key="download" icon={<DownloadOutlined />}>
  384. <a
  385. download={"BusinessTableColumnsImportTemplate.xlsx"}
  386. href="/Content/Template/BusinessTableColumnsImportTemplate.xlsx"
  387. >
  388. 下载模版
  389. </a>
  390. </Button>,
  391. <Upload
  392. key="upload"
  393. accept=".xlsx"
  394. showUploadList={false}
  395. beforeUpload={(file) => handleUpload(file)}
  396. >
  397. <Button
  398. type="primary"
  399. icon={<UploadOutlined />}
  400. onClick={() => {
  401. handleAdd();
  402. }}
  403. >
  404. 导入字段
  405. </Button>
  406. </Upload>,
  407. ]}
  408. />
  409. </div>
  410. );
  411. }