联调
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="属性名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名称" />
|
||||
@@ -33,7 +34,7 @@ const formRules = reactive({
|
||||
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const attributeList = ref([]) // 商品属性列表
|
||||
const attributeList = ref<any>([]) // 商品属性列表
|
||||
const props = defineProps({
|
||||
propertyList: {
|
||||
type: Array,
|
||||
@@ -69,17 +70,13 @@ const submitForm = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as PropertyApi.PropertyVO
|
||||
// 检查属性是否已存在,如果有则返回属性和其下属性值
|
||||
const res = await PropertyApi.getPropertyListAndValue({ name: data.name })
|
||||
if (res.length === 0) {
|
||||
const propertyId = await PropertyApi.createProperty(data)
|
||||
attributeList.value.push({ id: propertyId, ...formData.value, values: [] })
|
||||
} else {
|
||||
if (res[0].values === null) {
|
||||
res[0].values = []
|
||||
}
|
||||
attributeList.value.push(res[0]) // 因为只用一个
|
||||
}
|
||||
const propertyId = await PropertyApi.createProperty(data)
|
||||
// 添加到属性列表
|
||||
attributeList.value.push({
|
||||
id: propertyId + '',
|
||||
...formData.value,
|
||||
values: []
|
||||
})
|
||||
message.success(t('common.createSuccess'))
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
@@ -90,8 +87,7 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
name: '',
|
||||
remark: ''
|
||||
name: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
<template>
|
||||
<el-table
|
||||
:data="isBatch ? skuList : formData.skus"
|
||||
border
|
||||
class="tabNumWidth"
|
||||
max-height="500"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column align="center" fixed="left" label="图片" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData.specType && !isBatch">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
align="center"
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="center" label="商品条码" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.barCode" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售价(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="市场价(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.marketPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成本价(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.costPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="库存" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.stock" :min="0" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="重量(kg)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="体积(m^3)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData.subCommissionType">
|
||||
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.subCommissionFirstPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="二级返佣(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.subCommissionSecondPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
|
||||
批量添加
|
||||
</el-button>
|
||||
<el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" name="SkuList" setup>
|
||||
import { PropType } from 'vue'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { UploadImg } from '@/components/UploadFile'
|
||||
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
default: () => {}
|
||||
},
|
||||
propertyList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
|
||||
})
|
||||
const formData = ref<SpuType>() // 表单数据
|
||||
const skuList = ref<SkuType[]>([
|
||||
{
|
||||
price: 0, // 商品价格
|
||||
marketPrice: 0, // 市场价
|
||||
costPrice: 0, // 成本价
|
||||
barCode: '', // 商品条码
|
||||
picUrl: '', // 图片地址
|
||||
stock: 0, // 库存
|
||||
weight: 0, // 商品重量
|
||||
volume: 0, // 商品体积
|
||||
subCommissionFirstPrice: 0, // 一级分销的佣金
|
||||
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||
}
|
||||
]) // 批量添加时的临时数据
|
||||
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
|
||||
/** 批量添加 */
|
||||
const batchAdd = () => {
|
||||
formData.value.skus.forEach((item) => {
|
||||
copyValueToTarget(item, skuList.value[0])
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除 sku */
|
||||
const deleteSku = (row) => {
|
||||
const index = formData.value.skus.findIndex(
|
||||
// 直接把列表转成字符串比较
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
formData.value.skus.splice(index, 1)
|
||||
}
|
||||
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
watch(
|
||||
() => props.propFormData,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
formData.value = data
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 生成表数据 */
|
||||
const generateTableData = (propertyList: any[]) => {
|
||||
// 构建数据结构
|
||||
const propertyValues = propertyList.map((item) =>
|
||||
item.values.map((v) => ({
|
||||
propertyId: item.id,
|
||||
propertyName: item.name,
|
||||
valueId: v.id,
|
||||
valueName: v.name
|
||||
}))
|
||||
)
|
||||
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
|
||||
const buildList = build(propertyValues)
|
||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||
if (!validateData(propertyList)) {
|
||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||
formData.value!.skus = []
|
||||
}
|
||||
for (const item of buildList) {
|
||||
const row = {
|
||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||
price: 0,
|
||||
marketPrice: 0,
|
||||
costPrice: 0,
|
||||
barCode: '',
|
||||
picUrl: '',
|
||||
stock: 0,
|
||||
weight: 0,
|
||||
volume: 0,
|
||||
subCommissionFirstPrice: 0,
|
||||
subCommissionSecondPrice: 0
|
||||
}
|
||||
// 如果存在属性相同的 sku 则不做处理
|
||||
const index = formData.value!.skus.findIndex(
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
if (index !== -1) {
|
||||
continue
|
||||
}
|
||||
formData.value.skus.push(row)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 skus 前置校验
|
||||
*/
|
||||
const validateData = (propertyList: any[]) => {
|
||||
const skuPropertyIds = []
|
||||
formData.value.skus.forEach((sku) =>
|
||||
sku.properties
|
||||
?.map((property) => property.propertyId)
|
||||
.forEach((propertyId) => {
|
||||
if (skuPropertyIds.indexOf(propertyId) === -1) {
|
||||
skuPropertyIds.push(propertyId)
|
||||
}
|
||||
})
|
||||
)
|
||||
const propertyIds = propertyList.map((item) => item.id)
|
||||
return skuPropertyIds.length === propertyIds.length
|
||||
}
|
||||
|
||||
/** 构建所有排列组合 */
|
||||
const build = (propertyValuesList: Property[][]) => {
|
||||
if (propertyValuesList.length === 0) {
|
||||
return []
|
||||
} else if (propertyValuesList.length === 1) {
|
||||
return propertyValuesList[0]
|
||||
} else {
|
||||
const result: Property[][] = []
|
||||
const rest = build(propertyValuesList.slice(1))
|
||||
for (let i = 0; i < propertyValuesList[0].length; i++) {
|
||||
for (let j = 0; j < rest.length; j++) {
|
||||
// 第一次不是数组结构,后面的都是数组结构
|
||||
if (Array.isArray(rest[j])) {
|
||||
result.push([propertyValuesList[0][i], ...rest[j]])
|
||||
} else {
|
||||
result.push([propertyValuesList[0][i], rest[j]])
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听属性列表,生成相关参数和表头 */
|
||||
watch(
|
||||
() => props.propertyList,
|
||||
(propertyList) => {
|
||||
// 如果不是多规格则结束
|
||||
if (!formData.value.specType) {
|
||||
return
|
||||
}
|
||||
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||
if (props.isBatch) {
|
||||
skuList.value = [
|
||||
{
|
||||
price: 0,
|
||||
marketPrice: 0,
|
||||
costPrice: 0,
|
||||
barCode: '',
|
||||
picUrl: '',
|
||||
stock: 0,
|
||||
weight: 0,
|
||||
volume: 0,
|
||||
subCommissionFirstPrice: 0,
|
||||
subCommissionSecondPrice: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 判断代理对象是否为空
|
||||
if (JSON.stringify(propertyList) === '[]') {
|
||||
return
|
||||
}
|
||||
// 重置表头
|
||||
tableHeaders.value = []
|
||||
// 生成表头
|
||||
propertyList.forEach((item, index) => {
|
||||
// name加属性项index区分属性值
|
||||
tableHeaders.value.push({ prop: `name${index}`, label: item.name })
|
||||
})
|
||||
|
||||
// 如果回显的 sku 属性和添加的属性一致则不处理
|
||||
if (validateData(propertyList)) {
|
||||
return
|
||||
}
|
||||
// 添加新属性没有属性值也不做处理
|
||||
if (propertyList.some((item) => item.values.length === 0)) {
|
||||
return
|
||||
}
|
||||
// 生成 table 数据,即 sku 列表
|
||||
generateTableData(propertyList)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// 暴露出生成 sku 方法,给添加属性成功时调用
|
||||
defineExpose({ generateTableData })
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tabs v-model="tabName" type="border-card" tab-position="top">
|
||||
<el-tabs v-model="tabName" type="border-card" tab-position="top" v-loading="formLoading">
|
||||
<el-tab-pane label="商品信息" name="basic">
|
||||
<el-form :model="form" ref="spuForm" :rules="rules" label-width="90px">
|
||||
<el-row :gutter="20">
|
||||
@@ -11,9 +11,10 @@
|
||||
<el-col :span="8" :offset="0">
|
||||
<el-form-item label="分类" prop="productCategory">
|
||||
<el-cascader
|
||||
:options="opts.productCategory"
|
||||
:options="opts.category"
|
||||
v-model="form.productCategory"
|
||||
placeholder="请选择分类"
|
||||
:props="{ label: 'name', value: 'id' }"
|
||||
filterable
|
||||
show-all-levels
|
||||
style="width: 100%"
|
||||
@@ -25,9 +26,9 @@
|
||||
<el-select v-model="form.productBrand" placeholder="请选择品牌" filterable>
|
||||
<el-option
|
||||
v-for="item in opts.brand"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:key="item.brandId"
|
||||
:label="item.name"
|
||||
:value="item.brandId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -110,11 +111,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="col in form.productSpecList"
|
||||
:prop="col.id"
|
||||
v-for="(col, index) in form.productSpecList"
|
||||
:key="col.id"
|
||||
:label="col.name"
|
||||
/>
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="销售价">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0.01" :step="1" />
|
||||
@@ -130,22 +136,30 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div class="mt-20px flex justify-center">
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
<el-button plain>重置</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="详细信息" name="detail">
|
||||
<Editor v-model:modelValue="form.detailInfo" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="mt-20px flex justify-center">
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
<el-button plain>重置</el-button>
|
||||
</div>
|
||||
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="form.productSpecList" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import * as PropertyApi from '@/api/mall/product/property'
|
||||
import * as ProductApi from '@/api/mall/product/index'
|
||||
import ProductAttributesAddForm from './Comp/ProductAttributesAddForm.vue'
|
||||
import * as BrandApi from '@/api/mall/product/brand'
|
||||
import * as CategoryApi from '@/api/mall/product/category'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const tabName = ref('basic')
|
||||
const form = ref({
|
||||
@@ -157,25 +171,32 @@ const form = ref({
|
||||
carouselImages: [],
|
||||
productSpecList: [],
|
||||
skuList: [],
|
||||
detailInfo: null
|
||||
detailInfo: null,
|
||||
status: 0
|
||||
})
|
||||
const rules = ref({})
|
||||
const rules = {
|
||||
productName: { required: true, message: '产品名称不可为空', trigger: 'blur' }
|
||||
}
|
||||
const attributesAddFormRef = ref() // 添加商品属性表单
|
||||
|
||||
const opts = {
|
||||
brand: [{ value: 1, label: '品牌1' }],
|
||||
productCategory: [
|
||||
{
|
||||
id: 1,
|
||||
label: '分类1',
|
||||
children: [{ id: 2, label: '子分类1' }]
|
||||
}
|
||||
]
|
||||
const opts = ref({
|
||||
brand: [],
|
||||
category: []
|
||||
})
|
||||
|
||||
async function getOptions() {
|
||||
BrandApi.getSimpleBrandList().then((data) => {
|
||||
opts.value.brand = data || []
|
||||
})
|
||||
CategoryApi.getCategorySimpleList().then((data) => {
|
||||
opts.value.category = handleTree(data || [])
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除属性*/
|
||||
function handleCloseProperty(index) {
|
||||
form.value.productSpecList?.splice(index, 1)
|
||||
getTableList()
|
||||
}
|
||||
|
||||
const attributeIndex = ref(null)
|
||||
@@ -191,8 +212,7 @@ async function handleInputConfirm(index, propertyId) {
|
||||
if (inputValue.value) {
|
||||
// 保存属性值
|
||||
try {
|
||||
// const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
|
||||
const id = propertyId || parseInt(Math.random() * 1000000)
|
||||
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
|
||||
form.value.productSpecList[index].values.push({ id, name: inputValue.value })
|
||||
message.success('添加成功')
|
||||
} catch {
|
||||
@@ -201,31 +221,88 @@ async function handleInputConfirm(index, propertyId) {
|
||||
}
|
||||
attributeIndex.value = null
|
||||
inputValue.value = ''
|
||||
form.value.skuList = getTableList()
|
||||
getTableList()
|
||||
}
|
||||
|
||||
/** 删除属性值*/
|
||||
function handleCloseValue(index, valueIndex) {
|
||||
form.value.productSpecList[index].values?.splice(valueIndex, 1)
|
||||
getTableList()
|
||||
}
|
||||
|
||||
function getTableList() {
|
||||
let list = []
|
||||
form.value.productSpecList.map((item) => {
|
||||
if (!list.length) {
|
||||
item.values.map((it) => {
|
||||
const obj = {}
|
||||
obj[it.id] = it.name
|
||||
list.push(obj)
|
||||
})
|
||||
} else {
|
||||
item.values.map((it, index) => {
|
||||
if (index < list.length) {
|
||||
list[index][it.id] = it.name
|
||||
} else {
|
||||
const obj = {}
|
||||
obj[it.id] = it.name
|
||||
list.push(obj)
|
||||
}
|
||||
})
|
||||
const propertyList = [...form.value.productSpecList]
|
||||
// 构建数据结构
|
||||
const propertyValues = propertyList.map((item) =>
|
||||
item.values.map((v) => ({
|
||||
propertyId: item.id,
|
||||
propertyName: item.name,
|
||||
valueId: v.id,
|
||||
valueName: v.name
|
||||
}))
|
||||
)
|
||||
const buildSkuList = build(propertyValues)
|
||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skuList 列表
|
||||
if (!validateData(propertyList)) {
|
||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||
// form.value.skuList = []
|
||||
}
|
||||
form.value.skuList = []
|
||||
for (const item of buildSkuList) {
|
||||
const row = {
|
||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||
price: 0.01,
|
||||
intro: '',
|
||||
specsName: ''
|
||||
}
|
||||
})
|
||||
return list
|
||||
// 如果存在属性相同的 sku 则不做处理
|
||||
const index = form.value.skuList.findIndex(
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
if (index !== -1) {
|
||||
continue
|
||||
}
|
||||
form.value.skuList.push(row)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 skuList 前置校验
|
||||
*/
|
||||
const validateData = (propertyList) => {
|
||||
const skuPropertyIds = []
|
||||
form.value.skuList.forEach((sku) =>
|
||||
sku.properties.map((property) => {
|
||||
if (skuPropertyIds.indexOf(property.propertyId) === -1) {
|
||||
skuPropertyIds.push(property.propertyId)
|
||||
}
|
||||
})
|
||||
)
|
||||
const propertyIds = propertyList.map((item) => item.id)
|
||||
return skuPropertyIds.length === propertyIds.length
|
||||
}
|
||||
|
||||
/** 构建所有排列组合 */
|
||||
const build = (propertyValuesList) => {
|
||||
if (propertyValuesList.length === 0) {
|
||||
return []
|
||||
} else if (propertyValuesList.length === 1) {
|
||||
return propertyValuesList[0]
|
||||
} else {
|
||||
const result = []
|
||||
const rest = build(propertyValuesList.slice(1))
|
||||
for (let i = 0; i < propertyValuesList[0].length; i++) {
|
||||
for (let j = 0; j < rest.length; j++) {
|
||||
// 第一次不是数组结构,后面的都是数组结构
|
||||
if (Array.isArray(rest[j])) {
|
||||
result.push([propertyValuesList[0][i], ...rest[j]])
|
||||
} else {
|
||||
result.push([propertyValuesList[0][i], rest[j]])
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
const inputRef = ref([]) //标签输入框Ref
|
||||
@@ -248,14 +325,103 @@ function handleAddSpec() {
|
||||
attributesAddFormRef.value.open()
|
||||
}
|
||||
|
||||
const spuForm = ref()
|
||||
|
||||
function onSubmit() {
|
||||
message.success('保存成功!')
|
||||
spuForm.value.validate(async (valid) => {
|
||||
if (valid && validateSku()) {
|
||||
// 深拷贝一份, 这样最终 server 端不满足,不需要影响原始数据
|
||||
const deepCopyFormData = cloneDeep(unref(form.value))
|
||||
deepCopyFormData.productSpecList = deepCopyFormData.skuList
|
||||
if (deepCopyFormData.productCategory && deepCopyFormData.productCategory.length) {
|
||||
deepCopyFormData.productCategory = deepCopyFormData.productCategory.at(-1)
|
||||
}
|
||||
delete deepCopyFormData.skuList
|
||||
// 校验都通过后提交表单
|
||||
const data = deepCopyFormData
|
||||
const id = route.query.id
|
||||
if (!id) {
|
||||
await ProductApi.createProduct(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProductApi.updateProduct(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 作为活动组件的校验
|
||||
const ruleConfig = [
|
||||
{
|
||||
name: 'specsName',
|
||||
rule: (arg) => arg.length,
|
||||
message: '规格名称不可为空 !!!'
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
rule: (arg) => arg >= 0.01,
|
||||
message: '商品销售价格必须大于等于 0.01 元!!!'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
*/
|
||||
const validateSku = () => {
|
||||
let warningInfo = '请检查商品各行相关属性配置,'
|
||||
let validate = form.value.skuList.length > 0 // 默认通过
|
||||
if (!form.value.skuList.length) {
|
||||
message.info('请添加商品规格!!!')
|
||||
}
|
||||
for (const sku of form.value.skuList) {
|
||||
for (const rule of ruleConfig) {
|
||||
const arg = getValue(sku, rule.name)
|
||||
if (!rule.rule(arg)) {
|
||||
validate = false // 只要有一个不通过则直接不通过
|
||||
warningInfo += rule.message
|
||||
break
|
||||
}
|
||||
}
|
||||
// 只要有一个不通过则结束后续的校验
|
||||
if (!validate) {
|
||||
message.warning(warningInfo)
|
||||
throw new Error(warningInfo)
|
||||
}
|
||||
}
|
||||
return validate
|
||||
}
|
||||
|
||||
const getValue = (obj, arg) => {
|
||||
const keys = arg.split('.')
|
||||
let value = obj
|
||||
for (const key of keys) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = value[key]
|
||||
} else {
|
||||
value = undefined
|
||||
break
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const formLoading = ref(false)
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
if (route.query?.id) {
|
||||
form.value = {
|
||||
productName: '商品名称哦~'
|
||||
formLoading.value = true
|
||||
try {
|
||||
const res = await ProductApi.getProduct(route.query.id)
|
||||
const propList = getPropertyList(res.productSpecList)
|
||||
form.value = {
|
||||
...res,
|
||||
skuList: res.productSpecList,
|
||||
productSpecList: propList
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
form.value = {
|
||||
@@ -267,9 +433,41 @@ onMounted(() => {
|
||||
carouselImages: ['https://ss-cloud.ahduima.com/1001/1796426117251600384.png'],
|
||||
productSpecList: [],
|
||||
skuList: [],
|
||||
detailInfo: null
|
||||
detailInfo: null,
|
||||
status: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得商品的规格列表 - 商品相关的公共函数
|
||||
*
|
||||
* @param spu
|
||||
* @return PropertyAndValues 规格列表
|
||||
*/
|
||||
const getPropertyList = (list) => {
|
||||
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
||||
const properties = []
|
||||
// 只有是多规格才处理
|
||||
list.forEach((sku) => {
|
||||
sku.properties?.forEach(({ propertyId, propertyName, valueId, valueName }) => {
|
||||
// 添加属性
|
||||
if (!properties?.some((item) => item.id === propertyId)) {
|
||||
properties.push({ id: propertyId, name: propertyName, values: [] })
|
||||
}
|
||||
// 添加属性值
|
||||
const index = properties?.findIndex((item) => item.id === propertyId)
|
||||
if (!properties[index].values?.some((value) => value.id === valueId)) {
|
||||
properties[index].values?.push({ id: valueId, name: valueName })
|
||||
}
|
||||
})
|
||||
})
|
||||
return properties
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getOptions()
|
||||
await getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="item in opts.brand"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:key="item.brandId"
|
||||
:label="item.name"
|
||||
:value="item.brandId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -33,6 +33,7 @@
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
filterable
|
||||
:props="{ label: 'name', value: 'id', checkStrictly: true }"
|
||||
show-all-levels
|
||||
@change="handleQuery"
|
||||
/>
|
||||
@@ -40,7 +41,7 @@
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery" v-hasPermi="['mall:prod:search']"> 搜索 </el-button>
|
||||
<el-button @click="resetQuery" v-hasPermi="['mall:prod:reset']"> 重置 </el-button>
|
||||
<el-button plain type="primary" @click="openForm" v-hasPermi="['mall:prod:add']">
|
||||
<el-button plain type="primary" @click="openForm(null)" v-hasPermi="['mall:prod:add']">
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -53,6 +54,16 @@
|
||||
<div class="pl-100px pr-100px">
|
||||
<el-table :data="row.productSpecList">
|
||||
<el-table-column label="规格名称" prop="specsName" />
|
||||
<el-table-column />
|
||||
<el-table-column
|
||||
v-for="(item, index) in row.properties"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.properties[index].valueName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售价" prop="price" />
|
||||
<el-table-column label="简介" prop="intro" />
|
||||
</el-table>
|
||||
@@ -105,6 +116,9 @@
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import * as ProductApi from '@/api/mall/product'
|
||||
import * as BrandApi from '@/api/mall/product/brand'
|
||||
import * as CategoryApi from '@/api/mall/product/category'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
|
||||
const { currentRoute, push } = useRouter()
|
||||
const message = useMessage() // 消息弹窗
|
||||
@@ -143,8 +157,18 @@ async function handleDelete(id) {
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProductApi.getProductPage(queryParams.value)
|
||||
tableList.value = data.list
|
||||
const params = { ...queryParams.value }
|
||||
if (params.productCategory && params.productCategory.length) {
|
||||
params.productCategory = params.productCategory.at(-1)
|
||||
}
|
||||
const data = await ProductApi.getProductPage(params)
|
||||
tableList.value = data.list.map((prod) => ({
|
||||
...prod,
|
||||
properties: prod.productSpecList[0].properties.map((item) => ({
|
||||
propertyId: item.propertyId,
|
||||
label: item.propertyName
|
||||
}))
|
||||
}))
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -163,6 +187,15 @@ function handleQuery() {
|
||||
getList()
|
||||
}
|
||||
|
||||
async function getOptions() {
|
||||
BrandApi.getSimpleBrandList().then((data) => {
|
||||
opts.value.brand = data || []
|
||||
})
|
||||
CategoryApi.getCategorySimpleList().then((data) => {
|
||||
opts.value.productCategory = handleTree(data || [])
|
||||
})
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
queryParams.value = {
|
||||
@@ -182,7 +215,7 @@ function resetQuery() {
|
||||
*/
|
||||
function openForm(id) {
|
||||
// 修改
|
||||
if (typeof id == 'number') {
|
||||
if (id) {
|
||||
push({
|
||||
path: '/miniMall/productEdit',
|
||||
query: { id }
|
||||
@@ -201,6 +234,10 @@ watch(
|
||||
)
|
||||
|
||||
handleQuery()
|
||||
|
||||
onMounted(() => {
|
||||
getOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user