|
@@ -0,0 +1,384 @@
|
|
|
+<template>
|
|
|
+ <div class="product-select">
|
|
|
+ <div class="left-part" v-loading="infiniteLoad">
|
|
|
+ <el-tabs v-model="activeName">
|
|
|
+ <el-tab-pane label="搜索" name="search">
|
|
|
+ <div style="margin: 0 10px">
|
|
|
+ <vxe-input v-model="searchInp" placeholder="请输入商品名称" type="search" class="search-input" />
|
|
|
+ </div>
|
|
|
+ <div class="padding-0-10">
|
|
|
+ <div class="list-bar">
|
|
|
+ <span class="padding-0-10">商品列表</span>
|
|
|
+ <vxe-button
|
|
|
+ style="position: absolute; margin: 3px; right: 0"
|
|
|
+ type="text"
|
|
|
+ status="primary"
|
|
|
+ content="添加"
|
|
|
+ icon="vxe-icon-add"
|
|
|
+ @click="handleSelectedItems" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
|
|
|
+ <div>
|
|
|
+ <el-collapse v-model="activeNames" class="padding-0-10" style="border: none">
|
|
|
+ <el-collapse-item v-for="(item, index) in data" :key="item.parentAsin" :name="String(index)">
|
|
|
+ <template #title>
|
|
|
+ <el-checkbox v-model="item.checked" @click.stop="stopOpen" @change="checkAll(item, item.checked)">
|
|
|
+ {{ item.parentAsin }}
|
|
|
+ </el-checkbox>
|
|
|
+ <el-tag style="margin-left: 8px" effect="plain" size="small" round>{{ item.num }}ASIN</el-tag>
|
|
|
+ </template>
|
|
|
+ <ul class="list-container">
|
|
|
+ <li v-for="child in item.childAsin" :key="child.asin">
|
|
|
+ <div class="list-content">
|
|
|
+ <el-checkbox v-model="child.checked" @change="checkSingle(item)" />
|
|
|
+ <img :src="child.Image" class="image-item" />
|
|
|
+ <div>
|
|
|
+ <el-tooltip effect="dark" :content="child.Title" placement="top-start">
|
|
|
+ <span class="list-item-title">{{ child.Title }}</span>
|
|
|
+ </el-tooltip>
|
|
|
+ <div>
|
|
|
+ <span class="item-font">${{ child.Price ? child.Price : '--' }}</span>
|
|
|
+ | <span class="item-quantity">{{ child.Quantity ? '有库存' : '缺货' }}</span>
|
|
|
+ </div>
|
|
|
+ <div><span class="item-asin">ASIN:</span> {{ child.Asin }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </el-collapse-item>
|
|
|
+ </el-collapse>
|
|
|
+ </div>
|
|
|
+ </ul>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="输入" name="input">
|
|
|
+ <div style="padding: 15px 20px">
|
|
|
+ <el-input
|
|
|
+ disabled="true"
|
|
|
+ v-model="textarea"
|
|
|
+ style="width: 100%"
|
|
|
+ :rows="15"
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入ASIN, 多个ASIN使用逗号、空格或换行符分隔" />
|
|
|
+ <div style="display: flex">
|
|
|
+ <el-button style="flex-direction: row-reverse" :disabled="!textarea" type="primary" text bg>添加</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+ <div class="right-part">
|
|
|
+ <div>
|
|
|
+ <div class="right-top-part">
|
|
|
+ <span>已添加: {{ selectedData.length }}</span>
|
|
|
+ <span>
|
|
|
+ <vxe-button
|
|
|
+ v-if="hasSelectedItems"
|
|
|
+ type="text"
|
|
|
+ status="warning"
|
|
|
+ content="删除选中项"
|
|
|
+ icon="vxe-icon-delete"
|
|
|
+ @click="removeSelectedItems"></vxe-button>
|
|
|
+ <vxe-button type="text" status="danger" content="删除所有" icon="vxe-icon-delete" @click="removeAllItems"></vxe-button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <el-scrollbar height="540px">
|
|
|
+ <el-collapse v-model="activeNames" class="padding-0-10" style="border: none">
|
|
|
+ <el-collapse-item v-for="(item, index) in selectedData" :key="item.parentAsin" :name="String(index)">
|
|
|
+ <template #title>
|
|
|
+ <el-checkbox v-model="item.checked" @click.stop="stopOpen" @change="checkAll(item, item.checked)">{{ item.parentAsin }}</el-checkbox>
|
|
|
+ <el-tag style="margin-left: 8px" effect="plain" size="small" round>{{ item.num }}ASIN</el-tag>
|
|
|
+ </template>
|
|
|
+ <ul class="list-container">
|
|
|
+ <li v-for="child in item.childAsin" :key="child.asin">
|
|
|
+ <div class="list-content">
|
|
|
+ <el-checkbox v-model="child.checked" @change="checkSingle(item)"></el-checkbox>
|
|
|
+ <img :src="child.Image" class="image-item" />
|
|
|
+ <div>
|
|
|
+ <el-tooltip effect="dark" :content="child.Title" placement="top-start">
|
|
|
+ <span class="list-item-title">{{ child.Title }}</span>
|
|
|
+ </el-tooltip>
|
|
|
+ <div>
|
|
|
+ <span class="item-font">${{ child.Price ? child.Price : '--' }}</span>
|
|
|
+ | <span class="item-quantity">{{ child.Quantity ? '有库存' : '缺货' }}</span>
|
|
|
+ </div>
|
|
|
+ <div><span class="item-asin">ASIN:</span> {{ child.Asin }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </el-collapse-item>
|
|
|
+ </el-collapse>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
+import { getAllProduct } from '../api'
|
|
|
+import emitter from '/@/utils/emitter'
|
|
|
+
|
|
|
+const profile = <any>inject('profile')
|
|
|
+const activeName = ref('search')
|
|
|
+
|
|
|
+const searchInp = ref('')
|
|
|
+const textarea = ref('')
|
|
|
+function handleSearch() {}
|
|
|
+
|
|
|
+const data = ref([])
|
|
|
+const selectedData = ref([])
|
|
|
+const activeNames = ref([])
|
|
|
+const infiniteLoad = ref(false)
|
|
|
+let currentPage = 1
|
|
|
+let total = 0
|
|
|
+let limit = 10
|
|
|
+
|
|
|
+function load() {
|
|
|
+ if (currentPage * limit < total) {
|
|
|
+ currentPage++
|
|
|
+ fetchAllProduct()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchAllProduct() {
|
|
|
+ infiniteLoad.value = true
|
|
|
+ const query = {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ page: currentPage,
|
|
|
+ limit: limit,
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const res = await getAllProduct(query)
|
|
|
+ if (res && res.data) {
|
|
|
+ // 转换数据并更新data
|
|
|
+ const newData = res.data
|
|
|
+ if (currentPage > 1) {
|
|
|
+ data.value.push(...newData) // 追加数据而不是替换
|
|
|
+ } else {
|
|
|
+ data.value = newData // 第一页数据,直接替换
|
|
|
+ }
|
|
|
+ total = res.total
|
|
|
+ console.log('请求的数据', data.value)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log('error:', error)
|
|
|
+ } finally {
|
|
|
+ infiniteLoad.value = false
|
|
|
+ activeNames.value = data.value.map((_, index) => String(index))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 当选中或取消选中父级复选框时
|
|
|
+function checkAll(parent, checked) {
|
|
|
+ parent.childAsin.forEach((child) => {
|
|
|
+ child.checked = checked
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 当选中或取消选中单个子项复选框时
|
|
|
+function checkSingle(parent) {
|
|
|
+ const allChecked = parent.childAsin.every((child) => child.checked)
|
|
|
+ parent.checked = allChecked
|
|
|
+}
|
|
|
+
|
|
|
+// 添加所有选中的项
|
|
|
+function getSelectedItems() {
|
|
|
+ const selectedItems = []
|
|
|
+ data.value.forEach((parentItem) => {
|
|
|
+ if (parentItem.checked) {
|
|
|
+ // 如果父项被选中,添加父项的值,并包含所有子项的完整数据
|
|
|
+ const children = parentItem.childAsin.map((childItem) => {
|
|
|
+ return { ...childItem } // 复制整个childItem对象
|
|
|
+ })
|
|
|
+ selectedItems.push({ parentAsin: parentItem.parentAsin, childAsin: children, num: parentItem.num })
|
|
|
+ } else {
|
|
|
+ // 如果父项未被选中,仅添加被选中的子项的完整数据
|
|
|
+ const selectedChildren = parentItem.childAsin
|
|
|
+ .filter((childItem) => childItem.checked)
|
|
|
+ .map((childItem) => {
|
|
|
+ return { ...childItem } // 复制整个childItem对象
|
|
|
+ })
|
|
|
+ if (selectedChildren.length > 0) {
|
|
|
+ selectedItems.push({ parentAsin: parentItem.parentAsin, childAsin: selectedChildren, num: selectedChildren.length })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return selectedItems
|
|
|
+}
|
|
|
+
|
|
|
+// 获取所有选中项的深拷贝并取消勾选状态
|
|
|
+function handleSelectedItems() {
|
|
|
+ const newSelectedItems = getSelectedItems().map((item) => ({
|
|
|
+ ...item,
|
|
|
+ checked: false, // 取消父项勾选状态
|
|
|
+ num: item.childAsin.length, // 更新子项数量
|
|
|
+ childAsin: item.childAsin.map((child) => ({ ...child, checked: false })), // 取消子项勾选状态
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 更新selectedData以包含新选中的项,如果项已存在,则合并子项,并确保不重复
|
|
|
+ newSelectedItems.forEach((newItem) => {
|
|
|
+ const existingItemIndex = selectedData.value.findIndex((item) => item.parentAsin === newItem.parentAsin)
|
|
|
+ if (existingItemIndex !== -1) {
|
|
|
+ // 合并子项,并确保不重复添加相同的子项
|
|
|
+ const existingChildren = selectedData.value[existingItemIndex].childAsin
|
|
|
+ const newChildren = newItem.childAsin
|
|
|
+ const mergedChildren = [...existingChildren]
|
|
|
+ newChildren.forEach((newChild) => {
|
|
|
+ if (!mergedChildren.some((child) => child.asin === newChild.asin)) {
|
|
|
+ mergedChildren.push(newChild)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 更新现有项的子项和子项数量
|
|
|
+ selectedData.value[existingItemIndex].childAsin = mergedChildren
|
|
|
+ selectedData.value[existingItemIndex].num = mergedChildren.length // 更新子项数量
|
|
|
+ } else {
|
|
|
+ // 如果不存在相同的父Asin,则添加新项
|
|
|
+ selectedData.value.push(newItem)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ clearSelections()
|
|
|
+}
|
|
|
+
|
|
|
+// 清除所有选中状态的辅助函数
|
|
|
+function clearSelections() {
|
|
|
+ data.value.forEach((parentItem) => {
|
|
|
+ parentItem.checked = false // 取消父项的选中状态
|
|
|
+ parentItem.childAsin.forEach((childItem) => {
|
|
|
+ childItem.checked = false // 取消子项的选中状态
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function removeAllItems() {
|
|
|
+ selectedData.value.splice(0)
|
|
|
+}
|
|
|
+
|
|
|
+function removeSelectedItems() {
|
|
|
+ selectedData.value = selectedData.value.filter((parentItem) => {
|
|
|
+ // 过滤掉父项下所有被选中的子项
|
|
|
+ parentItem.childAsin = parentItem.childAsin.filter((childItem) => !childItem.checked)
|
|
|
+ // 仅保留未被完全移除的父项(即至少有一个子项未被选中)
|
|
|
+ return parentItem.childAsin.length > 0
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 阻止点击选择框时打开折叠面板
|
|
|
+function stopOpen() {
|
|
|
+ // console.log(1213)
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否有选中的项 给模板提供用来判断是否显示删除已选中的按钮
|
|
|
+const hasSelectedItems = computed(() => {
|
|
|
+ return selectedData.value.some((parentItem) => {
|
|
|
+ return parentItem.childAsin.some((childItem) => childItem.checked)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchAllProduct()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.product-select {
|
|
|
+ display: flex;
|
|
|
+ border: 1px solid #dde0eb;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #4e5969;
|
|
|
+}
|
|
|
+.left-part {
|
|
|
+ width: 50%;
|
|
|
+ border-right: 1px solid #e5e7ec;
|
|
|
+}
|
|
|
+.right-part {
|
|
|
+ width: 50%;
|
|
|
+}
|
|
|
+.right-top-part {
|
|
|
+ height: 40px;
|
|
|
+ padding: 0 10px;
|
|
|
+ border-top-right-radius: 4px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.infinite-list {
|
|
|
+ height: 458px;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ list-style: none;
|
|
|
+}
|
|
|
+.infinite-list .infinite-list-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 50px;
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ margin: 10px;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+}
|
|
|
+.infinite-list .infinite-list-item + .list-item {
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
+.list-item-title {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #1d2129;
|
|
|
+ overflow: hidden;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ -webkit-line-clamp: var(--line-clamp);
|
|
|
+ white-space: pre-wrap;
|
|
|
+ --line-clamp: 1;
|
|
|
+}
|
|
|
+/* Tab栏 */
|
|
|
+::v-deep(.el-tabs__nav-scroll) {
|
|
|
+ overflow: hidden;
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+::v-deep(.el-table__inner-wrapper::before) {
|
|
|
+ background-color: white;
|
|
|
+}
|
|
|
+.search-input {
|
|
|
+ width: 100%;
|
|
|
+ margin: 10px 0;
|
|
|
+}
|
|
|
+.list-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.image-item {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ margin: 0 5px;
|
|
|
+}
|
|
|
+.list-bar {
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.list-container {
|
|
|
+ padding-left: 20px;
|
|
|
+}
|
|
|
+.padding-0-10 {
|
|
|
+ padding: 0 10px;
|
|
|
+}
|
|
|
+.item-asin {
|
|
|
+ color: #6b7785;
|
|
|
+}
|
|
|
+.item-font {
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.item-quantity {
|
|
|
+ color: #6b7785;
|
|
|
+}
|
|
|
+/* 折叠面板底部间距 */
|
|
|
+::v-deep(.el-collapse-item__content) {
|
|
|
+ padding-bottom: 0;
|
|
|
+}
|
|
|
+</style>
|