品牌列表,批量删除

master
fy 2026-01-22 14:50:26 +08:00
parent 90476adffa
commit 61602c3adb
7 changed files with 1483 additions and 17 deletions

View File

@ -250,8 +250,66 @@ export function importProductData(filePath, formData) {
// 获取导入记录 // 获取导入记录
export function getImportRecord() { export function getImportRecord() {
return request8081({ return request8081({
baseUrl: 'http://192.168.0.7:8081', baseUrl: 'http://193.112.94.36:8081',
url: '/mall/product/importRecord', url: '/mall/product/importRecord',
method: 'get' method: 'get'
}); });
} }
// 批量删除商品
export function batchDeleteProduct(ids) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: '/mall/product/batchDelete',
method: 'delete',
data: { ids: ids }
})
}
// 添加品牌
export function addBrand(data) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: '/mall/brand/add',
method: 'post',
data: data
})
}
// 获取品牌列表
export function getBrandList(params) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: '/mall/brand/list',
method: 'get',
params: params
})
}
// 获取品牌树状结构
export function getBrandTree(storeId) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: `/mall/brand/getTree/${storeId}`,
method: 'get'
})
}
// 删除品牌
export function deleteBrand(brandId) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: `/mall/brand/delete/${brandId}`,
method: 'delete'
})
}
// 修改品牌名称
export function updateBrand(data) {
return request8081({
baseUrl: 'http://193.112.94.36:8081',
url: '/mall/brand/update',
method: 'post',
data: data
})
}

View File

@ -3,6 +3,7 @@ module.exports = {
// baseUrl: 'https://vue.ruoyi.vip/prod-api', // baseUrl: 'https://vue.ruoyi.vip/prod-api',
baseUrl:'http://193.112.94.36:8080', baseUrl:'http://193.112.94.36:8080',
// baseUrl = 'https://api.ruoyi.com'
// prodApi: 'https://vue.ruoyi.vip/prod-api', // prodApi: 'https://vue.ruoyi.vip/prod-api',
// baseUrl: 'http://localhost:8080', // baseUrl: 'http://localhost:8080',
// 应用信息 // 应用信息

View File

@ -155,6 +155,18 @@
"style": { "style": {
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
},
{
"path": "pages/batchDeleteProduct/batchDeleteProduct",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/addBrand/addBrand",
"style": {
"navigationBarTitleText": ""
}
} }
], ],
"tabBar": { "tabBar": {

961
pages/addBrand/addBrand.vue Normal file
View File

@ -0,0 +1,961 @@
<template>
<view class="container">
<!-- 顶部导航栏 -->
<view class="navbar">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
</view>
<view class="title">品牌管理</view>
</view>
<!-- 搜索框 -->
<view class="search-box">
<input type="text" placeholder="搜索品牌名称" class="search-input" />
</view>
<!-- 品牌树状列表 -->
<view class="brand-tree">
<!-- 一级品牌项 -->
<view v-for="(brand, index) in brandList" :key="index" class="level1-item">
<view class="level1-header">
<text class="expand-icon" @click="toggleExpand(index)">{{ brand.expanded ? '' : '' }}</text>
<text class="level1-name" @click="toggleExpand(index)">{{ brand.name }}</text>
<view class="menu-container" style="position: relative;">
<view class="menu-btn" @click.stop="toggleMenuOptions(index)">
<text class="menu-icon">···</text>
</view>
<!-- 菜单选项列表 -->
<view v-if="showMenuOptions && selectedBrandIndex === index" class="menu-options">
<!-- 重命名选项 -->
<view class="menu-option" @click.stop="showRenameDialog(index)">
<text class="rename-icon"></text>
<text>重命名</text>
</view>
<!-- 删除品牌选项 -->
<view class="menu-option delete-option" @click.stop="showDeleteBrandConfirm(index)">
<text class="trash-icon">🗑</text>
<text>删除品牌</text>
</view>
</view>
</view>
</view>
<!-- 二级子品牌列表展开时显示 -->
<view v-if="brand.expanded" class="level2-container">
<view
v-for="(subBrand, subIndex) in brand.children"
:key="subIndex"
class="level2-item"
draggable="true"
@dragstart="dragStart(index, subIndex)"
@dragover="dragOver"
@drop="drop(index, subIndex)"
>
<text class="level2-name">{{ subBrand.name }}</text>
<view class="edit-btn" @click.stop="openEditDialog(index, subIndex)">
<text class="edit-icon"></text>
<text class="edit-text">编辑</text>
</view>
</view>
<!-- 添加子品牌按钮 -->
<view class="add-sub-brand" @click="addSubBrand(index)">
<text class="add-icon">+</text>
<text class="add-text">添加子品牌</text>
</view>
</view>
</view>
</view>
<!-- 底部添加一级品牌按钮 -->
<view class="add-brand-btn" @click="addBrand">
<text class="add-btn-icon">+</text>
<text class="add-btn-text">添加品牌</text>
</view>
<!-- 修改品牌名称弹窗 -->
<view v-if="editDialogVisible" class="dialog-overlay">
<view class="dialog">
<view class="dialog-title">修改品牌名称</view>
<!-- 输入框 -->
<view class="input-wrapper">
<input
v-model="editBrandName"
type="text"
class="dialog-input"
placeholder="请输入品牌名称"
:focus="inputFocus"
@blur="inputFocus = false"
@focus="onInputFocus"
/>
<text v-if="editBrandName" class="clear-icon" @click="clearInput">×</text>
</view>
<!-- 带垃圾桶图标的删除按钮 -->
<view class="delete-btn" @click="showDeleteConfirm">
<text class="trash-icon">🗑</text>
<text>删除品牌</text>
</view>
<view class="dialog-buttons">
<view class="cancel-btn" @click="closeEditDialog"></view>
<view class="confirm-btn" @click="confirmEdit"></view>
</view>
</view>
</view>
<!-- 添加品牌弹窗 -->
<view v-if="addBrandDialogVisible" class="dialog-overlay">
<view class="dialog">
<view class="dialog-title">{{ isAddingSubBrand ? '添加子品牌' : '添加品牌' }}</view>
<!-- 输入框 -->
<view class="input-wrapper">
<input
v-model="addBrandName"
type="text"
class="dialog-input"
placeholder="请输入品牌名称"
:focus="inputFocus"
@blur="inputFocus = false"
@focus="onInputFocus"
/>
<text v-if="addBrandName" class="clear-icon" @click="clearAddInput">×</text>
</view>
<view class="dialog-buttons">
<view class="cancel-btn" @click="closeAddBrandDialog"></view>
<view class="confirm-btn" @click="confirmAddBrand"></view>
</view>
</view>
</view>
<!-- 删除确认弹窗在修改弹窗之上 -->
<view v-if="deleteConfirmVisible" class="delete-confirm-overlay">
<view class="delete-confirm-dialog">
<view class="delete-dialog-title">确认删除</view>
<view class="delete-dialog-content">确定要删除这个品牌吗</view>
<view class="delete-dialog-buttons">
<view class="delete-cancel-btn" @click="hideDeleteConfirm"></view>
<view class="delete-confirm-btn" @click="deleteBrand"></view>
</view>
</view>
</view>
</view>
</template>
<script>
// API
import { getBrandList, addBrand, getBrandTree, deleteBrand, updateBrand } from '@/api/product'
import { getStoreId } from '@/utils/auth'
export default {
data() {
return {
brandList: [],
storeId: null,
editDialogVisible: false,
deleteConfirmVisible: false,
editParentIndex: null,
editSubIndex: null,
editBrandName: '',
inputFocus: false,
dragSource: {
parentIndex: null,
subIndex: null
},
//
addBrandDialogVisible: false,
addBrandName: '',
isAddingSubBrand: false,
currentParentIndex: null,
//
showMenuOptions: false,
selectedBrandIndex: null,
isDeletingLevel1Brand: false,
//
renameDialogVisible: false,
renameBrandName: '',
renameBrandIndex: null,
//
editSubBrandId: null,
}
},
onLoad() {
// storeId
this.storeId = getStoreId()
//
this.loadBrandList()
},
methods: {
goBack() {
uni.navigateBack()
},
//
async loadBrandList() {
try {
// 使getBrandTreestoreIdURL
const res = await getBrandTree(this.storeId)
if (res.code === 200 && res.data) {
//
//
this.brandList = this.formatBrandData(res.data)
}
} catch (error) {
console.error('加载品牌列表失败:', error)
uni.showToast({ title: '加载品牌列表失败', icon: 'none' })
}
},
//
formatBrandData(brandData) {
// idbrandNamechildren
//
return brandData.map(brand => ({
name: brand.brandName,
id: brand.id,
expanded: true,
children: brand.children ? brand.children.map(subBrand => ({
name: subBrand.brandName,
id: subBrand.id
})) : []
}))
},
// /
toggleExpand(index) {
this.brandList[index].expanded = !this.brandList[index].expanded
},
//
addBrand() {
//
this.isAddingSubBrand = false
this.currentParentIndex = null
this.addBrandName = ''
this.addBrandDialogVisible = true
//
this.$nextTick(() => {
setTimeout(() => {
this.inputFocus = true
}, 50)
})
},
//
addSubBrand(parentIndex) {
//
this.isAddingSubBrand = true
this.currentParentIndex = parentIndex
this.addBrandName = ''
this.addBrandDialogVisible = true
//
this.$nextTick(() => {
setTimeout(() => {
this.inputFocus = true
}, 50)
})
},
//
async confirmAddBrand() {
if (!this.addBrandName.trim()) {
uni.showToast({ title: '请输入品牌名称', icon: 'none' })
return
}
try {
//
const params = {
brandName: this.addBrandName.trim(),
storeId: this.storeId
}
// parentId
if (this.isAddingSubBrand) {
params.parentId = this.brandList[this.currentParentIndex].id || 0
} else {
// parentId0
params.parentId = 0
}
//
const res = await addBrand(params)
if (res.code === 200) {
//
this.closeAddBrandDialog()
//
await this.loadBrandList()
uni.showToast({
title: '添加成功',
icon: 'success'
})
} else {
uni.showToast({ title: res.msg || '操作失败', icon: 'none' })
}
} catch (error) {
console.error('添加品牌失败:', error)
uni.showToast({ title: '添加失败', icon: 'none' })
}
},
//
closeAddBrandDialog() {
this.addBrandDialogVisible = false
this.addBrandName = ''
this.inputFocus = false
this.editSubBrandId = null
},
//
clearAddInput() {
this.addBrandName = ''
//
this.$nextTick(() => {
this.inputFocus = true
})
},
//
openEditDialog(parentIndex, subIndex) {
this.editParentIndex = parentIndex
this.editSubIndex = subIndex
//
if (subIndex === undefined) {
//
this.isDeletingLevel1Brand = true
this.editBrandName = this.brandList[parentIndex].name
} else {
//
this.isDeletingLevel1Brand = false
this.editBrandName = this.brandList[parentIndex].children[subIndex].name
}
this.editDialogVisible = true
this.$nextTick(() => {
setTimeout(() => {
this.inputFocus = true
}, 50)
})
},
//
onInputFocus() {
this.inputFocus = true
},
//
closeEditDialog() {
this.editDialogVisible = false
this.editParentIndex = null
this.editSubIndex = null
this.editBrandName = ''
this.inputFocus = false
},
//
clearInput() {
this.editBrandName = ''
setTimeout(() => {
this.inputFocus = true
}, 50)
},
//
async confirmEdit() {
if (this.editBrandName.trim() === '') {
uni.showToast({ title: '请输入品牌名称', icon: 'none' })
return
}
try {
//
let brandId;
//
if (this.isDeletingLevel1Brand) {
// 使id
brandId = this.brandList[this.editParentIndex].id
} else {
// 使id
brandId = this.brandList[this.editParentIndex].children[this.editSubIndex].id
}
const params = {
brandName: this.editBrandName.trim(),
id: brandId,
storeId: this.storeId
}
// API
const res = await updateBrand(params)
if (res.code === 200) {
//
if (this.isDeletingLevel1Brand) {
//
this.brandList[this.editParentIndex].name = this.editBrandName.trim()
} else {
//
this.brandList[this.editParentIndex].children[this.editSubIndex].name = this.editBrandName.trim()
}
//
this.closeEditDialog()
uni.showToast({ title: '修改成功', icon: 'success' })
} else {
uni.showToast({ title: res.msg || '修改失败', icon: 'none' })
}
} catch (error) {
console.error('修改品牌名称失败:', error)
uni.showToast({ title: '修改失败', icon: 'none' })
}
},
//
showDeleteConfirm() {
this.deleteConfirmVisible = true
},
//
hideDeleteConfirm() {
this.deleteConfirmVisible = false
},
//
async deleteBrand() {
try {
let brandId;
//
if (this.isDeletingLevel1Brand) {
// 使id
brandId = this.brandList[this.editParentIndex].id
} else {
// 使id
brandId = this.brandList[this.editParentIndex].children[this.editSubIndex].id
}
// API
const res = await deleteBrand(brandId)
if (res.code === 200) {
//
this.hideDeleteConfirm()
this.closeEditDialog()
//
await this.loadBrandList()
uni.showToast({ title: '删除成功', icon: 'success' })
} else {
uni.showToast({ title: res.msg || '删除失败', icon: 'none' })
}
} catch (error) {
console.error('删除品牌失败:', error)
uni.showToast({ title: '删除失败', icon: 'none' })
}
},
// /
toggleMenuOptions(index) {
if (this.showMenuOptions && this.selectedBrandIndex === index) {
//
this.showMenuOptions = false
this.selectedBrandIndex = null
} else {
//
this.showMenuOptions = true
this.selectedBrandIndex = index
}
},
//
showRenameDialog(index) {
//
this.showMenuOptions = false
this.selectedBrandIndex = null
// parentIndexsubIndex
this.openEditDialog(index)
},
//
showDeleteBrandConfirm(index) {
//
this.showMenuOptions = false
this.selectedBrandIndex = null
//
this.editParentIndex = index
this.editSubIndex = 0
this.isDeletingLevel1Brand = true
//
this.showDeleteConfirm()
},
//
dragStart(parentIndex, subIndex) {
this.dragSource = { parentIndex, subIndex }
},
//
dragOver(e) {
e.preventDefault()
},
//
drop(targetParentIndex, targetSubIndex) {
const { parentIndex, subIndex } = this.dragSource
if (parentIndex === targetParentIndex) {
const draggedItem = this.brandList[parentIndex].children.splice(subIndex, 1)[0]
this.brandList[targetParentIndex].children.splice(targetSubIndex, 0, draggedItem)
}
this.dragSource = { parentIndex: null, subIndex: null }
}
}
}
</script>
<style scoped>
.container {
background-color: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 导航栏 */
.navbar {
background-color: #e62318;
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
position: relative;
height: 88rpx;
box-sizing: border-box;
}
.title {
font-size: 34rpx;
font-weight: bold;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.back-icon {
font-size: 32rpx;
}
.menu-icon {
font-size: 28rpx;
letter-spacing: 6rpx;
}
/* 菜单容器 */
.menu-container {
position: relative;
}
/* 菜单选项列表 */
.menu-options {
position: absolute;
top: 100%;
right: 0;
background-color: #fff;
border: 1rpx solid #f0f0f0;
border-radius: 8rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-top: 10rpx;
min-width: 200rpx;
}
/* 菜单项 */
.menu-option {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
padding: 20rpx 30rpx;
cursor: pointer;
white-space: nowrap;
transition: background-color 0.2s;
}
/* 菜单项 hover 效果 */
.menu-option:active {
background-color: #f5f5f5;
}
/* 重命名选项 */
.menu-option:not(.delete-option) {
color: #333;
}
/* 重命名图标 */
.rename-icon {
font-size: 24rpx;
}
/* 删除品牌选项 */
.menu-option.delete-option {
color: #e62318;
border-top: 1rpx solid #f0f0f0;
}
/* 垃圾桶图标 */
.menu-option .trash-icon {
font-size: 24rpx;
}
/* 搜索框 */
.search-box {
background-color: #fff;
padding: 16rpx 30rpx;
}
.search-input {
background-color: #f5f5f5;
border-radius: 6rpx;
padding: 18rpx;
font-size: 28rpx;
width: 100%;
box-sizing: border-box;
}
/* 品牌树 */
.brand-tree {
flex: 1;
padding: 20rpx 30rpx;
}
.level1-item {
background-color: #fff;
border-radius: 8rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.level1-header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.expand-icon {
font-size: 24rpx;
margin-right: 15rpx;
color: #666;
}
.level1-name {
font-size: 28rpx;
flex: 1;
}
.more-btn {
font-size: 24rpx;
color: #999;
letter-spacing: 4rpx;
}
/* 二级子品牌容器 */
.level2-container {
display: flex;
padding: 24rpx 30rpx;
gap: 24rpx;
flex-wrap: wrap;
background-color: #fff;
}
.level2-item {
background-color: #f5f5f5;
border-radius: 8rpx;
width: 180rpx;
height: 180rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: move;
}
.level2-name {
font-size: 36rpx;
margin-bottom: 8rpx;
}
.edit-btn {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
}
.edit-icon {
margin-right: 4rpx;
}
.add-sub-brand {
border: 2rpx dashed #1677ff;
border-radius: 8rpx;
width: 180rpx;
height: 180rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #1677ff;
}
.add-icon {
font-size: 36rpx;
margin-bottom: 8rpx;
}
.add-text {
font-size: 24rpx;
}
/* 底部添加按钮 */
.add-brand-btn {
background-color: #e62318;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
font-size: 30rpx;
margin: 0 30rpx 30rpx 30rpx;
border-radius: 8rpx;
}
.add-btn-icon {
margin-right: 8rpx;
}
/* ================ 弹窗样式 ================ */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog {
background-color: #fff;
border-radius: 12rpx;
width: 85%;
max-width: 650rpx;
padding: 40rpx 50rpx;
box-sizing: border-box;
position: relative;
z-index: 1001;
}
.dialog-title {
font-size: 34rpx;
font-weight: 600;
text-align: center;
margin-bottom: 40rpx;
color: #333;
}
/* 输入框样式 */
.input-wrapper {
position: relative;
margin-bottom: 30rpx;
width: 100%;
}
.dialog-input {
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
padding: 22rpx 60rpx 22rpx 22rpx;
font-size: 30rpx;
width: 100%;
box-sizing: border-box;
outline: none;
background-color: #fff;
pointer-events: auto;
user-select: text;
-webkit-user-select: text;
caret-color: #e62318;
height: 88rpx;
line-height: normal;
}
.dialog-input:focus {
border-color: #e62318;
box-shadow: 0 0 0 2rpx rgba(230, 35, 24, 0.1);
}
.clear-icon {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
font-size: 28rpx;
color: #999;
width: 30rpx;
height: 30rpx;
text-align: center;
line-height: 30rpx;
border-radius: 50%;
background-color: #f5f5f5;
z-index: 1002;
cursor: pointer;
}
.clear-icon:active {
background-color: #e0e0e0;
}
/* 删除按钮 */
.delete-btn {
color: #e62318;
font-size: 28rpx;
text-align: center;
margin-bottom: 40rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 16rpx;
border-radius: 8rpx;
/* background-color: #fff5f5; */
cursor: pointer;
}
.delete-btn:active {
background-color: #ffeaea;
}
.trash-icon {
font-size: 24rpx;
}
/* 弹窗按钮 */
.dialog-buttons {
display: flex;
gap: 24rpx;
}
.cancel-btn {
flex: 1;
background-color: #f5f5f5;
color: #666;
text-align: center;
padding: 24rpx;
border-radius: 8rpx;
font-size: 30rpx;
cursor: pointer;
}
.cancel-btn:active {
background-color: #e8e8e8;
}
.confirm-btn {
flex: 1;
background-color: #e62318;
color: #fff;
text-align: center;
padding: 24rpx;
border-radius: 8rpx;
font-size: 30rpx;
cursor: pointer;
}
.confirm-btn:active {
background-color: #d11f15;
}
/* ================ 删除确认弹窗(在修改弹窗之上) ================ */
.delete-confirm-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000; /* 比修改弹窗更高的层级 */
}
.delete-confirm-dialog {
background-color: #fff;
border-radius: 16rpx;
width: 70%;
max-width: 560rpx;
padding: 40rpx;
box-sizing: border-box;
position: relative;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.delete-dialog-title {
font-size: 34rpx;
font-weight: 600;
text-align: center;
margin-bottom: 24rpx;
color: #333;
}
.delete-dialog-content {
font-size: 30rpx;
color: #666;
text-align: center;
margin-bottom: 40rpx;
line-height: 1.4;
}
.delete-dialog-buttons {
display: flex;
gap: 24rpx;
}
.delete-cancel-btn {
flex: 1;
background-color: #f5f5f5;
color: #333;
text-align: center;
padding: 24rpx;
border-radius: 8rpx;
font-size: 30rpx;
cursor: pointer;
}
.delete-cancel-btn:active {
background-color: #e8e8e8;
}
.delete-confirm-btn {
flex: 1;
background-color: #e62318;
color: #fff;
text-align: center;
padding: 24rpx;
border-radius: 8rpx;
font-size: 30rpx;
cursor: pointer;
}
.delete-confirm-btn:active {
background-color: #d11f15;
}
</style>

View File

@ -0,0 +1,380 @@
<template>
<view class="container">
<!-- 顶部导航栏 -->
<view class="navbar">
<view class="nav-back" @tap="goBack">
<uni-icons type="back" size="18" color="#fff"></uni-icons>
</view>
<view class="nav-title">批量删除</view>
<view class="nav-more">
<uni-icons type="more-filled" size="18" color="#fff"></uni-icons>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input">
<uni-icons type="search" size="18" color="#999"></uni-icons>
<input
type="text"
placeholder="搜索商品名称、条码"
placeholder-class="placeholder"
v-model="searchText"
@confirm="onSearch"
/>
<uni-icons type="scan" size="18" color="#999"></uni-icons>
</view>
<view class="filter-btn" @tap="showFilter = !showFilter">
<uni-icons type="filter" size="18" color="#999"></uni-icons>
<text>筛选</text>
</view>
</view>
<!-- 商品列表 -->
<scroll-view class="goods-list" scroll-y="true">
<!-- 空列表提示 -->
<view v-if="goodsList.length === 0" class="empty-tip">
<text>暂无商品数据</text>
</view>
<!-- 商品项 -->
<view
class="goods-item"
v-for="item in goodsList"
:key="item.id"
>
<view class="checkbox-wrap" @tap="toggleSelect(item.id)">
<view
class="custom-checkbox"
:class="{ checked: selectedIds.includes(String(item.id)) }"
>
<text v-if="selectedIds.includes(String(item.id))" class="check-icon"></text>
</view>
</view>
<view class="goods-info">
<image
:src="item.mainImage ? 'http://193.112.94.36:8081' + item.mainImage : '/static/default-goods.png'"
class="goods-img"
mode="aspectFill"
/>
<view class="goods-details">
<text class="goods-name">{{ item.productName || '未命名商品' }}</text>
<text class="goods-barcode">{{ item.productBarCode || '无条码' }}</text>
<text class="goods-price">{{ item.storePrice ? Number(item.storePrice).toFixed(2) : '0.00' }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar" v-if="goodsList.length > 0">
<view class="select-all" @tap="toggleSelectAll">
<view class="custom-checkbox" :class="{ checked: isAllSelected }">
<text v-if="isAllSelected" class="check-icon"></text>
</view>
<text>全选 (已选{{ selectedIds.length }})</text>
</view>
<button
class="delete-btn"
:class="{ disabled: selectedIds.length === 0 }"
:disabled="selectedIds.length === 0"
@tap="onDeleteSelected"
>
删除商品
</button>
</view>
</view>
</template>
<script>
import { getProductList as fetchProductList, batchDeleteProduct } from '@/api/product'
import { getToken, getStoreId } from '@/utils/auth'
export default {
data() {
return {
searchText: '',
showFilter: false,
storeId: null,
goodsList: [],
selectedIds: []
}
},
computed: {
allCheckboxValues() {
return this.goodsList.map(item => ({
text: '',
value: String(item.id)
}));
},
isAllSelected() {
return this.goodsList.length > 0 && this.selectedIds.length === this.goodsList.length;
}
},
onLoad() {
const storeId = getStoreId();
if (storeId) {
this.storeId = storeId;
this.getProductList();
} else {
uni.showToast({ title: '请先选择门店', icon: 'none', duration: 1500 });
setTimeout(() => uni.navigateBack(), 1500);
}
},
methods: {
goBack() {
uni.navigateBack();
},
//
async getProductList(searchParams = {}) {
try {
if (this.storeId) {
searchParams.storeId = this.storeId;
}
const res = await fetchProductList(searchParams);
if (res && res.code === 200 && Array.isArray(res.data)) {
this.goodsList = res.data;
this.selectedIds = [];
} else {
uni.showToast({ title: res?.msg || '获取商品失败', icon: 'none' });
}
} catch (error) {
console.error('获取商品异常:', error);
uni.showToast({ title: '网络错误', icon: 'none' });
}
},
//
onSearch() {
const keyword = this.searchText.trim();
if (!keyword) {
this.getProductList();
return;
}
const searchParams = /^\d+$/.test(keyword)
? { productBarCode: keyword }
: { productName: keyword };
this.getProductList(searchParams);
},
// /
toggleSelect(id) {
const idStr = String(id);
const index = this.selectedIds.indexOf(idStr);
if (index > -1) {
this.selectedIds.splice(index, 1);
} else {
this.selectedIds.push(idStr);
}
},
// /
toggleSelectAll() {
if (this.isAllSelected) {
this.selectedIds = [];
} else {
this.selectedIds = this.goodsList.map(item => String(item.id));
}
},
//
async onDeleteSelected() {
if (this.selectedIds.length === 0) return;
uni.showModal({
title: '确认删除',
content: `确定删除选中的${this.selectedIds.length}个商品?删除后不可恢复!`,
success: async (res) => {
if (res.confirm) {
try {
const idsToDelete = this.selectedIds.map(id => Number(id));
const result = await batchDeleteProduct(idsToDelete);
if (result && result.code === 200) {
uni.showToast({ title: '删除成功', icon: 'success' });
this.getProductList();
} else {
uni.showToast({ title: result?.msg || '删除失败', icon: 'none' });
}
} catch (error) {
console.error('删除异常:', error);
uni.showToast({ title: '网络错误', icon: 'none' });
}
}
}
});
}
}
}
</script>
<style scoped>
/* 全局容器 */
.container {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 80rpx;
}
/* 导航栏 */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background-color: #e63946;
color: #fff;
}
.nav-title { font-size: 18px; font-weight: 600; }
/* 搜索栏 */
.search-bar {
display: flex;
align-items: center;
padding: 12px 16px;
background-color: #fff;
}
.search-input {
flex: 1;
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 8px;
padding: 10px 12px;
margin-right: 12px;
}
.search-input input {
flex: 1;
margin-left: 8px;
font-size: 14px;
border: none;
outline: none;
}
.placeholder { color: #999; }
.filter-btn {
display: flex;
align-items: center;
font-size: 14px;
color: #666;
}
.filter-btn text { margin-left: 4px; }
/* 商品列表 */
.goods-list {
height: calc(100vh - 200px);
padding: 16px;
}
.empty-tip {
text-align: center;
padding: 40px 0;
color: #999;
font-size: 14px;
}
.goods-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* 复选框容器 */
.checkbox-wrap {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
/* 自定义复选框样式 */
.custom-checkbox {
width: 28rpx;
height: 28rpx;
border-radius: 4rpx;
border: 2rpx solid #ddd;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.custom-checkbox.checked {
background-color: #e63946;
border-color: #e63946;
}
.check-icon {
color: #fff;
font-size: 18rpx;
font-weight: bold;
}
/* 商品信息 */
.goods-info {
display: flex;
align-items: center;
flex: 1;
}
.goods-img {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
margin-right: 12px;
}
.goods-details { flex: 1; }
.goods-name {
display: block;
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-barcode {
font-size: 12px;
color: #999;
display: block;
margin-bottom: 4px;
}
.goods-price {
font-size: 12px;
color: #e63946;
font-weight: 600;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background-color: #fff;
border-top: 1px solid #eee;
z-index: 99;
}
.select-all {
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.select-all text { margin-left: 6px; }
.delete-btn {
padding: 10px 24px;
border-radius: 8px;
font-size: 16px;
background-color: #e63946;
color: #fff;
border: none;
}
.delete-btn.disabled {
background-color: #ccc;
color: #999;
cursor: not-allowed;
}
</style>

View File

@ -104,8 +104,8 @@
<!-- 底部操作按钮 --> <!-- 底部操作按钮 -->
<view class="bottom-actions"> <view class="bottom-actions">
<view class="action-btn more-btn" @tap="showMoreActions(item)"> <view class="action-btn more-btn" @tap="showMoreActions()">
<text>更多</text> <text>更多</text>
</view> </view>
<button class="action-button primary" @tap="addNewGoods"> <button class="action-button primary" @tap="addNewGoods">
添加商品/库存 添加商品/库存
@ -118,17 +118,29 @@
<!-- 更多操作弹窗 --> <!-- 更多操作弹窗 -->
<uni-popup ref="popup" type="bottom"> <uni-popup ref="popup" type="bottom">
<view class="popup-content"> <view class="popup-content">
<view class="popup-item" @tap="editGoods(selectedGoods)"> <view class="popup-item" @tap="goToInventoryCount">
<uni-icons type="compose" size="20" color="#333"></uni-icons> <uni-icons type="scan" size="20" color="#333"></uni-icons>
<text>编辑商品</text> <text>盘点库存</text>
</view> </view>
<view class="popup-item" @tap="adjustStock(selectedGoods)"> <view class="popup-item" @tap="goToOutbound">
<uni-icons type="plus" size="20" color="#333"></uni-icons> <uni-icons type="arrowright" size="20" color="#333"></uni-icons>
<text>调整库存</text> <text>出库</text>
</view> </view>
<view class="popup-item" @tap="deleteGoods(selectedGoods)"> <view class="popup-item" @tap="batchDeleteGoods">
<uni-icons type="trash" size="20" color="#ff4444"></uni-icons> <uni-icons type="trash" size="20" color="#333"></uni-icons>
<text style="color: #ff4444;">删除商品</text> <text>批量删除商品</text>
</view>
<view class="popup-item" @tap="goToCategoryManagement">
<uni-icons type="apps" size="20" color="#333"></uni-icons>
<text>分类管理</text>
</view>
<view class="popup-item" @tap="goToBrandManagement">
<uni-icons type="navigate" size="20" color="#333"></uni-icons>
<text>品牌管理</text>
</view>
<view class="popup-item" @tap="goToBarcodeSettings">
<uni-icons type="settings" size="20" color="#333"></uni-icons>
<text>条码规则设置</text>
</view> </view>
<view class="popup-item cancel" @tap="$refs.popup.close()"> <view class="popup-item cancel" @tap="$refs.popup.close()">
<text>取消</text> <text>取消</text>
@ -262,8 +274,7 @@
}); });
}, },
showMoreActions(goods) { showMoreActions() {
this.selectedGoods = goods;
this.$refs.popup.open(); this.$refs.popup.open();
}, },
@ -328,8 +339,8 @@
addNewGoods() { addNewGoods() {
uni.navigateTo({ uni.navigateTo({
url: '/pages/enter/enter' // url: '/pages/enter/enter'
// url: '/pages/addProduct/addProduct' url: '/pages/addProduct/addProduct'
}); });
}, },
@ -386,6 +397,48 @@
} }
} }
}); });
},
//
goToInventoryCount() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/inventoryCount/inventoryCount'
});
},
//
goToOutbound() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/outbound/outbound'
});
},
//
batchDeleteGoods() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/batchDeleteProduct/batchDeleteProduct'
});
},
//
goToCategoryManagement() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/categoryManagement/categoryManagement'
});
},
//
goToBrandManagement() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/addBrand/addBrand'
});
},
//
goToBarcodeSettings() {
this.$refs.popup.close();
uni.navigateTo({
url: '/pages/barcodeSettings/barcodeSettings'
});
} }
} }
} }

View File

@ -35,6 +35,7 @@ const request = config => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const requestUrl = config.baseUrl ? config.baseUrl + config.url : baseUrl + config.url const requestUrl = config.baseUrl ? config.baseUrl + config.url : baseUrl + config.url
console.log('最终请求URL:', requestUrl) console.log('最终请求URL:', requestUrl)
console.log('请求配置:', config) console.log('请求配置:', config)