feate:添加分组标签
This commit is contained in:
@@ -45,6 +45,14 @@
|
||||
<el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
|
||||
列表视图
|
||||
</el-button>
|
||||
<template v-if="viewMode === 'tree'">
|
||||
<el-button type="info" plain @click="expandAll">
|
||||
<el-icon><ArrowDown /></el-icon>全部展开
|
||||
</el-button>
|
||||
<el-button type="info" plain @click="collapseAll">
|
||||
<el-icon><ArrowUp /></el-icon>全部收起
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,18 +74,17 @@
|
||||
<!-- 树形表格视图 -->
|
||||
<el-table
|
||||
v-else-if="viewMode === 'tree'"
|
||||
:data="treeData"
|
||||
ref="treeTableRef"
|
||||
:data="flattenedTreeData"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
default-expand-all
|
||||
>
|
||||
<el-table-column prop="name" label="分组名称" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="group-name-cell">
|
||||
<div class="group-name-cell" :style="{ paddingLeft: (row.level - 1) * 24 + 'px' }">
|
||||
<el-avatar v-if="row.cover" :size="32" :src="row.cover" />
|
||||
<el-avatar v-else :size="32" style="background: #409eff">
|
||||
<el-avatar v-else :size="32" :style="{ background: getLevelColor(row.level) }">
|
||||
<el-icon><Folder /></el-icon>
|
||||
</el-avatar>
|
||||
<span class="group-name">{{ row.name }}</span>
|
||||
@@ -103,12 +110,20 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="success" link @click="handleAdd(row)" v-if="row.level < 3">添加子级</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
<el-button
|
||||
v-if="row.children && row.children.length > 0"
|
||||
type="info"
|
||||
link
|
||||
@click="toggleRowExpand(row)"
|
||||
>
|
||||
{{ isRowExpanded(row) ? '收起' : '展开' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -259,18 +274,19 @@
|
||||
<div class="recommend-user-selector">
|
||||
<el-input
|
||||
:model-value="selectedParentName"
|
||||
placeholder="无父级(顶级分组)"
|
||||
:placeholder="isAddingChild ? '已自动设置父级分组' : '无父级(顶级分组)'"
|
||||
readonly
|
||||
@click="showParentSelector = true"
|
||||
:disabled="isAddingChild"
|
||||
@click="!isAddingChild && (showParentSelector = true)"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="showParentSelector = true">
|
||||
<el-button @click="!isAddingChild && (showParentSelector = true)" :disabled="isAddingChild">
|
||||
<el-icon><Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button
|
||||
v-if="groupForm.parent_id"
|
||||
v-if="groupForm.parent_id && !isAddingChild"
|
||||
type="danger"
|
||||
link
|
||||
@click="clearParent"
|
||||
@@ -279,6 +295,9 @@
|
||||
清除
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="isAddingChild" class="form-tip">
|
||||
添加子级时自动继承父级分组,不可修改
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="分组层级" prop="level">
|
||||
<el-select v-model="groupForm.level" placeholder="请选择层级" style="width: 100%" disabled>
|
||||
@@ -464,7 +483,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, Folder } from '@element-plus/icons-vue'
|
||||
import { Plus, Refresh, Search, Folder, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getProductGroupList,
|
||||
createProductGroup,
|
||||
@@ -488,7 +507,7 @@ const viewMode = ref('tree')
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
count: 10, // 列表视图分页
|
||||
count: 100, // 列表视图分页
|
||||
level: undefined,
|
||||
disable: undefined,
|
||||
key: ''
|
||||
@@ -498,10 +517,10 @@ const queryParams = reactive({
|
||||
watch(viewMode, (newVal) => {
|
||||
if (newVal === 'tree') {
|
||||
// 树形视图获取全部数据
|
||||
queryParams.count = 1000
|
||||
queryParams.count = 100
|
||||
} else {
|
||||
// 列表视图使用分页
|
||||
queryParams.count = 10
|
||||
queryParams.count = 100
|
||||
queryParams.page = 1
|
||||
}
|
||||
fetchGroupList()
|
||||
@@ -538,8 +557,58 @@ const dialogTitle = computed(() => {
|
||||
}
|
||||
return '编辑商品分组'
|
||||
})
|
||||
|
||||
// 是否正在添加子级分组(用于禁用父级选择)
|
||||
const isAddingChild = computed(() => {
|
||||
return dialogType.value === 'add' && groupForm.parent_id
|
||||
})
|
||||
const groupFormRef = ref(null)
|
||||
|
||||
// 树形表格展开控制
|
||||
const treeTableRef = ref(null)
|
||||
const expandedRowIds = ref(new Set()) // 记录展开的行ID
|
||||
|
||||
// 切换单行展开状态
|
||||
const toggleRowExpand = (row) => {
|
||||
const newSet = new Set(expandedRowIds.value)
|
||||
if (newSet.has(row.id)) {
|
||||
newSet.delete(row.id)
|
||||
} else {
|
||||
newSet.add(row.id)
|
||||
}
|
||||
expandedRowIds.value = newSet
|
||||
}
|
||||
|
||||
// 判断行是否展开
|
||||
const isRowExpanded = (row) => {
|
||||
return expandedRowIds.value.has(row.id)
|
||||
}
|
||||
|
||||
// 全部展开
|
||||
const expandAll = () => {
|
||||
const newSet = new Set()
|
||||
const addAllWithChildren = (rows) => {
|
||||
rows.forEach(row => {
|
||||
if (row.children && row.children.length > 0) {
|
||||
newSet.add(row.id)
|
||||
addAllWithChildren(row.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
addAllWithChildren(treeData.value)
|
||||
expandedRowIds.value = newSet
|
||||
}
|
||||
|
||||
// 全部收起
|
||||
const collapseAll = () => {
|
||||
expandedRowIds.value = new Set()
|
||||
}
|
||||
|
||||
// 初始化展开状态(默认全部展开)
|
||||
const initExpandedState = () => {
|
||||
expandAll()
|
||||
}
|
||||
|
||||
// 父级选择相关
|
||||
const showParentSelector = ref(false)
|
||||
const showCoverSelector = ref(false)
|
||||
@@ -564,10 +633,21 @@ const tagOptionsForSelector = ref([])
|
||||
const selectedTag = ref(null)
|
||||
const allTagOptions = ref([]) // 存储所有标签用于显示名称
|
||||
|
||||
// 存储当前编辑行的标签信息(用于在allTagOptions未加载时显示)
|
||||
const currentEditTag = ref(null)
|
||||
|
||||
const selectedTagName = computed(() => {
|
||||
if (groupForm.tag_id) {
|
||||
// 优先从allTagOptions中查找
|
||||
const tag = allTagOptions.value.find(t => t.id === groupForm.tag_id)
|
||||
return tag ? `${tag.name} (ID: ${tag.id})` : `标签ID: ${groupForm.tag_id}`
|
||||
if (tag) {
|
||||
return `${tag.name} (ID: ${tag.id})`
|
||||
}
|
||||
// 如果没找到,使用当前编辑行的标签信息
|
||||
if (currentEditTag.value && currentEditTag.value.id === groupForm.tag_id) {
|
||||
return `${currentEditTag.value.name} (ID: ${currentEditTag.value.id})`
|
||||
}
|
||||
return `标签ID: ${groupForm.tag_id}`
|
||||
}
|
||||
return ''
|
||||
})
|
||||
@@ -607,7 +687,7 @@ const fetchTagOptionsForSelector = async () => {
|
||||
// 初始化获取所有标签
|
||||
const fetchAllTagOptions = async () => {
|
||||
try {
|
||||
const res = await getProductGroupTagList({ page: 1, count: 1000 })
|
||||
const res = await getProductGroupTagList({ page: 1, count: 100 })
|
||||
if (res.data.code === 200) {
|
||||
const data = res.data.data
|
||||
if (Array.isArray(data)) {
|
||||
@@ -672,6 +752,24 @@ const treeData = computed(() => {
|
||||
return roots
|
||||
})
|
||||
|
||||
// 将树形数据扁平化(根据展开状态)
|
||||
const flattenedTreeData = computed(() => {
|
||||
const result = []
|
||||
|
||||
const flatten = (nodes) => {
|
||||
nodes.forEach(node => {
|
||||
result.push(node)
|
||||
// 如果有子节点且当前节点是展开状态,则继续展开
|
||||
if (node.children && node.children.length > 0 && isRowExpanded(node)) {
|
||||
flatten(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
flatten(treeData.value)
|
||||
return result
|
||||
})
|
||||
|
||||
// 获取层级文本
|
||||
const getLevelText = (level) => {
|
||||
const map = { 1: '一级', 2: '二级', 3: '三级' }
|
||||
@@ -684,6 +782,12 @@ const getLevelType = (level) => {
|
||||
return map[level] || 'info'
|
||||
}
|
||||
|
||||
// 获取层级颜色(用于头像背景)
|
||||
const getLevelColor = (level) => {
|
||||
const map = { 1: '#409eff', 2: '#67c23a', 3: '#e6a23c' }
|
||||
return map[level] || '#909399'
|
||||
}
|
||||
|
||||
// 获取父级名称
|
||||
const getParentName = (parentId) => {
|
||||
const parent = allGroupList.value.find(g => g.id === parentId)
|
||||
@@ -704,7 +808,13 @@ const fetchGroupList = async () => {
|
||||
const data = res.data.data.data || []
|
||||
allGroupList.value = data
|
||||
groupList.value = data
|
||||
total.value = res.data.data.total || data.length
|
||||
total.value = res.data.data.all_count || data.length
|
||||
|
||||
// 初始化展开状态(默认全部展开)
|
||||
// 使用nextTick确保treeData已更新
|
||||
setTimeout(() => {
|
||||
initExpandedState()
|
||||
}, 0)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取商品分组列表失败')
|
||||
@@ -736,10 +846,16 @@ const handleCurrentChange = (page) => {
|
||||
// 新增商品分组
|
||||
const handleAdd = (parentRow) => {
|
||||
dialogType.value = 'add'
|
||||
dialogVisible.value = true
|
||||
|
||||
// 先重置表单,再设置值
|
||||
groupFormRef.value?.resetFields()
|
||||
|
||||
if (parentRow) {
|
||||
// 添加子级
|
||||
// 添加子级 - 自动获取父级分组信息
|
||||
// 继承父级标签
|
||||
const parentTagId = parentRow.tag?.id || parentRow.tagId
|
||||
currentEditTag.value = parentRow.tag || null // 保存父级标签信息用于显示
|
||||
|
||||
Object.assign(groupForm, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
@@ -748,10 +864,12 @@ const handleAdd = (parentRow) => {
|
||||
level: parentRow.level + 1,
|
||||
parent_id: parentRow.id,
|
||||
cover_id: undefined,
|
||||
tag_id: undefined
|
||||
tag_id: parentTagId || undefined
|
||||
})
|
||||
console.log('添加子级,父级信息:', parentRow.name, 'ID:', parentRow.id, 'Level:', parentRow.level, '标签:', parentRow.tag)
|
||||
} else {
|
||||
// 添加顶级
|
||||
currentEditTag.value = null
|
||||
Object.assign(groupForm, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
@@ -763,23 +881,36 @@ const handleAdd = (parentRow) => {
|
||||
tag_id: undefined
|
||||
})
|
||||
}
|
||||
groupFormRef.value?.resetFields()
|
||||
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑商品分组
|
||||
const handleEdit = (row) => {
|
||||
dialogType.value = 'edit'
|
||||
dialogVisible.value = true
|
||||
|
||||
// 保存当前行的标签信息(用于显示)
|
||||
currentEditTag.value = row.tag || null
|
||||
|
||||
// 处理数据映射
|
||||
// API返回: cover(URL), tag(对象{id, name}), parentId, coverId
|
||||
// 表单需要: cover_id, tag_id, parent_id
|
||||
// disable: true 表示禁用,false 表示启用
|
||||
Object.assign(groupForm, {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
note: row.note,
|
||||
disable: row.disable,
|
||||
note: row.note || '',
|
||||
disable: row.disable === true, // 确保是布尔值
|
||||
level: row.level || 1,
|
||||
parent_id: row.parentId,
|
||||
cover_id: row.coverId,
|
||||
tag_id: row.tagId
|
||||
parent_id: row.parentId || undefined,
|
||||
cover_id: row.coverId || undefined,
|
||||
tag_id: row.tag?.id || row.tagId || undefined
|
||||
})
|
||||
|
||||
dialogVisible.value = true
|
||||
|
||||
console.log('编辑分组数据:', row, '原始disable:', row.disable)
|
||||
console.log('表单数据:', groupForm, '表单disable:', groupForm.disable)
|
||||
}
|
||||
|
||||
// 清除父级
|
||||
@@ -865,18 +996,34 @@ const submitForm = () => {
|
||||
const submitData = {
|
||||
name: groupForm.name.trim(),
|
||||
note: groupForm.note || '',
|
||||
disable: groupForm.disable,
|
||||
level: String(groupForm.level || 1)
|
||||
disable: groupForm.disable
|
||||
}
|
||||
// 只有有值时才传递这些字段,避免外键约束错误
|
||||
|
||||
// 处理 level 和 parent_id
|
||||
// 如果有 parent_id,则设置 level 为父级+1,否则为顶级(level=1)
|
||||
if (groupForm.parent_id) {
|
||||
submitData.parent_id = groupForm.parent_id
|
||||
submitData.parent_id = Number(groupForm.parent_id)
|
||||
// 根据父级计算level
|
||||
const parentGroup = allGroupList.value.find(g => g.id === groupForm.parent_id)
|
||||
console.log('父级分组:', parentGroup)
|
||||
if (parentGroup && typeof parentGroup.level === 'number') {
|
||||
submitData.level = String(parentGroup.level + 1)
|
||||
} else {
|
||||
// 如果找不到父级或父级没有level,使用表单中的level
|
||||
submitData.level = String(groupForm.level || 2)
|
||||
}
|
||||
console.log('计算得到的level:', submitData.level, '父级level:', parentGroup?.level)
|
||||
} else {
|
||||
// 顶级分组,level=1,不传parent_id
|
||||
submitData.level = '1'
|
||||
}
|
||||
|
||||
// 只有有值时才传递这些字段,避免外键约束错误
|
||||
if (groupForm.cover_id) {
|
||||
submitData.cover_id = groupForm.cover_id
|
||||
submitData.cover_id = Number(groupForm.cover_id)
|
||||
}
|
||||
if (groupForm.tag_id) {
|
||||
submitData.tag_id = groupForm.tag_id
|
||||
submitData.tag_id = Number(groupForm.tag_id)
|
||||
}
|
||||
console.log('提交数据:', submitData)
|
||||
let res
|
||||
@@ -901,7 +1048,7 @@ const submitForm = () => {
|
||||
// ==================== 分组标签管理 ====================
|
||||
const tagQueryParams = reactive({
|
||||
page: 1,
|
||||
count: 10,
|
||||
count: 100,
|
||||
key: ''
|
||||
})
|
||||
|
||||
@@ -1126,8 +1273,13 @@ onMounted(() => {
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.action-buttons .el-button {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@@ -1177,6 +1329,11 @@ onMounted(() => {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* 隐藏el-table自带的树形展开图标 */
|
||||
:deep(.el-table__expand-icon) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
@@ -638,14 +638,15 @@ const coverPreviewUrl = ref('')
|
||||
const fetchProductList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getProductList(queryParams)
|
||||
// 添加 delete=false 参数,让后端只返回未删除的数据
|
||||
const params = { ...queryParams, delete: false }
|
||||
const res = await getProductList(params)
|
||||
if (res.data.code === 200) {
|
||||
const allData = res.data.data.data || []
|
||||
// 过滤掉已删除的数据
|
||||
productList.value = allData.filter(item => item.delete == false)
|
||||
// 计算未删除数据的总数(API返回的all_count包含已删除的,需要减去已删除的数量)
|
||||
const deletedCount = allData.filter(item => item.delete == true).length
|
||||
total.value = (res.data.data.data.length || 0) - deletedCount
|
||||
// 数据已经是未删除的,直接使用
|
||||
productList.value = allData
|
||||
// 使用后端返回的总数
|
||||
total.value = res.data.data.all_count || allData.length
|
||||
|
||||
// 异步获取所有商品的封面图片
|
||||
const imagePromises = productList.value.map(async (item) => {
|
||||
@@ -665,7 +666,6 @@ const fetchProductList = async () => {
|
||||
|
||||
// 等待所有图片加载完成
|
||||
await Promise.all(imagePromises)
|
||||
console.log('productList', productList.value)
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取商品列表失败')
|
||||
|
||||
Reference in New Issue
Block a user