ry_app/package_a/addBrand/addBrand.vue

977 lines
24 KiB
Vue
Raw Normal View History

2026-01-22 14:50:26 +08:00
<template>
<view class="container">
<!-- 顶部导航栏 -->
<view class="navbar">
<view class="back-btn" @click="goBack">
2026-02-04 14:57:28 +08:00
<!-- <text class="back-icon"></text> -->
2026-01-22 14:50:26 +08:00
</view>
<view class="title">品牌管理</view>
</view>
<!-- 搜索框 -->
<view class="search-box">
2026-01-26 18:58:10 +08:00
<input
v-model="searchKeyword"
type="text"
placeholder="搜索品牌名称"
class="search-input"
@input="onSearchInput"
/>
2026-01-22 14:50:26 +08:00
</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,
2026-01-26 18:58:10 +08:00
// 搜索关键字
searchKeyword: ''
2026-01-22 14:50:26 +08:00
}
},
onLoad() {
// 获取storeId
this.storeId = getStoreId()
2026-01-26 18:58:10 +08:00
// 加载品牌列表,初始时不传入搜索关键字
2026-01-22 14:50:26 +08:00
this.loadBrandList()
},
methods: {
goBack() {
uni.navigateBack()
},
// 加载品牌列表
2026-01-26 18:58:10 +08:00
async loadBrandList(brandName = '') {
2026-01-22 14:50:26 +08:00
try {
2026-01-26 18:58:10 +08:00
// 使用新的getBrandTree接口传入storeId作为URL参数和brandName作为查询参数
const res = await getBrandTree(this.storeId, brandName)
2026-01-22 14:50:26 +08:00
if (res.code === 200 && res.data) {
2026-01-26 18:58:10 +08:00
// 格式化并设置品牌列表
2026-01-22 14:50:26 +08:00
this.brandList = this.formatBrandData(res.data)
}
} catch (error) {
console.error('加载品牌列表失败:', error)
uni.showToast({ title: '加载品牌列表失败', icon: 'none' })
}
},
// 格式化品牌数据为树状结构
formatBrandData(brandData) {
// 假设新接口返回的数据结构已经是树状的包含id、brandName、children等字段
// 但需要根据实际返回格式进行调整
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
})) : []
}))
},
2026-01-26 18:58:10 +08:00
// 搜索输入处理
onSearchInput() {
const keyword = this.searchKeyword.trim()
// 调用API进行搜索传入搜索关键字
this.loadBrandList(keyword)
},
2026-01-22 14:50:26 +08:00
// 展开/折叠一级品牌
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 {
// 添加一级品牌时parentId为0
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
// 调用打开编辑弹窗方法只传parentIndex不传subIndex表示修改一级品牌
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;
2026-01-26 18:58:10 +08:00
color: #333;
height: 72rpx;
2026-01-22 14:50:26 +08:00
}
/* 品牌树 */
.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>