fix:修改视图样式
This commit is contained in:
+277
-135
@@ -9,11 +9,19 @@
|
|||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<div class="filter-content">
|
<div class="filter-content">
|
||||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||||
|
<el-form-item label="分组标签">
|
||||||
|
<el-select v-model="queryParams.tag" placeholder="全部标签" clearable style="width: 140px" @change="fetchGroupList">
|
||||||
|
<el-option
|
||||||
|
v-for="tag in allTagOptions"
|
||||||
|
:key="tag.id"
|
||||||
|
:label="tag.name"
|
||||||
|
:value="tag.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="层级筛选">
|
<el-form-item label="层级筛选">
|
||||||
<el-select v-model="queryParams.level" placeholder="全部层级" clearable style="width: 120px" @change="fetchGroupList">
|
<el-select v-model="queryParams.level" placeholder="全部层级" clearable style="width: 120px" @change="fetchGroupList">
|
||||||
<el-option label="一级" :value="1" />
|
<el-option v-for="n in 10" :key="n" :label="`${n}级`" :value="n" />
|
||||||
<el-option label="二级" :value="2" />
|
|
||||||
<el-option label="三级" :value="3" />
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态筛选">
|
<el-form-item label="状态筛选">
|
||||||
@@ -39,20 +47,19 @@
|
|||||||
<el-button type="success" @click="fetchGroupList">
|
<el-button type="success" @click="fetchGroupList">
|
||||||
<el-icon><Refresh /></el-icon>刷新
|
<el-icon><Refresh /></el-icon>刷新
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :type="viewMode === 'tree' ? 'primary' : 'default'" @click="viewMode = 'tree'">
|
<div class="view-switch">
|
||||||
树形视图
|
<el-radio-group v-model="viewMode" size="default">
|
||||||
</el-button>
|
<el-radio-button value="tree">
|
||||||
<el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
|
<el-icon><Grid /></el-icon>
|
||||||
列表视图
|
<span>树形视图</span>
|
||||||
</el-button>
|
</el-radio-button>
|
||||||
<template v-if="viewMode === 'tree'">
|
<el-radio-button value="list">
|
||||||
<el-button type="info" plain @click="expandAll">
|
<el-icon><List /></el-icon>
|
||||||
<el-icon><ArrowDown /></el-icon>全部展开
|
<span>列表视图</span>
|
||||||
</el-button>
|
</el-radio-button>
|
||||||
<el-button type="info" plain @click="collapseAll">
|
</el-radio-group>
|
||||||
<el-icon><ArrowUp /></el-icon>全部收起
|
</div>
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,14 +82,24 @@
|
|||||||
<el-table
|
<el-table
|
||||||
v-else-if="viewMode === 'tree'"
|
v-else-if="viewMode === 'tree'"
|
||||||
ref="treeTableRef"
|
ref="treeTableRef"
|
||||||
:data="flattenedTreeData"
|
:data="treeDisplayData"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||||
>
|
>
|
||||||
<el-table-column prop="name" label="分组名称" min-width="250">
|
<el-table-column prop="name" label="分组名称" min-width="280">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="group-name-cell" :style="{ paddingLeft: (row.level - 1) * 24 + 'px' }">
|
<div class="group-name-cell" :style="{ paddingLeft: (row.level - 1) * 24 + 'px' }">
|
||||||
|
<!-- 展开/收起按钮 -->
|
||||||
|
<span
|
||||||
|
v-if="row.existSub"
|
||||||
|
class="expand-icon"
|
||||||
|
@click="toggleExpand(row)"
|
||||||
|
>
|
||||||
|
<el-icon v-if="row._loading"><Loading /></el-icon>
|
||||||
|
<el-icon v-else :class="{ 'is-expanded': row._expanded }"><ArrowRight /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span v-else class="expand-placeholder"></span>
|
||||||
<el-avatar v-if="row.cover" :size="32" :src="row.cover" />
|
<el-avatar v-if="row.cover" :size="32" :src="row.cover" />
|
||||||
<el-avatar v-else :size="32" :style="{ background: getLevelColor(row.level) }">
|
<el-avatar v-else :size="32" :style="{ background: getLevelColor(row.level) }">
|
||||||
<el-icon><Folder /></el-icon>
|
<el-icon><Folder /></el-icon>
|
||||||
@@ -92,15 +109,21 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
<el-table-column label="层级" width="100">
|
<el-table-column label="标签" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.tag" size="small">{{ row.tag.name }}</el-tag>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="层级" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getLevelType(row.level)" size="small">
|
<el-tag :type="getLevelType(row.level)" size="small">
|
||||||
{{ getLevelText(row.level) }}
|
{{ getLevelText(row.level) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
|
<el-table-column prop="note" label="备注" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column label="状态" width="100">
|
<el-table-column label="状态" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="row.disable"
|
v-model="row.disable"
|
||||||
@@ -110,20 +133,12 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="320" fixed="right">
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button type="success" link @click="handleAdd(row)" v-if="row.level < 3">添加子级</el-button>
|
<el-button type="success" link @click="handleAdd(row)">添加子级</el-button>
|
||||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button type="danger" link @click="handleDelete(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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -300,11 +315,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="分组层级" prop="level">
|
<el-form-item label="分组层级" prop="level">
|
||||||
<el-select v-model="groupForm.level" placeholder="请选择层级" style="width: 100%" disabled>
|
<el-input :model-value="`${groupForm.level}级`" disabled style="width: 100%" />
|
||||||
<el-option label="一级" :value="1" />
|
|
||||||
<el-option label="二级" :value="2" />
|
|
||||||
<el-option label="三级" :value="3" />
|
|
||||||
</el-select>
|
|
||||||
<div class="form-tip">层级根据父级分组自动计算</div>
|
<div class="form-tip">层级根据父级分组自动计算</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="分组封面" prop="cover_id">
|
<el-form-item label="分组封面" prop="cover_id">
|
||||||
@@ -483,7 +494,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Refresh, Search, Folder, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
|
import { Plus, Refresh, Search, Folder, ArrowDown, ArrowUp, ArrowRight, Loading, Grid, List } from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
getProductGroupList,
|
getProductGroupList,
|
||||||
createProductGroup,
|
createProductGroup,
|
||||||
@@ -507,7 +518,8 @@ const viewMode = ref('tree')
|
|||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
count: 100, // 列表视图分页
|
count: 100,
|
||||||
|
tag: undefined,
|
||||||
level: undefined,
|
level: undefined,
|
||||||
disable: undefined,
|
disable: undefined,
|
||||||
key: ''
|
key: ''
|
||||||
@@ -516,14 +528,13 @@ const queryParams = reactive({
|
|||||||
// 监听视图模式变化
|
// 监听视图模式变化
|
||||||
watch(viewMode, (newVal) => {
|
watch(viewMode, (newVal) => {
|
||||||
if (newVal === 'tree') {
|
if (newVal === 'tree') {
|
||||||
// 树形视图获取全部数据
|
// 树形视图默认加载一级
|
||||||
queryParams.count = 100
|
fetchGroupList()
|
||||||
} else {
|
} else {
|
||||||
// 列表视图使用分页
|
// 列表视图使用分页
|
||||||
queryParams.count = 100
|
|
||||||
queryParams.page = 1
|
queryParams.page = 1
|
||||||
}
|
|
||||||
fetchGroupList()
|
fetchGroupList()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 商品分组表单
|
// 商品分组表单
|
||||||
@@ -547,7 +558,8 @@ const groupRules = {
|
|||||||
// 状态数据
|
// 状态数据
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const groupList = ref([])
|
const groupList = ref([])
|
||||||
const allGroupList = ref([]) // 用于树形构建
|
const allGroupList = ref([]) // 用于存储所有已加载的分组
|
||||||
|
const treeDataMap = ref(new Map()) // 存储树形数据,key为parent_id
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
@@ -566,47 +578,100 @@ const groupFormRef = ref(null)
|
|||||||
|
|
||||||
// 树形表格展开控制
|
// 树形表格展开控制
|
||||||
const treeTableRef = ref(null)
|
const treeTableRef = ref(null)
|
||||||
const expandedRowIds = ref(new Set()) // 记录展开的行ID
|
|
||||||
|
|
||||||
// 切换单行展开状态
|
// 树形显示数据(扁平化用于表格显示)
|
||||||
const toggleRowExpand = (row) => {
|
const treeDisplayData = computed(() => {
|
||||||
const newSet = new Set(expandedRowIds.value)
|
const result = []
|
||||||
if (newSet.has(row.id)) {
|
const rootItems = treeDataMap.value.get(0) || []
|
||||||
newSet.delete(row.id)
|
|
||||||
} else {
|
|
||||||
newSet.add(row.id)
|
|
||||||
}
|
|
||||||
expandedRowIds.value = newSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断行是否展开
|
const flatten = (items, parentExpanded = true) => {
|
||||||
const isRowExpanded = (row) => {
|
if (!parentExpanded) return
|
||||||
return expandedRowIds.value.has(row.id)
|
items.forEach(item => {
|
||||||
}
|
result.push(item)
|
||||||
|
if (item._expanded && item._children && item._children.length > 0) {
|
||||||
// 全部展开
|
flatten(item._children, true)
|
||||||
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
|
flatten(rootItems)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换展开状态
|
||||||
|
const toggleExpand = async (row) => {
|
||||||
|
if (row._loading) return
|
||||||
|
|
||||||
|
if (row._expanded) {
|
||||||
|
// 收起
|
||||||
|
row._expanded = false
|
||||||
|
} else {
|
||||||
|
// 展开 - 如果还没加载子级,先加载
|
||||||
|
if (row.existSub && (!row._children || row._children.length === 0)) {
|
||||||
|
row._loading = true
|
||||||
|
try {
|
||||||
|
const childLevel = row.level + 1
|
||||||
|
const res = await getProductGroupList({
|
||||||
|
parent_id: row.id,
|
||||||
|
level: childLevel,
|
||||||
|
count: 100
|
||||||
|
})
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
const children = res.data.data.data || []
|
||||||
|
row._children = children.map(child => ({
|
||||||
|
...child,
|
||||||
|
_expanded: false,
|
||||||
|
_children: [],
|
||||||
|
_loading: false
|
||||||
|
}))
|
||||||
|
// 更新allGroupList
|
||||||
|
allGroupList.value = [...allGroupList.value, ...children]
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载子级失败:', error)
|
||||||
|
ElMessage.error('加载子级分组失败')
|
||||||
|
} finally {
|
||||||
|
row._loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row._expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全部展开(递归加载所有子级)
|
||||||
|
const expandAll = async () => {
|
||||||
|
const loadAndExpand = async (items) => {
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.existSub) {
|
||||||
|
if (!item._children || item._children.length === 0) {
|
||||||
|
await toggleExpand(item)
|
||||||
|
} else {
|
||||||
|
item._expanded = true
|
||||||
|
}
|
||||||
|
if (item._children && item._children.length > 0) {
|
||||||
|
await loadAndExpand(item._children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootItems = treeDataMap.value.get(0) || []
|
||||||
|
await loadAndExpand(rootItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全部收起
|
// 全部收起
|
||||||
const collapseAll = () => {
|
const collapseAll = () => {
|
||||||
expandedRowIds.value = new Set()
|
const collapse = (items) => {
|
||||||
|
items.forEach(item => {
|
||||||
|
item._expanded = false
|
||||||
|
if (item._children && item._children.length > 0) {
|
||||||
|
collapse(item._children)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化展开状态(默认全部展开)
|
const rootItems = treeDataMap.value.get(0) || []
|
||||||
const initExpandedState = () => {
|
collapse(rootItems)
|
||||||
expandAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 父级选择相关
|
// 父级选择相关
|
||||||
@@ -622,7 +687,7 @@ const selectedParentName = computed(() => {
|
|||||||
})
|
})
|
||||||
const parentOptions = computed(() => {
|
const parentOptions = computed(() => {
|
||||||
// 只能选择层级1或2的作为父级
|
// 只能选择层级1或2的作为父级
|
||||||
return allGroupList.value.filter(g => g.level < 3 && g.id !== groupForm.id)
|
return allGroupList.value.filter(g => g.id !== groupForm.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 标签选择相关
|
// 标签选择相关
|
||||||
@@ -728,64 +793,21 @@ watch(showTagSelector, (val) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 构建树形数据
|
|
||||||
const treeData = computed(() => {
|
|
||||||
const list = allGroupList.value
|
|
||||||
const map = new Map()
|
|
||||||
const roots = []
|
|
||||||
|
|
||||||
// 创建映射
|
|
||||||
list.forEach(item => {
|
|
||||||
map.set(item.id, { ...item, children: [] })
|
|
||||||
})
|
|
||||||
|
|
||||||
// 构建树
|
|
||||||
list.forEach(item => {
|
|
||||||
const node = map.get(item.id)
|
|
||||||
if (item.parentId && map.has(item.parentId)) {
|
|
||||||
map.get(item.parentId).children.push(node)
|
|
||||||
} else {
|
|
||||||
roots.push(node)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 getLevelText = (level) => {
|
||||||
const map = { 1: '一级', 2: '二级', 3: '三级' }
|
return `${level}级`
|
||||||
return map[level] || `${level}级`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取层级类型
|
// 获取层级类型
|
||||||
const getLevelType = (level) => {
|
const getLevelType = (level) => {
|
||||||
const map = { 1: 'primary', 2: 'success', 3: 'warning' }
|
const types = ['primary', 'success', 'warning', 'danger', 'info']
|
||||||
return map[level] || 'info'
|
return types[(level - 1) % types.length] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取层级颜色(用于头像背景)
|
// 获取层级颜色(用于头像背景)
|
||||||
const getLevelColor = (level) => {
|
const getLevelColor = (level) => {
|
||||||
const map = { 1: '#409eff', 2: '#67c23a', 3: '#e6a23c' }
|
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#9c27b0', '#00bcd4', '#ff9800']
|
||||||
return map[level] || '#909399'
|
return colors[(level - 1) % colors.length] || '#909399'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取父级名称
|
// 获取父级名称
|
||||||
@@ -799,6 +821,21 @@ const fetchGroupList = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const params = { ...queryParams }
|
const params = { ...queryParams }
|
||||||
|
|
||||||
|
// 判断是否有筛选条件(tag/key/disable/level)
|
||||||
|
const hasFilter = params.tag || params.key || params.disable !== undefined || params.level !== undefined
|
||||||
|
|
||||||
|
if (viewMode.value === 'tree' && !hasFilter) {
|
||||||
|
// 无筛选条件:只加载一级
|
||||||
|
params.level = 1
|
||||||
|
delete params.page
|
||||||
|
} else if (viewMode.value === 'tree' && hasFilter) {
|
||||||
|
// 有筛选条件时,获取匹配数据
|
||||||
|
delete params.page
|
||||||
|
// 如果指定了level筛选,保留它;否则获取全部层级
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.tag) delete params.tag
|
||||||
if (params.level === undefined) delete params.level
|
if (params.level === undefined) delete params.level
|
||||||
if (params.disable === undefined) delete params.disable
|
if (params.disable === undefined) delete params.disable
|
||||||
if (!params.key) delete params.key
|
if (!params.key) delete params.key
|
||||||
@@ -806,15 +843,30 @@ const fetchGroupList = async () => {
|
|||||||
const res = await getProductGroupList(params)
|
const res = await getProductGroupList(params)
|
||||||
if (res.data.code === 200) {
|
if (res.data.code === 200) {
|
||||||
const data = res.data.data.data || []
|
const data = res.data.data.data || []
|
||||||
allGroupList.value = data
|
|
||||||
groupList.value = data
|
|
||||||
total.value = res.data.data.all_count || data.length
|
|
||||||
|
|
||||||
// 初始化展开状态(默认全部展开)
|
if (viewMode.value === 'tree') {
|
||||||
// 使用nextTick确保treeData已更新
|
if (hasFilter) {
|
||||||
setTimeout(() => {
|
// 有筛选条件:构建多级树形结构
|
||||||
initExpandedState()
|
const treeResult = buildTreeFromFilteredData(data)
|
||||||
}, 0)
|
treeDataMap.value.set(0, treeResult)
|
||||||
|
} else {
|
||||||
|
// 无筛选条件:只显示一级数据
|
||||||
|
const rootItems = data.map(item => ({
|
||||||
|
...item,
|
||||||
|
_expanded: false,
|
||||||
|
_children: [],
|
||||||
|
_loading: false
|
||||||
|
}))
|
||||||
|
treeDataMap.value.set(0, rootItems)
|
||||||
|
}
|
||||||
|
allGroupList.value = data
|
||||||
|
} else {
|
||||||
|
// 列表视图
|
||||||
|
groupList.value = data
|
||||||
|
allGroupList.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = res.data.data.all_count || data.length
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('获取商品分组列表失败')
|
ElMessage.error('获取商品分组列表失败')
|
||||||
@@ -823,12 +875,53 @@ const fetchGroupList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从筛选后的数据构建树形结构
|
||||||
|
const buildTreeFromFilteredData = (data) => {
|
||||||
|
// 按 parentId 分组
|
||||||
|
const map = new Map()
|
||||||
|
const roots = []
|
||||||
|
|
||||||
|
// 先为所有节点创建带有状态的对象
|
||||||
|
const nodeMap = new Map()
|
||||||
|
data.forEach(item => {
|
||||||
|
nodeMap.set(item.id, {
|
||||||
|
...item,
|
||||||
|
_expanded: true, // 筛选后默认展开
|
||||||
|
_children: [],
|
||||||
|
_loading: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构建父子关系
|
||||||
|
data.forEach(item => {
|
||||||
|
const node = nodeMap.get(item.id)
|
||||||
|
const parentId = item.parentId || 0
|
||||||
|
|
||||||
|
if (parentId === 0 || !nodeMap.has(parentId)) {
|
||||||
|
// 如果是顶级或者父级不在筛选结果中,作为根节点
|
||||||
|
roots.push(node)
|
||||||
|
} else {
|
||||||
|
// 父级在筛选结果中,添加为子节点
|
||||||
|
const parent = nodeMap.get(parentId)
|
||||||
|
parent._children.push(node)
|
||||||
|
parent.existSub = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 按 level 排序根节点
|
||||||
|
roots.sort((a, b) => a.level - b.level)
|
||||||
|
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
// 重置查询
|
// 重置查询
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
|
queryParams.tag = undefined
|
||||||
queryParams.level = undefined
|
queryParams.level = undefined
|
||||||
queryParams.disable = undefined
|
queryParams.disable = undefined
|
||||||
queryParams.key = ''
|
queryParams.key = ''
|
||||||
queryParams.page = 1
|
queryParams.page = 1
|
||||||
|
treeDataMap.value.clear()
|
||||||
fetchGroupList()
|
fetchGroupList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1255,6 +1348,23 @@ onMounted(() => {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 视图切换样式 */
|
||||||
|
.view-switch {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-switch :deep(.el-radio-button__inner) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-switch :deep(.el-icon) {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-section {
|
.table-section {
|
||||||
@@ -1271,6 +1381,42 @@ onMounted(() => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 展开图标样式 */
|
||||||
|
.expand-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #909399;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon .el-icon {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon .el-icon.is-expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-placeholder {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -1297,10 +1443,6 @@ onMounted(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-tip {
|
.form-tip {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
|
|||||||
@@ -138,14 +138,29 @@
|
|||||||
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
|
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商品分组" prop="good_group_id">
|
<el-form-item label="商品分组" prop="good_group_id">
|
||||||
<el-select v-model="productForm.good_group_id" placeholder="请选择商品分组" style="width: 100%">
|
<div class="group-tree-selector">
|
||||||
<el-option
|
<el-input
|
||||||
v-for="item in groupOptions"
|
:model-value="selectedGroupName"
|
||||||
:key="item.id"
|
placeholder="请选择商品分组"
|
||||||
:label="item.name"
|
readonly
|
||||||
:value="item.id"
|
@click="showGroupSelector = true"
|
||||||
/>
|
>
|
||||||
</el-select>
|
<template #append>
|
||||||
|
<el-button @click="showGroupSelector = true">
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button
|
||||||
|
v-if="productForm.good_group_id"
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
@click="clearGroupSelect"
|
||||||
|
class="clear-btn"
|
||||||
|
>
|
||||||
|
清除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商品所属表" prop="table">
|
<el-form-item label="商品所属表" prop="table">
|
||||||
<el-input v-model="productForm.table" placeholder="请输入商品所属表" />
|
<el-input v-model="productForm.table" placeholder="请输入商品所属表" />
|
||||||
@@ -546,14 +561,68 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 商品分组选择器对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showGroupSelector"
|
||||||
|
title="选择商品分组"
|
||||||
|
width="700px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<div class="group-selector-content">
|
||||||
|
<el-table
|
||||||
|
:data="groupTreeDisplayData"
|
||||||
|
style="width: 100%"
|
||||||
|
row-key="id"
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="handleGroupSelect"
|
||||||
|
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||||
|
max-height="400"
|
||||||
|
>
|
||||||
|
<el-table-column prop="name" label="分组名称" min-width="250">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="group-name-cell" :style="{ paddingLeft: (row.level - 1) * 24 + 'px' }">
|
||||||
|
<span
|
||||||
|
v-if="row.existSub"
|
||||||
|
class="expand-icon"
|
||||||
|
@click.stop="toggleGroupExpand(row)"
|
||||||
|
>
|
||||||
|
<el-icon v-if="row._loading"><Loading /></el-icon>
|
||||||
|
<el-icon v-else :class="{ 'is-expanded': row._expanded }"><ArrowRight /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span v-else class="expand-placeholder"></span>
|
||||||
|
<span class="group-name">{{ row.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="标签" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.tag" size="small">{{ row.tag.name }}</el-tag>
|
||||||
|
<span v-else class="text-muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="层级" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getLevelType(row.level)" size="small">
|
||||||
|
{{ getLevelText(row.level) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showGroupSelector = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmGroupSelect">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||||
import { getFileDetail } from '@/api/admin/file'
|
import { getFileDetail } from '@/api/admin/file'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Delete, Search, Refresh, Picture } from '@element-plus/icons-vue'
|
import { Plus, Delete, Search, Refresh, Picture, ArrowRight, Loading } from '@element-plus/icons-vue'
|
||||||
import AvatarSelector from '@/components/admin/AvatarSelector.vue'
|
import AvatarSelector from '@/components/admin/AvatarSelector.vue'
|
||||||
import { getProductList, createProduct, updateProduct, deleteProduct, getProductGroupList,
|
import { getProductList, createProduct, updateProduct, deleteProduct, getProductGroupList,
|
||||||
getProductTagList,
|
getProductTagList,
|
||||||
@@ -625,6 +694,124 @@ const productList = ref([])
|
|||||||
const groupOptions = ref([])
|
const groupOptions = ref([])
|
||||||
const tagOptions = ref([])
|
const tagOptions = ref([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 商品分组树形选择器
|
||||||
|
const showGroupSelector = ref(false)
|
||||||
|
const groupTreeData = ref([]) // 存储树形数据
|
||||||
|
const selectedGroup = ref(null)
|
||||||
|
|
||||||
|
// 计算选中的分组名称
|
||||||
|
const selectedGroupName = computed(() => {
|
||||||
|
if (productForm.good_group_id) {
|
||||||
|
// 递归查找分组名称
|
||||||
|
const findGroup = (items) => {
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.id === productForm.good_group_id) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
if (item._children && item._children.length > 0) {
|
||||||
|
const found = findGroup(item._children)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const group = findGroup(groupTreeData.value)
|
||||||
|
if (group) {
|
||||||
|
return `${group.name} (ID: ${group.id})`
|
||||||
|
}
|
||||||
|
// 从groupOptions中查找
|
||||||
|
const groupFromOptions = groupOptions.value.find(g => g.id === productForm.good_group_id)
|
||||||
|
if (groupFromOptions) {
|
||||||
|
return `${groupFromOptions.name} (ID: ${groupFromOptions.id})`
|
||||||
|
}
|
||||||
|
return `分组ID: ${productForm.good_group_id}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 树形数据扁平化显示
|
||||||
|
const groupTreeDisplayData = computed(() => {
|
||||||
|
const result = []
|
||||||
|
const flatten = (items) => {
|
||||||
|
items.forEach(item => {
|
||||||
|
result.push(item)
|
||||||
|
if (item._expanded && item._children && item._children.length > 0) {
|
||||||
|
flatten(item._children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
flatten(groupTreeData.value)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取层级类型
|
||||||
|
const getLevelType = (level) => {
|
||||||
|
const types = ['primary', 'success', 'warning', 'danger', 'info']
|
||||||
|
return types[(level - 1) % types.length] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取层级文本
|
||||||
|
const getLevelText = (level) => {
|
||||||
|
return `${level}级`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换分组展开状态
|
||||||
|
const toggleGroupExpand = async (row) => {
|
||||||
|
if (row._loading) return
|
||||||
|
|
||||||
|
if (row._expanded) {
|
||||||
|
row._expanded = false
|
||||||
|
} else {
|
||||||
|
// 如果还没加载子级
|
||||||
|
if (row.existSub && (!row._children || row._children.length === 0)) {
|
||||||
|
row._loading = true
|
||||||
|
try {
|
||||||
|
const childLevel = row.level + 1
|
||||||
|
const res = await getProductGroupList({
|
||||||
|
parent_id: row.id,
|
||||||
|
level: childLevel,
|
||||||
|
count: 100
|
||||||
|
})
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
const children = res.data.data.data || []
|
||||||
|
row._children = children.map(child => ({
|
||||||
|
...child,
|
||||||
|
_expanded: false,
|
||||||
|
_children: [],
|
||||||
|
_loading: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载子级失败:', error)
|
||||||
|
} finally {
|
||||||
|
row._loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row._expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择分组
|
||||||
|
const handleGroupSelect = (row) => {
|
||||||
|
selectedGroup.value = row
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择分组
|
||||||
|
const confirmGroupSelect = () => {
|
||||||
|
if (selectedGroup.value) {
|
||||||
|
productForm.good_group_id = selectedGroup.value.id
|
||||||
|
showGroupSelector.value = false
|
||||||
|
selectedGroup.value = null
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('请选择一个分组')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除分组选择
|
||||||
|
const clearGroupSelect = () => {
|
||||||
|
productForm.good_group_id = undefined
|
||||||
|
}
|
||||||
const selectedRows = ref([])
|
const selectedRows = ref([])
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
@@ -677,14 +864,26 @@ const fetchProductList = async () => {
|
|||||||
// 获取商品分组列表
|
// 获取商品分组列表
|
||||||
const fetchGroupList = async () => {
|
const fetchGroupList = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 获取全部分组用于下拉列表
|
||||||
const res = await getProductGroupList({ page: 1, count: 100 })
|
const res = await getProductGroupList({ page: 1, count: 100 })
|
||||||
if (res.data.code === 200) {
|
if (res.data.code === 200) {
|
||||||
groupOptions.value = res.data.data.data || []
|
groupOptions.value = res.data.data.data || []
|
||||||
console.log('商品分组列表:', groupOptions.value) // 调试日志
|
|
||||||
if (groupOptions.value.length === 0) {
|
if (groupOptions.value.length === 0) {
|
||||||
ElMessage.warning('暂无商品分组,请先创建商品分组')
|
ElMessage.warning('暂无商品分组,请先创建商品分组')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取一级分组用于树形选择器
|
||||||
|
const treeRes = await getProductGroupList({ level: 1, count: 100 })
|
||||||
|
if (treeRes.data.code === 200) {
|
||||||
|
const rootItems = treeRes.data.data.data || []
|
||||||
|
groupTreeData.value = rootItems.map(item => ({
|
||||||
|
...item,
|
||||||
|
_expanded: false,
|
||||||
|
_children: [],
|
||||||
|
_loading: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取分组列表失败:', error)
|
console.error('获取分组列表失败:', error)
|
||||||
ElMessage.error('获取分组列表失败')
|
ElMessage.error('获取分组列表失败')
|
||||||
@@ -1654,5 +1853,66 @@ const submitPlanForm = () => {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分组树形选择器样式 */
|
||||||
|
.group-tree-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tree-selector .el-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tree-selector .clear-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon .el-icon {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon .el-icon.is-expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-placeholder {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组选择器弹窗内容 */
|
||||||
|
.group-selector-content {
|
||||||
|
max-height: 450px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user