-
+
-
+
-
@@ -47,7 +47,6 @@
-
@@ -79,9 +78,115 @@
-
+
+
+
+
+
属性值:
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+ 增加字段
+
+
+
+
+ 增加销售属性
+
+
+ {{ stockKeepUnits }}
+
+
+
+
+ {{ row.attributeValue }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 显示
+ 不显示
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 赠送固定积分
+
+
+
+ 按比例赠送积分
+
+ %
+
+
+
+
+
+
+
+ 立即上架
+ 放入仓库
+
+
-
-
-
-
@@ -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
({
name: [{
required: true,
@@ -231,61 +393,186 @@ const formRules = reactive({
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 = ref([])
+const stockKeepUnits: Ref = 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) => {
@@ -301,15 +588,106 @@ const getDetail = async (row: Record) => {
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([])
let firstCategory = reactive([])
let secondCategory = reactive([])
let thirdCategory = reactive([])
-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([])
-// 添加规格项(最多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,
})
+
+
diff --git a/src/views/goods/goods.vue b/src/views/goods/goods.vue
new file mode 100644
index 0000000..3fcf850
--- /dev/null
+++ b/src/views/goods/goods.vue
@@ -0,0 +1,178 @@
+
+
+
+ - {{ item.name }}
+ -
+
+
+ {{ val.name }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/views/goods/index.vue b/src/views/goods/index.vue
index 597e5c4..a7f0960 100644
--- a/src/views/goods/index.vue
+++ b/src/views/goods/index.vue
@@ -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)
}
// 删除
diff --git a/src/views/goods/power-set.js b/src/views/goods/power-set.js
new file mode 100644
index 0000000..e6dd87c
--- /dev/null
+++ b/src/views/goods/power-set.js
@@ -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
+}
\ No newline at end of file
diff --git a/src/views/goods/sku.vue b/src/views/goods/sku.vue
new file mode 100644
index 0000000..98ee3df
--- /dev/null
+++ b/src/views/goods/sku.vue
@@ -0,0 +1,240 @@
+
+
+
+ 添加属性
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+ 生成SKU
+ 批量设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+