修改商品规格
This commit is contained in:
258
package-lock.json
generated
258
package-lock.json
generated
@ -15,11 +15,13 @@
|
||||
"@vueuse/core": "^12.7.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"@yipai-front-end/lib": "^0.0.27",
|
||||
"axios": "^1.7.9",
|
||||
"css-color-function": "^1.3.3",
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.9.4",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jsonp": "^0.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
@ -27,7 +29,9 @@
|
||||
"vue": "^3.5.13",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-echarts": "^6.7.3",
|
||||
"vue-qqmap": "^1.1.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue3-sku-form": "^1.0.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -2772,6 +2776,19 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.40.2",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
|
||||
@ -3929,6 +3946,12 @@
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@yipai-front-end/lib": {
|
||||
"version": "0.0.27",
|
||||
"resolved": "https://registry.npmmirror.com/@yipai-front-end/lib/-/lib-0.0.27.tgz",
|
||||
"integrity": "sha512-X5llKyS/sImpDOQKkZU/BU2HB1N4mqFdAmAKoogO2I3gTHK65/KwvvfoqF/cLYVvnyfjFDONkPGfGwaUNWfKeg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
|
||||
@ -4036,6 +4059,19 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
|
||||
@ -4455,7 +4491,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
@ -4757,7 +4792,6 @@
|
||||
"version": "3.42.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.42.0.tgz",
|
||||
"integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -7547,6 +7581,29 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonp": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/jsonp/-/jsonp-0.2.1.tgz",
|
||||
"integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==",
|
||||
"dependencies": {
|
||||
"debug": "^2.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonp/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonp/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -7837,6 +7894,19 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@ -8256,7 +8326,6 @@
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@ -8514,12 +8583,13 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
@ -8906,6 +8976,21 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz",
|
||||
@ -9550,7 +9635,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
@ -9569,7 +9653,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
@ -9585,7 +9668,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
@ -9603,7 +9685,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
@ -10539,6 +10620,19 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@ -10622,18 +10716,6 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/to-object-path": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz",
|
||||
@ -11076,18 +11158,6 @@
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/union-value/-/union-value-1.0.1.tgz",
|
||||
@ -11158,18 +11228,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-auto-import/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
|
||||
@ -11186,18 +11244,6 @@
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components": {
|
||||
"version": "28.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz",
|
||||
@ -11274,6 +11320,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@ -11286,18 +11345,6 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unset-value/-/unset-value-1.0.0.tgz",
|
||||
@ -11633,18 +11680,6 @@
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
@ -11818,6 +11853,50 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-jsonp": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vue-jsonp/-/vue-jsonp-2.1.0.tgz",
|
||||
"integrity": "sha512-kezmjaAcMWdieO3tWxniC+82DitYUYjR1e2GsWIKHCTf+zhWUt2nPhN3dnmnAVhDQ+po3BspM7sKjiQaIhijUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-qqmap": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-qqmap/-/vue-qqmap-1.1.1.tgz",
|
||||
"integrity": "sha512-fL58MO31pmXuADRc8eYYPdLTNl7b68pP0YbyR3CLja9D5PeFv7cF4K5DOR2mOb/lCHjRVUUF+Ft5f6KVDnPUog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"qs": "^6.10.1",
|
||||
"typescript": "^4.3.5",
|
||||
"vue": "^3.2.0",
|
||||
"vue-jsonp": "^2.0.0",
|
||||
"vue-qqmap": "^1.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-qqmap/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-qqmap/node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
||||
@ -11848,6 +11927,19 @@
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue3-sku-form": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue3-sku-form/-/vue3-sku-form-1.0.1.tgz",
|
||||
"integrity": "sha512-K54a7pYk6VKVhOpB/aG7syM9s4WXnAx4jdmbu2WUs0CoQDvvZ1TJONeybDyJB61k1Vv9UmGUhuc0PjZO194IEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"element-plus": "^2.3.0",
|
||||
"vue-router": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vuedraggable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"@vueuse/core": "^12.7.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"@yipai-front-end/lib": "^0.0.27",
|
||||
"axios": "^1.7.9",
|
||||
"css-color-function": "^1.3.3",
|
||||
"echarts": "^5.6.0",
|
||||
@ -31,6 +32,7 @@
|
||||
"vue-echarts": "^6.7.3",
|
||||
"vue-qqmap": "^1.1.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue3-sku-form": "^1.0.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -24,8 +24,8 @@ importers:
|
||||
specifier: ^5.1.23
|
||||
version: 5.1.23
|
||||
'@wangeditor/editor-for-vue':
|
||||
specifier: ^5.1.12
|
||||
version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.14(typescript@5.8.3))
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@wangeditor/editor@5.1.23)(vue@3.5.14(typescript@5.8.3))
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.9.0
|
||||
@ -1444,11 +1444,11 @@ packages:
|
||||
slate: ^0.72.0
|
||||
snabbdom: ^3.1.0
|
||||
|
||||
'@wangeditor/editor-for-vue@5.1.12':
|
||||
resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==}
|
||||
'@wangeditor/editor-for-vue@1.0.2':
|
||||
resolution: {integrity: sha512-BOENvAXJVtVXlE2X50AAvjV82YlCUeu5cbeR0cvEQHQjYtiVnJtq7HSoj85r2kTgGouI5OrpJG9BBEjSjUSPyA==}
|
||||
peerDependencies:
|
||||
'@wangeditor/editor': '>=5.1.0'
|
||||
vue: ^3.0.5
|
||||
vue: ^2.6.14
|
||||
|
||||
'@wangeditor/editor@5.1.23':
|
||||
resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==}
|
||||
@ -5345,7 +5345,7 @@ snapshots:
|
||||
slate-history: 0.66.0(slate@0.72.8)
|
||||
snabbdom: 3.6.2
|
||||
|
||||
'@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.14(typescript@5.8.3))':
|
||||
'@wangeditor/editor-for-vue@1.0.2(@wangeditor/editor@5.1.23)(vue@3.5.14(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@wangeditor/editor': 5.1.23
|
||||
vue: 3.5.14(typescript@5.8.3)
|
||||
|
||||
@ -29,3 +29,8 @@ export function apiGoodsDetail(params: any) {
|
||||
export function checkCategory() {
|
||||
return request.post({ url: '/goodsCategory/checkCategory' })
|
||||
}
|
||||
|
||||
// 商品上传图片
|
||||
export function uploadImage(params: any) {
|
||||
return request.post({ url: '/upload/image', params })
|
||||
}
|
||||
|
||||
@ -1,25 +1,9 @@
|
||||
<template>
|
||||
<div class="border border-br flex flex-col" :style="styles">
|
||||
<toolbar
|
||||
class="border-b border-br"
|
||||
:editor="editorRef"
|
||||
:defaultConfig="toolbarConfig"
|
||||
:mode="mode"
|
||||
/>
|
||||
<w-editor
|
||||
class="flex-1 overflow-hidden"
|
||||
v-model="valueHtml"
|
||||
:defaultConfig="editorConfig"
|
||||
:mode="mode"
|
||||
@onCreated="handleCreated"
|
||||
/>
|
||||
<material-picker
|
||||
ref="materialPickerRef"
|
||||
:type="fileType"
|
||||
:limit="-1"
|
||||
hidden-upload
|
||||
@change="selectChange"
|
||||
/>
|
||||
<toolbar class="border-b border-br" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
|
||||
<w-editor class="flex-1 overflow-hidden" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode"
|
||||
@onCreated="handleCreated" />
|
||||
<material-picker ref="materialPickerRef" :type="fileType" :limit="-1" hidden-upload @change="selectChange" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@ -114,27 +98,35 @@ const handleCreated = (editor: any) => {
|
||||
.w-e-full-screen-container {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.w-e-text-container [data-slate-editor] ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.w-e-text-container [data-slate-editor] ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
|
||||
@ -4,66 +4,31 @@
|
||||
<el-page-header :content="$route.meta.title" @back="$router.back()" />
|
||||
</el-card>
|
||||
<el-card class="mt-4 !border-none" shadow="never">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
class="ls-form"
|
||||
:model="formData"
|
||||
label-width="85px"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form ref="formRef" class="ls-form" :model="formData" label-width="85px" :rules="rules">
|
||||
<div class="xl:flex">
|
||||
<div>
|
||||
<el-form-item label="文章标题" prop="title">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.title"
|
||||
placeholder="请输入文章标题"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 3 }"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="formData.title" placeholder="请输入文章标题" type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 3 }" maxlength="64" show-word-limit clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章栏目" prop="cid">
|
||||
<el-select
|
||||
class="w-80"
|
||||
v-model="formData.cid"
|
||||
placeholder="请选择文章栏目"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in optionsData.article_cate"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
<el-select class="w-80" v-model="formData.cid" placeholder="请选择文章栏目" clearable>
|
||||
<el-option v-for="item in optionsData.article_cate" :key="item.id" :label="item.name"
|
||||
:value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章简介" prop="desc">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.desc"
|
||||
placeholder="请输入文章简介"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
:maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="formData.desc" placeholder="请输入文章简介" type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }" :maxlength="200" show-word-limit clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="abstract">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
v-model="formData.abstract"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
<el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6 }"
|
||||
v-model="formData.abstract" maxlength="200" show-word-limit clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章封面" prop="image">
|
||||
|
||||
794
src/views/goods/components/SkuForm.vue
Normal file
794
src/views/goods/components/SkuForm.vue
Normal file
@ -0,0 +1,794 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="sku-form-container" :class="`sku-form-container-${theme}`" v-if="!disabled">
|
||||
<div class="sku-form-section" v-for="(attrItem, attrIndex) in myAttribute" :key="attrIndex">
|
||||
<div class="flex items-center">
|
||||
<div class="sku-form-title">{{ attrItem.name }}</div>
|
||||
<div @click="deleteAttrItemName(attrIndex)" class="mt-1 ml-2 cursor-pointer">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sku-form-tags-box">
|
||||
<el-checkbox-group v-model="checked[attrIndex]" :disabled="disabled" class="checkbox-group">
|
||||
<el-checkbox v-for="(item, index) in attrItem.item" :key="index" :disabled="disabled"
|
||||
:label="item.name" @change="checked => onCheckedChange(attrIndex, index, checked)">
|
||||
<div class="sku-checkbox-content">
|
||||
<img v-if="item.image" :src="item.image" class="sku-option-image" :alt="item.name" />
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<div class="sku-form-add-tags" v-if="attrItem.canAddAttribute">
|
||||
<el-input v-model="inputValues[attrIndex]" placeholder="请输入规格名称" size="small"
|
||||
@keyup.enter="onAddAttribute(attrIndex)">
|
||||
<template #append>
|
||||
<el-button :icon="Plus" @click="onAddAttribute(attrIndex)">添加</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form v-if="myAttribute.length > 0" ref="formRef" :model="form" :rules="rules" class="sku-form-table"
|
||||
:class="disabled ? 'sku-form-table-disabled' : ''">
|
||||
<el-table :data="form.skuData" border style="width: 100%;overflow-x: auto;" :key="form.skuData.length">
|
||||
<el-table-column v-for="(col, colIndex) in emitAttribute" :key="colIndex" :label="col.name"
|
||||
align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="sku-table-cell">
|
||||
<img v-if="getAttributeImage(col.name, row[col.name])"
|
||||
:src="getAttributeImage(col.name, row[col.name])" class="sku-table-image" />
|
||||
<span>{{ row[col.name] }} --{{ row[col.price] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column v-for="(col, colIndex) in structure" :key="colIndex" :label="col.label" align="center">
|
||||
<template #header v-if="col.batch !== false && col.type === 'input' && isBatch">
|
||||
<div class="sku-form-batch">
|
||||
<el-input v-model="batch[col.name]" :placeholder="`批量${col.label}`"
|
||||
@change="onBatchSet(col.name)">
|
||||
<!-- <template #append>
|
||||
<el-button @click="onBatchSet(col.name)">设置</el-button>
|
||||
</template> -->
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ row, $index }">
|
||||
<div v-if="col.type === 'slot'">
|
||||
<slot :name="col.name" :row="row" :index="$index" />
|
||||
</div>
|
||||
<el-form-item v-else :prop="`skuData.${$index}.${col.name}`"
|
||||
:class="`sku-form-${$index}-${col.name}`">
|
||||
<el-tooltip v-if="col.tips" :content="col.tips" placement="top" :hide-after="0">
|
||||
<el-icon class="sku-form-tips">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-input v-model="row[col.name]" size="small" :placeholder="col.placeholder"
|
||||
:disabled="col.disabled" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, toRefs, nextTick } from 'vue'
|
||||
import { Plus, InfoFilled } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 原始规格数据
|
||||
* sourceAttribute: [
|
||||
* { name: '颜色', item: ['黑', '金', '白'] },
|
||||
* { name: '内存', item: ['16G', '32G'] },
|
||||
* { name: '运营商', item: ['电信', '移动', '联通'] }
|
||||
* ]
|
||||
*/
|
||||
sourceAttribute: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
/**
|
||||
* 已使用的规格数据,用于复原数据,支持v-model:attribute修饰符
|
||||
* attribute: [
|
||||
* { name: '颜色', item: ['黑'] },
|
||||
* { name: '运营商', item: ['电信', '移动', '联通'] }
|
||||
* ]
|
||||
*/
|
||||
attribute: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
/**
|
||||
* 用于复原sku数据,支持v-model:sku修饰符
|
||||
* sku: [
|
||||
* { sku: '黑;电信', price: 1, stock: 1 },
|
||||
* { sku: '黑;移动', price: 2, stock: 2 },
|
||||
* { sku: '黑;联通', price: 3, stock: 3 }
|
||||
* ]
|
||||
*/
|
||||
sku: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
/**
|
||||
* 表格结构,注意name字段,用于输出sku数据
|
||||
*/
|
||||
structure: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ name: 'price', type: 'input', label: '价格' },
|
||||
{ name: 'cost_price', type: 'input', label: '成本价' },
|
||||
{ name: 'stock', type: 'input', label: '库存' }
|
||||
]
|
||||
},
|
||||
// sku 字段分隔符
|
||||
separator: {
|
||||
type: String,
|
||||
default: ';'
|
||||
},
|
||||
// 无规格的 sku
|
||||
emptySku: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示 sku 选择栏
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 主题风格
|
||||
theme: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 是否开启异步加载
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可添加属性值
|
||||
canAddAttribute: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:attribute', 'update:sku', 'validate'])
|
||||
|
||||
// 使用toRefs优化props解构,保持响应性
|
||||
const { sourceAttribute, attribute, sku, structure, separator, emptySku, async: isAsync, canAddAttribute } = toRefs(props)
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref(null)
|
||||
const isInit = ref(false)
|
||||
|
||||
// 输入框的值独立管理,避免深层次响应式问题
|
||||
const inputValues = ref([])
|
||||
|
||||
// 数据
|
||||
const form = reactive({
|
||||
skuData: []
|
||||
})
|
||||
|
||||
// 批量设置暂存数据
|
||||
const batch = reactive({})
|
||||
|
||||
// 属性数据(包含选中状态)
|
||||
const myAttribute = ref([])
|
||||
|
||||
// 用于管理checkbox组的选中状态
|
||||
const checked = ref([])
|
||||
|
||||
// 计算规则
|
||||
const rules = computed(() => {
|
||||
const result = {}
|
||||
structure.value.forEach(item => {
|
||||
if (item.required) {
|
||||
const rule = { required: true, message: `请输入${item.label}`, trigger: 'blur' }
|
||||
if (item.validator) {
|
||||
rule.validator = item.validator
|
||||
}
|
||||
result[item.name] = [rule]
|
||||
} else if (item.validator) {
|
||||
result[item.name] = [{ validator: item.validator, trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
// 是否显示批量设置
|
||||
const isBatch = computed(() => {
|
||||
return structure.value.some(item => item.type === 'input' && item.batch !== false)
|
||||
})
|
||||
|
||||
// 仅输出勾选的属性
|
||||
const emitAttribute = computed(() => {
|
||||
return myAttribute.value.map(attr => {
|
||||
// 过滤选中的项目并包含完整的属性信息
|
||||
const selectedItems = attr.item
|
||||
.filter(item => item.checked)
|
||||
.map(item => {
|
||||
// 仅移除checked状态,保留其他所有属性
|
||||
const { checked, ...itemDetail } = item;
|
||||
return itemDetail;
|
||||
});
|
||||
|
||||
return {
|
||||
name: attr.name,
|
||||
item: selectedItems
|
||||
};
|
||||
}).filter(attr => attr.item.length > 0);
|
||||
})
|
||||
|
||||
// 初始化方法
|
||||
const init = () => {
|
||||
nextTick(() => {
|
||||
isInit.value = true
|
||||
// 初始化 myAttribute
|
||||
const newMyAttribute = []
|
||||
// 根据 sourceAttribute 复原 myAttribute 的结构
|
||||
sourceAttribute.value.forEach(v => {
|
||||
const temp = {
|
||||
name: v.name,
|
||||
canAddAttribute: typeof v.canAddAttribute !== 'undefined' ? v.canAddAttribute : canAddAttribute.value,
|
||||
addAttribute: '',
|
||||
item: []
|
||||
}
|
||||
|
||||
// 处理规格项,支持字符串或对象格式
|
||||
v.item.forEach(itemValue => {
|
||||
if (typeof itemValue === 'string') {
|
||||
// 处理字符串类型的规格项(向后兼容)
|
||||
temp.item.push({
|
||||
name: itemValue,
|
||||
checked: false
|
||||
})
|
||||
} else {
|
||||
// 处理对象类型的规格项(新格式)
|
||||
temp.item.push({
|
||||
...itemValue,
|
||||
checked: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
newMyAttribute.push(temp)
|
||||
})
|
||||
|
||||
// 初始化输入值数组
|
||||
inputValues.value = Array(sourceAttribute.value.length).fill('');
|
||||
|
||||
// 根据 attribute 更新 myAttribute,处理已选中的属性
|
||||
attribute.value.forEach(attrVal => {
|
||||
newMyAttribute.forEach(myAttrVal => {
|
||||
if (attrVal.name === myAttrVal.name) {
|
||||
attrVal.item.forEach(attrItem => {
|
||||
const attrName = typeof attrItem === 'string' ? attrItem : attrItem.name;
|
||||
|
||||
// 查找匹配的属性项
|
||||
const existingItem = myAttrVal.item.find(myAttrItem => myAttrItem.name === attrName);
|
||||
|
||||
if (existingItem) {
|
||||
// 如果找到匹配项,标记为选中
|
||||
existingItem.checked = true;
|
||||
} else {
|
||||
// 如果没找到,添加新项
|
||||
myAttrVal.item.push({
|
||||
...(typeof attrItem === 'string' ? { name: attrItem } : attrItem),
|
||||
checked: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
myAttribute.value = newMyAttribute
|
||||
|
||||
// 因为 skuData 是实时监听 myAttribute 变化并自动生成,使用微任务确保已生成
|
||||
nextTick(() => {
|
||||
console.log("sku.value>>>", sku.value);
|
||||
console.log(" form.skuData2>>>", form.skuData);
|
||||
sku.value.forEach(skuItem => {
|
||||
form.skuData.forEach(skuDataItem => {
|
||||
console.log("form.skuDataItem>>>", skuDataItem);
|
||||
if (skuItem.sku === skuDataItem.sku) {
|
||||
structure.value.forEach(structureItem => {
|
||||
skuDataItem[structureItem.name] = skuItem[structureItem.name]
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
isInit.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取属性的图片路径
|
||||
const getAttributeImage = (attrName, attrValue) => {
|
||||
if (!attrName || !attrValue) return null;
|
||||
|
||||
const attrGroup = myAttribute.value.find(attr => attr.name === attrName);
|
||||
if (!attrGroup) return null;
|
||||
|
||||
const attrItem = attrGroup.item.find(item => item.name === attrValue);
|
||||
return attrItem?.image || null;
|
||||
}
|
||||
|
||||
// 初始化属性
|
||||
watch(
|
||||
attribute,
|
||||
() => {
|
||||
if (!isAsync.value) {
|
||||
init()
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
// 监听选中属性的变化
|
||||
watch(myAttribute, () => {
|
||||
if (!isInit.value) {
|
||||
// 更新父组件
|
||||
emit('update:attribute', emitAttribute.value)
|
||||
}
|
||||
// 解决通过 $emit 更新后无法拿到 attribute 最新数据的问题
|
||||
nextTick(() => {
|
||||
if (emitAttribute.value.length !== 0) {
|
||||
combinationAttribute()
|
||||
} else {
|
||||
form.skuData = []
|
||||
const obj = {
|
||||
sku: emptySku.value
|
||||
}
|
||||
structure.value.forEach(v => {
|
||||
if (!(v.type === 'slot' && v.skuProperty === false)) {
|
||||
obj[v.name] = typeof v.defaultValue !== 'undefined' ? v.defaultValue : ''
|
||||
}
|
||||
})
|
||||
console.log('obj=', obj);
|
||||
|
||||
form.skuData.push(obj)
|
||||
}
|
||||
clearValidate()
|
||||
})
|
||||
}, { deep: true })
|
||||
|
||||
// 监听skuData变化
|
||||
watch(() => form.skuData, (newValue, oldValue) => {
|
||||
if (!isInit.value || (newValue.length === 1 && newValue[0].sku === emptySku.value)) {
|
||||
// 如果有老数据,或者 sku 数据为空,则更新父级 sku 数据
|
||||
if (oldValue.length || !sku.value.length) {
|
||||
// 更新父组件
|
||||
const arr = [];
|
||||
newValue.forEach(v1 => {
|
||||
const obj = {
|
||||
sku: v1.sku,
|
||||
skuData: v1.skuData || {} // 完整的SKU数据
|
||||
};
|
||||
|
||||
structure.value.forEach(v2 => {
|
||||
if (!(v2.type === 'slot' && v2.skuProperty === false)) {
|
||||
obj[v2.name] = v1[v2.name] || (typeof v2.defaultValue !== 'undefined' ? v2.defaultValue : '');
|
||||
}
|
||||
});
|
||||
|
||||
arr.push(obj);
|
||||
});
|
||||
|
||||
emit('update:sku', arr);
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 组合属性,生成SKU表格数据
|
||||
const combinationAttribute = (index = 0, dataTemp = []) => {
|
||||
if (index === 0) {
|
||||
for (let i = 0; i < emitAttribute.value[0].item.length; i++) {
|
||||
const attrItem = emitAttribute.value[0].item[i];
|
||||
const attrName = attrItem.name;
|
||||
|
||||
const obj = {
|
||||
sku: attrName,
|
||||
[emitAttribute.value[0].name]: attrName,
|
||||
skuData: { // 存储完整的SKU对象数据
|
||||
[emitAttribute.value[0].name]: attrItem
|
||||
}
|
||||
};
|
||||
|
||||
structure.value.forEach(v => {
|
||||
if (!(v.type === 'slot' && v.skuProperty === false)) {
|
||||
obj[v.name] = typeof v.defaultValue !== 'undefined' ? v.defaultValue : '';
|
||||
}
|
||||
});
|
||||
|
||||
dataTemp.push(obj);
|
||||
}
|
||||
} else {
|
||||
const temp = [];
|
||||
for (let i = 0; i < dataTemp.length; i++) {
|
||||
for (let j = 0; j < emitAttribute.value[index].item.length; j++) {
|
||||
const attrItem = emitAttribute.value[index].item[j];
|
||||
const attrName = attrItem.name;
|
||||
const newItem = JSON.parse(JSON.stringify(dataTemp[i]));
|
||||
|
||||
// 添加新的属性名称
|
||||
newItem[emitAttribute.value[index].name] = attrName;
|
||||
|
||||
// 更新SKU编码
|
||||
newItem['sku'] = [newItem['sku'], attrName].join(separator.value);
|
||||
|
||||
// 更新完整的SKU对象数据
|
||||
newItem.skuData = {
|
||||
...newItem.skuData,
|
||||
[emitAttribute.value[index].name]: attrItem
|
||||
};
|
||||
|
||||
temp.push(newItem);
|
||||
}
|
||||
}
|
||||
dataTemp = temp;
|
||||
}
|
||||
|
||||
// if (index !== emitAttribute.value.length - 1) {
|
||||
// combinationAttribute(index + 1, dataTemp);
|
||||
// } else {
|
||||
// if (!isInit.value || isAsync.value) {
|
||||
// // 将原有的 sku 数据和新的 sku 数据比较,相同的 sku 则把原有的 sku 数据覆盖到新的 sku 数据里
|
||||
// for (let i = 0; i < form.skuData.length; i++) {
|
||||
// for (let j = 0; j < dataTemp.length; j++) {
|
||||
// if (form.skuData[i].sku === dataTemp[j].sku) {
|
||||
// // 保留原SKU数据中的属性值
|
||||
// structure.value.forEach(structureItem => {
|
||||
// dataTemp[j][structureItem.name] = form.skuData[i][structureItem.name];
|
||||
// });
|
||||
|
||||
// // 保留原SKU数据中的完整对象信息
|
||||
// dataTemp[j].skuData = {
|
||||
// ...dataTemp[j].skuData,
|
||||
// ...form.skuData[i].skuData
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// form.skuData = dataTemp;
|
||||
// }
|
||||
if (index !== emitAttribute.value.length - 1) {
|
||||
combinationAttribute(index + 1, dataTemp);
|
||||
} else {
|
||||
// 先合并外部传入的 sku 数据
|
||||
if (sku.value && sku.value.length) {
|
||||
const skuMap = new Map();
|
||||
sku.value.forEach(skuItem => {
|
||||
skuMap.set(skuItem.sku, skuItem);
|
||||
});
|
||||
dataTemp.forEach(dataItem => {
|
||||
const matched = skuMap.get(dataItem.sku);
|
||||
if (matched) {
|
||||
structure.value.forEach(structureItem => {
|
||||
dataItem[structureItem.name] = matched[structureItem.name];
|
||||
});
|
||||
// 合并skuData对象
|
||||
dataItem.skuData = {
|
||||
...dataItem.skuData,
|
||||
...matched.skuData
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
form.skuData = dataTemp;
|
||||
}
|
||||
}
|
||||
|
||||
// 查找属性的详细信息
|
||||
const findAttributeDetail = (attrName, itemName) => {
|
||||
const attrGroup = myAttribute.value.find(attr => attr.name === attrName);
|
||||
if (!attrGroup) return { name: itemName };
|
||||
|
||||
const item = attrGroup.item.find(item => item.name === itemName);
|
||||
if (!item) return { name: itemName };
|
||||
|
||||
// 返回完整的属性对象,仅过滤掉checked状态
|
||||
const { checked, ...itemDetail } = item;
|
||||
return itemDetail;
|
||||
}
|
||||
|
||||
// 添加新属性值
|
||||
const onAddAttribute = (index) => {
|
||||
const newValue = inputValues.value[index]?.trim();
|
||||
|
||||
if (!newValue) {
|
||||
ElMessage.warning('请输入规格名称');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查分隔符
|
||||
if (newValue.includes(separator.value)) {
|
||||
ElMessage.warning(`规格里不允许出现「 ${separator.value} 」字符,请检查后重新添加`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
if (myAttribute.value[index].item.some(item => item.name === newValue)) {
|
||||
ElMessage.warning('请勿添加相同规格');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加新属性,并默认选中
|
||||
myAttribute.value[index].item.push({
|
||||
name: newValue,
|
||||
checked: true
|
||||
});
|
||||
|
||||
// 清空输入框
|
||||
inputValues.value[index] = '';
|
||||
}
|
||||
|
||||
// 批量设置
|
||||
const onBatchSet = (field) => {
|
||||
if (batch[field] !== '') {
|
||||
form.skuData.forEach(row => {
|
||||
row[field] = batch[field]
|
||||
})
|
||||
|
||||
batch[field] = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const validate = (callback) => {
|
||||
if (formRef.value) {
|
||||
formRef.value.validate(valid => {
|
||||
callback && callback(valid)
|
||||
})
|
||||
} else {
|
||||
callback && callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义验证
|
||||
const validateFieldByColumns = (columns, callback) => {
|
||||
if (!formRef.value) {
|
||||
callback && callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
const propPaths = []
|
||||
form.skuData.forEach((_, i) => {
|
||||
columns.forEach(col => {
|
||||
propPaths.push(`skuData.${i}.${col}`)
|
||||
})
|
||||
})
|
||||
|
||||
formRef.value.validateField(propPaths, valid => {
|
||||
callback && callback(valid)
|
||||
})
|
||||
}
|
||||
|
||||
// 按行验证
|
||||
const validateFieldByRows = (index, prop, callback) => {
|
||||
if (formRef.value) {
|
||||
formRef.value.validateField([`skuData.${index}.${prop}`], valid => {
|
||||
callback && callback(valid)
|
||||
})
|
||||
} else {
|
||||
callback && callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 清除验证
|
||||
const clearValidate = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.clearValidate()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除规格先
|
||||
const deleteAttrItemName = (attrIndex) => {
|
||||
myAttribute.value.splice(attrIndex, 1)
|
||||
emit('update:attribute', emitAttribute.value)
|
||||
}
|
||||
|
||||
// 添加新属性(带图片)
|
||||
const onAddAttributeWithImage = (index, name, imagePath) => {
|
||||
if (!name || typeof name !== 'string') {
|
||||
ElMessage.warning('请提供有效的规格名称');
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = name.trim();
|
||||
|
||||
if (!newValue) {
|
||||
ElMessage.warning('规格名称不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查分隔符
|
||||
if (newValue.includes(separator.value)) {
|
||||
ElMessage.warning(`规格里不允许出现「 ${separator.value} 」字符,请检查后重新添加`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重复
|
||||
if (myAttribute.value[index].item.some(item => item.name === newValue)) {
|
||||
ElMessage.warning('请勿添加相同规格');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加新属性(带图片),并默认选中
|
||||
myAttribute.value[index].item.push({
|
||||
name: newValue,
|
||||
image: imagePath || '',
|
||||
checked: true
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 监听属性变化,初始化checked数组
|
||||
watch(() => myAttribute.value, () => {
|
||||
// 初始化checked数组
|
||||
checked.value = myAttribute.value.map(attr =>
|
||||
attr.item.filter(item => item.checked).map(item => item.name)
|
||||
)
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
watch(sourceAttribute, () => {
|
||||
if (!isAsync.value) {
|
||||
init()
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// 处理checkbox变化
|
||||
const onCheckedChange = (attrIndex, itemIndex, isChecked) => {
|
||||
// 更新原始item的checked状态
|
||||
myAttribute.value[attrIndex].item[itemIndex].checked = isChecked
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
init,
|
||||
validate,
|
||||
validateFieldByColumns,
|
||||
validateFieldByRows,
|
||||
clearValidate,
|
||||
onAddAttributeWithImage
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sku-form {
|
||||
&-container {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&-1 {
|
||||
.sku-form-section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sku-form-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sku-form-tags-box {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-2 {
|
||||
padding: 10px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
|
||||
.sku-form-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sku-form-title {
|
||||
min-width: 70px;
|
||||
margin-right: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sku-form-tags-box {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.sku-form-add-tags {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-table {
|
||||
:deep(.el-table .cell) {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
.el-table {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-batch {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-tips {
|
||||
margin-right: 5px;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增样式 - 规格选项相关
|
||||
.sku-checkbox-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sku-option-image {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sku-table-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sku-table-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
25
src/views/goods/components/index.js
Normal file
25
src/views/goods/components/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
import SkuForm from './SkuForm.vue'
|
||||
|
||||
// 版本信息
|
||||
const version = '1.0.0'
|
||||
|
||||
// 组件安装函数
|
||||
const install = (app) => {
|
||||
app.component('SkuForm', SkuForm)
|
||||
}
|
||||
|
||||
// 组件库对象
|
||||
const SkuFormLib = {
|
||||
version,
|
||||
install,
|
||||
SkuForm
|
||||
}
|
||||
|
||||
// Vue插件安装函数
|
||||
SkuForm.install = app => {
|
||||
app.component('SkuForm', SkuForm)
|
||||
}
|
||||
|
||||
// 导出方式支持 ES Module、CommonJS 和全局变量
|
||||
export { version, SkuForm }
|
||||
export default SkuFormLib
|
||||
307
src/views/goods/components/sku.vue
Normal file
307
src/views/goods/components/sku.vue
Normal file
@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="销售属性" name="skuAttributes">
|
||||
<div class="mb-[10px] bg-[#fafafa] p-[8px] max-w-[1000px]" v-for="(item, index) in skuAttributes">
|
||||
<div class="flex items-center relative">
|
||||
<!-- <a-popconfirm title="确定删除吗?" @confirm="deleteSkuAttr(index)" placement="right">
|
||||
<a-button class="absolute top-[5px] right-[5px]" type="text">删除</a-button>
|
||||
</a-popconfirm> -->
|
||||
<div class="absolute top-[5px] right-[5px]" @confirm="deleteSkuAttr(index)">删除</div>
|
||||
<div class="w-[60px] text-right">属性名称:</div>
|
||||
<el-input class="ml-5 w-[130px]" v-model="item.title" placeholder="请输入属性名称"></el-input>
|
||||
</div>
|
||||
<div class="flex items-start mt-[10px] overflow-x-auto">
|
||||
<div class="w-[60px] text-right leading-[32px]">属性值:</div>
|
||||
<div class="ml-5 relative sku_item" v-for="(text, cindex) in item.values">
|
||||
<!-- <DeleteIcon class="sku_item_delete absolute top-[-4px] right-[4px] z-50"
|
||||
@click="deleteSkuAttrName(index, cindex)"></DeleteIcon> -->
|
||||
<el-icon class="sku_item_delete absolute top-[-4px] right-[4px] z-50" color="red"
|
||||
@click="deleteSkuAttrName(index, cindex)">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
<div class="flex flex-col items-center mr-[10px]">
|
||||
<el-input placeholder="请输入属性值" class="w-[130px]" v-model="text.attributeValue"></el-input>
|
||||
<div v-if="item.isAddImage"
|
||||
class="relative w-[100px] h-[100px] border-solid border-[#eee] border-[1px] mt-[10px] cursor-pointer flex justify-center items-center bg-white"
|
||||
@click="addSkuAttrImage(index, cindex)">
|
||||
<img v-if="text.thumbnailUrl" class="w-[100%] h-[100%]" :src="text.thumbnailUrl" />
|
||||
<!-- <svgIcon v-else class="w-[40px] h-[40px]" name="add" color="#eee"></svgIcon> -->
|
||||
<el-icon v-else class="w-[40px] h-[40px]" name="add" color="#eee">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button :disabled="item.values.length >= 3" type="text" @click="addSkuAttrName(index)">
|
||||
增加字段
|
||||
</el-button>
|
||||
<!-- <el-button class="ml-[10px]" v-if="isAddImg" @click="toggleSkuImg(index)">上传图片</el-button>
|
||||
<el-button class="ml-[10px]" v-if="item.isAddImage" @click="toggleSkuImg(index)">
|
||||
取消上传
|
||||
</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<el-button type="primary" @click="addSkuAttr">增加销售属性</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="销售规格" name="stockKeepUnits">
|
||||
<el-table :data="stockKeepUnits" class="mt-[10px] max-w-[1000px] w-auto" border>
|
||||
<!-- 销售规格 -->
|
||||
<el-table-column prop="attributeValue" label="销售规格">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.attributeValue }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 售价 -->
|
||||
<el-table-column prop="price" label="*售价">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.price" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 市场价 -->
|
||||
<el-table-column prop="marketPrice" label="*市场价">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.marketPrice" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 库存 -->
|
||||
<el-table-column prop="stock" label="*库存">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.stock" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <el-table class="mt-[10px] max-w-[1000px] w-auto" bordered :data="stockKeepUnits">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<div v-if="column.dataIndex === 'attributeValue'">
|
||||
<span>{{ record.attributeValue }}</span>
|
||||
</div>
|
||||
<div v-else-if="column.dataIndex === 'thumbnailUrl'">
|
||||
<img v-if="record.thumbnailUrl" class="w-[90px] h-[90px]" :src="record.thumbnailUrl" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input class="w-[80px]" v-model:value="record[column.dataIndex]"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-table> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, type Ref, computed } from 'vue'
|
||||
import type { skuType, skuAttrItemType } from './type.d'
|
||||
import type { TableColumnCtx } from 'element-plus'
|
||||
import { deepClone } from '@yipai-front-end/lib'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const skuAttributes: Ref<skuAttrItemType[]> = ref([])
|
||||
const stockKeepUnits: Ref<skuType[]> = ref([])
|
||||
let afterSku: skuType[] = []
|
||||
|
||||
const emit = defineEmits(['update:attribute'])
|
||||
|
||||
|
||||
const skuAttrItem: skuAttrItemType = {
|
||||
title: '',
|
||||
isAddImage: false,
|
||||
values: [{ thumbnailUrl: '', attributeValue: '' }],
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataIndex: 'attributeValue',
|
||||
title: '销售规格',
|
||||
},
|
||||
{
|
||||
dataIndex: 'thumbnailUrl',
|
||||
title: '图片',
|
||||
},
|
||||
{
|
||||
dataIndex: 'price',
|
||||
title: '*售价',
|
||||
},
|
||||
{
|
||||
dataIndex: 'marketPrice',
|
||||
title: '*市场价',
|
||||
},
|
||||
{
|
||||
dataIndex: 'stock',
|
||||
title: '*库存',
|
||||
},
|
||||
{
|
||||
dataIndex: 'specificationBarCode',
|
||||
title: '商品条码',
|
||||
},
|
||||
]
|
||||
|
||||
// 是否可以增加图片
|
||||
const isAddImg = computed(() => {
|
||||
return skuAttributes.value.findIndex((e) => e.isAddImage == true) == -1 ? true : false
|
||||
})
|
||||
|
||||
// 监听sku本身的变化,并将当前sku进行备份
|
||||
watch(
|
||||
() => stockKeepUnits.value,
|
||||
(value) => {
|
||||
afterSku = deepClone(value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听销售属性的变化,并构建sku
|
||||
watch(
|
||||
() => skuAttributes.value,
|
||||
(value) => {
|
||||
if (value.length) {
|
||||
generateSku(deepClone(value))
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/**
|
||||
* 更新销售属性构建sku
|
||||
* @param skuAttribute
|
||||
*/
|
||||
function generateSku(skuAttribute: skuAttrItemType[]) {
|
||||
let attrValue: any[] = []
|
||||
skuAttribute.map((item) => {
|
||||
attrValue.push(item.values)
|
||||
})
|
||||
let skus: any[] = []
|
||||
if (attrValue.length === 0) {
|
||||
stockKeepUnits.value = []
|
||||
return
|
||||
}
|
||||
|
||||
skus = attrValue.reduce((col: any[], set) => {
|
||||
let res: any[] = []
|
||||
col.forEach((c) => {
|
||||
set.forEach((s) => {
|
||||
let t = c.attributeValue + ',' + s.attributeValue
|
||||
res.push({ attributeValue: t, thumbnailUrl: c.thumbnailUrl || s.thumbnailUrl || '' })
|
||||
})
|
||||
})
|
||||
return res
|
||||
})
|
||||
// 增加,回显相关字段
|
||||
skus.map((e: skuType) => {
|
||||
// 寻找销售规格一致的副本数据
|
||||
let old = afterSku.find((item) => item.attributeValue == e.attributeValue)
|
||||
console.log('单项', e)
|
||||
e.id = old == null ? '' : old.id
|
||||
e.price = old == null ? '' : old.price
|
||||
e.marketPrice = old == null ? '' : old.marketPrice
|
||||
e.stock = old == null ? '' : old.stock
|
||||
e.specificationBarCode = old == null ? '' : old.specificationBarCode
|
||||
return e
|
||||
})
|
||||
console.log(skus, 'skus')
|
||||
stockKeepUnits.value = skus
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除销售属性
|
||||
* @param index
|
||||
*/
|
||||
function deleteSkuAttr(index) {
|
||||
skuAttributes.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除销售属性字段
|
||||
* @param index
|
||||
* @param cindex
|
||||
*/
|
||||
function deleteSkuAttrName(index: number, cindex: number) {
|
||||
skuAttributes.value[index].values.splice(cindex, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加sku属性图片
|
||||
* @param index
|
||||
* @param cindex
|
||||
*/
|
||||
async function addSkuAttrImage(index: number, cindex: number) {
|
||||
let res = await chooseToFile()
|
||||
console.log(res)
|
||||
// 生产环境此处应该是上传到服务端,获取线上url
|
||||
// 此处写法仅限测试,上传大图片可能造成卡顿
|
||||
_blobToDataUrl(res[0], (res) => {
|
||||
skuAttributes.value[index].values[cindex].thumbnailUrl = res
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加销售属性字段
|
||||
* @param index
|
||||
*/
|
||||
function addSkuAttrName(index: number) {
|
||||
skuAttributes.value[index].values.push({ attributeValue: '', thumbnailUrl: '' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换首个sku是否上传图片的状态
|
||||
*/
|
||||
function toggleSkuImg(index) {
|
||||
let { isAddImage } = skuAttributes.value[index]
|
||||
if (isAddImage) {
|
||||
skuAttributes.value[index].values.map((e) => {
|
||||
e.thumbnailUrl = ''
|
||||
return e
|
||||
})
|
||||
}
|
||||
skuAttributes.value[index].isAddImage = !isAddImage
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加销售属性
|
||||
*/
|
||||
function addSkuAttr() {
|
||||
skuAttributes.value.push(deepClone(skuAttrItem))
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态更新表格字段
|
||||
* @param index
|
||||
* @param dataIndex
|
||||
*/
|
||||
function changeSkuData(index: number, dataIndex: string, evt: any) {
|
||||
let value = evt.target.value
|
||||
stockKeepUnits.value[index][dataIndex] = value
|
||||
}
|
||||
|
||||
function save() {
|
||||
message.success('请查看控制台输出')
|
||||
console.log('销售属性:', skuAttributes.value)
|
||||
console.log('sku:', stockKeepUnits.value)
|
||||
}
|
||||
|
||||
function _blobToDataUrl(file, callback) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const url = URL.createObjectURL(file) // 获取临时访问链接
|
||||
callback(url)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
skuAttributes,
|
||||
stockKeepUnits
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sku_item {
|
||||
&:hover {
|
||||
.sku_item_delete {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.sku_item_delete {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
src/views/goods/components/type.d.ts
vendored
Normal file
39
src/views/goods/components/type.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* sku表格字段
|
||||
*/
|
||||
export type skuType = {
|
||||
attributeValue: string
|
||||
id?: string
|
||||
marketPrice: string
|
||||
price: string
|
||||
specificationBarCode: string
|
||||
stock: string
|
||||
thumbnailUrl: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 销售属性类型
|
||||
*/
|
||||
export type skuAttrItemType = {
|
||||
/**
|
||||
* 是否上传图片
|
||||
*/
|
||||
isAddImage: boolean
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 具体数据
|
||||
*/
|
||||
values: {
|
||||
/**
|
||||
* 属性图片
|
||||
* */
|
||||
thumbnailUrl?: string
|
||||
/**
|
||||
* 属性名称
|
||||
*/
|
||||
attributeValue: string
|
||||
}[]
|
||||
}
|
||||
@ -15,26 +15,26 @@
|
||||
|
||||
<el-form-item label="商品分类" prop="first_category_id" required>
|
||||
<div class="flex w-full">
|
||||
<div class="w-4/12">
|
||||
<div class="w-4/12 mr-1">
|
||||
<el-select v-model="formData.first_category_id" placeholder="请选择分类"
|
||||
@change="selectFirstCategory">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in firstCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in firstCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="w-4/12">
|
||||
<div class="w-4/12 mr-1">
|
||||
<el-select v-model="formData.second_category_id" placeholder="请选择分类"
|
||||
@change="selectSecondCategory">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in secondCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in secondCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="w-4/12">
|
||||
<el-select v-model="formData.third_category_id" placeholder="请选择分类">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in thirdCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in thirdCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
@ -47,18 +47,17 @@
|
||||
<el-form-item label="商品主图" prop="image" required>
|
||||
<material-picker v-model="formData.image" :limit="1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品轮播图" prop="goods_image" required>
|
||||
<material-picker v-model="formData.goods_image" :limit="8" />
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="价格库存">
|
||||
<el-radio-group v-model="goodsSpec">
|
||||
<el-radio-group v-model="formData.spec_type">
|
||||
<el-radio :value="1">统一规格</el-radio>
|
||||
<el-radio :value="2">多规格</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- 单规格 -->
|
||||
<div v-if="goodsSpec === 1">
|
||||
<div v-if="formData.spec_type === 1">
|
||||
<el-table :data="[{}]" style="width: 100%">
|
||||
<el-table-column label="价格(元)" prop="one_price">
|
||||
<template #default="scope">
|
||||
@ -77,188 +76,113 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 单规格 -->
|
||||
<div v-if="goodsSpec === 2">
|
||||
<div>
|
||||
<el-form-item label="规格项" prop="code">
|
||||
<el-input v-model="formData.code" clearable placeholder="请填写规格名" />
|
||||
<div>
|
||||
<el-input v-model="specValue" clearable />
|
||||
</div>
|
||||
<div @click="addSpecValue" style="cursor: pointer;"> + 添加规格值</div>
|
||||
<!-- 多规格 -->
|
||||
<div v-if="formData.spec_type === 2">
|
||||
<div v-for="(spec, specIdx) in specs" :key="specIdx" class="mb-2">
|
||||
<el-form-item :label="'规格名'">
|
||||
<el-input v-model="spec.name" placeholder="如口味/尺寸" style="width: 100px;"
|
||||
@input="onSpecNameChange" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="'规格值'" style="flex:1;">
|
||||
<el-input v-model="spec.valuesStr" type="textarea" :rows="2" placeholder="每行一个规格值"
|
||||
@change="onSpecValueChange(specIdx)" />
|
||||
</el-form-item>
|
||||
<div class="flex justify-end">
|
||||
<el-button type="danger" @click="removeSpec(specIdx)">删除</el-button>
|
||||
</div>
|
||||
<el-button type="primary">添加规格项目</el-button>
|
||||
</div>
|
||||
<el-button type="primary" @click="addSpec" :disabled="specs.length >= 3"
|
||||
class="mb-2">添加规格项</el-button>
|
||||
|
||||
<!-- <el-table :data="[{}]" style="width: 100%">
|
||||
<el-table-column label="*价格(元)" prop="one_price">
|
||||
<el-table :data="skuTable" style="width: 100%; margin-top: 10px;">
|
||||
<el-table-column v-for="(spec, idx) in specs" :key="idx"
|
||||
:label="spec.name || `规格${idx + 1}`" :prop="'spec' + idx" />
|
||||
<el-table-column label="价格(元)">
|
||||
<template #default="scope">
|
||||
<el-input v-model="formData.one_price" clearable placeholder="请输入价格(元)" />
|
||||
<el-input v-model="scope.row.price" placeholder="价格" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="*成本价(元)" prop="one_cost_price">
|
||||
<el-table-column label="成本价(元)">
|
||||
<template #default="scope">
|
||||
<el-input v-model="formData.one_cost_price" clearable placeholder="请输入成本价(元)" />
|
||||
<el-input v-model="scope.row.cost_price" placeholder="成本价" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="*请输入库存" prop="one_stock">
|
||||
<el-table-column label="库存">
|
||||
<template #default="scope">
|
||||
<el-input v-model="formData.one_stock" clearable placeholder="请输入库存" />
|
||||
<el-input v-model="scope.row.stock" placeholder="库存" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table> -->
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商品详情"></el-tab-pane>
|
||||
<el-tab-pane label="销售设置"></el-tab-pane>
|
||||
<el-tab-pane label="商品详情">
|
||||
<editor v-model="formData.content" :height="667" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="销售设置">
|
||||
<el-form-item label="虚拟销量" prop="virtual_sales_sum">
|
||||
<el-input v-model="formData.virtual_sales_sum" placeholder="请输入虚拟销量" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="虚拟浏览量" prop="virtual_click">
|
||||
<el-input v-model="formData.virtual_click" placeholder="请输入虚拟浏览量" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="库存显示" prop="is_show_stock">
|
||||
<el-radio-group v-model="formData.is_show_stock">
|
||||
<el-radio :value="1">显示</el-radio>
|
||||
<el-radio :value="0">不显示</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="库存预警" prop="stock_warn">
|
||||
<el-input v-model="formData.stock_warn" placeholder="请输入库存预警" />
|
||||
<span>设置最低库存预警值,当库存低于预警值时会出现在库存预警商品列表页,0为不预警。</span>
|
||||
</el-form-item> -->
|
||||
|
||||
|
||||
<!-- <el-form-item label="配送方式" prop="goods_image" required>
|
||||
<el-checkbox v-model="formData.is_express" label="快递" size="large" />
|
||||
<el-checkbox v-model="formData.is_selffetch" label="门店自提" size="large" />
|
||||
</el-form-item> -->
|
||||
|
||||
<!-- <el-form-item label="积分抵扣" prop="is_integral">
|
||||
<el-radio-group v-model="formData.is_integral">
|
||||
<el-radio value="1">允许积分抵扣</el-radio>
|
||||
<el-radio value="0">不能使用积分抵扣</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="赠送积分" prop="give_integral_type">
|
||||
<el-radio-group v-model="formData.give_integral_type">
|
||||
<el-radio :value="1">
|
||||
赠送固定积分
|
||||
<el-input v-model="formData.give_integral_num" style="width: 120px"
|
||||
placeholder="请输入赠送积分" />
|
||||
</el-radio>
|
||||
<el-radio :value="2" class="mt-1">
|
||||
按比例赠送积分
|
||||
<el-input v-model="formData.give_integral_ratio" style="width: 120px"
|
||||
placeholder="请输入赠送积分比例" />
|
||||
<span class="ml-1">%</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="会员价" prop="is_member">
|
||||
<el-radio-group v-model="formData.is_member">
|
||||
<el-radio value="0">不参与会员价</el-radio>
|
||||
<el-radio value="1">根据会员等级折扣计算会员价</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="销售状态" prop="status" required>
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">立即上架</el-radio>
|
||||
<el-radio :value="0">放入仓库</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<el-dialog v-model="showSpec" title="输入规格值,多个请换行" width="500">
|
||||
<el-input v-model="specValue" type="textarea" clearable :rows="3" />
|
||||
<div class="mt-4">
|
||||
<el-button type="default" @click="closeSpecPopup">取消</el-button>
|
||||
<el-button type="primary" @click="confirmSpec">确定</el-button>
|
||||
</div>
|
||||
|
||||
</el-dialog>
|
||||
<!-- <el-form ref="formRef" :model="formData" label-width="90px" :rules="formRules">
|
||||
|
||||
|
||||
<el-form-item label="一级分类id" prop="first_category_id">
|
||||
<el-input v-model="formData.first_category_id" clearable placeholder="请输入一级分类id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="二级分类id" prop="second_category_id">
|
||||
<el-input v-model="formData.second_category_id" clearable placeholder="请输入二级分类id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="三级分类id" prop="third_category_id">
|
||||
<el-input v-model="formData.third_category_id" clearable placeholder="请输入三级分类id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌id" prop="brand_id">
|
||||
<el-input v-model="formData.brand_id" clearable placeholder="请输入品牌id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商id" prop="supplier_id">
|
||||
<el-input v-model="formData.supplier_id" clearable placeholder="请输入供应商id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品状态:-1-回收站;0-下架;1-上架" prop="status">
|
||||
<el-input v-model="formData.status" clearable placeholder="请输入商品状态:-1-回收站;0-下架;1-上架" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品主图" prop="image">
|
||||
<el-input v-model="formData.image" clearable placeholder="请输入商品主图" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品视频" prop="video">
|
||||
<el-input v-model="formData.video" clearable placeholder="请输入商品视频" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品自定义海报" prop="poster">
|
||||
<el-input v-model="formData.poster" clearable placeholder="请输入商品自定义海报" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品简介" prop="remark">
|
||||
<el-input v-model="formData.remark" clearable placeholder="请输入商品简介" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品详细描述" prop="content">
|
||||
<el-input v-model="formData.content" clearable placeholder="请输入商品详细描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="formData.sort" clearable placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品销量" prop="sales_sum">
|
||||
<el-input v-model="formData.sales_sum" clearable placeholder="请输入商品销量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="虚拟销量" prop="virtual_sales_sum">
|
||||
<el-input v-model="formData.virtual_sales_sum" clearable placeholder="请输入虚拟销量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品点击量" prop="click_count">
|
||||
<el-input v-model="formData.click_count" clearable placeholder="请输入商品点击量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="虚拟点击量" prop="virtual_click">
|
||||
<el-input v-model="formData.virtual_click" clearable placeholder="请输入虚拟点击量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品规格:1-统一规格;2-多规格;" prop="spec_type">
|
||||
<el-input v-model="formData.spec_type" clearable placeholder="请输入商品规格:1-统一规格;2-多规格;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最高价格" prop="max_price">
|
||||
<el-input v-model="formData.max_price" clearable placeholder="请输入最高价格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低价格" prop="min_price">
|
||||
<el-input v-model="formData.min_price" clearable placeholder="请输入最低价格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价(sku中最高的市场价)" prop="market_price">
|
||||
<el-input v-model="formData.market_price" clearable placeholder="请输入市场价(sku中最高的市场价)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总库存" prop="stock">
|
||||
<el-input v-model="formData.stock" clearable placeholder="请输入总库存" />
|
||||
</el-form-item>
|
||||
<el-form-item label="库存预警" prop="stock_warn">
|
||||
<el-input v-model="formData.stock_warn" clearable placeholder="请输入库存预警" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示库存:1-是;0-否" prop="is_show_stock">
|
||||
<el-input v-model="formData.is_show_stock" clearable placeholder="请输入是否显示库存:1-是;0-否" />
|
||||
</el-form-item>
|
||||
<el-form-item label="运费类型:1-包邮;2-统一运费;3-运费模板" prop="free_shipping_type">
|
||||
<el-input v-model="formData.free_shipping_type" clearable
|
||||
placeholder="请输入运费类型:1-包邮;2-统一运费;3-运费模板" />
|
||||
</el-form-item>
|
||||
<el-form-item label="统一运费金额" prop="free_shipping">
|
||||
<el-input v-model="formData.free_shipping" clearable placeholder="请输入统一运费金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="运费模板" prop="free_shipping_template_id">
|
||||
<el-input v-model="formData.free_shipping_template_id" clearable placeholder="请输入运费模板" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分销佣金:1-开启;0-不开启" prop="is_commission">
|
||||
<el-input v-model="formData.is_commission" clearable placeholder="请输入分销佣金:1-开启;0-不开启" />
|
||||
</el-form-item>
|
||||
<el-form-item label="一级分销比例" prop="first_ratio">
|
||||
<el-input v-model="formData.first_ratio" clearable placeholder="请输入一级分销比例" />
|
||||
</el-form-item>
|
||||
<el-form-item label="二级分销比例" prop="second_ratio">
|
||||
<el-input v-model="formData.second_ratio" clearable placeholder="请输入二级分销比例" />
|
||||
</el-form-item>
|
||||
<el-form-item label="三级分销比例" prop="three_ratio">
|
||||
<el-input v-model="formData.three_ratio" clearable placeholder="请输入三级分销比例" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区域股东分红:1-开启;0-不开启" prop="is_share_bouns">
|
||||
<el-input v-model="formData.is_share_bouns" clearable placeholder="请输入区域股东分红:1-开启;0-不开启" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区域分红比例" prop="region_ratio">
|
||||
<el-input v-model="formData.region_ratio" clearable placeholder="请输入区域分红比例" />
|
||||
</el-form-item>
|
||||
<el-form-item label="股东分红比例" prop="shareholder_ratio">
|
||||
<el-input v-model="formData.shareholder_ratio" clearable placeholder="请输入股东分红比例" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新品推荐:1-是;0-否" prop="is_new">
|
||||
<el-input v-model="formData.is_new" clearable placeholder="请输入新品推荐:1-是;0-否" />
|
||||
</el-form-item>
|
||||
<el-form-item label="好物优选:1-是;0-否" prop="is_best">
|
||||
<el-input v-model="formData.is_best" clearable placeholder="请输入好物优选:1-是;0-否" />
|
||||
</el-form-item>
|
||||
<el-form-item label="猜你喜欢:1-是;0-否" prop="is_like">
|
||||
<el-input v-model="formData.is_like" clearable placeholder="请输入猜你喜欢:1-是;0-否" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开启拼团[0=否, 1=是]" prop="is_team">
|
||||
<el-input v-model="formData.is_team" clearable placeholder="请输入是否开启拼团[0=否, 1=是]" />
|
||||
</el-form-item>
|
||||
<el-form-item label="积分抵扣:1-开启;0-不开启" prop="is_integral">
|
||||
<el-input v-model="formData.is_integral" clearable placeholder="请输入积分抵扣:1-开启;0-不开启" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会员价:1-开启;0-不开启" prop="is_member">
|
||||
<el-input v-model="formData.is_member" clearable placeholder="请输入会员价:1-开启;0-不开启" />
|
||||
</el-form-item>
|
||||
<el-form-item label="赠送积分类型:0-不赠送;1-赠送固定积分;2-按比例赠送积分" prop="give_integral_type">
|
||||
<el-input v-model="formData.give_integral_type" clearable
|
||||
placeholder="请输入赠送积分类型:0-不赠送;1-赠送固定积分;2-按比例赠送积分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="赠送积分;" prop="give_integral">
|
||||
<el-input v-model="formData.give_integral" clearable placeholder="请输入赠送积分;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否删除:1-是;0-否" prop="del">
|
||||
<el-input v-model="formData.del" clearable placeholder="请输入是否删除:1-是;0-否" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开启快递配送:1-是;0-否;" prop="is_express">
|
||||
<el-input v-model="formData.is_express" clearable placeholder="请输入是否开启快递配送:1-是;0-否;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开启上门自提:1-是;0-否;" prop="is_selffetch">
|
||||
<el-input v-model="formData.is_selffetch" clearable placeholder="请输入是否开启上门自提:1-是;0-否;" />
|
||||
</el-form-item>
|
||||
</el-form> -->
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
@ -267,8 +191,10 @@
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import { apiGoodsAdd, apiGoodsEdit, apiGoodsDetail, checkCategory } from '@/api/goods'
|
||||
import { timeFormat } from '@/utils/util'
|
||||
import type { PropType } from 'vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
import type { AnyAaaaRecord } from 'dns'
|
||||
|
||||
defineProps({
|
||||
dictData: {
|
||||
type: Object as PropType<Record<string, any[]>>,
|
||||
@ -279,9 +205,7 @@ const emit = defineEmits(['success', 'close'])
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
const mode = ref('add')
|
||||
const goodsSpec = ref(1)
|
||||
let showSpec = ref(false)
|
||||
let specValue = ref("")
|
||||
const goodsSpec = ref(2)
|
||||
|
||||
// 弹窗标题
|
||||
const popupTitle = computed(() => {
|
||||
@ -300,55 +224,58 @@ const formData = reactive({
|
||||
remark: '',
|
||||
image: '',
|
||||
goods_image: '',
|
||||
|
||||
video: '',
|
||||
poster: '',
|
||||
one_price: '',
|
||||
one_cost_price: '',
|
||||
one_stock: '',
|
||||
one_market_price: '',
|
||||
one_spec_image: '',
|
||||
one_volume: '',
|
||||
one_weight: '',
|
||||
one_bar_code: '',
|
||||
spec_name: '',
|
||||
spec_type: 1,
|
||||
content: '',
|
||||
sales_sum: 0,
|
||||
click_count: 0,
|
||||
virtual_sales_sum: '',
|
||||
virtual_click: '',
|
||||
stock_warn: 0,
|
||||
is_show_stock: 1,
|
||||
is_express: 1,
|
||||
is_integral: 0,
|
||||
give_integral_type: 0,
|
||||
give_integral_num: '',
|
||||
give_integral_ratio: '',
|
||||
status: 0,
|
||||
brand_id: '',
|
||||
supplier_id: '',
|
||||
status: '',
|
||||
|
||||
video: '',
|
||||
one_spec_image: '',
|
||||
poster: '',
|
||||
content: '',
|
||||
sort: '',
|
||||
sales_sum: '',
|
||||
virtual_sales_sum: '',
|
||||
click_count: '',
|
||||
virtual_click: '',
|
||||
spec_type: '',
|
||||
max_price: '',
|
||||
min_price: '',
|
||||
market_price: '',
|
||||
stock: '',
|
||||
stock_warn: '',
|
||||
is_show_stock: '',
|
||||
free_shipping_type: '',
|
||||
free_shipping: '',
|
||||
free_shipping_template_id: '',
|
||||
is_commission: '',
|
||||
first_ratio: '',
|
||||
second_ratio: '',
|
||||
three_ratio: '',
|
||||
is_share_bouns: '',
|
||||
region_ratio: '',
|
||||
shareholder_ratio: '',
|
||||
max_price: 0,
|
||||
min_price: 0,
|
||||
market_price: 0,
|
||||
free_shipping_type: 0,
|
||||
free_shipping: 0,
|
||||
free_shipping_template_id: 0,
|
||||
is_commission: 0,
|
||||
first_ratio: 0,
|
||||
second_ratio: 0,
|
||||
three_ratio: 0,
|
||||
is_share_bouns: 0,
|
||||
region_ratio: 0,
|
||||
shareholder_ratio: 0,
|
||||
is_new: '',
|
||||
is_best: '',
|
||||
is_like: '',
|
||||
is_team: '',
|
||||
is_integral: '',
|
||||
is_member: '',
|
||||
give_integral_type: '',
|
||||
give_integral: '',
|
||||
del: '',
|
||||
is_express: '',
|
||||
is_team: 0,
|
||||
is_member: 0,
|
||||
give_integral: 0,
|
||||
is_selffetch: '',
|
||||
})
|
||||
|
||||
|
||||
// 表单验证
|
||||
// // 表单验证
|
||||
const formRules = reactive<any>({
|
||||
name: [{
|
||||
required: true,
|
||||
@ -360,60 +287,20 @@ const formRules = reactive<any>({
|
||||
message: '请选择商品分类',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
// status: [{
|
||||
// required: true,
|
||||
// message: '请输入商品状态:-1-回收站;0-下架;1-上架',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// image: [{
|
||||
// required: true,
|
||||
// message: '请输入商品主图',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_show_stock: [{
|
||||
// required: true,
|
||||
// message: '请输入是否显示库存:1-是;0-否',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// free_shipping_type: [{
|
||||
// required: true,
|
||||
// message: '请输入运费类型:1-包邮;2-统一运费;3-运费模板',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_commission: [{
|
||||
// required: true,
|
||||
// message: '请输入分销佣金:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_share_bouns: [{
|
||||
// required: true,
|
||||
// message: '请输入区域股东分红:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_team: [{
|
||||
// required: true,
|
||||
// message: '请输入是否开启拼团[0=否, 1=是]',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_integral: [{
|
||||
// required: true,
|
||||
// message: '请输入积分抵扣:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_member: [{
|
||||
// required: true,
|
||||
// message: '请输入会员价:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// give_integral_type: [{
|
||||
// required: true,
|
||||
// message: '请输入赠送积分类型:0-不赠送;1-赠送固定积分;2-按比例赠送积分',
|
||||
// trigger: ['blur']
|
||||
// }]
|
||||
image: [{
|
||||
required: true,
|
||||
message: '请上传商品主图',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
goods_image: [{
|
||||
required: true,
|
||||
message: '请上传商品轮播图',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
category()
|
||||
getCategory()
|
||||
});
|
||||
|
||||
// 获取详情
|
||||
@ -430,15 +317,60 @@ const getDetail = async (row: Record<string, any>) => {
|
||||
const data = await apiGoodsDetail({
|
||||
id: row.id
|
||||
})
|
||||
setFormData(data)
|
||||
|
||||
// 提取 abs_image 并重新赋值给 goods_image
|
||||
if (Array.isArray(data['base']['goods_image'])) {
|
||||
data['base']['goods_image'] = data['base']['goods_image'].map(item => item.abs_image)
|
||||
}
|
||||
|
||||
setFormData(data['base'])
|
||||
// 编辑--重新组成数据
|
||||
const { base, item, spec } = data
|
||||
if (base.spec_type == 1) {
|
||||
// 单规格
|
||||
formData['one_price'] = item[0].price
|
||||
formData['one_cost_price'] = item[0].cost_price
|
||||
formData['one_stock'] = item[0].stock
|
||||
formData['one_volume'] = item[0].volume
|
||||
formData['one_weight'] = item[0].weight
|
||||
formData['one_bar_code'] = item[0].bar_code
|
||||
} else if (base.spec_type == 2) {
|
||||
// 多规格
|
||||
// 1. 渲染规格名和规格值
|
||||
specs.value = spec.map((s: any) => ({
|
||||
name: s.name,
|
||||
valuesStr: s.values.map((v: any) => v.value).join('\n'),
|
||||
values: s.values.map((v: any) => v.value)
|
||||
}))
|
||||
// 2. 渲染规格表格
|
||||
skuTable.value = item.map((it: any) => {
|
||||
const row: any = {
|
||||
price: it.price,
|
||||
cost_price: it.cost_price,
|
||||
stock: it.stock
|
||||
}
|
||||
// 动态添加 spec0, spec1, spec2
|
||||
if (it.spec_value_str) {
|
||||
it.spec_value_str.split(',').forEach((v: string, idx: number) => {
|
||||
row[`spec${idx}`] = v
|
||||
})
|
||||
}
|
||||
return row
|
||||
})
|
||||
|
||||
syncSpecToFormData()
|
||||
}
|
||||
|
||||
// 设置分类
|
||||
selectFirstCategory(data.first_category_id)
|
||||
selectSecondCategory(data.second_category_id)
|
||||
}
|
||||
//
|
||||
// 创建分类列表
|
||||
let goodsCategory = reactive<any[]>([])
|
||||
let firstCategory = reactive<any[]>([])
|
||||
let secondCategory = reactive<any[]>([])
|
||||
let thirdCategory = reactive<any[]>([])
|
||||
const category = async () => {
|
||||
const getCategory = async () => {
|
||||
const res = await checkCategory()
|
||||
goodsCategory = res
|
||||
firstCategory = res.filter((item: { pid: number }) => {
|
||||
@ -477,10 +409,30 @@ const selectSecondCategory = (id: number) => {
|
||||
|
||||
// 提交按钮
|
||||
const handleSubmit = async () => {
|
||||
// await formRef.value?.validate()
|
||||
const data = { ...formData, }
|
||||
console.log("data>>>", data);
|
||||
return false;
|
||||
await formRef.value?.validate()
|
||||
const data = { ...formData }
|
||||
if (data.spec_type == 1 && !data.one_price && !data.one_cost_price && !data.one_stock) {
|
||||
feedback.notifyError('请填写商品规格')
|
||||
return false
|
||||
}
|
||||
|
||||
data.price = skuTable.value.map(item => item.price)
|
||||
data.cost_price = skuTable.value.map(item => item.cost_price)
|
||||
data.stock = skuTable.value.map(item => item.stock)
|
||||
data.spec_value_str = skuTable.value.map(item => {
|
||||
const arr = []
|
||||
if ('spec0' in item) arr.push(item.spec0)
|
||||
if ('spec1' in item) arr.push(item.spec1)
|
||||
if ('spec2' in item) arr.push(item.spec2)
|
||||
return arr.join(',')
|
||||
})
|
||||
|
||||
if (data.spec_type == 2 && !data.price && !data.cost_price && !data.stock && !data.spec_value_str) {
|
||||
feedback.notifyError('请填写商品多规格字段')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
mode.value == 'edit'
|
||||
? await apiGoodsEdit(data)
|
||||
: await apiGoodsAdd(data)
|
||||
@ -499,33 +451,141 @@ const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// 多规格相关
|
||||
// const specs = ref([
|
||||
// { name: '', valuesStr: '', values: [] }
|
||||
// ])
|
||||
const specs = ref([])
|
||||
const skuTable = ref<any[]>([])
|
||||
|
||||
// 添加规格值
|
||||
const addSpecValue = () => {
|
||||
showSpec.value = true
|
||||
const onSpecNameChange = () => {
|
||||
syncSpecToFormData()
|
||||
}
|
||||
|
||||
// 关闭规格值弹窗
|
||||
const closeSpecPopup = () => {
|
||||
specValue.value = ''
|
||||
showSpec.value = false
|
||||
}
|
||||
|
||||
const confirmSpec = () => {
|
||||
if (specValue.value) {
|
||||
let specs = specValue.value.split('\n');
|
||||
for (let i in specs) {
|
||||
specs[i] = specs[i].trim();
|
||||
}
|
||||
|
||||
// 添加规格项(最多3个)
|
||||
const addSpec = () => {
|
||||
if (specs.value.length < 3) {
|
||||
specs.value.push({ name: '', valuesStr: '', values: [] })
|
||||
syncSpecToFormData()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除规格项
|
||||
const removeSpec = (idx: number) => {
|
||||
specs.value.splice(idx, 1)
|
||||
updateSkuTable()
|
||||
syncSpecToFormData()
|
||||
}
|
||||
|
||||
// 规格值变更
|
||||
const onSpecValueChange = (idx: number) => {
|
||||
const spec = specs.value[idx]
|
||||
spec.values = spec.valuesStr
|
||||
.split('\n')
|
||||
.map(v => v.trim())
|
||||
.filter(v => v)
|
||||
updateSkuTable()
|
||||
syncSpecToFormData()
|
||||
}
|
||||
|
||||
// 计算所有规格组合(笛卡尔积)
|
||||
function cartesianProduct(arr: any[][]) {
|
||||
if (arr.length === 0) return []
|
||||
return arr.reduce((a, b) =>
|
||||
a.flatMap(d => b.map(e => [].concat(d, e)))
|
||||
)
|
||||
}
|
||||
|
||||
// 同步规格数据到formData
|
||||
const syncSpecToFormData = () => {
|
||||
formData.spec_name = specs.value.map(item => item.name)
|
||||
formData.spec_values = specs.value.map(item => item.valuesStr.replace(/\n/g, ','))
|
||||
}
|
||||
|
||||
|
||||
// 更新SKU表格
|
||||
const updateSkuTable = () => {
|
||||
const valueArr = specs.value.map(s => s.values)
|
||||
if (valueArr.some(arr => arr.length === 0)) {
|
||||
skuTable.value = []
|
||||
return
|
||||
}
|
||||
|
||||
let result: any[] = []
|
||||
if (valueArr.length === 1) {
|
||||
result = valueArr[0].map(v1 => {
|
||||
const old = skuTable.value.find(row => row.spec0 === v1)
|
||||
return {
|
||||
spec0: v1,
|
||||
price: old ? old.price : '',
|
||||
cost_price: old ? old.cost_price : '',
|
||||
stock: old ? old.stock : ''
|
||||
}
|
||||
})
|
||||
} else if (valueArr.length === 2) {
|
||||
result = cartesianProduct(valueArr).map(([v1, v2]) => {
|
||||
const old = skuTable.value.find(row => row.spec0 === v1 && row.spec1 === v2)
|
||||
return {
|
||||
spec0: v1,
|
||||
spec1: v2,
|
||||
price: old ? old.price : '',
|
||||
cost_price: old ? old.cost_price : '',
|
||||
stock: old ? old.stock : ''
|
||||
}
|
||||
})
|
||||
} else if (valueArr.length === 3) {
|
||||
result = cartesianProduct(valueArr).map(([v1, v2, v3]) => {
|
||||
const old = skuTable.value.find(row => row.spec0 === v1 && row.spec1 === v2 && row.spec2 === v3)
|
||||
return {
|
||||
spec0: v1,
|
||||
spec1: v2,
|
||||
spec2: v3,
|
||||
price: old ? old.price : '',
|
||||
cost_price: old ? old.cost_price : '',
|
||||
stock: old ? old.stock : ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 合并已有 skuTable 的价格等数据
|
||||
result.forEach(row => {
|
||||
// 1. 精确匹配
|
||||
let old = skuTable.value.find(oldRow =>
|
||||
['spec0', 'spec1', 'spec2'].every(key => row[key] === oldRow[key])
|
||||
)
|
||||
// 2. 如果精确找不到,尝试模糊匹配(只要有一项不同也算)
|
||||
if (!old) {
|
||||
old = skuTable.value.find(oldRow => {
|
||||
let sameCount = 0
|
||||
let total = 0
|
||||
for (const key of ['spec0', 'spec1', 'spec2']) {
|
||||
if (row[key] !== undefined && oldRow[key] !== undefined) {
|
||||
total++
|
||||
if (row[key] === oldRow[key]) sameCount++
|
||||
}
|
||||
}
|
||||
// 至少有一项相同(比如只改了一个规格值)
|
||||
return total > 0 && sameCount === total - 1
|
||||
})
|
||||
}
|
||||
if (old) {
|
||||
row.price = old.price
|
||||
row.cost_price = old.cost_price
|
||||
row.stock = old.stock
|
||||
}
|
||||
})
|
||||
|
||||
skuTable.value = result
|
||||
}
|
||||
|
||||
// 监听规格变化自动生成表格
|
||||
watch(specs, updateSkuTable, { deep: true })
|
||||
defineExpose({
|
||||
open,
|
||||
setFormData,
|
||||
getDetail,
|
||||
selectFirstCategory,
|
||||
selectSecondCategory
|
||||
selectSecondCategory,
|
||||
mode: 'default', // 或 'simple'
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -15,26 +15,26 @@
|
||||
|
||||
<el-form-item label="商品分类" prop="first_category_id" required>
|
||||
<div class="flex w-full">
|
||||
<div class="w-4/12">
|
||||
<div class="w-4/12 mr-1">
|
||||
<el-select v-model="formData.first_category_id" placeholder="请选择分类"
|
||||
@change="selectFirstCategory">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in firstCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in firstCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="w-4/12">
|
||||
<div class="w-4/12 mr-1">
|
||||
<el-select v-model="formData.second_category_id" placeholder="请选择分类"
|
||||
@change="selectSecondCategory">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in secondCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in secondCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="w-4/12">
|
||||
<el-select v-model="formData.third_category_id" placeholder="请选择分类">
|
||||
<el-option :label="item.name" :value="item.id"
|
||||
v-for="(item, index) in thirdCategory" />
|
||||
<el-option :label="item.name" :value="item.id" :key="item.id"
|
||||
v-for="item in thirdCategory" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
@ -47,7 +47,6 @@
|
||||
<el-form-item label="商品主图" prop="image" required>
|
||||
<material-picker v-model="formData.image" :limit="1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品轮播图" prop="goods_image" required>
|
||||
<material-picker v-model="formData.goods_image" :limit="8" />
|
||||
</el-form-item>
|
||||
@ -79,9 +78,115 @@
|
||||
</div>
|
||||
<!-- 多规格 -->
|
||||
<div v-if="formData.spec_type === 2">
|
||||
<div v-for="(spec, specIdx) in specs" :key="specIdx" class="mb-2">
|
||||
<el-form-item label="销售属性" name="skuAttributes">
|
||||
<div class="mb-[10px] bg-[#fafafa] p-[8px] max-w-[1000px]"
|
||||
v-for="(item, index) in skuAttributes">
|
||||
<div class="flex items-center relative">
|
||||
<!-- <a-popconfirm title="确定删除吗?" @confirm="deleteSkuAttr(index)" placement="right">
|
||||
<a-button class="absolute top-[5px] right-[5px]" type="text">删除</a-button>
|
||||
</a-popconfirm> -->
|
||||
<div class="absolute top-[5px] right-[5px]" @confirm="deleteSkuAttr(index)">删除
|
||||
</div>
|
||||
<div class="w-[60px] text-right">属性名称:</div>
|
||||
<el-input class="ml-5 w-[130px]" v-model="item.title"
|
||||
placeholder="请输入属性名称"></el-input>
|
||||
</div>
|
||||
<div class="flex items-start mt-[10px] overflow-x-auto">
|
||||
<div class="w-[60px] text-right leading-[32px]">属性值:</div>
|
||||
<div class="ml-5 relative sku_item" v-for="(text, cindex) in item.values">
|
||||
<!-- <DeleteIcon class="sku_item_delete absolute top-[-4px] right-[4px] z-50"
|
||||
@click="deleteSkuAttrName(index, cindex)"></DeleteIcon> -->
|
||||
<el-icon class="sku_item_delete absolute top-[-4px] right-[4px] z-50"
|
||||
color="red" @click="deleteSkuAttrName(index, cindex)">
|
||||
<CircleCloseFilled />
|
||||
</el-icon>
|
||||
<div class="flex flex-col items-center mr-[10px]">
|
||||
<el-input placeholder="请输入属性值" class="w-[130px]"
|
||||
v-model="text.attributeValue"></el-input>
|
||||
<div v-if="item.isAddImage"
|
||||
class="relative w-[100px] h-[100px] border-solid border-[#eee] border-[1px] mt-[10px] cursor-pointer flex justify-center items-center bg-white"
|
||||
@click="addSkuAttrImage(index, cindex)">
|
||||
<img v-if="text.thumbnailUrl" class="w-[100%] h-[100%]"
|
||||
:src="text.thumbnailUrl" />
|
||||
<!-- <svgIcon v-else class="w-[40px] h-[40px]" name="add" color="#eee"></svgIcon> -->
|
||||
<el-icon v-else class="w-[40px] h-[40px]" name="add" color="#eee">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button :disabled="item.values.length >= 3" type="text"
|
||||
@click="addSkuAttrName(index)">
|
||||
增加字段
|
||||
</el-button>
|
||||
<!-- <el-button class="ml-[10px]" v-if="isAddImg" @click="toggleSkuImg(index)">上传图片</el-button>
|
||||
<el-button class="ml-[10px]" v-if="item.isAddImage" @click="toggleSkuImg(index)">
|
||||
取消上传
|
||||
</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<el-button type="primary" @click="addSkuAttr">增加销售属性</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="销售规格" name="stockKeepUnits">
|
||||
{{ stockKeepUnits }}
|
||||
<el-table :data="stockKeepUnits" class="mt-[10px] max-w-[1000px] w-auto" border>
|
||||
<!-- 销售规格 -->
|
||||
<el-table-column prop="attributeValue" label="销售规格">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.attributeValue }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 售价 -->
|
||||
<el-table-column prop="price" label="*售价">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.price" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 市场价 -->
|
||||
<el-table-column prop="marketPrice" label="*市场价">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.marketPrice" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 库存 -->
|
||||
<el-table-column prop="stock" label="*库存">
|
||||
<template #default="{ row }">
|
||||
<el-input class="w-[80px]" v-model="row.stock" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <el-table class="mt-[10px] max-w-[1000px] w-auto" bordered :data="stockKeepUnits">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<div v-if="column.dataIndex === 'attributeValue'">
|
||||
<span>{{ record.attributeValue }}</span>
|
||||
</div>
|
||||
<div v-else-if="column.dataIndex === 'thumbnailUrl'">
|
||||
<img v-if="record.thumbnailUrl" class="w-[90px] h-[90px]" :src="record.thumbnailUrl" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input class="w-[80px]" v-model:value="record[column.dataIndex]"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-table> -->
|
||||
</el-form-item>
|
||||
<!-- <div>
|
||||
<div>
|
||||
<el-input v-model="attributes.name" placeholder="添加规格项: 如口味/尺寸" />
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<el-button type="primary" @click="addSourceAttribute" :disabled="specs.length >= 3"
|
||||
class="mb-2">添加规格项</el-button>
|
||||
<span style="color: #999;margin: 10px;">备注:商品规格只能添加三个</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- <el-button type="primary" @click="addSourceAttribute" :disabled="specs.length >= 3"
|
||||
class="mb-2">添加规格项</el-button> -->
|
||||
|
||||
<!-- <div v-for="(spec, specIdx) in specs" :key="specIdx" class="mb-2">
|
||||
<el-form-item :label="'规格名'">
|
||||
<el-input v-model="spec.name" placeholder="如口味/尺寸" style="width: 100px;" />
|
||||
<el-input v-model="spec.name" placeholder="如口味/尺寸" style="width: 100px;"
|
||||
@change="onSpecNameChange" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="'规格值'" style="flex:1;">
|
||||
<el-input v-model="spec.valuesStr" type="textarea" :rows="2" placeholder="每行一个规格值"
|
||||
@ -112,23 +217,77 @@
|
||||
<el-input v-model="scope.row.stock" placeholder="库存" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-table> -->
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商品详情">
|
||||
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef"
|
||||
:defaultConfig="toolbarConfig" :mode="mode" />
|
||||
<Editor style="height: 500px; overflow-y: hidden;" v-model="formData.content"
|
||||
:defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated" />
|
||||
<editor v-model="formData.content" :height="667" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="销售设置">
|
||||
<el-form-item label="虚拟销量" prop="virtual_sales_sum">
|
||||
<el-input v-model="formData.virtual_sales_sum" placeholder="请输入虚拟销量" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="虚拟浏览量" prop="virtual_click">
|
||||
<el-input v-model="formData.virtual_click" placeholder="请输入虚拟浏览量" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="库存显示" prop="is_show_stock">
|
||||
<el-radio-group v-model="formData.is_show_stock">
|
||||
<el-radio :value="1">显示</el-radio>
|
||||
<el-radio :value="0">不显示</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="库存预警" prop="stock_warn">
|
||||
<el-input v-model="formData.stock_warn" placeholder="请输入库存预警" />
|
||||
<span>设置最低库存预警值,当库存低于预警值时会出现在库存预警商品列表页,0为不预警。</span>
|
||||
</el-form-item> -->
|
||||
|
||||
|
||||
<!-- <el-form-item label="配送方式" prop="goods_image" required>
|
||||
<el-checkbox v-model="formData.is_express" label="快递" size="large" />
|
||||
<el-checkbox v-model="formData.is_selffetch" label="门店自提" size="large" />
|
||||
</el-form-item> -->
|
||||
|
||||
<!-- <el-form-item label="积分抵扣" prop="is_integral">
|
||||
<el-radio-group v-model="formData.is_integral">
|
||||
<el-radio value="1">允许积分抵扣</el-radio>
|
||||
<el-radio value="0">不能使用积分抵扣</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="赠送积分" prop="give_integral_type">
|
||||
<el-radio-group v-model="formData.give_integral_type">
|
||||
<el-radio :value="1">
|
||||
赠送固定积分
|
||||
<el-input v-model="formData.give_integral_num" style="width: 120px"
|
||||
placeholder="请输入赠送积分" />
|
||||
</el-radio>
|
||||
<el-radio :value="2" class="mt-1">
|
||||
按比例赠送积分
|
||||
<el-input v-model="formData.give_integral_ratio" style="width: 120px"
|
||||
placeholder="请输入赠送积分比例" />
|
||||
<span class="ml-1">%</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="会员价" prop="is_member">
|
||||
<el-radio-group v-model="formData.is_member">
|
||||
<el-radio value="0">不参与会员价</el-radio>
|
||||
<el-radio value="1">根据会员等级折扣计算会员价</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="销售状态" prop="status" required>
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">立即上架</el-radio>
|
||||
<el-radio :value="0">放入仓库</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="销售设置"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
|
||||
<!-- <el-dialog v-model="showImagePicker" title="选择图片" width="800px" @close="showImagePicker = false">
|
||||
<material-picker :limit="10" @change="onImageSelected" />
|
||||
</el-dialog> -->
|
||||
<!-- <material-picker ref="materialPickerRef" :limit="10" @change="onImageSelected" style="display: none;" /> -->
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
@ -138,8 +297,9 @@ import type { FormInstance } from 'element-plus'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import { apiGoodsAdd, apiGoodsEdit, apiGoodsDetail, checkCategory } from '@/api/goods'
|
||||
import type { PropType } from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||
import feedback from '@/utils/feedback'
|
||||
import { ref, watch, type Ref, computed } from 'vue'
|
||||
import sku from './components/sku.vue'
|
||||
|
||||
defineProps({
|
||||
dictData: {
|
||||
@ -170,56 +330,58 @@ const formData = reactive({
|
||||
remark: '',
|
||||
image: '',
|
||||
goods_image: '',
|
||||
video: '',
|
||||
poster: '',
|
||||
one_price: '',
|
||||
one_cost_price: '',
|
||||
one_stock: '',
|
||||
one_market_price: '',
|
||||
one_spec_image: '',
|
||||
one_volume: '',
|
||||
one_weight: '',
|
||||
one_bar_code: '',
|
||||
spec_name: '',
|
||||
spec_type: 1,
|
||||
spec_type: 2,
|
||||
content: '',
|
||||
|
||||
sales_sum: 0,
|
||||
click_count: 0,
|
||||
virtual_sales_sum: '',
|
||||
virtual_click: '',
|
||||
stock_warn: 0,
|
||||
is_show_stock: 1,
|
||||
is_express: 1,
|
||||
is_integral: 0,
|
||||
give_integral_type: 0,
|
||||
give_integral_num: '',
|
||||
give_integral_ratio: '',
|
||||
status: 0,
|
||||
brand_id: '',
|
||||
supplier_id: '',
|
||||
status: '',
|
||||
|
||||
video: '',
|
||||
one_spec_image: '',
|
||||
poster: '',
|
||||
sort: '',
|
||||
sales_sum: '',
|
||||
virtual_sales_sum: '',
|
||||
click_count: '',
|
||||
virtual_click: '',
|
||||
max_price: '',
|
||||
min_price: '',
|
||||
market_price: '',
|
||||
stock: '',
|
||||
stock_warn: '',
|
||||
is_show_stock: '',
|
||||
free_shipping_type: '',
|
||||
free_shipping: '',
|
||||
free_shipping_template_id: '',
|
||||
is_commission: '',
|
||||
first_ratio: '',
|
||||
second_ratio: '',
|
||||
three_ratio: '',
|
||||
is_share_bouns: '',
|
||||
region_ratio: '',
|
||||
shareholder_ratio: '',
|
||||
max_price: 0,
|
||||
min_price: 0,
|
||||
market_price: 0,
|
||||
free_shipping_type: 0,
|
||||
free_shipping: 0,
|
||||
free_shipping_template_id: 0,
|
||||
is_commission: 0,
|
||||
first_ratio: 0,
|
||||
second_ratio: 0,
|
||||
three_ratio: 0,
|
||||
is_share_bouns: 0,
|
||||
region_ratio: 0,
|
||||
shareholder_ratio: 0,
|
||||
is_new: '',
|
||||
is_best: '',
|
||||
is_like: '',
|
||||
is_team: '',
|
||||
is_integral: '',
|
||||
is_member: '',
|
||||
give_integral_type: '',
|
||||
give_integral: '',
|
||||
del: '',
|
||||
is_express: '',
|
||||
is_team: 0,
|
||||
is_member: 0,
|
||||
give_integral: 0,
|
||||
is_selffetch: '',
|
||||
})
|
||||
|
||||
|
||||
// 表单验证
|
||||
// // 表单验证
|
||||
const formRules = reactive<any>({
|
||||
name: [{
|
||||
required: true,
|
||||
@ -231,61 +393,186 @@ const formRules = reactive<any>({
|
||||
message: '请选择商品分类',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
// status: [{
|
||||
// required: true,
|
||||
// message: '请输入商品状态:-1-回收站;0-下架;1-上架',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// image: [{
|
||||
// required: true,
|
||||
// message: '请输入商品主图',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_show_stock: [{
|
||||
// required: true,
|
||||
// message: '请输入是否显示库存:1-是;0-否',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// free_shipping_type: [{
|
||||
// required: true,
|
||||
// message: '请输入运费类型:1-包邮;2-统一运费;3-运费模板',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_commission: [{
|
||||
// required: true,
|
||||
// message: '请输入分销佣金:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_share_bouns: [{
|
||||
// required: true,
|
||||
// message: '请输入区域股东分红:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_team: [{
|
||||
// required: true,
|
||||
// message: '请输入是否开启拼团[0=否, 1=是]',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_integral: [{
|
||||
// required: true,
|
||||
// message: '请输入积分抵扣:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// is_member: [{
|
||||
// required: true,
|
||||
// message: '请输入会员价:1-开启;0-不开启',
|
||||
// trigger: ['blur']
|
||||
// }],
|
||||
// give_integral_type: [{
|
||||
// required: true,
|
||||
// message: '请输入赠送积分类型:0-不赠送;1-赠送固定积分;2-按比例赠送积分',
|
||||
// trigger: ['blur']
|
||||
// }]
|
||||
image: [{
|
||||
required: true,
|
||||
message: '请上传商品主图',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
goods_image: [{
|
||||
required: true,
|
||||
message: '请上传商品轮播图',
|
||||
trigger: ['blur']
|
||||
}],
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
category()
|
||||
});
|
||||
/*******************************多规格开始**************************************/
|
||||
import type { skuType, skuAttrItemType } from './components/type.d'
|
||||
import { deepClone } from '@yipai-front-end/lib'
|
||||
|
||||
const skuAttributes: Ref<skuAttrItemType[]> = ref([])
|
||||
const stockKeepUnits: Ref<skuType[]> = ref([])
|
||||
let afterSku: skuType[] = []
|
||||
|
||||
|
||||
const skuAttrItem: skuAttrItemType = {
|
||||
title: '',
|
||||
isAddImage: false,
|
||||
values: [{ thumbnailUrl: '', attributeValue: '' }],
|
||||
}
|
||||
|
||||
// 监听sku本身的变化,并将当前sku进行备份
|
||||
watch(
|
||||
() => stockKeepUnits.value,
|
||||
(value) => {
|
||||
console.log("value>>>", value);
|
||||
afterSku = deepClone(value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听销售属性的变化,并构建sku
|
||||
watch(
|
||||
() => skuAttributes.value,
|
||||
(value) => {
|
||||
if (value.length) {
|
||||
console.log("123>>>", 123);
|
||||
generateSku(deepClone(value))
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
/**
|
||||
* 更新销售属性构建sku
|
||||
* @param skuAttribute
|
||||
*/
|
||||
function generateSku(skuAttribute: skuAttrItemType[]) {
|
||||
let attrValue: any[] = []
|
||||
skuAttribute.map((item) => {
|
||||
attrValue.push(item.values)
|
||||
})
|
||||
let skus: any[] = []
|
||||
if (attrValue.length === 0) {
|
||||
stockKeepUnits.value = []
|
||||
return
|
||||
}
|
||||
|
||||
skus = attrValue.reduce((col: any[], set) => {
|
||||
let res: any[] = []
|
||||
col.forEach((c) => {
|
||||
set.forEach((s) => {
|
||||
let t = c.attributeValue + ',' + s.attributeValue
|
||||
res.push({ attributeValue: t, thumbnailUrl: c.thumbnailUrl || s.thumbnailUrl || '' })
|
||||
})
|
||||
})
|
||||
return res
|
||||
})
|
||||
// 增加,回显相关字段
|
||||
skus.map((e: skuType) => {
|
||||
// 寻找销售规格一致的副本数据
|
||||
let old = afterSku.find((item) => item.attributeValue == e.attributeValue)
|
||||
console.log('old=', old)
|
||||
|
||||
console.log('单项', e)
|
||||
e.id = old == null ? '' : old.id
|
||||
e.price = old == null ? '' : old.price
|
||||
e.marketPrice = old == null ? '' : old.marketPrice
|
||||
e.stock = old == null ? '' : old.stock
|
||||
e.specificationBarCode = old == null ? '' : old.specificationBarCode
|
||||
return e
|
||||
})
|
||||
console.log(skus, 'skus')
|
||||
stockKeepUnits.value = skus
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除销售属性
|
||||
* @param index
|
||||
*/
|
||||
function deleteSkuAttr(index) {
|
||||
skuAttributes.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除销售属性字段
|
||||
* @param index
|
||||
* @param cindex
|
||||
*/
|
||||
function deleteSkuAttrName(index: number, cindex: number) {
|
||||
skuAttributes.value[index].values.splice(cindex, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加sku属性图片
|
||||
* @param index
|
||||
* @param cindex
|
||||
*/
|
||||
async function addSkuAttrImage(index: number, cindex: number) {
|
||||
let res = await chooseToFile()
|
||||
console.log(res)
|
||||
// 生产环境此处应该是上传到服务端,获取线上url
|
||||
// 此处写法仅限测试,上传大图片可能造成卡顿
|
||||
_blobToDataUrl(res[0], (res) => {
|
||||
skuAttributes.value[index].values[cindex].thumbnailUrl = res
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加销售属性字段
|
||||
* @param index
|
||||
*/
|
||||
function addSkuAttrName(index: number) {
|
||||
skuAttributes.value[index].values.push({ attributeValue: '', thumbnailUrl: '' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换首个sku是否上传图片的状态
|
||||
*/
|
||||
function toggleSkuImg(index) {
|
||||
let { isAddImage } = skuAttributes.value[index]
|
||||
if (isAddImage) {
|
||||
skuAttributes.value[index].values.map((e) => {
|
||||
e.thumbnailUrl = ''
|
||||
return e
|
||||
})
|
||||
}
|
||||
skuAttributes.value[index].isAddImage = !isAddImage
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加销售属性
|
||||
*/
|
||||
function addSkuAttr() {
|
||||
skuAttributes.value.push(deepClone(skuAttrItem))
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态更新表格字段
|
||||
* @param index
|
||||
* @param dataIndex
|
||||
*/
|
||||
function changeSkuData(index: number, dataIndex: string, evt: any) {
|
||||
let value = evt.target.value
|
||||
stockKeepUnits.value[index][dataIndex] = value
|
||||
}
|
||||
|
||||
function save() {
|
||||
// message.success('请查看控制台输出')
|
||||
console.log('销售属性:', skuAttributes.value)
|
||||
console.log('sku:', stockKeepUnits.value)
|
||||
}
|
||||
|
||||
function _blobToDataUrl(file, callback) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const url = URL.createObjectURL(file) // 获取临时访问链接
|
||||
callback(url)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
|
||||
/*******************************多规格结束**************************************/
|
||||
|
||||
// 获取详情
|
||||
const setFormData = async (data: Record<any, any>) => {
|
||||
@ -301,15 +588,106 @@ const getDetail = async (row: Record<string, any>) => {
|
||||
const data = await apiGoodsDetail({
|
||||
id: row.id
|
||||
})
|
||||
setFormData(data)
|
||||
|
||||
// 提取 abs_image 并重新赋值给 goods_image
|
||||
if (Array.isArray(data['base']['goods_image'])) {
|
||||
data['base']['goods_image'] = data['base']['goods_image'].map(item => item.abs_image)
|
||||
}
|
||||
|
||||
setFormData(data['base'])
|
||||
// 编辑--重新组成数据
|
||||
const { base, item, spec } = data
|
||||
if (base.spec_type == 1) {
|
||||
// 单规格
|
||||
formData['one_price'] = item[0].price
|
||||
formData['one_cost_price'] = item[0].cost_price
|
||||
formData['one_stock'] = item[0].stock
|
||||
formData['one_volume'] = item[0].volume
|
||||
formData['one_weight'] = item[0].weight
|
||||
formData['one_bar_code'] = item[0].bar_code
|
||||
} else if (base.spec_type == 2) {
|
||||
console.log("456>>>", 456);
|
||||
// 多规格
|
||||
skuAttributes.value = [
|
||||
{
|
||||
isAddImage: false,
|
||||
title: '口味',
|
||||
values: [
|
||||
{
|
||||
attributeValue: '甜',
|
||||
thumbnailUrl: ''
|
||||
},
|
||||
{
|
||||
attributeValue: '辣',
|
||||
thumbnailUrl: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
isAddImage: false,
|
||||
title: '尺寸',
|
||||
values: [
|
||||
{
|
||||
attributeValue: '大',
|
||||
thumbnailUrl: ''
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
stockKeepUnits.value = [
|
||||
{
|
||||
attributeValue: '甜,大',
|
||||
id: '',
|
||||
marketPrice: "2",
|
||||
price: "1",
|
||||
specificationBarCode: '',
|
||||
stock: '3',
|
||||
thumbnailUrl: ''
|
||||
},
|
||||
{
|
||||
attributeValue: '辣,大',
|
||||
id: '',
|
||||
marketPrice: "2",
|
||||
price: "1",
|
||||
specificationBarCode: '',
|
||||
stock: '3',
|
||||
thumbnailUrl: ''
|
||||
}
|
||||
]
|
||||
afterSku = [
|
||||
{
|
||||
attributeValue: '甜,大',
|
||||
id: '',
|
||||
marketPrice: "2",
|
||||
price: "1",
|
||||
specificationBarCode: '',
|
||||
stock: '3',
|
||||
thumbnailUrl: ''
|
||||
},
|
||||
{
|
||||
attributeValue: '辣,大',
|
||||
id: '',
|
||||
marketPrice: "2",
|
||||
price: "1",
|
||||
specificationBarCode: '',
|
||||
stock: '3',
|
||||
thumbnailUrl: ''
|
||||
}
|
||||
]
|
||||
// generateSku(deepClone(skuAttributes.value))
|
||||
}
|
||||
|
||||
// 设置分类
|
||||
selectFirstCategory(data.first_category_id)
|
||||
selectSecondCategory(data.second_category_id)
|
||||
}
|
||||
//
|
||||
// 创建分类列表
|
||||
let goodsCategory = reactive<any[]>([])
|
||||
let firstCategory = reactive<any[]>([])
|
||||
let secondCategory = reactive<any[]>([])
|
||||
let thirdCategory = reactive<any[]>([])
|
||||
const category = async () => {
|
||||
const getCategory = async () => {
|
||||
const res = await checkCategory()
|
||||
goodsCategory = res
|
||||
firstCategory = res.filter((item: { pid: number }) => {
|
||||
@ -348,26 +726,29 @@ const selectSecondCategory = (id: number) => {
|
||||
|
||||
// 提交按钮
|
||||
const handleSubmit = async () => {
|
||||
console.log('销售属性:', skuAttributes.value)
|
||||
console.log('sku:', stockKeepUnits.value)
|
||||
return false
|
||||
// await formRef.value?.validate()
|
||||
const data = { ...formData, }
|
||||
data.price = skuTable.value.map(item => item.price)
|
||||
data.cost_price = skuTable.value.map(item => item.cost_price)
|
||||
data.stock = skuTable.value.map(item => item.stock)
|
||||
data.spec_value_str = skuTable.value.map(item => {
|
||||
const arr = []
|
||||
if ('spec0' in item) arr.push(item.spec0)
|
||||
if ('spec1' in item) arr.push(item.spec1)
|
||||
if ('spec2' in item) arr.push(item.spec2)
|
||||
return arr.join(',')
|
||||
})
|
||||
// data.spec_name = specs.value.map(item => item.name);
|
||||
// data.spec_values = specs.value.map(item => item.valuesStr.replace(/\n/g, ','));
|
||||
console.log("data>>>", data);
|
||||
const data = { ...formData }
|
||||
if (data.spec_type == 1 && !data.one_price && !data.one_cost_price && !data.one_stock) {
|
||||
feedback.notifyError('请填写商品规格')
|
||||
return false
|
||||
}
|
||||
console.log("attribute>>>", attribute);
|
||||
data.spec_name = attribute.spec.map(item => item.name) // 规格名称
|
||||
data.spec_values = attribute.spec.map(item => item.item.map(i => i.name).join(',')) // 规格项名称
|
||||
data.spec_value_str = attribute.sku.map(item => item.sku) // 规格项笛卡尔积
|
||||
data.price = attribute.sku.map(item => item.price)
|
||||
data.cost_price = attribute.sku.map(item => item.cost_price)
|
||||
data.stock = attribute.sku.map(item => item.stock)
|
||||
|
||||
if (data.spec_type == 2 && !data.price && !data.cost_price && !data.stock && !data.spec_value_str) {
|
||||
feedback.notifyError('请完善商品多规格字段')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// console.log("specs>>>", specs);
|
||||
// console.log("specs>>>", skuTable);
|
||||
return false;
|
||||
mode.value == 'edit'
|
||||
? await apiGoodsEdit(data)
|
||||
: await apiGoodsAdd(data)
|
||||
@ -386,151 +767,112 @@ const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// 多规格相关
|
||||
// const specs = ref([
|
||||
// { name: '', valuesStr: '', values: [] }
|
||||
// ])
|
||||
const specs = ref([])
|
||||
const skuTable = ref<any[]>([])
|
||||
|
||||
// 添加规格项(最多3个)
|
||||
const addSpec = () => {
|
||||
if (specs.value.length < 3) {
|
||||
specs.value.push({ name: '', valuesStr: '', values: [] })
|
||||
syncSpecToFormData()
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
getCategory()
|
||||
|
||||
// 删除规格项
|
||||
const removeSpec = (idx: number) => {
|
||||
specs.value.splice(idx, 1)
|
||||
updateSkuTable()
|
||||
syncSpecToFormData()
|
||||
}
|
||||
// console.log('销售属性:', skuAttributes.value)
|
||||
// console.log('sku:', stockKeepUnits.value)
|
||||
|
||||
// 规格值变更
|
||||
const onSpecValueChange = (idx: number) => {
|
||||
const spec = specs.value[idx]
|
||||
spec.values = spec.valuesStr
|
||||
.split('\n')
|
||||
.map(v => v.trim())
|
||||
.filter(v => v)
|
||||
updateSkuTable()
|
||||
syncSpecToFormData()
|
||||
}
|
||||
// skuAttributes.value = [
|
||||
// {
|
||||
// isAddImage: false,
|
||||
// title: '口味',
|
||||
// values: [
|
||||
// {
|
||||
// attributeValue: '甜',
|
||||
// thumbnailUrl: ''
|
||||
// },
|
||||
// {
|
||||
// attributeValue: '辣',
|
||||
// thumbnailUrl: ''
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// isAddImage: false,
|
||||
// title: '尺寸',
|
||||
// values: [
|
||||
// {
|
||||
// attributeValue: '大',
|
||||
// thumbnailUrl: ''
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
|
||||
// 计算所有规格组合(笛卡尔积)
|
||||
function cartesianProduct(arr: any[][]) {
|
||||
if (arr.length === 0) return []
|
||||
return arr.reduce((a, b) =>
|
||||
a.flatMap(d => b.map(e => [].concat(d, e)))
|
||||
)
|
||||
}
|
||||
|
||||
// 同步规格数据到formData
|
||||
const syncSpecToFormData = () => {
|
||||
formData.spec_name = specs.value.map(item => item.name)
|
||||
formData.spec_values = specs.value.map(item => item.valuesStr.replace(/\n/g, ','))
|
||||
}
|
||||
// stockKeepUnits.value = [
|
||||
// {
|
||||
// attributeValue: '甜,大',
|
||||
// id: '',
|
||||
// marketPrice: "2",
|
||||
// price: "1",
|
||||
// specificationBarCode: '',
|
||||
// stock: '3',
|
||||
// thumbnailUrl: ''
|
||||
// },
|
||||
// {
|
||||
// attributeValue: '辣,大',
|
||||
// id: '',
|
||||
// marketPrice: "2",
|
||||
// price: "1",
|
||||
// specificationBarCode: '',
|
||||
// stock: '3',
|
||||
// thumbnailUrl: ''
|
||||
// }
|
||||
// ]
|
||||
// generateSku(deepClone(skuAttributes.value))
|
||||
// skuAttributes = {
|
||||
// 0: {
|
||||
// 'isAddImage': false,
|
||||
// 'title': '口味',
|
||||
// 'values': {
|
||||
// {
|
||||
// 'attributeValue': '甜',
|
||||
// 'thumbnailUrl': ''
|
||||
// },
|
||||
// {
|
||||
// 'attributeValue': '辣',
|
||||
// 'thumbnailUrl': ''
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// 1: {
|
||||
// 'isAddImage': false,
|
||||
// 'title': '尺寸',
|
||||
// 'values': {
|
||||
// {
|
||||
// 'attributeValue': '大',
|
||||
// 'thumbnailUrl': ''
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// 更新SKU表格
|
||||
const updateSkuTable = () => {
|
||||
const valueArr = specs.value.map(s => s.values)
|
||||
if (valueArr.some(arr => arr.length === 0)) {
|
||||
skuTable.value = []
|
||||
return
|
||||
}
|
||||
let result: any[] = []
|
||||
if (valueArr.length === 1) {
|
||||
result = valueArr[0].map(v1 => ({
|
||||
spec0: v1,
|
||||
price: '',
|
||||
cost_price: '',
|
||||
stock: ''
|
||||
}))
|
||||
} else if (valueArr.length === 2) {
|
||||
result = cartesianProduct(valueArr).map(([v1, v2]) => ({
|
||||
spec0: v1,
|
||||
spec1: v2,
|
||||
price: '',
|
||||
cost_price: '',
|
||||
stock: ''
|
||||
}))
|
||||
} else if (valueArr.length === 3) {
|
||||
result = cartesianProduct(valueArr).map(([v1, v2, v3]) => ({
|
||||
spec0: v1,
|
||||
spec1: v2,
|
||||
spec2: v3,
|
||||
price: '',
|
||||
cost_price: '',
|
||||
stock: ''
|
||||
}))
|
||||
}
|
||||
skuTable.value = result
|
||||
}
|
||||
});
|
||||
|
||||
// 编辑器
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
const toolbarConfig = {}
|
||||
const materialPickerRef = ref()
|
||||
|
||||
const showImagePicker = ref(false)
|
||||
let imageInsertFn: ((url: string) => void) | null = null
|
||||
|
||||
const editorConfig = {
|
||||
placeholder: '请输入内容...',
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
// 自定义上传图片
|
||||
customBrowseAndUpload(insertFn: (url: string, alt: string, href: string) => void) {
|
||||
showImagePicker.value = true
|
||||
imageInsertFn = (url: string) => {
|
||||
insertFn(url, '', '')
|
||||
// 直接调用 material-picker 的打开方法
|
||||
materialPickerRef.value?.open(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片选择后回调
|
||||
function onImageSelected(val: string | string[]) {
|
||||
const urls = Array.isArray(val) ? val : [val]
|
||||
if (imageInsertFn && urls.length) {
|
||||
urls.forEach(url => {
|
||||
imageInsertFn!(url)
|
||||
})
|
||||
imageInsertFn = null
|
||||
}
|
||||
showImagePicker.value = false
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor // 记录 editor 实例,重要!
|
||||
}
|
||||
|
||||
// 监听规格变化自动生成表格
|
||||
watch(specs, updateSkuTable, { deep: true })
|
||||
defineExpose({
|
||||
open,
|
||||
setFormData,
|
||||
getDetail,
|
||||
selectFirstCategory,
|
||||
selectSecondCategory,
|
||||
editorRef,
|
||||
mode: 'default', // 或 'simple'
|
||||
toolbarConfig,
|
||||
editorConfig,
|
||||
handleCreated,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sku_item {
|
||||
&:hover {
|
||||
.sku_item_delete {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.sku_item_delete {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
178
src/views/goods/goods.vue
Normal file
178
src/views/goods/goods.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="goods-sku" v-if="goods">
|
||||
<dl v-for="item in goods.specs" :key="item.id">
|
||||
<dt>{{ item.name }}</dt>
|
||||
<dd>
|
||||
<template v-for="val in item.values" :key="val.name">
|
||||
<img v-if="val.picture" :class="{ selected: val.selected, disabled: val.disabled }"
|
||||
@click="changeSelectedStatus(item, val)" :src="val.picture" :title="val.name" />
|
||||
<span v-else :class="{ selected: val.selected, disabled: val.disabled }"
|
||||
@click="changeSelectedStatus(item, val)">{{ val.name }}</span>
|
||||
</template>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import axios from "axios";
|
||||
import powerSet from "./power-set.js";
|
||||
const goods = ref();
|
||||
let pathMap = {};
|
||||
const getGoods = async () => {
|
||||
//1135076无库存规格
|
||||
//1369155859933827074
|
||||
const res = await axios.get(
|
||||
"http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1369155859933827074"
|
||||
);
|
||||
goods.value = res.data.result;
|
||||
pathMap = getPathMap(goods.value);
|
||||
initDosabledStatus(goods.value.specs, pathMap);
|
||||
};
|
||||
onMounted(() => {
|
||||
getGoods();
|
||||
});
|
||||
//切换选中状态
|
||||
const changeSelectedStatus = (item, val) => {
|
||||
if (val.disabled) {
|
||||
return;
|
||||
}
|
||||
//item:同一排对象,val:当前点击项
|
||||
if (val.selected) {
|
||||
val.selected = false;
|
||||
} else {
|
||||
item.values.forEach((val) => (val.selected = false));
|
||||
val.selected = true;
|
||||
}
|
||||
updateDisabledStatus(goods.value.specs, pathMap);
|
||||
//产出sku对象
|
||||
const index = getSelectedValues(goods.value.specs).findIndex(
|
||||
(item) => item === undefined
|
||||
);
|
||||
if (index > -1) {
|
||||
} else {
|
||||
const key = getSelectedValues(goods.value.specs).join("-");
|
||||
const skuIds = pathMap[key];
|
||||
const skuObj = goods.value.skus.find((item) => item.id === skuIds[0]);
|
||||
console.log("skuObj", skuObj);
|
||||
}
|
||||
};
|
||||
//生成有效路径字典对象
|
||||
const getPathMap = (goods) => {
|
||||
const pathMap = {};
|
||||
// 1.根据库存字段得到有效的sku数组
|
||||
const effectiveSkus = goods.skus.filter((sku) => sku.inventory > 0);
|
||||
// 2.根据有效的sku数组使用powerSet算法得到所有子集
|
||||
effectiveSkus.forEach((sku) => {
|
||||
//2.1获取匹配的valueName组成的数组
|
||||
const selectedValArr = sku.specs.map((val) => val.valueName);
|
||||
//2.2使用算法获取子集
|
||||
const valueNamePowerSet = powerSet(selectedValArr);
|
||||
// 3.根据子集生成路径字典对象
|
||||
valueNamePowerSet.forEach((arr) => {
|
||||
//初始化key
|
||||
const key = arr.join("-");
|
||||
//如果已经存在当前key就往数组中直接添加skuId,如果不存在直接做赋值
|
||||
if (pathMap[key]) {
|
||||
pathMap[key].push(sku.id);
|
||||
} else {
|
||||
pathMap[key] = [sku.id];
|
||||
}
|
||||
});
|
||||
});
|
||||
return pathMap;
|
||||
};
|
||||
//初始化禁用状态
|
||||
const initDosabledStatus = (specs, pathMap) => {
|
||||
specs.forEach((spe) => {
|
||||
spe.values.forEach((val) => {
|
||||
if (pathMap[val.name]) {
|
||||
val.disabled = false;
|
||||
} else {
|
||||
val.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
//获取相中项的数组
|
||||
const getSelectedValues = (specs) => {
|
||||
const arr = [];
|
||||
specs.forEach((spe) => {
|
||||
//找到values中selected为true的项,然后把它的name字段添加到对应的位置
|
||||
const selectedVal = spe.values.find((item) => item.selected);
|
||||
arr.push(selectedVal ? selectedVal.name : undefined);
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
//切换时更新禁用状态
|
||||
const updateDisabledStatus = (specs, pathMap) => {
|
||||
specs.forEach((spe, index) => {
|
||||
const selectedValues = getSelectedValues(specs);
|
||||
spe.values.forEach((val) => {
|
||||
selectedValues[index] = val.name;
|
||||
const key = selectedValues.filter((value) => value).join("-");
|
||||
console.log("key", key);
|
||||
if (pathMap[key]) {
|
||||
val.disabled = false;
|
||||
} else {
|
||||
val.disabled = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@mixin sku-state-mixin {
|
||||
border: 1px solid #e4e4e4;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
border-color: #27ba9b;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
border-style: dashed;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-sku {
|
||||
padding-left: 10px;
|
||||
padding-top: 20px;
|
||||
|
||||
dl {
|
||||
display: flex;
|
||||
padding-bottom: 20px;
|
||||
align-items: center;
|
||||
|
||||
dt {
|
||||
width: 50px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
dd {
|
||||
flex: 1;
|
||||
color: #666;
|
||||
|
||||
>img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 4px;
|
||||
@include sku-state-mixin;
|
||||
}
|
||||
|
||||
>span {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
line-height: 28px;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 4px;
|
||||
@include sku-state-mixin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -106,7 +106,7 @@ const handleEdit = async (data: any) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('edit')
|
||||
editRef.value?.setFormData(data)
|
||||
editRef.value?.getDetail(data)
|
||||
}
|
||||
|
||||
// 删除
|
||||
|
||||
28
src/views/goods/power-set.js
Normal file
28
src/views/goods/power-set.js
Normal file
@ -0,0 +1,28 @@
|
||||
export default function bwPowerSet(originalSet) {
|
||||
const subSets = []
|
||||
|
||||
// We will have 2^n possible combinations (where n is a length of original set).
|
||||
// It is because for every element of original set we will decide whether to include
|
||||
// it or not (2 options for each set element).
|
||||
const numberOfCombinations = 2 ** originalSet.length
|
||||
|
||||
// Each number in binary representation in a range from 0 to 2^n does exactly what we need:
|
||||
// it shows by its bits (0 or 1) whether to include related element from the set or not.
|
||||
// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
|
||||
// include only "2" to the current set.
|
||||
for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
|
||||
const subSet = []
|
||||
|
||||
for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
|
||||
// Decide whether we need to include current element into the subset or not.
|
||||
if (combinationIndex & (1 << setElementIndex)) {
|
||||
subSet.push(originalSet[setElementIndex])
|
||||
}
|
||||
}
|
||||
|
||||
// Add current subset to the list of all subsets.
|
||||
subSets.push(subSet)
|
||||
}
|
||||
|
||||
return subSets
|
||||
}
|
||||
240
src/views/goods/sku.vue
Normal file
240
src/views/goods/sku.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 属性配置 -->
|
||||
<el-button @click="addAttribute" type="success" size="mini">添加属性</el-button>
|
||||
<el-table :data="attributes" border style="width: 100%">
|
||||
<!-- <el-table-column prop="attributeName" label="属性名称">
|
||||
<template v-slot="{ row }">
|
||||
<el-input v-model="row.attributeValues" placeholder="用竖线(|)分隔,如:蓝色|白色|灰色"></el-input>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="属性值">
|
||||
<template v-slot="{ row }">
|
||||
<el-input v-model="row.attributeValues" placeholder="用竖线(|)分隔,如:蓝色|白色|灰色"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template v-slot="{ $index }">
|
||||
<el-button size="mini" type="danger" @click="removeAttribute($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
<!-- SKU列表 -->
|
||||
<el-button @click="handleMakeSku" size="mini" style="margin-top: 20px" type="warning">生成SKU</el-button>
|
||||
<el-button @click="batchSetVisible = true" size="mini">批量设置</el-button>
|
||||
<el-table :data="skus" border style="width: 100%;" @cell-change="handleSkuChange">
|
||||
<el-table-column prop="spec_key" label="规格键名" header-align="center"></el-table-column>
|
||||
<el-table-column prop="spec_name" label="规格名称" header-align="center"></el-table-column>
|
||||
<el-table-column prop="store_count" label="库存" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-input size="mini" v-model.number="row.stock" @input="handleSkuChange(row)"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="销售价格" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-input-number v-model="row.price" size="mini" controls-position="right" :precision="2"
|
||||
:step="0.01" :min="0" @change="handleSkuChange(row)"></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cost_price" label="成本价" header-align="center">
|
||||
<template v-slot="{ row }">
|
||||
<el-input-number v-model="row.cost_price" size="mini" controls-position="right" :precision="2"
|
||||
:step="0.01" :min="0" @change="handleSkuChange(row)"></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" header-align="center">
|
||||
<template v-slot="{ $index }">
|
||||
<el-button size="mini" type="danger" @click="removeSku($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 批量设置 -->
|
||||
<!-- <el-button @click="batchSetVisible = true">批量设置</el-button>-->
|
||||
<!-- <el-dialog title="批量设置" :visible.sync="batchSetVisible">
|
||||
<el-form :model="batchForm">
|
||||
<el-form-item label="价格" :label-width="formLabelWidth">
|
||||
<el-input v-model="batchForm.price"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="库存" :label-width="formLabelWidth">
|
||||
<el-input v-model.number="batchForm.store_count"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价" :label-width="formLabelWidth">
|
||||
<el-input v-model="batchForm.cost_price"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="市场价" :label-width="formLabelWidth">
|
||||
<el-input v-model="batchForm.market_price"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="batchSetVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="applyBatchSet">确定</el-button>
|
||||
</span>
|
||||
</el-dialog> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
initialSkus: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attributes: [], // 存储属性及其值
|
||||
skus: [], // 存储生成的SKU列表
|
||||
batchSetVisible: false, // 控制批量设置对话框显示状态
|
||||
batchForm: {
|
||||
price: '',
|
||||
store_count: null,
|
||||
cost_price: null,
|
||||
market_price: null
|
||||
},
|
||||
formLabelWidth: '80px'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
skus: {
|
||||
deep: true,
|
||||
handler(newValue) {
|
||||
this.$emit('sku-updated', newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化时从props中获取已有SKU数据,并解析出属性和SKU列表
|
||||
if (this.initialSkus.length > 0) {
|
||||
this.parseInitialSkus(this.initialSkus)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAttribute() {
|
||||
this.attributes.push({ attributeName: '', attributeValues: '' })
|
||||
},
|
||||
removeAttribute(index) {
|
||||
this.attributes.splice(index, 1)
|
||||
},
|
||||
handleMakeSku() {
|
||||
console.log('handleMakeSku')
|
||||
if (this.attributes.length === 0) {
|
||||
this.$message.error('请先添加属性')
|
||||
return
|
||||
}
|
||||
|
||||
if (this.skus.length > 0) {
|
||||
this.$confirm('已存在SKU,是否覆盖?', '操作提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.generateSkus()
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
} else {
|
||||
this.generateSkus()
|
||||
}
|
||||
},
|
||||
generateSkus() {
|
||||
// 清空现有的SKU列表
|
||||
this.skus = []
|
||||
|
||||
// 获取所有属性值并创建所有可能的组合
|
||||
const combinations = this.createCombinations(this.attributes)
|
||||
|
||||
// 根据组合生成SKUs
|
||||
combinations.forEach(combination => {
|
||||
const specKey = combination.join('_')
|
||||
const specName = combination.join(';')
|
||||
|
||||
this.skus.push({
|
||||
spec_key: specKey,
|
||||
spec_name: specName,
|
||||
store_count: 0,
|
||||
price: 0.00,
|
||||
cost_price: 0.00,
|
||||
market_price: 0.00
|
||||
})
|
||||
})
|
||||
},
|
||||
createCombinations(attributes) {
|
||||
const valueLists = attributes.map(attr => attr.attributeValues.split('|').map(val => val.trim()))
|
||||
const result = []
|
||||
|
||||
function combine(tempArray, index) {
|
||||
if (index === valueLists.length) {
|
||||
result.push(tempArray.slice())
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < valueLists[index].length; i++) {
|
||||
tempArray[index] = valueLists[index][i]
|
||||
combine(tempArray, index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
combine([], 0)
|
||||
return result
|
||||
},
|
||||
removeSku(index) {
|
||||
this.skus.splice(index, 1)
|
||||
},
|
||||
applyBatchSet() {
|
||||
const { price, store_count, cost_price, market_price } = this.batchForm
|
||||
|
||||
// 更新所有SKU对应的字段
|
||||
this.skus.forEach(sku => {
|
||||
if (price !== '') sku.price = parseFloat(price)
|
||||
if (store_count !== null) sku.store_count = store_count
|
||||
if (cost_price !== null) sku.cost_price = cost_price
|
||||
if (market_price !== null) sku.market_price = market_price
|
||||
})
|
||||
|
||||
// 关闭对话框
|
||||
this.batchSetVisible = false
|
||||
},
|
||||
handleSkuChange(row) {
|
||||
// 触发更新事件,将最新的SKU数据传递给父组件
|
||||
this.$emit('sku-updated', this.skus)
|
||||
},
|
||||
parseInitialSkus(initialSkus) {
|
||||
// 创建一个映射来保存每个属性名称及其对应的值集合
|
||||
const attributeMap = new Map()
|
||||
|
||||
// 遍历初始SKU数据,提取所有属性和对应的值
|
||||
initialSkus.forEach(sku => {
|
||||
// 提取属性名称
|
||||
const [color, size] = sku.spec_key.split('_')
|
||||
|
||||
// 提取属性值
|
||||
const values = sku.spec_name.split(';')
|
||||
|
||||
// 添加属性值到集合中
|
||||
values.forEach(value => {
|
||||
if (!attributeMap.has(color)) {
|
||||
attributeMap.set(color, [])
|
||||
}
|
||||
if (!attributeMap.has(size)) {
|
||||
attributeMap.set(size, [])
|
||||
}
|
||||
|
||||
attributeMap.get(color).push(value.trim())
|
||||
attributeMap.get(size).push(value.trim())
|
||||
})
|
||||
})
|
||||
// 设置初始SKU列表
|
||||
this.skus = initialSkus
|
||||
// 构建属性对象数组,并确保每个属性值是按规则分隔的字符串,fixme 这里有问题,不是和生成一致的
|
||||
/* this.attributes = Array.from(attributeMap.entries()).map(([key, values]) => ({
|
||||
attributeName: key,
|
||||
// 将数组转换为字符串,使用' | '作为分隔符
|
||||
attributeValues: values.sort().join(' | ')
|
||||
})) */
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user