ColumnItem.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import React, { useMemo, useState } from "react";
  2. import { HolderOutlined } from "@ant-design/icons";
  3. import {
  4. Button,
  5. Form,
  6. Input,
  7. InputNumber,
  8. Popover,
  9. Row,
  10. Col,
  11. Tooltip,
  12. Select,
  13. } from "antd";
  14. import { ColumnItem as ColumnItemType } from "@/type";
  15. import { DATA_TYPE_OPTIONS } from "@/constants";
  16. import { useSortable } from "@dnd-kit/sortable";
  17. import { CSS } from "@dnd-kit/utilities";
  18. import LangInput from "@/components/LangInput";
  19. import { DataType } from "@/enum";
  20. export default function ColumnItem({
  21. column,
  22. onChange,
  23. onDelete,
  24. }: {
  25. column: ColumnItemType;
  26. onChange: (key: string, value: any) => void;
  27. onDelete: (id: string) => void;
  28. }) {
  29. const { setNodeRef, attributes, listeners, transform, transition } =
  30. useSortable({
  31. id: column.id,
  32. transition: {
  33. duration: 500,
  34. easing: "cubic-bezier(0.25, 1, 0.5, 1)",
  35. },
  36. });
  37. const [code, setCode] = useState(column.schemaName);
  38. const [open, setOpen] = React.useState(false);
  39. const styles = {
  40. transform: CSS.Transform.toString(transform),
  41. transition,
  42. };
  43. const validCodeErrMsg = useMemo(() => {
  44. setOpen(true);
  45. if (!code) return "编码不能为空";
  46. if (code.length >= 50) {
  47. return "编码长度不能超过50";
  48. }
  49. const regex = /^[a-z][a-z0-9_]*$/;
  50. if (!regex.test(code)) return "编码只能包含字母和数字, 必须小写字母开头!";
  51. setOpen(false);
  52. return false;
  53. }, [code]);
  54. const handleSetCode = () => {
  55. if (code && !validCodeErrMsg) {
  56. onChange("schemaName", code);
  57. }
  58. if (!code) {
  59. setCode(column.schemaName);
  60. }
  61. };
  62. return (
  63. <div
  64. key={column.id}
  65. className="column-item flex gap-4px items-center jutify-space-between hover:bg-gray-100 mb-4px"
  66. style={styles}
  67. ref={setNodeRef}
  68. {...attributes}
  69. data-cypress="draggable-item"
  70. >
  71. <HolderOutlined
  72. className="cursor-move"
  73. data-cypress="draggable-handle"
  74. {...listeners}
  75. />
  76. <Tooltip title="字段编码">
  77. <Popover
  78. title={<span className="text-red">{validCodeErrMsg}</span>}
  79. open={open}
  80. onOpenChange={(o) => setOpen(o && !!validCodeErrMsg)}
  81. trigger={["focus"]}
  82. >
  83. <Input
  84. placeholder="编码"
  85. defaultValue={code}
  86. className="flex-1"
  87. disabled={column.isPreDefined}
  88. onChange={(e) => setCode(e.target.value)}
  89. onBlur={handleSetCode}
  90. status={validCodeErrMsg ? "error" : ""}
  91. />
  92. </Popover>
  93. </Tooltip>
  94. <Tooltip title="字段类型">
  95. <Select
  96. placeholder="类型"
  97. className="w-80px"
  98. options={DATA_TYPE_OPTIONS}
  99. value={column.type}
  100. disabled={column.isPreDefined}
  101. onChange={(value) => onChange("type", value)}
  102. dropdownStyle={{ width: 120 }}
  103. />
  104. </Tooltip>
  105. <Tooltip title="非空">
  106. <div
  107. className="
  108. rounded-4px
  109. cus-btn
  110. w-32px
  111. h-32px
  112. bg-#eee
  113. flex-none
  114. text-center
  115. leading-32px
  116. hover:bg-#ddd"
  117. style={{
  118. ...(column.isRequired
  119. ? { background: "#1677ff", color: "#fff" }
  120. : {}),
  121. cursor: column.isPreDefined ? "not-allowed" : "pointer",
  122. }}
  123. onClick={() =>
  124. !column.isPreDefined && onChange("isRequired", !column.isRequired)
  125. }
  126. >
  127. !
  128. </div>
  129. </Tooltip>
  130. <Tooltip title="唯一">
  131. <div
  132. className="
  133. rounded-4px
  134. cus-btn
  135. w-32px
  136. h-32px
  137. bg-#eee
  138. flex-none
  139. text-center
  140. leading-32px
  141. cursor-pointer
  142. hover:bg-#ddd"
  143. style={{
  144. ...(column.isUnique
  145. ? { background: "#1677ff", color: "#fff" }
  146. : {}),
  147. cursor: column.isPreDefined ? "not-allowed" : "pointer",
  148. }}
  149. onClick={() =>
  150. !column.isPreDefined && onChange("isUnique", !column.isUnique)
  151. }
  152. >
  153. 1
  154. </div>
  155. </Tooltip>
  156. <Popover
  157. trigger="click"
  158. placement="right"
  159. content={
  160. <div
  161. className="w-360px max-h-400px overflow-hidden"
  162. onClick={(e) => e.stopPropagation()}
  163. >
  164. <Form layout="vertical">
  165. <Row gutter={8}>
  166. <Col span={12}>
  167. <Form.Item label="字段名称">
  168. <LangInput
  169. disabled={column.isPreDefined}
  170. value={column.langNameList}
  171. onChange={(val) => {
  172. onChange("langNameList", val);
  173. }}
  174. />
  175. </Form.Item>
  176. </Col>
  177. <Col span={12}>
  178. <Form.Item label="默认值">
  179. <Input
  180. className="w-full"
  181. placeholder="默认值"
  182. disabled={column.isPreDefined}
  183. value={column.defaultValue}
  184. onChange={(e) => onChange("defaultValue", e.target.value)}
  185. />
  186. </Form.Item>
  187. </Col>
  188. </Row>
  189. {column.type === DataType.Nvarchar && (
  190. <Row gutter={8}>
  191. <Col span={12}>
  192. <Form.Item label="长度">
  193. <InputNumber
  194. placeholder="请输入"
  195. min={0}
  196. className="w-full"
  197. disabled={column.isPreDefined}
  198. value={column.maxLength}
  199. onChange={(num) => onChange("maxLength", num)}
  200. />
  201. </Form.Item>
  202. </Col>
  203. </Row>
  204. )}
  205. {column.type === DataType.Decimal && (
  206. <Row gutter={8}>
  207. <Col span={12}>
  208. <Form.Item label="总长度">
  209. <InputNumber
  210. placeholder="请输入"
  211. min={0}
  212. step={1}
  213. className="w-full"
  214. disabled={column.isPreDefined}
  215. value={column.precision}
  216. onChange={(num) => onChange("precision", num)}
  217. />
  218. </Form.Item>
  219. </Col>
  220. <Col span={12}>
  221. <Form.Item label="小数位数">
  222. <InputNumber
  223. placeholder="请输入"
  224. min={0}
  225. step={1}
  226. className="w-full"
  227. disabled={column.isPreDefined}
  228. value={column.scale}
  229. onChange={(num) => onChange("scale", num)}
  230. />
  231. </Form.Item>
  232. </Col>
  233. </Row>
  234. )}
  235. <Row>
  236. <Col span={24}>
  237. <Form.Item label="描述">
  238. <Input.TextArea
  239. placeholder="请输入"
  240. disabled={column.isPreDefined}
  241. value={column.memo}
  242. onChange={(e) => onChange("memo", e.target.value)}
  243. />
  244. </Form.Item>
  245. </Col>
  246. </Row>
  247. </Form>
  248. {!column.isPreDefined && (
  249. <Button
  250. type="default"
  251. danger
  252. className="w-full"
  253. onClick={() => onDelete(column.id)}
  254. >
  255. 删除
  256. </Button>
  257. )}
  258. </div>
  259. }
  260. >
  261. <div className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none text-center leading-32px cursor-pointer hover:bg-#ddd">
  262. <i className="iconfont icon-gengduo" />
  263. </div>
  264. </Popover>
  265. </div>
  266. );
  267. }