商品管理: 完善表单校验,优化信息提示,完成新建、编辑、提交逻辑

This commit is contained in:
puhui999 2023-04-26 17:17:39 +08:00
parent a0014bed65
commit c38abc365c
7 changed files with 374 additions and 133 deletions

View File

@ -0,0 +1,22 @@
export interface SpuType {
name?: string // 商品名称
categoryId?: number // 商品分类
keyword?: string // 关键字
unit?: string // 单位
picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介
deliveryTemplateId?: number // 运费模版
selectRule?: string // 选择规格 TODO 暂时定义
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
description?: string // 商品详情
sort?: string // 商品排序
giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
}

17
src/utils/object.ts Normal file
View File

@ -0,0 +1,17 @@
/**
* target: {a:1} source:{a:2,b:3} {a:2}
* @param target
* @param source
*/
export const copyValueToTarget = (target, source) => {
const newObj = Object.assign({}, target, source)
// 删除多余属性
Object.keys(newObj).forEach((key) => {
// 如果不是target中的属性则删除
if (Object.keys(target).indexOf(key) === -1) {
delete newObj[key]
}
})
// 更新目标对象值
Object.assign(target, newObj)
}

View File

@ -2,13 +2,25 @@
<ContentWrap v-loading="formLoading">
<el-tabs v-model="activeName">
<el-tab-pane label="商品信息" name="basicInfo">
<BasicInfoForm ref="basicInfoRef" />
<BasicInfoForm
ref="BasicInfoRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
<el-tab-pane label="商品详情" name="description">
<DescriptionForm ref="DescriptionRef" />
<DescriptionForm
ref="DescriptionRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
<el-tab-pane label="其他设置" name="otherSettings">
<OtherSettingsForm ref="otherSettingsRef" />
<OtherSettingsForm
ref="OtherSettingsRef"
v-model:activeName="activeName"
:propFormData="formData"
/>
</el-tab-pane>
</el-tabs>
<el-form>
@ -22,6 +34,7 @@
<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() // 国际化
// const { t } = useI18n() //
// const message = useMessage() //
@ -30,18 +43,77 @@ const { push, currentRoute } = useRouter() // 路由
const { delView } = useTagsViewStore() //
const formLoading = ref(false) // 12
const activeName = ref('otherSettings') // Tag
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
const DescriptionRef = ref<ComponentRef<typeof DescriptionForm>>()
const activeName = ref('basicInfo') // Tag
const BasicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // Ref
const DescriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // Ref
const OtherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // Ref
const formData = ref<SpuType>({
name: '', //
categoryId: 0, //
keyword: '', //
unit: '', //
picUrl: '', //
sliderPicUrls: [], //
introduction: '', //
deliveryTemplateId: 0, //
selectRule: '',
specType: false, //
subCommissionType: false, //
description: '', //
sort: 1, //
giveIntegral: 1, //
virtualSalesCount: 1, //
recommendHot: false, //
recommendBenefit: false, //
recommendBest: false, //
recommendNew: false, //
recommendGood: false //
})
/** 获得详情 */
const getDetail = async () => {}
/** 提交按钮 */
const submitForm = async () => {}
const submitForm = async () => {
// TODO
//
try {
await unref(BasicInfoRef)?.validate()
await unref(DescriptionRef)?.validate()
await unref(OtherSettingsRef)?.validate()
//
console.log(formData.value)
} catch {}
}
/**
* 重置表单
*/
const resetForm = async () => {
formData.value = {
name: '', //
categoryId: 0, //
keyword: '', //
unit: '', //
picUrl: '', //
sliderPicUrls: [], //
introduction: '', //
deliveryTemplateId: 0, //
selectRule: '',
specType: false, //
subCommissionType: false, //
description: '', //
sort: 1, //
giveIntegral: 1, //
virtualSalesCount: 1, //
recommendHot: false, //
recommendBenefit: false, //
recommendBest: false, //
recommendNew: false, //
recommendGood: false //
}
}
/** 关闭按钮 */
const close = () => {
resetForm()
delView(unref(currentRoute))
push('/product/product-management')
}

View File

@ -1,5 +1,5 @@
<template>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="ProductManagementBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="商品名称" prop="name">
@ -40,26 +40,12 @@
</el-col>
<el-col :span="12">
<el-form-item label="商品封面图" prop="picUrl">
<div class="demo-image__preview pt-5px pb-5px pl-11x pr-11px">
<el-image
:initial-index="0"
:preview-src-list="srcList"
:src="url"
:zoom-rate="1.2"
fit="cover"
style="width: 100%; height: 90px"
/>
</div>
<UploadImg v-model="formData.picUrl" height="80px" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="商品轮播图" prop="sliderPicUrls">
<el-button>添加轮播图</el-button>
<el-carousel :interval="3000" height="200px" style="width: 100%" type="card">
<el-carousel-item v-for="item in 6" :key="item">
<h3 justify="center" text="2xl">{{ item }}</h3>
</el-carousel-item>
</el-carousel>
<UploadImgs v-model="formData.sliderPicUrls" />
</el-form-item>
</el-col>
<el-col :span="12">
@ -72,6 +58,7 @@
<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)">
@ -113,23 +100,31 @@
</el-form>
</template>
<script lang="ts" name="ProductManagementBasicInfoForm" setup>
// TODO
import { PropType } from 'vue'
import { defaultProps } from '@/utils/tree'
import type { SpuType } from '@/api/mall/product/management/type'
import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { copyValueToTarget } from '@/utils/object'
const url = 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']
const message = useMessage() //
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
}
})
const formRef = ref()
const formData = ref({
const ProductManagementBasicInfoRef = ref() // Ref
const formData = ref<SpuType>({
name: '', //
categoryId: '', //
categoryId: 155415, //
keyword: '', //
unit: '', //
picUrl: '', //
sliderPicUrls: [], //
introduction: '', //
deliveryTemplateId: '', //
selectRule: '',
selectRule: '', // TODO
specType: false, //
subCommissionType: false //
})
@ -138,12 +133,47 @@ const rules = reactive({
categoryId: [required],
keyword: [required],
unit: [required],
introduction: [required],
picUrl: [required],
sliderPicUrls: [required],
deliveryTemplateId: [required],
specType: [required],
subCommissionType: [required]
sliderPicUrls: [required]
// deliveryTemplateId: [required],
// specType: [required],
// subCommissionType: [required],
})
/**
* 将传进来的值赋值给formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
copyValueToTarget(formData.value, data)
},
{
deep: true,
immediate: true
}
)
const emit = defineEmits(['update:activeName'])
/**
* 表单校验
*/
const validate = async () => {
//
if (!ProductManagementBasicInfoRef) return
return await unref(ProductManagementBasicInfoRef).validate((valid) => {
if (!valid) {
message.warning('商品信息未完善!!')
emit('update:activeName', 'basicInfo')
//
throw new Error('商品信息未完善!!')
} else {
//
Object.assign(props.propFormData, formData.value)
}
})
}
defineExpose({ validate })
//
const changeSpecType = (specType) => {
console.log(specType)
@ -157,35 +187,3 @@ const confirm = () => {}
//
const addRule = () => {}
</script>
<style scoped>
/*TODO 商品轮播图测试样式*/
.el-carousel__item h3 {
color: #475669;
opacity: 0.75;
line-height: 200px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
/*TODO 商品封面测试样式*/
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
</style>

View File

@ -1,13 +1,79 @@
<template>
<!--富文本编辑器组件-->
<el-row>
<Editor v-model="content" :editor-config="editorConfig" />
</el-row>
<el-form ref="DescriptionFormRef" :model="formData" :rules="rules" label-width="120px">
<!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" />
</el-form-item>
</el-form>
</template>
<script lang="ts" name="DescriptionForm" setup>
import type { SpuType } from '@/api/mall/product/management/type'
import { Editor } from '@/components/Editor'
import { createEditorConfig } from '@/views/mp/draft/editor-config'
// TODO
const content = ref('')
const editorConfig = createEditorConfig('', 1)
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'
const message = useMessage() //
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
}
})
const DescriptionFormRef = ref() // Ref
const formData = ref<SpuType>({
description: '' //
})
/**
* 富文本编辑器如果输入过再清空会有残留需再重置一次
*/
watch(
() => formData.value.description,
(newValue) => {
if ('<p><br></p>' === newValue) {
formData.value.description = ''
}
},
{
deep: true,
immediate: true
}
)
//
const rules = reactive({
description: [required]
})
/**
* 将传进来的值赋值给formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
copyValueToTarget(formData.value, data)
},
{
deep: true,
immediate: true
}
)
const emit = defineEmits(['update:activeName'])
/**
* 表单校验
*/
const validate = async () => {
//
if (!DescriptionFormRef) return
return unref(DescriptionFormRef).validate((valid) => {
if (!valid) {
message.warning('商品详情为完善!!')
emit('update:activeName', 'description')
//
throw new Error('商品详情为完善!!')
} else {
//
Object.assign(props.propFormData, formData.value)
}
})
}
defineExpose({ validate })
</script>

View File

@ -1,19 +1,19 @@
<template>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form ref="OtherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
<el-row>
<el-col :span="24">
<el-col :span="8">
<el-form-item label="商品排序">
<el-form-item label="商品排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="赠送积分">
<el-form-item label="赠送积分" prop="giveIntegral">
<el-input-number v-model="formData.giveIntegral" :min="0" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟销量">
<el-form-item label="虚拟销量" prop="virtualSalesCount">
<el-input-number
v-model="formData.virtualSalesCount"
:min="0"
@ -50,6 +50,18 @@
</template>
<script lang="ts" name="OtherSettingsForm" setup>
//
import type { SpuType } from '@/api/mall/product/management/type'
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils/object'
const message = useMessage() //
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
}
})
//
const recommend = [
{ name: '是否热卖', value: 'recommendHot' },
{ name: '是否优惠', value: 'recommendBenefit' },
@ -57,7 +69,9 @@ const recommend = [
{ name: '是否新品', value: 'recommendNew' },
{ name: '是否优品', value: 'recommendGood' }
]
const checkboxGroup = ref<string[]>([])
//
const checkboxGroup = ref<string[]>(['recommendHot'])
//
const onChangeGroup = () => {
checkboxGroup.value.includes('recommendHot')
? (formData.value.recommendHot = true)
@ -75,20 +89,63 @@ const onChangeGroup = () => {
? (formData.value.recommendGood = true)
: (formData.value.recommendGood = false)
}
const formRef = ref()
const formData = ref({
sort: '',
giveIntegral: 666,
virtualSalesCount: 565656,
recommendHot: false,
recommendBenefit: false,
recommendBest: false,
recommendNew: false,
recommendGood: false
const OtherSettingsFormRef = ref() // Ref
//
const formData = ref<SpuType>({
sort: 12, //
giveIntegral: 666, //
virtualSalesCount: 565656, //
recommendHot: false, //
recommendBenefit: false, //
recommendBest: false, //
recommendNew: false, //
recommendGood: false //
})
//
const rules = reactive({
sort: [required],
giveIntegral: [required],
virtualSalesCount: [required]
})
/**
* 将传进来的值赋值给formData
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
copyValueToTarget(formData.value, data)
checkboxGroup.value = []
formData.value.recommendHot ? checkboxGroup.value.push('recommendHot') : ''
formData.value.recommendBenefit ? checkboxGroup.value.push('recommendBenefit') : ''
formData.value.recommendBest ? checkboxGroup.value.push('recommendBest') : ''
formData.value.recommendNew ? checkboxGroup.value.push('recommendNew') : ''
formData.value.recommendGood ? checkboxGroup.value.push('recommendGood') : ''
},
{
deep: true,
immediate: true
}
)
const emit = defineEmits(['update:activeName'])
/**
* 表单校验
*/
const validate = async () => {
//
if (!OtherSettingsFormRef) return
return await unref(OtherSettingsFormRef).validate((valid) => {
if (!valid) {
message.warning('商品其他设置未完善!!')
emit('update:activeName', 'otherSettings')
//
throw new Error('商品其他设置未完善!!')
} else {
//
Object.assign(props.propFormData, formData.value)
}
})
}
defineExpose({ validate })
</script>

View File

@ -2,36 +2,36 @@
<!-- 搜索工作栏 -->
<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="type">
<el-input
v-model="queryParams.type"
placeholder="请输入字典类型"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入字典类型"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择字典状态"
clearable
class="!w-240px"
clearable
placeholder="请选择字典状态"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -44,33 +44,41 @@
<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
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['system:dict:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<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
type="success"
v-hasPermi="['system:dict:create']"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:dict:export']"
type="primary"
@click="openForm('create')"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
<el-button
v-hasPermi="['system:dict:export']"
:loading="exportLoading"
plain
type="success"
@click="handleExport"
>
<Icon class="mr-5px" icon="ep:download" />
导出
</el-button>
</el-form-item>
</el-form>
@ -79,29 +87,29 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="字典编号" align="center" prop="id" />
<el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip />
<el-table-column label="字典类型" align="center" prop="type" width="300" />
<el-table-column label="状态" align="center" prop="status">
<el-table-column align="center" label="字典编号" prop="id" />
<el-table-column align="center" label="字典名称" prop="name" show-overflow-tooltip />
<el-table-column align="center" label="字典类型" prop="type" width="300" />
<el-table-column align="center" label="状态" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column align="center" label="备注" prop="remark" />
<el-table-column
label="创建时间"
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180"
/>
<el-table-column label="操作" align="center">
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button
v-hasPermi="['system:dict:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['system:dict:update']"
>
修改
</el-button>
@ -109,10 +117,10 @@
<el-button link type="primary">数据</el-button>
</router-link>
<el-button
v-hasPermi="['system:dict:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:dict:delete']"
>
删除
</el-button>
@ -121,9 +129,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>
@ -132,12 +140,13 @@
<DictTypeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="SystemDictType">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
<script lang="ts" name="SystemDictType" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as DictTypeApi from '@/api/system/dict/dict.type'
import DictTypeForm from './DictTypeForm.vue'
import download from '@/utils/download'
const message = useMessage() //
const { t } = useI18n() //