袁伟杰: V2.0.002 其他入库其他出库扫码
This commit is contained in:
@@ -88,6 +88,7 @@
|
|||||||
"build:quickapp-webview": "uni build -p quickapp-webview",
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"test": "vitest run --config vitest.config.ts",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"init-husky": "git init && husky",
|
"init-husky": "git init && husky",
|
||||||
"init-baseFiles": "node ./scripts/create-base-files.js",
|
"init-baseFiles": "node ./scripts/create-base-files.js",
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
"unplugin-auto-import": "^20.0.0",
|
"unplugin-auto-import": "^20.0.0",
|
||||||
"vite": "5.2.8",
|
"vite": "5.2.8",
|
||||||
"vite-plugin-restart": "^1.0.0",
|
"vite-plugin-restart": "^1.0.0",
|
||||||
|
"vitest": "^3.2.4",
|
||||||
"vue-tsc": "^3.0.6"
|
"vue-tsc": "^3.0.6"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
279
pnpm-lock.yaml
generated
279
pnpm-lock.yaml
generated
@@ -135,7 +135,7 @@ importers:
|
|||||||
version: 20.19.11
|
version: 20.19.11
|
||||||
'@uni-helper/eslint-config':
|
'@uni-helper/eslint-config':
|
||||||
specifier: 0.5.0
|
specifier: 0.5.0
|
||||||
version: 0.5.0(@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.34.0(jiti@2.6.1))
|
version: 0.5.0(@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1)))(eslint@9.34.0(jiti@2.6.1))
|
||||||
'@uni-helper/plugin-uni':
|
'@uni-helper/plugin-uni':
|
||||||
specifier: 0.1.0
|
specifier: 0.1.0
|
||||||
version: 0.1.0(@dcloudio/vite-plugin-uni@3.0.0-4070620250821001(postcss@8.5.6)(rollup@4.50.0)(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))(vue@3.4.21(typescript@5.8.3)))
|
version: 0.1.0(@dcloudio/vite-plugin-uni@3.0.0-4070620250821001(postcss@8.5.6)(rollup@4.50.0)(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))(vue@3.4.21(typescript@5.8.3)))
|
||||||
@@ -235,6 +235,9 @@ importers:
|
|||||||
vite-plugin-restart:
|
vite-plugin-restart:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))
|
version: 1.0.0(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))
|
||||||
|
vitest:
|
||||||
|
specifier: ^3.2.4
|
||||||
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1)
|
||||||
vue-tsc:
|
vue-tsc:
|
||||||
specifier: ^3.0.6
|
specifier: ^3.0.6
|
||||||
version: 3.0.6(typescript@5.8.3)
|
version: 3.0.6(typescript@5.8.3)
|
||||||
@@ -1937,12 +1940,18 @@ packages:
|
|||||||
'@types/babel__traverse@7.28.0':
|
'@types/babel__traverse@7.28.0':
|
||||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||||
|
|
||||||
|
'@types/chai@5.2.3':
|
||||||
|
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||||
|
|
||||||
'@types/conventional-commits-parser@5.0.1':
|
'@types/conventional-commits-parser@5.0.1':
|
||||||
resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
|
resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2':
|
||||||
|
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||||
|
|
||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
@@ -2345,6 +2354,35 @@ packages:
|
|||||||
vitest:
|
vitest:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4':
|
||||||
|
resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||||
|
|
||||||
'@volar/language-core@2.4.23':
|
'@volar/language-core@2.4.23':
|
||||||
resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
|
resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==}
|
||||||
|
|
||||||
@@ -2565,6 +2603,10 @@ packages:
|
|||||||
array-timsort@1.0.3:
|
array-timsort@1.0.3:
|
||||||
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
|
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
|
||||||
|
|
||||||
|
assertion-error@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ast-kit@0.11.3:
|
ast-kit@0.11.3:
|
||||||
resolution: {integrity: sha512-qdwwKEhckRk0XE22/xDdmU3v/60E8Edu4qFhgTLIhGGDs/PAJwLw9pQn8Rj99PitlbBZbYpx0k/lbir4kg0SuA==}
|
resolution: {integrity: sha512-qdwwKEhckRk0XE22/xDdmU3v/60E8Edu4qFhgTLIhGGDs/PAJwLw9pQn8Rj99PitlbBZbYpx0k/lbir4kg0SuA==}
|
||||||
engines: {node: '>=16.14.0'}
|
engines: {node: '>=16.14.0'}
|
||||||
@@ -2758,6 +2800,10 @@ packages:
|
|||||||
centra@2.7.0:
|
centra@2.7.0:
|
||||||
resolution: {integrity: sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==}
|
resolution: {integrity: sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2776,6 +2822,10 @@ packages:
|
|||||||
character-entities@2.0.2:
|
character-entities@2.0.2:
|
||||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||||
|
|
||||||
|
check-error@2.1.3:
|
||||||
|
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
chokidar@3.6.0:
|
chokidar@3.6.0:
|
||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||||
engines: {node: '>= 8.10.0'}
|
engines: {node: '>= 8.10.0'}
|
||||||
@@ -3045,6 +3095,10 @@ packages:
|
|||||||
dedent@0.7.0:
|
dedent@0.7.0:
|
||||||
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
||||||
|
|
||||||
|
deep-eql@5.0.2:
|
||||||
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
@@ -3478,6 +3532,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
expect-type@1.3.0:
|
||||||
|
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
expect@27.5.1:
|
expect@27.5.1:
|
||||||
resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
|
resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
|
||||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||||
@@ -4299,6 +4357,9 @@ packages:
|
|||||||
longest-streak@3.1.0:
|
longest-streak@3.1.0:
|
||||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||||
|
|
||||||
|
loupe@3.2.1:
|
||||||
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
@@ -4782,6 +4843,10 @@ packages:
|
|||||||
pathe@2.0.3:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
pathval@2.0.1:
|
||||||
|
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||||
|
engines: {node: '>= 14.16'}
|
||||||
|
|
||||||
perfect-debounce@1.0.0:
|
perfect-debounce@1.0.0:
|
||||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||||
|
|
||||||
@@ -5210,6 +5275,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
siginfo@2.0.0:
|
||||||
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
signal-exit@3.0.7:
|
signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
@@ -5271,6 +5339,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -5399,6 +5470,9 @@ packages:
|
|||||||
timm@1.7.1:
|
timm@1.7.1:
|
||||||
resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==}
|
resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==}
|
||||||
|
|
||||||
|
tinybench@2.9.0:
|
||||||
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
tinycolor2@1.6.0:
|
tinycolor2@1.6.0:
|
||||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||||
|
|
||||||
@@ -5416,6 +5490,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
tinypool@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0:
|
||||||
|
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyspy@4.0.4:
|
||||||
|
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
tmpl@1.0.5:
|
tmpl@1.0.5:
|
||||||
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
||||||
|
|
||||||
@@ -5652,6 +5738,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
vite-node@3.2.4:
|
||||||
|
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
vite-plugin-restart@1.0.0:
|
vite-plugin-restart@1.0.0:
|
||||||
resolution: {integrity: sha512-t2ktkTOa+DQX05TEZm/3FE0DyrYEyFXdhG5gLcta5p71zOpg9yG3DeRcHWJVLJgWNoaVtOr4fUlr1kKu+WfXyQ==}
|
resolution: {integrity: sha512-t2ktkTOa+DQX05TEZm/3FE0DyrYEyFXdhG5gLcta5p71zOpg9yG3DeRcHWJVLJgWNoaVtOr4fUlr1kKu+WfXyQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5685,6 +5776,34 @@ packages:
|
|||||||
terser:
|
terser:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vitest@3.2.4:
|
||||||
|
resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/debug': ^4.1.12
|
||||||
|
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||||
|
'@vitest/browser': 3.2.4
|
||||||
|
'@vitest/ui': 3.2.4
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/debug':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
vscode-uri@3.1.0:
|
vscode-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||||
|
|
||||||
@@ -5772,6 +5891,11 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
word-wrap@1.2.5:
|
word-wrap@1.2.5:
|
||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5916,7 +6040,7 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping': 0.3.13
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
'@jridgewell/trace-mapping': 0.3.30
|
'@jridgewell/trace-mapping': 0.3.30
|
||||||
|
|
||||||
'@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)':
|
'@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/install-pkg': 1.1.0
|
'@antfu/install-pkg': 1.1.0
|
||||||
'@clack/prompts': 0.11.0
|
'@clack/prompts': 0.11.0
|
||||||
@@ -5925,7 +6049,7 @@ snapshots:
|
|||||||
'@stylistic/eslint-plugin': 5.4.0(eslint@9.34.0(jiti@2.6.1))
|
'@stylistic/eslint-plugin': 5.4.0(eslint@9.34.0(jiti@2.6.1))
|
||||||
'@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
'@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
||||||
'@typescript-eslint/parser': 8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
||||||
'@vitest/eslint-plugin': 1.3.16(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
'@vitest/eslint-plugin': 1.3.16(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1))
|
||||||
ansis: 4.2.0
|
ansis: 4.2.0
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
eslint: 9.34.0(jiti@2.6.1)
|
eslint: 9.34.0(jiti@2.6.1)
|
||||||
@@ -8508,6 +8632,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.28.4
|
'@babel/types': 7.28.4
|
||||||
|
|
||||||
|
'@types/chai@5.2.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/deep-eql': 4.0.2
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
|
||||||
'@types/conventional-commits-parser@5.0.1':
|
'@types/conventional-commits-parser@5.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.11
|
'@types/node': 20.19.11
|
||||||
@@ -8516,6 +8645,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2': {}
|
||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
'@types/graceful-fs@4.1.9':
|
'@types/graceful-fs@4.1.9':
|
||||||
@@ -8711,9 +8842,9 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.0
|
'@typescript-eslint/types': 8.46.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@uni-helper/eslint-config@0.5.0(@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.34.0(jiti@2.6.1))':
|
'@uni-helper/eslint-config@0.5.0(@antfu/eslint-config@5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1)))(eslint@9.34.0(jiti@2.6.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/eslint-config': 5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
'@antfu/eslint-config': 5.2.1(@unocss/eslint-plugin@66.5.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.1(eslint@9.34.0(jiti@2.6.1)))(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1))
|
||||||
'@eslint/eslintrc': 3.3.1
|
'@eslint/eslintrc': 3.3.1
|
||||||
eslint: 9.34.0(jiti@2.6.1)
|
eslint: 9.34.0(jiti@2.6.1)
|
||||||
eslint-flat-config-utils: 2.1.1
|
eslint-flat-config-utils: 2.1.1
|
||||||
@@ -9108,16 +9239,59 @@ snapshots:
|
|||||||
vite: 5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
vite: 5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
||||||
vue: 3.4.21(typescript@5.8.3)
|
vue: 3.4.21(typescript@5.8.3)
|
||||||
|
|
||||||
'@vitest/eslint-plugin@1.3.16(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)':
|
'@vitest/eslint-plugin@1.3.16(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.46.0
|
'@typescript-eslint/scope-manager': 8.46.0
|
||||||
'@typescript-eslint/utils': 8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
'@typescript-eslint/utils': 8.46.0(eslint@9.34.0(jiti@2.6.1))(typescript@5.8.3)
|
||||||
eslint: 9.34.0(jiti@2.6.1)
|
eslint: 9.34.0(jiti@2.6.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.3
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.19
|
||||||
|
optionalDependencies:
|
||||||
|
vite: 5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
pathe: 2.0.3
|
||||||
|
strip-literal: 3.0.0
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
magic-string: 0.30.19
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyspy: 4.0.4
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
loupe: 3.2.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
'@volar/language-core@2.4.23':
|
'@volar/language-core@2.4.23':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/source-map': 2.4.23
|
'@volar/source-map': 2.4.23
|
||||||
@@ -9383,6 +9557,8 @@ snapshots:
|
|||||||
|
|
||||||
array-timsort@1.0.3: {}
|
array-timsort@1.0.3: {}
|
||||||
|
|
||||||
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
ast-kit@0.11.3(rollup@4.50.0):
|
ast-kit@0.11.3(rollup@4.50.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.28.3
|
'@babel/parser': 7.28.3
|
||||||
@@ -9633,6 +9809,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
dependencies:
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
check-error: 2.1.3
|
||||||
|
deep-eql: 5.0.2
|
||||||
|
loupe: 3.2.1
|
||||||
|
pathval: 2.0.1
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
@@ -9646,6 +9830,8 @@ snapshots:
|
|||||||
|
|
||||||
character-entities@2.0.2: {}
|
character-entities@2.0.2: {}
|
||||||
|
|
||||||
|
check-error@2.1.3: {}
|
||||||
|
|
||||||
chokidar@3.6.0:
|
chokidar@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
anymatch: 3.1.3
|
anymatch: 3.1.3
|
||||||
@@ -9877,6 +10063,8 @@ snapshots:
|
|||||||
|
|
||||||
dedent@0.7.0: {}
|
dedent@0.7.0: {}
|
||||||
|
|
||||||
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
deepmerge@4.3.1: {}
|
deepmerge@4.3.1: {}
|
||||||
@@ -10381,6 +10569,8 @@ snapshots:
|
|||||||
|
|
||||||
exit@0.1.2: {}
|
exit@0.1.2: {}
|
||||||
|
|
||||||
|
expect-type@1.3.0: {}
|
||||||
|
|
||||||
expect@27.5.1:
|
expect@27.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 27.5.1
|
'@jest/types': 27.5.1
|
||||||
@@ -11427,6 +11617,8 @@ snapshots:
|
|||||||
|
|
||||||
longest-streak@3.1.0: {}
|
longest-streak@3.1.0: {}
|
||||||
|
|
||||||
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
@@ -12045,6 +12237,8 @@ snapshots:
|
|||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
pathval@2.0.1: {}
|
||||||
|
|
||||||
perfect-debounce@1.0.0: {}
|
perfect-debounce@1.0.0: {}
|
||||||
|
|
||||||
phin@2.9.3: {}
|
phin@2.9.3: {}
|
||||||
@@ -12475,6 +12669,8 @@ snapshots:
|
|||||||
side-channel-map: 1.0.1
|
side-channel-map: 1.0.1
|
||||||
side-channel-weakmap: 1.0.2
|
side-channel-weakmap: 1.0.2
|
||||||
|
|
||||||
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
signal-exit@3.0.7: {}
|
signal-exit@3.0.7: {}
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
@@ -12527,6 +12723,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 2.0.0
|
||||||
|
|
||||||
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
std-env@3.9.0: {}
|
std-env@3.9.0: {}
|
||||||
@@ -12645,6 +12843,8 @@ snapshots:
|
|||||||
|
|
||||||
timm@1.7.1: {}
|
timm@1.7.1: {}
|
||||||
|
|
||||||
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinycolor2@1.6.0: {}
|
tinycolor2@1.6.0: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
@@ -12661,6 +12861,12 @@ snapshots:
|
|||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
tinypool@1.1.1: {}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0: {}
|
||||||
|
|
||||||
|
tinyspy@4.0.4: {}
|
||||||
|
|
||||||
tmpl@1.0.5: {}
|
tmpl@1.0.5: {}
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
@@ -12946,6 +13152,23 @@ snapshots:
|
|||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
|
vite-node@3.2.4(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.3
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
vite: 5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vite-plugin-restart@1.0.0(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)):
|
vite-plugin-restart@1.0.0(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
micromatch: 4.0.8
|
micromatch: 4.0.8
|
||||||
@@ -12962,6 +13185,45 @@ snapshots:
|
|||||||
sass: 1.77.8
|
sass: 1.77.8
|
||||||
terser: 5.43.1
|
terser: 5.43.1
|
||||||
|
|
||||||
|
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(jsdom@16.7.0)(sass@1.77.8)(terser@5.43.1):
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.3
|
||||||
|
'@vitest/expect': 3.2.4
|
||||||
|
'@vitest/mocker': 3.2.4(vite@5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1))
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
'@vitest/runner': 3.2.4
|
||||||
|
'@vitest/snapshot': 3.2.4
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
debug: 4.4.3
|
||||||
|
expect-type: 1.3.0
|
||||||
|
magic-string: 0.30.19
|
||||||
|
pathe: 2.0.3
|
||||||
|
picomatch: 4.0.3
|
||||||
|
std-env: 3.9.0
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinyexec: 0.3.2
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
tinypool: 1.1.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
vite: 5.2.8(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
||||||
|
vite-node: 3.2.4(@types/node@20.19.11)(sass@1.77.8)(terser@5.43.1)
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/debug': 4.1.12
|
||||||
|
'@types/node': 20.19.11
|
||||||
|
jsdom: 16.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- msw
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vscode-uri@3.1.0: {}
|
vscode-uri@3.1.0: {}
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.4.21(typescript@5.8.3)):
|
vue-demi@0.14.10(vue@3.4.21(typescript@5.8.3)):
|
||||||
@@ -13047,6 +13309,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
siginfo: 2.0.0
|
||||||
|
stackback: 0.0.2
|
||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
wot-design-uni@1.13.0(vue@3.4.21(typescript@5.8.3)):
|
wot-design-uni@1.13.0(vue@3.4.21(typescript@5.8.3)):
|
||||||
|
|||||||
12
src/api/erp/customer/index.ts
Normal file
12
src/api/erp/customer/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { http } from '@/http/http'
|
||||||
|
|
||||||
|
/** 客户精简信息 */
|
||||||
|
export interface CustomerSimple {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取客户精简列表 */
|
||||||
|
export function getCustomerSimpleList() {
|
||||||
|
return http.get<CustomerSimple[]>('/erp/customer/simple-list')
|
||||||
|
}
|
||||||
@@ -16,7 +16,23 @@ export interface ProductSimple {
|
|||||||
weight?: number
|
weight?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按条码查询的产品信息 */
|
||||||
|
export interface ProductByBarCode {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
barCode?: string
|
||||||
|
unitId?: number
|
||||||
|
unitName?: string
|
||||||
|
standard?: string
|
||||||
|
minPrice?: number
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取产品精简列表 */
|
/** 获取产品精简列表 */
|
||||||
export function getProductSimpleList() {
|
export function getProductSimpleList() {
|
||||||
return http.get<ProductSimple[]>('/erp/product/simple-list')
|
return http.get<ProductSimple[]>('/erp/product/simple-list')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按条码查询启用中的产品 */
|
||||||
|
export function getProductByBarCode(barCode: string) {
|
||||||
|
return http.get<ProductByBarCode>('/erp/product/get-by-barcode', { barCode })
|
||||||
|
}
|
||||||
|
|||||||
157
src/components/erp-scan/item-list.vue
Normal file
157
src/components/erp-scan/item-list.vue
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<view class="item-list">
|
||||||
|
<view v-if="items.length === 0" class="item-list__empty">
|
||||||
|
暂无扫码明细
|
||||||
|
</view>
|
||||||
|
<view v-for="(item, index) in items" :key="`${item.warehouseId}-${item.productId}-${index}`" class="item-card">
|
||||||
|
<view class="item-card__header">
|
||||||
|
<text class="item-card__name">{{ item.productName }}</text>
|
||||||
|
<text class="item-card__remove" @tap="emit('remove', index)">删除</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-card__meta">
|
||||||
|
<text>仓库:{{ item.warehouseName || '-' }}</text>
|
||||||
|
<text>条码:{{ item.productBarCode || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-card__meta">
|
||||||
|
<text>规格:{{ item.productSpec || '-' }}</text>
|
||||||
|
<text>单位:{{ item.productUnitName || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-card__meta">
|
||||||
|
<text>单价:{{ formatNumber(item.productPrice) }}</text>
|
||||||
|
<text v-if="showStock">库存:{{ formatNumber(item.stockCount) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-card__footer">
|
||||||
|
<view class="counter">
|
||||||
|
<text class="counter__button" @tap="emit('decrease', index)">-</text>
|
||||||
|
<text class="counter__value">{{ formatNumber(item.count) }}</text>
|
||||||
|
<text class="counter__button" @tap="emit('increase', index)">+</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
export interface ItemCardValue {
|
||||||
|
warehouseId: number
|
||||||
|
warehouseName?: string
|
||||||
|
productId: number
|
||||||
|
productName: string
|
||||||
|
productBarCode?: string
|
||||||
|
productSpec?: string
|
||||||
|
productUnitName?: string
|
||||||
|
productPrice?: number
|
||||||
|
stockCount?: number
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
items: ItemCardValue[]
|
||||||
|
showStock?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showStock: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'increase', index: number): void
|
||||||
|
(event: 'decrease', index: number): void
|
||||||
|
(event: 'remove', index: number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:格式化数值展示,避免 `undefined` 直接透出到页面。
|
||||||
|
* 适用场景:数量、库存、单价展示。
|
||||||
|
* @param value 原始数值
|
||||||
|
* @return 格式化后的字符串
|
||||||
|
* 注意事项:扫码页优先保证可读性,这里只保留最多 4 位小数,不做复杂金额格式化。
|
||||||
|
*/
|
||||||
|
function formatNumber(value?: number) {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(value).toFixed(Number.isInteger(Number(value)) ? 0 : 4)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.item-list__empty {
|
||||||
|
padding: 28rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #fff;
|
||||||
|
color: #86909c;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-card {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&__header,
|
||||||
|
&__meta,
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__remove {
|
||||||
|
color: #f53f3f;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
color: #4e5969;
|
||||||
|
font-size: 24rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
|
||||||
|
&__button,
|
||||||
|
&__value {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f2f3f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
min-width: 88rpx;
|
||||||
|
background: #fff7e8;
|
||||||
|
color: #ad6800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
117
src/components/erp-scan/scan-input.vue
Normal file
117
src/components/erp-scan/scan-input.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<view class="scan-input">
|
||||||
|
<input
|
||||||
|
class="scan-input__field"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:focus="autoFocus"
|
||||||
|
:disabled="disabled"
|
||||||
|
confirm-type="done"
|
||||||
|
@input="handleInput"
|
||||||
|
@confirm="handleSubmit"
|
||||||
|
>
|
||||||
|
<button class="scan-input__button scan-input__button--primary" size="mini" :disabled="disabled || loading" @tap="handleSubmit">
|
||||||
|
{{ loading ? '处理中' : '确认' }}
|
||||||
|
</button>
|
||||||
|
<button class="scan-input__button scan-input__button--camera" size="mini" :disabled="disabled || loading" @tap="handleCameraScan">
|
||||||
|
相机
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
loading?: boolean
|
||||||
|
autoFocus?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
placeholder: '请扫描条码',
|
||||||
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
|
autoFocus: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', value: string): void
|
||||||
|
(event: 'submit'): void
|
||||||
|
(event: 'camera-scan'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:同步输入框内容到父组件。
|
||||||
|
* 适用场景:扫码枪键盘模拟输入、手工补录条码。
|
||||||
|
* @param event 输入事件
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:扫码枪的原始值最终仍由父组件统一清洗,这里只做输入透传。
|
||||||
|
*/
|
||||||
|
function handleInput(event: Record<string, any>) {
|
||||||
|
emit('update:modelValue', event.detail.value || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:把确认动作抛给父组件处理。
|
||||||
|
* 适用场景:扫码枪回车提交、用户手工点击确认按钮。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:父组件会负责防重入和业务校验,这里不重复做业务判断。
|
||||||
|
*/
|
||||||
|
function handleSubmit() {
|
||||||
|
if (props.disabled || props.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('submit')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:触发父组件进入相机扫码流程。
|
||||||
|
* 适用场景:普通手机没有外接扫码枪时,通过摄像头扫描条码。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:组件层不直接执行业务逻辑,只负责抛出统一事件,保证扫码入口一致。
|
||||||
|
*/
|
||||||
|
function handleCameraScan() {
|
||||||
|
if (props.disabled || props.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('camera-scan')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.scan-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
&__field {
|
||||||
|
flex: 1;
|
||||||
|
height: 84rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
border: 2rpx solid #d0d7de;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
width: 128rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
line-height: 84rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--camera {
|
||||||
|
background: #e8f3ff;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/components/erp-scan/submit-bar.vue
Normal file
49
src/components/erp-scan/submit-bar.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<view class="submit-bar">
|
||||||
|
<button class="submit-bar__ghost" @tap="emit('secondary')">
|
||||||
|
{{ secondaryText }}
|
||||||
|
</button>
|
||||||
|
<button class="submit-bar__primary" :loading="loading" @tap="emit('submit')">
|
||||||
|
{{ submitText }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
loading?: boolean
|
||||||
|
submitText?: string
|
||||||
|
secondaryText?: string
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
loading: false,
|
||||||
|
submitText: '提交单据',
|
||||||
|
secondaryText: '清空明细',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'submit'): void
|
||||||
|
(event: 'secondary'): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.submit-bar {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 0 calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
background: #f4f6f8;
|
||||||
|
|
||||||
|
&__ghost,
|
||||||
|
&__primary {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
403
src/pages-erp/stock-in/scan/index.vue
Normal file
403
src/pages-erp/stock-in/scan/index.vue
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
<template>
|
||||||
|
<view class="yd-page-container scan-page">
|
||||||
|
<wd-navbar
|
||||||
|
title="扫码其它入库"
|
||||||
|
left-arrow
|
||||||
|
placeholder
|
||||||
|
safe-area-inset-top
|
||||||
|
fixed
|
||||||
|
@click-left="handleBack"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<view class="scan-page__content">
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
单据信息
|
||||||
|
</view>
|
||||||
|
<picker :range="warehouses" range-key="name" :value="selectedWarehouseIndex" @change="handleWarehouseChange">
|
||||||
|
<view class="scan-field">
|
||||||
|
{{ currentWarehouse?.name || '请选择入库仓库' }}
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<picker :range="suppliers" range-key="name" :value="selectedSupplierIndex" @change="handleSupplierChange">
|
||||||
|
<view class="scan-field">
|
||||||
|
{{ currentSupplierName }}
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<input v-model="remark" class="scan-field" placeholder="请输入备注">
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
扫码录入
|
||||||
|
</view>
|
||||||
|
<ScanInput
|
||||||
|
v-model="scanCode"
|
||||||
|
placeholder="请先选择仓库,再扫描产品条码"
|
||||||
|
:loading="scanLoading"
|
||||||
|
@submit="handleScanSubmit"
|
||||||
|
@camera-scan="handleCameraScan"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
入库明细
|
||||||
|
</view>
|
||||||
|
<ItemList :items="items" @increase="increaseCount" @decrease="decreaseCount" @remove="removeItem" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<SubmitBar :loading="submitting" submit-text="提交其它入库" @submit="handleSubmit" @secondary="clearItems" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ProductByBarCode } from '@/api/erp/product'
|
||||||
|
import type { StockIn, StockInItem } from '@/api/erp/stock-in'
|
||||||
|
import type { SupplierSimple } from '@/api/erp/supplier'
|
||||||
|
import type { Warehouse } from '@/api/erp/warehouse'
|
||||||
|
import type { ItemCardValue } from '@/components/erp-scan/item-list.vue'
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useToast } from 'wot-design-uni'
|
||||||
|
import { getProductByBarCode } from '@/api/erp/product'
|
||||||
|
import { createStockIn } from '@/api/erp/stock-in'
|
||||||
|
import { getSupplierSimpleList } from '@/api/erp/supplier'
|
||||||
|
import { getWarehouseSimpleList } from '@/api/erp/warehouse'
|
||||||
|
import ItemList from '@/components/erp-scan/item-list.vue'
|
||||||
|
import ScanInput from '@/components/erp-scan/scan-input.vue'
|
||||||
|
import SubmitBar from '@/components/erp-scan/submit-bar.vue'
|
||||||
|
import { navigateBackPlus } from '@/utils'
|
||||||
|
import { buildScanMergeKey, normalizeCameraScanResult, normalizeScanCode } from '@/utils/scan'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '',
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface StockInScanItem extends ItemCardValue {
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const scanCode = ref('')
|
||||||
|
const scanLoading = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const remark = ref('')
|
||||||
|
const warehouses = ref<Warehouse[]>([])
|
||||||
|
const suppliers = ref<SupplierSimple[]>([])
|
||||||
|
const selectedWarehouseId = ref<number>()
|
||||||
|
const selectedSupplierId = ref<number>()
|
||||||
|
const items = ref<StockInScanItem[]>([])
|
||||||
|
|
||||||
|
const currentWarehouse = computed(() =>
|
||||||
|
warehouses.value.find(item => item.id === selectedWarehouseId.value),
|
||||||
|
)
|
||||||
|
const currentSupplierName = computed(() => suppliers.value.find(item => item.id === selectedSupplierId.value)?.name || '请选择供应商(可选)')
|
||||||
|
const selectedWarehouseIndex = computed(() => Math.max(warehouses.value.findIndex(item => item.id === selectedWarehouseId.value), 0))
|
||||||
|
const selectedSupplierIndex = computed(() => {
|
||||||
|
const index = suppliers.value.findIndex(item => item.id === selectedSupplierId.value)
|
||||||
|
return index < 0 ? 0 : index
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:返回上一页,若没有上一级页面则回到其它入库列表页。
|
||||||
|
* 适用场景:用户从快捷页返回库存业务列表。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:使用增强返回逻辑,避免页面栈为空时返回失败。
|
||||||
|
*/
|
||||||
|
function handleBack() {
|
||||||
|
navigateBackPlus('/pages-erp/stock-in/index')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:加载扫码其它入库页依赖的基础下拉数据。
|
||||||
|
* 适用场景:页面首次进入时准备仓库和供应商数据。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:默认仓库优先取系统默认仓库,减少仓库人员开始作业前的额外点击。
|
||||||
|
*/
|
||||||
|
async function loadInitialData() {
|
||||||
|
const [warehouseList, supplierList] = await Promise.all([
|
||||||
|
getWarehouseSimpleList(),
|
||||||
|
getSupplierSimpleList(),
|
||||||
|
])
|
||||||
|
|
||||||
|
warehouses.value = warehouseList
|
||||||
|
suppliers.value = supplierList
|
||||||
|
selectedWarehouseId.value = warehouseList.find(item => item.defaultStatus)?.id || warehouseList[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:切换当前入库仓库。
|
||||||
|
* 适用场景:操作员选择本次扫码作业仓库。
|
||||||
|
* @param event picker 事件
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:已录入明细不会被自动改仓,避免把历史扫码明细误迁到新仓库。
|
||||||
|
*/
|
||||||
|
function handleWarehouseChange(event: Record<string, any>) {
|
||||||
|
const warehouse = warehouses.value[Number(event.detail.value)]
|
||||||
|
selectedWarehouseId.value = warehouse?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:切换当前供应商。
|
||||||
|
* 适用场景:其它入库单头维护可选供应商。
|
||||||
|
* @param event picker 事件
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:供应商当前不是强校验项,因此仅在存在时传给后端。
|
||||||
|
*/
|
||||||
|
function handleSupplierChange(event: Record<string, any>) {
|
||||||
|
const supplier = suppliers.value[Number(event.detail.value)]
|
||||||
|
selectedSupplierId.value = supplier?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:把扫码命中的产品合并到当前入库明细。
|
||||||
|
* 适用场景:扫码枪连续扫到同一产品时自动累计数量。
|
||||||
|
* @param product 条码命中的产品
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:重复扫码按“仓库 + 产品”合并,避免同一产品刷出多行影响 PDA 作业效率。
|
||||||
|
*/
|
||||||
|
function appendScannedProduct(product: ProductByBarCode) {
|
||||||
|
const warehouseId = selectedWarehouseId.value as number
|
||||||
|
const targetKey = buildScanMergeKey({ warehouseId, productId: product.id, count: 1 })
|
||||||
|
const existedIndex = items.value.findIndex(item => buildScanMergeKey({
|
||||||
|
warehouseId: item.warehouseId,
|
||||||
|
productId: item.productId,
|
||||||
|
count: item.count,
|
||||||
|
}) === targetKey)
|
||||||
|
|
||||||
|
if (existedIndex >= 0) {
|
||||||
|
items.value[existedIndex].count += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value.unshift({
|
||||||
|
warehouseId,
|
||||||
|
warehouseName: currentWarehouse.value?.name,
|
||||||
|
productId: product.id,
|
||||||
|
productName: product.name,
|
||||||
|
productBarCode: product.barCode,
|
||||||
|
productSpec: product.standard,
|
||||||
|
productUnitName: product.unitName,
|
||||||
|
productPrice: Number(product.minPrice || 0),
|
||||||
|
count: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:处理扫码提交。
|
||||||
|
* 适用场景:扫码枪回车或用户点击确认按钮。
|
||||||
|
* @param sourceCode 可选的外部条码输入值
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:未选择仓库时直接拦截,避免条码产品被录入到错误仓库上下文。
|
||||||
|
*/
|
||||||
|
async function handleScanSubmit(sourceCode?: string) {
|
||||||
|
if (scanLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedWarehouseId.value) {
|
||||||
|
toast.error('请先选择仓库')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const barCode = normalizeScanCode(sourceCode ?? scanCode.value)
|
||||||
|
if (!barCode) {
|
||||||
|
toast.error('请先扫描条码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanLoading.value = true
|
||||||
|
try {
|
||||||
|
const product = await getProductByBarCode(barCode)
|
||||||
|
if (!product?.id) {
|
||||||
|
toast.error('未匹配到启用中的产品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendScannedProduct(product)
|
||||||
|
scanCode.value = ''
|
||||||
|
} finally {
|
||||||
|
scanLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:触发相机扫码,并将结果接入现有扫码录入流程。
|
||||||
|
* 适用场景:普通手机没有外接扫码枪时,通过摄像头录入条码。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:扫码取消应静默返回,不应污染当前输入框内容或产生误报。
|
||||||
|
*/
|
||||||
|
function handleCameraScan() {
|
||||||
|
if (scanLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.scanCode({
|
||||||
|
onlyFromCamera: true,
|
||||||
|
success: async (result) => {
|
||||||
|
const barCode = normalizeCameraScanResult(result)
|
||||||
|
if (!barCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanCode.value = barCode
|
||||||
|
await handleScanSubmit(barCode)
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
// 用户主动取消扫码不属于异常流程,这里静默返回,避免产生干扰提示。
|
||||||
|
if (String(error?.errMsg || '').includes('cancel')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error('相机扫码失败,请重试')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:增加指定明细数量。
|
||||||
|
* 适用场景:扫码后人工补正入库数量。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:其它入库不需要前端库存拦截,只做数量累加。
|
||||||
|
*/
|
||||||
|
function increaseCount(index: number) {
|
||||||
|
items.value[index].count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:减少指定明细数量。
|
||||||
|
* 适用场景:误扫后人工回退数量。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:数量最低保留 1,避免留下 0 数量脏明细。
|
||||||
|
*/
|
||||||
|
function decreaseCount(index: number) {
|
||||||
|
if (items.value[index].count <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value[index].count -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:删除指定明细。
|
||||||
|
* 适用场景:误扫错误产品后移除整行。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:这里只删除当前行,不联动其他已录入产品。
|
||||||
|
*/
|
||||||
|
function removeItem(index: number) {
|
||||||
|
items.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:清空当前录入明细。
|
||||||
|
* 适用场景:整单作废后重新扫码。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:清空后保留当前仓库和供应商上下文,避免重新开始时重复选择。
|
||||||
|
*/
|
||||||
|
function clearItems() {
|
||||||
|
items.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:将页面明细转换成其它入库接口需要的明细结构。
|
||||||
|
* 适用场景:提交前组装请求体。
|
||||||
|
* @return 其它入库明细数组
|
||||||
|
* 注意事项:这里只保留后端真正需要的字段,避免把展示字段一并带入接口契约。
|
||||||
|
*/
|
||||||
|
function buildSubmitItems(): StockInItem[] {
|
||||||
|
return items.value.map(item => ({
|
||||||
|
warehouseId: item.warehouseId,
|
||||||
|
productId: item.productId,
|
||||||
|
productPrice: item.productPrice,
|
||||||
|
count: item.count,
|
||||||
|
remark: item.remark,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:提交扫码其它入库单。
|
||||||
|
* 适用场景:扫码录入完成后生成正式其它入库单。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:提交失败时保留当前录入数据,方便用户修正后重试。
|
||||||
|
*/
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (submitting.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.value.length === 0) {
|
||||||
|
toast.error('请先扫描入库明细')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const payload: StockIn = {
|
||||||
|
supplierId: selectedSupplierId.value,
|
||||||
|
inTime: new Date().toISOString(),
|
||||||
|
remark: remark.value,
|
||||||
|
items: buildSubmitItems(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await createStockIn(payload)
|
||||||
|
toast.success('入库成功')
|
||||||
|
clearItems()
|
||||||
|
remark.value = ''
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:初始化扫码其它入库页。
|
||||||
|
* 适用场景:页面首次挂载时准备基础数据。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:初始化失败时由请求封装统一提示,这里不额外重复 toast。
|
||||||
|
*/
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadInitialData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.scan-page {
|
||||||
|
background: #f4f6f8;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-section {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-field {
|
||||||
|
height: 84rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
line-height: 84rpx;
|
||||||
|
border: 2rpx solid #e5e6eb;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f7f8fa;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
455
src/pages-erp/stock-out/scan/index.vue
Normal file
455
src/pages-erp/stock-out/scan/index.vue
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
<template>
|
||||||
|
<view class="yd-page-container scan-page">
|
||||||
|
<wd-navbar
|
||||||
|
title="扫码其它出库"
|
||||||
|
left-arrow
|
||||||
|
placeholder
|
||||||
|
safe-area-inset-top
|
||||||
|
fixed
|
||||||
|
@click-left="handleBack"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<view class="scan-page__content">
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
单据信息
|
||||||
|
</view>
|
||||||
|
<picker :range="warehouses" range-key="name" :value="selectedWarehouseIndex" @change="handleWarehouseChange">
|
||||||
|
<view class="scan-field">
|
||||||
|
{{ currentWarehouse?.name || '请选择出库仓库' }}
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<picker :range="customers" range-key="name" :value="selectedCustomerIndex" @change="handleCustomerChange">
|
||||||
|
<view class="scan-field">
|
||||||
|
{{ currentCustomerName }}
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<input v-model="remark" class="scan-field" placeholder="请输入备注">
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
扫码录入
|
||||||
|
</view>
|
||||||
|
<ScanInput
|
||||||
|
v-model="scanCode"
|
||||||
|
placeholder="请先选择仓库,再扫描产品条码"
|
||||||
|
:loading="scanLoading"
|
||||||
|
@submit="handleScanSubmit"
|
||||||
|
@camera-scan="handleCameraScan"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="scan-section">
|
||||||
|
<view class="scan-section__title">
|
||||||
|
出库明细
|
||||||
|
</view>
|
||||||
|
<ItemList :items="items" :show-stock="true" @increase="increaseCount" @decrease="decreaseCount" @remove="removeItem" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<SubmitBar :loading="submitting" submit-text="提交其它出库" @submit="handleSubmit" @secondary="clearItems" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CustomerSimple } from '@/api/erp/customer'
|
||||||
|
import type { ProductByBarCode } from '@/api/erp/product'
|
||||||
|
import type { Stock } from '@/api/erp/stock'
|
||||||
|
import type { StockOut, StockOutItem } from '@/api/erp/stock-out'
|
||||||
|
import type { Warehouse } from '@/api/erp/warehouse'
|
||||||
|
import type { ItemCardValue } from '@/components/erp-scan/item-list.vue'
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useToast } from 'wot-design-uni'
|
||||||
|
import { getCustomerSimpleList } from '@/api/erp/customer'
|
||||||
|
import { getProductByBarCode } from '@/api/erp/product'
|
||||||
|
import { getStock2 } from '@/api/erp/stock'
|
||||||
|
import { createStockOut } from '@/api/erp/stock-out'
|
||||||
|
import { getWarehouseSimpleList } from '@/api/erp/warehouse'
|
||||||
|
import ItemList from '@/components/erp-scan/item-list.vue'
|
||||||
|
import ScanInput from '@/components/erp-scan/scan-input.vue'
|
||||||
|
import SubmitBar from '@/components/erp-scan/submit-bar.vue'
|
||||||
|
import { navigateBackPlus } from '@/utils'
|
||||||
|
import { buildScanMergeKey, normalizeCameraScanResult, normalizeScanCode } from '@/utils/scan'
|
||||||
|
|
||||||
|
definePage({
|
||||||
|
style: {
|
||||||
|
navigationBarTitleText: '',
|
||||||
|
navigationStyle: 'custom',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface StockOutScanItem extends ItemCardValue {
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const scanCode = ref('')
|
||||||
|
const scanLoading = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const remark = ref('')
|
||||||
|
const warehouses = ref<Warehouse[]>([])
|
||||||
|
const customers = ref<CustomerSimple[]>([])
|
||||||
|
const selectedWarehouseId = ref<number>()
|
||||||
|
const selectedCustomerId = ref<number>()
|
||||||
|
const items = ref<StockOutScanItem[]>([])
|
||||||
|
|
||||||
|
const currentWarehouse = computed(() =>
|
||||||
|
warehouses.value.find(item => item.id === selectedWarehouseId.value),
|
||||||
|
)
|
||||||
|
const currentCustomerName = computed(() => customers.value.find(item => item.id === selectedCustomerId.value)?.name || '请选择客户(可选)')
|
||||||
|
const selectedWarehouseIndex = computed(() => Math.max(warehouses.value.findIndex(item => item.id === selectedWarehouseId.value), 0))
|
||||||
|
const selectedCustomerIndex = computed(() => {
|
||||||
|
const index = customers.value.findIndex(item => item.id === selectedCustomerId.value)
|
||||||
|
return index < 0 ? 0 : index
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:返回上一页,若没有上一级页面则回到其它出库列表页。
|
||||||
|
* 适用场景:用户从快捷页返回库存业务列表。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:使用增强返回逻辑,避免页面栈为空时返回失败。
|
||||||
|
*/
|
||||||
|
function handleBack() {
|
||||||
|
navigateBackPlus('/pages-erp/stock-out/index')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:加载扫码其它出库页依赖的基础下拉数据。
|
||||||
|
* 适用场景:页面首次进入时准备仓库和客户数据。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:默认仓库优先取系统默认仓库,减少开始作业前的额外点击。
|
||||||
|
*/
|
||||||
|
async function loadInitialData() {
|
||||||
|
const [warehouseList, customerList] = await Promise.all([
|
||||||
|
getWarehouseSimpleList(),
|
||||||
|
getCustomerSimpleList(),
|
||||||
|
])
|
||||||
|
|
||||||
|
warehouses.value = warehouseList
|
||||||
|
customers.value = customerList
|
||||||
|
selectedWarehouseId.value = warehouseList.find(item => item.defaultStatus)?.id || warehouseList[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:切换当前出库仓库。
|
||||||
|
* 适用场景:操作员选择本次扫码作业仓库。
|
||||||
|
* @param event picker 事件
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:已录入明细不会被自动改仓,避免中途切仓导致历史扫码上下文错乱。
|
||||||
|
*/
|
||||||
|
function handleWarehouseChange(event: Record<string, any>) {
|
||||||
|
const warehouse = warehouses.value[Number(event.detail.value)]
|
||||||
|
selectedWarehouseId.value = warehouse?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:切换当前客户。
|
||||||
|
* 适用场景:其它出库单头维护可选客户信息。
|
||||||
|
* @param event picker 事件
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:客户当前不是必填项,但保留该字段便于后续业务追溯。
|
||||||
|
*/
|
||||||
|
function handleCustomerChange(event: Record<string, any>) {
|
||||||
|
const customer = customers.value[Number(event.detail.value)]
|
||||||
|
selectedCustomerId.value = customer?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:按产品和仓库查询即时库存。
|
||||||
|
* 适用场景:扫码新增明细前或手工增加数量前进行库存校验。
|
||||||
|
* @param productId 产品编号
|
||||||
|
* @return 当前仓库下的库存信息
|
||||||
|
* 注意事项:出库页需要每次实时查询,避免沿用旧库存误放行。
|
||||||
|
*/
|
||||||
|
async function loadStockInfo(productId: number): Promise<Stock> {
|
||||||
|
return await getStock2(productId, selectedWarehouseId.value as number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:校验目标数量是否超过当前库存。
|
||||||
|
* 适用场景:扫码新增、重复扫码累计、手工增加数量。
|
||||||
|
* @param stockCount 当前库存数量
|
||||||
|
* @param nextCount 目标数量
|
||||||
|
* @param productName 产品名称
|
||||||
|
* @return 是否允许继续
|
||||||
|
* 注意事项:不在前端提前拦截的话,仓库人员可能连续扫码后才在提交时整体失败,体验会很差。
|
||||||
|
*/
|
||||||
|
function validateStock(stockCount: number, nextCount: number, productName: string): boolean {
|
||||||
|
if (Number(stockCount || 0) < nextCount) {
|
||||||
|
toast.error(`${productName} 库存不足`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:把扫码命中的产品合并到当前出库明细。
|
||||||
|
* 适用场景:扫码枪连续扫到同一产品时自动累计数量。
|
||||||
|
* @param product 条码命中的产品
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:重复扫码前会先刷新库存并验证累计后数量,避免页面沿用旧库存误放行。
|
||||||
|
*/
|
||||||
|
async function appendScannedProduct(product: ProductByBarCode) {
|
||||||
|
const warehouseId = selectedWarehouseId.value as number
|
||||||
|
const targetKey = buildScanMergeKey({ warehouseId, productId: product.id, count: 1 })
|
||||||
|
const existedIndex = items.value.findIndex(item => buildScanMergeKey({
|
||||||
|
warehouseId: item.warehouseId,
|
||||||
|
productId: item.productId,
|
||||||
|
count: item.count,
|
||||||
|
}) === targetKey)
|
||||||
|
const stockInfo = await loadStockInfo(product.id)
|
||||||
|
const stockCount = Number(stockInfo?.count || 0)
|
||||||
|
|
||||||
|
if (existedIndex >= 0) {
|
||||||
|
const nextCount = items.value[existedIndex].count + 1
|
||||||
|
if (!validateStock(stockCount, nextCount, product.name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value[existedIndex].count = nextCount
|
||||||
|
items.value[existedIndex].stockCount = stockCount
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateStock(stockCount, 1, product.name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value.unshift({
|
||||||
|
warehouseId,
|
||||||
|
warehouseName: currentWarehouse.value?.name,
|
||||||
|
productId: product.id,
|
||||||
|
productName: product.name,
|
||||||
|
productBarCode: product.barCode,
|
||||||
|
productSpec: product.standard,
|
||||||
|
productUnitName: product.unitName,
|
||||||
|
productPrice: Number(stockInfo?.unitPrice ?? product.minPrice ?? 0),
|
||||||
|
stockCount,
|
||||||
|
count: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:处理扫码提交。
|
||||||
|
* 适用场景:扫码枪回车或用户点击确认按钮。
|
||||||
|
* @param sourceCode 可选的外部条码输入值
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:未选择仓库时直接拦截,避免库存查询与出库校验失去仓库上下文。
|
||||||
|
*/
|
||||||
|
async function handleScanSubmit(sourceCode?: string) {
|
||||||
|
if (scanLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedWarehouseId.value) {
|
||||||
|
toast.error('请先选择仓库')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const barCode = normalizeScanCode(sourceCode ?? scanCode.value)
|
||||||
|
if (!barCode) {
|
||||||
|
toast.error('请先扫描条码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanLoading.value = true
|
||||||
|
try {
|
||||||
|
const product = await getProductByBarCode(barCode)
|
||||||
|
if (!product?.id) {
|
||||||
|
toast.error('未匹配到启用中的产品')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await appendScannedProduct(product)
|
||||||
|
scanCode.value = ''
|
||||||
|
} finally {
|
||||||
|
scanLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:触发相机扫码,并将结果接入现有扫码录入流程。
|
||||||
|
* 适用场景:普通手机没有外接扫码枪时,通过摄像头录入条码。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:扫码取消应静默返回,避免把用户主动取消误判成系统错误。
|
||||||
|
*/
|
||||||
|
function handleCameraScan() {
|
||||||
|
if (scanLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.scanCode({
|
||||||
|
onlyFromCamera: true,
|
||||||
|
success: async (result) => {
|
||||||
|
const barCode = normalizeCameraScanResult(result)
|
||||||
|
if (!barCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanCode.value = barCode
|
||||||
|
await handleScanSubmit(barCode)
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
// 用户主动取消扫码不属于异常流程,这里静默返回,避免产生干扰提示。
|
||||||
|
if (String(error?.errMsg || '').includes('cancel')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error('相机扫码失败,请重试')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:增加指定明细数量。
|
||||||
|
* 适用场景:扫码后人工补正出库数量。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:每次加数量前都重新校验库存,避免页面显示的库存已被其他单据消耗后继续放行。
|
||||||
|
*/
|
||||||
|
async function increaseCount(index: number) {
|
||||||
|
const item = items.value[index]
|
||||||
|
const stockInfo = await loadStockInfo(item.productId)
|
||||||
|
const nextCount = item.count + 1
|
||||||
|
if (!validateStock(Number(stockInfo?.count || 0), nextCount, item.productName)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.stockCount = Number(stockInfo?.count || 0)
|
||||||
|
item.count = nextCount
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:减少指定明细数量。
|
||||||
|
* 适用场景:误扫后人工回退数量。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:数量最低保留 1,避免出现 0 数量明细继续提交。
|
||||||
|
*/
|
||||||
|
function decreaseCount(index: number) {
|
||||||
|
if (items.value[index].count <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.value[index].count -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:删除指定明细。
|
||||||
|
* 适用场景:误扫错误产品后移除整行。
|
||||||
|
* @param index 明细下标
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:这里只删除当前行,不联动其他产品数量和库存展示。
|
||||||
|
*/
|
||||||
|
function removeItem(index: number) {
|
||||||
|
items.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:清空当前录入明细。
|
||||||
|
* 适用场景:整单作废后重新扫码。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:清空后保留当前仓库和客户上下文,便于同一作业场景继续录单。
|
||||||
|
*/
|
||||||
|
function clearItems() {
|
||||||
|
items.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:将页面明细转换成其它出库接口需要的明细结构。
|
||||||
|
* 适用场景:提交前组装请求体。
|
||||||
|
* @return 其它出库明细数组
|
||||||
|
* 注意事项:这里只保留后端真正需要的字段,避免把展示字段一并带入接口契约。
|
||||||
|
*/
|
||||||
|
function buildSubmitItems(): StockOutItem[] {
|
||||||
|
return items.value.map(item => ({
|
||||||
|
warehouseId: item.warehouseId,
|
||||||
|
productId: item.productId,
|
||||||
|
productPrice: item.productPrice,
|
||||||
|
count: item.count,
|
||||||
|
remark: item.remark,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:提交扫码其它出库单。
|
||||||
|
* 适用场景:扫码录入完成后生成正式其它出库单。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:前端库存校验只做即时提醒,最终正确性以后端校验结果为准。
|
||||||
|
*/
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (submitting.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.value.length === 0) {
|
||||||
|
toast.error('请先扫描出库明细')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const payload: StockOut = {
|
||||||
|
customerId: selectedCustomerId.value,
|
||||||
|
outTime: new Date().toISOString(),
|
||||||
|
remark: remark.value,
|
||||||
|
items: buildSubmitItems(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await createStockOut(payload)
|
||||||
|
toast.success('出库成功')
|
||||||
|
clearItems()
|
||||||
|
remark.value = ''
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:初始化扫码其它出库页。
|
||||||
|
* 适用场景:页面首次挂载时准备基础数据。
|
||||||
|
* @return 无
|
||||||
|
* 注意事项:初始化失败时由请求封装统一提示,这里不额外重复 toast。
|
||||||
|
*/
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadInitialData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.scan-page {
|
||||||
|
background: #f4f6f8;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-section {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-field {
|
||||||
|
height: 84rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
line-height: 84rpx;
|
||||||
|
border: 2rpx solid #e5e6eb;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f7f8fa;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -146,6 +146,14 @@ const menuGroupsData: MenuGroup[] = [
|
|||||||
iconColor: '#13c2c2',
|
iconColor: '#13c2c2',
|
||||||
permission: 'erp:stock-in:query',
|
permission: 'erp:stock-in:query',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'stockInScan',
|
||||||
|
name: '扫码其它入库',
|
||||||
|
icon: 'scan',
|
||||||
|
url: '/pages-erp/stock-in/scan/index',
|
||||||
|
iconColor: '#36cfc9',
|
||||||
|
permission: 'erp:stock-in:query',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'stockOut',
|
key: 'stockOut',
|
||||||
name: '其他出库',
|
name: '其他出库',
|
||||||
@@ -154,6 +162,14 @@ const menuGroupsData: MenuGroup[] = [
|
|||||||
iconColor: '#fa8c16',
|
iconColor: '#fa8c16',
|
||||||
permission: 'erp:stock-out:query',
|
permission: 'erp:stock-out:query',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'stockOutScan',
|
||||||
|
name: '扫码其它出库',
|
||||||
|
icon: 'scan',
|
||||||
|
url: '/pages-erp/stock-out/scan/index',
|
||||||
|
iconColor: '#ff7a45',
|
||||||
|
permission: 'erp:stock-out:query',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'stockGain',
|
key: 'stockGain',
|
||||||
name: '库存报溢',
|
name: '库存报溢',
|
||||||
|
|||||||
81
src/utils/__tests__/scan.spec.ts
Normal file
81
src/utils/__tests__/scan.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import type { ScanItem } from '../scan'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildScanMergeKey,
|
||||||
|
mergeScannedItem,
|
||||||
|
normalizeCameraScanResult,
|
||||||
|
normalizeScanCode,
|
||||||
|
} from '../scan'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:构造测试明细数据,避免每个用例重复拼装字段。
|
||||||
|
* 适用场景:扫码合并工具函数单元测试。
|
||||||
|
* @param overrides 需要覆盖的字段
|
||||||
|
* @return 测试用明细对象
|
||||||
|
* 注意事项:测试数据默认模拟“自由扫码”场景,订单约束字段按需覆盖。
|
||||||
|
*/
|
||||||
|
function createItem(overrides: Partial<ScanItem> = {}): ScanItem {
|
||||||
|
return {
|
||||||
|
warehouseId: 1,
|
||||||
|
productId: 1001,
|
||||||
|
count: 1,
|
||||||
|
orderItemId: undefined,
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('normalizeScanCode', () => {
|
||||||
|
it('应去掉回车换行和前后空格', () => {
|
||||||
|
expect(normalizeScanCode(' TEST-001\r\n')).toBe('TEST-001')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('空值应返回空字符串', () => {
|
||||||
|
expect(normalizeScanCode('')).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('normalizeCameraScanResult', () => {
|
||||||
|
it('应从手机扫码结果对象中提取并清洗条码', () => {
|
||||||
|
expect(normalizeCameraScanResult({ result: ' MOBILE-001\r\n' })).toBe('MOBILE-001')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('扫码取消或结果为空时应返回空字符串', () => {
|
||||||
|
expect(normalizeCameraScanResult({ result: '' })).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('buildScanMergeKey', () => {
|
||||||
|
it('自由扫码模式应按仓库和产品生成合并键', () => {
|
||||||
|
expect(buildScanMergeKey(createItem())).toBe('1:1001')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('订单约束模式应额外带上订单明细编号', () => {
|
||||||
|
expect(buildScanMergeKey(createItem({ orderItemId: 5001 }), true)).toBe('1:1001:5001')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mergeScannedItem', () => {
|
||||||
|
it('同仓库同产品重复扫码应累计数量', () => {
|
||||||
|
const merged = mergeScannedItem([createItem({ count: 2 })], createItem(), false)
|
||||||
|
|
||||||
|
expect(merged).toHaveLength(1)
|
||||||
|
expect(merged[0].count).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('跨仓库扫码不应错误合并', () => {
|
||||||
|
const merged = mergeScannedItem([createItem({ warehouseId: 2 })], createItem(), false)
|
||||||
|
|
||||||
|
expect(merged).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('订单约束模式下不同订单行不应合并', () => {
|
||||||
|
const merged = mergeScannedItem(
|
||||||
|
[createItem({ orderItemId: 9001 })],
|
||||||
|
createItem({ orderItemId: 9002 }),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(merged).toHaveLength(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
85
src/utils/scan.ts
Normal file
85
src/utils/scan.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* 功能说明:定义扫码明细的最小结构,统一扫码页里的重复合并计算输入。
|
||||||
|
* 适用场景:扫码其它入库、扫码其它出库的条码累计逻辑。
|
||||||
|
*/
|
||||||
|
export interface ScanItem {
|
||||||
|
warehouseId: number
|
||||||
|
productId: number
|
||||||
|
count: number
|
||||||
|
orderItemId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:清洗扫码枪输入的条码文本,去掉回车、换行和前后空白。
|
||||||
|
* 适用场景:扫码枪通过键盘模拟输入后,页面提交前统一清洗条码值。
|
||||||
|
* @param raw 原始扫码内容
|
||||||
|
* @return 清洗后的条码字符串
|
||||||
|
* 注意事项:扫码枪通常会追加回车,不先清洗会导致条码查产品接口无法命中。
|
||||||
|
*/
|
||||||
|
export function normalizeScanCode(raw: string): string {
|
||||||
|
return (raw || '').replace(/[\r\n]/g, '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:把相机扫码结果统一转换成业务可用条码。
|
||||||
|
* 适用场景:`uni.scanCode` 返回对象后,与扫码枪输入共用同一套处理流程。
|
||||||
|
* @param result uni-app 扫码返回结果
|
||||||
|
* @return 清洗后的条码字符串
|
||||||
|
* 注意事项:扫码取消、空返回或结构变化时,需要安全降级为空字符串,避免页面直接抛错。
|
||||||
|
*/
|
||||||
|
export function normalizeCameraScanResult(result?: { result?: string } | null): string {
|
||||||
|
return normalizeScanCode(result?.result || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:生成扫码明细的合并键,决定重复扫码时应该累加到哪一行。
|
||||||
|
* 适用场景:自由扫码模式按“仓库 + 产品”合并,订单约束模式再附加订单行编号。
|
||||||
|
* @param item 当前明细
|
||||||
|
* @param withOrderConstraint 是否启用订单约束模式
|
||||||
|
* @return 合并键字符串
|
||||||
|
* 注意事项:订单约束场景若不带订单行编号,会把不同订单行的同一产品错误合并。
|
||||||
|
*/
|
||||||
|
export function buildScanMergeKey(item: ScanItem, withOrderConstraint = false): string {
|
||||||
|
const segments = [item.warehouseId, item.productId]
|
||||||
|
|
||||||
|
// 订单约束扫码时需要把订单明细编号带入合并键,避免不同订单行误合并。
|
||||||
|
if (withOrderConstraint) {
|
||||||
|
segments.push(item.orderItemId || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments.join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:将一次新的扫码结果合并到现有明细列表中。
|
||||||
|
* 适用场景:连续扫到同一产品时自动累计数量,减少仓库人员手工修改次数。
|
||||||
|
* @param items 当前明细列表
|
||||||
|
* @param scannedItem 新扫码得到的明细
|
||||||
|
* @param withOrderConstraint 是否启用订单约束模式
|
||||||
|
* @return 合并后的新明细列表
|
||||||
|
* 注意事项:这里返回新数组而不是原地修改,避免页面状态更新不稳定。
|
||||||
|
*/
|
||||||
|
export function mergeScannedItem(
|
||||||
|
items: ScanItem[],
|
||||||
|
scannedItem: ScanItem,
|
||||||
|
withOrderConstraint = false,
|
||||||
|
): ScanItem[] {
|
||||||
|
const targetKey = buildScanMergeKey(scannedItem, withOrderConstraint)
|
||||||
|
const existedIndex = items.findIndex(item => buildScanMergeKey(item, withOrderConstraint) === targetKey)
|
||||||
|
|
||||||
|
if (existedIndex === -1) {
|
||||||
|
return [...items, scannedItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.map((item, index) => {
|
||||||
|
if (index !== existedIndex) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// 命中同一合并键时只累计数量,避免把已录入明细拆成多行,影响 PDA 连续扫码效率。
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
count: Number(item.count || 0) + Number(scannedItem.count || 0),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
20
vitest.config.ts
Normal file
20
vitest.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能说明:为纯工具函数测试提供独立的 Vitest 配置,避免 uni-app 构建插件影响测试执行。
|
||||||
|
* 适用场景:运行扫码工具、数据清洗、明细合并等不依赖页面渲染的单元测试。
|
||||||
|
* @return Vitest 配置对象
|
||||||
|
* 注意事项:当前仅解析 `@/` 别名并执行 `src/utils/__tests__` 下的测试,避免把页面编译链路引入测试环境。
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'node',
|
||||||
|
include: ['src/utils/__tests__/**/*.spec.ts'],
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user