fix:修改视图样式
Build and Deploy Vue3 / build (push) Successful in 3m43s
Build and Deploy Vue3 / deploy (push) Successful in 1m24s

This commit is contained in:
2026-01-29 18:13:25 +08:00
parent 043be60f4f
commit 793a96a44f
2 changed files with 551 additions and 149 deletions
+280 -138
View File
@@ -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;