sy-form-components 0.2.14 → 0.2.15

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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/core/FormProvider.tsx
2
- import React34, { useCallback as useCallback5, useRef as useRef6, useState as useState16, useMemo as useMemo9 } from "react";
2
+ import React36, { useCallback as useCallback5, useRef as useRef6, useState as useState16, useMemo as useMemo9 } from "react";
3
3
 
4
4
  // src/core/FormContext.ts
5
5
  import { createContext, useContext } from "react";
@@ -1356,6 +1356,7 @@ function CascadeDateField(props) {
1356
1356
  import { useEffect as useEffect10 } from "react";
1357
1357
 
1358
1358
  // src/fields/AttachmentField/AttachmentFieldPC.tsx
1359
+ import React14 from "react";
1359
1360
  import { Upload, Button } from "antd";
1360
1361
 
1361
1362
  // src/fields/shared/fieldFormat.ts
@@ -1462,6 +1463,31 @@ function normalizeAttachmentItem(item, fallback) {
1462
1463
  extension: item?.extension || fallback?.extension || getFileExtension(name)
1463
1464
  };
1464
1465
  }
1466
+ function getAttachmentItemIdentity(item) {
1467
+ return String(item?.uid || item?.id || item?.objectName || item?.url || item?.name || "");
1468
+ }
1469
+ function dedupeAttachmentItems(items) {
1470
+ const result = [];
1471
+ const indexes = /* @__PURE__ */ new Map();
1472
+ for (const item of items) {
1473
+ const key = getAttachmentItemIdentity(item);
1474
+ if (!key) {
1475
+ result.push(item);
1476
+ continue;
1477
+ }
1478
+ const existingIndex = indexes.get(key);
1479
+ if (existingIndex === void 0) {
1480
+ indexes.set(key, result.length);
1481
+ result.push(item);
1482
+ continue;
1483
+ }
1484
+ result[existingIndex] = {
1485
+ ...result[existingIndex],
1486
+ ...item
1487
+ };
1488
+ }
1489
+ return result;
1490
+ }
1465
1491
 
1466
1492
  // src/fields/AttachmentField/AttachmentFieldPC.tsx
1467
1493
  import { jsx as jsx38, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -1516,38 +1542,47 @@ function AttachmentFieldPC({
1516
1542
  onChange
1517
1543
  }) {
1518
1544
  const { formData, setFieldValue, api } = useFormContext();
1519
- const value = formData[fieldId] ?? [];
1545
+ const value = React14.useMemo(
1546
+ () => dedupeAttachmentItems(
1547
+ Array.isArray(formData[fieldId]) ? formData[fieldId] : []
1548
+ ),
1549
+ [fieldId, formData]
1550
+ );
1551
+ const valueRef = React14.useRef(value);
1520
1552
  const disabled = behavior === "DISABLED";
1553
+ React14.useEffect(() => {
1554
+ valueRef.current = value;
1555
+ }, [value]);
1521
1556
  const setValue = (items) => {
1522
- setFieldValue(fieldId, items);
1523
- onChange?.(items);
1524
- };
1525
- const handleChange = (info) => {
1526
- const fileList2 = info.fileList ?? [];
1527
- const items = fileList2.map((f) => {
1528
- if (f.uid === void 0 && f.name === void 0 && f.url === void 0 && !f.response) {
1529
- return { url: "", name: "", id: "" };
1530
- }
1531
- return normalizeAttachmentItem({
1532
- url: f.url ?? f.response?.url ?? "",
1533
- name: f.name,
1534
- id: f.uid ?? f.response?.id,
1535
- uid: f.uid ?? f.response?.uid,
1536
- status: f.status,
1537
- percent: f.percent,
1538
- objectName: f.objectName ?? f.response?.objectName,
1539
- bucketName: f.bucketName ?? f.response?.bucketName,
1540
- size: f.size ?? f.response?.size,
1541
- contentType: f.type ?? f.response?.contentType,
1542
- downloadUrl: f.downloadUrl ?? f.response?.downloadUrl,
1543
- previewUrl: f.previewUrl ?? f.response?.previewUrl
1544
- });
1557
+ const nextItems = dedupeAttachmentItems(items).slice(0, maxCount ?? Infinity);
1558
+ valueRef.current = nextItems;
1559
+ setFieldValue(fieldId, nextItems);
1560
+ onChange?.(nextItems);
1561
+ };
1562
+ const replaceItem = (target, nextItem) => {
1563
+ const targetKeys = new Set(
1564
+ [target.id, target.uid, target.objectName, getAttachmentItemIdentity(target)].filter(Boolean)
1565
+ );
1566
+ let replaced = false;
1567
+ const nextItems = valueRef.current.map((item) => {
1568
+ const currentKeys = [item.id, item.uid, item.objectName, getAttachmentItemIdentity(item)];
1569
+ if (currentKeys.some((key) => key && targetKeys.has(key))) {
1570
+ replaced = true;
1571
+ return nextItem;
1572
+ }
1573
+ return item;
1545
1574
  });
1546
- setValue(items);
1575
+ if (!replaced) nextItems.push(nextItem);
1576
+ setValue(nextItems);
1547
1577
  };
1548
1578
  const handleRemove = (file) => {
1549
- const removed = value.find((item) => item.id === file.uid || item.uid === file.uid);
1550
- const newValue = value.filter((item) => item.id !== file.uid && item.uid !== file.uid);
1579
+ const fileKey = String(file.uid || file.id || file.objectName || "");
1580
+ const removed = valueRef.current.find(
1581
+ (item) => item.id === fileKey || item.uid === fileKey || item.objectName === fileKey
1582
+ );
1583
+ const newValue = valueRef.current.filter(
1584
+ (item) => item.id !== fileKey && item.uid !== fileKey && item.objectName !== fileKey
1585
+ );
1551
1586
  setValue(newValue);
1552
1587
  if (removed?.objectName) {
1553
1588
  api.deleteFile(removed.objectName, removed.bucketName || bucketName).catch(() => void 0);
@@ -1588,7 +1623,9 @@ function AttachmentFieldPC({
1588
1623
  return url;
1589
1624
  };
1590
1625
  const handlePreview = async (file) => {
1591
- const item = value.find((current) => current.id === file.uid || current.uid === file.uid);
1626
+ const item = valueRef.current.find(
1627
+ (current) => current.id === file.uid || current.uid === file.uid || current.objectName === file.uid
1628
+ );
1592
1629
  const url = item ? await resolvePreviewUrl(item) : file.url;
1593
1630
  if (url) window.open(url, "_blank", "noopener,noreferrer");
1594
1631
  };
@@ -1596,14 +1633,10 @@ function AttachmentFieldPC({
1596
1633
  const url = await resolveDownloadUrl(item);
1597
1634
  if (url) window.open(url, "_blank", "noopener,noreferrer");
1598
1635
  };
1599
- const fileList = value.map((item) => ({
1600
- uid: item.id,
1601
- name: item.name,
1602
- url: item.url,
1603
- status: item.status ?? "done",
1604
- percent: item.percent
1605
- }));
1606
1636
  const beforeUpload = (file) => {
1637
+ if (maxCount && valueRef.current.length >= maxCount) {
1638
+ return false;
1639
+ }
1607
1640
  if (maxSize && file.size / 1024 / 1024 > maxSize) {
1608
1641
  return false;
1609
1642
  }
@@ -1616,27 +1649,26 @@ function AttachmentFieldPC({
1616
1649
  const customRequest = async ({ file, onProgress, onSuccess, onError }) => {
1617
1650
  const currentFile = file;
1618
1651
  const localItem = createLocalItem(currentFile);
1619
- const nextValue = multiple ? [...value, localItem].slice(0, maxCount ?? Infinity) : [localItem];
1652
+ const currentValue = valueRef.current.filter(
1653
+ (item) => item.id !== localItem.id && item.uid !== localItem.uid
1654
+ );
1655
+ const nextValue = multiple ? [...currentValue, localItem] : [localItem];
1620
1656
  setValue(nextValue);
1621
1657
  try {
1622
1658
  const uploaded = await api.uploadFile(currentFile, bucketName, (percent) => {
1623
1659
  onProgress?.({ percent });
1624
- setValue(
1625
- nextValue.map(
1626
- (item) => item.id === localItem.id ? { ...item, percent, status: "uploading" } : item
1627
- )
1628
- );
1629
- });
1630
- const completed = normalizeAttachmentItem({
1631
- ...uploaded,
1632
- id: uploaded.id || localItem.id,
1633
- uid: uploaded.uid || localItem.uid,
1634
- url: uploaded.url || localItem.url,
1635
- status: "done",
1636
- percent: 100,
1637
- extension: uploaded.extension || getFileExtension(uploaded.name || localItem.name)
1660
+ replaceItem(localItem, { ...localItem, percent, status: "uploading" });
1638
1661
  });
1639
- setValue(nextValue.map((item) => item.id === localItem.id ? completed : item));
1662
+ const completed = normalizeAttachmentItem(
1663
+ {
1664
+ ...uploaded,
1665
+ status: "done",
1666
+ percent: 100,
1667
+ extension: uploaded.extension || getFileExtension(uploaded.name || localItem.name)
1668
+ },
1669
+ localItem
1670
+ );
1671
+ replaceItem(localItem, completed);
1640
1672
  onSuccess?.(completed);
1641
1673
  } catch (error) {
1642
1674
  const failed = {
@@ -1644,7 +1676,7 @@ function AttachmentFieldPC({
1644
1676
  status: "error",
1645
1677
  error: error?.message || "\u4E0A\u4F20\u5931\u8D25"
1646
1678
  };
1647
- setValue(nextValue.map((item) => item.id === localItem.id ? failed : item));
1679
+ replaceItem(localItem, failed);
1648
1680
  onError?.(error);
1649
1681
  }
1650
1682
  };
@@ -1653,61 +1685,71 @@ function AttachmentFieldPC({
1653
1685
  Upload,
1654
1686
  {
1655
1687
  "data-testid": `attachmentfield-input-${fieldId}`,
1656
- fileList,
1688
+ fileList: [],
1657
1689
  customRequest,
1658
1690
  accept,
1659
1691
  maxCount,
1660
1692
  multiple,
1661
1693
  disabled,
1662
- onChange: handleChange,
1694
+ showUploadList: false,
1663
1695
  onRemove: handleRemove,
1664
1696
  onPreview: handlePreview,
1665
1697
  beforeUpload,
1666
1698
  children: /* @__PURE__ */ jsx38(Button, { disabled, "data-testid": `attachmentfield-upload-btn-${fieldId}`, children: "\u4E0A\u4F20\u6587\u4EF6" })
1667
1699
  }
1668
1700
  ),
1669
- value.length > 0 && /* @__PURE__ */ jsx38("div", { className: "sy-file-list", "data-testid": `attachmentfield-rich-list-${fieldId}`, children: value.map((item) => {
1701
+ value.length > 0 && /* @__PURE__ */ jsx38("div", { className: "sy-file-list", "data-testid": `attachmentfield-rich-list-${fieldId}`, children: value.map((item, index) => {
1670
1702
  const category = getFileCategory(item.name, item.contentType);
1671
1703
  const extension = getFileExtension(item.name);
1672
- return /* @__PURE__ */ jsxs5("div", { className: "sy-file-item", children: [
1673
- /* @__PURE__ */ jsx38("span", { className: `sy-file-icon sy-file-icon-${category}`, "aria-hidden": "true", children: category === "image" ? "IMG" : extension || "FILE" }),
1674
- /* @__PURE__ */ jsxs5("div", { className: "sy-file-meta", children: [
1675
- /* @__PURE__ */ jsx38("span", { className: "sy-file-name", title: item.name, children: item.name }),
1676
- /* @__PURE__ */ jsx38("span", { className: "sy-file-sub", children: item.status === "uploading" ? `\u4E0A\u4F20\u4E2D ${Math.round(item.percent ?? 0)}%` : item.status === "error" ? item.error || "\u4E0A\u4F20\u5931\u8D25" : [
1677
- showFileTypeBadge && extension ? extension.toUpperCase() : "",
1678
- showFileSize ? formatFileSize(item.size) : ""
1679
- ].filter(Boolean).join(" \xB7 ") || "\u5DF2\u4E0A\u4F20" })
1680
- ] }),
1681
- /* @__PURE__ */ jsxs5("div", { className: "sy-file-actions", children: [
1682
- showPreview && canPreview(item) && /* @__PURE__ */ jsx38(
1683
- "button",
1684
- {
1685
- type: "button",
1686
- disabled: item.status !== "done",
1687
- onClick: () => handlePreview({ uid: item.uid || item.id, url: item.url }),
1688
- children: "\u9884\u89C8"
1689
- }
1690
- ),
1691
- showDownload && /* @__PURE__ */ jsx38(
1692
- "button",
1693
- {
1694
- type: "button",
1695
- disabled: item.status !== "done",
1696
- onClick: () => handleDownload(item),
1697
- children: "\u4E0B\u8F7D"
1698
- }
1699
- ),
1700
- /* @__PURE__ */ jsx38(
1701
- "button",
1702
- {
1703
- type: "button",
1704
- disabled,
1705
- onClick: () => handleRemove({ uid: item.uid || item.id }),
1706
- children: "\u5220\u9664"
1707
- }
1708
- )
1709
- ] })
1710
- ] }, item.id || item.uid || item.name);
1704
+ const itemKey = getAttachmentItemIdentity(item) || `${fieldId}-attachment-${String(index)}`;
1705
+ return /* @__PURE__ */ jsxs5(
1706
+ "div",
1707
+ {
1708
+ className: "sy-file-item",
1709
+ "data-testid": `attachmentfield-item-${itemKey}`,
1710
+ children: [
1711
+ /* @__PURE__ */ jsx38("span", { className: `sy-file-icon sy-file-icon-${category}`, "aria-hidden": "true", children: category === "image" ? "IMG" : extension || "FILE" }),
1712
+ /* @__PURE__ */ jsxs5("div", { className: "sy-file-meta", children: [
1713
+ /* @__PURE__ */ jsx38("span", { className: "sy-file-name", title: item.name, children: item.name }),
1714
+ /* @__PURE__ */ jsx38("span", { className: "sy-file-sub", children: item.status === "uploading" ? `\u4E0A\u4F20\u4E2D ${Math.round(item.percent ?? 0)}%` : item.status === "error" ? item.error || "\u4E0A\u4F20\u5931\u8D25" : [
1715
+ showFileTypeBadge && extension ? extension.toUpperCase() : "",
1716
+ showFileSize ? formatFileSize(item.size) : ""
1717
+ ].filter(Boolean).join(" \xB7 ") || "\u5DF2\u4E0A\u4F20" })
1718
+ ] }),
1719
+ /* @__PURE__ */ jsxs5("div", { className: "sy-file-actions", children: [
1720
+ showPreview && canPreview(item) && /* @__PURE__ */ jsx38(
1721
+ "button",
1722
+ {
1723
+ type: "button",
1724
+ disabled: item.status !== "done",
1725
+ onClick: () => handlePreview({ uid: item.uid || item.id, url: item.url }),
1726
+ children: "\u9884\u89C8"
1727
+ }
1728
+ ),
1729
+ showDownload && /* @__PURE__ */ jsx38(
1730
+ "button",
1731
+ {
1732
+ type: "button",
1733
+ disabled: item.status !== "done",
1734
+ onClick: () => handleDownload(item),
1735
+ children: "\u4E0B\u8F7D"
1736
+ }
1737
+ ),
1738
+ /* @__PURE__ */ jsx38(
1739
+ "button",
1740
+ {
1741
+ type: "button",
1742
+ disabled,
1743
+ onClick: () => handleRemove({ uid: item.uid || item.id }),
1744
+ "data-testid": `attachmentfield-remove-${itemKey}`,
1745
+ children: "\u5220\u9664"
1746
+ }
1747
+ )
1748
+ ] })
1749
+ ]
1750
+ },
1751
+ `${itemKey}-${index}`
1752
+ );
1711
1753
  }) })
1712
1754
  ] });
1713
1755
  }
@@ -1888,8 +1930,9 @@ function AttachmentField(props) {
1888
1930
  import { useEffect as useEffect11 } from "react";
1889
1931
 
1890
1932
  // src/fields/ImageField/ImageFieldPC.tsx
1933
+ import React16 from "react";
1891
1934
  import { Upload as Upload2 } from "antd";
1892
- import { jsx as jsx42, jsxs as jsxs7 } from "react/jsx-runtime";
1935
+ import { Fragment, jsx as jsx42, jsxs as jsxs7 } from "react/jsx-runtime";
1893
1936
  var createLocalItem2 = (file) => {
1894
1937
  const uid = file.uid || createUid("image");
1895
1938
  return {
@@ -1905,6 +1948,23 @@ var createLocalItem2 = (file) => {
1905
1948
  extension: getFileExtension(file.name)
1906
1949
  };
1907
1950
  };
1951
+ function ImageThumbContent({ item }) {
1952
+ const [imageFailed, setImageFailed] = React16.useState(false);
1953
+ const src = item.previewUrl || item.url;
1954
+ React16.useEffect(() => {
1955
+ setImageFailed(false);
1956
+ }, [src]);
1957
+ if (src && !imageFailed) {
1958
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1959
+ /* @__PURE__ */ jsx42("img", { src, alt: item.name, onError: () => setImageFailed(true) }),
1960
+ item.status === "uploading" && /* @__PURE__ */ jsxs7("span", { className: "sy-image-state", children: [
1961
+ Math.round(item.percent ?? 0),
1962
+ "%"
1963
+ ] })
1964
+ ] });
1965
+ }
1966
+ return /* @__PURE__ */ jsx42("span", { className: "sy-image-state", children: item.status === "uploading" ? `${Math.round(item.percent ?? 0)}%` : item.status === "error" ? item.error || "\u4E0A\u4F20\u5931\u8D25" : item.name || "\u56FE\u7247" });
1967
+ }
1908
1968
  function ImageFieldPC({
1909
1969
  fieldId,
1910
1970
  behavior,
@@ -1920,44 +1980,56 @@ function ImageFieldPC({
1920
1980
  onChange
1921
1981
  }) {
1922
1982
  const { formData, setFieldValue, api } = useFormContext();
1923
- const value = formData[fieldId] ?? [];
1983
+ const value = React16.useMemo(
1984
+ () => dedupeAttachmentItems(
1985
+ Array.isArray(formData[fieldId]) ? formData[fieldId] : []
1986
+ ),
1987
+ [fieldId, formData]
1988
+ );
1989
+ const valueRef = React16.useRef(value);
1924
1990
  const disabled = behavior === "DISABLED";
1991
+ React16.useEffect(() => {
1992
+ valueRef.current = value;
1993
+ }, [value]);
1925
1994
  const setValue = (items) => {
1926
- setFieldValue(fieldId, items);
1927
- onChange?.(items);
1928
- };
1929
- const handleChange = (info) => {
1930
- const fileList2 = info.fileList ?? [];
1931
- const items = fileList2.map((f) => {
1932
- if (f.uid === void 0 && f.name === void 0 && f.url === void 0 && f.thumbUrl === void 0 && !f.response) {
1933
- return { url: "", name: "", id: "" };
1934
- }
1935
- return normalizeAttachmentItem({
1936
- url: f.url ?? f.thumbUrl ?? f.response?.url ?? "",
1937
- previewUrl: f.previewUrl ?? f.thumbUrl ?? f.response?.previewUrl,
1938
- name: f.name,
1939
- id: f.uid ?? f.response?.id,
1940
- uid: f.uid ?? f.response?.uid,
1941
- status: f.status,
1942
- percent: f.percent,
1943
- objectName: f.objectName ?? f.response?.objectName,
1944
- bucketName: f.bucketName ?? f.response?.bucketName,
1945
- size: f.size ?? f.response?.size,
1946
- contentType: f.type ?? f.response?.contentType
1947
- });
1995
+ const nextItems = dedupeAttachmentItems(items).slice(0, maxCount ?? Infinity);
1996
+ valueRef.current = nextItems;
1997
+ setFieldValue(fieldId, nextItems);
1998
+ onChange?.(nextItems);
1999
+ };
2000
+ const replaceItem = (target, nextItem) => {
2001
+ const targetKeys = new Set(
2002
+ [target.id, target.uid, target.objectName, getAttachmentItemIdentity(target)].filter(Boolean)
2003
+ );
2004
+ let replaced = false;
2005
+ const nextItems = valueRef.current.map((item) => {
2006
+ const currentKeys = [item.id, item.uid, item.objectName, getAttachmentItemIdentity(item)];
2007
+ if (currentKeys.some((key) => key && targetKeys.has(key))) {
2008
+ replaced = true;
2009
+ return nextItem;
2010
+ }
2011
+ return item;
1948
2012
  });
1949
- setValue(items);
2013
+ if (!replaced) nextItems.push(nextItem);
2014
+ setValue(nextItems);
1950
2015
  };
1951
2016
  const handleRemove = (file) => {
1952
- const removed = value.find((item) => item.id === file.uid || item.uid === file.uid);
1953
- const newValue = value.filter((item) => item.id !== file.uid && item.uid !== file.uid);
2017
+ const fileKey = String(file.uid || file.id || file.objectName || "");
2018
+ const removed = valueRef.current.find(
2019
+ (item) => item.id === fileKey || item.uid === fileKey || item.objectName === fileKey
2020
+ );
2021
+ const newValue = valueRef.current.filter(
2022
+ (item) => item.id !== fileKey && item.uid !== fileKey && item.objectName !== fileKey
2023
+ );
1954
2024
  setValue(newValue);
1955
2025
  if (removed?.objectName) {
1956
2026
  api.deleteFile(removed.objectName, removed.bucketName || bucketName).catch(() => void 0);
1957
2027
  }
1958
2028
  };
1959
2029
  const handlePreview = async (file) => {
1960
- const item = value.find((current) => current.id === file.uid || current.uid === file.uid);
2030
+ const item = valueRef.current.find(
2031
+ (current) => current.id === file.uid || current.uid === file.uid || current.objectName === file.uid
2032
+ );
1961
2033
  let url = item?.previewUrl || item?.url || file.url || file.thumbUrl;
1962
2034
  if (item?.objectName) {
1963
2035
  const ticket = await api.createFileAccessTicket(
@@ -1970,15 +2042,22 @@ function ImageFieldPC({
1970
2042
  }
1971
2043
  if (url) window.open(url, "_blank", "noopener,noreferrer");
1972
2044
  };
1973
- const fileList = value.map((item) => ({
1974
- uid: item.id,
1975
- name: item.name,
1976
- url: item.url,
1977
- thumbUrl: item.url,
1978
- status: item.status ?? "done",
1979
- percent: item.percent
1980
- }));
2045
+ const handleDownload = async (item) => {
2046
+ let url = item.downloadUrl || item.url;
2047
+ if (item.objectName) {
2048
+ const ticket = await api.createDownloadTicket(
2049
+ item.bucketName || bucketName,
2050
+ item.objectName,
2051
+ item.name
2052
+ );
2053
+ url = typeof ticket === "string" ? ticket : ticket?.downloadUrl || ticket?.relayUrl || ticket?.url || url;
2054
+ }
2055
+ if (url) window.open(url, "_blank", "noopener,noreferrer");
2056
+ };
1981
2057
  const beforeUpload = (file) => {
2058
+ if (maxCount && valueRef.current.length >= maxCount) {
2059
+ return false;
2060
+ }
1982
2061
  if (!String(file.type || "").startsWith("image/") && !/\.(png|jpe?g|gif|bmp|svg|webp)$/i.test(file.name)) {
1983
2062
  return false;
1984
2063
  }
@@ -1990,26 +2069,27 @@ function ImageFieldPC({
1990
2069
  const customRequest = async ({ file, onProgress, onSuccess, onError }) => {
1991
2070
  const currentFile = file;
1992
2071
  const localItem = createLocalItem2(currentFile);
1993
- const nextValue = multiple ? [...value, localItem].slice(0, maxCount ?? Infinity) : [localItem];
2072
+ const currentValue = valueRef.current.filter(
2073
+ (item) => item.id !== localItem.id && item.uid !== localItem.uid
2074
+ );
2075
+ const nextValue = multiple ? [...currentValue, localItem] : [localItem];
1994
2076
  setValue(nextValue);
1995
2077
  try {
1996
2078
  const uploaded = await api.uploadFile(currentFile, bucketName, (percent) => {
1997
2079
  onProgress?.({ percent });
1998
- setValue(
1999
- nextValue.map(
2000
- (item) => item.id === localItem.id ? { ...item, percent, status: "uploading" } : item
2001
- )
2002
- );
2003
- });
2004
- const completed = normalizeAttachmentItem({
2005
- ...uploaded,
2006
- id: uploaded.id || localItem.id,
2007
- uid: uploaded.uid || localItem.uid,
2008
- url: uploaded.previewUrl || uploaded.url || localItem.url,
2009
- status: "done",
2010
- percent: 100
2080
+ replaceItem(localItem, { ...localItem, percent, status: "uploading" });
2011
2081
  });
2012
- setValue(nextValue.map((item) => item.id === localItem.id ? completed : item));
2082
+ const completed = normalizeAttachmentItem(
2083
+ {
2084
+ ...uploaded,
2085
+ url: uploaded.previewUrl || uploaded.url || localItem.url,
2086
+ previewUrl: uploaded.previewUrl || uploaded.url || localItem.url,
2087
+ status: "done",
2088
+ percent: 100
2089
+ },
2090
+ localItem
2091
+ );
2092
+ replaceItem(localItem, completed);
2013
2093
  onSuccess?.(completed);
2014
2094
  } catch (error) {
2015
2095
  const failed = {
@@ -2017,7 +2097,7 @@ function ImageFieldPC({
2017
2097
  status: "error",
2018
2098
  error: error?.message || "\u4E0A\u4F20\u5931\u8D25"
2019
2099
  };
2020
- setValue(nextValue.map((item) => item.id === localItem.id ? failed : item));
2100
+ replaceItem(localItem, failed);
2021
2101
  onError?.(error);
2022
2102
  }
2023
2103
  };
@@ -2027,42 +2107,74 @@ function ImageFieldPC({
2027
2107
  {
2028
2108
  "data-testid": `imagefield-input-${fieldId}`,
2029
2109
  listType,
2030
- fileList,
2110
+ fileList: [],
2031
2111
  customRequest,
2032
2112
  accept: accept ?? "image/*",
2033
2113
  maxCount,
2034
2114
  multiple,
2035
2115
  disabled,
2036
- showUploadList: {
2037
- showPreviewIcon,
2038
- showRemoveIcon,
2039
- showDownloadIcon
2040
- },
2041
- onChange: handleChange,
2116
+ showUploadList: false,
2042
2117
  onRemove: handleRemove,
2043
2118
  onPreview: handlePreview,
2044
2119
  beforeUpload,
2045
2120
  children: (!maxCount || value.length < maxCount) && /* @__PURE__ */ jsx42("div", { "data-testid": `imagefield-upload-btn-${fieldId}`, children: "+ \u4E0A\u4F20\u56FE\u7247" })
2046
2121
  }
2047
2122
  ),
2048
- value.length > 0 && /* @__PURE__ */ jsx42("div", { className: "sy-image-grid", "data-testid": `imagefield-rich-grid-${fieldId}`, children: value.map((item) => /* @__PURE__ */ jsx42(
2049
- "button",
2050
- {
2051
- type: "button",
2052
- className: "sy-image-thumb",
2053
- disabled: item.status !== "done",
2054
- onClick: () => handlePreview({
2055
- uid: item.uid || item.id,
2056
- url: item.url,
2057
- thumbUrl: item.previewUrl
2058
- }),
2059
- children: item.url || item.previewUrl ? /* @__PURE__ */ jsx42("img", { src: item.previewUrl || item.url, alt: item.name }) : /* @__PURE__ */ jsxs7("span", { children: [
2060
- Math.round(item.percent ?? 0),
2061
- "%"
2062
- ] })
2063
- },
2064
- item.id || item.uid || item.name
2065
- )) })
2123
+ value.length > 0 && /* @__PURE__ */ jsx42("div", { className: "sy-image-grid", "data-testid": `imagefield-rich-grid-${fieldId}`, children: value.map((item, index) => {
2124
+ const itemKey = getAttachmentItemIdentity(item) || `${fieldId}-image-${String(index)}`;
2125
+ const canAct = item.status === "done";
2126
+ return /* @__PURE__ */ jsxs7(
2127
+ "div",
2128
+ {
2129
+ className: "sy-image-thumb",
2130
+ "data-testid": `imagefield-thumb-${itemKey}`,
2131
+ children: [
2132
+ /* @__PURE__ */ jsx42(
2133
+ "button",
2134
+ {
2135
+ type: "button",
2136
+ className: "sy-image-preview",
2137
+ disabled: !canAct && !(item.previewUrl || item.url),
2138
+ onClick: () => handlePreview({
2139
+ uid: item.uid || item.id,
2140
+ url: item.url,
2141
+ thumbUrl: item.previewUrl
2142
+ }),
2143
+ children: /* @__PURE__ */ jsx42(ImageThumbContent, { item })
2144
+ }
2145
+ ),
2146
+ /* @__PURE__ */ jsxs7("div", { className: "sy-image-actions", children: [
2147
+ showPreviewIcon && /* @__PURE__ */ jsx42(
2148
+ "button",
2149
+ {
2150
+ type: "button",
2151
+ disabled: !canAct,
2152
+ onClick: () => handlePreview({
2153
+ uid: item.uid || item.id,
2154
+ url: item.url,
2155
+ thumbUrl: item.previewUrl
2156
+ }),
2157
+ children: "\u9884\u89C8"
2158
+ }
2159
+ ),
2160
+ showDownloadIcon && /* @__PURE__ */ jsx42("button", { type: "button", disabled: !canAct, onClick: () => handleDownload(item), children: "\u4E0B\u8F7D" }),
2161
+ showRemoveIcon && /* @__PURE__ */ jsx42(
2162
+ "button",
2163
+ {
2164
+ type: "button",
2165
+ disabled,
2166
+ onClick: () => handleRemove({ uid: item.uid || item.id }),
2167
+ "data-testid": `remove-btn-${itemKey}`,
2168
+ children: "\u5220\u9664"
2169
+ }
2170
+ )
2171
+ ] }),
2172
+ canAct && item.size ? /* @__PURE__ */ jsx42("span", { className: "sy-image-meta", children: formatFileSize(item.size) }) : null
2173
+ ]
2174
+ },
2175
+ `${itemKey}-${index}`
2176
+ );
2177
+ }) })
2066
2178
  ] });
2067
2179
  }
2068
2180
 
@@ -6346,15 +6458,15 @@ function FormProvider({
6346
6458
  ]
6347
6459
  );
6348
6460
  const registryComponents = components ?? defaultComponentRegistry;
6349
- return React34.createElement(
6461
+ return React36.createElement(
6350
6462
  FormContext.Provider,
6351
6463
  { value: contextValue },
6352
- React34.createElement(ComponentRegistryProvider, { components: registryComponents, children })
6464
+ React36.createElement(ComponentRegistryProvider, { components: registryComponents, children })
6353
6465
  );
6354
6466
  }
6355
6467
 
6356
6468
  // src/core/FormRenderer.tsx
6357
- import React35 from "react";
6469
+ import React37 from "react";
6358
6470
  import { jsx as jsx69 } from "react/jsx-runtime";
6359
6471
  var columnsClassMap = {
6360
6472
  1: "sy-grid-cols-1",
@@ -6378,7 +6490,7 @@ function FieldRenderer({ field, fieldClassName }) {
6378
6490
  return null;
6379
6491
  }
6380
6492
  const { fieldId, componentName: _, ...fieldProps } = field;
6381
- return React35.createElement(Component, {
6493
+ return React37.createElement(Component, {
6382
6494
  ...fieldProps,
6383
6495
  fieldId,
6384
6496
  className: fieldClassName ?? fieldProps.className
@@ -7178,7 +7290,7 @@ function FormTabs({ items, defaultActiveKey, className, tabClassName }) {
7178
7290
  }
7179
7291
 
7180
7292
  // src/layout/FormSteps/index.tsx
7181
- import React39, { useState as useState20 } from "react";
7293
+ import React41, { useState as useState20 } from "react";
7182
7294
  import { jsx as jsx75, jsxs as jsxs27 } from "react/jsx-runtime";
7183
7295
  function FormSteps({ items, className, onStepChange }) {
7184
7296
  const [currentStep, setCurrentStep] = useState20(0);
@@ -7197,7 +7309,7 @@ function FormSteps({ items, className, onStepChange }) {
7197
7309
  }
7198
7310
  };
7199
7311
  return /* @__PURE__ */ jsxs27("div", { className: className ?? "w-full", "data-testid": "form-steps", children: [
7200
- /* @__PURE__ */ jsx75("div", { className: "flex items-center mb-6", "data-testid": "form-steps-indicator", children: items.map((item, index) => /* @__PURE__ */ jsxs27(React39.Fragment, { children: [
7312
+ /* @__PURE__ */ jsx75("div", { className: "flex items-center mb-6", "data-testid": "form-steps-indicator", children: items.map((item, index) => /* @__PURE__ */ jsxs27(React41.Fragment, { children: [
7201
7313
  /* @__PURE__ */ jsxs27("div", { className: "flex items-center", children: [
7202
7314
  /* @__PURE__ */ jsx75(
7203
7315
  "div",
@@ -8654,7 +8766,7 @@ function defineFormSchema(schema) {
8654
8766
  }
8655
8767
 
8656
8768
  // src/modules/FormSummaryCard.tsx
8657
- import { Fragment, jsx as jsx77, jsxs as jsxs29 } from "react/jsx-runtime";
8769
+ import { Fragment as Fragment2, jsx as jsx77, jsxs as jsxs29 } from "react/jsx-runtime";
8658
8770
  var toneClasses = {
8659
8771
  brand: "bg-blue-50 text-blue-600",
8660
8772
  success: "bg-green-50 text-green-600",
@@ -8707,11 +8819,11 @@ var FormSummaryCard = ({
8707
8819
  creator && /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2 mt-4 text-sm text-gray-600", children: [
8708
8820
  renderAvatar(),
8709
8821
  /* @__PURE__ */ jsx77("span", { className: "font-medium", children: creator.name }),
8710
- creator.department && /* @__PURE__ */ jsxs29(Fragment, { children: [
8822
+ creator.department && /* @__PURE__ */ jsxs29(Fragment2, { children: [
8711
8823
  /* @__PURE__ */ jsx77("span", { className: "text-gray-300", children: "\xB7" }),
8712
8824
  /* @__PURE__ */ jsx77("span", { children: creator.department })
8713
8825
  ] }),
8714
- createdAt && /* @__PURE__ */ jsxs29(Fragment, { children: [
8826
+ createdAt && /* @__PURE__ */ jsxs29(Fragment2, { children: [
8715
8827
  /* @__PURE__ */ jsx77("span", { className: "text-gray-300", children: "\xB7" }),
8716
8828
  /* @__PURE__ */ jsx77("span", { className: "text-gray-400", children: createdAt })
8717
8829
  ] })
@@ -9202,7 +9314,7 @@ var ApprovalTimeline = ({
9202
9314
  };
9203
9315
 
9204
9316
  // src/modules/ApprovalActionBar.tsx
9205
- import React43, { useMemo as useMemo16, useState as useState32 } from "react";
9317
+ import React45, { useMemo as useMemo16, useState as useState32 } from "react";
9206
9318
  import { Form, Input as Input10, Modal as Modal5, Select as Select5 } from "antd";
9207
9319
  import {
9208
9320
  CheckOutlined,
@@ -9231,7 +9343,7 @@ var confirmAction = (title, content) => {
9231
9343
  };
9232
9344
 
9233
9345
  // src/modules/StickyActionBar.tsx
9234
- import { Fragment as Fragment2, jsx as jsx81, jsxs as jsxs33 } from "react/jsx-runtime";
9346
+ import { Fragment as Fragment3, jsx as jsx81, jsxs as jsxs33 } from "react/jsx-runtime";
9235
9347
  var getActionPriority = (action) => {
9236
9348
  const maybePriority = action.priority;
9237
9349
  if (typeof maybePriority === "number") return maybePriority;
@@ -9293,7 +9405,7 @@ var StickyActionBar = ({
9293
9405
  {
9294
9406
  className: `mx-auto flex w-full items-center gap-3 ${layoutMode === "approval" && !isMobile ? "justify-center" : "justify-end"}`,
9295
9407
  style: { maxWidth: maxWidthStyle },
9296
- children: isMobile ? /* @__PURE__ */ jsxs33(Fragment2, { children: [
9408
+ children: isMobile ? /* @__PURE__ */ jsxs33(Fragment3, { children: [
9297
9409
  /* @__PURE__ */ jsx81("div", { className: "flex flex-1 gap-2", children: primaryMobile.map((action) => renderButton(action, true)) }),
9298
9410
  moreMobile.length > 0 && /* @__PURE__ */ jsx81(
9299
9411
  Dropdown,
@@ -9321,7 +9433,7 @@ var StickyActionBar = ({
9321
9433
  };
9322
9434
 
9323
9435
  // src/modules/ApprovalActionBar.tsx
9324
- import { Fragment as Fragment3, jsx as jsx82, jsxs as jsxs34 } from "react/jsx-runtime";
9436
+ import { Fragment as Fragment4, jsx as jsx82, jsxs as jsxs34 } from "react/jsx-runtime";
9325
9437
  var actionPriority = {
9326
9438
  agree: 10,
9327
9439
  approved: 10,
@@ -9371,14 +9483,14 @@ function DefaultTransferSelector({
9371
9483
  value,
9372
9484
  onChange
9373
9485
  }) {
9374
- const formContext = React43.useContext(FormContext);
9486
+ const formContext = React45.useContext(FormContext);
9375
9487
  const [open, setOpen] = useState32(false);
9376
9488
  const [selectedUsers, setSelectedUsers] = useState32([]);
9377
9489
  if (!formContext) {
9378
9490
  return /* @__PURE__ */ jsx82(Input10, { value, onChange: (event) => onChange(event.target.value) });
9379
9491
  }
9380
9492
  const selectedLabel = selectedUsers[0] ? getUserName(selectedUsers[0]) : value;
9381
- return /* @__PURE__ */ jsxs34(Fragment3, { children: [
9493
+ return /* @__PURE__ */ jsxs34(Fragment4, { children: [
9382
9494
  /* @__PURE__ */ jsx82(
9383
9495
  Input10,
9384
9496
  {
@@ -9519,7 +9631,7 @@ var ApprovalActionBar = ({
9519
9631
  };
9520
9632
  });
9521
9633
  const requiredComment = activeAction?.remark?.required || modalAction === "rejected" || modalAction === "withdraw";
9522
- return /* @__PURE__ */ jsxs34(Fragment3, { children: [
9634
+ return /* @__PURE__ */ jsxs34(Fragment4, { children: [
9523
9635
  /* @__PURE__ */ jsx82(
9524
9636
  StickyActionBar,
9525
9637
  {
@@ -9565,7 +9677,7 @@ var ApprovalActionBar = ({
9565
9677
  )
9566
9678
  }
9567
9679
  ),
9568
- modalAction === "return" && /* @__PURE__ */ jsxs34(Fragment3, { children: [
9680
+ modalAction === "return" && /* @__PURE__ */ jsxs34(Fragment4, { children: [
9569
9681
  formatReturnPolicy(returnPolicy) && /* @__PURE__ */ jsxs34("div", { className: "mb-3 text-sm text-gray-500", children: [
9570
9682
  "\u9000\u56DE\u540E\u5BA1\u6279\u903B\u8F91\uFF1A",
9571
9683
  formatReturnPolicy(returnPolicy)
@@ -9610,7 +9722,7 @@ var ApprovalActionBar = ({
9610
9722
  import { useMemo as useMemo17, useState as useState33 } from "react";
9611
9723
  import { Button as Button11, Dropdown as Dropdown2, Form as Form2, Input as Input11, Modal as Modal6 } from "antd";
9612
9724
  import { MoreOutlined as MoreOutlined2 } from "@ant-design/icons";
9613
- import { Fragment as Fragment4, jsx as jsx83, jsxs as jsxs35 } from "react/jsx-runtime";
9725
+ import { Fragment as Fragment5, jsx as jsx83, jsxs as jsxs35 } from "react/jsx-runtime";
9614
9726
  var priority = {
9615
9727
  agree: 10,
9616
9728
  approved: 10,
@@ -9724,7 +9836,7 @@ var ApprovalActions = ({
9724
9836
  },
9725
9837
  action.action
9726
9838
  );
9727
- return /* @__PURE__ */ jsxs35(Fragment4, { children: [
9839
+ return /* @__PURE__ */ jsxs35(Fragment5, { children: [
9728
9840
  /* @__PURE__ */ jsxs35(
9729
9841
  "div",
9730
9842
  {
@@ -9788,7 +9900,7 @@ var ApprovalActions = ({
9788
9900
  // src/modules/FormActionBar.tsx
9789
9901
  import { useState as useState34 } from "react";
9790
9902
  import { Button as Button12 } from "antd";
9791
- import { Fragment as Fragment5, jsx as jsx84, jsxs as jsxs36 } from "react/jsx-runtime";
9903
+ import { Fragment as Fragment6, jsx as jsx84, jsxs as jsxs36 } from "react/jsx-runtime";
9792
9904
  var FormActionBar = ({
9793
9905
  actions,
9794
9906
  position = "bottom-fixed",
@@ -9849,7 +9961,7 @@ var FormActionBar = ({
9849
9961
  }
9850
9962
  );
9851
9963
  if (isFixed) {
9852
- return /* @__PURE__ */ jsxs36(Fragment5, { children: [
9964
+ return /* @__PURE__ */ jsxs36(Fragment6, { children: [
9853
9965
  bar,
9854
9966
  /* @__PURE__ */ jsx84("div", { className: "h-16" })
9855
9967
  ] });
@@ -9859,7 +9971,7 @@ var FormActionBar = ({
9859
9971
 
9860
9972
  // src/modules/RuntimePageShell.tsx
9861
9973
  import { Alert, Empty as Empty4, Spin as Spin5 } from "antd";
9862
- import { Fragment as Fragment6, jsx as jsx85, jsxs as jsxs37 } from "react/jsx-runtime";
9974
+ import { Fragment as Fragment7, jsx as jsx85, jsxs as jsxs37 } from "react/jsx-runtime";
9863
9975
  var RuntimePageShell = ({
9864
9976
  children,
9865
9977
  actions,
@@ -9885,7 +9997,7 @@ var RuntimePageShell = ({
9885
9997
  return /* @__PURE__ */ jsx85(Alert, { type: "error", showIcon: true, message: "\u9875\u9762\u52A0\u8F7D\u5931\u8D25", description: error });
9886
9998
  }
9887
9999
  if (empty) {
9888
- return /* @__PURE__ */ jsx85(Fragment6, { children: empty });
10000
+ return /* @__PURE__ */ jsx85(Fragment7, { children: empty });
9889
10001
  }
9890
10002
  return children;
9891
10003
  };
@@ -11483,7 +11595,7 @@ var DataManagementList = ({
11483
11595
  import { useState as useState36, useCallback as useCallback18 } from "react";
11484
11596
  import { Button as Button16, Select as Select7 } from "antd";
11485
11597
  import { CheckCircleFilled as CheckCircleFilled2 } from "@ant-design/icons";
11486
- import { Fragment as Fragment7, jsx as jsx90, jsxs as jsxs42 } from "react/jsx-runtime";
11598
+ import { Fragment as Fragment8, jsx as jsx90, jsxs as jsxs42 } from "react/jsx-runtime";
11487
11599
  var pickFormInstanceId = (value) => {
11488
11600
  if (!value) return void 0;
11489
11601
  if (typeof value === "string") return value;
@@ -11500,7 +11612,7 @@ var SubmitSuccessCard = ({
11500
11612
  renderSuccess
11501
11613
  }) => {
11502
11614
  if (renderSuccess) {
11503
- return /* @__PURE__ */ jsx90(Fragment7, { children: renderSuccess(info) });
11615
+ return /* @__PURE__ */ jsx90(Fragment8, { children: renderSuccess(info) });
11504
11616
  }
11505
11617
  return /* @__PURE__ */ jsx90("div", { className: "bg-white rounded-xl shadow-sm border border-gray-100 p-6", children: /* @__PURE__ */ jsxs42("div", { className: "flex flex-col items-center py-16 animate-[fadeIn_0.3s_ease-out,scaleIn_0.3s_ease-out]", children: [
11506
11618
  /* @__PURE__ */ jsx90("div", { className: "w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx90(CheckCircleFilled2, { className: "text-2xl text-green-500" }) }),