Files
sora_front/src/component/productComponent/editProduct.vue
2024-05-08 14:07:26 +08:00

769 lines
28 KiB
Vue

<template>
<div class="add_product_modal">
<a-modal class="add_product_modal_component"
wrapClassName="edit_modal"
:destroyOnClose="true"
v-model:visible="editProductModal"
:footer="null"
title="Edit Product"
width="900px"
:maskClosable="false"
:centered="true"
@cancel="closeProduct"
>
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
<a-form-item label="Product image" name="pictureUrl">
<div class="product_master_diagram">
<a-upload
:action="uploadUrl + '/api/product/uploadFile'"
class="edit_product_pic"
list-type="picture-card"
:data="{
...upload,
id:productDetail.id
}"
:headers="{Authorization:token}"
v-model:file-list="fileList"
:maxCount="1"
accept=".jpg,.png,.jpeg,.bmp"
@change="(file)=>fileUploadChange(file)"
>
<img class="product_master_img" :src="formState.pictureUrl">
</a-upload>
</div>
</a-form-item>
<a-form-item label="Product label(s)" name="productLabel">
<a-select v-model:value="formState.productLabel" mode="multiple" size="large" :options="productLabelList" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)" optionFilterProp="label"></a-select>
</a-form-item>
<a-form-item label="Price" name="price">
<a-input-number
style="width:100%"
v-model:value="formState.price"
:formatter="value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
:parser="value => value.replace(/\$\s?|(,*)/g, '')"
/>
</a-form-item>
<div class="form_item_block">
<!-- <a-form-item label="库存信息" name="inventoryData"></a-form-item> -->
<div class="form_item_title required">
Inventory
<!-- <div class="title_right_button">
<a-button class="primary_button" type="primary" @click="addStore">+ Add stores</a-button>
</div> -->
</div>
<div class="form_edit_table">
<a-table :columns="columns" :data-source="formState.storeList" bordered
:pagination="false" :scroll="{y: 400 }">
<template v-slot:bodyCell="{column,record}">
<template v-for="item in formState.storeNameList">
<template v-if="column.dataIndex === item">
<a-input class="store_input" v-model:value="record[item]" type="number" />
</template>
</template>
</template>
<!-- <template v-slot:bodyCell="{column,record}" >
<template v-if="column.dataIndex === 'XS'">
<a-input class="store_input" v-model:value="record.XS" type="number" />
</template>
<template v-if="column.dataIndex === 'S'">
<a-input class="store_input" v-model:value="record.S" type="number" />
</template>
<template v-if="column.dataIndex === 'M'">
<a-input class="store_input" v-model:value="record.M" type="number" />
</template>
<template v-if="column.dataIndex === 'L'">
<a-input class="store_input" v-model:value="record.L" type="number" />
</template>
<template v-if="column.dataIndex === 'XL'">
<a-input class="store_input" v-model:value="record.XL" type="number" />
</template>
<template v-if="column.dataIndex === 'XXL'">
<a-input class="store_input" v-model:value="record.XXL" type="number" />
</template>
</template> -->
</a-table>
</div>
</div>
<div class="form_item_block">
<div class="form_item_module_title">Fashion Attributes</div>
<a-form-item label="Select category" name="productCategory">
<a-select v-model:value="formState.productCategory" size="large" :options="productCategoryList" placeholder="Please select" @change="productCategoryChange" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)" optionFilterProp="label"></a-select>
</a-form-item>
<div class="sec_form_block">
<div class="form_item_title margin_bottom_10">Attribute(s)</div>
<div class="attribute_block" v-if="productAttributeList.length">
<div class="attribute_block_item" v-for="attr in productAttributeList" :key="attr.id">
<div class="attribute_block_item_content">
<div class="attribute_item_title">{{attr.labelType}}</div>
<a-select class="attribute_item_select" allowClear v-model:value="attr.value" size="large" :options="attr.attributeValueList" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)"></a-select>
</div>
</div>
</div>
<div v-else class="null_data_block">
<img class="null_data_img" src="@/assets/images/null_img.png">
</div>
</div>
</div>
<div class="form_item_block">
<!-- <a-form-item label="库存信息" name="inventoryData"></a-form-item> -->
<div class="form_item_title">
Mix and Match(es)
<div class="title_right_button">
<a-button class="primary_button" type="primary" @click="addLook">+ Add Look</a-button>
</div>
</div>
<div class="product_match_block" v-show="formState.assortmentList.length">
<div class="product_match_item" v-for="(match,index) in formState.assortmentList" :key="match" v-show="index<5 || assortmentShowMore">
<div class="product_match_item_header">
<div>Look{{index+1}}</div>
<div class="item_header_button_list">
<a-button class="primary_button btn-margin-r-20" type="primary" @click="deleteSingleAllMatch(index)">Delete</a-button>
<a-button class="primary_button" type="primary" @click="addProductMatch(match,index)" v-show="match.length<6">Add Item</a-button>
</div>
</div>
<div class="product_match_item_content">
<div class="match_item_img_block" v-for="(img,imgIndex) in match" :key="img.id">
<img :src="img.generatePictureUrl">
<div class="delete_img_block" @click="deleteSignleMatchImg(index,imgIndex)">
<span class="icon iconfont delete_icon icon-guanbi"></span>
</div>
</div>
</div>
</div>
<a-button class="primary_button show_more_button" type="primary" @click="assortmentListShowMore()" v-show="formState.assortmentList.length>5">{{assortmentShowMore?'Hide':'See More'}}</a-button>
</div>
</div>
</a-form>
<div class="modal_button_list">
<a-button class="default_button btn-margin-r-20" size="large" @click="closeProduct">Cancel</a-button>
<a-button class="primary_button" type="primary" size="large" @click="submitProduct" :loading="submitLoading">Submit</a-button>
</div>
</a-modal>
<ProductMatchModal ref="ProductMatchModalComponent" :productCategoryList="productCategoryList" @selectMatchFile="selectMatchFile"></ProductMatchModal>
<AddStoreModal ref="addStoreModalComponent" @storeChange="storeChange"></AddStoreModal>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,reactive,toRefs,onMounted, computed, nextTick } from "vue";
import ProductMatchModal from '@/component/productComponent/productMatchModal.vue'
import AddStoreModal from '@/component/productComponent/addStoreModa.vue'
import {getCookie} from '@/tool/cookie'
import {getUploadUrl} from '@/tool/util'
import { Https } from "@/tool/https";
import { message,Modal } from "ant-design-vue";
export default defineComponent({
name:'EditProduct',
components:{ProductMatchModal,AddStoreModal},
props:{
productLabelList:{
type:Array,
}
},
emits:['refreshList'],
setup(props,{ emit }){
let formRef = ref();
let submitLoading:any = ref(false)
let addStoreModalComponent:any = ref(null)
let uploadUrl:any = ref('')
let fileList:any = ref([])
let token:any = ref('')
let upload:any = reactive({
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
})
let editProductModal = ref(false)
let productPicSeletcModal:any = ref()
let ProductMatchModalComponent:any =ref()
let productDetail:any =ref({})
let formState:any = ref({
pictureName:'',
pictureUrl:'',
productLabel:[],
inventoryData:[],
storeList:[
],
storeNameList:[],
productCategory:[],
productAttribute:[],
assortmentList:[]
});
let productCategoryList:any = ref([])
let productAttributeList = ref([])
let columns = reactive([
{ title: 'Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',width:"200px" },
{
title: 'Inventory',
align:'center',
ellipsis: true,
dataIndex: 'inventory',
key: 'inventory',
children: [
// {title: 'XS',dataIndex: 'XS',key: 'XS', align:'center', },
// {title: 'S',dataIndex: 'S',key: 'S', align:'center',},
// {title: 'M',dataIndex: 'M',key: 'M', align:'center',},
// {title: 'L',dataIndex: 'L',key: 'L', align:'center',},
// {title: 'XL',dataIndex: 'XL',key: 'XL', align:'center',},
// {title: 'XXL',dataIndex: 'XXL',key: 'XXL', align:'center',},
],
},
])
let addMatchIndex:number = 0
let rules = {
pictureUrl: [
{ required: true, trigger: 'blur' },
],
productLabel: [
{ required: true, message: 'Please select product label', trigger: 'blur' },
],
inventoryData:[
{ required: true, trigger: 'blur' },
],
productCategory:[
{ required: true, message: 'Please select', trigger: 'blur' },
],
productAttribute:[
{ required: true, message: 'Please select', trigger: 'blur' },
]
}
let assortmentShowMore = ref(false)
let getPopupContainer = (triggerNode:any) =>{
return triggerNode.parentNode || document.body
}
let closeProduct = () =>{
formState.value = {
pictureName:'',
pictureUrl:'',
productLabel:[],
inventoryData:[],
storeList:[],
productCategory:[],
productAttribute:[],
assortmentList:[],
storeNameList:[],
}
productAttributeList.value =[]
editProductModal.value = false
addStoreModalComponent.value.clearStoreData()
}
let showModal = (data:any) =>{
let record = JSON.parse(JSON.stringify(data.record))
let store = getStoreInfo(record.storeInfoList)
let labelItemList = JSON.parse(JSON.stringify(data.labelItemList))
editProductModal.value = true
productDetail.value = record
formState.value = {
pictureName:record.pictureName,
pictureUrl:record.pictureUrl,
productLabel:record.productLabelInfo.map((v:any)=>v.id),
price:record.price,
productCategory:record.attributeItemInfo.labelItem,
storeList:store.storeList,
storeNameList:store.storeNameList,
assortmentList:record.assortmentList,
}
productCategoryList.value = getProductCategoryList(labelItemList)
productAttributeList.value = getProductAttributeList()
formState.value.productAttribute = productAttributeList.value
}
let getProductAttributeList = () => {
let attributeList = productCategoryList.value.filter((v:any) => v.labelItem == formState.value.productCategory)
if(attributeList && attributeList.length){
attributeList = attributeList[0]
}else{
return []
}
let attributeValueList = attributeList.attributeTypeList.map((v:any) =>{
v.value = ''
for(let item of productDetail.value.attributeItemInfo.attributeTypeList){
if(v.labelType === item.labelType){
v.value = item.attributeValueList.join(',')
}
}
return v
})
return attributeValueList
}
let getProductCategoryList =(labelItemList:any)=>{
let newLabelItemList = []
let labelItemListNew = JSON.parse(JSON.stringify(labelItemList))
for(let item of labelItemListNew){
item.attributeTypeList = item.attributeTypeList.map((v:any) =>{
let data = {
...v,
value:v.value || [],
attributeValueList:v.attributeValueList.map((v:any)=>{
let valueData = {
value:v,
label:v,
}
return valueData
})
}
return data
})
newLabelItemList.push(item)
}
return newLabelItemList
}
let getStoreInfo = (storeInfo:any) =>{
let storeList = []
let storeNameList:any = []
for(let item of storeInfo){
let sizeData:any = {}
for(let size of item.stock){
sizeData[size.size] = size.num
}
let data = {
storeName:item.storeName,
storeId:item.storeId,
...sizeData,
}
storeList.push(data)
}
storeInfo.forEach((item:any) => {
item.stock.forEach((stockItem:any) => {
storeNameList.push(stockItem.size);
});
});
// 排序并去重
storeNameList = storeNameList.sort().filter((value:any, index:any, array:any) => {
return array.indexOf(value) === index;
});
let children:any = []
storeNameList.forEach((item:any) => {
children.push({
title:item,
dataIndex:item,
key:item,
align:'center'
})
});
columns[1].children = children
return {storeList,storeNameList}
}
let openUploadModal = () =>{
productPicSeletcModal.value.openPictureModal()
}
//删除单条的全部搭配
let deleteSingleAllMatch = (index:number) =>{
formState.value.assortmentList.splice(index,1)
}
let deleteSignleMatchImg = (matchindex:any,index:number) =>{
if(formState.value.assortmentList[matchindex].length == 1){
deleteSingleAllMatch(matchindex)
}else{
formState.value.assortmentList[matchindex].splice(index,1)
}
}
let addProductMatch = (match:any,index:number) =>{
addMatchIndex = index
let assortmentIdList = match.map((v:any)=>{
return v.id
})
ProductMatchModalComponent.value.openMatchModal(assortmentIdList,productCategoryList.value)
}
let submitStore= () => {
}
let productCategoryChange = (value:any,selectedOptions:any) => {
let attributeList = selectedOptions ? selectedOptions.attributeTypeList :[]
productAttributeList.value = attributeList
formState.value.productAttribute = attributeList
}
let fileUploadChange = (data:any) =>{
let file = data.file
if(file.status === 'done'){
let res = JSON.parse(file.xhr.response)
if(res.data){
productDetail.value.md5 = res.data.md5
formState.value.pictureUrl = res.data.pictureUrl
}else{
message.error(res.errMsg)
}
}else if(file.status === 'error'){
message.error(file.name + 'upload failed')
}
}
let addStore = () =>{
let data = {
type:'edit',
storeData:formState.value.storeList,
storeNameData:formState.value.storeNameList,
}
addStoreModalComponent.value.openAddStoreModal(data)
}
let storeChange = (event:any) =>{
formState.value.storeList = formState.value.storeList.concat(event)
}
let submitProduct = () =>{
formRef.value.validate().then(()=>{
submit()
})
let submit = () =>{
let data = {
id:productDetail.value.id,
pictureName:formState.value.pictureName,
pictureUrl:formState.value.pictureUrl,
price:formState.value.price,
productLabelInfo:getSubmitProductLable(),
storeInfoList:getStoreList(),
attributeItemInfo:getAttributeValueList(),
assortmentList:getAssortmentList(),
md5:productDetail.value.md5,
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
}
submitLoading.value = true
Https.axiosPost(Https.httpUrls.productEdit, data).then(
(rv: any) => {
if (rv) {
submitLoading.value = false
message.success('Edit success')
emit('refreshList')
closeProduct()
}
}
).catch(rv=>{
submitLoading.value = false
});
}
}
let getAttributeValueList = () =>{
let data:any = {
labelItem:formState.value.productCategory
}
let attributeTypeList = formState.value.productAttribute.map((v:any)=>{
let newData = {
id:v.id,
labelType:v.labelType,
attributeValueList:v.value && v.value.length ? [v.value] : []
}
return newData
})
data.attributeTypeList = attributeTypeList
return data
}
let getStoreList = () =>{
let field:any = formState.value.storeNameList
let storeInfos = []
for(let v of formState.value.storeList){
let data:any = {
stock:[],
storeId:v.storeId,
storeName:v.storeName
}
for(let key in v){
if(field.indexOf(key) > -1){
let sizeData:any = {
num:v[key],
size:key
}
data.stock.push(sizeData)
}
}
storeInfos.push(data)
}
return storeInfos
}
let getSubmitProductLable = () =>{
let labelList:any = props.productLabelList?.filter((v:any)=>formState.value.productLabel.indexOf(v.id) > -1)
labelList = labelList.map((v:any) => {
return {
id:v.id,
name:v.name,
type:v.type,
}
})
labelList = labelList.length ? labelList :productDetail.value.productLabelInfo
return labelList
}
let getAssortmentList = () =>{
let assortmentList = formState.value.assortmentList.map((v:any,index:any)=>{
let data = v
for(let item of data){
item.coverSort = index + 1
}
return data
})
return assortmentList
}
//选择搭配的方法
let selectMatchFile = (file:any) =>{
let data = {
generatePictureUrl:file.pictureUrl,
price:file.price,
productId:file.productId,
}
formState.value.assortmentList[addMatchIndex].push(data)
}
//增加搭配
let addLook = () =>{
let formStateNew:any = productDetail.value
let data = [{
generatePictureUrl:formStateNew.pictureUrl,
price:formStateNew.price,
productId:formStateNew.id,
}]
formState.value.assortmentList.push(data)
assortmentShowMore.value = true
nextTick(()=>{
document.getElementsByClassName('edit_modal')[0].scrollTo(0, document.getElementsByClassName('edit_modal')[0].scrollHeight)
})
}
let assortmentListShowMore = () =>{
assortmentShowMore.value = !assortmentShowMore.value
}
onMounted(() => {
uploadUrl.value = getUploadUrl()
token.value = getCookie('token') || ''
})
return {
formRef,
submitLoading,
addStoreModalComponent,
uploadUrl,
upload,
token,
fileList,
editProductModal,
columns,
productDetail,
productPicSeletcModal,
ProductMatchModalComponent,
formState,
productCategoryList,
productAttributeList,
rules,
getPopupContainer,
closeProduct,
showModal,
openUploadModal,
deleteSingleAllMatch,
deleteSignleMatchImg,
addProductMatch,
submitStore,
productCategoryChange,
fileUploadChange,
addStore,
storeChange,
submitProduct,
selectMatchFile,
addLook,
assortmentShowMore,
assortmentListShowMore
}
},
});
</script>
<style lang="less" scoped>
.add_product_modal_component{
.product_master_diagram{
width: 400px;
height: 400px;
margin: 10px auto;
display: flex;
align-items: center;
justify-content: center;
.product_master_img{
max-width: 100%;
max-height: 100%;
}
}
.store_input{
text-align: center;
}
.attribute_block{
background: #F7F8FC;
border: 1px solid #DFDFDF;
padding-top: 30px;
.attribute_block_item{
padding: 0 22px;
display: inline-block;
margin-bottom: 30px;
width: 50%;
.attribute_block_item_content{
display: flex;
align-items: center;
.attribute_item_title{
width: 120px;
margin-right: 20px;
}
.attribute_item_select{
max-width: 275px;
width: 250px;
}
}
}
}
.null_data_block{
background: #F7F8FC;
border: 1px solid #DFDFDF;
padding: 30px 0;
text-align: center;
.null_data_img{
width: 130px;
}
}
.product_match_block{
margin-top: 20px;
.product_match_item{
background: #F7F8FC;
border: 1px solid #DFDFDF;
padding: 0 16px 26px;
margin-bottom: 20px;
.product_match_item_header{
height: 83px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
color: #030303;
}
.product_match_item_content{
display: flex;
.match_item_img_block{
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 90px;
margin-right: 20px;
position: relative;
border: 1px solid #DFDFDF;
img{
max-width: 100%;
max-height: 100%;
}
.delete_img_block{
position: absolute;
right: -10px;
top: -10px;
width: 20px;
height: 20px;
background: #000000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.delete_icon{
font-size: 16px;
color: #fff;
}
}
&:last-child{
margin-right: 0;
}
}
}
}
}
.form_edit_table{
margin: 20px 0;
}
.margin_bottom_10{
margin-bottom: 10px;
}
.show_more_button{
margin: 20px auto;
display: block;
}
}
</style>
<style lang="less">
.edit_product_pic{
width: 100%;
height: 100%;
.ant-upload-list{
width: 100%;
height: 100%;
.ant-upload-select-picture-card{
width: 100%;
height: 100%;
background: #fff;
border: none;
}
}
}
</style>