javacodeadmin/ruoyi-ui/src/views/system/ServiceCate/index.vue

910 lines
29 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<!-- 一级分类管理区域 -->
<el-card shadow="hover" style="margin-bottom: 20px;">
<div slot="header" class="clearfix">
<span style="font-weight: bold; color: #409EFF;">一级分类管理</span>
<div style="float: right;">
<el-input
v-model="firstLevelSearch"
placeholder="请输入一级分类名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="width: 200px; margin-right: 10px;"
/>
<el-button type="primary" size="small" @click="handleAddFirstLevel">
<i class="el-icon-plus"></i> 添加一级分类
</el-button>
</div>
</div>
<!-- 一级分类网格布局 -->
<div class="category-grid">
<div
v-for="category in displayedFirstLevelList"
:key="category.id"
class="category-card"
:class="{ active: selectedFirstLevel && selectedFirstLevel.id === category.id }"
@click="selectFirstLevel(category)"
>
<!-- 卡片主体内容 -->
<div class="card-content">
<!-- 图标区域 -->
<div class="card-icon">
<image-preview
v-if="category.icon"
:src="category.icon"
:width="32"
:height="32"
style="border-radius: 4px;"
/>
<div v-else class="default-icon-small">
<i class="el-icon-folder" style="font-size: 20px; color: #409EFF;"></i>
</div>
</div>
<!-- 信息区域 -->
<div class="card-info">
<div class="card-title">{{ category.title }}</div>
<div class="card-meta">
<dict-tag :options="dict.type.service_sate_type" :value="category.type" size="mini"/>
<el-tag
size="mini"
:type="category.status === 1 ? 'success' : 'danger'"
effect="light"
>
{{ category.status === 1 ? '启用' : '禁用' }}
</el-tag>
</div>
<div class="card-stats">
<span class="stat-item">
<i class="el-icon-view"></i> {{ category.browse || 0 }}
</span>
<span class="stat-item" v-if="getSubCategoryCount(category.id) > 0">
<i class="el-icon-document"></i> {{ getSubCategoryCount(category.id) }}
</span>
<span class="stat-item">
<i class="el-icon-sort"></i> {{ category.sort }}
</span>
</div>
</div>
</div>
<!-- 操作区域 -->
<div class="card-actions">
<div class="action-buttons">
<el-tooltip content="编辑分类" placement="top">
<el-button
size="mini"
type="primary"
plain
@click.stop="handleEditFirstLevel(category)"
class="action-btn edit-btn"
>
<i class="el-icon-edit"></i>
</el-button>
</el-tooltip>
<el-tooltip content="删除分类" placement="top">
<el-button
size="mini"
type="danger"
plain
@click.stop="handleDeleteFirstLevel(category)"
class="action-btn delete-btn"
>
<i class="el-icon-delete"></i>
</el-button>
</el-tooltip>
</div>
</div>
</div>
</div>
<!-- 显示更多按钮和统计信息 -->
<div class="grid-footer">
<div class="grid-stats">
<span class="stats-text">
显示 {{ displayedFirstLevelList.length }} / {{ filteredFirstLevelList.length }} 个分类
</span>
</div>
<div class="grid-actions">
<el-button
v-if="shouldShowMoreButton"
type="text"
size="small"
@click="showAllFirstLevel = true"
>
<i class="el-icon-arrow-down"></i> 显示更多 ({{ filteredFirstLevelList.length - defaultShowCount }})
</el-button>
<el-button
v-if="showAllFirstLevel && !firstLevelSearch"
type="text"
size="small"
@click="showAllFirstLevel = false"
>
<i class="el-icon-arrow-up"></i> 收起
</el-button>
</div>
</div>
<!-- 空状态 -->
<div v-if="firstLevelList.length === 0" class="empty-state">
<i class="el-icon-folder-add" style="font-size: 48px; color: #ddd;"></i>
<p style="color: #999; margin-top: 20px;">暂无一级分类</p>
</div>
</el-card>
<el-card shadow="hover">
<div slot="header" class="clearfix">
<span style="font-weight: bold; color: #67C23A;">
二级分类管理
<span v-if="selectedFirstLevel" style="color: #909399; font-size: 12px;">
({{ selectedFirstLevel.title }} · 共{{ filteredSecondLevelList.length }}个)
</span>
</span>
<div style="float: right;">
<el-input
v-if="selectedFirstLevel"
v-model="secondLevelSearch"
placeholder="请输入二级分类名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="width: 200px; margin-right: 10px;"
/>
<el-button
v-if="selectedFirstLevel"
type="success"
size="small"
@click="handleAddSecondLevel"
>
<i class="el-icon-plus"></i> 添加二级分类
</el-button>
</div>
</div>
<!-- 未选择一级分类时的提示 -->
<div v-if="!selectedFirstLevel" class="empty-state">
<i class="el-icon-info" style="font-size: 48px; color: #ddd;"></i>
<p style="color: #999; margin-top: 20px;">请先选择上方的一级分类</p>
</div>
<!-- 二级分类表格 -->
<div v-else>
<el-table
:data="filteredSecondLevelList"
border
style="width: 100%"
:empty-text="'暂无二级分类'"
>
<el-table-column label="父级分类" width="100" align="center">
<template>
<el-tag size="mini" type="primary" effect="light">
{{ selectedFirstLevel.title }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="title" label="分类名称" width="150">
<template slot-scope="scope">
<i class="el-icon-document" style="margin-right: 8px; color: #67C23A;"></i>
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="60" align="center">
<template slot-scope="scope">
<el-tag size="mini" effect="plain">{{ scope.row.sort }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="60" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="browse" label="浏览量" width="70" align="center">
<template slot-scope="scope">
<span>{{ scope.row.browse || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="类型" width="80" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.service_sate_type" :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column prop="icon" label="图标" width="60" align="center">
<template slot-scope="scope">
<image-preview v-if="scope.row.icon" :src="scope.row.icon" :width="30" :height="30"/>
<span v-else style="color: #ccc;">无</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="handleEditSecondLevel(scope.row)">
编辑
</el-button>
<el-button size="mini" type="text" @click="handleDeleteSecondLevel(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="secondLevelTotal > 0"
:total="secondLevelTotal"
:page.sync="secondLevelPageNum"
:limit.sync="secondLevelPageSize"
@pagination="getSecondLevelList(selectedFirstLevel ? selectedFirstLevel.id : 0)"
style="margin-top: 20px;"
/>
</div>
</el-card>
<!-- 添加/编辑一级分类对话框 -->
<el-dialog :title="firstLevelTitle" :visible.sync="firstLevelDialogVisible" width="500px">
<el-form ref="firstLevelForm" :model="firstLevelForm" :rules="firstLevelRules" label-width="100px">
<el-form-item label="分类名称" prop="title">
<el-input v-model="firstLevelForm.title" placeholder="请输入一级分类名称" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="firstLevelForm.sort" :min="0" :max="9999" style="width: 100%;" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="firstLevelForm.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="浏览量" prop="browse">
<el-input-number v-model="firstLevelForm.browse" :min="0" style="width: 100%;" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-radio-group v-model="firstLevelForm.type">
<el-radio
v-for="dict in dict.type.service_sate_type"
:key="dict.value"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图标" prop="icon">
<image-upload v-model="firstLevelForm.icon"/>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="firstLevelDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitFirstLevel">确 定</el-button>
</div>
</el-dialog>
<!-- 添加/编辑二级分类对话框 -->
<el-dialog :title="secondLevelTitle" :visible.sync="secondLevelDialogVisible" width="500px">
<el-form ref="secondLevelForm" :model="secondLevelForm" :rules="secondLevelRules" label-width="100px">
<el-form-item label="父级分类">
<el-input
:value="selectedFirstLevel ? selectedFirstLevel.title : ''"
disabled
>
<template slot="prepend">
<i class="el-icon-folder" style="color: #409EFF;"></i>
</template>
<template slot="append">
<dict-tag
v-if="selectedFirstLevel && selectedFirstLevel.type !== null"
:options="dict.type.service_sate_type"
:value="selectedFirstLevel.type"
size="mini"
/>
</template>
</el-input>
</el-form-item>
<el-form-item label="分类名称" prop="title">
<el-input v-model="secondLevelForm.title" placeholder="请输入二级分类名称" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="secondLevelForm.sort" :min="0" :max="9999" style="width: 100%;" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="secondLevelForm.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="浏览量" prop="browse">
<el-input-number v-model="secondLevelForm.browse" :min="0" style="width: 100%;" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-input
:value="getTypeLabel(secondLevelForm.type)"
disabled
placeholder="跟随父级分类类型"
/>
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
<i class="el-icon-info"></i> 二级分类的类型自动跟随父级分类
</div>
</el-form-item>
<el-form-item label="图标" prop="icon">
<image-upload v-model="secondLevelForm.icon"/>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="secondLevelDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitSecondLevel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listServiceCate, getServiceCate, delServiceCate, addServiceCate, updateServiceCate, changefenleiStatus } from "@/api/system/ServiceCate"
import { parseTime } from "@/utils/index"
export default {
name: "ServiceCate",
dicts: ['service_sate_status', 'service_sate_type'],
data() {
return {
// 一级分类相关
firstLevelList: [],
selectedFirstLevel: null,
firstLevelSearch: "",
firstLevelDialogVisible: false,
firstLevelTitle: "",
firstLevelForm: {},
firstLevelRules: {
title: [{ required: true, message: "分类名称不能为空", trigger: "blur" }],
sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
status: [{ required: true, message: "状态不能为空", trigger: "change" }],
type: [{ required: true, message: "类型不能为空", trigger: "change" }],
},
// 二级分类相关
secondLevelList: [],
secondLevelSearch: "",
secondLevelDialogVisible: false,
secondLevelTitle: "",
secondLevelForm: {},
secondLevelRules: {
title: [{ required: true, message: "分类名称不能为空", trigger: "blur" }],
sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
status: [{ required: true, message: "状态不能为空", trigger: "change" }],
type: [{ required: true, message: "类型不能为空", trigger: "change" }],
},
// 二级分类分页相关
secondLevelTotal: 0,
secondLevelPageNum: 1,
secondLevelPageSize: 10,
// 所有分类数据(用于统计子分类数量)
allCategoryList: [],
// 显示更多控制
showAllFirstLevel: false,
defaultShowCount: 20
}
},
computed: {
// 过滤后的一级分类
filteredFirstLevelList() {
let filtered = this.firstLevelList
if (this.firstLevelSearch) {
filtered = this.firstLevelList.filter(item =>
item.title.toLowerCase().includes(this.firstLevelSearch.toLowerCase())
)
}
return filtered
},
// 显示的一级分类(考虑显示更多逻辑)
displayedFirstLevelList() {
if (this.showAllFirstLevel || this.firstLevelSearch) {
return this.filteredFirstLevelList
}
return this.filteredFirstLevelList.slice(0, this.defaultShowCount)
},
// 是否显示"显示更多"按钮
shouldShowMoreButton() {
return !this.showAllFirstLevel &&
!this.firstLevelSearch &&
this.filteredFirstLevelList.length > this.defaultShowCount
},
// 过滤后的二级分类
filteredSecondLevelList() {
if (!this.secondLevelSearch) return this.secondLevelList
return this.secondLevelList.filter(item =>
item.title.toLowerCase().includes(this.secondLevelSearch.toLowerCase())
)
}
},
created() {
this.getFirstLevelList()
},
methods: {
// 时间格式化方法
parseTime,
/** 获取一级分类列表 */
getFirstLevelList() {
// 获取一级分类(不分页)
const params = {
parentId: 0,
pageNum: 1,
pageSize: 1000 // 设置一个很大的数值,确保获取所有一级分类
}
listServiceCate(params).then(response => {
this.firstLevelList = response.rows || []
})
// 获取所有分类数据用于统计(不分页)
listServiceCate({
pageNum: 1,
pageSize: 10000 // 获取所有分类数据
}).then(response => {
this.allCategoryList = response.rows || []
})
},
/** 获取二级分类列表 */
getSecondLevelList(parentId) {
const params = {
parentId: parentId,
pageNum: this.secondLevelPageNum,
pageSize: this.secondLevelPageSize
}
listServiceCate(params).then(response => {
this.secondLevelList = response.rows || []
this.secondLevelTotal = response.total || 0
})
},
/** 选择一级分类 */
selectFirstLevel(category) {
this.selectedFirstLevel = category
// 重置分页参数
this.secondLevelPageNum = 1
this.secondLevelPageSize = 10
this.secondLevelSearch = ""
this.getSecondLevelList(category.id)
},
/** 获取子分类数量 */
getSubCategoryCount(parentId) {
return this.allCategoryList.filter(item => item.parentId === parentId).length
},
/** 状态修改 */
handleStatusChange(row) {
let text = row.status === 0 ? "启用" : "停用"
this.$modal.confirm('确认要"' + text + '""' + row.title + '"状态吗?').then(function() {
return changefenleiStatus(row.id, row.status)
}).then(() => {
this.$modal.msgSuccess(text + "成功")
}).catch(function() {
row.status = row.status === 0 ? 1 : 0
})
},
// ========== 一级分类操作 ==========
/** 添加一级分类 */
handleAddFirstLevel() {
this.resetFirstLevelForm()
this.firstLevelDialogVisible = true
this.firstLevelTitle = "添加一级分类"
},
/** 编辑一级分类 */
handleEditFirstLevel(row) {
this.resetFirstLevelForm()
getServiceCate(row.id).then(response => {
this.firstLevelForm = response.data
this.firstLevelDialogVisible = true
this.firstLevelTitle = "编辑一级分类"
})
},
/** 删除一级分类 */
handleDeleteFirstLevel(row) {
// 检查是否有二级分类
const params = { parentId: row.id }
listServiceCate(params).then(response => {
if (response.rows && response.rows.length > 0) {
this.$modal.msgError("该分类下还有二级分类,不能删除")
return
}
this.$modal.confirm('是否确认删除分类"' + row.title + '"').then(function() {
return delServiceCate(row.id)
}).then(() => {
this.getFirstLevelList()
this.$modal.msgSuccess("删除成功")
// 如果删除的是当前选中的分类,清空选中状态
if (this.selectedFirstLevel && this.selectedFirstLevel.id === row.id) {
this.selectedFirstLevel = null
this.secondLevelList = []
this.secondLevelTotal = 0
}
// 刷新统计数据
listServiceCate({
pageNum: 1,
pageSize: 10000
}).then(response => {
this.allCategoryList = response.rows || []
})
}).catch(() => {})
})
},
/** 提交一级分类 */
submitFirstLevel() {
this.$refs["firstLevelForm"].validate(valid => {
if (valid) {
this.firstLevelForm.parentId = 0 // 设置为一级分类
if (this.firstLevelForm.id != null) {
updateServiceCate(this.firstLevelForm).then(response => {
this.$modal.msgSuccess("修改成功")
this.firstLevelDialogVisible = false
this.getFirstLevelList()
})
} else {
addServiceCate(this.firstLevelForm).then(response => {
this.$modal.msgSuccess("新增成功")
this.firstLevelDialogVisible = false
this.getFirstLevelList()
})
}
}
})
},
/** 重置一级分类表单 */
resetFirstLevelForm() {
this.firstLevelForm = {
id: null,
title: null,
icon: null,
sort: 0,
status: 1,
browse: 0,
type: 1,
parentId: 0
}
this.resetForm("firstLevelForm")
},
// ========== 二级分类操作 ==========
/** 添加二级分类 */
handleAddSecondLevel() {
this.resetSecondLevelForm()
// 自动设置父级分类的类型
this.secondLevelForm.type = this.selectedFirstLevel ? this.selectedFirstLevel.type : 1
this.secondLevelDialogVisible = true
this.secondLevelTitle = "添加二级分类"
},
/** 编辑二级分类 */
handleEditSecondLevel(row) {
this.resetSecondLevelForm()
getServiceCate(row.id).then(response => {
this.secondLevelForm = response.data
// 确保类型跟随一级分类
this.secondLevelForm.type = this.selectedFirstLevel ? this.selectedFirstLevel.type : this.secondLevelForm.type
this.secondLevelDialogVisible = true
this.secondLevelTitle = "编辑二级分类"
})
},
/** 删除二级分类 */
handleDeleteSecondLevel(row) {
this.$modal.confirm('是否确认删除分类"' + row.title + '"').then(function() {
return delServiceCate(row.id)
}).then(() => {
this.getSecondLevelList(this.selectedFirstLevel.id)
this.$modal.msgSuccess("删除成功")
// 更新所有分类数据以刷新计数
listServiceCate({}).then(response => {
this.allCategoryList = response.rows || []
})
}).catch(() => {})
},
/** 提交二级分类 */
submitSecondLevel() {
this.$refs["secondLevelForm"].validate(valid => {
if (valid) {
this.secondLevelForm.parentId = this.selectedFirstLevel ? this.selectedFirstLevel.id : 0 // 设置父级ID
this.secondLevelForm.type = this.selectedFirstLevel ? this.selectedFirstLevel.type : this.secondLevelForm.type // 确保类型跟随一级分类
if (this.secondLevelForm.id != null) {
updateServiceCate(this.secondLevelForm).then(response => {
this.$modal.msgSuccess("修改成功")
this.secondLevelDialogVisible = false
this.getSecondLevelList(this.selectedFirstLevel.id)
// 更新所有分类数据以刷新计数
listServiceCate({}).then(response => {
this.allCategoryList = response.rows || []
})
})
} else {
addServiceCate(this.secondLevelForm).then(response => {
this.$modal.msgSuccess("新增成功")
this.secondLevelDialogVisible = false
this.getSecondLevelList(this.selectedFirstLevel.id)
// 更新所有分类数据以刷新计数
listServiceCate({}).then(response => {
this.allCategoryList = response.rows || []
})
})
}
}
})
},
/** 重置二级分类表单 */
resetSecondLevelForm() {
this.secondLevelForm = {
id: null,
title: null,
icon: null,
sort: 0,
status: 1,
browse: 0,
type: null, // 不预设类型,在添加时设置
parentId: null
}
this.resetForm("secondLevelForm")
},
/** 获取类型标签 */
getTypeLabel(value) {
const dict = this.dict.type.service_sate_type
const found = dict.find(item => parseInt(item.value) === value)
return found ? found.label : value
}
}
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.category-grid {
display: grid;
grid-template-columns: repeat(10, 1fr);
gap: 12px;
padding: 15px 0;
}
.category-card {
border: 1px solid #e8e8e8;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: #fff;
position: relative;
display: flex;
flex-direction: column;
min-height: 90px;
max-width: 120px;
margin: 0 auto;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
}
.category-card:hover {
border-color: #409EFF;
background-color: #fafbfc;
box-shadow: 0 8px 25px rgba(64, 158, 255, 0.12);
transform: translateY(-2px);
}
.category-card.active {
border-color: #409EFF;
background: linear-gradient(135deg, #ecf5ff 0%, #f0f9ff 100%);
box-shadow: 0 8px 25px rgba(64, 158, 255, 0.15);
transform: translateY(-2px);
}
.card-content {
padding: 12px 8px 8px 8px;
flex: 1;
display: flex;
flex-direction: column;
}
.card-icon {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6px;
}
.default-icon-small {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #f5f7fa 0%, #e8f4f8 100%);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
}
.card-info {
flex: 1;
text-align: center;
}
.card-title {
font-size: 12px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 4px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-meta {
display: flex;
justify-content: center;
align-items: center;
gap: 3px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.card-stats {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
font-size: 10px;
color: #909399;
}
.stat-item {
display: flex;
align-items: center;
gap: 2px;
}
.card-actions {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-top: 1px solid rgba(0, 0, 0, 0.06);
padding: 8px 6px;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(10px);
}
.category-card:hover .card-actions {
opacity: 1;
transform: translateY(0);
}
.category-card.active .card-actions {
opacity: 1;
transform: translateY(0);
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
border-top-color: rgba(64, 158, 255, 0.1);
}
.action-buttons {
display: flex;
gap: 6px;
align-items: center;
}
.action-btn {
width: 28px !important;
height: 28px !important;
padding: 0 !important;
font-size: 12px !important;
border-radius: 6px !important;
border-width: 1px !important;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08) !important;
}
.edit-btn {
background: linear-gradient(135deg, #e3f2fd 0%, #f0f9ff 100%) !important;
border-color: #90caf9 !important;
color: #1976d2 !important;
}
.edit-btn:hover {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%) !important;
border-color: #1565c0 !important;
color: #fff !important;
transform: translateY(-1px) scale(1.05) !important;
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.3) !important;
}
.delete-btn {
background: linear-gradient(135deg, #ffebee 0%, #fce4ec 100%) !important;
border-color: #f8bbd9 !important;
color: #c62828 !important;
}
.delete-btn:hover {
background: linear-gradient(135deg, #c62828 0%, #b71c1c 100%) !important;
border-color: #b71c1c !important;
color: #fff !important;
transform: translateY(-1px) scale(1.05) !important;
box-shadow: 0 4px 12px rgba(198, 40, 40, 0.3) !important;
}
.grid-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0 10px 0;
border-top: 1px solid rgba(0, 0, 0, 0.06);
margin-top: 15px;
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%);
border-radius: 8px;
margin-left: -10px;
margin-right: -10px;
padding-left: 20px;
padding-right: 20px;
}
.grid-stats {
flex: 1;
}
.stats-text {
font-size: 12px;
color: #6b7280;
font-weight: 500;
}
.grid-actions {
display: flex;
gap: 10px;
}
.empty-state {
text-align: center;
padding: 60px 0;
color: #909399;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
</style>