fix:修改视图样式
This commit is contained in:
+280
-138
@@ -9,11 +9,19 @@
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<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-select v-model="queryParams.level" placeholder="全部层级" clearable style="width: 120px" @change="fetchGroupList">
|
||||
<el-option label="一级" :value="1" />
|
||||
<el-option label="二级" :value="2" />
|
||||
<el-option label="三级" :value="3" />
|
||||
<el-option v-for="n in 10" :key="n" :label="`${n}级`" :value="n" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态筛选">
|
||||
@@ -39,20 +47,19 @@
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button :type="viewMode === 'tree' ? 'primary' : 'default'" @click="viewMode = 'tree'">
|
||||
树形视图
|
||||
</el-button>
|
||||
<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 class="view-switch">
|
||||
<el-radio-group v-model="viewMode" size="default">
|
||||
<el-radio-button value="tree">
|
||||
<el-icon><Grid /></el-icon>
|
||||
<span>树形视图</span>
|
||||
</el-radio-button>
|
||||
<el-radio-button value="list">
|
||||
<el-icon><List /></el-icon>
|
||||
<span>列表视图</span>
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,14 +82,24 @@
|
||||
<el-table
|
||||
v-else-if="viewMode === 'tree'"
|
||||
ref="treeTableRef"
|
||||
:data="flattenedTreeData"
|
||||
:data="treeDisplayData"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
: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 }">
|
||||
<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-else :size="32" :style="{ background: getLevelColor(row.level) }">
|
||||
<el-icon><Folder /></el-icon>
|
||||
@@ -92,15 +109,21 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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 }">
|
||||
<el-tag :type="getLevelType(row.level)" size="small">
|
||||
{{ getLevelText(row.level) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="100">
|
||||
<el-table-column prop="note" label="备注" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.disable"
|
||||
@@ -110,20 +133,12 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<el-table-column label="操作" width="200" 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="success" link @click="handleAdd(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
|
||||
v-if="row.children && row.children.length > 0"
|
||||
type="info"
|
||||
link
|
||||
@click="toggleRowExpand(row)"
|
||||
>
|
||||
{{ isRowExpanded(row) ? '收起' : '展开' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -300,11 +315,7 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="分组层级" prop="level">
|
||||
<el-select v-model="groupForm.level" placeholder="请选择层级" style="width: 100%" disabled>
|
||||
<el-option label="一级" :value="1" />
|
||||
<el-option label="二级" :value="2" />
|
||||
<el-option label="三级" :value="3" />
|
||||
</el-select>
|
||||
<el-input :model-value="`${groupForm.level}级`" disabled style="width: 100%" />
|
||||
<div class="form-tip">层级根据父级分组自动计算</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="分组封面" prop="cover_id">
|
||||
@@ -483,7 +494,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
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 {
|
||||
getProductGroupList,
|
||||
createProductGroup,
|
||||
@@ -507,7 +518,8 @@ const viewMode = ref('tree')
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
count: 100, // 列表视图分页
|
||||
count: 100,
|
||||
tag: undefined,
|
||||
level: undefined,
|
||||
disable: undefined,
|
||||
key: ''
|
||||
@@ -516,14 +528,13 @@ const queryParams = reactive({
|
||||
// 监听视图模式变化
|
||||
watch(viewMode, (newVal) => {
|
||||
if (newVal === 'tree') {
|
||||
// 树形视图获取全部数据
|
||||
queryParams.count = 100
|
||||
// 树形视图默认加载一级
|
||||
fetchGroupList()
|
||||
} else {
|
||||
// 列表视图使用分页
|
||||
queryParams.count = 100
|
||||
queryParams.page = 1
|
||||
fetchGroupList()
|
||||
}
|
||||
fetchGroupList()
|
||||
})
|
||||
|
||||
// 商品分组表单
|
||||
@@ -547,7 +558,8 @@ const groupRules = {
|
||||
// 状态数据
|
||||
const loading = ref(false)
|
||||
const groupList = ref([])
|
||||
const allGroupList = ref([]) // 用于树形构建
|
||||
const allGroupList = ref([]) // 用于存储所有已加载的分组
|
||||
const treeDataMap = ref(new Map()) // 存储树形数据,key为parent_id
|
||||
const total = ref(0)
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
@@ -566,47 +578,100 @@ 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)
|
||||
// 树形显示数据(扁平化用于表格显示)
|
||||
const treeDisplayData = computed(() => {
|
||||
const result = []
|
||||
const rootItems = treeDataMap.value.get(0) || []
|
||||
|
||||
const flatten = (items, parentExpanded = true) => {
|
||||
if (!parentExpanded) return
|
||||
items.forEach(item => {
|
||||
result.push(item)
|
||||
if (item._expanded && item._children && item._children.length > 0) {
|
||||
flatten(item._children, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
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 = () => {
|
||||
expandedRowIds.value = new Set()
|
||||
}
|
||||
|
||||
// 初始化展开状态(默认全部展开)
|
||||
const initExpandedState = () => {
|
||||
expandAll()
|
||||
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) || []
|
||||
collapse(rootItems)
|
||||
}
|
||||
|
||||
// 父级选择相关
|
||||
@@ -622,7 +687,7 @@ const selectedParentName = computed(() => {
|
||||
})
|
||||
const parentOptions = computed(() => {
|
||||
// 只能选择层级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 map = { 1: '一级', 2: '二级', 3: '三级' }
|
||||
return map[level] || `${level}级`
|
||||
return `${level}级`
|
||||
}
|
||||
|
||||
// 获取层级类型
|
||||
const getLevelType = (level) => {
|
||||
const map = { 1: 'primary', 2: 'success', 3: 'warning' }
|
||||
return map[level] || 'info'
|
||||
const types = ['primary', 'success', 'warning', 'danger', 'info']
|
||||
return types[(level - 1) % types.length] || 'info'
|
||||
}
|
||||
|
||||
// 获取层级颜色(用于头像背景)
|
||||
const getLevelColor = (level) => {
|
||||
const map = { 1: '#409eff', 2: '#67c23a', 3: '#e6a23c' }
|
||||
return map[level] || '#909399'
|
||||
const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#9c27b0', '#00bcd4', '#ff9800']
|
||||
return colors[(level - 1) % colors.length] || '#909399'
|
||||
}
|
||||
|
||||
// 获取父级名称
|
||||
@@ -799,6 +821,21 @@ const fetchGroupList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
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.disable === undefined) delete params.disable
|
||||
if (!params.key) delete params.key
|
||||
@@ -806,15 +843,30 @@ const fetchGroupList = async () => {
|
||||
const res = await getProductGroupList(params)
|
||||
if (res.data.code === 200) {
|
||||
const data = res.data.data.data || []
|
||||
allGroupList.value = data
|
||||
groupList.value = data
|
||||
total.value = res.data.data.all_count || data.length
|
||||
|
||||
// 初始化展开状态(默认全部展开)
|
||||
// 使用nextTick确保treeData已更新
|
||||
setTimeout(() => {
|
||||
initExpandedState()
|
||||
}, 0)
|
||||
if (viewMode.value === 'tree') {
|
||||
if (hasFilter) {
|
||||
// 有筛选条件:构建多级树形结构
|
||||
const treeResult = buildTreeFromFilteredData(data)
|
||||
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) {
|
||||
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 = () => {
|
||||
queryParams.tag = undefined
|
||||
queryParams.level = undefined
|
||||
queryParams.disable = undefined
|
||||
queryParams.key = ''
|
||||
queryParams.page = 1
|
||||
treeDataMap.value.clear()
|
||||
fetchGroupList()
|
||||
}
|
||||
|
||||
@@ -1255,6 +1348,23 @@ onMounted(() => {
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
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 {
|
||||
@@ -1271,6 +1381,42 @@ onMounted(() => {
|
||||
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 {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
@@ -1297,10 +1443,6 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
@@ -138,14 +138,29 @@
|
||||
<el-input v-model="productForm.name" placeholder="请输入商品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品分组" prop="good_group_id">
|
||||
<el-select v-model="productForm.good_group_id" placeholder="请选择商品分组" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in groupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="group-tree-selector">
|
||||
<el-input
|
||||
:model-value="selectedGroupName"
|
||||
placeholder="请选择商品分组"
|
||||
readonly
|
||||
@click="showGroupSelector = true"
|
||||
>
|
||||
<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 label="商品所属表" prop="table">
|
||||
<el-input v-model="productForm.table" placeholder="请输入商品所属表" />
|
||||
@@ -546,14 +561,68 @@
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { getFileDetail } from '@/api/admin/file'
|
||||
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 { getProductList, createProduct, updateProduct, deleteProduct, getProductGroupList,
|
||||
getProductTagList,
|
||||
@@ -625,6 +694,124 @@ const productList = ref([])
|
||||
const groupOptions = ref([])
|
||||
const tagOptions = ref([])
|
||||
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 dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
@@ -677,14 +864,26 @@ const fetchProductList = async () => {
|
||||
// 获取商品分组列表
|
||||
const fetchGroupList = async () => {
|
||||
try {
|
||||
// 获取全部分组用于下拉列表
|
||||
const res = await getProductGroupList({ page: 1, count: 100 })
|
||||
if (res.data.code === 200) {
|
||||
groupOptions.value = res.data.data.data || []
|
||||
console.log('商品分组列表:', groupOptions.value) // 调试日志
|
||||
if (groupOptions.value.length === 0) {
|
||||
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) {
|
||||
console.error('获取分组列表失败:', error)
|
||||
ElMessage.error('获取分组列表失败')
|
||||
@@ -1654,5 +1853,66 @@ const submitPlanForm = () => {
|
||||
flex-wrap: wrap;
|
||||
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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user