📖 MALL:商品编辑 => 优化 SKU 表单

(cherry picked from commit 20b4a7fd66)
This commit is contained in:
YunaiV 2024-01-11 22:22:07 +08:00 committed by shizhong
parent d352e022b6
commit 48fe4332bc
6 changed files with 141 additions and 124 deletions

View File

@ -70,11 +70,6 @@ export const getPropertyList = (params: any) => {
return request.get({ url: '/product/property/list', params })
}
// 获得属性项列表
export const getPropertyListAndValue = (data: any) => {
return request.post({ url: '/product/property/get-value-list', data })
}
// ------------------------ 属性值 -------------------
// 获得属性值分页

View File

@ -34,12 +34,19 @@
<el-input v-model="row.barCode" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="168">
<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%" />
<el-input-number
v-model="row.price"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="168">
<el-table-column align="center" label="市场价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.marketPrice"
@ -47,10 +54,11 @@
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="168">
<el-table-column align="center" label="成本价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.costPrice"
@ -58,22 +66,37 @@
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</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%" />
<el-input-number v-model="row.stock" :min="0" class="w-100%" controls-position="right" />
</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%" />
<el-input-number
v-model="row.weight"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</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%" />
<el-input-number
v-model="row.volume"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
@ -85,6 +108,7 @@
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
@ -96,6 +120,7 @@
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
@ -124,7 +149,12 @@
<el-table-column v-if="isComponent" type="selection" width="45" />
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
<el-image
v-if="row.picUrl"
:src="row.picUrl"
class="h-60px w-60px"
@click="imagePreview(row.picUrl)"
/>
</template>
</el-table-column>
<template v-if="formData!.specType && !isBatch">

View File

@ -100,9 +100,7 @@ const rules = reactive({
brandId: [required]
})
/**
* 将传进来的值赋值给 formData
*/
/** 将传进来的值赋值给 formData */
watch(
() => props.propFormData,
(data) => {

View File

@ -1,9 +1,10 @@
<!-- 商品发布 - 库存价格 - 属性列表 -->
<template>
<el-col v-for="(item, index) in attributeList" :key="index">
<div>
<el-text class="mx-1">属性名</el-text>
<el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)"
>{{ item.name }}
<el-tag class="mx-1" :closable="!isDetail" type="success" @close="handleCloseProperty(index)">
{{ item.name }}
</el-tag>
</div>
<div>
@ -12,7 +13,7 @@
v-for="(value, valueIndex) in item.values"
:key="value.id"
class="mx-1"
closable
:closable="!isDetail"
@close="handleCloseValue(index, valueIndex)"
>
{{ value.name }}
@ -43,6 +44,9 @@
<script lang="ts" setup>
import { ElInput } from 'element-plus'
import * as PropertyApi from '@/api/mall/product/property'
import { PropertyVO } from '@/api/mall/product/property'
import { PropertyAndValues } from '@/views/mall/product/spu/components'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'ProductAttributes' })
@ -51,7 +55,7 @@ const message = useMessage() // 消息弹窗
const inputValue = ref('') //
const attributeIndex = ref<number | null>(null) // index
//
const inputVisible = computed(() => (index) => {
const inputVisible = computed(() => (index: number) => {
if (attributeIndex.value === null) return false
if (attributeIndex.value === index) return true
})
@ -59,17 +63,18 @@ const inputRef = ref([]) //标签输入框Ref
/** 解决 ref 在 v-for 中的获取问题*/
const setInputRef = (el) => {
if (el === null || typeof el === 'undefined') return
// id
// id
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) {
inputRef.value.push(el)
}
}
const attributeList = ref([]) //
const attributeList = ref<PropertyAndValues[]>([]) //
const props = defineProps({
propertyList: {
type: Array,
default: () => {}
}
},
isDetail: propTypes.bool.def(false) //
})
watch(
@ -85,22 +90,23 @@ watch(
)
/** 删除属性值*/
const handleCloseValue = (index, valueIndex) => {
const handleCloseValue = (index: number, valueIndex: number) => {
attributeList.value[index].values?.splice(valueIndex, 1)
}
/** 删除属性*/
const handleCloseProperty = (index) => {
const handleCloseProperty = (index: number) => {
attributeList.value?.splice(index, 1)
}
/** 显示输入框并获取焦点 */
const showInput = async (index) => {
attributeIndex.value = index
inputRef.value[index].focus()
}
const emit = defineEmits(['success']) // success
/** 输入框失去焦点或点击回车时触发 */
const emit = defineEmits(['success']) // success
const handleInputConfirm = async (index, propertyId) => {
if (inputValue.value) {
//
@ -110,7 +116,7 @@ const handleInputConfirm = async (index, propertyId) => {
message.success(t('common.createSuccess'))
emit('success', attributeList.value)
} catch {
message.error('添加失败,请重试') // TODO
message.error('添加失败,请重试')
}
}
attributeIndex.value = null

View File

@ -1,5 +1,6 @@
<!-- 商品发布 - 库存价格 - 添加属性 -->
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<Dialog v-model="dialogVisible" title="添加商品属性">
<el-form
ref="formRef"
v-loading="formLoading"
@ -26,8 +27,7 @@ const { t } = useI18n() // 国际化
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('添加商品属性') //
const formLoading = ref(false) // 12
const formLoading = ref(false) //
const formData = ref({
name: ''
})
@ -44,7 +44,7 @@ const props = defineProps({
})
watch(
() => props.propertyList,
() => props.propertyList, // props
(data) => {
if (!data) return
attributeList.value = data
@ -54,6 +54,7 @@ watch(
immediate: true
}
)
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
@ -71,19 +72,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 = []
}
//
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 {

View File

@ -1,58 +1,49 @@
<!-- 商品发布 - 库存价格 -->
<template>
<!-- 情况一添加/修改 -->
<el-form
ref="productSpuSkuRef"
:model="formData"
:rules="rules"
label-width="120px"
:disabled="isDetail"
>
<el-row>
<el-col :span="12">
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
<el-radio :label="false" class="radio">单规格</el-radio>
<el-radio :label="true">多规格</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 多规格添加-->
<el-col :span="24">
<el-form-item v-if="!formData.specType">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置">
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
<el-form-item label="属性列表">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:propertyList="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
</template>
</el-col>
</el-row>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" :disabled="isDetail">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
<el-radio :label="false" class="radio">单规格</el-radio>
<el-radio :label="true">多规格</el-radio>
</el-radio-group>
</el-form-item>
<!-- 多规格添加-->
<el-form-item v-if="!formData.specType">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
<ProductAttributes
:property-list="propertyList"
@success="generateSkus"
:is-detail="isDetail"
/>
</el-form-item>
<template v-if="formData.specType && propertyList.length > 0">
<el-form-item label="批量设置" v-if="!isDetail">
<SkuList :is-batch="true" :prop-form-data="formData" :property-list="propertyList" />
</el-form-item>
<el-form-item label="规格列表">
<SkuList
ref="skuListRef"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
:is-detail="isDetail"
/>
</el-form-item>
</template>
</el-form>
<!-- 商品属性添加 Form 表单 -->
@ -62,7 +53,12 @@
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { getPropertyList, RuleConfig, SkuList } from '@/views/mall/product/spu/components/index.ts'
import {
getPropertyList,
PropertyAndValues,
RuleConfig,
SkuList
} from '@/views/mall/product/spu/components/index'
import ProductAttributes from './ProductAttributes.vue'
import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
import type { Spu } from '@/api/mall/product/spu'
@ -100,17 +96,12 @@ const props = defineProps({
type: Object as PropType<Spu>,
default: () => {}
},
activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) //
})
const attributesAddFormRef = ref() //
const productSpuSkuRef = ref() // Ref
const propertyList = ref([]) //
const skuListRef = ref() // Ref
/** 调用 SkuList generateTableData 方法*/
const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList)
}
const formRef = ref() // Ref
const propertyList = ref<PropertyAndValues[]>([]) //
const skuListRef = ref() // Ref
const formData = reactive<Spu>({
specType: false, //
subCommissionType: false, //
@ -121,9 +112,7 @@ const rules = reactive({
subCommissionType: [required]
})
/**
* 将传进来的值赋值给 formData
*/
/** 将传进来的值赋值给 formData */
watch(
() => props.propFormData,
(data) => {
@ -131,6 +120,7 @@ watch(
return
}
copyValueToTarget(formData, data)
// SKU PropertyAndValues
propertyList.value = getPropertyList(data)
},
{
@ -144,25 +134,23 @@ const validate = async () => {
// sku
skuListRef.value.validateSku()
//
if (!productSpuSkuRef) return
return await unref(productSpuSkuRef).validate((valid) => {
if (!valid) {
message.warning('商品信息未完善!!')
emit('update:activeName', 'sku')
//
throw new Error('商品信息未完善!!')
} else {
//
Object.assign(props.propFormData, formData)
}
})
if (!formRef) return
try {
await unref(formRef).validate()
//
Object.assign(props.propFormData, formData)
} catch (e) {
message.error('【库存价格】不完善,请填写相关信息')
emit('update:activeName', 'sku')
throw e //
}
}
defineExpose({ validate })
/** 分销类型 */
const changeSubCommissionType = () => {
//
for (const item of formData.skus) {
for (const item of formData.skus!) {
item.firstBrokeragePrice = 0
item.secondBrokeragePrice = 0
}
@ -188,4 +176,9 @@ const onChangeSpec = () => {
}
]
}
/** 调用 SkuList generateTableData 方法*/
const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList)
}
</script>