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;
+271 -11
View File
@@ -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>