sa2kit 1.6.89 → 1.6.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/{booking-473Db8Bo.d.mts → booking-BH7HM0D0.d.mts} +1 -0
  2. package/dist/{booking-473Db8Bo.d.ts → booking-BH7HM0D0.d.ts} +1 -0
  3. package/dist/{bookingAdminService-DqQ7hEGw.d.ts → bookingAdminService-nr1vOp6I.d.ts} +1 -1
  4. package/dist/{bookingAdminService-SBX4JA_U.d.mts → bookingAdminService-pvk2MY1r.d.mts} +1 -1
  5. package/dist/{client-Bkn6mRI7.d.ts → client-UDQ7uMFA.d.ts} +1 -1
  6. package/dist/{client-exYn2Qla.d.mts → client-jOToHJEx.d.mts} +1 -1
  7. package/dist/festivalCard/index.js +803 -212
  8. package/dist/festivalCard/index.js.map +1 -1
  9. package/dist/festivalCard/index.mjs +784 -193
  10. package/dist/festivalCard/index.mjs.map +1 -1
  11. package/dist/festivalCard/miniapp/index.js +162 -21
  12. package/dist/festivalCard/miniapp/index.js.map +1 -1
  13. package/dist/festivalCard/miniapp/index.mjs +153 -12
  14. package/dist/festivalCard/miniapp/index.mjs.map +1 -1
  15. package/dist/festivalCard/web/index.d.mts +17 -3
  16. package/dist/festivalCard/web/index.d.ts +17 -3
  17. package/dist/festivalCard/web/index.js +803 -212
  18. package/dist/festivalCard/web/index.js.map +1 -1
  19. package/dist/festivalCard/web/index.mjs +784 -193
  20. package/dist/festivalCard/web/index.mjs.map +1 -1
  21. package/dist/{index-z15F7afa.d.mts → index-Bs06cHTn.d.mts} +2 -2
  22. package/dist/{index-BJpxvH7X.d.ts → index-C-oNM7Gv.d.ts} +1 -1
  23. package/dist/{index-XTV6IU-M.d.ts → index-CUab5EBV.d.ts} +2 -2
  24. package/dist/{index-Cum2EknK.d.mts → index-CYDb3AKs.d.mts} +1 -1
  25. package/dist/{index-DyxLpkmm.d.mts → index-DBB4ad0S.d.mts} +2 -2
  26. package/dist/{index-CdTIsNsy.d.ts → index-DBHwbXrv.d.ts} +2 -2
  27. package/dist/index.js +575 -170
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +575 -170
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/showmasterpiece/core.d.mts +3 -3
  32. package/dist/showmasterpiece/core.d.ts +3 -3
  33. package/dist/showmasterpiece/db.d.mts +2 -0
  34. package/dist/showmasterpiece/db.d.ts +2 -0
  35. package/dist/showmasterpiece/db.js +4 -2
  36. package/dist/showmasterpiece/db.js.map +1 -1
  37. package/dist/showmasterpiece/db.mjs +4 -2
  38. package/dist/showmasterpiece/db.mjs.map +1 -1
  39. package/dist/showmasterpiece/index.js +18 -2
  40. package/dist/showmasterpiece/index.js.map +1 -1
  41. package/dist/showmasterpiece/index.mjs +18 -2
  42. package/dist/showmasterpiece/index.mjs.map +1 -1
  43. package/dist/showmasterpiece/logic/index.d.mts +2 -2
  44. package/dist/showmasterpiece/logic/index.d.ts +2 -2
  45. package/dist/showmasterpiece/server/index.js +4 -2
  46. package/dist/showmasterpiece/server/index.js.map +1 -1
  47. package/dist/showmasterpiece/server/index.mjs +4 -2
  48. package/dist/showmasterpiece/server/index.mjs.map +1 -1
  49. package/dist/showmasterpiece/service/api/index.d.mts +1 -1
  50. package/dist/showmasterpiece/service/api/index.d.ts +1 -1
  51. package/dist/showmasterpiece/service/client-business/index.d.mts +3 -3
  52. package/dist/showmasterpiece/service/client-business/index.d.ts +3 -3
  53. package/dist/showmasterpiece/service/index.d.mts +6 -6
  54. package/dist/showmasterpiece/service/index.d.ts +6 -6
  55. package/dist/showmasterpiece/service/miniapp/index.d.mts +2 -2
  56. package/dist/showmasterpiece/service/miniapp/index.d.ts +2 -2
  57. package/dist/showmasterpiece/service/web/index.d.mts +4 -4
  58. package/dist/showmasterpiece/service/web/index.d.ts +4 -4
  59. package/dist/showmasterpiece/ui/miniapp/index.d.mts +2 -2
  60. package/dist/showmasterpiece/ui/miniapp/index.d.ts +2 -2
  61. package/dist/showmasterpiece/ui/miniapp/index.js +4 -3
  62. package/dist/showmasterpiece/ui/miniapp/index.js.map +1 -1
  63. package/dist/showmasterpiece/ui/miniapp/index.mjs +4 -3
  64. package/dist/showmasterpiece/ui/miniapp/index.mjs.map +1 -1
  65. package/dist/showmasterpiece/ui/web/index.js +18 -2
  66. package/dist/showmasterpiece/ui/web/index.js.map +1 -1
  67. package/dist/showmasterpiece/ui/web/index.mjs +18 -2
  68. package/dist/showmasterpiece/ui/web/index.mjs.map +1 -1
  69. package/dist/showmasterpiece/web/index.js +18 -2
  70. package/dist/showmasterpiece/web/index.js.map +1 -1
  71. package/dist/showmasterpiece/web/index.mjs +18 -2
  72. package/dist/showmasterpiece/web/index.mjs.map +1 -1
  73. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7420,15 +7420,92 @@ var renderElement = (element) => {
7420
7420
  }
7421
7421
  );
7422
7422
  };
7423
- var FestivalCardPageRenderer = ({ page }) => {
7424
- const backgroundElement = page.elements.find(
7425
- (element) => element.type === "image" && Boolean(element.isBackground)
7423
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
7424
+ var FestivalCardPageRenderer = ({
7425
+ page,
7426
+ editable = false,
7427
+ selectedElementId = null,
7428
+ onElementSelect,
7429
+ onElementChange
7430
+ }) => {
7431
+ const [draggingElementId, setDraggingElementId] = React69.useState(null);
7432
+ const [resizingElementId, setResizingElementId] = React69.useState(null);
7433
+ const stageRef = React69.useRef(null);
7434
+ const interactionRef = React69.useRef(null);
7435
+ const backgroundElement = React69.useMemo(
7436
+ () => page.elements.find(
7437
+ (element) => element.type === "image" && Boolean(element.isBackground)
7438
+ ),
7439
+ [page]
7426
7440
  );
7427
- const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
7441
+ const foregroundElements = React69.useMemo(
7442
+ () => page.elements.filter((element) => !(element.type === "image" && element.isBackground)),
7443
+ [page]
7444
+ );
7445
+ const updateElementByPointer = (element, interaction, clientX, clientY) => {
7446
+ if (!onElementChange || interaction.rect.width <= 0 || interaction.rect.height <= 0) return;
7447
+ const xPercent = clamp((clientX - interaction.rect.left) / interaction.rect.width * 100, 0, 100);
7448
+ const yPercent = clamp((clientY - interaction.rect.top) / interaction.rect.height * 100, 0, 100);
7449
+ if (interaction.mode === "move") {
7450
+ onElementChange(element.id, { x: xPercent, y: yPercent });
7451
+ return;
7452
+ }
7453
+ const nextWidth = clamp(Math.abs(xPercent - element.x) * 2, 4, 100);
7454
+ if (element.type === "image") {
7455
+ const nextHeight = clamp(Math.abs(yPercent - element.y) * 2, 4, 100);
7456
+ onElementChange(element.id, { width: nextWidth, height: nextHeight });
7457
+ return;
7458
+ }
7459
+ onElementChange(element.id, { width: nextWidth });
7460
+ };
7461
+ const beginInteraction = (event, elementId, mode) => {
7462
+ if (!editable || !stageRef.current) return;
7463
+ event.preventDefault();
7464
+ event.stopPropagation();
7465
+ const rect = stageRef.current.getBoundingClientRect();
7466
+ interactionRef.current = {
7467
+ pointerId: event.pointerId,
7468
+ elementId,
7469
+ mode,
7470
+ rect
7471
+ };
7472
+ event.currentTarget.setPointerCapture(event.pointerId);
7473
+ onElementSelect?.(elementId);
7474
+ if (mode === "move") {
7475
+ setDraggingElementId(elementId);
7476
+ setResizingElementId(null);
7477
+ } else {
7478
+ setResizingElementId(elementId);
7479
+ setDraggingElementId(null);
7480
+ }
7481
+ const element = foregroundElements.find((item) => item.id === elementId);
7482
+ if (element) {
7483
+ updateElementByPointer(element, interactionRef.current, event.clientX, event.clientY);
7484
+ }
7485
+ };
7486
+ const handlePointerMove = (event) => {
7487
+ const interaction = interactionRef.current;
7488
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
7489
+ const element = foregroundElements.find((item) => item.id === interaction.elementId);
7490
+ if (!element) return;
7491
+ updateElementByPointer(element, interaction, event.clientX, event.clientY);
7492
+ };
7493
+ const endInteraction = (event) => {
7494
+ const interaction = interactionRef.current;
7495
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
7496
+ interactionRef.current = null;
7497
+ setDraggingElementId(null);
7498
+ setResizingElementId(null);
7499
+ };
7428
7500
  return /* @__PURE__ */ React69__namespace.default.createElement(
7429
7501
  "div",
7430
7502
  {
7431
- className: "relative h-full w-full overflow-hidden rounded-2xl",
7503
+ ref: stageRef,
7504
+ onPointerMove: editable ? handlePointerMove : void 0,
7505
+ onPointerUp: editable ? endInteraction : void 0,
7506
+ onPointerCancel: editable ? endInteraction : void 0,
7507
+ onClick: editable ? () => onElementSelect?.(null) : void 0,
7508
+ className: `relative h-full w-full overflow-hidden rounded-2xl ${editable ? "touch-none" : ""}`,
7432
7509
  style: {
7433
7510
  backgroundColor: page.background?.color || "#0f172a",
7434
7511
  backgroundImage: backgroundElement ? `url(${backgroundElement.src})` : page.background?.image ? `url(${page.background.image})` : void 0,
@@ -7437,17 +7514,249 @@ var FestivalCardPageRenderer = ({ page }) => {
7437
7514
  }
7438
7515
  },
7439
7516
  /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
7440
- foregroundElements.map(renderElement)
7517
+ foregroundElements.map((element) => {
7518
+ if (!editable) {
7519
+ return renderElement(element);
7520
+ }
7521
+ const isSelected = selectedElementId === element.id;
7522
+ const isDragging = draggingElementId === element.id;
7523
+ const isResizing = resizingElementId === element.id;
7524
+ return /* @__PURE__ */ React69__namespace.default.createElement(
7525
+ "div",
7526
+ {
7527
+ key: element.id,
7528
+ role: "button",
7529
+ tabIndex: 0,
7530
+ onClick: (event) => {
7531
+ event.stopPropagation();
7532
+ onElementSelect?.(element.id);
7533
+ },
7534
+ onPointerDown: (event) => beginInteraction(event, element.id, "move"),
7535
+ className: `absolute select-none touch-none rounded-md ${isDragging ? "cursor-grabbing" : isResizing ? "cursor-se-resize" : "cursor-grab"} ${isSelected ? "ring-2 ring-sky-300" : "ring-1 ring-white/40"}`,
7536
+ style: {
7537
+ ...elementStyle(element),
7538
+ zIndex: isSelected ? 4 : 2
7539
+ }
7540
+ },
7541
+ element.type === "text" ? /* @__PURE__ */ React69__namespace.default.createElement(
7542
+ "div",
7543
+ {
7544
+ className: "rounded-md bg-black/20 px-2 py-1",
7545
+ style: {
7546
+ color: element.color || "#f8fafc",
7547
+ fontSize: element.fontSize || 18,
7548
+ fontWeight: element.fontWeight || 500,
7549
+ fontFamily: element.fontFamily || "inherit",
7550
+ textAlign: element.align || "left",
7551
+ lineHeight: 1.45,
7552
+ whiteSpace: "pre-wrap"
7553
+ }
7554
+ },
7555
+ element.content
7556
+ ) : /* @__PURE__ */ React69__namespace.default.createElement(
7557
+ "img",
7558
+ {
7559
+ src: element.src,
7560
+ alt: element.alt || "festival-card-image",
7561
+ draggable: false,
7562
+ className: "pointer-events-none h-full w-full",
7563
+ style: {
7564
+ objectFit: element.fit || "cover",
7565
+ borderRadius: element.borderRadius || 0,
7566
+ overflow: "hidden",
7567
+ boxShadow: "0 12px 30px rgba(2, 6, 23, 0.32)"
7568
+ }
7569
+ }
7570
+ ),
7571
+ /* @__PURE__ */ React69__namespace.default.createElement(
7572
+ "button",
7573
+ {
7574
+ type: "button",
7575
+ "aria-label": "resize",
7576
+ onPointerDown: (event) => beginInteraction(event, element.id, "resize"),
7577
+ className: "absolute -bottom-2 -right-2 h-4 w-4 rounded-full border border-white bg-sky-500 shadow"
7578
+ }
7579
+ )
7580
+ );
7581
+ })
7441
7582
  );
7442
7583
  };
7443
7584
 
7444
7585
  // src/festivalCard/components/FestivalCardBook3D.tsx
7445
- var FestivalCardBook3D = ({ config, className }) => {
7446
- const [currentPage, setCurrentPage] = React69.useState(0);
7586
+ var loadImage2 = (src) => new Promise((resolve, reject) => {
7587
+ const image = new window.Image();
7588
+ image.crossOrigin = "anonymous";
7589
+ image.decoding = "async";
7590
+ image.onload = () => resolve(image);
7591
+ image.onerror = () => reject(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${src}`));
7592
+ image.src = src;
7593
+ });
7594
+ var drawImageWithFit = (ctx, image, left, top, width, height, fit) => {
7595
+ const imageRatio = image.width / image.height;
7596
+ const boxRatio = width / height;
7597
+ let drawWidth = width;
7598
+ let drawHeight = height;
7599
+ let offsetX = left;
7600
+ let offsetY = top;
7601
+ if (fit === "cover") {
7602
+ if (imageRatio > boxRatio) {
7603
+ drawHeight = height;
7604
+ drawWidth = height * imageRatio;
7605
+ offsetX = left - (drawWidth - width) / 2;
7606
+ } else {
7607
+ drawWidth = width;
7608
+ drawHeight = width / imageRatio;
7609
+ offsetY = top - (drawHeight - height) / 2;
7610
+ }
7611
+ } else if (imageRatio > boxRatio) {
7612
+ drawWidth = width;
7613
+ drawHeight = width / imageRatio;
7614
+ offsetY = top + (height - drawHeight) / 2;
7615
+ } else {
7616
+ drawHeight = height;
7617
+ drawWidth = height * imageRatio;
7618
+ offsetX = left + (width - drawWidth) / 2;
7619
+ }
7620
+ ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
7621
+ };
7622
+ var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
7623
+ const safeRadius = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
7624
+ if (safeRadius <= 0) {
7625
+ draw();
7626
+ return;
7627
+ }
7628
+ ctx.save();
7629
+ ctx.beginPath();
7630
+ ctx.moveTo(left + safeRadius, top);
7631
+ ctx.lineTo(left + width - safeRadius, top);
7632
+ ctx.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
7633
+ ctx.lineTo(left + width, top + height - safeRadius);
7634
+ ctx.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
7635
+ ctx.lineTo(left + safeRadius, top + height);
7636
+ ctx.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
7637
+ ctx.lineTo(left, top + safeRadius);
7638
+ ctx.quadraticCurveTo(left, top, left + safeRadius, top);
7639
+ ctx.closePath();
7640
+ ctx.clip();
7641
+ draw();
7642
+ ctx.restore();
7643
+ };
7644
+ var drawMultilineText = (ctx, text5, left, top, maxWidth, lineHeight) => {
7645
+ const paragraphs = text5.split("\n");
7646
+ let currentY = top;
7647
+ paragraphs.forEach((paragraph, index) => {
7648
+ const words = paragraph.split("");
7649
+ let line = "";
7650
+ for (const word of words) {
7651
+ const testLine = line + word;
7652
+ if (ctx.measureText(testLine).width > maxWidth && line) {
7653
+ ctx.fillText(line, left, currentY);
7654
+ line = word;
7655
+ currentY += lineHeight;
7656
+ } else {
7657
+ line = testLine;
7658
+ }
7659
+ }
7660
+ ctx.fillText(line, left, currentY);
7661
+ currentY += lineHeight;
7662
+ if (index < paragraphs.length - 1) {
7663
+ currentY += lineHeight * 0.2;
7664
+ }
7665
+ });
7666
+ };
7667
+ var exportPageToPng = async (page, fileName) => {
7668
+ const width = 1080;
7669
+ const height = 1440;
7670
+ const canvas = document.createElement("canvas");
7671
+ canvas.width = width;
7672
+ canvas.height = height;
7673
+ const ctx = canvas.getContext("2d");
7674
+ if (!ctx) throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas \u4E0A\u4E0B\u6587");
7675
+ ctx.fillStyle = page.background?.color || "#0f172a";
7676
+ ctx.fillRect(0, 0, width, height);
7677
+ const backgroundElement = page.elements.find(
7678
+ (element) => element.type === "image" && Boolean(element.isBackground)
7679
+ );
7680
+ const backgroundImageSrc = backgroundElement?.src || page.background?.image;
7681
+ if (backgroundImageSrc) {
7682
+ const image = await loadImage2(backgroundImageSrc);
7683
+ drawImageWithFit(ctx, image, 0, 0, width, height, "cover");
7684
+ }
7685
+ const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
7686
+ for (const element of foregroundElements) {
7687
+ const elementWidth = width * (element.width ?? 70) / 100;
7688
+ const elementHeight = element.height ? height * element.height / 100 : void 0;
7689
+ const centerX = width * element.x / 100;
7690
+ const centerY = height * element.y / 100;
7691
+ const left = centerX - elementWidth / 2;
7692
+ if (element.type === "image") {
7693
+ const image = await loadImage2(element.src);
7694
+ const drawHeight = elementHeight ?? elementWidth;
7695
+ const boxTop = centerY - drawHeight / 2;
7696
+ withRoundedClip(ctx, left, boxTop, elementWidth, drawHeight, element.borderRadius ?? 0, () => {
7697
+ drawImageWithFit(ctx, image, left, boxTop, elementWidth, drawHeight, element.fit || "cover");
7698
+ });
7699
+ continue;
7700
+ }
7701
+ const fontSize = (element.fontSize || 18) * 1.5;
7702
+ ctx.fillStyle = element.color || "#f8fafc";
7703
+ ctx.font = `${element.fontWeight || 500} ${fontSize}px ${element.fontFamily || "sans-serif"}`;
7704
+ ctx.textBaseline = "top";
7705
+ ctx.textAlign = element.align || "left";
7706
+ const textX = element.align === "center" ? centerX : element.align === "right" ? left + elementWidth : left;
7707
+ drawMultilineText(ctx, element.content || "", textX, centerY - fontSize * 0.72, elementWidth, fontSize * 1.45);
7708
+ }
7709
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
7710
+ if (!blob) throw new Error("\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
7711
+ const url = URL.createObjectURL(blob);
7712
+ const anchor = document.createElement("a");
7713
+ anchor.href = url;
7714
+ anchor.download = fileName;
7715
+ anchor.click();
7716
+ URL.revokeObjectURL(url);
7717
+ };
7718
+ var FestivalCardBook3D = ({
7719
+ config,
7720
+ className,
7721
+ editable = false,
7722
+ enableExportImage = !editable,
7723
+ currentPage: currentPageProp,
7724
+ onCurrentPageChange,
7725
+ selectedElementId = null,
7726
+ onSelectedElementChange,
7727
+ onElementChange
7728
+ }) => {
7729
+ const [internalCurrentPage, setInternalCurrentPage] = React69.useState(0);
7730
+ const [exporting, setExporting] = React69.useState(false);
7447
7731
  const normalized = React69.useMemo(() => normalizeFestivalCardConfig(config), [config]);
7448
7732
  const pages = normalized.pages;
7733
+ const currentPage = typeof currentPageProp === "number" ? currentPageProp : internalCurrentPage;
7734
+ const setCurrentPage = (updater) => {
7735
+ const prev = currentPage;
7736
+ const nextValue = typeof updater === "function" ? updater(prev) : updater;
7737
+ if (typeof currentPageProp === "number") {
7738
+ onCurrentPageChange?.(nextValue);
7739
+ return;
7740
+ }
7741
+ setInternalCurrentPage(nextValue);
7742
+ onCurrentPageChange?.(nextValue);
7743
+ };
7449
7744
  const canPrev = currentPage > 0;
7450
7745
  const canNext = currentPage < pages.length - 1;
7746
+ const currentPageData = pages[currentPage];
7747
+ const handleExportCurrentPage = async () => {
7748
+ if (!currentPageData || exporting) return;
7749
+ setExporting(true);
7750
+ try {
7751
+ const base = normalized.id || "festival-card";
7752
+ const fileName = `${base}-page-${currentPage + 1}.png`;
7753
+ await exportPageToPng(currentPageData, fileName);
7754
+ } catch (error) {
7755
+ window.alert(error.message || "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25");
7756
+ } finally {
7757
+ setExporting(false);
7758
+ }
7759
+ };
7451
7760
  return /* @__PURE__ */ React69__namespace.default.createElement("div", { className }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "w-full min-h-screen px-0 py-4" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "mx-auto w-full text-center text-slate-100" }, /* @__PURE__ */ React69__namespace.default.createElement("h3", { className: "mb-3 text-lg font-semibold" }, normalized.coverTitle || "Festival Card")), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "mx-auto w-full" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "relative h-[calc(100vh-170px)] min-h-[460px]" }, pages.map((page, index) => /* @__PURE__ */ React69__namespace.default.createElement(
7452
7761
  "div",
7453
7762
  {
@@ -7458,7 +7767,16 @@ var FestivalCardBook3D = ({ config, className }) => {
7458
7767
  pointerEvents: index === currentPage ? "auto" : "none"
7459
7768
  }
7460
7769
  },
7461
- /* @__PURE__ */ React69__namespace.default.createElement(FestivalCardPageRenderer, { page })
7770
+ /* @__PURE__ */ React69__namespace.default.createElement(
7771
+ FestivalCardPageRenderer,
7772
+ {
7773
+ page,
7774
+ editable: editable && index === currentPage,
7775
+ selectedElementId,
7776
+ onElementSelect: onSelectedElementChange,
7777
+ onElementChange: (elementId, patch) => onElementChange?.(index, elementId, patch)
7778
+ }
7779
+ )
7462
7780
  )))), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "mt-4 flex justify-center gap-3" }, /* @__PURE__ */ React69__namespace.default.createElement(
7463
7781
  "button",
7464
7782
  {
@@ -7486,6 +7804,24 @@ var FestivalCardBook3D = ({ config, className }) => {
7486
7804
  controls: true,
7487
7805
  className: "mt-3 w-full"
7488
7806
  }
7807
+ ) : null, enableExportImage ? /* @__PURE__ */ React69__namespace.default.createElement(
7808
+ FloatingMenu_default,
7809
+ {
7810
+ initialPosition: { x: 24, y: 120 },
7811
+ trigger: /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "text-lg leading-none text-slate-700", "aria-hidden": true }, "\u2301"),
7812
+ menu: /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, "\u8D3A\u5361\u5DE5\u5177"), /* @__PURE__ */ React69__namespace.default.createElement(
7813
+ "button",
7814
+ {
7815
+ type: "button",
7816
+ onClick: () => void handleExportCurrentPage(),
7817
+ disabled: exporting,
7818
+ className: "rounded-lg bg-sky-600 px-3 py-2 text-left text-sm font-medium text-white disabled:opacity-60"
7819
+ },
7820
+ exporting ? "\u5BFC\u51FA\u4E2D..." : `\u5BFC\u51FA\u7B2C ${currentPage + 1} \u9875 PNG`
7821
+ )),
7822
+ triggerClassName: "bg-white/95 backdrop-blur",
7823
+ menuClassName: "bg-white/95 backdrop-blur"
7824
+ }
7489
7825
  ) : null);
7490
7826
  };
7491
7827
  var createTextElement = (pageIndex) => ({
@@ -7512,8 +7848,22 @@ var createImageElement = (pageIndex) => ({
7512
7848
  fit: "cover",
7513
7849
  borderRadius: 12
7514
7850
  });
7515
- var FestivalCardConfigEditor = ({ value, onChange }) => {
7516
- const [activePageIndex, setActivePageIndex] = React69.useState(0);
7851
+ var FestivalCardConfigEditor = ({
7852
+ value,
7853
+ onChange,
7854
+ activePageIndex: activePageIndexProp,
7855
+ onActivePageIndexChange,
7856
+ selectedElementId
7857
+ }) => {
7858
+ const [internalActivePageIndex, setInternalActivePageIndex] = React69.useState(0);
7859
+ const activePageIndex = activePageIndexProp ?? internalActivePageIndex;
7860
+ const setActivePageIndex = (index) => {
7861
+ if (typeof activePageIndexProp === "number") {
7862
+ onActivePageIndexChange?.(index);
7863
+ return;
7864
+ }
7865
+ setInternalActivePageIndex(index);
7866
+ };
7517
7867
  const page = value.pages[activePageIndex];
7518
7868
  const canEditPage = Boolean(page);
7519
7869
  const pageOptions = React69.useMemo(() => value.pages.map((_, index) => index), [value.pages]);
@@ -7646,166 +7996,174 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
7646
7996
  className: "rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white"
7647
7997
  },
7648
7998
  "+ \u56FE\u7247"
7649
- )), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React69__namespace.default.createElement("div", { key: element.id, className: "rounded-xl border border-slate-200 bg-slate-50 p-3" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React69__namespace.default.createElement(
7650
- "button",
7651
- {
7652
- type: "button",
7653
- onClick: () => removeElement(element.id),
7654
- className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
7655
- },
7656
- "\u5220\u9664"
7657
- )), element.type === "text" ? /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
7658
- "textarea",
7659
- {
7660
- value: element.content,
7661
- onChange: (event) => updateElement(element.id, { content: event.target.value }),
7662
- rows: 3,
7663
- className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
7664
- }
7665
- ), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7666
- "input",
7667
- {
7668
- type: "number",
7669
- value: element.x,
7670
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
7671
- className: numberFieldClassName
7672
- }
7673
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7674
- "input",
7675
- {
7676
- type: "number",
7677
- value: element.y,
7678
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
7679
- className: numberFieldClassName
7680
- }
7681
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7682
- "input",
7683
- {
7684
- type: "number",
7685
- value: element.width ?? 70,
7686
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
7687
- className: numberFieldClassName
7688
- }
7689
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__namespace.default.createElement(
7690
- "input",
7691
- {
7692
- type: "number",
7693
- value: element.fontSize ?? 18,
7694
- onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
7695
- className: numberFieldClassName
7696
- }
7697
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__namespace.default.createElement(
7698
- "input",
7699
- {
7700
- type: "number",
7701
- min: 100,
7702
- max: 900,
7703
- step: 100,
7704
- value: element.fontWeight ?? 500,
7705
- onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
7706
- className: numberFieldClassName
7707
- }
7708
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__namespace.default.createElement(
7709
- "select",
7710
- {
7711
- value: element.align || "left",
7712
- onChange: (event) => updateElement(element.id, { align: event.target.value }),
7713
- className: numberFieldClassName
7714
- },
7715
- /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "left" }, "left"),
7716
- /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "center" }, "center"),
7717
- /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "right" }, "right")
7718
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__namespace.default.createElement(
7719
- "input",
7720
- {
7721
- type: "text",
7722
- value: element.fontFamily || "",
7723
- onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
7724
- placeholder: "inherit / serif / sans-serif / PingFang SC",
7725
- className: numberFieldClassName
7726
- }
7727
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
7728
- "input",
7729
- {
7730
- type: "color",
7731
- value: element.color || "#ffffff",
7732
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
7733
- className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
7734
- }
7735
- ), /* @__PURE__ */ React69__namespace.default.createElement(
7736
- "input",
7737
- {
7738
- type: "text",
7739
- value: element.color || "#ffffff",
7740
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
7741
- className: numberFieldClassName
7742
- }
7743
- ))))) : /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
7744
- "input",
7745
- {
7746
- type: "url",
7747
- value: element.src,
7748
- onChange: (event) => updateElement(element.id, { src: event.target.value }),
7749
- className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
7750
- }
7751
- ), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7752
- "input",
7753
- {
7754
- type: "number",
7755
- value: element.x,
7756
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
7757
- className: numberFieldClassName
7758
- }
7759
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7760
- "input",
7761
- {
7762
- type: "number",
7763
- value: element.y,
7764
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
7765
- className: numberFieldClassName
7766
- }
7767
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7768
- "input",
7769
- {
7770
- type: "number",
7771
- value: element.width ?? 60,
7772
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
7773
- className: numberFieldClassName
7774
- }
7775
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
7776
- "input",
7777
- {
7778
- type: "number",
7779
- value: element.height ?? 40,
7780
- onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
7781
- className: numberFieldClassName
7782
- }
7783
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__namespace.default.createElement(
7784
- "input",
7785
- {
7786
- type: "number",
7787
- value: element.borderRadius ?? 0,
7788
- onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
7789
- className: numberFieldClassName
7790
- }
7791
- )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__namespace.default.createElement(
7792
- "select",
7999
+ )), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React69__namespace.default.createElement(
8000
+ "div",
7793
8001
  {
7794
- value: element.fit || "cover",
7795
- onChange: (event) => updateElement(element.id, { fit: event.target.value }),
7796
- className: numberFieldClassName
8002
+ key: element.id,
8003
+ className: `rounded-xl border bg-slate-50 p-3 ${selectedElementId === element.id ? "border-sky-400 ring-2 ring-sky-100" : "border-slate-200"}`
7797
8004
  },
7798
- /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "cover" }, "cover"),
7799
- /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "contain" }, "contain")
7800
- ))), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__namespace.default.createElement(
7801
- "input",
7802
- {
7803
- type: "checkbox",
7804
- checked: Boolean(element.isBackground),
7805
- onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
7806
- className: "h-4 w-4 rounded border-slate-300 text-sky-600"
7807
- }
7808
- ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE")))))) : null);
8005
+ /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React69__namespace.default.createElement(
8006
+ "button",
8007
+ {
8008
+ type: "button",
8009
+ onClick: () => removeElement(element.id),
8010
+ className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
8011
+ },
8012
+ "\u5220\u9664"
8013
+ )),
8014
+ element.type === "text" ? /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
8015
+ "textarea",
8016
+ {
8017
+ value: element.content,
8018
+ onChange: (event) => updateElement(element.id, { content: event.target.value }),
8019
+ rows: 3,
8020
+ className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
8021
+ }
8022
+ ), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8023
+ "input",
8024
+ {
8025
+ type: "number",
8026
+ value: element.x,
8027
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
8028
+ className: numberFieldClassName
8029
+ }
8030
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8031
+ "input",
8032
+ {
8033
+ type: "number",
8034
+ value: element.y,
8035
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
8036
+ className: numberFieldClassName
8037
+ }
8038
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8039
+ "input",
8040
+ {
8041
+ type: "number",
8042
+ value: element.width ?? 70,
8043
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
8044
+ className: numberFieldClassName
8045
+ }
8046
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__namespace.default.createElement(
8047
+ "input",
8048
+ {
8049
+ type: "number",
8050
+ value: element.fontSize ?? 18,
8051
+ onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
8052
+ className: numberFieldClassName
8053
+ }
8054
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__namespace.default.createElement(
8055
+ "input",
8056
+ {
8057
+ type: "number",
8058
+ min: 100,
8059
+ max: 900,
8060
+ step: 100,
8061
+ value: element.fontWeight ?? 500,
8062
+ onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
8063
+ className: numberFieldClassName
8064
+ }
8065
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__namespace.default.createElement(
8066
+ "select",
8067
+ {
8068
+ value: element.align || "left",
8069
+ onChange: (event) => updateElement(element.id, { align: event.target.value }),
8070
+ className: numberFieldClassName
8071
+ },
8072
+ /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "left" }, "left"),
8073
+ /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "center" }, "center"),
8074
+ /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "right" }, "right")
8075
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__namespace.default.createElement(
8076
+ "input",
8077
+ {
8078
+ type: "text",
8079
+ value: element.fontFamily || "",
8080
+ onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
8081
+ placeholder: "inherit / serif / sans-serif / PingFang SC",
8082
+ className: numberFieldClassName
8083
+ }
8084
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
8085
+ "input",
8086
+ {
8087
+ type: "color",
8088
+ value: element.color || "#ffffff",
8089
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
8090
+ className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
8091
+ }
8092
+ ), /* @__PURE__ */ React69__namespace.default.createElement(
8093
+ "input",
8094
+ {
8095
+ type: "text",
8096
+ value: element.color || "#ffffff",
8097
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
8098
+ className: numberFieldClassName
8099
+ }
8100
+ ))))) : /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement(
8101
+ "input",
8102
+ {
8103
+ type: "url",
8104
+ value: element.src,
8105
+ onChange: (event) => updateElement(element.id, { src: event.target.value }),
8106
+ className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
8107
+ }
8108
+ ), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8109
+ "input",
8110
+ {
8111
+ type: "number",
8112
+ value: element.x,
8113
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
8114
+ className: numberFieldClassName
8115
+ }
8116
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8117
+ "input",
8118
+ {
8119
+ type: "number",
8120
+ value: element.y,
8121
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
8122
+ className: numberFieldClassName
8123
+ }
8124
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8125
+ "input",
8126
+ {
8127
+ type: "number",
8128
+ value: element.width ?? 60,
8129
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
8130
+ className: numberFieldClassName
8131
+ }
8132
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__namespace.default.createElement(
8133
+ "input",
8134
+ {
8135
+ type: "number",
8136
+ value: element.height ?? 40,
8137
+ onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
8138
+ className: numberFieldClassName
8139
+ }
8140
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__namespace.default.createElement(
8141
+ "input",
8142
+ {
8143
+ type: "number",
8144
+ value: element.borderRadius ?? 0,
8145
+ onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
8146
+ className: numberFieldClassName
8147
+ }
8148
+ )), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__namespace.default.createElement(
8149
+ "select",
8150
+ {
8151
+ value: element.fit || "cover",
8152
+ onChange: (event) => updateElement(element.id, { fit: event.target.value }),
8153
+ className: numberFieldClassName
8154
+ },
8155
+ /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "cover" }, "cover"),
8156
+ /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "contain" }, "contain")
8157
+ ))), /* @__PURE__ */ React69__namespace.default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__namespace.default.createElement(
8158
+ "input",
8159
+ {
8160
+ type: "checkbox",
8161
+ checked: Boolean(element.isBackground),
8162
+ onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
8163
+ className: "h-4 w-4 rounded border-slate-300 text-sky-600"
8164
+ }
8165
+ ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE"))
8166
+ )))) : null);
7809
8167
  };
7810
8168
 
7811
8169
  // src/festivalCard/components/FestivalCardStudio.tsx
@@ -7815,8 +8173,55 @@ var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
7815
8173
  fetchConfig,
7816
8174
  onSave
7817
8175
  });
8176
+ const [activePageIndex, setActivePageIndex] = React69.useState(0);
8177
+ const [selectedElementId, setSelectedElementId] = React69.useState(null);
8178
+ React69.useEffect(() => {
8179
+ if (config.pages.length === 0) return;
8180
+ if (activePageIndex <= config.pages.length - 1) return;
8181
+ setActivePageIndex(config.pages.length - 1);
8182
+ }, [activePageIndex, config.pages.length]);
8183
+ const updateElementByPreview = (pageIndex, elementId, patch) => {
8184
+ setConfig((prev) => ({
8185
+ ...prev,
8186
+ pages: prev.pages.map(
8187
+ (page, index) => index === pageIndex ? {
8188
+ ...page,
8189
+ elements: page.elements.map(
8190
+ (element) => element.id === elementId ? { ...element, ...patch } : element
8191
+ )
8192
+ } : page
8193
+ )
8194
+ }));
8195
+ };
7818
8196
  if (loading) return /* @__PURE__ */ React69__namespace.default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
7819
- return /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__namespace.default.createElement(FestivalCardBook3D, { config, className: "h-full" }), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__namespace.default.createElement(FestivalCardConfigEditor, { value: config, onChange: setConfig }), onSave ? /* @__PURE__ */ React69__namespace.default.createElement(
8197
+ return /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__namespace.default.createElement(
8198
+ FestivalCardBook3D,
8199
+ {
8200
+ config,
8201
+ className: "h-full",
8202
+ editable: true,
8203
+ currentPage: activePageIndex,
8204
+ onCurrentPageChange: (index) => {
8205
+ setActivePageIndex(index);
8206
+ setSelectedElementId(null);
8207
+ },
8208
+ selectedElementId,
8209
+ onSelectedElementChange: setSelectedElementId,
8210
+ onElementChange: updateElementByPreview
8211
+ }
8212
+ ), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__namespace.default.createElement(
8213
+ FestivalCardConfigEditor,
8214
+ {
8215
+ value: config,
8216
+ onChange: setConfig,
8217
+ activePageIndex,
8218
+ onActivePageIndexChange: (index) => {
8219
+ setActivePageIndex(index);
8220
+ setSelectedElementId(null);
8221
+ },
8222
+ selectedElementId
8223
+ }
8224
+ ), onSave ? /* @__PURE__ */ React69__namespace.default.createElement(
7820
8225
  "button",
7821
8226
  {
7822
8227
  type: "button",