GraphStyle.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. import CustomColorPicker from "@/components/CustomColorPicker";
  2. import {
  3. BoldOutlined,
  4. ColumnHeightOutlined,
  5. FontSizeOutlined,
  6. ItalicOutlined,
  7. PictureOutlined,
  8. StrikethroughOutlined,
  9. SwapOutlined,
  10. UnderlineOutlined,
  11. VerticalAlignBottomOutlined,
  12. VerticalAlignMiddleOutlined,
  13. VerticalAlignTopOutlined,
  14. } from "@ant-design/icons";
  15. import {
  16. Button,
  17. Col,
  18. Divider,
  19. Form,
  20. Input,
  21. InputNumber,
  22. Row,
  23. Select,
  24. Tooltip,
  25. } from "antd";
  26. import { arrowOptions } from "@/pages/flow/data";
  27. import { useModel } from "umi";
  28. import { useEffect, useRef, useState } from "react";
  29. import { ImageFillType, ConnectorType, LineType } from "@/enum";
  30. import { set, cloneDeep } from "lodash-es";
  31. import { Cell } from "@antv/x6";
  32. import { fontFamilyOptions, alignOptionData } from '@/pages/flow/data';
  33. import { alignCell, matchSize } from '@/utils';
  34. type FormModel = {
  35. opacity: number;
  36. width: number;
  37. height: number;
  38. x: number;
  39. y: number;
  40. rotation: number;
  41. text: {
  42. fontFamily: string;
  43. color: string;
  44. fontSize: number;
  45. lineHeight: number;
  46. textAlign: "left" | "center" | "right";
  47. textVAlign: "top" | "middle" | "bottom";
  48. bold: boolean;
  49. italic: boolean;
  50. textDecoration: "underline" | "line-through" | "none";
  51. };
  52. connectorType: ConnectorType;
  53. startArrow: string;
  54. endArrow: string;
  55. fill: {
  56. fillType: "color" | "gradient" | "image";
  57. color1: string;
  58. color2: string;
  59. gradientType: "linear" | "radial";
  60. gradientValue: number;
  61. objectFit: ImageFillType;
  62. imageUrl: string;
  63. };
  64. stroke: {
  65. type: "solid" | "dashed" | "dotted" | "dashdot";
  66. color: string;
  67. width: number;
  68. };
  69. };
  70. export default function GraphStyle() {
  71. const { selectedCell } = useModel("graphModel");
  72. const [isMulit, setIsMulit] = useState(false);
  73. const [hasEdge, setHasEdge] = useState(false);
  74. const eventNodeList = useRef<Cell[]>([]);
  75. const [formModel, setFormModel] = useState<FormModel>({
  76. opacity: 100,
  77. width: 20,
  78. height: 20,
  79. x: 0,
  80. y: 0,
  81. rotation: 0,
  82. text: {
  83. fontFamily: "normal",
  84. color: "#000000",
  85. fontSize: 14,
  86. lineHeight: 1.25,
  87. textAlign: "center",
  88. textVAlign: "middle",
  89. bold: false,
  90. italic: false,
  91. textDecoration: "none",
  92. },
  93. fill: {
  94. fillType: "color",
  95. color1: "#FFFFFF",
  96. color2: "#eeeeee",
  97. gradientType: "linear",
  98. gradientValue: 0,
  99. objectFit: ImageFillType.Fill,
  100. imageUrl: "",
  101. },
  102. stroke: {
  103. type: "solid",
  104. color: "#323232",
  105. width: 1,
  106. },
  107. connectorType: ConnectorType.Normal,
  108. startArrow: "",
  109. endArrow: "",
  110. });
  111. useEffect(() => {
  112. const firstNode = selectedCell?.find((item) => item.isNode());
  113. const firstEdge = selectedCell?.find((item) => item.isEdge());
  114. eventNodeList.current = [];
  115. if (firstNode) {
  116. const position = firstNode.position();
  117. const size = firstNode.size();
  118. const data = firstNode.getData();
  119. setFormModel({
  120. ...formModel,
  121. x: position.x,
  122. y: position.y,
  123. width: size.width,
  124. height: size.height,
  125. rotation: firstNode.angle(),
  126. text: data.text,
  127. fill: data.fill,
  128. stroke: data.stroke,
  129. connectorType: ConnectorType.Normal,
  130. startArrow: "",
  131. endArrow: "",
  132. });
  133. // 监听当前选中节点的属性变化
  134. if (!eventNodeList.current.find((item) => item.id === firstNode.id)) {
  135. eventNodeList.current.push(firstNode);
  136. firstNode.on("change:*", (args) => {
  137. if (args.key === "position") {
  138. setFormModel((state) => {
  139. return {
  140. ...state,
  141. x: parseInt(args.current.x),
  142. y: parseInt(args.current.y),
  143. };
  144. });
  145. }
  146. if (args.key === "size") {
  147. setFormModel((state) => {
  148. return {
  149. ...state,
  150. width: args.current.width,
  151. height: args.current.height,
  152. };
  153. });
  154. }
  155. if (args.key === "data") {
  156. setFormModel((state) => {
  157. return {
  158. ...state,
  159. text: args.current.text,
  160. fill: args.current.fill,
  161. stroke: args.current.stroke,
  162. };
  163. });
  164. }
  165. });
  166. }
  167. }
  168. if(firstEdge) {
  169. const data = firstEdge.getData();
  170. const attrs = firstEdge.attrs || {};
  171. const sourceMarker = attrs.line?.sourceMarker as Record<string, any>;
  172. const targetMarker = attrs.line?.targetMarker as Record<string, any>;
  173. const lineType = attrs.line?.strokeDasharray === LineType.solid
  174. ? "solid"
  175. : attrs.line?.strokeDasharray === LineType.dashed
  176. ? "dashed"
  177. : attrs.line?.strokeDasharray === LineType.dotted
  178. ? "dotted"
  179. : "dashdot";
  180. let obj = {};
  181. if(!firstNode) {
  182. obj = {
  183. stroke: {
  184. type: lineType,
  185. color: attrs.line?.stroke || "#000000",
  186. width: attrs.line?.strokeWidth || 1,
  187. }
  188. }
  189. }
  190. setFormModel((state) => {
  191. return {
  192. ...state,
  193. ...obj,
  194. startArrow: sourceMarker?.name,
  195. endArrow: targetMarker?.name,
  196. }
  197. })
  198. }
  199. let nodeCount = 0;
  200. selectedCell?.forEach((cell) => {
  201. if (cell.isEdge()) {
  202. // 存在边线
  203. setHasEdge(true);
  204. }
  205. if (cell.isNode()) nodeCount++;
  206. });
  207. // 多个节点
  208. setIsMulit(nodeCount > 1);
  209. }, [selectedCell]);
  210. // 表单值改变,修改元素属性
  211. const handleChange = (model: FormModel) => {
  212. selectedCell?.forEach((cell) => {
  213. if (cell.isNode()) {
  214. cell.setPosition(model.x, model.y);
  215. cell.setSize(model.width, model.height);
  216. cell.rotate(model.rotation, { absolute: true });
  217. cell.setData({
  218. text: model.text,
  219. fill: model.fill,
  220. stroke: model.stroke,
  221. opacity: model.opacity,
  222. });
  223. }
  224. if (cell.isEdge()) {
  225. const attr = cell.attrs;
  226. const sourceMarker = attr?.line?.sourceMarker as Record<string, any>;
  227. const targetMarker = attr?.line?.targetMarker as Record<string, any>;
  228. cell.setAttrs({
  229. line: {
  230. ...(attr?.line || {}),
  231. stroke: model.stroke.color,
  232. strokeWidth: model.stroke.width,
  233. strokeDasharray: LineType[model.stroke.type],
  234. targetMarker: {
  235. ...(targetMarker || {}),
  236. name: model.endArrow,
  237. args: {
  238. size: model.stroke.width + 8,
  239. }
  240. },
  241. sourceMarker: {
  242. ...(sourceMarker || {}),
  243. name: model.startArrow,
  244. args: {
  245. size: model.stroke.width + 8,
  246. }
  247. }
  248. }
  249. })
  250. }
  251. });
  252. };
  253. // 设置对齐方式
  254. const handleAlign = (type: string) => {
  255. selectedCell && alignCell(type, selectedCell);
  256. };
  257. // 匹配宽高
  258. const handleMatchSize = (type: "width" | "height" | "auto") => {
  259. selectedCell && matchSize(type, selectedCell);
  260. };
  261. // 调换渐变色颜色
  262. const handleSwapColor = () => {
  263. const { color1, color2 } = formModel.fill;
  264. handleSetFormModel("fill", {
  265. ...formModel.fill,
  266. color1: color2,
  267. color2: color1,
  268. });
  269. };
  270. // 设置表单数据
  271. const handleSetFormModel = (key: string, value: any) => {
  272. const obj = cloneDeep(formModel);
  273. set(obj, key, value);
  274. setFormModel(obj);
  275. handleChange(obj);
  276. };
  277. return (
  278. <div>
  279. <section className="px-16px">
  280. <div className="bg-white rounded-s flex justify-between items-center mb-8px">
  281. {alignOptionData.map((item) => (
  282. <Tooltip key={item.id} title={item.name}>
  283. <Button
  284. type="text"
  285. icon={<i className={"iconfont " + item.icon} />}
  286. disabled={!isMulit}
  287. onClick={() => handleAlign(item.id)}
  288. />
  289. </Tooltip>
  290. ))}
  291. </div>
  292. <Form.Item
  293. label="不透明度"
  294. labelCol={{ span: 14 }}
  295. wrapperCol={{ span: 10 }}
  296. labelAlign="left"
  297. className="mb-8px"
  298. colon={false}
  299. >
  300. <InputNumber
  301. className="w-full"
  302. step={5}
  303. min={0}
  304. max={100}
  305. formatter={(val) => `${val}%`}
  306. disabled={!selectedCell?.length}
  307. value={formModel.opacity}
  308. onChange={(val) => handleSetFormModel("opacity", val)}
  309. />
  310. </Form.Item>
  311. </section>
  312. <Divider className="my-8px" />
  313. <section className="px-16px">
  314. <div className="font-bold mb-8px">布局</div>
  315. <Row gutter={8} className="mb-8px">
  316. <Col span={12}>
  317. <InputNumber
  318. className="w-full"
  319. step={1}
  320. min={20}
  321. max={10000}
  322. suffix="W"
  323. formatter={(val) => `${val}px`}
  324. disabled={!selectedCell?.length}
  325. value={formModel.width}
  326. onChange={(val) => handleSetFormModel("width", val)}
  327. />
  328. </Col>
  329. <Col span={12}>
  330. <InputNumber
  331. className="w-full"
  332. step={1}
  333. min={20}
  334. max={10000}
  335. suffix="H"
  336. formatter={(val) => `${val}px`}
  337. disabled={!selectedCell?.length}
  338. value={formModel.height}
  339. onChange={(val) => handleSetFormModel("height", val)}
  340. />
  341. </Col>
  342. </Row>
  343. <Row gutter={8} className="mb-8px">
  344. <Col span={12}>
  345. <InputNumber
  346. className="w-full"
  347. step={1}
  348. min={0}
  349. max={10000}
  350. precision={0}
  351. suffix="X"
  352. formatter={(val) => `${val}px`}
  353. disabled={!selectedCell?.length}
  354. value={formModel.x}
  355. onChange={(val) => handleSetFormModel("x", val)}
  356. />
  357. </Col>
  358. <Col span={12}>
  359. <InputNumber
  360. className="w-full"
  361. step={1}
  362. min={0}
  363. max={10000}
  364. precision={0}
  365. suffix="Y"
  366. formatter={(val) => `${val}px`}
  367. disabled={!selectedCell?.length}
  368. value={formModel.y}
  369. onChange={(val) => handleSetFormModel("y", val)}
  370. />
  371. </Col>
  372. </Row>
  373. <div className="flex justify-between items-center gap-12px mb-8px">
  374. <InputNumber
  375. className="flex-1"
  376. step={1}
  377. min={0}
  378. max={360}
  379. formatter={(val) => `${val}°`}
  380. suffix={<i className="iconfont icon-a-ziyuan126 text-12px" />}
  381. disabled={!selectedCell?.length}
  382. value={formModel.rotation}
  383. onChange={(val) => handleSetFormModel("rotation", val)}
  384. />
  385. <Button
  386. icon={<i className="iconfont icon-shangxiafanzhuan" />}
  387. disabled={!selectedCell?.length}
  388. onClick={() => handleSetFormModel("rotation", 0)}
  389. />
  390. <Button
  391. icon={<i className="iconfont icon-zuoyoufanzhuan" />}
  392. disabled={!selectedCell?.length}
  393. onClick={() => handleSetFormModel("rotation", 90)}
  394. />
  395. </div>
  396. <Form.Item
  397. label="匹配大小"
  398. labelCol={{ span: 14 }}
  399. wrapperCol={{ span: 10 }}
  400. labelAlign="left"
  401. className="mb-8px"
  402. colon={false}
  403. >
  404. <div className="bg-white rounded-s flex justify-between items-center mb-8px">
  405. <Tooltip title="适配宽度">
  406. <Button
  407. type="text"
  408. icon={<i className="iconfont icon-kuandu" />}
  409. disabled={!isMulit}
  410. onClick={() => handleMatchSize("width")}
  411. />
  412. </Tooltip>
  413. <Tooltip title="适配高度">
  414. <Button
  415. type="text"
  416. icon={<i className="iconfont icon-gaodu" />}
  417. disabled={!isMulit}
  418. onClick={() => handleMatchSize("height")}
  419. />
  420. </Tooltip>
  421. <Tooltip title="适配宽高">
  422. <Button
  423. type="text"
  424. icon={<i className="iconfont icon-shiyingkuangao" />}
  425. disabled={!isMulit}
  426. onClick={() => handleMatchSize("auto")}
  427. />
  428. </Tooltip>
  429. </div>
  430. </Form.Item>
  431. </section>
  432. <Divider className="my-8px" />
  433. <section className="px-16px">
  434. <div className="font-bold mb-8px">文本</div>
  435. <div className="flex items-center gap-12px mb-8px">
  436. <Select
  437. className="flex-1"
  438. disabled={!selectedCell?.length}
  439. options={fontFamilyOptions}
  440. value={formModel.text.fontFamily}
  441. labelRender={(item) => item.value}
  442. onChange={(val) => handleSetFormModel("text.fontFamily", val)}
  443. />
  444. <CustomColorPicker
  445. disabled={!selectedCell?.length}
  446. color={formModel.text.color}
  447. onChange={(color) => handleSetFormModel("text.color", color)}
  448. />
  449. </div>
  450. <div className="flex items-center gap-12px mb-8px">
  451. <InputNumber
  452. className="flex-1"
  453. prefix={<FontSizeOutlined />}
  454. step={1}
  455. min={12}
  456. max={10000}
  457. formatter={(val) => `${val}px`}
  458. disabled={!selectedCell?.length}
  459. value={formModel.text.fontSize}
  460. onChange={(val) => handleSetFormModel("text.fontSize", val)}
  461. />
  462. <Select
  463. className="flex-1"
  464. suffixIcon={<ColumnHeightOutlined />}
  465. options={[
  466. { label: "1.0", value: 1 },
  467. { label: "1.25", value: 1.25 },
  468. { label: "1.5", value: 1.5 },
  469. { label: "2.0", value: 2 },
  470. { label: "2.5", value: 2.5 },
  471. { label: "3.0", value: 3 },
  472. ]}
  473. disabled={!selectedCell?.length}
  474. value={formModel.text.lineHeight}
  475. onChange={(val) => handleSetFormModel("text.lineHeight", val)}
  476. />
  477. </div>
  478. <div className="flex items-center gap-12px mb-8px">
  479. <div className="bg-white rounded-s flex justify-between items-center mb-8px">
  480. <Tooltip title="左对齐">
  481. <Button
  482. type="text"
  483. icon={<i className="iconfont icon-zuoduiqi" />}
  484. className={formModel.text.textAlign === "left" ? "active" : ""}
  485. disabled={!selectedCell?.length}
  486. onClick={() => handleSetFormModel("text.textAlign", "left")}
  487. />
  488. </Tooltip>
  489. <Tooltip title="居中">
  490. <Button
  491. type="text"
  492. icon={<i className="iconfont icon-juzhong" />}
  493. className={
  494. formModel.text.textAlign === "center" ? "active" : ""
  495. }
  496. disabled={!selectedCell?.length}
  497. onClick={() => handleSetFormModel("text.textAlign", "center")}
  498. />
  499. </Tooltip>
  500. <Tooltip title="右对齐">
  501. <Button
  502. type="text"
  503. icon={<i className="iconfont icon-youduiqi" />}
  504. className={formModel.text.textAlign === "right" ? "active" : ""}
  505. disabled={!selectedCell?.length}
  506. onClick={() => handleSetFormModel("text.textAlign", "right")}
  507. />
  508. </Tooltip>
  509. </div>
  510. <div className="bg-white rounded-s flex justify-between items-center mb-8px">
  511. <Tooltip title="顶部对齐">
  512. <Button
  513. type="text"
  514. icon={<VerticalAlignTopOutlined />}
  515. disabled={!selectedCell?.length}
  516. className={formModel.text.textVAlign === "top" ? "active" : ""}
  517. onClick={() => handleSetFormModel("text.textVAlign", "top")}
  518. />
  519. </Tooltip>
  520. <Tooltip title="垂直居中">
  521. <Button
  522. type="text"
  523. icon={<VerticalAlignMiddleOutlined />}
  524. className={
  525. formModel.text.textVAlign === "middle" ? "active" : ""
  526. }
  527. disabled={!selectedCell?.length}
  528. onClick={() => handleSetFormModel("text.textVAlign", "middle")}
  529. />
  530. </Tooltip>
  531. <Tooltip title="底部对齐">
  532. <Button
  533. type="text"
  534. icon={<VerticalAlignBottomOutlined />}
  535. className={
  536. formModel.text.textVAlign === "bottom" ? "active" : ""
  537. }
  538. disabled={!selectedCell?.length}
  539. onClick={() => handleSetFormModel("text.textVAlign", "bottom")}
  540. />
  541. </Tooltip>
  542. </div>
  543. </div>
  544. <div className="flex items-center gap-12px mb-8px">
  545. <div className="bg-white rounded-s flex justify-between items-center mb-8px">
  546. <Tooltip placement="bottom" title="字体加粗">
  547. <Button
  548. type="text"
  549. icon={<BoldOutlined />}
  550. className={formModel.text.bold ? "active" : ""}
  551. disabled={!selectedCell?.length}
  552. onClick={() =>
  553. handleSetFormModel("text.bold", !formModel.text.bold)
  554. }
  555. />
  556. </Tooltip>
  557. <Tooltip placement="bottom" title="字体倾斜">
  558. <Button
  559. type="text"
  560. icon={<ItalicOutlined />}
  561. className={formModel.text.italic ? "active" : ""}
  562. disabled={!selectedCell?.length}
  563. onClick={() =>
  564. handleSetFormModel("text.italic", !formModel.text.italic)
  565. }
  566. />
  567. </Tooltip>
  568. <Tooltip placement="bottom" title="下划线">
  569. <Button
  570. type="text"
  571. icon={<UnderlineOutlined />}
  572. className={
  573. formModel.text.textDecoration === "underline" ? "active" : ""
  574. }
  575. disabled={!selectedCell?.length}
  576. onClick={() =>
  577. handleSetFormModel(
  578. "text.textDecoration",
  579. formModel.text.textDecoration === "underline"
  580. ? "none"
  581. : "underline"
  582. )
  583. }
  584. />
  585. </Tooltip>
  586. <Tooltip placement="bottom" title="中划线">
  587. <Button
  588. type="text"
  589. icon={<StrikethroughOutlined />}
  590. className={
  591. formModel.text.textDecoration === "line-through"
  592. ? "active"
  593. : ""
  594. }
  595. disabled={!selectedCell?.length}
  596. onClick={() =>
  597. handleSetFormModel(
  598. "text.textDecoration",
  599. formModel.text.textDecoration === "line-through"
  600. ? "none"
  601. : "line-through"
  602. )
  603. }
  604. />
  605. </Tooltip>
  606. </div>
  607. </div>
  608. </section>
  609. <Divider className="my-8px" />
  610. <section className="px-16px">
  611. <div className="font-bold mb-8px">填充</div>
  612. <div className="flex items-center gap-12px mb-8px">
  613. <Select
  614. className="flex-1"
  615. options={[
  616. { label: "纯色", value: "color" },
  617. { label: "渐变", value: "gradient" },
  618. { label: "图片", value: "image" },
  619. ]}
  620. disabled={!selectedCell?.length}
  621. value={formModel.fill.fillType}
  622. onChange={(value) => handleSetFormModel("fill.fillType", value)}
  623. />
  624. <div className="flex items-center gap-12px">
  625. <CustomColorPicker
  626. disabled={!selectedCell?.length}
  627. color={formModel.fill.color1}
  628. onChange={(color) => handleSetFormModel("fill.color1", color)}
  629. />
  630. {formModel.fill.fillType === "gradient" && (
  631. <>
  632. <Button
  633. icon={<SwapOutlined />}
  634. disabled={!selectedCell?.length}
  635. onClick={handleSwapColor}
  636. />
  637. <CustomColorPicker
  638. disabled={!selectedCell?.length}
  639. color={formModel.fill.color2}
  640. onChange={(color) => handleSetFormModel("fill.color2", color)}
  641. />
  642. </>
  643. )}
  644. </div>
  645. </div>
  646. {formModel.fill.fillType === "image" && (
  647. <>
  648. <Form.Item
  649. label="图片地址"
  650. labelCol={{ span: 14 }}
  651. wrapperCol={{ span: 10 }}
  652. labelAlign="left"
  653. className="mb-8px"
  654. colon={false}
  655. >
  656. <Input
  657. placeholder="图片地址"
  658. disabled={!selectedCell?.length}
  659. suffix={<PictureOutlined />}
  660. value={formModel.fill.imageUrl}
  661. onChange={(e) =>
  662. handleSetFormModel("fill.imageUrl", e.target.value)
  663. }
  664. />
  665. </Form.Item>
  666. <Form.Item
  667. label="填充方式"
  668. labelCol={{ span: 14 }}
  669. wrapperCol={{ span: 10 }}
  670. labelAlign="left"
  671. className="mb-8px"
  672. colon={false}
  673. >
  674. <Select
  675. options={[
  676. { value: ImageFillType.Fill, label: "填充" },
  677. { value: ImageFillType.Auto, label: "自适应" },
  678. { value: ImageFillType.Stretch, label: "按图形伸展" },
  679. { value: ImageFillType.Original, label: "原始尺寸" },
  680. { value: ImageFillType.Tiled, label: "平铺" },
  681. ]}
  682. disabled={!selectedCell?.length}
  683. value={formModel.fill.objectFit}
  684. onChange={(value) =>
  685. handleSetFormModel("fill.objectFit", value)
  686. }
  687. />
  688. </Form.Item>
  689. </>
  690. )}
  691. {formModel.fill.fillType === "gradient" && (
  692. <div className="flex items-center gap-12px">
  693. <Select
  694. className="flex-1"
  695. options={[
  696. { value: "linear", label: "线性渐变" },
  697. { value: "radial", label: "径向渐变" },
  698. ]}
  699. disabled={!selectedCell?.length}
  700. value={formModel.fill.gradientType}
  701. onChange={(value) => {
  702. handleSetFormModel("fill.gradientType", value);
  703. }}
  704. />
  705. {formModel.fill.fillType === "gradient" && (
  706. <>
  707. {formModel.fill.gradientType === "linear" && (
  708. <InputNumber
  709. className="flex-1"
  710. step={1}
  711. min={0}
  712. max={360}
  713. formatter={(val) => `${val}°`}
  714. suffix={
  715. <i className="iconfont icon-a-ziyuan126 text-12px" />
  716. }
  717. disabled={!selectedCell?.length}
  718. value={formModel.fill.gradientValue}
  719. onChange={(val) =>
  720. handleSetFormModel("fill.gradientValue", val)
  721. }
  722. />
  723. )}
  724. {formModel.fill.gradientType === "radial" && (
  725. <InputNumber
  726. className="flex-1"
  727. step={1}
  728. min={0}
  729. max={100}
  730. formatter={(val) => `${val}%`}
  731. suffix={
  732. <i className="iconfont icon-jingxiangjianbian text-12px" />
  733. }
  734. disabled={!selectedCell?.length}
  735. value={formModel.fill.gradientValue}
  736. onChange={(val) =>
  737. handleSetFormModel("fill.gradientValue", val)
  738. }
  739. />
  740. )}
  741. </>
  742. )}
  743. </div>
  744. )}
  745. </section>
  746. <Divider className="my-8px" />
  747. <section className="px-16px">
  748. <div className="font-bold mb-8px">线条</div>
  749. <div className="flex gap-12px mb-8px">
  750. <Select
  751. className="flex-1"
  752. options={[
  753. { label: "实线", value: "solid" },
  754. { label: "虚线", value: "dashed" },
  755. { label: "点线", value: "dotted" },
  756. { label: "点划线", value: "dashdot" },
  757. ]}
  758. disabled={!selectedCell?.length}
  759. value={formModel.stroke.type}
  760. onChange={(value) => handleSetFormModel("stroke.type", value)}
  761. />
  762. <CustomColorPicker
  763. disabled={!selectedCell?.length}
  764. color={formModel.stroke.color}
  765. onChange={(color) => handleSetFormModel("stroke.color", color)}
  766. />
  767. </div>
  768. <div className="flex gap-12px mb-8px">
  769. <InputNumber
  770. className="flex-1"
  771. step={1}
  772. min={1}
  773. max={10}
  774. formatter={(val) => `${val}px`}
  775. disabled={!selectedCell?.length}
  776. value={formModel.stroke.width}
  777. onChange={(val) => handleSetFormModel("stroke.width", val)}
  778. />
  779. <div className="bg-white rounded-s flex justify-between items-center">
  780. <Button
  781. type="text"
  782. icon={
  783. <i className="iconfont icon-a-icon16lianxianleixinghuizhilianxian" />
  784. }
  785. className={
  786. formModel.connectorType === ConnectorType.Rounded
  787. ? "active"
  788. : ""
  789. }
  790. disabled={!hasEdge}
  791. onClick={() =>
  792. handleSetFormModel("connectorType", ConnectorType.Rounded)
  793. }
  794. />
  795. <Button
  796. type="text"
  797. icon={
  798. <i className="iconfont icon-a-icon16lianxianleixingbeisaierquxian" />
  799. }
  800. className={
  801. formModel.connectorType === ConnectorType.Smooth ? "active" : ""
  802. }
  803. disabled={!hasEdge}
  804. onClick={() =>
  805. handleSetFormModel("connectorType", ConnectorType.Smooth)
  806. }
  807. />
  808. <Button
  809. type="text"
  810. icon={
  811. <i className="iconfont icon-a-icon16lianxianleixinghuizhizhixian" />
  812. }
  813. className={
  814. formModel.connectorType === ConnectorType.Normal ? "active" : ""
  815. }
  816. disabled={!hasEdge}
  817. onClick={() =>
  818. handleSetFormModel("connectorType", ConnectorType.Normal)
  819. }
  820. />
  821. </div>
  822. </div>
  823. <Form.Item
  824. label="箭头类型"
  825. labelAlign="left"
  826. colon={false}
  827. labelCol={{ span: 6 }}
  828. wrapperCol={{ span: 18 }}
  829. >
  830. <div className="flex gap-12px items-center">
  831. <Select
  832. options={arrowOptions.map((item) => {
  833. return {
  834. value: item.name,
  835. label: <img className="w-12px mx-50%" src={item.icon} />,
  836. };
  837. })}
  838. disabled={!hasEdge}
  839. value={formModel.startArrow}
  840. onChange={(value) => handleSetFormModel("startArrow", value)}
  841. />
  842. <Select
  843. options={arrowOptions.map((item) => {
  844. return {
  845. value: item.name,
  846. label: <img className="w-12px mx-50%" src={item.icon} />,
  847. };
  848. })}
  849. disabled={!hasEdge}
  850. value={formModel.endArrow}
  851. onChange={(value) => handleSetFormModel("endArrow", value)}
  852. />
  853. </div>
  854. </Form.Item>
  855. </section>
  856. </div>
  857. );
  858. }