Browse Source

添加xlsx

yourname 5 months ago
parent
commit
129f2482f7
4 changed files with 755 additions and 1 deletions
  1. 2 1
      package.json
  2. 97 0
      pnpm-lock.yaml
  3. 587 0
      src/server/utils/excelParser.ts
  4. 69 0
      src/share/exceltypes.ts

+ 2 - 1
package.json

@@ -41,7 +41,8 @@
     "react-router-dom": "^7.6.1",
     "react-router-dom": "^7.6.1",
     "react-toastify": "^11.0.5",
     "react-toastify": "^11.0.5",
     "reflect-metadata": "^0.2.2",
     "reflect-metadata": "^0.2.2",
-    "typeorm": "^0.3.24"
+    "typeorm": "^0.3.24",
+    "xlsx": "^0.18.5"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@types/debug": "^4.1.12",
     "@types/debug": "^4.1.12",

+ 97 - 0
pnpm-lock.yaml

@@ -110,6 +110,9 @@ importers:
       typeorm:
       typeorm:
         specifier: ^0.3.24
         specifier: ^0.3.24
         version: 0.3.24(babel-plugin-macros@3.1.0)(ioredis@5.6.1)(mysql2@3.14.1)(reflect-metadata@0.2.2)
         version: 0.3.24(babel-plugin-macros@3.1.0)(ioredis@5.6.1)(mysql2@3.14.1)(reflect-metadata@0.2.2)
+      xlsx:
+        specifier: ^0.18.5
+        version: 0.18.5
     devDependencies:
     devDependencies:
       '@types/debug':
       '@types/debug':
         specifier: ^4.1.12
         specifier: ^4.1.12
@@ -151,6 +154,9 @@ packages:
   '@ant-design/colors@7.2.1':
   '@ant-design/colors@7.2.1':
     resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==}
     resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==}
 
 
+  '@ant-design/colors@8.0.0':
+    resolution: {integrity: sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==}
+
   '@ant-design/cssinjs-utils@1.1.3':
   '@ant-design/cssinjs-utils@1.1.3':
     resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==}
     resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==}
     peerDependencies:
     peerDependencies:
@@ -167,6 +173,10 @@ packages:
     resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==}
     resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==}
     engines: {node: '>=8.x'}
     engines: {node: '>=8.x'}
 
 
+  '@ant-design/fast-color@3.0.0':
+    resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==}
+    engines: {node: '>=8.x'}
+
   '@ant-design/icons-svg@4.4.2':
   '@ant-design/icons-svg@4.4.2':
     resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
     resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
 
 
@@ -1015,6 +1025,12 @@ packages:
       react: '>=16.9.0'
       react: '>=16.9.0'
       react-dom: '>=16.9.0'
       react-dom: '>=16.9.0'
 
 
+  '@rc-component/util@1.2.1':
+    resolution: {integrity: sha512-AUVu6jO+lWjQnUOOECwu8iR0EdElQgWW5NBv5vP/Uf9dWbAX3udhMutRlkVXjuac2E40ghkFy+ve00mc/3Fymg==}
+    peerDependencies:
+      react: '>=18.0.0'
+      react-dom: '>=18.0.0'
+
   '@rolldown/pluginutils@1.0.0-beta.11':
   '@rolldown/pluginutils@1.0.0-beta.11':
     resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
     resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
 
 
@@ -1319,6 +1335,10 @@ packages:
     engines: {node: '>=0.4.0'}
     engines: {node: '>=0.4.0'}
     hasBin: true
     hasBin: true
 
 
+  adler-32@1.3.1:
+    resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
+    engines: {node: '>=0.8'}
+
   ansi-regex@5.0.1:
   ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
     engines: {node: '>=8'}
@@ -1437,6 +1457,10 @@ packages:
   caniuse-lite@1.0.30001723:
   caniuse-lite@1.0.30001723:
     resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
     resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
 
 
+  cfb@1.2.2:
+    resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
+    engines: {node: '>=0.8'}
+
   chownr@3.0.0:
   chownr@3.0.0:
     resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
     resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
     engines: {node: '>=18'}
     engines: {node: '>=18'}
@@ -1456,6 +1480,10 @@ packages:
     resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
     resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
 
 
+  codepage@1.15.0:
+    resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
+    engines: {node: '>=0.8'}
+
   color-convert@2.0.1:
   color-convert@2.0.1:
     resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
     resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
     engines: {node: '>=7.0.0'}
     engines: {node: '>=7.0.0'}
@@ -1498,6 +1526,11 @@ packages:
     resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
     resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
 
 
+  crc-32@1.2.2:
+    resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
   cross-spawn@7.0.6:
   cross-spawn@7.0.6:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
     engines: {node: '>= 8'}
@@ -1690,6 +1723,10 @@ packages:
     resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
     resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
     engines: {node: '>=12.20.0'}
     engines: {node: '>=12.20.0'}
 
 
+  frac@1.1.2:
+    resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
+    engines: {node: '>=0.8'}
+
   fsevents@2.3.3:
   fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2575,6 +2612,10 @@ packages:
     resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
     resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
     engines: {node: '>= 0.6'}
     engines: {node: '>= 0.6'}
 
 
+  ssf@0.11.2:
+    resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
+    engines: {node: '>=0.8'}
+
   stacktracey@2.1.8:
   stacktracey@2.1.8:
     resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
     resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
 
 
@@ -2820,6 +2861,14 @@ packages:
     engines: {node: '>= 8'}
     engines: {node: '>= 8'}
     hasBin: true
     hasBin: true
 
 
+  wmf@1.0.2:
+    resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
+    engines: {node: '>=0.8'}
+
+  word@0.3.0:
+    resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
+    engines: {node: '>=0.8'}
+
   workerd@1.20250321.0:
   workerd@1.20250321.0:
     resolution: {integrity: sha512-vyuz9pdJ+7o1lC79vQ2UVRLXPARa2Lq94PbTfqEcYQeSxeR9X+YqhNq2yysv8Zs5vpokmexLCtMniPp9u+2LVQ==}
     resolution: {integrity: sha512-vyuz9pdJ+7o1lC79vQ2UVRLXPARa2Lq94PbTfqEcYQeSxeR9X+YqhNq2yysv8Zs5vpokmexLCtMniPp9u+2LVQ==}
     engines: {node: '>=16'}
     engines: {node: '>=16'}
@@ -2860,6 +2909,11 @@ packages:
       utf-8-validate:
       utf-8-validate:
         optional: true
         optional: true
 
 
+  xlsx@0.18.5:
+    resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
   xml2js@0.6.2:
   xml2js@0.6.2:
     resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
     resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
     engines: {node: '>=4.0.0'}
     engines: {node: '>=4.0.0'}
@@ -2916,6 +2970,10 @@ snapshots:
     dependencies:
     dependencies:
       '@ant-design/fast-color': 2.0.6
       '@ant-design/fast-color': 2.0.6
 
 
+  '@ant-design/colors@8.0.0':
+    dependencies:
+      '@ant-design/fast-color': 3.0.0
+
   '@ant-design/cssinjs-utils@1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
   '@ant-design/cssinjs-utils@1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
     dependencies:
     dependencies:
       '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
       '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -2940,6 +2998,8 @@ snapshots:
     dependencies:
     dependencies:
       '@babel/runtime': 7.27.6
       '@babel/runtime': 7.27.6
 
 
+  '@ant-design/fast-color@3.0.0': {}
+
   '@ant-design/icons-svg@4.4.2': {}
   '@ant-design/icons-svg@4.4.2': {}
 
 
   '@ant-design/icons@5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
   '@ant-design/icons@5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
@@ -3639,6 +3699,12 @@ snapshots:
       react: 19.1.0
       react: 19.1.0
       react-dom: 19.1.0(react@19.1.0)
       react-dom: 19.1.0(react@19.1.0)
 
 
+  '@rc-component/util@1.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+    dependencies:
+      react: 19.1.0
+      react-dom: 19.1.0(react@19.1.0)
+      react-is: 18.3.1
+
   '@rolldown/pluginutils@1.0.0-beta.11': {}
   '@rolldown/pluginutils@1.0.0-beta.11': {}
 
 
   '@rollup/rollup-android-arm-eabi@4.43.0':
   '@rollup/rollup-android-arm-eabi@4.43.0':
@@ -3884,6 +3950,8 @@ snapshots:
 
 
   acorn@8.14.0: {}
   acorn@8.14.0: {}
 
 
+  adler-32@1.3.1: {}
+
   ansi-regex@5.0.1: {}
   ansi-regex@5.0.1: {}
 
 
   ansi-regex@6.1.0: {}
   ansi-regex@6.1.0: {}
@@ -4048,6 +4116,11 @@ snapshots:
 
 
   caniuse-lite@1.0.30001723: {}
   caniuse-lite@1.0.30001723: {}
 
 
+  cfb@1.2.2:
+    dependencies:
+      adler-32: 1.3.1
+      crc-32: 1.2.2
+
   chownr@3.0.0: {}
   chownr@3.0.0: {}
 
 
   classnames@2.5.1: {}
   classnames@2.5.1: {}
@@ -4062,6 +4135,8 @@ snapshots:
 
 
   cluster-key-slot@1.1.2: {}
   cluster-key-slot@1.1.2: {}
 
 
+  codepage@1.15.0: {}
+
   color-convert@2.0.1:
   color-convert@2.0.1:
     dependencies:
     dependencies:
       color-name: 1.1.4
       color-name: 1.1.4
@@ -4104,6 +4179,8 @@ snapshots:
       path-type: 4.0.0
       path-type: 4.0.0
       yaml: 1.10.2
       yaml: 1.10.2
 
 
+  crc-32@1.2.2: {}
+
   cross-spawn@7.0.6:
   cross-spawn@7.0.6:
     dependencies:
     dependencies:
       path-key: 3.1.1
       path-key: 3.1.1
@@ -4304,6 +4381,8 @@ snapshots:
     dependencies:
     dependencies:
       fetch-blob: 3.2.0
       fetch-blob: 3.2.0
 
 
+  frac@1.1.2: {}
+
   fsevents@2.3.3:
   fsevents@2.3.3:
     optional: true
     optional: true
 
 
@@ -5284,6 +5363,10 @@ snapshots:
 
 
   sqlstring@2.3.3: {}
   sqlstring@2.3.3: {}
 
 
+  ssf@0.11.2:
+    dependencies:
+      frac: 1.1.2
+
   stacktracey@2.1.8:
   stacktracey@2.1.8:
     dependencies:
     dependencies:
       as-table: 1.0.55
       as-table: 1.0.55
@@ -5476,6 +5559,10 @@ snapshots:
     dependencies:
     dependencies:
       isexe: 2.0.0
       isexe: 2.0.0
 
 
+  wmf@1.0.2: {}
+
+  word@0.3.0: {}
+
   workerd@1.20250321.0:
   workerd@1.20250321.0:
     optionalDependencies:
     optionalDependencies:
       '@cloudflare/workerd-darwin-64': 1.20250321.0
       '@cloudflare/workerd-darwin-64': 1.20250321.0
@@ -5522,6 +5609,16 @@ snapshots:
 
 
   ws@8.18.0: {}
   ws@8.18.0: {}
 
 
+  xlsx@0.18.5:
+    dependencies:
+      adler-32: 1.3.1
+      cfb: 1.2.2
+      codepage: 1.15.0
+      crc-32: 1.2.2
+      ssf: 0.11.2
+      wmf: 1.0.2
+      word: 0.3.0
+
   xml2js@0.6.2:
   xml2js@0.6.2:
     dependencies:
     dependencies:
       sax: 1.4.1
       sax: 1.4.1

+ 587 - 0
src/server/utils/excelParser.ts

@@ -0,0 +1,587 @@
+import * as XLSX from 'xlsx';
+import type { SheetConfig } from '@/share/exceltypes';
+
+// Excel数据行类型
+export interface ExcelRow {
+  [key: string]: string | number | null | undefined;
+  _id?: string;
+  tableIndex?: number;
+}
+
+// Excel原始数据类型
+type ExcelJsonData = Array<Array<string | number | null>>;
+
+/**
+ * 服务器端Excel解析器
+ * 从useExcelParser.ts中抽取的核心功能,移除了React和UI相关代码
+ */
+export class ExcelParser {
+  /**
+   * 处理数据用于导出
+   * @param data 原始数据行
+   * @param sheetConfig 工作表配置
+   * @param tableIndex 表格索引
+   * @returns 处理后的数据
+   */
+  processDataForExport(
+    data: ExcelRow[],
+    sheetConfig: SheetConfig,
+    tableIndex: number = 0
+  ): ExcelRow[] {
+    console.log(`[ExcelParser] 开始处理导出数据,数据行数: ${data.length},表格索引: ${tableIndex}`);
+    
+    // 记录exportFields和requiredFields配置
+    console.log(`[ExcelParser] 导出字段配置: ${sheetConfig.exportFields.length} 个字段,必需字段: ${sheetConfig.requiredFields.length} 个`);
+    
+    const result = data
+      .map((row, index) => {
+        const processedRow: ExcelRow = {};
+        processedRow._id = `table-${tableIndex}-row-${index}`;
+        
+        // 如果exportFields为空,保留所有原始字段
+        if (sheetConfig.exportFields.length === 0) {
+          console.log(`[ExcelParser] 导出字段为空,保留行 ${index} 的所有原始字段`);
+          Object.keys(row).forEach(field => {
+            processedRow[field] = row[field] ?? null;
+          });
+        } else {
+          // 按照exportFields配置处理
+          sheetConfig.exportFields.forEach(field => {
+            const mappedField = sheetConfig.fieldMappings[field] || field;
+            processedRow[mappedField] = row[field] ?? null;
+          });
+        }
+        
+        return processedRow;
+      })
+      .filter(row => {
+        // 如果没有设置必需字段或exportFields为空,则不过滤
+        if (sheetConfig.requiredFields.length === 0 || sheetConfig.exportFields.length === 0) {
+          return true;
+        }
+        
+        // 检查必需字段是否有值
+        const isValid = sheetConfig.requiredFields.every(field => {
+          const value = row[sheetConfig.fieldMappings[field] || field];
+          return value !== null && value !== undefined && value !== '';
+        });
+        
+        if (!isValid) {
+          console.log(`[ExcelParser] 过滤掉不满足必需字段的行: ${row._id}`);
+        }
+        
+        return isValid;
+      });
+    
+    console.log(`[ExcelParser] 处理后的导出数据行数: ${result.length}`);
+    return result;
+  }
+
+  /**
+   * 处理所有字段数据
+   * @param data 原始数据行
+   * @param tableIndex 表格索引
+   * @returns 处理后的数据
+   */
+  processAllFieldsData(
+    data: ExcelRow[],
+    tableIndex: number = 0
+  ): ExcelRow[] {
+    console.log(`[ExcelParser] 处理所有字段数据,数据行数: ${data.length},表格索引: ${tableIndex}`);
+    
+    const result = data.map((row, index) => ({
+      ...row,
+      _id: `table-${tableIndex}-row-${index}`
+    }));
+    
+    return result;
+  }
+
+  /**
+   * 获取每个工作表中的所有可用字段
+   * @param data 工作表数据
+   * @returns 按工作表分类的可用字段
+   */
+  getAllAvailableFields(data: { [key: string]: ExcelRow[] }): { [key: string]: string[] } {
+    console.log(`[ExcelParser] 获取所有可用字段,工作表数: ${Object.keys(data).length}`);
+    
+    const result: { [key: string]: string[] } = {};
+    
+    // 遍历每个工作表的数据
+    Object.keys(data).forEach(sheetName => {
+      const sheetData = data[sheetName];
+      
+      if (sheetData.length === 0) {
+        console.log(`[ExcelParser] 工作表 ${sheetName} 没有数据,返回空字段列表`);
+        result[sheetName] = [];
+        return;
+      }
+      
+      // 获取该工作表中的所有唯一字段名
+      const uniqueFields = new Set<string>();
+      
+      sheetData.forEach(row => {
+        Object.keys(row).forEach(key => {
+          // 排除特殊字段和空值
+          if (key !== '_id' && key !== 'tableIndex') {
+            uniqueFields.add(key);
+          }
+        });
+      });
+      
+      // 转换为数组
+      result[sheetName] = Array.from(uniqueFields);
+      console.log(`[ExcelParser] 工作表 ${sheetName} 的可用字段数: ${result[sheetName].length}`);
+    });
+    
+    return result;
+  }
+
+  /**
+   * 解析单个表格数据
+   */
+  parseSingleTable(
+    json: ExcelJsonData,
+    headers: string[],
+    startRow: number,
+    endMarker: string,
+    orderNumberRow: number,
+    orderNumberCol: number,
+    productNameRow?: number,
+    productNameCol?: number
+  ): { data: ExcelRow[]; endIndex: number } {
+    console.log(`[ExcelParser] 开始解析单个表格,起始行: ${startRow},结束标记: ${endMarker || '无'}`);
+    console.log(`[ExcelParser] 表头行数: ${headers.length},订单号位置: 行=${orderNumberRow}, 列=${orderNumberCol}`);
+    if (productNameRow !== undefined && productNameCol !== undefined) {
+      console.log(`[ExcelParser] 产品名称位置: 行=${productNameRow}, 列=${productNameCol}`);
+    }
+    
+    const data: ExcelRow[] = [];
+    let endIndex = json.length - 1;
+    let orderNumber = '';
+    let productName = '';
+
+    // 获取订单号
+    if (orderNumberRow >= 0 && orderNumberRow < json.length && 
+        orderNumberCol >= 0 && orderNumberCol < (json[orderNumberRow]?.length || 0)) {
+      orderNumber = String(json[orderNumberRow][orderNumberCol] || '');
+      console.log(`[ExcelParser] 获取到订单号: ${orderNumber}`);
+    } else {
+      console.log(`[ExcelParser] 未获取到订单号,参数可能无效`);
+    }
+
+    // 获取产品名称(如果配置了产品名称行列号)
+    if (productNameRow !== undefined && productNameCol !== undefined && 
+        productNameRow >= 0 && productNameRow < json.length && 
+        productNameCol >= 0 && productNameCol < (json[productNameRow]?.length || 0)) {
+      productName = String(json[productNameRow][productNameCol] || '');
+      console.log(`[ExcelParser] 获取到产品名称: ${productName}`);
+    } else if (productNameRow !== undefined || productNameCol !== undefined) {
+      console.log(`[ExcelParser] 未获取到产品名称,参数可能无效`);
+    }
+
+    // 找到结束标记行(如果有)
+    if (endMarker && endMarker.trim() !== '') {
+      console.log(`[ExcelParser] 搜索结束标记: ${endMarker}`);
+      let found = false;
+      
+      for (let i = startRow; i < json.length; i++) {
+        if (!json[i]) continue;
+
+        const firstCell = json[i][0];
+        if (firstCell && String(firstCell).includes(endMarker)) {
+          endIndex = i - 1;
+          found = true;
+          console.log(`[ExcelParser] 找到结束标记,结束行索引: ${endIndex}`);
+          break;
+        }
+      }
+      
+      if (!found) {
+        console.log(`[ExcelParser] 未找到结束标记,将使用表格最后一行`);
+      }
+    }
+
+    // 从起始行到结束行,提取数据并映射到列标题
+    console.log(`[ExcelParser] 开始解析数据行,范围: ${startRow} - ${endIndex}`);
+    let validRowCount = 0;
+    
+    for (let i = startRow; i <= endIndex; i++) {
+      if (!json[i] || json[i].length === 0) {
+        console.log(`[ExcelParser] 跳过空行: ${i}`);
+        continue;
+      }
+
+      const row: ExcelRow = {};
+      
+      // 检查行是否有效(非空行)
+      let hasValidData = false;
+      
+      // 映射列标题和值
+      headers.forEach((header, columnIndex) => {
+        if (header) {
+          const value = json[i][columnIndex];
+          row[header] = value ?? null;
+          
+          // 如果有任何一个单元格有值,则认为这行有效
+          if (value !== null && value !== undefined && value !== '') {
+            hasValidData = true;
+          }
+        }
+      });
+      
+      // 添加订单号
+      if (orderNumber) {
+        row['订单号'] = orderNumber;
+      }
+      
+      // 添加产品名称
+      if (productName) {
+        row['产品名称'] = productName;
+      }
+      
+      // 只添加有效的行数据
+      if (hasValidData) {
+        data.push(row);
+        validRowCount++;
+      } else {
+        console.log(`[ExcelParser] 跳过无效行: ${i} (没有有效数据)`);
+      }
+    }
+
+    console.log(`[ExcelParser] 解析完成,有效数据行数: ${validRowCount}`);
+    return { data, endIndex };
+  }
+
+  /**
+   * 解析多表格数据
+   */
+  parseMultiTable(
+    json: ExcelJsonData,
+    sheetConfig: SheetConfig
+  ): ExcelRow[][] {
+    console.log(`[ExcelParser] 开始解析多表格数据,配置: 标题行=${sheetConfig.headerRowIndex}, 数据起始行=${sheetConfig.dataStartRow}`);
+    
+    const tableDataSets: ExcelRow[][] = [];
+    let currentRow = sheetConfig.dataStartRow - 1;
+    const { 
+      headerRowIndex,
+      endMarker, 
+      multiTableHeaderOffset = 2,
+      multiTableDataOffset = 1,
+      multiTableOrderNumberOffset = -1,
+      multiTableProductNameOffset = -1
+    } = sheetConfig;
+    
+    console.log(`[ExcelParser] 多表格配置: headerOffset=${multiTableHeaderOffset}, dataOffset=${multiTableDataOffset}, orderNumberOffset=${multiTableOrderNumberOffset}`);
+    if (sheetConfig.productNameRow !== undefined && sheetConfig.productNameCol !== undefined) {
+      console.log(`[ExcelParser] 产品名称配置: productNameOffset=${multiTableProductNameOffset}`);
+    }
+    
+    // 解析第一个表格
+    console.log(`[ExcelParser] 开始解析第一个表格`);
+    const { data: firstTableData, endIndex: firstEndIndex } = this.parseSingleTable(
+      json,
+      json[headerRowIndex - 1] as string[],
+      currentRow,
+      endMarker,
+      sheetConfig.orderNumberRow - 1,
+      sheetConfig.orderNumberCol - 1,
+      sheetConfig.productNameRow !== undefined ? sheetConfig.productNameRow - 1 : undefined,
+      sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined
+    );
+    
+    if (firstTableData.length > 0) {
+      tableDataSets.push(firstTableData);
+      console.log(`[ExcelParser] 第一个表格解析完成,数据行数: ${firstTableData.length}`);
+    } else {
+      console.log(`[ExcelParser] 第一个表格没有有效数据`);
+    }
+    
+    currentRow = firstEndIndex + 1;
+    console.log(`[ExcelParser] 更新当前行索引: ${currentRow}`);
+    
+    // 继续解析后续表格
+    let tableCount = 1;
+    
+    while (currentRow < json.length - 1 && currentRow + multiTableHeaderOffset < json.length) {
+      tableCount++;
+      console.log(`[ExcelParser] 开始解析表格 #${tableCount}`);
+      
+      const newHeaderRowIndex = currentRow + multiTableHeaderOffset;
+      const newDataStartRowIndex = newHeaderRowIndex + multiTableDataOffset;
+      const newOrderNumberRow = newHeaderRowIndex + multiTableOrderNumberOffset;
+      
+      console.log(`[ExcelParser] 表格 #${tableCount} 计算位置: 标题行=${newHeaderRowIndex}, 数据起始行=${newDataStartRowIndex}, 订单号行=${newOrderNumberRow}`);
+      
+      // 计算产品名称行(如果配置了产品名称行列号)
+      let newProductNameRow = undefined;
+      if (sheetConfig.productNameRow !== undefined && sheetConfig.productNameCol !== undefined) {
+        newProductNameRow = newHeaderRowIndex + multiTableProductNameOffset;
+        console.log(`[ExcelParser] 表格 #${tableCount} 产品名称行=${newProductNameRow}`);
+      }
+      
+      if (newDataStartRowIndex >= json.length) {
+        console.log(`[ExcelParser] 表格 #${tableCount} 数据起始行超出范围,停止解析`);
+        break;
+      }
+      
+      // 获取新表格的列标题
+      const newHeaders = json[newHeaderRowIndex] as string[];
+      if (!newHeaders || newHeaders.length === 0) {
+        console.log(`[ExcelParser] 表格 #${tableCount} 无有效标题行,停止解析`);
+        break;
+      }
+      
+      console.log(`[ExcelParser] 表格 #${tableCount} 获取到标题列数: ${newHeaders.filter(Boolean).length}`);
+      
+      // 解析新表格
+      const { data: tableData, endIndex } = this.parseSingleTable(
+        json,
+        newHeaders,
+        newDataStartRowIndex,
+        endMarker,
+        newOrderNumberRow,
+        sheetConfig.orderNumberCol - 1,
+        newProductNameRow,
+        sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined
+      );
+      
+      if (tableData.length > 0) {
+        tableDataSets.push(tableData);
+        console.log(`[ExcelParser] 表格 #${tableCount} 解析完成,数据行数: ${tableData.length}`);
+      } else {
+        console.log(`[ExcelParser] 表格 #${tableCount} 没有有效数据`);
+      }
+      
+      currentRow = endIndex + 1;
+      console.log(`[ExcelParser] 更新当前行索引: ${currentRow}`);
+    }
+    
+    console.log(`[ExcelParser] 多表格解析完成,共 ${tableDataSets.length} 个表格`);
+    return tableDataSets;
+  }
+
+  /**
+   * 解析Excel文件
+   * @param buffer Excel文件的ArrayBuffer数据
+   * @param sheetConfigs 工作表配置
+   * @returns 解析结果
+   */
+  async parseExcelBuffer(buffer: ArrayBuffer, sheetConfigs: SheetConfig[]) {
+    console.log(`[ExcelParser] 开始解析Excel文件,文件大小: ${buffer.byteLength} 字节,工作表配置数: ${sheetConfigs.length}`);
+    
+    try {
+      const workbook = XLSX.read(buffer);
+      console.log(`[ExcelParser] Excel文件读取成功,工作表数: ${workbook.SheetNames.length}`);
+      console.log(`[ExcelParser] 工作表列表: ${workbook.SheetNames.join(', ')}`);
+      
+      const rawData: { [key: string]: ExcelRow[] } = {};
+      const warnings: string[] = [];
+      let totalTables = 0;
+      
+      // 处理每个配置的工作表
+      for (const sheetConfig of sheetConfigs) {
+        console.log(`[ExcelParser] 开始处理工作表: ${sheetConfig.sheetName}`);
+        const worksheet = workbook.Sheets[sheetConfig.sheetName];
+        
+        if (!worksheet) {
+          const warning = `未找到工作表: ${sheetConfig.sheetName}`;
+          console.log(`[ExcelParser] 警告: ${warning}`);
+          warnings.push(warning);
+          continue;
+        }
+
+        console.log(`[ExcelParser] 转换工作表为JSON数据`);
+        const json = XLSX.utils.sheet_to_json<string[]>(worksheet, { header: 1 }) as ExcelJsonData;
+        console.log(`[ExcelParser] 转换完成,行数: ${json.length}`);
+        
+        const headers = json[sheetConfig.headerRowIndex - 1] as string[];
+        
+        if (!headers || headers.length === 0) {
+          const warning = `工作表 ${sheetConfig.sheetName} 的表头数据无效`;
+          console.log(`[ExcelParser] 警告: ${warning}`);
+          warnings.push(warning);
+          continue;
+        }
+        
+        console.log(`[ExcelParser] 获取到表头,列数: ${headers.filter(Boolean).length}`);
+        console.log(`[ExcelParser] 是否为多表格模式: ${sheetConfig.isMultiTable ? '是' : '否'}`);
+
+        if (sheetConfig.isMultiTable) {
+          // 处理多表格模式
+          const tableDataSets = this.parseMultiTable(json, sheetConfig);
+          
+          if (tableDataSets.length > 0) {
+            console.log(`[ExcelParser] 多表格模式解析完成,共 ${tableDataSets.length} 个表格`);
+            
+            // 保存所有原始字段数据
+            rawData[sheetConfig.sheetName] = tableDataSets.flatMap((tableData, tableIndex) => {
+              console.log(`[ExcelParser] 处理表格 #${tableIndex + 1} 的所有字段数据,行数: ${tableData.length}`);
+              return this.processAllFieldsData(tableData, tableIndex + 1).map(row => ({
+                ...row,
+                tableIndex: tableIndex + 1,
+              }));
+            });
+            
+            totalTables += tableDataSets.length;
+            console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 处理完成,原始数据行数: ${rawData[sheetConfig.sheetName].length}`);
+          } else {
+            const warning = `未在 "${sheetConfig.sheetName}" 中找到有效的数据表`;
+            console.log(`[ExcelParser] 警告: ${warning}`);
+            warnings.push(warning);
+          }
+        } else {
+          // 处理单表格模式
+          console.log(`[ExcelParser] 开始处理单表格模式`);
+          
+          const { data } = this.parseSingleTable(
+            json,
+            headers,
+            sheetConfig.dataStartRow - 1,
+            sheetConfig.endMarker,
+            sheetConfig.orderNumberRow - 1,
+            sheetConfig.orderNumberCol - 1,
+            sheetConfig.productNameRow !== undefined ? sheetConfig.productNameRow - 1 : undefined,
+            sheetConfig.productNameCol !== undefined ? sheetConfig.productNameCol - 1 : undefined
+          );
+          
+          if (data.length > 0) {
+            console.log(`[ExcelParser] 单表格模式解析完成,数据行数: ${data.length}`);
+            
+            // 保存所有原始字段数据
+            rawData[sheetConfig.sheetName] = this.processAllFieldsData(data, 1);
+            
+            totalTables += 1;
+            console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 处理完成,原始数据行数: ${rawData[sheetConfig.sheetName].length}`);
+          } else {
+            const warning = `未在 "${sheetConfig.sheetName}" 中找到有效数据`;
+            console.log(`[ExcelParser] 警告: ${warning}`);
+            warnings.push(warning);
+          }
+        }
+      }
+
+      // 生成导出数据
+      console.log(`[ExcelParser] 开始生成导出数据`);
+      const exportData = this.generateExportData(rawData, sheetConfigs);
+      
+      // 计算可用字段(按工作表分类)
+      console.log(`[ExcelParser] 计算可用字段`);
+      const availableFieldsBySheet = this.getAllAvailableFields(rawData);
+      
+      console.log(`[ExcelParser] Excel解析完成,总表格数: ${totalTables},警告数: ${warnings.length}`);
+      return {
+        rawData,
+        exportData,
+        availableFieldsBySheet,
+        warnings,
+        totalTables
+      };
+    } catch (error) {
+      console.error(`[ExcelParser] Excel解析错误:`, error);
+      throw error;
+    }
+  }
+
+  /**
+   * 从URL或base64字符串获取ArrayBuffer
+   */
+  async getBufferFromUrlOrBase64(input: string): Promise<ArrayBuffer> {
+    console.log(`[ExcelParser] 开始从输入获取数据`);
+    
+    if (input.startsWith('data:')) {
+      console.log(`[ExcelParser] 检测到Base64编码数据`);
+      
+      // 处理Base64编码
+      const base64 = input.split(',')[1];
+      
+      if (!base64) {
+        console.error(`[ExcelParser] Base64格式错误,无法提取数据部分`);
+        throw new Error('Base64格式错误,无法提取数据部分');
+      }
+      
+      console.log(`[ExcelParser] 正在解码Base64数据`);
+      const binaryString = atob(base64);
+      const len = binaryString.length;
+      console.log(`[ExcelParser] Base64解码完成,二进制数据长度: ${len} 字节`);
+      
+      const bytes = new Uint8Array(len);
+      for (let i = 0; i < len; i++) {
+        bytes[i] = binaryString.charCodeAt(i);
+      }
+      
+      console.log(`[ExcelParser] 成功获取ArrayBuffer,大小: ${bytes.buffer.byteLength} 字节`);
+      return bytes.buffer;
+    } else if (input.startsWith('http')) {
+      console.log(`[ExcelParser] 检测到URL: ${input.substring(0, 50)}...`);
+      
+      // 处理URL
+      try {
+        console.log(`[ExcelParser] 开始从URL获取文件`);
+        const response = await fetch(input);
+        
+        if (!response.ok) {
+          console.error(`[ExcelParser] 获取文件失败: ${response.status} ${response.statusText}`);
+          throw new Error(`获取文件失败: ${response.statusText}`);
+        }
+        
+        const buffer = await response.arrayBuffer();
+        console.log(`[ExcelParser] 成功从URL获取文件,大小: ${buffer.byteLength} 字节`);
+        
+        return buffer;
+      } catch (error) {
+        console.error(`[ExcelParser] 从URL获取文件时出错:`, error);
+        throw new Error(`从URL获取文件时出错: ${error instanceof Error ? error.message : String(error)}`);
+      }
+    } else {
+      console.error(`[ExcelParser] 不支持的输入格式: ${input.substring(0, 50)}...`);
+      throw new Error('不支持的输入格式,请提供URL或Base64编码的数据');
+    }
+  }
+
+  /**
+   * 根据当前配置动态生成导出数据
+   */
+  generateExportData(
+    allFieldsData: { [key: string]: ExcelRow[] },
+    sheetConfigs: SheetConfig[]
+  ): { [key: string]: ExcelRow[] } {
+    console.log(`[ExcelParser] 开始生成导出数据,工作表数: ${Object.keys(allFieldsData).length}`);
+    
+    // 如果没有原始数据,直接返回空对象
+    if (Object.keys(allFieldsData).length === 0) {
+      console.log(`[ExcelParser] 没有原始数据,返回空导出数据`);
+      return {};
+    }
+    
+    const exportData: { [key: string]: ExcelRow[] } = {};
+    
+    // 处理每个工作表的数据
+    for (const sheetConfig of sheetConfigs) {
+      console.log(`[ExcelParser] 处理工作表 ${sheetConfig.sheetName} 的导出数据`);
+      
+      const rawDataForSheet = allFieldsData[sheetConfig.sheetName];
+      
+      if (!rawDataForSheet || rawDataForSheet.length === 0) {
+        console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 没有原始数据,跳过处理`);
+        continue;
+      }
+      
+      // 根据当前配置处理数据
+      exportData[sheetConfig.sheetName] = this.processDataForExport(
+        rawDataForSheet,
+        sheetConfig,
+        1
+      );
+      
+      console.log(`[ExcelParser] 工作表 ${sheetConfig.sheetName} 导出数据处理完成,行数: ${exportData[sheetConfig.sheetName].length}`);
+    }
+    
+    console.log(`[ExcelParser] 导出数据生成完成,工作表数: ${Object.keys(exportData).length}`);
+    return exportData;
+  }
+}
+
+// 导出单例实例,便于服务端使用
+export const excelParser = new ExcelParser(); 

+ 69 - 0
src/share/exceltypes.ts

@@ -0,0 +1,69 @@
+export interface ExcelRow {
+    [key: string]: string | number | null | undefined;
+    _id?: string;
+    tableIndex?: number;
+  }
+  
+  export interface SheetConfig {
+    /** 表头行号 */
+    headerRowIndex: number;
+    /** 订单号行号 */
+    orderNumberRow: number;
+    /** 订单号列号 */
+    orderNumberCol: number;
+    /** 产品名称行号 */
+    productNameRow?: number;
+    /** 产品名称列号 */
+    productNameCol?: number;
+    /** 数据起始行号 */
+    dataStartRow: number;
+    /** 终止标记 */
+    endMarker: string;
+    /** 工作表名称 */
+    sheetName: string;
+    /** 导出字段 */
+    exportFields: string[];
+    /** 字段映射 */
+    fieldMappings: { [key: string]: string };
+    /** 必需字段 */
+    requiredFields: string[];
+    /** 是否支持多表格模式 */
+    isMultiTable?: boolean;
+    /** 多表格模式下,新表格的表头行号偏移量(相对于上一个表格的结束标记) */
+    multiTableHeaderOffset?: number;
+    /** 多表格模式下,新表格的数据起始行偏移量(相对于新表头) */
+    multiTableDataOffset?: number;
+    /** 多表格模式下,订单号行相对于表头的偏移量(负数表示在表头之前) */
+    multiTableOrderNumberOffset?: number;
+    /** 多表格模式下,产品名称行相对于表头的偏移量(负数表示在表头之前) */
+    multiTableProductNameOffset?: number;
+  }
+  
+  export interface ExcelConfig {
+    sheets: SheetConfig[];
+    activeSheetIndex: number;
+  }
+  
+  export type ViewMode = 'table' | 'json';
+  
+  export interface DataViewerProps {
+    data: { [key: string]: ExcelRow[] };
+    viewMode: ViewMode;
+    config: ExcelConfig;
+  }
+  
+  export interface SheetConfigProps {
+    config: SheetConfig;
+    availableFields: string[];
+    onConfigChange: (config: Partial<SheetConfig>) => void;
+    onRemove?: () => void;
+  }
+  
+  export interface FileUploadProps {
+    onFileChange: (file: File) => Promise<void>;
+  }
+  
+  export interface ExportButtonProps {
+    data: { [key: string]: ExcelRow[] };
+    disabled?: boolean;
+  }