merchapp/java-mall-app-shop-admin/pages/warehouse/manage/batch.vue
2025-08-06 10:11:54 +08:00

2033 lines
53 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="batch-container">
<navBar
class="nav-bar"
:statusBar="true"
:border="false"
:fixed="true"
:height="'44px'"
:rightWidth="30"
:leftWidth="30"
title="商品批量"
backgroundColor="#f5f6fa"
>
<block slot="left">
<u-icon
name="arrow-left"
color="#000"
size="20"
@click="pageBack()"
></u-icon>
</block>
<block slot="right">
<u-icon
name="search"
color="#000"
size="30"
@click="showRightPopup = true"
></u-icon>
</block>
</navBar>
<view class="productList-content">
<scroll-view
scroll-y
scroll-with-animation
class="tab-view"
:scroll-into-view="scrollViewId"
:style="{ top: top + 'px' }"
>
<block v-for="(item, index) of tabbar" :key="index">
<tui-collapse
:index="index"
:current="current"
hdBgColor="f5f6fa"
:arrow="item.children.length > 0 ? true : false"
@click="handerCollApse"
>
<template v-slot:title>
<view
class="tab-bar-item"
:class="[
{ active: currentTab === index },
{ active2: current === index },
]"
>
{{ item.label }}
</view>
</template>
<template v-slot:content v-if="item.children.length > 0">
<view
class="tab-bar-item tab-bar-item-2"
:class="[currentTab == group.id ? 'active' : '']"
v-for="(group, index2) of item.children"
@click="handerCollApseChildren(index, index2)"
>
{{ group.label }}
</view>
</template>
</tui-collapse>
</block>
</scroll-view>
<view class="right-box" :style="{ height: height + 'px' }">
<view class="commodity-status">
<u-tabs
class="u-tabs"
ref="tabsRef"
:list="commodityStatusList"
@click="handerCommodityStatus"
itemStyle="padding:0 15px;height: 44px;"
activeStyle="color: #222;"
inactiveStyle="color:#666;"
lineColor="#FE411B"
></u-tabs>
</view>
<!-- 有子集 - 全选按钮 -->
<view
v-if="
commodityList.length > 0 &&
!loadingCommodityData &&
tabbar.length > 0 &&
tabbar[collApseIndex].children.length > 0
"
class="checkbox-all"
@click="handleSelectAll(null, true)"
>
<u-checkbox-group
v-model="
tabbar[collApseIndex].children[currTabChildrenIndex]
.checkboxAllList
"
placement="column"
@change="handleSelectAll($event, true)"
>
<u-checkbox
class="commodity-checkbox"
:name="'select-all'"
iconSize="14"
size="14"
></u-checkbox>
</u-checkbox-group>
<view class="checkbox-all-name">
已勾选{{
tabbar[collApseIndex].children[currTabChildrenIndex].checkboxList
.length
}}个
</view>
</view>
<!--判断无子集-->
<view
v-if="
commodityList.length > 0 &&
!loadingCommodityData &&
tabbar.length > 0 &&
tabbar[collApseIndex].children.length <= 0
"
class="checkbox-all"
@click="handleSelectAll(null, false)"
>
<u-checkbox-group
v-model="tabbar[collApseIndex].checkboxAllList"
placement="column"
@change="handleSelectAll($event, false)"
>
<u-checkbox
size="14"
iconSize="14"
class="commodity-checkbox"
:name="'select-all'"
></u-checkbox>
</u-checkbox-group>
<view class="checkbox-all-name">
已勾选{{ tabbar[collApseIndex].checkboxList.length }}个
</view>
</view>
<favorite-loading
class="commodity-loading"
v-show="loadingCommodityData"
:color="'#fe4119'"
text=""
animation="spinner15"
></favorite-loading>
<view
class="no-data"
v-if="commodityList.length <= 0 && isNoCommodityData"
>
<view class="no-data-bg"></view>
<view class="no-data-tips">暂无商品</view>
</view>
<scroll-view
v-if="commodityList.length > 0 && !loadingCommodityData"
scroll-y
:style="{ height: height - 180 + 'px' }"
:show-scrollbar="false"
@scrolltolower="handerScrolltolower"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
>
<!-- 有子集 - 单个选择 -->
<u-checkbox-group
v-if="tabbar[collApseIndex].children.length > 0"
class="commodity-list"
v-model="
tabbar[collApseIndex].children[currTabChildrenIndex].checkboxList
"
placement="column"
@change="handleSingleCheckboxChange($event, true)"
>
<view
class="commodity-item"
v-for="(item, index) of commodityList"
:key="index"
>
<u-checkbox
class="commodity-checkbox"
:key="index"
:name="item.product_id"
iconSize="14"
size="14"
></u-checkbox>
<view class="commodity-info">
<view
:class="[
'commodity-img',
{ 'commodity-img-sold-out': item.product_state_id == 1002 },
]"
>
<u--image
:src="item.product_image"
radius="12"
width="64px"
height="64px"
@click="handlerShowImg(item.product_image)"
></u--image>
</view>
<view class="commodity-info-box">
<view class="commodity-name">
{{ item.product_name }}
</view>
<view class="commodity-inventory">
库存{{ item.itemQuantity }}
</view>
<view class="commodity-price">
<block class="currency">¥</block>
{{ item.product_unit_price }}
</view>
</view>
</view>
</view>
</u-checkbox-group>
<!-- 无子集 - 单个选择 -->
<u-checkbox-group
v-if="
tabbar[collApseIndex] &&
tabbar[collApseIndex].children.length <= 0
"
v-model="tabbar[collApseIndex].checkboxList"
placement="column"
@change="handleSingleCheckboxChange($event, false)"
>
<view
class="commodity-item"
v-for="(item, index) of commodityList"
:key="index"
>
<u-checkbox
class="commodity-checkbox"
:key="index"
:name="item.product_id"
iconSize="14"
size="14"
></u-checkbox>
<view class="commodity-info">
<view
:class="[
'commodity-img',
{ 'commodity-img-sold-out': item.product_state_id == 1002 },
]"
>
<u--image
:src="item.product_image"
radius="8"
width="68px"
height="68px"
@click="handlerShowImg(item.product_image)"
></u--image>
</view>
<view class="commodity-info-box">
<view class="commodity-name">
{{ item.product_name }}
</view>
<view class="commodity-inventory">
库存{{ item.itemQuantity }}
</view>
<view class="commodity-price">
<block class="currency">¥</block>
{{ item.product_unit_price }}
</view>
</view>
</view>
</view>
</u-checkbox-group>
<view
class="m-loading-box"
v-if="commodityList.length > 0 && !isNoCommodityData"
>
<block v-if="loadingDownCommodityData">
<view class="u-loadmore">
<view class="u-loading"></view>
<text class="u-loadmore-tips">正在加载...</text>
</view>
</block>
<block v-else>
<view class="u-loadmore u-loadmore-line">
<text class="u-loadmore-tips">没有更多商品了 ~</text>
</view>
</block>
</view>
</scroll-view>
</view>
</view>
<view
class="productList-bottom"
:class="[
'productList-bottom-2',
{ 'productList-bottom-2': selectCommodityList.length > 0 },
]"
>
<view class="commodity-btn-content">
<view
class="commodity-btn-item"
:hairline="true"
:plain="true"
shape="circle"
@click="updateProductPutaway(1001)"
>
<u-icon name="arrow-upward" color="#666" size="14"></u-icon>
上架
</view>
<view
class="commodity-btn-item"
:hairline="true"
:plain="true"
shape="circle"
@click="updateProductPutaway(1002)"
>
<u-icon name="arrow-downward" color="#666" size="14"></u-icon>
下架
</view>
<view
class="commodity-btn-item commodity-btn-item-2"
:hairline="true"
:plain="true"
shape="circle"
@click="delectCommodity"
>
<u-icon name="trash" color="#666" size="14"></u-icon>
删除
</view>
</view>
<view class="product-tips">
共选择
<text>{{ selectCommodityList.length }}</text>
/100
<text class="product-tips-check" @click="showPopup = true">
查看
<u-icon class="icon-up" size="8" name="arrow-up"></u-icon>
</text>
</view>
</view>
<u-loading-page
style="z-index: 999"
:loading="isEditLoading"
bgColor="rgba(0,0,0,0.5)"
fontSize="32rpx"
color="#fff"
:loadingText="''"
></u-loading-page>
<u-popup
class="batch-popup"
:show="showPopup"
mode="bottom"
:closeable="true"
zIndex="10075"
@close="handerCloseSelct"
>
<view class="batch-popup-content">
<view class="batch-popup-title">已选商品</view>
<view class="tips">
<view class="tips-name">
共选择
<text class="tips-num">{{ selectCommodityList.length }}/100</text>
个商品
</view>
<u-button
@click="emptySelectAll"
class="empty-select-btn"
:hairline="true"
:plain="true"
shape="circle"
>
清空已选
</u-button>
</view>
<view class="select-commodity-list">
<scroll-view
scroll-y
class="select-commodity-list-scroll-view"
:show-scrollbar="false"
:style="{
maxHeight: selectCommodityList.length >= 7 ? '500px' : 'none',
overflowY: selectCommodityList.length >= 7 ? 'auto' : 'visible',
}"
>
<view class="commodity-list">
<view
class="commodity-item"
v-for="(item, index) of selectCommodityList"
:key="index"
>
<view class="commodity-info">
<view
:class="[
'commodity-img',
{
'commodity-img-sold-out': item.product_state_id == 1002,
},
]"
>
<u--image
:src="item.product_image"
radius="8"
width="60px"
height="60px"
@click="handlerShowImg(item.product_image)"
></u--image>
</view>
<view class="commodity-info-box">
<view class="commodity-name">
{{ item.product_name }}
</view>
<view class="commodity-inventory">
库存{{ item.itemQuantity }}
</view>
<view class="commodity-price">
<block class="currency">¥</block>
{{ item.product_unit_price }}
</view>
</view>
</view>
<view
class="icon-content"
@click="delectSelectCommodity(item, index)"
>
<u-icon
class="u-icon-jianhao"
custom-prefix="custom-icon-jianhao_fangxing custom-icon"
size="22"
></u-icon>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</u-popup>
<u-popup
class="affirm-popup"
zIndex="10077"
:show="showAffirmEmptyPopup"
mode="center"
>
<view class="affirm-popup-content">
<view class="affirm-popup-title">确认清空</view>
<view class="affirm-popup-tips">
已选择
<text style="margin: 0 6rx">
{{ this.selectCommodityList.length }}
</text>
个商品,清空后需要重新勾选
</view>
<view class="popup-btn-list">
<u-button
class="btn-item"
:hairline="true"
:plain="true"
shape="circle"
@click="showAffirmEmptyPopup = false"
>
取消
</u-button>
<u-button
class="btn-item btn-item-2"
:hairline="true"
:plain="true"
shape="circle"
@click="handerAffirmEmptyAll"
>
清空
</u-button>
</view>
</view>
</u-popup>
<u-popup
class="affirm-popup"
zIndex="10077"
:show="showDelectPopup"
mode="center"
>
<view class="affirm-popup-content">
<view class="affirm-popup-title">确认删除</view>
<view class="affirm-popup-tips">
当前已选择
<text style="margin: 0 6rx">
{{ this.selectCommodityList.length }}
</text>
个商品,删除后不可恢复!
</view>
<view class="popup-btn-list">
<u-button
class="btn-item"
:hairline="true"
:plain="true"
shape="circle"
@click="showDelectPopup = false"
>
取消
</u-button>
<u-button
class="btn-item btn-item-2"
:hairline="true"
:plain="true"
shape="circle"
@click="handerDelectCommodity"
>
删除
</u-button>
</view>
</view>
</u-popup>
<u-popup
class="affirm-popup"
zIndex="10077"
:show="showBackPopup"
mode="center"
>
<view class="affirm-popup-content">
<view class="affirm-popup-title">确认退出</view>
<view class="affirm-popup-tips">
已选择
<text style="margin: 0 6rx">
{{ this.selectCommodityList.length }}
</text>
个商品,现在退出将不保留操作记录
</view>
<view class="popup-btn-list">
<u-button
class="btn-item"
:hairline="true"
:plain="true"
shape="circle"
@click="showBackPopup = false"
>
取消
</u-button>
<u-button
class="btn-item btn-item-2"
:hairline="true"
:plain="true"
shape="circle"
@click="handerPageBack"
>
确认
</u-button>
</view>
</view>
</u-popup>
<u-popup
class="right-popup"
zIndex="1000"
:overlay="false"
:show="showRightPopup"
mode="right"
>
<batchSearch
@pageBack="showRightPopup = false"
:selectCommodityList="selectCommodityList"
@searchCommodity="searchCommodity"
/>
</u-popup>
<tui-gallery
:urls="showImgList"
:show="showImg"
@hide="hideImg"
></tui-gallery>
</view>
</template>
<script>
import {
GetProductCategoryTree,
GetProductList,
UpdateProductPutaway,
DelectCommodity,
} from "@/api/warehouse/productList";
import navBar from "@/components/uni-nav-bar/uni-nav-bar";
import tuiCollapse from "../manage/components/tui-collapse/tui-collapse.vue";
import favoriteLoading from "@/components/favorite-loading/favorite-loading.vue";
import tuiGallery from "@/components/tui-gallery/tui-gallery.vue";
import batchSearch from "./batchSearch.vue";
export default {
name: "productList",
components: {
navBar,
tuiCollapse,
favoriteLoading,
tuiGallery,
batchSearch,
},
data() {
return {
tabbar: [],
height: 0, //scroll-view高度
top: 0,
currentTab: 0, //预设当前项的值
current: 0, // 手风琴状态
scrollViewId: "id_0",
commodityStatusList: [
{
value: null,
name: "全部",
badge: {
value: 0,
},
},
{
// 1001-正常;1002-下架仓库中;1000-违规禁售
value: 1001,
name: "销售中",
badge: {
value: 0,
},
},
{
value: 1002,
name: "仓库中",
badge: {
value: 0,
},
},
{
value: 1000,
name: "违规禁售",
badge: {
value: 0,
},
},
{
value: 1003,
name: "待审核",
badge: {
value: 0,
},
},
],
commodityList: [],
pageNum: 1,
pageSize: 20,
currCategoryId: null,
currProductStateId: null,
currTabChildrenIndex: 0,
collApseIndex: 0,
loadingCommodityData: false,
loadingDownCommodityData: false,
isNoCommodityData: false,
isNoDownCommodityData: false,
isRefreshing: false,
time: null,
time2: null,
showImgList: [],
showImg: false,
selectCommodityList: [],
showPopup: false,
showAffirmEmptyPopup: false,
isEditLoading: false,
showDelectPopup: false,
showBackPopup: false,
showRightPopup: false,
};
},
computed: {},
onLoad: function (options) {
setTimeout(() => {
uni.getSystemInfo({
success: (res) => {
let header = 60;
let top = 0;
//#ifdef H5
top = 24;
//#endif
this.height = res.windowHeight - uni.upx2px(header);
this.top = top + uni.upx2px(header);
},
});
}, 50);
},
onReady() {},
onShow() {
this.getProductCategoryTree();
},
mounted() {},
methods: {
pageBack() {
if (this.selectCommodityList.length > 0) {
this.showBackPopup = true;
} else {
uni.navigateBack();
}
},
handerPageBack() {
uni.navigateBack();
},
// 点击标题切换当前页时改变样式
swichNav: function (e) {
let cur = e.currentTarget.dataset.current;
if (this.currentTab == cur) {
return false;
} else {
this.currentTab = cur;
this.checkCor();
}
},
//判断当前滚动超过一屏时设置tab标题滚动条。
checkCor: function () {
if (this.currentTab > 6) {
this.scrollViewId = `id_${this.currentTab - 2}`;
} else {
this.scrollViewId = `id_0`;
}
},
async getProductCategoryTree() {
let res = await GetProductCategoryTree();
if (res && res.status == 200) {
this.tabbar = res.data;
// 定义递归函数,处理每个 item 及其 children
const resetCheckboxLists = (items) => {
return items.map((item) => {
const newItem = {
...item,
checkboxList: [],
checkboxAllList: [],
};
// 如果有 children递归处理
if (item.children && Array.isArray(item.children)) {
newItem.children = resetCheckboxLists(item.children);
}
return newItem;
});
};
// 应用递归函数处理整个 tabbar
this.tabbar = resetCheckboxLists(res.data);
if (this.tabbar.length > 0) {
if (this.tabbar[0].children.length > 0) {
this.current = 0; // 打开手风琴
this.currentTab = this.tabbar[0].children[0].id;
this.currCategoryId = this.tabbar[0].children[0].id;
this.currTabChildrenIndex = 0;
} else {
this.currCategoryId = this.tabbar[0].id;
}
this.getProductList();
}
}
},
async getProductList() {
this.isNoCommodityData = false;
if (this.loadingDownCommodityData) {
this.loadingCommodityData = false;
} else {
this.loadingCommodityData = true;
}
if (this.isRefreshing) {
this.pageNum = 1;
this.isNoCommodityData = false;
this.isNoDownCommodityData = false;
}
let params = {
kind_id: "1201,1202,1203",
pageNum: this.pageNum,
pageSize: this.pageSize,
product_state_id: this.currProductStateId,
category_id: this.currCategoryId,
openCount: true,
};
let res = await GetProductList(params);
if (res && res.status == 200) {
if (this.loadingDownCommodityData) {
if (res.data.items.length <= 0) {
this.loadingDownCommodityData = false;
this.isNoDownCommodityData = true;
return;
} else {
this.commodityList = [...this.commodityList, ...res.data.items];
}
} else {
this.commodityList = res.data.items;
}
if (this.commodityList.length <= 0) {
this.isNoCommodityData = true;
}
this.autoCheckSelectedProducts();
var {
allRecords,
normalRecords,
offRecords,
illegalRecords,
unCheckedRecords,
} = res.data;
this.commodityStatusList[0].badge.value = allRecords;
this.commodityStatusList[1].badge.value = normalRecords;
this.commodityStatusList[2].badge.value = offRecords;
this.commodityStatusList[3].badge.value = illegalRecords;
this.commodityStatusList[4].badge.value = unCheckedRecords;
this.$nextTick(() => {
this.$refs.tabsRef.init();
});
}
this.loadingDownCommodityData = false;
this.loadingCommodityData = false;
this.isRefreshing = false;
},
// 自动勾选已在selectCommodityList中的商品
autoCheckSelectedProducts() {
if (!this.commodityList.length || !this.selectCommodityList.length)
return;
// 获取当前页所有商品ID
const currentPageIds = this.commodityList.map((p) => p.product_id);
// 获取当前页中已选中的商品ID
const selectedIdsInCurrentPage = this.selectCommodityList
.filter((item) => currentPageIds.includes(item.product_id))
.map((item) => item.product_id);
if (!selectedIdsInCurrentPage.length) return;
// 根据是否有子分类决定操作哪个数据
const hasChildren = this.tabbar[this.collApseIndex]?.children?.length > 0;
const targetData = hasChildren
? this.tabbar[this.collApseIndex].children[this.currTabChildrenIndex]
: this.tabbar[this.collApseIndex];
if (!targetData) return;
// 合并去重,避免重复添加
const newCheckboxList = [
...new Set([
...(targetData.checkboxList || []),
...selectedIdsInCurrentPage,
]),
];
// 更新复选框状态
targetData.checkboxList = newCheckboxList;
// 检查是否需要更新全选状态
const allCurrentPageIds = this.commodityList
.filter((item) => item.product_id)
.map((item) => item.product_id);
const isAllSelected = allCurrentPageIds.every((id) =>
newCheckboxList.includes(id)
);
if (isAllSelected) {
targetData.checkboxAllList = ["select-all"];
}
},
handleRefresh() {
this.isRefreshing = true;
this.getProductList();
},
async updateProductPutaway(type) {
if (this.selectCommodityList.length <= 0) {
uni.$u.toast("请先选择商品");
return;
}
this.isEditLoading = true;
let productIdList = this.selectCommodityList.map(
(item) => item.product_id
);
let params = {
product_id: productIdList.toString(),
product_state_id: type,
};
let res = await UpdateProductPutaway(params);
if (res && res.status) {
uni.$u.toast(
`${this.selectCommodityList.length}个商品${
type == 1001 ? "上架" : "下架"
}成功`
);
this.commodityList = this.commodityList.map((item) => {
if (productIdList.includes(item.product_id)) {
return {
...item,
product_state_id: type,
};
}
return item;
});
await this.getProductList();
this.clearCheckboxLists(this.tabbar);
this.selectCommodityList = [];
}
this.isEditLoading = false;
},
async updateCommodityStatistics() {
let params = {
kind_id: "1201,1202,1203",
pageNum: this.pageNum,
pageSize: this.pageSize,
product_state_id: this.currProductStateId,
category_id: this.currCategoryId,
openCount: true,
};
let result = await GetProductList(params);
if (result && result.status == 200) {
var {
allRecords,
normalRecords,
offRecords,
illegalRecords,
unCheckedRecords,
} = result.data;
this.commodityStatusList[0].badge.value = allRecords;
this.commodityStatusList[1].badge.value = normalRecords;
this.commodityStatusList[2].badge.value = offRecords;
this.commodityStatusList[3].badge.value = illegalRecords;
this.commodityStatusList[4].badge.value = unCheckedRecords;
}
},
handerScrolltolower() {
if (this.tabbar[this.collApseIndex].children.length > 0) {
this.tabbar[this.collApseIndex].children[
this.currTabChildrenIndex
].checkboxAllList = [];
} else {
this.tabbar[this.collApseIndex].checkboxAllList = [];
}
if (this.isNoDownCommodityData) return;
this.loadingDownCommodityData = true;
this.time = setTimeout(() => {
this.pageNum = this.pageNum + 1;
this.getProductList();
}, 500);
},
handerCommodityStatus(e) {
this.pageNum = 1;
this.pageSize = 20;
this.isNoDownCommodityData = false;
this.isNoCommodityData = false;
this.currProductStateId = e.value;
this.commodityList = [];
this.getProductList();
},
handerSearchCommodityStatus() {},
emptySelectAll() {
this.showAffirmEmptyPopup = true;
},
handerAffirmEmptyAll() {
this.clearCheckboxLists(this.tabbar);
this.selectCommodityList = [];
this.showAffirmEmptyPopup = false;
this.showPopup = false;
},
clearCheckboxLists(data) {
data.forEach((item) => {
item.checkboxList = [];
item.checkboxAllList = [];
if (item.children && item.children.length) {
this.clearCheckboxLists(item.children);
}
});
},
handerCollApse(e) {
let index = e.index;
//手风琴展开状态 -1 == 不展开
const oldCategoryId = this.currCategoryId;
this.current = this.current == index ? -1 : e.index;
// 如果current没有实际变化点击的是已经展开的项直接返回
if (this.current != -1 && this.tabbar[index].children.length > 0) {
this.currentTab =
this.tabbar[index].children[this.currTabChildrenIndex].id;
this.currCategoryId =
this.tabbar[index].children[this.currTabChildrenIndex].id;
} else {
this.currentTab = index;
this.currCategoryId = this.tabbar[index].id;
if (this.current != -1) {
this.currTabChildrenIndex = 0;
}
}
this.pageNum = 1;
this.pageSize = 20;
if (this.collApseIndex == index) {
return;
}
if (this.currCategoryId != oldCategoryId && this.current != -1) {
this.getProductList();
this.collApseIndex = index;
}
},
// 点击子集时触发
handerCollApseChildren(index1, index2) {
const oldCategoryId = this.currCategoryId;
//获取当前子集下的坐标
this.currTabChildrenIndex = index2;
//active状态
this.currentTab = this.tabbar[index1].children[index2].id;
//当前子集id
this.currCategoryId = this.tabbar[index1].children[index2].id;
if (this.currCategoryId != oldCategoryId) {
this.getProductList();
}
},
handleSelectAll(e, isHaveChildren) {
const MAX_LIMIT = 100;
const currentTabData = isHaveChildren
? this.tabbar[this.collApseIndex].children[this.currTabChildrenIndex]
: this.tabbar[this.collApseIndex];
// 获取当前页所有有效商品ID
const allProductIds = this.commodityList
.filter((item) => item.product_id)
.map((item) => item.product_id);
// 判断当前是否全选状态
const isCurrentlyAllSelected =
currentTabData.checkboxList?.length === allProductIds.length &&
allProductIds.length > 0;
// 计算新的选中列表
let newIdList = isCurrentlyAllSelected ? [] : [...allProductIds];
// 计算当前总选中数(包括其他标签页)
const currentTotalSelected = this.calculateTotalSelected(
[],
isHaveChildren
);
// 计算可添加的数量
const availableSlots = MAX_LIMIT - currentTotalSelected;
// 如果可添加的数量小于当前页商品数,只添加部分商品
if (availableSlots < newIdList.length && !isCurrentlyAllSelected) {
// 只添加可添加数量的商品
newIdList = newIdList.slice(0, availableSlots);
// 如果可添加数量为0则提示用户
if (availableSlots > 0) {
uni.$u.toast(`最多可选${MAX_LIMIT}个商品,超出的商品无法选中`);
} else {
uni.$u.toast(`最多可选${MAX_LIMIT}个商品,超出的商品无法选中`);
return; // 添加return防止继续执行
}
}
// 检查是否超过限制
const totalSelected = this.calculateTotalSelected(
newIdList,
isHaveChildren
);
if (totalSelected > MAX_LIMIT) {
this.clearCheckboxState(currentTabData);
uni.$u.toast(`最多可选${MAX_LIMIT}个商品,超出的商品无法选中`);
return;
}
// 更新当前标签页的复选框状态
this.updateCheckboxState(currentTabData, newIdList, allProductIds);
// 更新选中的商品对象列表
this.updateSelectedCommodities(newIdList);
},
/**
* 处理单个商品选择变化
* @param {Array} list 变化的复选框值列表
* @param {Boolean} isHaveChildren 当前是否有子分类
*/
handleSingleCheckboxChange(list, isHaveChildren) {
const MAX_LIMIT = 100;
const currentTabData = isHaveChildren
? this.tabbar[this.collApseIndex].children[this.currTabChildrenIndex]
: this.tabbar[this.collApseIndex];
// 过滤掉全选标识和无效ID
let newIdList = list
.filter((id) => id !== "select-all" && id !== "")
.filter((id, index, self) => self.indexOf(id) === index); // 去重
// 计算当前总选中数(包括其他标签页)
const currentTotalSelected = this.calculateTotalSelected(
[],
isHaveChildren
);
// 计算可添加的数量
const availableSlots = MAX_LIMIT - currentTotalSelected;
// 如果尝试添加的数量超过可用槽位
if (newIdList.length > currentTabData.checkboxList.length) {
const addedCount =
newIdList.length - currentTabData.checkboxList.length;
// 如果添加的数量超过可用槽位
if (addedCount > availableSlots) {
// 只添加可添加数量的商品
const addedIds = newIdList.filter(
(id) => !currentTabData.checkboxList.includes(id)
);
const partialAddedIds = addedIds.slice(0, availableSlots);
newIdList = [...currentTabData.checkboxList, ...partialAddedIds];
if (availableSlots > 0) {
uni.$u.toast(`最多可选${MAX_LIMIT}个商品,超出的商品无法选中`);
} else {
uni.$u.toast(`最多可选${MAX_LIMIT}个商品,超出的商品无法选中`);
}
}
}
// 获取当前页所有有效商品ID
const allProductIds = this.commodityList
.filter((item) => item.product_id)
.map((item) => item.product_id);
// 更新当前标签页的复选框状态
this.updateCheckboxState(currentTabData, newIdList, allProductIds);
// 更新选中的商品对象列表
this.updateSelectedCommodities(newIdList);
},
/**
* 计算总选中数量(考虑其他标签页的选中)
*/
calculateTotalSelected(newList, isHaveChildren) {
// 计算当前页新选中的商品数量
const currentSelectedCount = newList.length;
// 计算其他标签页已选中的商品数量
let otherSelectedCount = 0;
this.tabbar.forEach((tab, tabIndex) => {
if (tabIndex === this.current && isHaveChildren) {
// 如果是当前父分类,检查其他子分类
tab.children.forEach((child, childIndex) => {
if (
!(
tabIndex === this.current &&
childIndex === this.currTabChildrenIndex
)
) {
otherSelectedCount += child.checkboxList?.length || 0;
}
});
} else if (tabIndex !== this.current) {
// 其他分类
if (tab.children?.length > 0) {
otherSelectedCount += tab.children.reduce(
(sum, child) => sum + (child.checkboxList?.length || 0),
0
);
} else {
otherSelectedCount += tab.checkboxList?.length || 0;
}
}
});
return currentSelectedCount + otherSelectedCount;
},
/**
* 更新当前标签页的复选框状态
* @param {Object} tabData 当前标签页数据
* @param {Array} idList 选中的商品ID列表
* @param {Array} allIds 当前页所有商品ID
*/
updateCheckboxState(tabData, idList, allIds) {
// 更新复选框的ID列表
tabData.checkboxList = idList;
// 计算是否全选状态
const isAllSelected =
idList.length === allIds.length &&
allIds.length > 0 &&
idList.every((id) => allIds.includes(id));
// 更新全选复选框状态
tabData.checkboxAllList = isAllSelected ? ["select-all"] : [];
},
/**
* 更新选中的商品对象列表
* @param {Array} idList 选中的商品ID列表
*/
updateSelectedCommodities(idList) {
// 创建当前页商品ID到商品对象的映射
const currentProductMap = this.commodityList.reduce((map, product) => {
map[product.product_id] = product;
return map;
}, {});
// 更新选中商品列表:
// 1. 保留不在当前页的商品
// 2. 添加当前页新选中的商品
// 3. 移除当前页取消选中的商品
// 获取当前页所有商品ID
const currentPageIds = this.commodityList.map((p) => p.product_id);
// 保留不在当前页的选中商品
const otherSelectedProducts = this.selectCommodityList.filter(
(item) => !currentPageIds.includes(item.product_id)
);
// 添加当前页选中的商品
const currentSelectedProducts = idList
.filter((id) => currentProductMap[id])
.map((id) => currentProductMap[id]);
// 合并并去重
const newSelectedList = [
...otherSelectedProducts,
...currentSelectedProducts,
];
// 确保不超过100个
this.selectCommodityList = newSelectedList.slice(0, 100);
},
/**
* 清空复选框状态
*/
clearCheckboxState(tabData) {
tabData.checkboxAllList = [];
tabData.checkboxList = [];
},
delectSelectCommodity(item, index) {
this.selectCommodityList.splice(index, 1);
// 从 checkboxList 中删除对应的 product_id
const productIdIndex = this.tabbar[this.currentTab].checkboxList.indexOf(
item.product_id
);
if (productIdIndex !== -1) {
this.tabbar[this.currentTab].checkboxList.splice(productIdIndex, 1);
}
// 如果 selectAll 被选中且 checkboxList 为空,则取消 selectAll
if (
this.tabbar[this.currentTab].checkboxAllList.includes("select-all") &&
this.tabbar[this.currentTab].checkboxList.length === 0
) {
this.tabbar[this.currentTab].checkboxAllList = [];
}
if (this.selectCommodityList.length <= 0) {
this.showPopup = false;
}
},
searchCommodity(list) {
this.selectCommodityList = [
...new Set([...this.selectCommodityList, ...list]),
];
this.autoCheckSelectedProducts();
const clearCheckboxAllList = (data) => {
data.forEach((item) => {
item.checkboxAllList = [];
if (item.children && item.children.length) {
clearCheckboxAllList(item.children);
}
});
};
if (this.selectCommodityList.length == 100) {
clearCheckboxAllList(this.tabbar);
}
},
handlerShowImg(url) {
if (!url) return;
this.showImg = true;
this.showImgList = [
{
src: url,
desc: "", // You can add description if needed
},
];
},
delectCommodity() {
this.showDelectPopup = true;
},
async handerDelectCommodity() {
const productIds = this.selectCommodityList.map(
(item) => item.product_id
);
let params = {
product_ids: productIds.toString(),
};
let res = await DelectCommodity(params);
if (res && res.status == 200) {
uni.$u.toast("操作成功");
this.commodityList = this.commodityList.filter(
(item) => !productIds.includes(item.product_id)
);
await this.updateCommodityStatistics();
this.clearCheckboxLists(this.tabbar);
this.selectCommodityList = [];
}
this.showDelectPopup = false;
},
hideImg() {
this.showImg = false;
},
handerCloseSelct() {
this.showPopup = false;
},
detail(e) {
uni.navigateTo({
url: "../productDetail/productDetail",
});
},
productList(e) {
let key = e.currentTarget.dataset.key;
uni.navigateTo({
url: "../productList/productList?searchKey=" + key,
});
},
search: function () {
uni.navigateTo({
url: "../../news/search/search",
});
},
skipuBatch() {
uni.navigateTo({
url: "/pages/warehouse/manage/batch",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "@/styles/variables.scss";
page {
background: #f5f6fa;
}
.batch-container {
height: calc(100vh);
background: #fff;
::v-deep.uni-nav-bar-text {
font-weight: bold;
font-size: 32rpx;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.tui-searchbox {
width: 100%;
height: 92rpx;
padding: 0 30rpx;
box-sizing: border-box;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
left: 0;
top: 0;
/* #ifdef H5 */
top: 0;
/* #endif */
z-index: 100;
}
.tui-searchbox::after {
content: "";
position: absolute;
border-bottom: 1rpx solid #d2d2d2;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
bottom: 0;
right: 0;
left: 0;
}
.tui-search-input {
width: 100%;
height: 60rpx;
background: #f1f1f1;
border-radius: 30rpx;
font-size: 26rpx;
color: #999;
display: flex;
align-items: center;
justify-content: center;
}
.tui-search-text {
padding-left: 16rpx;
}
.tab-view {
/* height: 100%; */
width: 210rpx;
position: fixed;
left: 0;
bottom: 88rpx;
z-index: 10;
background: #f5f6fa;
}
.tab-bar-item {
width: 210rpx;
min-height: 88rpx;
background: #f5f6fa;
box-sizing: border-box;
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 28rpx;
color: #555;
font-weight: 400;
padding-left: 44rpx;
word-wrap: break-word;
word-break: break-all;
}
::v-deep .tui-icon-arrow {
left: 8rpx;
}
.active {
position: relative;
color: #000;
background: #fff;
}
.tab-bar-item-2 {
min-height: 96rpx;
font-size: 24rpx;
background: #fff !important;
}
.active2 {
background: #fff;
}
.active::before {
content: "";
position: absolute;
border-left: 4rpx solid #fe411b;
height: 30rpx;
left: 0;
top: 50%;
transform: translateY(-50%);
}
/* 左侧导航布局 end*/
.right-box {
width: 100%;
position: fixed;
padding-left: 210rpx;
box-sizing: border-box;
left: 0;
background: #fff;
}
.page-view {
width: 100%;
overflow: hidden;
padding-top: 20rpx;
padding-right: 20rpx;
box-sizing: border-box;
padding-bottom: env(safe-area-inset-bottom);
}
.swiper {
width: 100%;
height: 220rpx;
border-radius: 12rpx;
overflow: hidden;
transform: translateZ(0);
}
/* #ifdef MP-WEIXIN */
.swiper .wx-swiper-dot {
width: 8rpx;
height: 8rpx;
display: inline-flex;
background: none;
justify-content: space-between;
}
.swiper .wx-swiper-dot::before {
content: "";
flex-grow: 1;
background: rgba(255, 255, 255, 0.8);
border-radius: 16rpx;
overflow: hidden;
}
.swiper .wx-swiper-dot-active::before {
background: #fff;
}
.swiper .wx-swiper-dot.wx-swiper-dot-active {
width: 16rpx;
}
/* #endif */
/* #ifndef MP-WEIXIN */
::v-deep .swiper .uni-swiper-dot {
width: 8rpx;
height: 8rpx;
display: inline-flex;
background: none;
justify-content: space-between;
}
::v-deep .swiper .uni-swiper-dot::before {
content: "";
flex-grow: 1;
background: rgba(255, 255, 255, 0.8);
border-radius: 16rpx;
overflow: hidden;
}
::v-deep .swiper .uni-swiper-dot-active::before {
background: #fff;
}
::v-deep .swiper .uni-swiper-dot.uni-swiper-dot-active {
width: 16rpx;
}
/* #endif */
.slide-image {
width: 100%;
height: 220rpx;
}
.class-box {
padding-top: 30rpx;
}
.class-item {
background: #fff;
width: 100%;
box-sizing: border-box;
padding: 20rpx;
margin-bottom: 20rpx;
border-radius: 12rpx;
}
.class-name {
font-size: 22rpx;
}
.g-container {
/* padding-top: 20rpx; */
display: flex;
display: -webkit-flex;
justify-content: flex-start;
flex-direction: row;
flex-wrap: wrap;
}
.g-box {
width: 33.3333%;
text-align: center;
padding-top: 40rpx;
}
.g-image {
width: 120rpx;
height: 120rpx;
}
.g-title {
font-size: 22rpx;
}
.tui-content {
padding: 20rpx 30rpx;
background-color: #fff;
color: #555;
font-size: 26rpx;
}
.u-tabs {
margin-left: 20rpx;
border-bottom: 1px solid #f3f3f3;
}
.commodity-status {
position: sticky;
top: 0;
left: 0;
width: 100%;
z-index: 3;
background: #fff;
}
.m-loading-box {
text-align: center;
padding: 40rpx;
color: #aaaa;
font-size: 28rpx;
}
.checkbox-all {
position: sticky;
top: 0;
left: 0;
display: flex;
align-items: center;
height: 100rpx;
width: 100%;
z-index: 100;
background: #fff;
padding-left: 24rpx;
}
.checkbox-all-name {
font-size: 28rpx;
}
.commodity-list {
padding: 24rpx;
.commodity-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
width: 100%;
.commodity-checkbox {
margin-right: 20rpx;
}
.commodity-info {
display: flex;
.commodity-img {
position: relative;
margin-right: 20rpx;
width: 128rpx;
height: 128rpx;
border: 1px solid $uni-border-color;
border-radius: 24rpx;
}
.commodity-img-sold-out {
&::before {
position: absolute;
bottom: 0;
width: 100%;
height: 20px;
line-height: 20px;
content: "已下架";
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
z-index: 99;
font-size: 24rpx;
border-bottom-left-radius: 16rpx;
border-bottom-right-radius: 16rpx;
}
}
.commodity-info-box {
display: flex;
flex-flow: column;
justify-content: space-between;
color: #666;
.commodity-name {
font-size: 28rpx;
word-break: break-all; /* 允许在任意字符间断行 */
overflow-wrap: break-word; /* 优先在单词间断行 */
display: -webkit-box;
-webkit-line-clamp: 2; /* 限制最多2行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis; /* 超出部分显示省略号 */
color: #000;
}
.commodity-inventory {
font-size: 24rpx;
}
.commodity-price {
font-size: 28rpx;
color: #fe411b;
.currency {
margin-right: 4px;
font-size: 24rpx;
}
}
}
}
}
}
.no-data {
.no-data-bg {
margin: 60% auto;
margin-bottom: 0;
width: 300rpx;
height: 200rpx;
background-image: url("../../../static/no-commodity.png");
background-size: 100%;
}
.no-data-tips {
margin: 80rpx auto;
color: #aaaaaa;
text-align: center;
}
}
.productList-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
padding: 0 24rpx;
height: 88rpx;
background: #fff;
border-top: 2rpx solid $uni-border-color;
display: flex;
align-items: center;
justify-content: space-between;
.commodity-btn-item {
display: flex;
align-items: center;
justify-content: center;
padding: 8rpx 12rpx;
font-size: 24rpx;
border: 2rpx solid #d2d2d2;
color: #333;
text-align: center;
margin-right: 24rpx;
border-radius: 40rpx;
line-height: 1;
}
.commodity-btn-item-2 {
color: $base-color;
border-color: $base-color;
::v-deep .u-icon__icon {
color: $base-color !important;
}
}
.product-tips {
font-size: 24rpx;
color: #666;
text {
color: $base-color;
}
.product-tips-check {
margin-left: 10rpx;
color: $base-color;
.icon-up {
color: $base-color;
display: inline-block;
::v-deep.uicon-arrow-up {
margin-left: 8rpx;
top: -1px !important;
color: $base-color !important;
}
}
}
}
.commodity-btn-content {
display: flex;
align-items: center;
justify-content: center;
}
.bottom-item {
display: flex;
align-items: center;
justify-content: center;
.bottom-item-name {
font-size: 28rpx;
color: #222;
}
.u-icon {
margin-right: 8rpx;
}
}
}
.batch-popup {
::v-deep.u-popup__content {
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
.batch-popup-content {
// width: 100%;
.batch-popup-title {
padding: 40rpx;
text-align: center;
font-weight: 500;
}
.tips {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
padding-left: 20rpx;
font-size: 15px;
color: #626262;
.tips-num {
padding: 0 8rpx;
font-size: 14px;
color: #000;
}
.empty-select-btn {
margin: 0;
margin-right: 20rpx;
width: 156rpx;
height: 60rpx;
font-size: 26rpx;
font-weight: bold;
border-color: $base-color;
color: $base-color;
&::after {
border: none;
}
}
}
.select-commodity-list {
padding: 20rpx;
.commodity-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
padding: 0 40rpx;
.u-icon-jianhao {
::v-deep.u-icon__icon {
color: $base-color !important;
}
}
.commodity-info {
display: flex;
margin-bottom: 24rpx;
.commodity-img {
position: relative;
margin-right: 20rpx;
width: 128rpx;
height: 128rpx;
border: 1px solid $uni-border-color;
border-radius: 24rpx;
}
.commodity-img-sold-out {
&::before {
position: absolute;
bottom: 0;
width: 100%;
height: 20px;
line-height: 20px;
content: "已下架";
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
z-index: 99;
font-size: 24rpx;
border-bottom-left-radius: 16rpx;
border-bottom-right-radius: 16rpx;
}
}
.commodity-info-box {
display: flex;
flex-flow: column;
justify-content: space-between;
color: #666;
.commodity-name {
font-size: 28rpx;
word-break: break-all; /* 允许在任意字符间断行 */
overflow-wrap: break-word; /* 优先在单词间断行 */
display: -webkit-box;
-webkit-line-clamp: 2; /* 限制最多2行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis; /* 超出部分显示省略号 */
color: #000;
}
.commodity-inventory {
font-size: 24rpx;
}
.commodity-price {
font-size: 28rpx;
color: #fe411b;
.currency {
margin-right: 4px;
font-size: 24rpx;
}
}
}
}
}
}
}
}
.affirm-popup {
::v-deep.u-popup__content {
border-radius: 16rpx;
}
::v-deep.u-fade-enter-to {
z-index: 10076 !important;
}
.affirm-popup-content {
width: 600rpx;
.affirm-popup-title {
padding: 40rpx;
text-align: center;
font-weight: bold;
}
.affirm-popup-tips {
padding: 0 60rpx;
font-size: 28rpx;
}
.popup-btn-list {
display: flex;
margin: 50rpx;
.btn-item {
width: 46%;
height: 80rpx;
border-color: #909193;
&::after {
border: none;
}
}
.btn-item-2 {
background: $base-color;
color: #fff;
border: none;
}
}
}
}
.right-popup {
.right-popup-content {
width: 100vw;
}
}
.commodity-loading {
margin: 70% auto;
display: flex;
}
}
</style>