TableItem.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import {
  2. DeleteOutlined,
  3. PlusOutlined,
  4. } from "@ant-design/icons";
  5. import {
  6. Col,
  7. Row,
  8. Form,
  9. Input,
  10. Select,
  11. Button,
  12. Tooltip,
  13. Popover,
  14. Popconfirm,
  15. } from "antd";
  16. import React, { useEffect, useState } from "react";
  17. import CustomColorPicker from "@/components/CustomColorPicker";
  18. import { ColumnItem as ColumnItemType, TableItemType } from "@/type";
  19. import { TABLE_TYPE_OPTIONS } from "@/constants";
  20. import { createColumn } from "@/utils";
  21. import { useModel } from "umi";
  22. import { DndContext } from "@dnd-kit/core";
  23. import type { DragEndEvent } from "@dnd-kit/core";
  24. import {
  25. SortableContext,
  26. arrayMove,
  27. verticalListSortingStrategy,
  28. } from "@dnd-kit/sortable";
  29. import { restrictToParentElement } from "@dnd-kit/modifiers";
  30. import ColumnItem from "./ColumnItem";
  31. import LangInput from "@/components/LangInput";
  32. import LangInputTextarea from "@/components/LangInputTextarea";
  33. import { validateAliasName, validateTableCode } from "@/utils/validator";
  34. export default function TableItem({
  35. data,
  36. onChange,
  37. active,
  38. setActive,
  39. }: {
  40. data: TableItemType;
  41. onChange: (data: TableItemType) => void;
  42. active?: string;
  43. setActive: (active: string) => void;
  44. }) {
  45. const { tableColumnList = [] } = data;
  46. const { addTable, deleteTable } = useModel("erModel");
  47. const [list, setList] = useState(tableColumnList);
  48. const [table, setTable] = React.useState(data?.table);
  49. useEffect(() => {
  50. setList(tableColumnList);
  51. }, [tableColumnList]);
  52. const handleTableChange = (key: string, value: any) => {
  53. onChange({
  54. tableColumnList,
  55. table: { ...table, [key]: value },
  56. isTable: true,
  57. });
  58. setTable({ ...table, [key]: value });
  59. };
  60. const handleChangeColumn = (index: number, key: string, value: any) => {
  61. const data = tableColumnList[index];
  62. const newData = { ...data, [key]: value };
  63. setList(tableColumnList.map((item, i) => (i === index ? newData : item)));
  64. onChange({
  65. isTable: true,
  66. table,
  67. tableColumnList: tableColumnList.map((item, i) => {
  68. if (index === i) {
  69. return { ...item, [key]: value };
  70. }
  71. return item;
  72. }),
  73. });
  74. };
  75. // 添加表
  76. const handleAddChildTable = () => {
  77. addTable(table.id);
  78. };
  79. // 添加字段
  80. const handleAddColumn = () => {
  81. const newColumn: ColumnItemType = createColumn(
  82. table.id,
  83. tableColumnList.length + 1
  84. );
  85. onChange({
  86. table,
  87. tableColumnList: [...tableColumnList, newColumn],
  88. isTable: true,
  89. });
  90. };
  91. const handleDeleteColumn = (columnId: string) => {
  92. onChange({
  93. table,
  94. tableColumnList: tableColumnList.filter((item) => item.id !== columnId),
  95. isTable: true,
  96. });
  97. };
  98. const handleDragEnd = (dragItem: DragEndEvent) => {
  99. const { active, over } = dragItem;
  100. if (!active || !over) return; // 处理边界情况
  101. const activeIndex = tableColumnList.findIndex(
  102. (item) => item.id === active.id
  103. );
  104. const overIndex = tableColumnList.findIndex((item) => item.id === over.id);
  105. const newList = arrayMove(tableColumnList, activeIndex, overIndex);
  106. setList(newList);
  107. onChange({
  108. table,
  109. tableColumnList: newList,
  110. isTable: true,
  111. });
  112. };
  113. return (
  114. <div
  115. className="
  116. w-full
  117. border-l-solid
  118. border-l-[#e6e6e6]
  119. border-l-[4px]
  120. border-b-solid
  121. border-b-[#e4e4e4]
  122. border-b-[1px]
  123. m-b-4px
  124. p-l-16px"
  125. style={{
  126. borderLeftColor: table.style?.color || "#eee",
  127. marginLeft: table.parentBusinessTableId ? 10 : 0,
  128. }}
  129. >
  130. <div
  131. className="
  132. header
  133. flex
  134. items-center
  135. justify-between
  136. leading-[40px]
  137. px-[10px]
  138. hover:bg-[#fafafa]
  139. cursor-pointer
  140. m-b-[10px]"
  141. onClick={() => setActive(active === table.id ? "" : table.id)}
  142. >
  143. <div className="font-bold truncate flex-1">
  144. {table.schemaName}(
  145. {table?.langNameList?.find((item) => item.name === "zh-CN")?.value})
  146. </div>
  147. <div>
  148. <Popover
  149. trigger="click"
  150. placement="right"
  151. content={
  152. <div className="w-200px" onClick={(e) => e.stopPropagation()}>
  153. <Form layout="vertical" initialValues={table}>
  154. <Form.Item label="表名称" name="langNameList">
  155. <LangInput
  156. value={table.langNameList}
  157. onChange={(lang) => {
  158. handleTableChange("langNameList", lang);
  159. }}
  160. />
  161. </Form.Item>
  162. <Form.Item label="描述" name="langDescriptionList">
  163. <LangInputTextarea
  164. value={table.langDescriptionList}
  165. onChange={(lang) => {
  166. handleTableChange("langDescriptionList", lang);
  167. }}
  168. />
  169. </Form.Item>
  170. </Form>
  171. </div>
  172. }
  173. >
  174. <i
  175. className="iconfont icon-shezhi mr-[10px] cursor-pointer"
  176. onClick={(e) => e.stopPropagation()}
  177. />
  178. </Popover>
  179. <i
  180. className="iconfont icon-open inline-block"
  181. style={{
  182. transform:
  183. active === table.id ? "rotate(180deg)" : "rotate(0deg)",
  184. transition: "all 0.3s",
  185. }}
  186. />
  187. </div>
  188. </div>
  189. <div
  190. className="content overflow-hidden"
  191. style={{
  192. display: "grid",
  193. gridTemplateRows: active === table.id ? "1fr" : "0fr",
  194. transition: "all 0.3s",
  195. }}
  196. >
  197. <div className="overflow-hidden">
  198. <Form
  199. layout="horizontal"
  200. labelCol={{ span: 8 }}
  201. initialValues={table}
  202. >
  203. <Form.Item
  204. label="类型"
  205. labelCol={{ span: 4 }}
  206. wrapperCol={{ span: 21 }}
  207. >
  208. <Select
  209. placeholder="请选择"
  210. options={TABLE_TYPE_OPTIONS}
  211. value={table.type}
  212. disabled
  213. onChange={(val) => handleTableChange("type", val)}
  214. />
  215. </Form.Item>
  216. <Row gutter={8}>
  217. <Col span={12}>
  218. <Form.Item
  219. label="编码"
  220. name="schemaName"
  221. rules={[
  222. { required: true, message: "请输入编码" },
  223. validateTableCode,
  224. ]}
  225. >
  226. <Tooltip title={table.schemaName}>
  227. <Input
  228. placeholder="请输入"
  229. value={table.schemaName}
  230. onChange={(e) =>
  231. handleTableChange("schemaName", e.target.value)
  232. }
  233. />
  234. </Tooltip>
  235. </Form.Item>
  236. </Col>
  237. <Col span={12}>
  238. <Form.Item
  239. label="别名"
  240. name="aliasName"
  241. rules={[
  242. { required: true, message: "请输入编码" },
  243. validateAliasName,
  244. ]}
  245. >
  246. <Tooltip title={table.aliasName}>
  247. <Input
  248. placeholder="请输入"
  249. value={table.aliasName}
  250. onChange={(e) =>
  251. handleTableChange("aliasName", e.target.value)
  252. }
  253. />
  254. </Tooltip>
  255. </Form.Item>
  256. </Col>
  257. </Row>
  258. </Form>
  259. <div className="flex justify-between m-b-10px">
  260. <CustomColorPicker
  261. color={table.style?.background}
  262. onChange={(color) =>
  263. handleTableChange("style", { ...table.style, color })
  264. }
  265. >
  266. <div
  267. className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none cursor-pointer shadow-inner"
  268. style={{ background: table.style?.color || "#eee" }}
  269. ></div>
  270. </CustomColorPicker>
  271. <div className="flex gap-4px">
  272. {!table.parentBusinessTableId && (
  273. <Button
  274. type="primary"
  275. className="w-50px"
  276. onClick={handleAddChildTable}
  277. >
  278. 子表
  279. </Button>
  280. )}
  281. <Button
  282. type="primary"
  283. className="w-50px"
  284. onClick={handleAddColumn}
  285. >
  286. 字段
  287. </Button>
  288. <Popconfirm
  289. okType="primary"
  290. title="确定删除该表?"
  291. okText="确定"
  292. cancelText="取消"
  293. onConfirm={() => deleteTable(table.id)}
  294. >
  295. <div className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none text-center leading-32px color-red cursor-pointer">
  296. <DeleteOutlined />
  297. </div>
  298. </Popconfirm>
  299. </div>
  300. </div>
  301. <div className="column-content border-solid border-1px border-#e4e4e4 border-x-none p-y-10px">
  302. {/* 字段内容 */}
  303. <DndContext
  304. onDragEnd={handleDragEnd}
  305. modifiers={[restrictToParentElement]}
  306. >
  307. <SortableContext
  308. items={list.map((item) => item.id)}
  309. strategy={verticalListSortingStrategy}
  310. >
  311. {list.map((column, index) => {
  312. return (
  313. <ColumnItem
  314. key={column.id}
  315. column={column}
  316. onChange={(key, val) =>
  317. handleChangeColumn(index, key, val)
  318. }
  319. onDelete={handleDeleteColumn}
  320. />
  321. );
  322. })}
  323. </SortableContext>
  324. </DndContext>
  325. </div>
  326. </div>
  327. </div>
  328. </div>
  329. );
  330. }