📖 MALL:商品编辑 => 优化 SKU 表单
This commit is contained in:
parent
efe702d383
commit
20b4a7fd66
@ -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 })
|
||||
}
|
||||
|
||||
// ------------------------ 属性值 -------------------
|
||||
|
||||
// 获得属性值分页
|
||||
|
@ -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">
|
||||
|
@ -100,9 +100,7 @@ const rules = reactive({
|
||||
brandId: [required]
|
||||
})
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 formData
|
||||
*/
|
||||
/** 将传进来的值赋值给 formData */
|
||||
watch(
|
||||
() => props.propFormData,
|
||||
(data) => {
|
||||
|
@ -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
|
||||
|
@ -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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
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]) // 因为只用一个
|
||||
}
|
||||
// 添加到属性列表
|
||||
attributeList.value.push({
|
||||
id: propertyId,
|
||||
...formData.value,
|
||||
values: []
|
||||
})
|
||||
message.success(t('common.createSuccess'))
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
|
@ -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 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-col>
|
||||
<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 :span="24">
|
||||
<el-form-item v-if="!formData.specType">
|
||||
<SkuList
|
||||
ref="skuListRef"
|
||||
:prop-form-data="formData"
|
||||
:propertyList="propertyList"
|
||||
: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 :propertyList="propertyList" @success="generateSkus" />
|
||||
<ProductAttributes
|
||||
:property-list="propertyList"
|
||||
@success="generateSkus"
|
||||
:is-detail="isDetail"
|
||||
/>
|
||||
</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 label="批量设置" v-if="!isDetail">
|
||||
<SkuList :is-batch="true" :prop-form-data="formData" :property-list="propertyList" />
|
||||
</el-form-item>
|
||||
<el-form-item label="属性列表">
|
||||
<el-form-item label="规格列表">
|
||||
<SkuList
|
||||
ref="skuListRef"
|
||||
:prop-form-data="formData"
|
||||
:propertyList="propertyList"
|
||||
:property-list="propertyList"
|
||||
:rule-config="ruleConfig"
|
||||
:is-detail="isDetail"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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 {
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user