This commit is contained in:
shahaibo
2024-04-02 16:15:22 +08:00
commit 67f5ce46ee
61 changed files with 30215 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
<template>
<div class="filter_component">
<div class="filter_component_content">
<span class="filter_title" :style="titleStyle">{{title}}</span>
<div class="slot_content" :style="slotStyle">
<slot class="slot_style"></slot>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent,ref } from "vue";
export default defineComponent({
name:'filterComponent',
props:{
title:{
type:String
},
titleStyle:{
type:Object
},
slotStyle:{
type:Object
}
},
setup(){
return {
}
},
});
</script>
<style lang="less">
.filter_component{
display: inline-block;
margin-right: 40px;
margin-bottom: 30px;
.filter_component_content{
display: flex;
align-items: center;
.filter_title{
font-size: 16px;
font-family: Roboto;
font-weight: 400;
color: #030303;
margin-right: 15px;
flex-shrink: 0;
display: block;
min-width: 150px;
text-align: right;
}
.slot_content{
width: 280px;
.ant-picker-large{
width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,385 @@
<template>
<div class="add_product_modal">
<a-modal class="add_product_modal_component"
v-model:visible="addProductModal"
:footer="null"
title="Add Product"
width="680px"
:closable="false"
:destroyOnClose="true"
:maskClosable="false"
:centered="true"
:keyboard="false"
>
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
<a-form-item label="Add picture" name="pictureList">
<div class="picture_list">
<div class="picture_list_block" v-show="showPicture.length">
<div class="pricture_item_block" v-for="pic in showPicture" :key="pic">
<img class="pricture_item" :src="pic?.resData.pictureUrl">
</div>
</div>
<div class="pricture_item_block add_pic_button" @click="openUploadModal">
<div class="add_pic_button_content">
<span class="icon iconfont add_pic_button_icon icon-tianjiatupian_huaban"></span>
<div>{{showPicture.length ? '继续上传' :'上传'}}</div>
</div>
</div>
</div>
</a-form-item>
<a-form-item label="Label attributes" name="productLabel">
<a-select v-model:value="formState.productLabel" mode="multiple" size="large" optionFilterProp="label" :options="productLabelList" showArrow></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>
<a-form-item label="Store Inventory" name="storeInventory">
<div class="store_inventory_block">
<div class="store_inventory_content">{{storeNameList}}</div>
<a-button class="primary_button" type="primary" @click="selectStore">Choose store</a-button>
</div>
</a-form-item> -->
</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" :loading="submitLoading" @click="submitAddProduct">Submit</a-button>
</div>
<!-- design collection的进度蒙层 start-->
<div class="progress_mark" v-show="showDesignMark">
<div class="mark_content">
<a-progress type="circle" :percent="designProgress" :width="200"/>
<div class="mark_content_spin">
<a-spin :indicator="indicator"/>
</div>
<div class="upload_tip" v-show="uploadType == 'Attribute'">Recognizing garment, please wait.</div>
<div class="upload_tip" v-show="uploadType == 'Collocation'">Generating outfit, please wait.</div>
</div>
</div>
<!-- design collection的进度蒙层 end-->
</a-modal>
<ProductPicUpload ref="productPicUploadModal" @uploadPicChange="uploadPicChange"></ProductPicUpload>
<AddStoreModal ref="addStoreModalComponent" @storeChange="storeChange"></AddStoreModal>
</div>
</template>
<script lang="ts">
import { defineComponent,h,ref,reactive,toRefs,onMounted, computed,createVNode } from "vue";
import ProductPicUpload from '@/component/productComponent/productPicUpload.vue'
import AddStoreModal from '@/component/productComponent/addStoreModa.vue'
import { Https } from "@/tool/https";
import { message,Modal } from "ant-design-vue";
import {unique} from '@/tool/util'
import { WarningOutlined,LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name:'AddProduct',
components:{ProductPicUpload,AddStoreModal},
emits: ['confirmSubmitAdd'],
props:{
productLabelList:{
type:Array,
}
},
setup(props,{ emit }){
let formRef = ref();
let indicator :any = h(LoadingOutlined, {
style: {
fontSize: '24px',
},
spin: true,
})
let addProductModal = ref(false)
let showDesignMark = ref(false)
let designProgress:any = ref(0)
let uploadType:any = ref('Attribute')
let addStoreModal = ref(false)
let productPicUploadModal:any = ref(null)
let formState:any = ref({
pictureList: [],
productLabel:[],
price:'',
storeInventory:'',
});
let submitLoading = ref(false)
let addStoreModalComponent:any = ref(null)
let storeNameList:any = ref('')
let showPicture = computed(()=> formState.value.pictureList.length > 4 ? formState.value.pictureList.slice(0,4) : formState.value.pictureList)
let rules = {
pictureList: [
{ required: true, message: 'Please upload product image', trigger: 'blur' },
],
productLabel: [
{ required: true, message: 'Please select product type', trigger: 'blur' },
],
price: [
{ required: true, message: 'Please input price', trigger: 'blur' },
],
storeInventory:[
{ required: true, message: 'Please select store',trigger: 'blur' },
]
}
let showModal = (data:any) =>{
addProductModal.value = true
}
let closeProduct = () =>{
Modal.confirm({
title: "Are you sure you want to close the popup? Closing the popup will clear the set data.",
icon: createVNode(WarningOutlined),
class:'confirm_style',
okText: 'Ok',
cancelText: 'Cancel',
// centered:true,
onOk() {
formState.value = {
pictureList: '',
productLabel:[],
storeInventory:'',
}
clearChildData()
storeNameList.value = ''
addProductModal.value = false
}
});
}
let openUploadModal = () =>{
productPicUploadModal.value.openPictureModal()
}
let selectStore = () =>{
addStoreModalComponent.value.openAddStoreModal({type:'add'})
}
let uploadPicChange = (event:any) =>{
formState.value.pictureList = event.fileList
formRef.value.validateFields(['pictureList'])
}
let storeChange = (event:any) =>{
formState.value.storeInventory = event
storeNameList.value = event.map((v:any) => v.storeName).join(',')
formRef.value.validateFields(['storeInventory'])
}
let submitAddProduct = () =>{
formRef.value.validate().then(()=>{
submit()
})
let submit = () =>{
let pictureList = unique(formState.value.pictureList, 'md5')
let productIds = pictureList.map((v:any)=>{
return v.resData.productId
})
let data = {
labelIds:formState.value.productLabel,
productIds:productIds,
// price:formState.value.price,
// storeInfos:formState.value.storeInventory,
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
}
submitLoading.value = true
showDesignMark.value = true
Https.axiosPost(Https.httpUrls.batchUploadProductRelation, data).then(
(rv: any) => {
if (rv) {
submitLoading.value = false
showDesignMark.value = false
emit('confirmSubmitAdd')
formState.value = {
pictureList: '',
productLabel:[],
storeInventory:'',
}
clearChildData()
addProductModal.value = false
storeNameList.value = ''
designProgress.value = 0
uploadType.value = 'Attribute'
}
}
).catch((rv:any)=>{
submitLoading.value = false
showDesignMark.value = false
designProgress.value = 0
uploadType.value = 'Attribute'
});
submitProcess()
}
}
let submitProcess = () =>{
Https.axiosPost(Https.httpUrls.countProductUpdateProcess, {}).then(
(rv: any) => {
if(rv.status != 1 && showDesignMark.value){
let designProgressNew:any = rv.process * 100
designProgress.value = parseInt(designProgressNew)
uploadType.value = rv.uploadType
setTimeout(()=>{
submitProcess()
},2000)
}
}
);
}
//清除子组件的数据
let clearChildData = () =>{
productPicUploadModal.value.clearPicData()
addStoreModalComponent.value.clearStoreData()
}
onMounted(() => {
})
return {
formRef,
indicator,
addProductModal,
showDesignMark,
designProgress,
uploadType,
addStoreModal,
productPicUploadModal,
submitLoading,
showModal,
storeNameList,
formState,
showPicture,
rules,
closeProduct,
openUploadModal,
selectStore,
addStoreModalComponent,
uploadPicChange,
storeChange,
submitAddProduct,
}
},
});
</script>
<style lang="less" scoped>
.add_product_modal_component{
.picture_list{
display: flex;
.picture_list_block{
display: flex;
margin-right: 20px;
}
.pricture_item_block{
width: 130px;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
margin-left: -13px;
&.add_pic_button{
background: #F7F8FC;
border: 1px solid #DFDFDF;
cursor: pointer;
margin: 0;
}
.add_pic_button_content{
text-align: center;
color: #C7CDD5;
font-size: 16px;
.add_pic_button_icon{
font-size: 36px;
margin: 0 auto 15px;
}
}
&:first-child{
margin-left: 0;
}
.pricture_item{
max-height: 100%;
max-width: 100%;
}
}
}
.progress_mark{
width: 100%;
height: 100%;
background: #fff;
position: absolute;
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
.mark_content{
text-align: center;
.mark_content_spin{
margin-top: 20px;
}
.upload_tip{
font-size: 14px;
text-align: center;
margin-top: 10px;
}
}
}
.store_inventory_block{
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
height: 46px;
border: 1px solid #DFDFDF;
padding: 0 12px;
.store_inventory_content{
width: 500px;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.add_store_modal_component{
.modal_button_list{
margin-top: 20px;
}
.store_input{
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<div class="add_product_modal">
<a-modal class="add_store_modal_component"
v-model:visible="addStoreModal"
:footer="null"
title="Choose store"
width="1080px"
:destroyOnClose="true"
:closable="false"
:maskClosable="false"
:centered="true"
>
<a-table :columns="columns" :data-source="storeList" bordered
:pagination="false" :scroll="{y: 400 }">
<template v-slot:bodyCell="{column,record}">
<template v-if="column.dataIndex === 'storeName'">
<a-checkbox v-model:checked="record.storeCheck">{{record.storeName}}</a-checkbox>
</template>
<template v-for="item in storeNameData">
<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 === 'storeName'">
<a-checkbox v-model:checked="record.storeCheck">{{record.storeName}}</a-checkbox>
</template>
<template v-if="column.dataIndex === 'XS'">
<a-input class="store_input" v-model:value="record.XS" type="number" :min="0"/>
</template>
<template v-if="column.dataIndex === 'S'">
<a-input class="store_input" v-model:value="record.S" type="number" :min="0"/>
</template>
<template v-if="column.dataIndex === 'M'">
<a-input class="store_input" v-model:value="record.M" type="number" :min="0"/>
</template>
<template v-if="column.dataIndex === 'L'">
<a-input class="store_input" v-model:value="record.L" type="number" :min="0"/>
</template>
<template v-if="column.dataIndex === 'XL'">
<a-input class="store_input" v-model:value="record.XL" type="number" :min="0"/>
</template>
<template v-if="column.dataIndex === 'XXL'">
<a-input class="store_input" v-model:value="record.XXL" type="number" :min="0"/>
</template>
</template> -->
</a-table>
<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="submitStore">Submit</a-button>
</div>
</a-modal>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,reactive,toRefs,onMounted, computed,createVNode } from "vue";
import { Https } from "@/tool/https";
import { message,Modal } from "ant-design-vue";
import { WarningOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name:'AddStoreModal',
emits: ['storeChange'],
setup(props,{ emit }){
let addStoreModal = ref(false)
let addStoreType = ref('add')
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 storeList:any = ref([])
let backupStoreList:any = ref([])//提交时备份的数据
let storeNameData:any = ref([])
let openAddStoreModal = async (data:any) =>{
if(data.type === 'add'){
if(backupStoreList.value.length){
storeList.value = JSON.parse(JSON.stringify(backupStoreList.value))
}
}else{
let storeIds:any = []
for(let item of data.storeData){
storeIds.push(item.storeId)
}
let children:any = []
data.storeNameData.forEach((item:any) => {
children.push({
title:item,
dataIndex:item,
key:item,
align:'center'
})
});
columns[1].children = children
storeNameData.value = data.storeNameData
await storeQueryAll()
storeList.value = backupStoreList.value.filter((v:any) => storeIds.indexOf(v.id) == -1)
console.log(storeList.value);
}
addStoreModal.value = true
addStoreType.value = data.type
}
let submitStore= () => {
if(addStoreType.value === 'add'){
addStore()
}else{
editStore()
}
}
let addStore = () =>{
let field:any = JSON.parse(JSON.stringify(storeNameData.value))
let storeInfos = []
for(let v of storeList.value){
if(v.storeCheck){
let data:any = {
stock:[],
storeId:v.id,
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)
}
}
backupStoreList.value = JSON.parse(JSON.stringify(storeList.value))
emit('storeChange',storeInfos)
addStoreModal.value = false
}
let editStore = () =>{
let storeInfos = storeList.value.filter((v:any) => v.storeCheck)
emit('storeChange',storeInfos)
addStoreModal.value = false
}
let closeProduct = () =>{
Modal.confirm({
title: "To confirm the cancellation of setting store information? Cancellation will clear the setting data for this time.",
icon: createVNode(WarningOutlined),
class:'confirm_style',
okText: 'Ok',
cancelText: 'Cancel',
// centered:true,
onOk() {
storeList.value = JSON.parse(JSON.stringify(backupStoreList.value))
addStoreModal.value = false
}
});
}
let storeQueryAll = async () =>{
let storeData:any = {
storeCheck:false,
}
await new Promise((resolve,reject)=>{
storeNameData.value.forEach((item:any) => {
storeData[item] = 0
});
console.log(storeData);
Https.axiosPost(Https.httpUrls.queryProductStore,{}).then(
(rv: any) => {
if (rv) {
storeList.value = rv.map((v:any)=>{
let data = {
...v,
storeName:v.name,
storeId:v.id,
...storeData,
}
return data
})
backupStoreList.value = JSON.parse(JSON.stringify(storeList.value))
resolve('')
}
}
);
})
}
let clearStoreData = () =>{
storeQueryAll()
}
onMounted(() => {
// storeQueryAll()
})
return {
addStoreModal,
storeNameData,
columns,
storeList,
openAddStoreModal,
closeProduct,
submitStore,
clearStoreData,
}
},
});
</script>
<style lang="less" scoped>
.add_product_modal_component{
.picture_list{
display: flex;
.pricture_item_block{
width: 130px;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
margin-left: -30px;
&.add_pic_button{
background: #F7F8FC;
border: 1px solid #DFDFDF;
cursor: pointer;
}
.add_pic_button_content{
text-align: center;
color: #C7CDD5;
font-size: 16px;
.add_pic_button_icon{
font-size: 36px;
margin: 0 auto 15px;
}
}
&:first-child{
margin-left: 0;
}
.pricture_item{
max-height: 100%;
max-width: 100%;
width: 100%;
}
}
}
}
.add_store_modal_component{
.modal_button_list{
margin-top: 20px;
}
.store_input{
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,769 @@
<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="Eidt 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">Label 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>

View File

@@ -0,0 +1,529 @@
<template>
<a-modal class="product_detail_modal_component"
v-model:visible="productDetailModal"
:footer="null"
title="商品详情"
width="1150px"
:maskClosable="false"
:centered="true"
>
<div class="productDetail_modal">
<div class="productDetail_modal_cotent">
<div class="productDetail_modal_cotent_center">
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Product image</div>
</div>
<div class="product_master_diagram">
<img class="product_master_img" :src="formState.pictureUrl">
</div>
</div>
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Product label(s)</div>
</div>
<div class="detail_item_content">
<div class="productCategory_block">
<div class="produce_label_item" :style="{background:colorMap[label.type]}" v-for="label in productLabelList" :key="label.id">{{label.name}}</div>
</div>
</div>
</div>
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Pirce</div>
</div>
<div class="detail_item_content">
<div class="productCategory_block">${{formState.price}}</div>
</div>
</div>
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Inventory</div>
</div>
<div class="detail_item_content">
<a-table class="form_width_100" :columns="columns" :data-source="formState.storeList" bordered
:pagination="false" :scroll="{y: 400 }">
</a-table>
</div>
</div>
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Label attributes</div>
</div>
<div class="detail_item_sec_title">类目</div>
<div class="detail_item_content">
<div class="productCategory_block">{{formState.productCategory}}</div>
</div>
<div>
<div class="detail_item_sec_title sec_title_margin">Attribute(s)</div>
<div class="detail_item_content">
<div class="attribute_block" v-if="formState.productAttribute?.length">
<div class="attribute_block_item" v-for="attr in formState.productAttribute" :key="attr.value">
<div class="attribute_block_item_content">
<div class="attribute_item_title">{{attr.labelType}}</div>
<div class="attribute_item_value" :title="attr.attributeValueList.join(',')">{{attr.attributeValueList.join(',')}}</div>
</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>
<div class="detail_item_block">
<div class="detail_item_title_block">
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
<div>Mix and Match(es)</div>
</div>
<div class="detail_item_content">
<div class="product_match_block">
<div class="product_match_item" v-for="(match,index) in formState.assortmentList" :key="match.title" v-show="index<5 || assortmentShowMore">
<div class="product_match_item_header">
<div>Look{{index+1}}</div>
</div>
<div class="product_match_item_content">
<div class="match_item_img_content" v-for="img in match" :key="img">
<div class="match_item_img_block">
<img :src="img.generatePictureUrl">
</div>
<div class="product_price">${{img.price}}</div>
</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>
</div>
</div>
<div class="loading_marking" v-show="loading">
<a-spin size="large" />
</div>
</div>
</a-modal>
</template>
<script lang="ts">
import { defineComponent, h,reactive,ref,onMounted,toRefs } from 'vue'
import { message, Upload } from 'ant-design-vue';
import filterComponent from '@/component/filterComponent.vue'
import { Https } from "@/tool/https";
export default defineComponent({
name:'productDetailModal',
setup(){
let loading = ref(false)
let productDetailModal = ref(false)
let productDetail:any = ref([])
let formState:any = ref({
productPic:'',
productLabel:[],
inventoryData:[],
storeList:[],
storeNameList:[],
productCategory:'',
productAttribute:[],
assortmentList:[]
});
let assortmentShowMore = ref(false)
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 colorMap = ref({
CUSTOM:'#798BD9',
NEW_PRODUCT:'#FFC384',
SALE:'#79D97C',
})
let productLabelList:any = ref([])
let getProductLabelList = () =>{
Https.axiosPost(Https.httpUrls.queryProductLabel).then(
(rv: any) => {
if (rv) {
productLabelList.value = rv.filter((v:any) => formState.value.productLabel.indexOf(v.id) > -1)
}
loading.value = false
}
).catch(res=>{
loading.value = false
});
}
let getProductDetail = (id:any) =>{
return new Promise((resolve:any,rj:any)=>{
Https.axiosGet(Https.httpUrls.productDetail+'?id='+id).then(
(rv: any) => {
if (rv) {
let store = getStoreInfo(rv.storeInfoList)
formState.value = {
pictureUrl:rv.pictureUrl,
productLabel:rv.productLabelInfo.map((v:any)=>v.id),
price:rv.price,
productCategory:rv.attributeItemInfo.labelItem,
productAttribute:rv.attributeItemInfo.attributeTypeList,
storeList:store.storeList,
storeNameList:store.storeNameList,
assortmentList:rv.assortmentList,
}
productDetail.value = rv
resolve(rv)
}
loading.value = false
}
).catch(res=>{
loading.value = false
});
})
}
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 openProductDetailModal = (productId:any) =>{
formState.value = {
productPic:'',
productLabel:[],
inventoryData:[],
storeList:[],
storeNameList:[],
productCategory:'',
productAttribute:[],
assortmentList:[]
}
productDetailModal.value= true
loading.value = true
assortmentShowMore.value = false
getProductDetail(productId).then(rv=>{
getProductLabelList()
})
}
let assortmentListShowMore = () =>{
assortmentShowMore.value = !assortmentShowMore.value
}
return {
loading,
productDetailModal,
columns,
formState,
assortmentShowMore,
productCategoryList,
productAttributeList,
colorMap,
productLabelList,
openProductDetailModal,
assortmentListShowMore
}
},
})
</script>
<style lang="less" scoped>
.product_detail_modal_component{
.productDetail_modal{
height: 100%;
position: relative;
.productDetail_modal_cotent{
width: 100%;
height: 100%;
background: #fff;
.productDetail_modal_cotent_center{
width: 1000px;
padding: 20px 0 30px;
margin: 0 auto;
height: 100%;
}
.detail_item_block{
margin-bottom: 15px;
.detail_icon{
color: #80B8F8;
font-size: 24px;
margin-right: 14px;
}
.detail_item_title_block{
display: flex;
align-items: center;
font-size: 16px;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #030303;
margin-bottom: 10px;
}
.detail_item_content{
padding-left: 10px;
width: 100%;
position: relative;
}
.produce_label_item{
padding: 0 11px;
margin: 9px 27px 9px 0;
height: 27px;
line-height: 27px;
box-sizing: border-box;
border-radius: 4px;
font-size: 16px;
color: #fff;
display: inline-block;
vertical-align: top;
}
.productCategory_block{
width: 100%;
line-height: 46px;
padding-left: 20px;
font-size: 18px;
color: #030303;
height: 46px;
border: 1px solid #DFDFDF;
}
.detail_item_sec_title{
padding-left: 38px;
font-size: 16px;
font-family: Adobe Heiti Std;
font-weight: normal;
color: #030303;
margin-bottom: 10px;
}
.sec_title_margin{
margin: 10px 0;
}
.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_value{
width: 200px;
height: 36px;
line-height: 36px;
border: 1px solid #DFDFDF;
border-radius: 4px;
font-size: 16px;
padding-left: 18px;
color: #030303;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.null_data_block{
background: #F7F8FC;
border: 1px solid #DFDFDF;
padding: 30px 0;
text-align: center;
.null_data_img{
width: 130px;
}
}
.product_match_block{
.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_content{
margin-right: 20px;
width: 160px;
}
.match_item_img_block{
display: flex;
align-items: center;
justify-content: center;
width: 160px;
height: 100px;
position: relative;
border: 1px solid #DFDFDF;
cursor: pointer;
img{
max-width: 100%;
max-height: 100%;
}
&:last-child{
margin-right: 0;
}
}
.product_price{
text-align: center;
margin-top: 5px;
color: #030303;
}
}
}
}
.show_more_button{
margin: 20px auto;
display: block;
}
}
.form_width_100{
width: 100%;
}
.product_master_diagram{
width: 400px;
height: 400px;
margin: 30px auto;
display: flex;
align-items: center;
justify-content: center;
.product_master_img{
max-width: 100%;
max-height: 100%;
}
}
}
.loading_marking{
position: absolute;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@@ -0,0 +1,341 @@
<template>
<a-modal class="add_match_modal_component"
v-model:visible="productMatchModal"
:footer="null"
title="商品搭配选择"
width="1150px"
:maskClosable="false"
:centered="true"
>
<div class="upload_modal_body">
<div class="filter_content">
<div class="filter_content_block">
<filterComponent class="filter_component_style" :title="'Product Type'" :titleStyle="titleStyle" :slotStyle="slotStyle"><a-select v-model:value="labelItem" size="large" style="width:200px" :options="productCategoryList" placeholder="Please select" allowClear @change="productTypeChange"></a-select></filterComponent>
<div class="filter_component_style item_subtype_content">
<div class="item_subtype_button" @click="changeItemFilter">
<span>Item Subtype</span>
<span :class="['icon','iconfont','item_subtype_button_icon','icon-xialajiantouxiao', showItemFilter?'turn_round_icon':'']"></span>
</div>
</div>
<div v-show="showItemFilter">
<filterComponent v-for="item in productAttributeList" :key="item.title" class="filter_component_style" :title="item.labelType" :titleStyle="titleStyle" :slotStyle="slotStyle">
<a-select v-model:value="item.value" size="large" style="width:200px" :options="item.attributeValueList" placeholder="Please select" allowClear></a-select>
</filterComponent>
</div>
</div>
<div class="filter_content_button_list">
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Search</a-button>
<a-button class="default_button" size="large">Reset</a-button>
</div>
</div>
<div class="modal_body_content">
<div class="upload_img_body scroll_style" v-show="imageList.length">
<div class="upload_file_item" v-for="(file) in imageList" :key="file.id">
<div :class="['upload_file_item_content']" @click="selectFile(file)">
<!-- <img v-lazy="img.url" class="upload_img"> -->
<img :src="file.pictureUrl" class="upload_img">
</div>
<div class="product_price">${{file.price}}</div>
</div>
</div>
<div class="upload_img_body null_img_block" v-show="!imageList.length">
<img src="@/assets/images/null_img.png">
</div>
<div class="img_mark_loading" v-show="tableLoading">
<a-spin size="large" />
</div>
<a-pagination class="pagination_content" show-quick-jumper v-model:current="currentPage" :page-size-options="pageSizeOptions" show-size-changer :page-size="pageSize" :total="total" @change="pageChange" @showSizeChange="onShowSizeChange"/>
</div>
</div>
</a-modal>
</template>
<script lang="ts">
import { defineComponent, h,reactive,ref,onMounted,toRefs } from 'vue'
import { message, Upload } from 'ant-design-vue';
import filterComponent from '@/component/filterComponent.vue'
import { Https } from "@/tool/https";
export default defineComponent({
name:'ProductMatchModal',
components:{filterComponent},
props:{
productCategoryList:{
type:Array,
}
},
setup(props,{emit}){
let productMatchModal = ref(false)
let filter = reactive({
labelItem:[],
labelTypeMap:{},
})
let tableLoading = ref(false)
let imageList:any = ref([])
let currentPage = ref(1)
let total = ref(0)
let pageSize = ref(10)
let pageSizeOptions = ref<string[]>(['10', '20', '30', '40', '50']);
let titleStyle = ref({textAlign:'left',minWidth:'120px',textOverflow:'ellipsis',width:'120px',overflow:'hidden'})
let slotStyle = ref({width:'auto'})
let showItemFilter = ref(false)
let subTypeList:any = ref([])
let productAttributeList:any = ref([])
let assortmentIdList:any = []
let openMatchModal = (idList:any) =>{
productMatchModal.value = true
productAttributeList.value = []
filter.labelItem = []
filter.labelTypeMap = {}
currentPage.value = 1
imageList.value = []
assortmentIdList = idList
getProductAssortemlist()
}
let pageChange = (e:any) =>{
currentPage.value = e
getProductAssortemlist()
}
let onShowSizeChange = (current: number, size: number) =>{
pageSize.value = size
getProductAssortemlist()
}
let productTypeChange = (value:any,selectedOptions:any) => {
let attributeList = selectedOptions ? selectedOptions.attributeTypeList :[]
productAttributeList.value = JSON.parse(JSON.stringify(attributeList))
}
let changeItemFilter = () =>{
showItemFilter.value = !showItemFilter.value
}
let selectFile = (file:any) =>{
emit('selectMatchFile',file)
productMatchModal.value = false
}
let getLabelTypeMap = () =>{
let labelTypeMap:any = {}
for(let item of productAttributeList.value){
if(item.value && item.value.length){
labelTypeMap[item.labelType] = [item.value]
}
}
return labelTypeMap
}
let searchList = () =>{
currentPage.value = 1
getProductAssortemlist()
}
let getProductAssortemlist = () =>{
let data = {
assortmentIdList:assortmentIdList,
labelItem:filter.labelItem && filter.labelItem.length ? filter.labelItem : '',
labelTypeMap:getLabelTypeMap(),
page:currentPage.value,
size:pageSize.value
}
tableLoading.value = true
Https.axiosPost(Https.httpUrls.queryProductAssortmentPage, data).then(
(rv: any) => {
if (rv) {
tableLoading.value = false
imageList.value = rv.content
total.value = rv.total
}
}
);
}
onMounted(()=>{
})
return {
...toRefs(filter),
productMatchModal,
tableLoading,
imageList,
currentPage,
total,
pageSize,
pageSizeOptions,
titleStyle,
slotStyle,
showItemFilter,
subTypeList,
productAttributeList,
openMatchModal,
pageChange,
onShowSizeChange,
productTypeChange,
changeItemFilter,
selectFile,
searchList,
}
},
})
</script>
<style lang="less" scoped>
.add_match_modal_component{
.upload_modal_body{
.filter_content{
.filter_content_block{
.filter_component_style:nth-child(3n){
margin-right: 0;
}
.item_subtype_content{
width: 335px;
display: inline-block;
overflow: hidden;
vertical-align: top;
.item_subtype_button{
width: 160px;
height: 46px;
border: 1px solid #030303;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-family: Roboto;
font-weight: 400;
color: #030303;
cursor: pointer;
float: right;
.item_subtype_button_icon{
margin-left: 15px;
color: #030303;
font-size: 16px;
}
.turn_round_icon{
-moz-transform:rotate(180deg);
-webkit-transform:rotate(180deg);
transform: rotate(180deg);
animation-direction: 0.5s;
}
}
}
}
.filter_content_button_list{
display: flex;
align-items: center;
}
}
.modal_body_content{
margin-top: 40px;
padding: 25px;
background: #F7F8FC;
border: 1px solid #DFDFDF;
height: 550px;
position: relative;
.upload_img_body{
height: calc(100% - 30px);
overflow-y: auto;
margin-bottom: 10px;
}
.null_img_block{
display: flex;
align-items: center;
justify-content: center;
}
.img_mark_loading{
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background: rgba(0,0,0,0.2);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.upload_file_item{
margin: 0 50px 30px 0;
display: inline-block;
width: 165px;
height: 165px;
border: 1px solid #F5F5F5;
vertical-align: top;
background: rgba(0,0,0,0.2);
&:nth-child(5n){
margin-right: 0;
}
.upload_file_item_content{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
position: relative;
cursor: pointer;
&.select_file_block{
border:solid 1px #000;
}
.upload_img{
max-height: 100%;
max-width: 100%;
}
}
.product_price{
text-align: center;
margin-top: 5px;
color: #030303;
}
}
}
}
.pagination_content{
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,318 @@
<template>
<a-modal class="add_product_modal_component"
v-model:visible="addProductPicModal"
:footer="null"
title="商品图片上传"
width="1150px"
:maskClosable="false"
:destroyOnClose="true"
:centered="true"
@cancel="closeModal"
>
<div class="upload_modal_body">
<div class="upload_img_body scroll_style" >
<div class="upload_item">
<div class="upload_file_item" v-for="(file, index) in fileList" :key="file">
<div class="upload_file_item_content" v-show="file?.status === 'uploading'">
<a-spin :indicator="indicator" tip="Uploading..."/>
</div>
<div class="upload_file_item_content" v-show="file?.status === 'done'">
<img v-lazy="file?.pictureUrl" class="upload_img">
<div class="delete_file_block" @click="deleteFile(index)">
<span class="icon iconfont icon-shanchu"></span>
</div>
</div>
</div>
<div class="upload_file_item upload_component" v-show="fileList.length < 100">
<a-upload
:action="uploadUrl + '/api/product/uploadFileSingle'"
class="upload_component_button"
list-type="picture-card"
:data="{
...upload
}"
:headers="{Authorization:token}"
v-model:file-list="fileList"
:before-upload="beforeUpload"
multiple
:maxCount="100"
accept=".jpg,.png,.jpeg,.bmp"
@change="(file)=>fileUploadChange(file)"
>
<div class="upload_tip_block" v-show="fileList.length < 100">
<span class="icon iconfont add_pic_button_icon icon-tianjiatupian_huaban"></span>
</div>
</a-upload>
</div>
</div>
</div>
<div class="upload_max_tip">
<WarningFilled class="icon-zhuyi"/>
<span>Maximum 100 images can be uploaded, Maximum 2M per image</span>
</div>
</div>
<div class="modal_button_list">
<a-button class="primary_button" type="primary" size="large" @click="closeModal()">Submit</a-button>
</div>
</a-modal>
</template>
<script lang="ts">
import { defineComponent, h,reactive,ref,onMounted} from 'vue'
import { LoadingOutlined,WarningFilled } from '@ant-design/icons-vue';
import {getCookie} from '@/tool/cookie'
import {getUploadUrl} from '@/tool/util'
import { message, Upload } from 'ant-design-vue';
export default defineComponent({
name:'ProductPicUpload',
components:{WarningFilled},
emits: ['uploadPicChange'],
setup(props,{ emit }){
let addProductPicModal = ref(false)
let fileList:any = ref([])
let templateModal:any = ref(false)
let templateFileList:any = ref([])
let upload:any = reactive({
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
})
let beforeUploadIndex:any = 0
let successFileList:any = ref([])
let token:any = ref('')
let indicator :any = h(LoadingOutlined, {
style: {
fontSize: '24px',
},
spin: true,
})
let uploadUrl:any = ref('')
let fileUploadChange = (data:any) =>{
let file = data.file
if(file.status === 'done'){
let res = file.response
if(res.data){
file.pictureUrl = res.data.pictureUrl
file.md5 = res.data.md5
file.resData = res.data
successFileList.value = fileList.value.filter((v:any)=> v.status === 'done')
emit('uploadPicChange', {fileList:successFileList.value})
}else{
let index = -1
fileList.value.forEach((ele:any,index1:any) => {
if(file.uid === ele.uid){
index = index1
}
});
if(index > -1){
fileList.value.splice(index, 1)
}
message.error(file.name + ' ' + res.errMsg)
}
}else if(file.status === 'error'){
let index = -1
fileList.value.forEach((ele:any,index1:any) => {
if(file.uid === ele.uid){
index = index1
}
});
if(index > -1){
fileList.value.splice(index, 1)
}
message.error(file.name + 'upload failed')
}
}
let beforeUpload = (file:any,fileList:any) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/bmp';
beforeUploadIndex = beforeUploadIndex +1
console.log(file, 66)
if(fileList.length > 10 && beforeUploadIndex == fileList.length){
message.error('You can only upload 10 pictures at a time!');
}
if (!isJpgOrPng) {
message.error('You can only upload Image file!');
}
const isLt2M = file.size / 1024 / 1024 < 5;
if (!isLt2M) {
message.error('Image must smaller than 5MB!');
}
if(beforeUploadIndex == fileList.length) {
beforeUploadIndex = 0
}
return (isJpgOrPng && isLt2M && fileList.length <= 10) || Upload.LIST_IGNORE;
}
let deleteFile = (index:any) =>{
fileList.value.splice(index,1)
successFileList.value = fileList.value
emit('uploadPicChange', {fileList:successFileList.value})
}
let openPictureModal = () =>{
addProductPicModal.value = true
}
let closeModal = () =>{
addProductPicModal.value = false
}
let clearPicData = () =>{
fileList.value = []
successFileList.value =[]
}
onMounted(()=>{
token.value = getCookie('token') || '',
uploadUrl.value = getUploadUrl()
})
return {
addProductPicModal,
fileList,
templateModal,
templateFileList,
upload,
successFileList,
indicator,
token,
uploadUrl,
fileUploadChange,
beforeUpload,
deleteFile,
openPictureModal,
closeModal,
clearPicData
}
},
})
</script>
<style lang="less" scoped>
.add_product_modal_component{
.upload_modal_body{
height: 600px;
.upload_img_body{
height: calc(100% - 30px);
overflow-y: auto;
margin-bottom: 10px;
}
.upload_file_item{
margin: 0 17px 17px 0;
display: inline-block;
width: 165px;
height: 165px;
border: 1px solid #F5F5F5;
vertical-align: top;
&.upload_component{
background: #FFFFFF;
.upload_tip_block{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.add_pic_button_icon{
font-size: 44px;
color: #C6CBD3;
}
}
}
.upload_file_item_content{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
position: relative;
&:hover .delete_file_block{
display: block;
}
.upload_img{
display: block;
max-height: 100%;
max-width: 100%;
}
.delete_file_block{
display: none;
width: 36px;
border-radius: 50%;
cursor: pointer;
height: 36px;
background: #000000;
font-size: 16px;
color: #FFFFFF;
line-height: 36px;
text-align: center;
position: absolute;
right: 10px;
top: 10px;
}
}
}
.upload_max_tip{
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #030303;
margin-bottom: 30px;
.icon-zhuyi{
font-size: 16px;
margin-right: 8px;
}
}
}
.modal_button_list{
display: flex;
justify-content: flex-end;
}
}
</style>
<style lang="less">
.upload_component_button{
width: 165px;
height: 165px;
.ant-upload-list{
width: 100%;
height: 100%;
.ant-upload-select-picture-card{
width: 100%;
height: 100%;
}
}
}
</style>