feat: 重做出入库逻辑
This commit is contained in:
parent
210e0d7dd5
commit
8ffd117dcf
|
@ -41,20 +41,27 @@
|
|||
"@ant-design/icons-vue": "~7.0.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@tinymce/tinymce-vue": "^5.1.1",
|
||||
"@types/dat.gui": "^0.7.12",
|
||||
"@types/three": "^0.162.0",
|
||||
"@vueuse/core": "~10.8.0",
|
||||
"ant-design-vue": "~4.1.2",
|
||||
"axios": "~1.6.7",
|
||||
"dat.gui": "^0.7.9",
|
||||
"dayjs": "~1.11.10",
|
||||
"dhtmlx-gantt": "^8.0.6",
|
||||
"echarts": "^5.5.0",
|
||||
"file-saver": "~2.0.5",
|
||||
"gsap": "^3.12.5",
|
||||
"js-file-download": "^0.4.12",
|
||||
"lodash-es": "~4.17.21",
|
||||
"mathjs": "^12.4.1",
|
||||
"mitt": "~3.0.1",
|
||||
"nprogress": "~1.0.0-1",
|
||||
"pinia": "~2.1.7",
|
||||
"qiniu-js": "^3.4.2",
|
||||
"qs": "~6.11.2",
|
||||
"sortablejs": "~1.15.2",
|
||||
"three": "^0.162.0",
|
||||
"tinymce": "^6.8.3",
|
||||
"vue": "~3.4.19",
|
||||
"vue-echarts": "^6.6.9",
|
||||
|
|
127
pnpm-lock.yaml
127
pnpm-lock.yaml
|
@ -14,6 +14,12 @@ dependencies:
|
|||
'@tinymce/tinymce-vue':
|
||||
specifier: ^5.1.1
|
||||
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':
|
||||
specifier: ~10.8.0
|
||||
version: 10.8.0(vue@3.4.19)
|
||||
|
@ -23,21 +29,33 @@ dependencies:
|
|||
axios:
|
||||
specifier: ~1.6.7
|
||||
version: 1.6.7(debug@4.3.4)
|
||||
dat.gui:
|
||||
specifier: ^0.7.9
|
||||
version: 0.7.9
|
||||
dayjs:
|
||||
specifier: ~1.11.10
|
||||
version: 1.11.10
|
||||
dhtmlx-gantt:
|
||||
specifier: ^8.0.6
|
||||
version: 8.0.6
|
||||
echarts:
|
||||
specifier: ^5.5.0
|
||||
version: 5.5.0
|
||||
file-saver:
|
||||
specifier: ~2.0.5
|
||||
version: 2.0.5
|
||||
gsap:
|
||||
specifier: ^3.12.5
|
||||
version: 3.12.5
|
||||
js-file-download:
|
||||
specifier: ^0.4.12
|
||||
version: 0.4.12
|
||||
lodash-es:
|
||||
specifier: ~4.17.21
|
||||
version: 4.17.21
|
||||
mathjs:
|
||||
specifier: ^12.4.1
|
||||
version: 12.4.1
|
||||
mitt:
|
||||
specifier: ~3.0.1
|
||||
version: 3.0.1
|
||||
|
@ -56,6 +74,9 @@ dependencies:
|
|||
sortablejs:
|
||||
specifier: ~1.15.2
|
||||
version: 1.15.2
|
||||
three:
|
||||
specifier: ^0.162.0
|
||||
version: 0.162.0
|
||||
tinymce:
|
||||
specifier: ^6.8.3
|
||||
version: 6.8.3
|
||||
|
@ -1753,6 +1774,13 @@ packages:
|
|||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -2903,10 +2931,18 @@ packages:
|
|||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
dev: true
|
||||
|
||||
/@tweenjs/tween.js@23.1.1:
|
||||
resolution: {integrity: sha512-ZpboH7pCPPeyBWKf8c7TJswtCEQObFo3bOBYalm99NzZarATALYCo5OhbCa/n4RQyJyHfhkdx+hNrdL5ByFYDw==}
|
||||
dev: false
|
||||
|
||||
/@types/cookie@0.6.0:
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
dev: true
|
||||
|
||||
/@types/dat.gui@0.7.12:
|
||||
resolution: {integrity: sha512-el5dYeQZu2r6YW6Ft4rGtjr/dLe/uzXESMoie5UM6/weVShB1V8IRpXtTKrczd4qe7044fTKZS2l8d6EBFOkoA==}
|
||||
dev: false
|
||||
|
||||
/@types/eslint@7.29.0:
|
||||
resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==}
|
||||
dependencies:
|
||||
|
@ -2981,6 +3017,10 @@ packages:
|
|||
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
|
||||
dev: true
|
||||
|
||||
/@types/stats.js@0.17.3:
|
||||
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
|
||||
dev: false
|
||||
|
||||
/@types/statuses@2.0.4:
|
||||
resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==}
|
||||
dev: true
|
||||
|
@ -2991,6 +3031,16 @@ packages:
|
|||
'@types/node': 20.11.16
|
||||
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:
|
||||
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
|
||||
dev: true
|
||||
|
@ -2999,6 +3049,10 @@ packages:
|
|||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
dev: false
|
||||
|
||||
/@types/webxr@0.5.14:
|
||||
resolution: {integrity: sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==}
|
||||
dev: false
|
||||
|
||||
/@types/wrap-ansi@3.0.0:
|
||||
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
|
||||
dev: true
|
||||
|
@ -4698,6 +4752,10 @@ packages:
|
|||
dot-prop: 5.3.0
|
||||
dev: true
|
||||
|
||||
/complex.js@2.1.1:
|
||||
resolution: {integrity: sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==}
|
||||
dev: false
|
||||
|
||||
/component-emitter@1.3.1:
|
||||
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
|
||||
dev: true
|
||||
|
@ -5263,6 +5321,10 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/dat.gui@0.7.9:
|
||||
resolution: {integrity: sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==}
|
||||
dev: false
|
||||
|
||||
/dateformat@3.0.3:
|
||||
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
|
||||
dev: true
|
||||
|
@ -5320,6 +5382,10 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/decimal.js@10.4.3:
|
||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||
dev: false
|
||||
|
||||
/decode-uri-component@0.2.2:
|
||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
@ -5415,6 +5481,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/dhtmlx-gantt@8.0.6:
|
||||
resolution: {integrity: sha512-GrEQ40/vgV1wDWkv/IvjJEM27Z4lDv76XvE5nlvMtFQTqUuo5VnL1XNDv/uFSJVMRnaN9StYaPxP1ebGamDLFg==}
|
||||
dev: false
|
||||
|
||||
/diff@4.0.2:
|
||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
@ -5790,6 +5860,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/escape-latex@1.2.0:
|
||||
resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
|
||||
dev: false
|
||||
|
||||
/escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
@ -6459,6 +6533,10 @@ packages:
|
|||
reusify: 1.0.4
|
||||
dev: true
|
||||
|
||||
/fflate@0.6.10:
|
||||
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
|
||||
dev: false
|
||||
|
||||
/figures@2.0.0:
|
||||
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -6639,6 +6717,10 @@ packages:
|
|||
engines: {node: '>=0.8'}
|
||||
dev: false
|
||||
|
||||
/fraction.js@4.3.4:
|
||||
resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==}
|
||||
dev: false
|
||||
|
||||
/fragment-cache@0.2.1:
|
||||
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -6989,6 +7071,10 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
|
||||
dev: true
|
||||
|
||||
/gsap@3.12.5:
|
||||
resolution: {integrity: sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==}
|
||||
dev: false
|
||||
|
||||
/gzip-size@6.0.0:
|
||||
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -7721,6 +7807,10 @@ packages:
|
|||
'@pkgjs/parseargs': 0.11.0
|
||||
dev: true
|
||||
|
||||
/javascript-natural-sort@0.7.1:
|
||||
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
|
||||
dev: false
|
||||
|
||||
/jiti@1.21.0:
|
||||
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
|
||||
hasBin: true
|
||||
|
@ -8319,6 +8409,22 @@ packages:
|
|||
object-visit: 1.0.1
|
||||
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:
|
||||
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
|
||||
dev: true
|
||||
|
@ -8445,6 +8551,10 @@ packages:
|
|||
resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==}
|
||||
dev: true
|
||||
|
||||
/meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
dev: false
|
||||
|
||||
/micromark@2.11.4:
|
||||
resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
|
||||
dependencies:
|
||||
|
@ -10192,6 +10302,10 @@ packages:
|
|||
compute-scroll-into-view: 1.0.20
|
||||
dev: false
|
||||
|
||||
/seedrandom@3.0.5:
|
||||
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
|
||||
dev: false
|
||||
|
||||
/semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
|
@ -11132,6 +11246,10 @@ packages:
|
|||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
||||
/three@0.162.0:
|
||||
resolution: {integrity: sha512-xfCYj4RnlozReCmUd+XQzj6/5OjDNHBy5nT6rVwrOKGENAvpXe2z1jL+DZYaMu4/9pNsjH/4Os/VvS9IrH7IOQ==}
|
||||
dev: false
|
||||
|
||||
/throttle-debounce@5.0.0:
|
||||
resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==}
|
||||
engines: {node: '>=12.22'}
|
||||
|
@ -11161,6 +11279,10 @@ packages:
|
|||
next-tick: 1.1.0
|
||||
dev: true
|
||||
|
||||
/tiny-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
|
||||
dev: false
|
||||
|
||||
/tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
dev: true
|
||||
|
@ -11398,6 +11520,11 @@ packages:
|
|||
is-typed-array: 1.1.12
|
||||
dev: true
|
||||
|
||||
/typed-function@4.1.1:
|
||||
resolution: {integrity: sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/typedarray-to-buffer@3.1.5:
|
||||
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
|
||||
dependencies:
|
||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -63,7 +63,7 @@ export async function materialsInventoryInfo(
|
|||
options?: RequestOptions,
|
||||
) {
|
||||
const { id: param0, ...queryParams } = params;
|
||||
return request<API.MaterialsInventoryEntity>(`/api/contract/${param0}`, {
|
||||
return request<API.MaterialsInventoryEntity>(`/api/materials-inventory/${param0}`, {
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
...(options || {}),
|
||||
|
|
|
@ -1381,14 +1381,16 @@ declare namespace API {
|
|||
type MaterialsInventoryListParams = {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
field?: string;
|
||||
isCreateInout?: boolean;
|
||||
order?: 'ASC' | 'DESC';
|
||||
_t?: number;
|
||||
};
|
||||
|
||||
type MaterialsInventoryEntity = {
|
||||
/** 项目 */
|
||||
project?: ProjectEntity;
|
||||
project: ProjectEntity;
|
||||
/** 产品 */
|
||||
product: ProductEntity;
|
||||
/** 单价 */
|
||||
|
@ -1580,6 +1582,10 @@ declare namespace API {
|
|||
};
|
||||
// Product
|
||||
type ProductEntity = {
|
||||
/** 产品编号 */
|
||||
productNumber: string;
|
||||
/** 产品规格 */
|
||||
productSpecification: string;
|
||||
/** 产品名称 */
|
||||
name: string;
|
||||
/** 所属公司 */
|
||||
|
@ -1597,6 +1603,8 @@ declare namespace API {
|
|||
updatedAt: string;
|
||||
};
|
||||
type ProductDto = {
|
||||
/** 产品规格 */
|
||||
productSpecification: string;
|
||||
/** 产品名称 */
|
||||
name: string;
|
||||
/** 所属公司 */
|
||||
|
@ -1613,6 +1621,7 @@ declare namespace API {
|
|||
type ProductParams = {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
keyword?:string;
|
||||
company?: string;
|
||||
name?: string;
|
||||
field?: string;
|
||||
|
@ -1625,6 +1634,8 @@ declare namespace API {
|
|||
type ProductUpdateDto = {
|
||||
/** 产品名称 */
|
||||
name?: string;
|
||||
/** 产品规格 */
|
||||
productSpecification: string;
|
||||
/** 所属公司 */
|
||||
companyId?: number;
|
||||
/** 单位 */
|
||||
|
@ -1774,6 +1785,8 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type MaterialsInOutEntity = {
|
||||
/** 入库后的库存Id */
|
||||
inventoryId: number;
|
||||
/** 库存编号 */
|
||||
inventoryNumber: string;
|
||||
/** 公司信息 */
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export const LOGIN_NAME = 'Login';
|
||||
|
||||
export const THREE = 'Three';
|
||||
|
||||
export const REDIRECT_NAME = 'Redirect';
|
||||
|
||||
export const PARENT_LAYOUT_NAME = 'ParentLayout';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { LOGIN_NAME } from '@/router/constant';
|
||||
import { LOGIN_NAME, THREE } from '@/router/constant';
|
||||
|
||||
/**
|
||||
* layout布局之外的路由
|
||||
|
@ -12,5 +12,12 @@ export const LoginRoute: RouteRecordRaw = {
|
|||
title: '登录',
|
||||
},
|
||||
};
|
||||
|
||||
export default [LoginRoute];
|
||||
export const ThreeRoute: RouteRecordRaw = {
|
||||
path: '/three',
|
||||
name: THREE,
|
||||
component: () => import('@/views/three/index.vue'),
|
||||
meta: {
|
||||
title: 'web3d',
|
||||
},
|
||||
};
|
||||
export default [LoginRoute,ThreeRoute];
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import './transition.less';
|
||||
@import './common.less';
|
||||
@import './antdv.override.less';
|
||||
@import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
|
||||
|
||||
#global-loading {
|
||||
display: flex;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import dayjs from 'dayjs';
|
||||
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
|
||||
* @param str
|
||||
|
@ -205,3 +205,31 @@ export const str2tree = (str: string, treeData: DataNode[] = [], separator = ':'
|
|||
}
|
||||
}, 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));
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<!-- <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> -->
|
||||
</div>
|
||||
<a-form layout="horizontal" :model="state.formInline" @submit.prevent="handleSubmit">
|
||||
|
@ -49,14 +49,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons-vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import Api from '@/api/';
|
||||
import { to } from '@/utils/awaitTo';
|
||||
|
||||
import logo1 from '@/assets/images/logowithtext.png';
|
||||
const logo = ref(logo1);
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
captcha: '',
|
||||
|
|
|
@ -10,7 +10,7 @@ export type TableQueryItem = API.MaterialsInOutListParams;
|
|||
export type TableColumnItem = TableColumn<TableListItem>;
|
||||
export const baseColumns: TableColumnItem[] = [
|
||||
{
|
||||
title: '原材料库存编号',
|
||||
title: '出入库单号',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
dataIndex: 'inventoryNumber',
|
||||
|
@ -29,7 +29,7 @@ export const baseColumns: TableColumnItem[] = [
|
|||
return record?.project?.name || '';
|
||||
},
|
||||
formItemProps: {
|
||||
component:'Select',
|
||||
component: 'Select',
|
||||
componentProps: ({ formInstance, schema, formModel }) => ({
|
||||
showSearch: true,
|
||||
filterOption: false,
|
||||
|
|
|
@ -3,6 +3,9 @@ import type { FormSchema } from '@/components/core/schema-form/';
|
|||
import Api from '@/api';
|
||||
import { debounce, isEmpty } from 'lodash-es';
|
||||
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>[] => [
|
||||
{
|
||||
field: 'inOrOut',
|
||||
|
@ -23,14 +26,16 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
},
|
||||
},
|
||||
{
|
||||
field: 'inventoryNumber',
|
||||
field: 'inventoryId',
|
||||
component: 'Select',
|
||||
label: '库存编号',
|
||||
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.Out,
|
||||
label: '产品', //出口用
|
||||
rules: [{ required: true, type: 'number' }],
|
||||
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.Out && !isEdit,
|
||||
colProps: {
|
||||
span: 24,
|
||||
},
|
||||
helpMessage: '出库必须选择入库时的编号',
|
||||
helpMessage:
|
||||
'出库必须选择某个价格的某个产品。因为库中的相同产品的价格,可能会不同(不同批次的产品)。另外在库存管理中会显示不同价格的相同产品。',
|
||||
componentProps: ({ formInstance, schema, formModel }) => ({
|
||||
showSearch: true,
|
||||
filterOption: false,
|
||||
|
@ -49,7 +54,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
options: [] as LabelValueOptions,
|
||||
},
|
||||
};
|
||||
const options = await getInventoryNumberOptions().finally(() => (schema.loading = false));
|
||||
const options = await getInventories().finally(() => (schema.loading = false));
|
||||
newSchema.componentProps.options = options;
|
||||
formInstance?.updateSchema([newSchema]);
|
||||
},
|
||||
|
@ -60,20 +65,27 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
},
|
||||
callback: async ({ formModel }) => {
|
||||
if (formModel.inOrOut === MaterialsInOutEnum.Out) {
|
||||
return getInventoryNumberOptions();
|
||||
return getInventories();
|
||||
} else {
|
||||
return Promise.resolve([] as LabelValueOptions);
|
||||
}
|
||||
},
|
||||
},
|
||||
onSelect: async (inventoryNumber: string) => {
|
||||
const { items = [] } = await Api.materialsInOut.materialsInOutList({
|
||||
inventoryNumber,
|
||||
isCreateOut: true,
|
||||
onSelect: async (id: number) => {
|
||||
const data = await Api.materialsInventory.materialsInventoryInfo({
|
||||
id,
|
||||
});
|
||||
if (!isEmpty(items)) {
|
||||
const { inOrOut, id, ...ext } = items[0] as API.MaterialsInOutEntity;
|
||||
formInstance?.setFieldsValue(ext);
|
||||
if (!isEmpty(data)) {
|
||||
const { project, unitPrice, quantity, product } = data as API.MaterialsInventoryEntity;
|
||||
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) => {
|
||||
|
@ -85,9 +97,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
},
|
||||
};
|
||||
formInstance?.updateSchema([newSchema]);
|
||||
const options = await getInventoryNumberOptions(keyword).finally(
|
||||
() => (schema.loading = false),
|
||||
);
|
||||
const options = await getInventories(keyword).finally(() => (schema.loading = false));
|
||||
newSchema.componentProps.options = options;
|
||||
formInstance?.updateSchema([newSchema]);
|
||||
}, 500),
|
||||
|
@ -97,7 +107,7 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
{
|
||||
field: 'productId',
|
||||
component: 'Select',
|
||||
vIf: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.In || isEdit,
|
||||
vShow: ({ formModel }) => formModel.inOrOut === MaterialsInOutEnum.In || isEdit,
|
||||
label: '产品',
|
||||
colProps: {
|
||||
span: 16,
|
||||
|
@ -152,11 +162,11 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
span: 12,
|
||||
},
|
||||
vIf: ({ formModel }) =>
|
||||
formModel.inOrOut === MaterialsInOutEnum.In || isEdit || formModel.inventoryNumber,
|
||||
formModel.inOrOut === MaterialsInOutEnum.In || isEdit || formModel.inventoryId,
|
||||
rules: [{ required: true, type: 'number' }],
|
||||
helpMessage: '如未找到对应项目,请先去项目管理添加项目。',
|
||||
helpMessage: '如未找到对应项目,请先去项目管理添加项目。出库时选择的项目可以入库时不同',
|
||||
componentProps: ({ formInstance, schema, formModel }) => ({
|
||||
disabled: Object.is(formModel.inOrOut, MaterialsInOutEnum.Out) || isEdit,
|
||||
// disabled: Object.is(formModel.inOrOut, MaterialsInOutEnum.Out) || isEdit,
|
||||
showSearch: true,
|
||||
filterOption: false,
|
||||
fieldNames: {
|
||||
|
@ -179,17 +189,6 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
request: () => {
|
||||
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) => {
|
||||
schema.loading = true;
|
||||
const newSchema = {
|
||||
|
@ -209,17 +208,50 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
label: '时间',
|
||||
field: 'time',
|
||||
component: 'DatePicker',
|
||||
defaultValue: formatToDate(new Date()),
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '数量',
|
||||
helpMessage: ({ formModel }) => {
|
||||
return '出库时,选择产品后显示的数量为实时的库存剩余数量。';
|
||||
},
|
||||
field: 'quantity',
|
||||
component: 'InputNumber',
|
||||
colProps: {
|
||||
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: '单价',
|
||||
|
@ -228,6 +260,22 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
colProps: {
|
||||
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: '金额',
|
||||
|
@ -236,6 +284,22 @@ export const formSchemas = (isEdit?: boolean): FormSchema<API.MaterialsInOutEnti
|
|||
colProps: {
|
||||
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: '经办人',
|
||||
|
@ -273,10 +337,10 @@ export const getProjectOptions = async (keyword?: string): Promise<LabelValueOpt
|
|||
};
|
||||
|
||||
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 (
|
||||
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,
|
||||
})) || []
|
||||
);
|
||||
|
@ -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,
|
||||
})) || []
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,53 +1,51 @@
|
|||
<template>
|
||||
<div v-if="columns?.length">
|
||||
<DynamicTable
|
||||
row-key="id"
|
||||
<DynamicTable row-key="id"
|
||||
header-title="原材料出入库记录"
|
||||
title-tooltip="要创建入库记录,必须创建入库产品,产品所属公司。"
|
||||
:data-request="Api.materialsInOut.materialsInOutList"
|
||||
:columns="columns"
|
||||
bordered
|
||||
:scroll="{ x: 1920 }"
|
||||
size="small"
|
||||
>
|
||||
size="small">
|
||||
<template #toolbar>
|
||||
<a-button
|
||||
type="primary"
|
||||
<a-button type="primary"
|
||||
: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>
|
||||
</DynamicTable>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
<script setup
|
||||
lang="tsx">
|
||||
import { useTable } from '@/components/core/dynamic-table';
|
||||
import {
|
||||
baseColumns,
|
||||
type TableColumnItem,
|
||||
type TableListItem,
|
||||
type TableQueryItem,
|
||||
} from './columns';
|
||||
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 { Button } from 'ant-design-vue';
|
||||
import { formSchemas } from './formSchemas';
|
||||
|
||||
import AttachmentManage from '@/components/business/attachment-manage/index.vue';
|
||||
import AttachmentUpload from '@/components/business/attachment-upload/index.vue';
|
||||
import fileDownload from 'js-file-download';
|
||||
import dayjs from 'dayjs';
|
||||
import { useExportExcelModal, jsonToSheetXlsx } from '@/components/basic/excel';
|
||||
import { MaterialsInOutEnum } from '@/enums/materialsInventoryEnum';
|
||||
import { formatToDate } from '@/utils/dateUtil';
|
||||
defineOptions({
|
||||
name: 'MaterialsInOut',
|
||||
});
|
||||
const [DynamicTable, dynamicTableInstance] = useTable({ formProps: { autoSubmitOnEnter: true } });
|
||||
const [showModal] = useFormModal();
|
||||
|
||||
const exportExcelModal = useExportExcelModal();
|
||||
const [fnModal] = useModal();
|
||||
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) => {
|
||||
isUploadPopupVisiable.value = true;
|
||||
fnModal.show({
|
||||
|
@ -219,4 +272,5 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less"
|
||||
scoped></style>
|
||||
|
|
|
@ -33,6 +33,22 @@ export const baseColumns: TableColumnItem[] = [
|
|||
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: '单位',
|
||||
width: 60,
|
||||
|
@ -57,7 +73,7 @@ export const baseColumns: TableColumnItem[] = [
|
|||
dataIndex: 'quantity',
|
||||
},
|
||||
{
|
||||
title: '库存单价',
|
||||
title: '入库库存单价',
|
||||
hideInSearch: true,
|
||||
width: 80,
|
||||
dataIndex: 'unitPrice',
|
||||
|
|
|
@ -4,10 +4,19 @@ import type { TableColumn } from '@/components/core/dynamic-table';
|
|||
export type TableListItem = API.ProductEntity;
|
||||
export type TableColumnItem = TableColumn<TableListItem>;
|
||||
export const baseColumns: TableColumnItem[] = [
|
||||
{
|
||||
title: '产品编号',
|
||||
dataIndex: 'productNumber',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '产品规格',
|
||||
dataIndex: 'productSpecification',
|
||||
},
|
||||
|
||||
{
|
||||
title: '所属公司',
|
||||
dataIndex: 'company',
|
||||
|
@ -18,7 +27,7 @@ export const baseColumns: TableColumnItem[] = [
|
|||
{
|
||||
title: '单位',
|
||||
dataIndex: 'unit',
|
||||
width:80,
|
||||
width: 80,
|
||||
customRender: ({ record }) => {
|
||||
return record?.unit?.label || '';
|
||||
},
|
||||
|
|
|
@ -14,6 +14,14 @@ export const formSchemas: FormSchema<API.ProductDto>[] = [
|
|||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'productSpecification',
|
||||
component: 'Input',
|
||||
label: '产品规格',
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '单位',
|
||||
component: 'Select',
|
||||
|
|
|
@ -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>
|
|
@ -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" },
|
||||
],
|
||||
};
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
// 创建3D场景对象Scene
|
||||
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'); // 每次绕y轴旋转0.01弧度
|
||||
mesh.rotation.x += 0.01;
|
||||
mesh.rotation.y += 0.01;
|
||||
mesh.rotation.z += 0.01;
|
||||
}
|
||||
if (animates.cameraMove) {
|
||||
angle += 0.01;
|
||||
// 相机y坐标不变,在XOZ平面上做圆周运动
|
||||
camera.position.x = R * Math.cos(angle);
|
||||
camera.position.z = R * Math.sin(angle);
|
||||
// .position改变,重新执行lookAt(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;
|
||||
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
|
||||
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
|
||||
// 如果相机的一些属性发生了变化,需要执行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)
|
||||
// 使zhijia8绕z轴旋转,旋转角度为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>
|
|
@ -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>
|
Loading…
Reference in New Issue