TASK
This commit is contained in:
74
src/component/filterComponent.vue
Normal file
74
src/component/filterComponent.vue
Normal 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>
|
||||
385
src/component/productComponent/addProduct.vue
Normal file
385
src/component/productComponent/addProduct.vue
Normal 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>
|
||||
290
src/component/productComponent/addStoreModa.vue
Normal file
290
src/component/productComponent/addStoreModa.vue
Normal 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>
|
||||
769
src/component/productComponent/editProduct.vue
Normal file
769
src/component/productComponent/editProduct.vue
Normal 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>
|
||||
529
src/component/productComponent/productDetailModal.vue
Normal file
529
src/component/productComponent/productDetailModal.vue
Normal 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>
|
||||
|
||||
341
src/component/productComponent/productMatchModal.vue
Normal file
341
src/component/productComponent/productMatchModal.vue
Normal 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>
|
||||
|
||||
318
src/component/productComponent/productPicUpload.vue
Normal file
318
src/component/productComponent/productPicUpload.vue
Normal 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>
|
||||
Reference in New Issue
Block a user