feat: 重做出入库逻辑

This commit is contained in:
louis 2024-03-22 16:47:44 +08:00
parent 210e0d7dd5
commit 8ffd117dcf
29 changed files with 562223 additions and 64 deletions

View File

@ -41,20 +41,27 @@
"@ant-design/icons-vue": "~7.0.1", "@ant-design/icons-vue": "~7.0.1",
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@tinymce/tinymce-vue": "^5.1.1", "@tinymce/tinymce-vue": "^5.1.1",
"@types/dat.gui": "^0.7.12",
"@types/three": "^0.162.0",
"@vueuse/core": "~10.8.0", "@vueuse/core": "~10.8.0",
"ant-design-vue": "~4.1.2", "ant-design-vue": "~4.1.2",
"axios": "~1.6.7", "axios": "~1.6.7",
"dat.gui": "^0.7.9",
"dayjs": "~1.11.10", "dayjs": "~1.11.10",
"dhtmlx-gantt": "^8.0.6",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"file-saver": "~2.0.5", "file-saver": "~2.0.5",
"gsap": "^3.12.5",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"lodash-es": "~4.17.21", "lodash-es": "~4.17.21",
"mathjs": "^12.4.1",
"mitt": "~3.0.1", "mitt": "~3.0.1",
"nprogress": "~1.0.0-1", "nprogress": "~1.0.0-1",
"pinia": "~2.1.7", "pinia": "~2.1.7",
"qiniu-js": "^3.4.2", "qiniu-js": "^3.4.2",
"qs": "~6.11.2", "qs": "~6.11.2",
"sortablejs": "~1.15.2", "sortablejs": "~1.15.2",
"three": "^0.162.0",
"tinymce": "^6.8.3", "tinymce": "^6.8.3",
"vue": "~3.4.19", "vue": "~3.4.19",
"vue-echarts": "^6.6.9", "vue-echarts": "^6.6.9",

View File

@ -14,6 +14,12 @@ dependencies:
'@tinymce/tinymce-vue': '@tinymce/tinymce-vue':
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.1(vue@3.4.19) version: 5.1.1(vue@3.4.19)
'@types/dat.gui':
specifier: ^0.7.12
version: 0.7.12
'@types/three':
specifier: ^0.162.0
version: 0.162.0
'@vueuse/core': '@vueuse/core':
specifier: ~10.8.0 specifier: ~10.8.0
version: 10.8.0(vue@3.4.19) version: 10.8.0(vue@3.4.19)
@ -23,21 +29,33 @@ dependencies:
axios: axios:
specifier: ~1.6.7 specifier: ~1.6.7
version: 1.6.7(debug@4.3.4) version: 1.6.7(debug@4.3.4)
dat.gui:
specifier: ^0.7.9
version: 0.7.9
dayjs: dayjs:
specifier: ~1.11.10 specifier: ~1.11.10
version: 1.11.10 version: 1.11.10
dhtmlx-gantt:
specifier: ^8.0.6
version: 8.0.6
echarts: echarts:
specifier: ^5.5.0 specifier: ^5.5.0
version: 5.5.0 version: 5.5.0
file-saver: file-saver:
specifier: ~2.0.5 specifier: ~2.0.5
version: 2.0.5 version: 2.0.5
gsap:
specifier: ^3.12.5
version: 3.12.5
js-file-download: js-file-download:
specifier: ^0.4.12 specifier: ^0.4.12
version: 0.4.12 version: 0.4.12
lodash-es: lodash-es:
specifier: ~4.17.21 specifier: ~4.17.21
version: 4.17.21 version: 4.17.21
mathjs:
specifier: ^12.4.1
version: 12.4.1
mitt: mitt:
specifier: ~3.0.1 specifier: ~3.0.1
version: 3.0.1 version: 3.0.1
@ -56,6 +74,9 @@ dependencies:
sortablejs: sortablejs:
specifier: ~1.15.2 specifier: ~1.15.2
version: 1.15.2 version: 1.15.2
three:
specifier: ^0.162.0
version: 0.162.0
tinymce: tinymce:
specifier: ^6.8.3 specifier: ^6.8.3
version: 6.8.3 version: 6.8.3
@ -1753,6 +1774,13 @@ packages:
dependencies: dependencies:
regenerator-runtime: 0.14.1 regenerator-runtime: 0.14.1
/@babel/runtime@7.24.1:
resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.1
dev: false
/@babel/template@7.22.15: /@babel/template@7.22.15:
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -2903,10 +2931,18 @@ packages:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true dev: true
/@tweenjs/tween.js@23.1.1:
resolution: {integrity: sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==}
dev: false
/@types/cookie@0.6.0: /@types/cookie@0.6.0:
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
dev: true dev: true
/@types/dat.gui@0.7.12:
resolution: {integrity: sha512-el5dYeQZu2r6YW6Ft4rGtjr/dLe/uzXESMoie5UM6/weVShB1V8IRpXtTKrczd4qe7044fTKZS2l8d6EBFOkoA==}
dev: false
/@types/eslint@7.29.0: /@types/eslint@7.29.0:
resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==}
dependencies: dependencies:
@ -2981,6 +3017,10 @@ packages:
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
dev: true dev: true
/@types/stats.js@0.17.3:
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
dev: false
/@types/statuses@2.0.4: /@types/statuses@2.0.4:
resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==}
dev: true dev: true
@ -2991,6 +3031,16 @@ packages:
'@types/node': 20.11.16 '@types/node': 20.11.16
dev: true dev: true
/@types/three@0.162.0:
resolution: {integrity: sha512-0j5yZcVukVIhrhSIC7+LmBPkkMoMuEJ1AfYBZfgNytdYqYREMuiyXWhYOMeZLBElTEAlJIZn7r2W3vqTIgjWlg==}
dependencies:
'@tweenjs/tween.js': 23.1.1
'@types/stats.js': 0.17.3
'@types/webxr': 0.5.14
fflate: 0.6.10
meshoptimizer: 0.18.1
dev: false
/@types/unist@2.0.10: /@types/unist@2.0.10:
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
dev: true dev: true
@ -2999,6 +3049,10 @@ packages:
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
dev: false dev: false
/@types/webxr@0.5.14:
resolution: {integrity: sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==}
dev: false
/@types/wrap-ansi@3.0.0: /@types/wrap-ansi@3.0.0:
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
dev: true dev: true
@ -4698,6 +4752,10 @@ packages:
dot-prop: 5.3.0 dot-prop: 5.3.0
dev: true dev: true
/complex.js@2.1.1:
resolution: {integrity: sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==}
dev: false
/component-emitter@1.3.1: /component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
dev: true dev: true
@ -5263,6 +5321,10 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/dat.gui@0.7.9:
resolution: {integrity: sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==}
dev: false
/dateformat@3.0.3: /dateformat@3.0.3:
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
dev: true dev: true
@ -5320,6 +5382,10 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: false
/decode-uri-component@0.2.2: /decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@ -5415,6 +5481,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/dhtmlx-gantt@8.0.6:
resolution: {integrity: sha512-GrEQ40/vgV1wDWkv/IvjJEM27Z4lDv76XvE5nlvMtFQTqUuo5VnL1XNDv/uFSJVMRnaN9StYaPxP1ebGamDLFg==}
dev: false
/diff@4.0.2: /diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
@ -5790,6 +5860,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/escape-latex@1.2.0:
resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
dev: false
/escape-string-regexp@1.0.5: /escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -6459,6 +6533,10 @@ packages:
reusify: 1.0.4 reusify: 1.0.4
dev: true dev: true
/fflate@0.6.10:
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
dev: false
/figures@2.0.0: /figures@2.0.0:
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -6639,6 +6717,10 @@ packages:
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
dev: false dev: false
/fraction.js@4.3.4:
resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==}
dev: false
/fragment-cache@0.2.1: /fragment-cache@0.2.1:
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -6989,6 +7071,10 @@ packages:
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
dev: true dev: true
/gsap@3.12.5:
resolution: {integrity: sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==}
dev: false
/gzip-size@6.0.0: /gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7721,6 +7807,10 @@ packages:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
dev: true dev: true
/javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
dev: false
/jiti@1.21.0: /jiti@1.21.0:
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
hasBin: true hasBin: true
@ -8319,6 +8409,22 @@ packages:
object-visit: 1.0.1 object-visit: 1.0.1
dev: true dev: true
/mathjs@12.4.1:
resolution: {integrity: sha512-welnW3khgwYjPYvECFHO+xkCxAx9IKIIPDDWPi8B5rKAvmgoEHnQX9slEmHKZTNaJiE+OS4qrJJcB4sfDn/4sw==}
engines: {node: '>= 18'}
hasBin: true
dependencies:
'@babel/runtime': 7.24.1
complex.js: 2.1.1
decimal.js: 10.4.3
escape-latex: 1.2.0
fraction.js: 4.3.4
javascript-natural-sort: 0.7.1
seedrandom: 3.0.5
tiny-emitter: 2.1.0
typed-function: 4.1.1
dev: false
/mathml-tag-names@2.1.3: /mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
dev: true dev: true
@ -8445,6 +8551,10 @@ packages:
resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==}
dev: true dev: true
/meshoptimizer@0.18.1:
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
dev: false
/micromark@2.11.4: /micromark@2.11.4:
resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
dependencies: dependencies:
@ -10192,6 +10302,10 @@ packages:
compute-scroll-into-view: 1.0.20 compute-scroll-into-view: 1.0.20
dev: false dev: false
/seedrandom@3.0.5:
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
dev: false
/semver@5.7.2: /semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true hasBin: true
@ -11132,6 +11246,10 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true dev: true
/three@0.162.0:
resolution: {integrity: sha512-xfCYj4RnlozReCmUd+XQzj6/5OjDNHBy5nT6rVwrOKGENAvpXe2z1jL+DZYaMu4/9pNsjH/4Os/VvS9IrH7IOQ==}
dev: false
/throttle-debounce@5.0.0: /throttle-debounce@5.0.0:
resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==} resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==}
engines: {node: '>=12.22'} engines: {node: '>=12.22'}
@ -11161,6 +11279,10 @@ packages:
next-tick: 1.1.0 next-tick: 1.1.0
dev: true dev: true
/tiny-emitter@2.1.0:
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
dev: false
/tiny-invariant@1.3.3: /tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
dev: true dev: true
@ -11398,6 +11520,11 @@ packages:
is-typed-array: 1.1.12 is-typed-array: 1.1.12
dev: true dev: true
/typed-function@4.1.1:
resolution: {integrity: sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==}
engines: {node: '>= 14'}
dev: false
/typedarray-to-buffer@3.1.5: /typedarray-to-buffer@3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
dependencies: dependencies:

BIN
public/model/1.STL Normal file

Binary file not shown.

207827
public/model/1.gltf Normal file

File diff suppressed because one or more lines are too long

116323
public/model/2.gltf Normal file

File diff suppressed because one or more lines are too long

107300
public/model/3.gltf Normal file

File diff suppressed because one or more lines are too long

128775
public/model/4.gltf Normal file

File diff suppressed because one or more lines are too long

View File

@ -63,7 +63,7 @@ export async function materialsInventoryInfo(
options?: RequestOptions, options?: RequestOptions,
) { ) {
const { id: param0, ...queryParams } = params; const { id: param0, ...queryParams } = params;
return request<API.MaterialsInventoryEntity>(`/api/contract/${param0}`, { return request<API.MaterialsInventoryEntity>(`/api/materials-inventory/${param0}`, {
method: 'GET', method: 'GET',
params: { ...queryParams }, params: { ...queryParams },
...(options || {}), ...(options || {}),

View File

@ -1381,14 +1381,16 @@ declare namespace API {
type MaterialsInventoryListParams = { type MaterialsInventoryListParams = {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
keyword?: string;
field?: string; field?: string;
isCreateInout?: boolean;
order?: 'ASC' | 'DESC'; order?: 'ASC' | 'DESC';
_t?: number; _t?: number;
}; };
type MaterialsInventoryEntity = { type MaterialsInventoryEntity = {
/** 项目 */ /** 项目 */
project?: ProjectEntity; project: ProjectEntity;
/** 产品 */ /** 产品 */
product: ProductEntity; product: ProductEntity;
/** 单价 */ /** 单价 */
@ -1580,6 +1582,10 @@ declare namespace API {
}; };
// Product // Product
type ProductEntity = { type ProductEntity = {
/** 产品编号 */
productNumber: string;
/** 产品规格 */
productSpecification: string;
/** 产品名称 */ /** 产品名称 */
name: string; name: string;
/** 所属公司 */ /** 所属公司 */
@ -1597,6 +1603,8 @@ declare namespace API {
updatedAt: string; updatedAt: string;
}; };
type ProductDto = { type ProductDto = {
/** 产品规格 */
productSpecification: string;
/** 产品名称 */ /** 产品名称 */
name: string; name: string;
/** 所属公司 */ /** 所属公司 */
@ -1613,6 +1621,7 @@ declare namespace API {
type ProductParams = { type ProductParams = {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
keyword?:string;
company?: string; company?: string;
name?: string; name?: string;
field?: string; field?: string;
@ -1625,6 +1634,8 @@ declare namespace API {
type ProductUpdateDto = { type ProductUpdateDto = {
/** 产品名称 */ /** 产品名称 */
name?: string; name?: string;
/** 产品规格 */
productSpecification: string;
/** 所属公司 */ /** 所属公司 */
companyId?: number; companyId?: number;
/** 单位 */ /** 单位 */
@ -1774,6 +1785,8 @@ declare namespace API {
}; };
type MaterialsInOutEntity = { type MaterialsInOutEntity = {
/** 入库后的库存Id */
inventoryId: number;
/** 库存编号 */ /** 库存编号 */
inventoryNumber: string; inventoryNumber: string;
/** 公司信息 */ /** 公司信息 */

View File

@ -1,5 +1,7 @@
export const LOGIN_NAME = 'Login'; export const LOGIN_NAME = 'Login';
export const THREE = 'Three';
export const REDIRECT_NAME = 'Redirect'; export const REDIRECT_NAME = 'Redirect';
export const PARENT_LAYOUT_NAME = 'ParentLayout'; export const PARENT_LAYOUT_NAME = 'ParentLayout';

View File

@ -1,5 +1,5 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { LOGIN_NAME } from '@/router/constant'; import { LOGIN_NAME, THREE } from '@/router/constant';
/** /**
* layout布局之外的路由 * layout布局之外的路由
@ -12,5 +12,12 @@ export const LoginRoute: RouteRecordRaw = {
title: '登录', title: '登录',
}, },
}; };
export const ThreeRoute: RouteRecordRaw = {
export default [LoginRoute]; path: '/three',
name: THREE,
component: () => import('@/views/three/index.vue'),
meta: {
title: 'web3d',
},
};
export default [LoginRoute,ThreeRoute];

View File

@ -5,6 +5,7 @@
@import './transition.less'; @import './transition.less';
@import './common.less'; @import './common.less';
@import './antdv.override.less'; @import './antdv.override.less';
@import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
#global-loading { #global-loading {
display: flex; display: flex;

View File

@ -1,6 +1,6 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { DataNode } from 'ant-design-vue/es/vc-tree-select/interface'; import type { DataNode } from 'ant-design-vue/es/vc-tree-select/interface';
import { add, subtract, multiply, divide, bignumber, type BigNumber, format } from 'mathjs';
/** /**
* @description abc => Abc * @description abc => Abc
* @param str * @param str
@ -205,3 +205,31 @@ export const str2tree = (str: string, treeData: DataNode[] = [], separator = ':'
} }
}, treeData); }, treeData);
}; };
type CalclateOption = 'add' | 'subtract' | 'multiply' | 'divide';
export function calcNumber(
firstNumber: number,
secondNumber: number,
option: CalclateOption,
precision: number = 14,
): number {
let result: BigNumber;
switch (option) {
case 'add':
// 添加精度保留2位小数 add(bignumber(firstNumber), bignumber(secondNumber)).toNumber();
result = add(bignumber(firstNumber), bignumber(secondNumber));
break;
case 'subtract':
result = subtract(bignumber(firstNumber), bignumber(secondNumber));
break;
case 'multiply':
result = multiply(bignumber(firstNumber), bignumber(secondNumber)) as BigNumber;
break;
case 'divide':
result = divide(bignumber(firstNumber), bignumber(secondNumber)) as BigNumber;
break;
default:
return 0;
}
return parseFloat(result.toFixed(precision));
}

View File

@ -2,7 +2,7 @@
<div class="login-box"> <div class="login-box">
<div class="login-logo"> <div class="login-logo">
<!-- <svg-icon name="logo" :size="45" /> --> <!-- <svg-icon name="logo" :size="45" /> -->
<img src="~@/assets/images/logowithtext.png" width="100%" height="70px" /> <img :src="logo" width="100%" height="70px" />
<!-- <h1 class="mb-0 ml-2 text-3xl font-bold">Antd Admin</h1> --> <!-- <h1 class="mb-0 ml-2 text-3xl font-bold">Antd Admin</h1> -->
</div> </div>
<a-form layout="horizontal" :model="state.formInline" @submit.prevent="handleSubmit"> <a-form layout="horizontal" :model="state.formInline" @submit.prevent="handleSubmit">
@ -49,14 +49,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons-vue'; import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons-vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { message, Modal } from 'ant-design-vue'; import { message, Modal } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user'; import { useUserStore } from '@/store/modules/user';
import Api from '@/api/'; import Api from '@/api/';
import { to } from '@/utils/awaitTo'; import { to } from '@/utils/awaitTo';
import logo1 from '@/assets/images/logowithtext.png';
const logo = ref(logo1);
const state = reactive({ const state = reactive({
loading: false, loading: false,
captcha: '', captcha: '',

View File

@ -10,7 +10,7 @@ export type TableQueryItem = API.MaterialsInOutListParams;
export type TableColumnItem = TableColumn<TableListItem>; export type TableColumnItem = TableColumn<TableListItem>;
export const baseColumns: TableColumnItem[] = [ export const baseColumns: TableColumnItem[] = [
{ {
title: '原材料库存编号', title: '出入库单号',
width: 100, width: 100,
fixed: 'left', fixed: 'left',
dataIndex: 'inventoryNumber', dataIndex: 'inventoryNumber',
@ -29,7 +29,7 @@ export const baseColumns: TableColumnItem[] = [
return record?.project?.name || ''; return record?.project?.name || '';
}, },
formItemProps: { formItemProps: {
component:'Select', component: 'Select',
componentProps: ({ formInstance, schema, formModel }) => ({ componentProps: ({ formInstance, schema, formModel }) => ({
showSearch: true, showSearch: true,
filterOption: false, filterOption: false,

View File

@ -3,6 +3,9 @@ import type { FormSchema } from '@/components/core/schema-form/';
import Api from '@/api'; import Api from '@/api';
import { debounce, isEmpty } from 'lodash-es'; import { debounce, isEmpty } from 'lodash-es';
import { MaterialsInOutEnum } from '@/enums/materialsInventoryEnum'; import { MaterialsInOutEnum } from '@/enums/materialsInventoryEnum';
import { toRaw } from 'vue';
import { calcNumber } from '@/utils/common';
import { formatToDate } from '@/utils/dateUtil';
export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEntity>[] => [ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEntity>[] => [
{ {
field: 'inOrOut', field: 'inOrOut',
@ -23,14 +26,16 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
}, },
}, },
{ {
field: 'inventoryNumber', field: 'inventoryId',
component: 'Select', component: 'Select',
label: '库存编号', label: '产品', //出口用
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.Out, rules: [{ required: true, type: 'number' }],
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.Out && !isEdit,
colProps: { colProps: {
span: 24, span: 24,
}, },
helpMessage: '出库必须选择入库时的编号', helpMessage:
'出库必须选择某个价格的某个产品。因为库中的相同产品的价格,可能会不同(不同批次的产品)。另外在库存管理中会显示不同价格的相同产品。',
componentProps: ({ formInstance, schema, formModel }) => ({ componentProps: ({ formInstance, schema, formModel }) => ({
showSearch: true, showSearch: true,
filterOption: false, filterOption: false,
@ -49,7 +54,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
options: [] as LabelValueOptions, options: [] as LabelValueOptions,
}, },
}; };
const options = await getInventoryNumberOptions().finally(() => (schema.loading = false)); const options = await getInventories().finally(() => (schema.loading = false));
newSchema.componentProps.options = options; newSchema.componentProps.options = options;
formInstance?.updateSchema([newSchema]); formInstance?.updateSchema([newSchema]);
}, },
@ -60,20 +65,27 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
}, },
callback: async ({ formModel }) => { callback: async ({ formModel }) => {
if (formModel.inOrOut === MaterialsInOutEnum.Out) { if (formModel.inOrOut === MaterialsInOutEnum.Out) {
return getInventoryNumberOptions(); return getInventories();
} else { } else {
return Promise.resolve([] as LabelValueOptions); return Promise.resolve([] as LabelValueOptions);
} }
}, },
}, },
onSelect: async (inventoryNumber: string) => { onSelect: async (id: number) => {
const { items = [] } = await Api.materialsInOut.materialsInOutList({ const data = await Api.materialsInventory.materialsInventoryInfo({
inventoryNumber, id,
isCreateOut: true,
}); });
if (!isEmpty(items)) { if (!isEmpty(data)) {
const { inOrOut, id, ...ext } = items[0] as API.MaterialsInOutEntity; const { project, unitPrice, quantity, product } = data as API.MaterialsInventoryEntity;
formInstance?.setFieldsValue(ext); const amount = calcNumber(unitPrice, quantity, 'multiply');
formInstance?.setFieldsValue({
projectId: project.id,
productId: product.id,
unitPrice,
quantity,
amount,
});
console.log(data, formModel);
} }
}, },
onSearch: debounce(async (keyword) => { onSearch: debounce(async (keyword) => {
@ -85,9 +97,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
}, },
}; };
formInstance?.updateSchema([newSchema]); formInstance?.updateSchema([newSchema]);
const options = await getInventoryNumberOptions(keyword).finally( const options = await getInventories(keyword).finally(() => (schema.loading = false));
() => (schema.loading = false),
);
newSchema.componentProps.options = options; newSchema.componentProps.options = options;
formInstance?.updateSchema([newSchema]); formInstance?.updateSchema([newSchema]);
}, 500), }, 500),
@ -97,7 +107,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
{ {
field: 'productId', field: 'productId',
component: 'Select', component: 'Select',
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.In || isEdit, vShow: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.In || isEdit,
label: '产品', label: '产品',
colProps: { colProps: {
span: 16, span: 16,
@ -152,11 +162,11 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
span: 12, span: 12,
}, },
vIf: ({ formModel }) => vIf: ({ formModel }) =>
formModel.inOrOut === MaterialsInOutEnum.In || isEdit || formModel.inventoryNumber, formModel.inOrOut === MaterialsInOutEnum.In || isEdit || formModel.inventoryId,
rules: [{ required: true, type: 'number' }], rules: [{ required: true, type: 'number' }],
helpMessage: '如未找到对应项目,请先去项目管理添加项目。', helpMessage: '如未找到对应项目,请先去项目管理添加项目。出库时选择的项目可以入库时不同',
componentProps: ({ formInstance, schema, formModel }) => ({ componentProps: ({ formInstance, schema, formModel }) => ({
disabled: Object.is(formModel.inOrOut, MaterialsInOutEnum.Out) || isEdit, // disabled: Object.is(formModel.inOrOut, MaterialsInOutEnum.Out) || isEdit,
showSearch: true, showSearch: true,
filterOption: false, filterOption: false,
fieldNames: { fieldNames: {
@ -179,17 +189,6 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
request: () => { request: () => {
return getProjectOptions(); return getProjectOptions();
}, },
// request: {
// watchFields: ['framework'],
// options: {
// immediate: true,
// },
// callback: async ({ formModel }) => {
// console.log('values', formModel);
// formModel['lib'] = undefined;
// return fetchLibData(formModel['framework']);
// },
// },
onSearch: debounce(async (keyword) => { onSearch: debounce(async (keyword) => {
schema.loading = true; schema.loading = true;
const newSchema = { const newSchema = {
@ -209,17 +208,50 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
label: '时间', label: '时间',
field: 'time', field: 'time',
component: 'DatePicker', component: 'DatePicker',
defaultValue: formatToDate(new Date()),
colProps: { colProps: {
span: 12, span: 12,
}, },
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
}, },
{ {
label: '数量', label: '数量',
helpMessage: ({ formModel }) => {
return '出库时,选择产品后显示的数量为实时的库存剩余数量。';
},
field: 'quantity', field: 'quantity',
component: 'InputNumber', component: 'InputNumber',
colProps: { colProps: {
span: 12, span: 12,
}, },
rules: [{ required: true, type: 'number', min: 1 }],
componentProps: ({ formInstance, formModel }) => ({
onBlur(e) {
const { quantity, unitPrice, amount, inOrOut } = toRaw(formModel);
// 出库只关心数量,单价已经有了,直接算出出库金额
if (MaterialsInOutEnum.Out === inOrOut) {
if (unitPrice) {
formInstance?.setFieldsValue({
amount: calcNumber(unitPrice, quantity, 'multiply'),
});
}
} else {
// 入库一般是先输入总价和数量,然后计算单价
if (amount) {
formInstance?.setFieldsValue({
unitPrice: calcNumber(amount, quantity, 'divide'),
});
} else if (unitPrice) {
formInstance?.setFieldsValue({
amount: calcNumber(unitPrice, quantity, 'multiply'),
});
}
}
},
}),
}, },
{ {
label: '单价', label: '单价',
@ -228,6 +260,22 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
colProps: { colProps: {
span: 12, span: 12,
}, },
helpMessage: ({ formModel }) => {
return '出库时,单价不可更改,如需修改单价,请找到入库记录进行修改。';
},
dynamicDisabled: ({ formModel }) => {
return formModel.inOrOut === MaterialsInOutEnum.Out;
},
componentProps: ({ formInstance, formModel }) => ({
onBlur(e) {
const { quantity, unitPrice } = toRaw(formModel);
if (quantity) {
formInstance?.setFieldsValue({
amount: calcNumber(unitPrice, quantity, 'multiply'),
});
}
},
}),
}, },
{ {
label: '金额', label: '金额',
@ -236,6 +284,22 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
colProps: { colProps: {
span: 12, span: 12,
}, },
helpMessage: ({ formModel }) => {
return '出库时,金额不可更改,输入数量会自动计算。';
},
dynamicDisabled: ({ formModel }) => {
return formModel.inOrOut === MaterialsInOutEnum.Out;
},
componentProps: ({ formInstance, formModel }) => ({
onBlur(e) {
const { quantity, amount } = toRaw(formModel);
if (quantity) {
formInstance?.setFieldsValue({
unitPrice: calcNumber(amount, quantity, 'divide'),
});
}
},
}),
}, },
{ {
label: '经办人', label: '经办人',
@ -273,10 +337,10 @@ export const getProjectOptions = async (keyword?: string): Promise<LabelValueOpt
}; };
const getProductOptions = async (keyword?: string): Promise<LabelValueOptions> => { const getProductOptions = async (keyword?: string): Promise<LabelValueOptions> => {
const { items: result } = await Api.product.productList({ pageSize: 100, name: keyword }); const { items: result } = await Api.product.productList({ pageSize: 100, keyword: keyword });
return ( return (
result?.map((item) => ({ result?.map((item) => ({
label: `${item.name}` + (item.company?.name ? `(${item.company?.name})` : ''), label: ` ${item.name} ${item.productNumber ? `(${item.productNumber})` : ''} ${item.company?.name ? `(${item.company?.name})` : ''} `,
value: item.id, value: item.id,
})) || [] })) || []
); );
@ -295,3 +359,16 @@ const getInventoryNumberOptions = async (inventoryNumber?: string): Promise<Labe
})) || [] })) || []
); );
}; };
const getInventories = async (keyword?: string): Promise<LabelValueOptions> => {
const { items: result } = await Api.materialsInventory.materialsInventoryList({
keyword,
isCreateInout: true,
pageSize: 100,
});
return (
result?.map((item) => ({
label: `${item.product?.name} (¥${parseFloat(item.unitPrice)}) (${item.product?.company?.name || '-'}) (${item.project?.name || '-'}) `,
value: item.id,
})) || []
);
};

View File

@ -1,53 +1,51 @@
<template> <template>
<div v-if="columns?.length"> <div v-if="columns?.length">
<DynamicTable <DynamicTable row-key="id"
row-key="id"
header-title="原材料出入库记录" header-title="原材料出入库记录"
title-tooltip="要创建入库记录,必须创建入库产品,产品所属公司。" title-tooltip="要创建入库记录,必须创建入库产品,产品所属公司。"
:data-request="Api.materialsInOut.materialsInOutList" :data-request="Api.materialsInOut.materialsInOutList"
:columns="columns" :columns="columns"
bordered bordered
:scroll="{ x: 1920 }" :scroll="{ x: 1920 }"
size="small" size="small">
>
<template #toolbar> <template #toolbar>
<a-button <a-button type="primary"
type="primary"
:disabled="!$auth('materials_inventory:history_in_out:create')" :disabled="!$auth('materials_inventory:history_in_out:create')"
@click="openEditModal({})" @click="openEditModal({})">
>
新增 新增
</a-button> </a-button> <a-button type="primary"
@click="openExportModal">导出</a-button>
</template> </template>
</DynamicTable> </DynamicTable>
</div> </div>
</template> </template>
<script setup lang="tsx"> <script setup
lang="tsx">
import { useTable } from '@/components/core/dynamic-table'; import { useTable } from '@/components/core/dynamic-table';
import { import {
baseColumns, baseColumns,
type TableColumnItem, type TableColumnItem,
type TableListItem, type TableListItem,
type TableQueryItem,
} from './columns'; } from './columns';
import Api from '@/api/'; import Api from '@/api/';
import { onMounted, ref, type FunctionalComponent, unref } from 'vue'; import { onMounted, ref, type FunctionalComponent } from 'vue';
import { useFormModal, useModal } from '@/hooks/useModal'; import { useFormModal, useModal } from '@/hooks/useModal';
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue';
import { formSchemas } from './formSchemas'; import { formSchemas } from './formSchemas';
import AttachmentManage from '@/components/business/attachment-manage/index.vue'; import AttachmentManage from '@/components/business/attachment-manage/index.vue';
import AttachmentUpload from '@/components/business/attachment-upload/index.vue'; import AttachmentUpload from '@/components/business/attachment-upload/index.vue';
import fileDownload from 'js-file-download'; import { useExportExcelModal, jsonToSheetXlsx } from '@/components/basic/excel';
import dayjs from 'dayjs'; import { MaterialsInOutEnum } from '@/enums/materialsInventoryEnum';
import { formatToDate } from '@/utils/dateUtil';
defineOptions({ defineOptions({
name: 'MaterialsInOut', name: 'MaterialsInOut',
}); });
const [DynamicTable, dynamicTableInstance] = useTable({ formProps: { autoSubmitOnEnter: true } }); const [DynamicTable, dynamicTableInstance] = useTable({ formProps: { autoSubmitOnEnter: true } });
const [showModal] = useFormModal(); const [showModal] = useFormModal();
const exportExcelModal = useExportExcelModal();
const [fnModal] = useModal(); const [fnModal] = useModal();
const isUploadPopupVisiable = ref(false); const isUploadPopupVisiable = ref(false);
@ -101,7 +99,62 @@
}, },
]; ];
}); });
const openExportModal = () => {
exportExcelModal.openModal({
onOk: ({ filename, bookType }) => {
const tableData: TableListItem[] = dynamicTableInstance.tableData;
let exportData: any[] = []
for (let item of tableData) {
exportData.push({
projectName:item.project?.name,
inOrOut: item.inOrOut === MaterialsInOutEnum.In ? '入库' : '出库',
inventoryNumber:item.inventoryNumber,
time:formatToDate(item.time),
company:item.product?.company?.name,
productName:item.product?.name,
productSpecification:item.product?.productSpecification,
unit:item.product?.unit?.label,
quantity:item.quantity,
unitPrice:parseFloat(item.unitPrice),
amount:parseFloat(item.amount),
agent:item.agent,
issuanceNumber:item.issuanceNumber,
remark:item.remark
})
}
jsonToSheetXlsx({
data: exportData,
header: {
inOrOut: '出/入库',
inventoryNumber: '出入库单号',
time: '时间',
projectName:'项目',
company: '公司',
productName: '产品名',
productSpecification: "产品规格",
unit: '单位',
quantity: '数量',
unitPrice: '单价',
amount: '金额',
agent: '经办人',
issuanceNumber: '领料单号',
remark: '备注'
},
filename,
write2excelOpts: {
bookType,
},
json2sheetOpts: {
//
header: [
'inOrOut', 'inventoryNumber', 'time','projectName', 'company', 'productName', 'productSpecification', 'unit', 'quantity',
'unitPrice', 'amount', 'agent', 'issuanceNumber', 'remark'],
},
});
},
});
};
const openAttachmentUploadModal = async (record: TableListItem) => { const openAttachmentUploadModal = async (record: TableListItem) => {
isUploadPopupVisiable.value = true; isUploadPopupVisiable.value = true;
fnModal.show({ fnModal.show({
@ -219,4 +272,5 @@
}; };
</script> </script>
<style lang="less" scoped></style> <style lang="less"
scoped></style>

View File

@ -33,6 +33,22 @@ export const baseColumns: TableColumnItem[] = [
return record?.product?.name || ''; return record?.product?.name || '';
}, },
}, },
{
title: '产品规格',
width: 180,
dataIndex: 'productSpecification',
customRender: ({ record }) => {
return record?.product?.productSpecification || '';
},
},
{
title: '产品编号',
width: 180,
dataIndex: 'productNumber',
customRender: ({ record }) => {
return record?.product?.productNumber || '';
},
},
{ {
title: '单位', title: '单位',
width: 60, width: 60,
@ -57,7 +73,7 @@ export const baseColumns: TableColumnItem[] = [
dataIndex: 'quantity', dataIndex: 'quantity',
}, },
{ {
title: '库存单价', title: '入库库存单价',
hideInSearch: true, hideInSearch: true,
width: 80, width: 80,
dataIndex: 'unitPrice', dataIndex: 'unitPrice',

View File

@ -4,10 +4,19 @@ import type { TableColumn } from '@/components/core/dynamic-table';
export type TableListItem = API.ProductEntity; export type TableListItem = API.ProductEntity;
export type TableColumnItem = TableColumn<TableListItem>; export type TableColumnItem = TableColumn<TableListItem>;
export const baseColumns: TableColumnItem[] = [ export const baseColumns: TableColumnItem[] = [
{
title: '产品编号',
dataIndex: 'productNumber',
},
{ {
title: '名称', title: '名称',
dataIndex: 'name', dataIndex: 'name',
}, },
{
title: '产品规格',
dataIndex: 'productSpecification',
},
{ {
title: '所属公司', title: '所属公司',
dataIndex: 'company', dataIndex: 'company',
@ -18,7 +27,7 @@ export const baseColumns: TableColumnItem[] = [
{ {
title: '单位', title: '单位',
dataIndex: 'unit', dataIndex: 'unit',
width:80, width: 80,
customRender: ({ record }) => { customRender: ({ record }) => {
return record?.unit?.label || ''; return record?.unit?.label || '';
}, },

View File

@ -14,6 +14,14 @@ export const formSchemas: FormSchema<API.ProductDto>[] = [
span: 12, span: 12,
}, },
}, },
{
field: 'productSpecification',
component: 'Input',
label: '产品规格',
colProps: {
span: 12,
},
},
{ {
label: '单位', label: '单位',
component: 'Select', component: 'Select',

33
src/views/task/gantt.vue Normal file
View File

@ -0,0 +1,33 @@
<template>
<div ref="ganttContainer"></div>
</template>
<script setup lang="ts">
import { gantt } from 'dhtmlx-gantt';
import { onMounted, ref } from 'vue';
const ganttContainer = ref();
const props = defineProps({
tasks: {
type: Object,
required: true,
},
});
onMounted(() => {
gantt.config.date_format = '%Y-%m-%d';
gantt.init(ganttContainer.value);
gantt.config.columns = [
{ name: 'text', label: 'Task name', tree: true, width: '*' },
{ name: 'start_date', label: 'Start time', align: 'center' },
{ name: 'end_date', label: 'End time', align: 'center' },
{ name: 'duration', label: 'Duration', align: 'center' },
{ name: 'add', label: '' },
];
gantt.parse(props.tasks);
// gantt.parse(this.$props.tasks);
});
defineOptions({
name: 'HuaxinGantt',
});
</script>
<style lang="less" scoped></style>

121
src/views/task/ganttData.js Normal file
View File

@ -0,0 +1,121 @@
export default {
data: [
{ id: 11, text: "Project #1", type: "project", progress: 0.6, open: true },
{
id: 12,
text: "Task #1",
start_date: "03-04-2018",
duration: "5",
parent: "11",
progress: 1,
open: true,
},
{
id: 13,
text: "Task #2",
start_date: "03-04-2018",
type: "project",
parent: "11",
progress: 0.5,
open: true,
},
{
id: 14,
text: "Task #3",
start_date: "02-04-2018",
duration: "6",
parent: "11",
progress: 0.8,
open: true,
},
{ id: 15, text: "Task #4", type: "project", parent: "11", progress: 0.2, open: true },
{
id: 16,
text: "Final milestone",
start_date: "15-04-2018",
type: "milestone",
parent: "11",
progress: 0,
open: true,
},
{
id: 17,
text: "Task #2.1",
start_date: "03-04-2018",
duration: "2",
parent: "13",
progress: 1,
open: true,
},
{
id: 18,
text: "Task #2.2",
start_date: "06-04-2018",
duration: "3",
parent: "13",
progress: 0.8,
open: true,
},
{
id: 19,
text: "Task #2.3",
start_date: "10-04-2018",
duration: "4",
parent: "13",
progress: 0.2,
open: true,
},
{
id: 20,
text: "Task #2.4",
start_date: "10-04-2018",
duration: "4",
parent: "13",
progress: 0,
open: true,
},
{
id: 21,
text: "Task #4.1",
start_date: "03-04-2018",
duration: "4",
parent: "15",
progress: 0.5,
open: true,
},
{
id: 22,
text: "Task #4.2",
start_date: "03-04-2018",
duration: "4",
parent: "15",
progress: 0.1,
open: true,
},
{
id: 23,
text: "Mediate milestone",
start_date: "14-04-2018",
type: "milestone",
parent: "15",
progress: 0,
open: true,
},
],
links: [
{ id: "10", source: "11", target: "12", type: "1" },
{ id: "11", source: "11", target: "13", type: "1" },
{ id: "12", source: "11", target: "14", type: "1" },
{ id: "13", source: "11", target: "15", type: "1" },
{ id: "14", source: "23", target: "16", type: "0" },
{ id: "15", source: "13", target: "17", type: "1" },
{ id: "16", source: "17", target: "18", type: "0" },
{ id: "17", source: "18", target: "19", type: "0" },
{ id: "18", source: "19", target: "20", type: "0" },
{ id: "19", source: "15", target: "21", type: "2" },
{ id: "20", source: "15", target: "22", type: "2" },
{ id: "21", source: "15", target: "23", type: "0" },
],
};

25
src/views/task/index.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<div class="container"><HuaxinGantt class="left-container" :tasks="tasks"></HuaxinGantt></div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import HuaxinGantt from './gantt.vue';
import GanttData from './ganttData'
const tasks = ref(GanttData);
defineOptions({
name: 'Task',
});
</script>
<style lang="less" scoped>
.container {
height: 100%;
width: 100%;
}
.left-container {
overflow: hidden;
position: relative;
height: 100%;
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<div class="slider-block">
<div class="slider-item">
<span class="demonstration">鼠标视角控制器</span>
<a-switch v-model:checked="mouseValue" @change="switchChange" />
</div>
<div class="slider-item">
<span class="demonstration">关节一绕Y轴旋转</span>
<a-slider
v-model:value="value1"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D1', 'y')"
/>
</div>
<div class="slider-item">
<span class="demonstration">关节二绕Z轴旋转</span>
<a-slider
v-model:value="value2"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D2', 'z')"
/>
</div>
<div class="slider-item">
<span class="demonstration">关节三绕Z轴旋转</span>
<a-slider
v-model:value="value3"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D3', 'z')"
/>
</div>
<div class="slider-item">
<span class="demonstration">关节四绕Z轴旋转</span>
<a-slider
v-model:value="value4"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D4', 'z')"
/>
</div>
<div class="slider-item">
<p class="demonstration">关节五</p>
<span class="demonstration">绕x轴旋转</span>
<a-slider
v-model:value="value5_1"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D5', 'x')"
/>
<span class="demonstration">绕y轴旋转</span>
<a-slider
v-model:value="value5_2"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D5', 'y')"
/>
<span class="demonstration">绕Z轴旋转</span>
<a-slider
v-model:value="value5_3"
show-input
:min="min"
:max="max"
:step="0.01"
@change="sliderInput($event, 'D5', 'z')"
/>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue';
const mouseValue = ref(true);
const value1 = ref(0);
const value2 = ref(0);
const value3 = ref(0);
const value4 = ref(0);
const value5_1 = ref(0);
const value5_2 = ref(0);
const value5_3 = ref(0);
const min = ref(Number(-Math.PI.toFixed(2)));
const max = ref(Number(Math.PI.toFixed(2)));
const emit = defineEmits(['sliderInput', 'switchChange']);
const sliderInput = (e, name, direction) => {
emit('sliderInput', e, name, direction);
};
const switchChange = (e) => {
emit('switchChange', e);
};
</script>
<style scope>
.slider-block {
padding: 20px 10px;
}
.slider-item {
margin: 20px 0;
}
.demonstration {
margin: 0 10px 10px 0;
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<canvas class="webgl" ref="webgl"></canvas>
</template>
<script setup>
import { onMounted } from 'vue';
import BaseManager from './manager/BaseManager.js';
let base = null;
onMounted(() => {
base = new BaseManager(document.querySelector('canvas.webgl'));
});
const setRobotRotation = (e, name, direction) => {
base.setRobotRotation(e, name, direction);
};
const setControlsEnabled = (enabled) => {
base.setControlsEnabled(enabled);
};
defineExpose({ setRobotRotation, setControlsEnabled });
</script>
<style scscope>
.webgl {
width: 100%;
height: 100%;
outline: none;
}
</style>

View File

@ -0,0 +1,293 @@
import * as dat from "dat.gui";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
export default class baseManager {
constructor(canvas) {
//Gui
this.gui = new dat.GUI();
this.gui.hide();
//Canvas
this.canvas = canvas
//Sizes
this.sizes = {}
//Camera
this.camera = null
//Renderer
this.renderer = null
// Scene
this.scene = new THREE.Scene();
//AnimateTick
this.clock = new THREE.Clock();
this.previousTime = 0;
this.initWindowSizes()
this.initcamera()
this.inLights()
this.initHelper()
this.initControls()
this.initRobot()
this.initRenderer()
this.initAnimateTick()
}
/**
* Sizes
*/
initWindowSizes() {
/**
* Sizes
*/
const sizes = {
width: this.canvas.parentNode.clientWidth,
height: this.canvas.parentNode.clientHeight,
};
window.addEventListener("resize", () => {
// Update sizes
sizes.width = this.canvas.parentNode.clientWidth;
sizes.height = this.canvas.parentNode.clientHeight;
// Update camera
this.camera.aspect = sizes.width / sizes.height;
this.camera.updateProjectionMatrix();
// Update renderer
this.renderer.setSize(sizes.width, sizes.height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
this.sizes = sizes;
}
/**
* Camera
*/
initcamera() {
// Base camera
const camera = new THREE.PerspectiveCamera(
75,
this.sizes.width / this.sizes.height,
0.1,
100
);
camera.position.set(0, 4, 10);
this.scene.add(camera);
this.camera = camera;
}
/**
* inLights
*/
inLights() {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.set(1024, 1024);
directionalLight.shadow.camera.far = 25;
directionalLight.shadow.camera.left = -7;
directionalLight.shadow.camera.top = 7;
directionalLight.shadow.camera.right = 7;
directionalLight.shadow.camera.bottom = -7;
directionalLight.position.set(5, 5, 5);
this.scene.add(directionalLight);
}
/**
* Helper
*/
initHelper() {
const axes = new THREE.AxesHelper(20);
this.scene.add(axes);
const gridHelper = new THREE.GridHelper(100, 100);
this.scene.add(gridHelper);
}
/**
* Controls
*/
initControls() {
const controls = new OrbitControls(this.camera, this.canvas);
controls.target.set(0, 0.75, 0);
controls.enableDamping = true;
this.controls = controls;
}
setControlsEnabled(enabled) {
this.controls.enabled = enabled
}
/**
* Robot
*/
initRobot() {
const D1 = new THREE.Mesh(
new THREE.CylinderGeometry(1, 1, 0.5, 32),
new THREE.MeshStandardMaterial({
color: "#E45826",
metalness: 0,
roughness: 0.5,
})
);
this.gui.add(D1.rotation, "y").min(-Math.PI).max(Math.PI).step(0.01);
const D2 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 16),
new THREE.MeshStandardMaterial({
color: "#1B1A17",
metalness: 0,
roughness: 0.5,
})
);
D2.position.set(0, 0.75, 0);
// D2.rotation.z = Math.PI / 4;
D1.add(D2);
this.gui.add(D2.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
const B1 = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 3, 0.5),
new THREE.MeshStandardMaterial({
color: "#E45826",
metalness: 0,
roughness: 0.5,
})
);
B1.position.set(-1.5 * Math.sin(Math.PI / 4), 1, 0);
B1.rotation.z = Math.PI / 4;
D2.add(B1);
const D3 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 16),
new THREE.MeshStandardMaterial({
color: "#1B1A17",
metalness: 0,
roughness: 0.5,
})
);
D3.position.set(0, 1.5, 0);
// D3.rotation.z = -Math.PI / 2;
B1.add(D3);
this.gui.add(D3.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
const B2 = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 3, 0.5),
new THREE.MeshStandardMaterial({
color: "#E45826",
metalness: 0,
roughness: 0.5,
})
);
B2.position.set(1.5, 0, 0);
B2.rotation.z = -Math.PI / 2;
D3.add(B2);
const D4 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 16),
new THREE.MeshStandardMaterial({
color: "#1B1A17",
metalness: 0,
roughness: 0.5,
})
);
D4.position.set(0, 1.5, 0);
// D4.rotation.z = -Math.PI / 4;
B2.add(D4);
this.gui.add(D4.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
const B3 = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 3, 0.5),
new THREE.MeshStandardMaterial({
color: "#E45826",
metalness: 0,
roughness: 0.5,
})
);
// B3.position.set(0, 1.5, 0);
B3.position.set(1.5 * Math.cos(Math.PI / 4), 1.5 * Math.sin(Math.PI / 4), 0);
B3.rotation.z = -Math.PI / 4;
D4.add(B3);
const D5 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 16),
new THREE.MeshStandardMaterial({
color: "#1B1A17",
metalness: 0,
roughness: 0.5,
})
);
D5.position.set(0, 1.5, 0);
// D5.rotation.z = -Math.PI / 2;
B3.add(D5);
this.gui.add(D5.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
const B4 = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 1, 0.5),
new THREE.MeshStandardMaterial({
color: "#E45826",
metalness: 0,
roughness: 0.5,
})
);
B4.position.set(0.5, 0, 0);
B4.rotation.z = -Math.PI / 2;
D5.add(B4);
D1.castShadow = true;
D2.castShadow = true;
B1.castShadow = true;
D3.castShadow = true;
B2.castShadow = true;
D4.castShadow = true;
B3.castShadow = true;
D5.castShadow = true;
B4.castShadow = true;
this.scene.add(D1);
this.D1 = D1
this.D2 = D2
this.D3 = D3
this.D4 = D4
this.D5 = D5
}
setRobotRotation(rotation, name, direction) {
this[name].rotation[direction] = rotation
}
/**
* Renderer
*/
initRenderer() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
});
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.setSize(this.sizes.width, this.sizes.height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// this.renderer.setClearColor("#fff");
}
/**
* AnimateTick
*/
initAnimateTick() {
const elapsedTime = this.clock.getElapsedTime();
const deltaTime = elapsedTime - this.previousTime;
this.previousTime = elapsedTime;
//Update controls
this.controls.update();
// Render
this.renderer.render(this.scene, this.camera);
// Call tick again on the next frame
window.requestAnimationFrame(() => {
this.initAnimateTick()
});
}
}

View File

@ -0,0 +1,269 @@
// import * as THREE from "three";
// import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
// import * as dat from "dat.gui";
// import { onMounted, nextTick } from "@vue/runtime-core";
// /**
// * Base
// */
// // Debug
// const gui = new dat.GUI();
// gui.close();
// // Canvas
// const canvas = document.querySelector("canvas.webgl");
// // Scene
// const scene = new THREE.Scene();
// /**
// * Floor
// */
// const floor = new THREE.Mesh(
// new THREE.PlaneBufferGeometry(100, 100),
// new THREE.MeshStandardMaterial({
// color: "#444444",
// metalness: 0,
// roughness: 0.5,
// })
// );
// floor.receiveShadow = true;
// floor.rotation.x = -Math.PI * 0.5;
// floor.receiveShadow = true;
// // scene.add(floor);
// /**
// * Robot
// */
// const D1 = new THREE.Mesh(
// new THREE.CylinderGeometry(1, 1, 0.5, 32),
// new THREE.MeshStandardMaterial({
// color: "#E45826",
// metalness: 0,
// roughness: 0.5,
// })
// );
// gui.add(D1.rotation, "y").min(-Math.PI).max(Math.PI).step(0.01);
// const D2 = new THREE.Mesh(
// new THREE.SphereGeometry(0.5, 32, 16),
// new THREE.MeshStandardMaterial({
// color: "#1B1A17",
// metalness: 0,
// roughness: 0.5,
// })
// );
// D2.position.set(0, 0.75, 0);
// // D2.rotation.z = Math.PI / 4;
// D1.add(D2);
// gui.add(D2.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
// const B1 = new THREE.Mesh(
// new THREE.BoxGeometry(0.5, 3, 0.5),
// new THREE.MeshStandardMaterial({
// color: "#E45826",
// metalness: 0,
// roughness: 0.5,
// })
// );
// B1.position.set(-1.5 * Math.sin(Math.PI / 4), 1, 0);
// B1.rotation.z = Math.PI / 4;
// D2.add(B1);
// const D3 = new THREE.Mesh(
// new THREE.SphereGeometry(0.5, 32, 16),
// new THREE.MeshStandardMaterial({
// color: "#1B1A17",
// metalness: 0,
// roughness: 0.5,
// })
// );
// D3.position.set(0, 1.5, 0);
// // D3.rotation.z = -Math.PI / 2;
// B1.add(D3);
// gui.add(D3.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
// const B2 = new THREE.Mesh(
// new THREE.BoxGeometry(0.5, 3, 0.5),
// new THREE.MeshStandardMaterial({
// color: "#E45826",
// metalness: 0,
// roughness: 0.5,
// })
// );
// B2.position.set(1.5, 0, 0);
// B2.rotation.z = -Math.PI / 2;
// D3.add(B2);
// const D4 = new THREE.Mesh(
// new THREE.SphereGeometry(0.5, 32, 16),
// new THREE.MeshStandardMaterial({
// color: "#1B1A17",
// metalness: 0,
// roughness: 0.5,
// })
// );
// D4.position.set(0, 1.5, 0);
// // D4.rotation.z = -Math.PI / 4;
// B2.add(D4);
// gui.add(D4.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
// const B3 = new THREE.Mesh(
// new THREE.BoxGeometry(0.5, 3, 0.5),
// new THREE.MeshStandardMaterial({
// color: "#E45826",
// metalness: 0,
// roughness: 0.5,
// })
// );
// // B3.position.set(0, 1.5, 0);
// B3.position.set(1.5 * Math.cos(Math.PI / 4), 1.5 * Math.sin(Math.PI / 4), 0);
// B3.rotation.z = -Math.PI / 4;
// D4.add(B3);
// scene.add(D1);
// const D5 = new THREE.Mesh(
// new THREE.SphereGeometry(0.5, 32, 16),
// new THREE.MeshStandardMaterial({
// color: "#1B1A17",
// metalness: 0,
// roughness: 0.5,
// })
// );
// D5.position.set(0, 1.5, 0);
// // D5.rotation.z = -Math.PI / 2;
// B3.add(D5);
// gui.add(D5.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);
// const B4 = new THREE.Mesh(
// new THREE.BoxGeometry(0.5, 1, 0.5),
// new THREE.MeshStandardMaterial({
// color: "#E45826",
// metalness: 0,
// roughness: 0.5,
// })
// );
// B4.position.set(0.5, 0, 0);
// B4.rotation.z = -Math.PI / 2;
// D5.add(B4);
// D1.castShadow = true;
// D2.castShadow = true;
// B1.castShadow = true;
// D3.castShadow = true;
// B2.castShadow = true;
// D4.castShadow = true;
// B3.castShadow = true;
// D5.castShadow = true;
// scene.add(D1);
// /**
// * helper
// */
// const axes = new THREE.AxesHelper(20);
// scene.add(axes);
// const gridHelper = new THREE.GridHelper(100, 100);
// scene.add(gridHelper);
// /**
// * Lights
// */
// const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
// scene.add(ambientLight);
// const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
// directionalLight.castShadow = true;
// directionalLight.shadow.mapSize.set(1024, 1024);
// directionalLight.shadow.camera.far = 25;
// directionalLight.shadow.camera.left = -7;
// directionalLight.shadow.camera.top = 7;
// directionalLight.shadow.camera.right = 7;
// directionalLight.shadow.camera.bottom = -7;
// directionalLight.position.set(5, 5, 5);
// scene.add(directionalLight);
// /**
// * Sizes
// */
// const sizes = {
// width: canvas.parentNode.clientWidth,
// height: canvas.parentNode.clientHeight,
// };
// window.addEventListener("resize", () => {
// // Update sizes
// sizes.width = canvas.parentNode.clientWidth;
// sizes.height = canvas.parentNode.clientHeight;
// // Update camera
// camera.aspect = sizes.width / sizes.height;
// camera.updateProjectionMatrix();
// // Update renderer
// renderer.setSize(sizes.width, sizes.height);
// renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// });
// /**
// * Camera
// */
// // Base camera
// const camera = new THREE.PerspectiveCamera(
// 75,
// sizes.width / sizes.height,
// 0.1,
// 100
// );
// camera.position.set(0, 4, 10);
// scene.add(camera);
// // Controls
// const controls = new OrbitControls(camera, canvas);
// controls.target.set(0, 0.75, 0);
// controls.enableDamping = true;
// // controls.enabled = false;
// // const transformControls = new TransformControls(camera, canvas);
// // transformControls.setMode("rotate");
// // transformControls.attach(D1);
// // transformControls.showX = false;
// // // transformControls.showY = false;
// // transformControls.showZ = false;
// // scene.add(transformControls);
// /**
// * Renderer
// */
// const renderer = new THREE.WebGLRenderer({
// canvas: canvas,
// });
// renderer.shadowMap.enabled = true;
// renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// renderer.setSize(sizes.width, sizes.height);
// renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// renderer.setClearColor("#fff");
// /**
// * Animate
// */
// const clock = new THREE.Clock();
// let previousTime = 0;
// const tick = () => {
// const elapsedTime = clock.getElapsedTime();
// const deltaTime = elapsedTime - previousTime;
// previousTime = elapsedTime;
// // Update controls
// controls.update();
// // Render
// renderer.render(scene, camera);
// // Call tick again on the next frame
// window.requestAnimationFrame(tick);
// };
// tick();

View File

@ -0,0 +1,657 @@
<template>
<a-spin v-if="isLoading" class="loading" :tip="progress"> </a-spin>
<div ref="container" class="main-container"></div>
<div class="fps-container">{{ fpsDisplay }}</div>
</template>
<script setup lang="ts">
// threejs
// import * as THREE from 'three';
import gsap from 'gsap';
import {
refDebounced,
refThrottled,
useDebounceFn,
useElementSize,
useResizeObserver,
} from '@vueuse/core';
import {
AxesHelper,
MeshBasicMaterial,
Scene,
BoxGeometry,
Mesh,
Box3,
Vector3,
Vector2,
PerspectiveCamera,
WebGLRenderer,
DoubleSide,
PlaneGeometry,
MeshPhongMaterial,
AmbientLight,
SpotLight,
MeshLambertMaterial,
SpotLightHelper,
Clock,
Raycaster,
Group,
CameraHelper,
Object3D,
} from 'three';
// OrbitControls.js
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// GUI
import { GUI } from 'dat.gui';
// stats.js
// import Stats from 'three/addons/libs/stats.module.js';
// GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// Loader.js
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
import { computed, onMounted } from 'vue';
import { ref, watch } from 'vue';
const container = ref();
const isLoading = ref(false);
const progress = ref('0%');
let scene: Scene;
let camera: PerspectiveCamera;
let renderer: WebGLRenderer;
let axesHelper: AxesHelper;
let mesh: Object3D;
let spotLight: SpotLight;
let lightHelper: SpotLightHelper;
let camerahelper: CameraHelper;
let controls: OrbitControls;
const fps = ref(0);
const fpsValue = refThrottled(fps, 500); //
const fpsDisplay = computed(() => {
return `${fpsValue.value} FPS`;
});
const clock = new Clock();
defineOptions({
name: 'ThreeBase',
});
onMounted(() => {
initScece();
initSTL().then((res) => {
initAmbientLight();
initSpotLight();
initPlane();
// initMesh();
initCamera();
initGUI();
initRenderer();
initControls();
initAxesHelper();
initCameraHelper();
initLightHelper(spotLight); //
render();
initListener();
});
});
// 3DScene
function initScece() {
scene = new Scene();
}
const initSTL = (): Promise<void> => {
isLoading.value = true;
return new Promise((resolve, reject) => {
const loader: GLTFLoader = new GLTFLoader();
loader.load(
'/model/3.gltf',
(geometry) => {
// const material = new MeshLambertMaterial({ color: 0x00ff00, side: DoubleSide });
console.log(geometry);
//
const model = geometry.scene;
// var box = new Box3().setFromObject(model);
// var size = box.getSize(new Vector3()).length();
// var scale = 1 / size;
model.scale.set(0.01, 0.01, 0.01);
model.name = 'zhijia';
mesh = model;
mesh.position.set(20, 0, 0);
scene.add(mesh);
initRayCaster();
isLoading.value = false;
resolve();
},
(event: ProgressEvent) => {
const { loaded, total } = event;
progress.value = `${Math.floor((loaded / total) * 100)}%`;
},
(err) => {
isLoading.value = false;
reject(err);
},
);
});
};
//
function initAxesHelper() {
axesHelper = new AxesHelper(150);
scene.add(axesHelper);
}
//
function initLightHelper(light) {
if (light) {
lightHelper = new SpotLightHelper(light);
lightHelper.visible = false;
scene.add(lightHelper);
}
}
//
function initCameraHelper() {
camerahelper = new CameraHelper(camera);
camerahelper.visible = false;
scene.add(camerahelper);
}
//
function initSpotLight() {
spotLight = new SpotLight(0xffffff, 8000);
spotLight.angle = Math.PI / 6;
spotLight.position.set(0, 30, 40);
spotLight.lookAt(0, 0, 0);
scene.add(spotLight);
}
//
function initAmbientLight() {
const light = new AmbientLight('#ffffff', 1);
scene.add(light);
}
//
function initPlane() {
// Geometry
const geometry = new PlaneGeometry(100, 100);
// Material
const material = new MeshLambertMaterial({
color: 0xcccccc, // 0x00ff00绿
// side: DoubleSide, //
});
const plane = new Mesh(geometry, material); // Mesh
plane.rotateX(-Math.PI / 2); //
scene.add(plane); //
}
//
function initMesh() {
// Geometry
const geometry = new BoxGeometry(10, 10, 10);
// Material
const material = new MeshPhongMaterial({
color: 0xff0000, // 0xff0000
// transparent: true, //
shininess: 100, // 30
specular: 0x444444, //
// opacity: 0.8, //
});
mesh = new Mesh(geometry, material); // Mesh
mesh.position.setY(5);
scene.add(mesh);
}
//
function initCamera() {
const width = container.value.clientWidth;
const height = container.value.clientHeight;
const aspect = width / height; //
camera = new PerspectiveCamera(45, aspect, 0.1, 1000);
camera.position.set(-10, 10, 100); //
camera.lookAt(0, 0, 0); // ()
scene.add(camera);
}
//
function initRenderer() {
renderer = new WebGLRenderer({
antialias: true, // 齿
});
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
renderer.render(scene, camera); //
container.value.appendChild(renderer.domElement);
}
//
function initControls() {
controls = new OrbitControls(camera, renderer.domElement);
//
controls.enableDamping = true;
controls.target.set(10, 10, 10);
//
camera.lookAt(0, 0, 0);
controls.addEventListener('change', () => {
renderer.render(scene, camera);
});
}
//
let angle = 0; //
const R = 100; //
const animates = {
meshRotate: false,
cameraMove: false,
meshMoveX: false,
gsap: false,
};
let raycaster;
let mouse;
let selectableGroups: any[] = [];
// 线
function initRayCaster() {
// let zhijiaObj;
// scene.traverse((object) => {
// if (object.name === 'zhijia') {
// zhijiaObj = object;
// }
// });
mesh.traverse((object) => {
if (object.id < 50) selectableGroups.push(object);
});
raycaster = new Raycaster();
mouse = new Vector2();
window.addEventListener(
'mousemove',
(event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
},
false,
);
}
//
function initAnimate() {
if (animates.meshMoveX) {
mesh.position.x += 0.1;
if (mesh.position.x > 50) {
mesh.position.x = 0;
}
}
if (animates.meshRotate) {
// mesh.rotation.set(Math.PI / 4, 0, 0, 'XZY'); // y0.01
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
mesh.rotation.z += 0.01;
}
if (animates.cameraMove) {
angle += 0.01;
// yXOZ
camera.position.x = R * Math.cos(angle);
camera.position.z = R * Math.sin(angle);
// .positionlookAt(0,0,0)线
camera.lookAt(0, 0, 0);
}
}
let INTERSECTED;
let selectedGroup;
function chooseGroup(intersects) {
if (intersects.length > 0) {
const object = intersects[0].object;
// object
let group = object;
while (group.parent) {
if (group.parent?.id === 7) {
break;
}
group = group.parent;
}
if (group && group !== selectedGroup) {
//
if (selectedGroup) {
selectedGroup.traverse((child) => {
if (child.isMesh) {
child.material.color.set(child.currentHex);
}
});
}
//
selectedGroup = group;
console.log(group);
group.traverse((child) => {
if (child.isMesh) {
child.currentHex = child.material.color.getHex();
child.material = child.material.clone();
child.material.color.set(0xff0000);
}
});
}
} else {
//
if (selectedGroup) {
selectedGroup.traverse((child) => {
if (child.isMesh) {
child.material.color.set(child.currentHex);
}
});
selectedGroup = null;
}
}
}
let gsapAnimate: gsap.core.Tween;
function initAnimateByGsap() {
// gsap.to(mesh.position, {
// x: 50,
// duration: 2,
// repeat: -1,
// yoyo: true,
// ease: 'power1.inOut',
// });
// gsap.to(mesh.rotation, {
// x: Math.PI / 2,
// duration: 2,
// repeat: -1,
// yoyo: true,
// ease: 'power1.inOut',
// });
gsapAnimate = gsap.to(camera.position, {
x: 100,
duration: 2,
repeat: -1,
yoyo: true,
ease: 'power1.inOut',
});
// camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix();
}
// resize
function initListener() {
// onresize
// windows
const debouncedFn = useDebounceFn(() => {
// canvas
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
//
renderer.setPixelRatio(window.devicePixelRatio);
// aspect
camera.aspect = container.value.clientWidth / container.value.clientHeight;
// renderprojectionMatrix
// ()
// updateProjectionMatrix ()
camera.updateProjectionMatrix();
}, 50);
window.addEventListener('resize', debouncedFn);
window.addEventListener('dblclick', () => {
const fullScreenElement = document.fullscreenElement;
if (!fullScreenElement) {
// 退
//
renderer.domElement.requestFullscreen();
} else {
// 退使document
document.exitFullscreen();
}
});
// vueuse
// useResizeObserver(container, entries => {
// const entry = entries[0];
// const { width, height } = entry.contentRect;
// renderer.setSize(width, height);
// camera.aspect = width / height;
// camera.updateProjectionMatrix();
// });
// vueuse
// function onResize({ width, height }: { width: number; height: number }) {
// // v-element-size="onResize"
// renderer.setSize(width, height);
// camera.aspect = width / height;
// camera.updateProjectionMatrix();
// }
// 使watch
// const { width, height } = useElementSize(container);
// watch(
// [width, height],
// (oldValue, newValue) => {
// console.log(newValue);
// const [width, height] = newValue;
// if (!width || !height) {
// return;
// }
// renderer.setSize(width, height);
// camera.aspect = width / height;
// camera.updateProjectionMatrix();
// },
// {
// immediate: false,
// flush: 'sync',
// }
// );
}
// initAnimateByGsap();
// GUI
function initGUI() {
// GUI
const gui = new GUI();
// initAnimateByGsap();
gui
.add(animates, 'gsap')
.name('GSAP动画')
.onChange((flg) => {
if (gsapAnimate.isActive()) {
gsapAnimate.pause();
} else {
gsapAnimate.resume();
}
});
const zhijiaGroup = gui.addFolder('支架');
const zhijia1 = scene.getObjectById(8);
const zhijia2 = scene.getObjectById(9);
const zhijia3 = scene.getObjectById(16);
const zhuzi1 = scene.getObjectById(53);
const zhuzi2 = scene.getObjectById(67);
const zhijia1Clone = zhijia1?.clone();
const zhijia2Clone = zhijia2?.clone();
const zhijia3Clone = zhijia3?.clone();
const zhuzi1Clone = zhuzi1?.clone();
const zhuzi2Clone = zhuzi2?.clone();
// if (zhijia1) {
// moveDownGroup.add(zhijia1);
// }
// if (zhijia2) {
// moveDownGroup.add(zhijia2);
// }
// if (zhijia3) {
// moveDownGroup.add(zhijia3);
// }
// scene.add(moveDownGroup)
// 使zhijia8z,0.01
const zhijiaProps = {
zhijiatouRotate: 0,
zhijiabiRotate: 0,
};
zhijiaGroup
.add(zhijiaProps, 'zhijiatouRotate', Math.PI / 2, Math.PI / 1)
.name('支架头部旋转')
.onChange((e) => {
if (zhijia1) {
zhijia1.rotation.z = e;
}
});
zhijiaGroup
.add(zhijiaProps, 'zhijiabiRotate', 0, 500)
.name('支架整体下移')
.onChange((e) => {
// moveDownGroup.position.y = moveDownGroup.position.y - e;
if (zhijia3 && zhijia3Clone) {
zhijia3.position.y = zhijia3Clone.position.y - e;
if (zhijia2 && zhijia2Clone) {
zhijia2.position.y = zhijia2Clone.position.y - e;
}
if (zhijia1 && zhijia1Clone) {
zhijia1.position.y = zhijia1Clone.position.y - e;
}
if (zhuzi1 && zhuzi1Clone) {
zhuzi1.rotation.z = zhuzi1Clone.rotation.z - e / 500;
}
if (zhuzi2 && zhuzi2Clone) {
zhuzi2.rotation.z = zhuzi2Clone.rotation.z - e / 500;
}
}
});
const spotLightGroup = gui.addFolder('聚光灯属性');
const spotProps = {
intensity: 10000,
spotLightHelpVisiable: false,
};
spotLightGroup
.add(spotProps, 'spotLightHelpVisiable')
.name('显示聚光灯辅助')
.onChange((e) => {
lightHelper.visible = e;
});
if (spotLight) {
spotLightGroup.add(spotLight.position, 'x', -50, 50).onChange((e) => {
lightHelper.update();
});
spotLightGroup.add(spotLight.position, 'y', -50, 50).onChange((e) => {
lightHelper.update();
});
spotLightGroup.add(spotLight.position, 'z', -50, 50).onChange((e) => {
lightHelper.update();
});
spotLightGroup.add(spotLight, 'angle', Math.PI / 10, Math.PI / 2).onChange((e) => {
spotLight.angle = e;
lightHelper.update();
});
spotLightGroup.add(spotProps, 'intensity', 0, 20000).onChange((e) => {
spotLight.intensity = e;
lightHelper.update();
});
}
const meshPostionGroup = gui.addFolder('位置');
const meshPostionProps = {
x: 30,
y: 60,
z: 300,
};
meshPostionGroup.add(meshPostionProps, 'x', -50, 50).onChange((e) => {
mesh.position.x = e;
});
meshPostionGroup.add(meshPostionProps, 'y', -50, 50).onChange((e) => {
mesh.position.y = e;
});
meshPostionGroup.add(meshPostionProps, 'z', -50, 50).onChange((e) => {
mesh.position.z = e;
});
meshPostionGroup.add(animates, 'meshMoveX').name('物体x轴运动');
meshPostionGroup.add(animates, 'meshRotate').name('物体旋转');
//
const cameraGroup = gui.addFolder('相机');
const cameraProps = {
x: 50,
y: 50,
z: 50,
fov: 45,
helperVisiable: false,
};
// cameraGroup.open();
cameraGroup.add(animates, 'cameraMove').name('开始绕Y轴旋转');
cameraGroup
.add(cameraProps, 'helperVisiable')
.name('显示相机辅助')
.onChange((visible) => {
camerahelper.visible = visible;
});
cameraGroup
.add(cameraProps, 'fov', 0, 100)
.name('修改相机远近')
.onChange((num) => {
camera.fov = num;
camera.updateProjectionMatrix();
});
cameraGroup.add(cameraProps, 'x', -50, 100).onChange((e) => {
camera.position.x = e;
camera.lookAt(mesh.position);
camera.updateProjectionMatrix();
});
cameraGroup.add(cameraProps, 'y', -50, 100).onChange((e) => {
camera.position.y = e;
camera.lookAt(mesh.position);
camera.updateProjectionMatrix();
});
cameraGroup.add(cameraProps, 'z', -50, 100).onChange((e) => {
camera.position.z = e;
camera.lookAt(mesh.position);
camera.updateProjectionMatrix();
});
// //
// gui.add(controls, 'rotationSpeed', 0, 10, 0.01).onChange(e => {
// mesh.rotateX = e;
// });
}
//
function render() {
const spt = clock.getDelta() * 1000; //
// console.log('()', spt);
// console.log('FPS', 1000 / spt);
fps.value = Math.floor(1000 / spt);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(selectableGroups, true);
// //
// if (intersects.length > 0) {
// //
// if (INTERSECTED != intersects[0].object) {
// //
// if (INTERSECTED) INTERSECTED.material.color.set(INTERSECTED.currentHex);
// //
// INTERSECTED = intersects[0].object;
// INTERSECTED.material = INTERSECTED.material.clone(); //
// INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// //
// INTERSECTED.material.color.set(0xff0000);
// console.log(INTERSECTED);
// }
// } else {
// //
// if (INTERSECTED) INTERSECTED.material.color.set(INTERSECTED.currentHex);
// INTERSECTED = null;
// }
chooseGroup(intersects);
initAnimate();
controls && controls.update(); //
renderer.render(scene, camera); //
requestAnimationFrame(render); // render
}
</script>
<style scoped>
.loading {
position: fixed;
left: 0;
top: 50vh;
right: 0;
}
.main-container {
width: 100vw;
height: 100vh;
overflow: hidden;
}
.fps-container {
position: fixed;
color: #fff;
top: 0;
left: 0;
}
</style>

34
src/views/three/index.vue Normal file
View File

@ -0,0 +1,34 @@
<template>
<div><ThreeBase></ThreeBase></div>
<!-- <a-drawer v-model:open="drawer" placement="right" :mask="false">
<RobotMenu @sliderInput="sliderInput" @switchChange="switchChange" />
</a-drawer>
<div class="btn" v-show="!drawer">
<a-button type="primary" circle size="large" @click="drawerSwitch" />
</div>
<Robot3d ref="Robot3dRef" /> -->
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ThreeBase from './base/index.vue'
import Robot3d from './Robot3d/index.vue';
import RobotMenu from './Menu/index.vue';
const Robot3dRef = ref();
const switchChange = (e) => {
Robot3dRef.value.setControlsEnabled(e);
};
const sliderInput = (e, name, direction) => {
Robot3dRef.value.setRobotRotation(e, name, direction);
};
const drawer = ref(false);
const drawerSwitch = () => {
drawer.value = !drawer.value;
};
defineOptions({
name: 'index',
});
</script>
<style lang="less" scoped></style>