商品管理: 初步完成相关组件

pull/141/head
puhui999 2023-04-29 21:39:25 +08:00
parent ab1685a741
commit 61218ae711
13 changed files with 492 additions and 76 deletions

View File

View File

@ -0,0 +1,15 @@
import request from '@/config/axios'
import type { SpuType } from './type/spuType'
// 获得sku列表
export const getSkuList = (params: any) => {
return request.get({ url: '/product/sku/list', params })
}
// 创建商品spu
export const createSpu = (data: SpuType) => {
return request.post({ url: '/product/spu/create', data })
}
// 更新商品spu
export const updateSpu = (data: SpuType) => {
return request.put({ url: '/product/spu/update', data })
}

View File

@ -0,0 +1,75 @@
export interface Property {
/**
*
*
* {@link ProductPropertyDO#getId()}
*/
propertyId?: number
/**
*
*
* {@link ProductPropertyValueDO#getId()}
*/
valueId?: number
}
export interface SkuType {
/**
* SKU
*/
id?: number
/**
* SPU
*/
spuId?: number
/**
* JSON
*/
properties?: Property[]
/**
*
*/
price?: number
/**
*
*/
marketPrice?: number
/**
*
*/
costPrice?: number
/**
*
*/
barCode?: string
/**
*
*/
picUrl?: string
/**
*
*/
stock?: number
/**
* kg
*/
weight?: number
/**
* m^3
*/
volume?: number
/**
*
*/
subCommissionFirstPrice?: number
/**
*
*/
subCommissionSecondPrice?: number
/**
*
*/
salesCount?: number
}

View File

@ -1,3 +1,5 @@
import { SkuType } from './skuType'
export interface SpuType {
name?: string // 商品名称
categoryId?: number | undefined // 商品分类
@ -10,6 +12,7 @@ export interface SpuType {
selectRule?: string // 选择规格 TODO 暂时定义
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
skus?: SkuType[] // sku数组
description?: string // 商品详情
sort?: string // 商品排序
giveIntegral?: number // 赠送积分

View File

@ -34,12 +34,14 @@
<script lang="ts" name="ProductManagementForm" setup>
import { useTagsViewStore } from '@/store/modules/tagsView'
import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
import { SpuType } from '@/api/mall/product/management/type' // const { t } = useI18n() // 国际化
import type { SpuType } from '@/api/mall/product/management/type/spuType'
// api
import * as managementApi from '@/api/mall/product/management/spu'
// const { t } = useI18n() //
// const message = useMessage() //
const { t } = useI18n() //
const message = useMessage() //
const { push, currentRoute } = useRouter() //
// const { query } = useRoute() //
const { query } = useRoute() //
const { delView } = useTagsViewStore() //
const formLoading = ref(false) // 12
@ -70,10 +72,17 @@ const formData = ref<SpuType>({
recommendGood: false //
})
/** 获得详情 */
const getDetail = async () => {}
const getDetail = async () => {
const id = query.id as unknown as number
if (!id) {
return
}
}
/** 提交按钮 */
const submitForm = async () => {
//
formLoading.value = true
// TODO
//
try {
@ -81,9 +90,20 @@ const submitForm = async () => {
await unref(DescriptionRef)?.validate()
await unref(OtherSettingsRef)?.validate()
//
console.log(formData.value)
} catch {}
const data = formData.value as SpuType
const id = query.id as unknown as number
if (!id) {
await managementApi.createSpu(data)
message.success(t('common.createSuccess'))
} else {
await managementApi.updateSpu(data)
message.success(t('common.updateSuccess'))
}
} finally {
formLoading.value = false
}
}
/**
* 重置表单
*/

View File

@ -58,7 +58,6 @@
<el-col :span="12">
<el-button class="ml-20px">运费模板</el-button>
</el-col>
<!-- TODO 商品规格和分销类型切换待定 -->
<el-col :span="12">
<el-form-item label="商品规格" props="specType">
<el-radio-group v-model="formData.specType" @change="changeSpecType(formData.specType)">
@ -67,45 +66,41 @@
</el-radio-group>
</el-form-item>
</el-col>
<!-- TODO 商品规格和分销类型切换待定 -->
<el-col :span="12">
<el-form-item label="分销类型" props="subCommissionType">
<el-radio-group
v-model="formData.subCommissionType"
@change="changeSubCommissionType(formData.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 v-if="formData.specType" :span="24">
<el-form-item label="选择规格" prop="">
<div class="acea-row">
<el-select v-model="formData.selectRule">
<el-option
v-for="item in []"
:key="item.id"
:label="item.ruleName"
:value="item.id"
/>
</el-select>
<el-button class="mr-20px" type="primary" @click="confirm"></el-button>
<el-button class="mr-15px" @click="addRule"></el-button>
</div>
<el-col :span="24">
<el-form-item v-if="formData.specType" label="商品属性" prop="">
<el-button class="mr-15px" @click="AttributesAddFormRef.open()"></el-button>
<ProductAttributes :attribute-data="attributeList" />
</el-form-item>
<el-form-item>
<SkuList :sku-data="formData.skus" :subCommissionType="formData.subCommissionType" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<ProductAttributesAddForm ref="AttributesAddFormRef" @success="addAttribute" />
</template>
<script lang="ts" name="ProductManagementBasicInfoForm" setup>
import { PropType } from 'vue'
import type { SpuType } from '@/api/mall/product/management/type'
import type { SpuType } from '@/api/mall/product/management/type/spuType'
import { UploadImg, UploadImgs } from '@/components/UploadFile'
import SkuList from './SkuList/index.vue'
import ProductAttributesAddForm from './ProductAttributesAddForm.vue'
import ProductAttributes from './ProductAttributes.vue'
import { copyValueToTarget } from '@/utils/object'
// Api
import * as ProductCategoryApi from '@/api/mall/product/category'
import * as PropertyApi from '@/api/mall/product/property'
import { defaultProps, handleTree } from '@/utils/tree'
import { ElInput } from 'element-plus'
const message = useMessage() //
const props = defineProps({
@ -114,9 +109,21 @@ const props = defineProps({
default: () => {}
}
})
const AttributesAddFormRef = ref() //
const ProductManagementBasicInfoRef = ref() // Ref
const formData = ref<SpuType>({
//
const attributeList = ref([
{
id: 1,
name: '颜色',
attributeValues: [{ id: 1, name: '白色' }]
}
])
const addAttribute = async (propertyId: number) => {
const data = await PropertyApi.getPropertyValuePage({ id: propertyId })
console.log(data)
}
const formData = reactive<SpuType>({
name: '', //
categoryId: undefined, //
keyword: '', //
@ -124,10 +131,46 @@ const formData = ref<SpuType>({
picUrl: '', //
sliderPicUrls: [], //
introduction: '', //
deliveryTemplateId: '', //
deliveryTemplateId: 1, //
selectRule: '', // TODO
specType: false, //
subCommissionType: false //
subCommissionType: false, //
skus: [
{
/**
* 商品价格单位
*/
price: 0,
/**
* 市场价单位
*/
marketPrice: 0,
/**
* 成本价单位
*/
costPrice: 0,
/**
* 商品条码
*/
barCode: '',
/**
* 图片地址
*/
picUrl: '',
/**
* 库存
*/
stock: 0,
/**
* 商品重量单位kg 千克
*/
weight: 0,
/**
* 商品体积单位m^3 平米
*/
volume: 0
}
]
})
const rules = reactive({
name: [required],
@ -148,7 +191,7 @@ watch(
() => props.propFormData,
(data) => {
if (!data) return
copyValueToTarget(formData.value, data)
copyValueToTarget(formData, data)
},
{
deep: true,
@ -170,7 +213,7 @@ const validate = async () => {
throw new Error('商品信息未完善!!')
} else {
//
Object.assign(props.propFormData, formData.value)
Object.assign(props.propFormData, formData)
}
})
}
@ -180,13 +223,17 @@ const changeSpecType = (specType) => {
console.log(specType)
}
//
const changeSubCommissionType = (subCommissionType) => {
console.log(subCommissionType)
const changeSubCommissionType = () => {
//
for (const item of formData.skus) {
item.subCommissionFirstPrice = 0
item.subCommissionSecondPrice = 0
}
}
//
const confirm = () => {}
// const confirm = () => {}
//
const addRule = () => {}
// const addRule = () => {}
const categoryList = ref() //
onMounted(async () => {
//

View File

@ -7,7 +7,7 @@
</el-form>
</template>
<script lang="ts" name="DescriptionForm" setup>
import type { SpuType } from '@/api/mall/product/management/type'
import type { SpuType } from '@/api/mall/product/management/type/spuType'
import { Editor } from '@/components/Editor'
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'

View File

@ -50,7 +50,7 @@
</template>
<script lang="ts" name="OtherSettingsForm" setup>
//
import type { SpuType } from '@/api/mall/product/management/type'
import type { SpuType } from '@/api/mall/product/management/type/spuType'
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'

View File

@ -0,0 +1,82 @@
<template>
<el-col v-for="(item, index) in attributeList" :key="index">
<div>
<el-text class="mx-1">属性名</el-text>
<el-text class="mx-1">{{ item.name }}</el-text>
</div>
<div>
<el-text class="mx-1">属性值</el-text>
<el-tag
v-for="(value, valueIndex) in item.attributeValues"
:key="value.name"
:disable-transitions="false"
class="mx-1"
closable
@close="handleClose(index, valueIndex)"
>
{{ value.name }}
</el-tag>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
class="!w-20"
size="small"
@blur="handleInputConfirm(index)"
@keyup.enter="handleInputConfirm(index)"
/>
<el-button v-else class="button-new-tag ml-1" size="small" @click="showInput(index)">
+ 添加
</el-button>
</div>
<el-divider class="my-10px" />
</el-col>
</template>
<script lang="ts" name="ProductAttributes" setup>
import { ElInput } from 'element-plus'
const inputValue = ref('') //
const inputVisible = ref(false) //
const InputRef = ref<InstanceType<typeof ElInput>>() //Ref
const attributeList = ref([])
const props = defineProps({
attributeData: {
type: Object,
default: () => {}
}
})
watch(
() => props.attributeData,
(data) => {
if (!data) return
attributeList.value = data
},
{
deep: true,
immediate: true
}
)
/** 删除标签 tagValue 标签值*/
const handleClose = (index, valueIndex) => {
const av = attributeList.value[index].attributeValues
av.splice(valueIndex, 1)
}
/** 显示输入框并获取焦点 */
const showInput = (index) => {
inputVisible.value = true
nextTick(() => {
InputRef.value[index]!.input!.focus()
})
}
/** 输入框失去焦点或点击回车时触发 */
const handleInputConfirm = (index) => {
if (inputValue.value) {
// refindexref
attributeList.value[index].attributeValues.push({ name: inputValue.value })
}
inputVisible.value = false
inputValue.value = ''
}
</script>

View File

@ -0,0 +1,82 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" name="ProductPropertyForm" setup>
import * as PropertyApi from '@/api/mall/product/property'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('添加商品属性') //
const formLoading = ref(false) // 12
const formData = ref({
name: '',
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
resetForm()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
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)
emit('success', { id: propertyId, ...formData.value, values: [] })
} else {
emit(res[0]) //
}
message.success(t('common.createSuccess'))
dialogVisible.value = false
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
name: '',
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,86 @@
<template>
<el-table :data="SkuData" border class="tabNumWidth" 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>
<el-table-column align="center" label="商品条码" min-width="120">
<template #default="{ row }">
<el-input v-model="row.barCode" :min="0" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="销售价(分)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.price" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="市场价(分)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.marketPrice" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="成本价(分)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.costPrice" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="120">
<template #default="{ row }">
<el-input v-model="row.stock" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.weight" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.volume" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<template v-if="subCommissionType">
<el-table-column align="center" label="一级返佣(分)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.subCommissionFirstPrice" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(分)" min-width="120">
<template #default="{ row }">
<el-input v-model="row.subCommissionSecondPrice" :min="0" class="w-100%" type="number" />
</template>
</el-table-column>
</template>
</el-table>
</template>
<script lang="ts" name="index" setup>
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import { PropType } from 'vue'
import type { SkuType } from '@/api/mall/product/management/type/skuType'
const props = defineProps({
skuData: {
type: Array as PropType<SkuType>,
default: () => []
},
subCommissionType: propTypes.bool.def(false) //
})
const SkuData = ref<SkuType[]>([])
/**
* 将传进来的值赋值给SkuData
*/
watch(
() => props.skuData,
(data) => {
if (!data) return
SkuData.value = data
},
{
deep: true,
immediate: true
}
)
</script>

View File

@ -47,12 +47,7 @@
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button
v-hasPermi="['product:brand:create']"
plain
type="primary"
@click="openForm('create')"
>
<el-button v-hasPermi="['product:brand:create']" plain type="primary" @click="openForm">
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
@ -133,8 +128,8 @@
</template>
<script lang="ts" name="ProductManagement" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { dateFormatter } from '@/utils/formatTime' // api
import * as managementApi from '@/api/mall/product/management/spu' // const message = useMessage() // 消息弹窗
// const message = useMessage() //
// const { t } = useI18n() //
const { push } = useRouter() //
@ -182,9 +177,9 @@ const queryFormRef = ref() // 搜索的表单
const getList = async () => {
loading.value = true
try {
// const data = await ProductBrandApi.getBrandParam(queryParams)
// list.value = data.list
// total.value = data.total
const data = await managementApi.getSkuList(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
@ -201,7 +196,10 @@ const resetQuery = () => {
handleQuery()
}
const openForm = () => {
const openForm = (id?: number) => {
if (typeof id === 'number') {
push('/product/productManagementAdd?id=' + id)
}
push('/product/productManagementAdd')
}

View File

@ -2,42 +2,49 @@
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button
v-hasPermi="['product:property:create']"
plain
type="primary"
@click="openForm('create')"
v-hasPermi="['product:property:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
</el-form-item>
</el-form>
@ -46,23 +53,23 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名称" align="center" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="名称" prop="name" />
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
<el-table-column
label="创建时间"
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button
v-hasPermi="['product:property:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['product:property:update']"
>
编辑
</el-button>
@ -70,10 +77,10 @@
<router-link :to="'/property/value/' + scope.row.id">属性值</router-link>
</el-button>
<el-button
v-hasPermi="['product:property:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['product:property:delete']"
>
删除
</el-button>
@ -82,9 +89,9 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
@ -92,10 +99,11 @@
<!-- 表单弹窗添加/修改 -->
<PropertyForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="ProductProperty">
<script lang="ts" name="ProductProperty" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as PropertyApi from '@/api/mall/product/property'
import PropertyForm from './PropertyForm.vue'
const message = useMessage() //
const { t } = useI18n() //