Compare commits
3 Commits
2a91cbb193
...
777022632c
| Author | SHA1 | Date | |
|---|---|---|---|
| 777022632c | |||
| 5ea4f2cfe3 | |||
| f7c3be1d30 |
+322
-12
@@ -88,40 +88,350 @@ html, body {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
/* Element Plus样式优化 */
|
||||
/* Element Plus全局配色优化 */
|
||||
|
||||
/* 按钮扁平化 */
|
||||
.el-button {
|
||||
font-weight: 400;
|
||||
border-radius: 4px;
|
||||
border-radius: 0 !important;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 主按钮 - 深蓝灰色 */
|
||||
.el-button--primary {
|
||||
background-color: #2c3e50 !important;
|
||||
border-color: #2c3e50 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.el-button--primary:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
|
||||
.el-button--primary:active {
|
||||
background-color: #1a252f !important;
|
||||
border-color: #1a252f !important;
|
||||
}
|
||||
|
||||
/* 成功按钮 - 绿色系 */
|
||||
.el-button--success {
|
||||
background-color: #27ae60 !important;
|
||||
border-color: #27ae60 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.el-button--success:hover {
|
||||
background-color: #2ecc71 !important;
|
||||
border-color: #2ecc71 !important;
|
||||
}
|
||||
|
||||
.el-button--success:active {
|
||||
background-color: #229954 !important;
|
||||
border-color: #229954 !important;
|
||||
}
|
||||
|
||||
/* 危险按钮 - 红色系 */
|
||||
.el-button--danger {
|
||||
background-color: #e74c3c !important;
|
||||
border-color: #e74c3c !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.el-button--danger:hover {
|
||||
background-color: #ec7063 !important;
|
||||
border-color: #ec7063 !important;
|
||||
}
|
||||
|
||||
.el-button--danger:active {
|
||||
background-color: #c0392b !important;
|
||||
border-color: #c0392b !important;
|
||||
}
|
||||
|
||||
/* 默认按钮 */
|
||||
.el-button--default {
|
||||
background-color: #ffffff !important;
|
||||
border-color: #d5d9e0 !important;
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
.el-button--default:hover {
|
||||
background-color: #f5f7fa !important;
|
||||
border-color: #c0c4cc !important;
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
/* Link按钮 */
|
||||
.el-button.is-link {
|
||||
color: #3498db !important;
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-button.is-link:hover {
|
||||
color: #2980b9 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-button--primary.is-link {
|
||||
color: #3498db !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.el-button--primary.is-link:hover {
|
||||
color: #2980b9 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 输入框扁平化 */
|
||||
.el-input__wrapper {
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 0 0 1px #d5d9e0 inset !important;
|
||||
background-color: #ffffff !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.el-input__wrapper:hover {
|
||||
box-shadow: 0 0 0 1px #b8bcc5 inset !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
box-shadow: 0 0 0 1px #2c3e50 inset !important;
|
||||
}
|
||||
|
||||
/* 标签扁平化 */
|
||||
.el-tag {
|
||||
border-radius: 0 !important;
|
||||
border: none !important;
|
||||
font-weight: 500;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
/* 成功标签 */
|
||||
.el-tag--success {
|
||||
background-color: #d5f4e6 !important;
|
||||
color: #27ae60 !important;
|
||||
}
|
||||
|
||||
/* 危险标签 */
|
||||
.el-tag--danger {
|
||||
background-color: #fadbd8 !important;
|
||||
color: #e74c3c !important;
|
||||
}
|
||||
|
||||
/* 信息标签 */
|
||||
.el-tag--info {
|
||||
background-color: #ebf5fb !important;
|
||||
color: #3498db !important;
|
||||
}
|
||||
|
||||
/* 卡片扁平化 */
|
||||
.el-card {
|
||||
border-radius: 4px;
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid #e1e8ed !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* 表格扁平化 */
|
||||
.el-table {
|
||||
border-radius: 0 !important;
|
||||
border: none !important;
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
.el-table__header {
|
||||
background: #f8f9fa !important;
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed !important;
|
||||
color: #2c3e50 !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.el-table td {
|
||||
border-bottom: 1px solid #f0f2f5 !important;
|
||||
color: #34495e !important;
|
||||
}
|
||||
|
||||
.el-table tr:hover > td {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
/* 分页扁平化 */
|
||||
.el-pagination .el-pager li {
|
||||
border-radius: 0 !important;
|
||||
color: #606266 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-pagination .el-pager li.is-active {
|
||||
background-color: #2c3e50 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.el-pagination .el-pager li:hover {
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
.el-pagination button {
|
||||
border-radius: 0 !important;
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
.el-pagination button:hover {
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
.el-pagination .el-select .el-input__wrapper {
|
||||
box-shadow: 0 0 0 1px #d5d9e0 inset !important;
|
||||
}
|
||||
|
||||
.el-pagination .el-input__inner {
|
||||
color: #606266 !important;
|
||||
}
|
||||
|
||||
/* 下拉菜单扁平化 */
|
||||
.el-dropdown-menu {
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid #e1e8ed !important;
|
||||
background-color: #ffffff !important;
|
||||
box-shadow: 0 2px 8px rgba(44, 62, 80, 0.1) !important;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
color: #34495e !important;
|
||||
transition: all 0.2s ease;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item.is-divided {
|
||||
border-top: 1px solid #e1e8ed !important;
|
||||
}
|
||||
|
||||
/* 选择框扁平化 */
|
||||
.el-select .el-input__wrapper {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* 文本域扁平化 */
|
||||
.el-textarea__inner {
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 0 0 1px #d5d9e0 inset !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.el-textarea__inner:hover {
|
||||
box-shadow: 0 0 0 1px #b8bcc5 inset !important;
|
||||
}
|
||||
|
||||
.el-textarea__inner:focus {
|
||||
box-shadow: 0 0 0 1px #2c3e50 inset !important;
|
||||
}
|
||||
|
||||
/* 菜单扁平化 */
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
border-radius: 4px;
|
||||
/* 表单标签 */
|
||||
.el-form-item__label {
|
||||
color: #2c3e50 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Dialog 扁平化样式 */
|
||||
.el-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.el-overlay-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
border-radius: 8px;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
box-shadow: 0 4px 16px rgba(44, 62, 80, 0.15);
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-weight: 500;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background-color: #fafbfc;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 20px;
|
||||
right: 24px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.el-dialog__close {
|
||||
color: #7f8c8d;
|
||||
font-size: 18px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.el-dialog__close:hover {
|
||||
color: #2c3e50;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
color: #34495e;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 10px 20px 20px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
/* Dialog 内表单组件样式 */
|
||||
.el-dialog .el-input__wrapper {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.el-dialog .el-select .el-input__wrapper {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.el-dialog .el-textarea__inner {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.el-dialog .el-form-item__label {
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-dialog .el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 28 KiB |
@@ -3,15 +3,15 @@
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<div class="logo-container">
|
||||
<h1 class="title">零零七云计算后台控制面板</h1>
|
||||
<img src="@/assets/logo.png" alt="Logo" class="logo-img" />
|
||||
</div>
|
||||
<el-scrollbar class="sidebar-scrollbar">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
class="sidebar-menu"
|
||||
background-color="#ffffff"
|
||||
text-color="#333333"
|
||||
active-text-color="#1890ff"
|
||||
background-color="transparent"
|
||||
text-color="#34495e"
|
||||
active-text-color="#2c3e50"
|
||||
:unique-opened="true"
|
||||
router
|
||||
>
|
||||
@@ -143,46 +143,39 @@ const handleLogout = () => {
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
width: 260px;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-right: 1px solid #e1e8ed;
|
||||
overflow: hidden;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
height: 60px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
color: #333;
|
||||
justify-content: center;
|
||||
padding: 0 20px;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: #1890ff;
|
||||
.logo-img {
|
||||
height: 50px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.sidebar-scrollbar {
|
||||
height: calc(100vh - 60px);
|
||||
height: calc(100vh - 70px);
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
border-right: none;
|
||||
min-height: 100%;
|
||||
background-color: transparent !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 主容器样式 */
|
||||
@@ -197,9 +190,9 @@ const handleLogout = () => {
|
||||
/* 顶部导航栏样式 */
|
||||
.navbar {
|
||||
height: 60px;
|
||||
padding: 0 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
padding: 0 20px;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -209,32 +202,35 @@ const handleLogout = () => {
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
padding: 0 10px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #606266;
|
||||
color: #34495e;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.header-btn:hover {
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f9fa;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
@@ -243,16 +239,31 @@ const handleLogout = () => {
|
||||
cursor: pointer;
|
||||
padding: 0 12px;
|
||||
height: 60px;
|
||||
gap: 8px;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.avatar-container:hover {
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.username {
|
||||
margin: 0 8px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.avatar-container .el-icon--right) {
|
||||
color: #7f8c8d;
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.avatar-container:hover :deep(.el-icon--right) {
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
@@ -275,13 +286,38 @@ const handleLogout = () => {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu) {
|
||||
border-radius: 0;
|
||||
border: 1px solid #e1e8ed;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(44, 62, 80, 0.1);
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #34495e;
|
||||
transition: all 0.2s ease;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin-right: 8px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item:hover) {
|
||||
background-color: #f8f9fa;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item:hover i) {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item.is-divided) {
|
||||
border-top: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
/* 侧边栏滚动条样式优化 */
|
||||
@@ -293,18 +329,98 @@ const handleLogout = () => {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
:deep(.sidebar-scrollbar .el-scrollbar__bar) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
:deep(.sidebar-scrollbar .el-scrollbar__thumb) {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
:deep(.sidebar-scrollbar .el-scrollbar__thumb:hover) {
|
||||
background-color: rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
/* Element Plus 菜单项样式优化 */
|
||||
:deep(.el-menu) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title) {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
transition: background-color 0.2s ease;
|
||||
color: #34495e !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title:hover) {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
transition: background-color 0.2s ease;
|
||||
color: #34495e !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item:hover) {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
background-color: rgba(44, 62, 80, 0.1) !important;
|
||||
color: #2c3e50 !important;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background-color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
|
||||
color: #2c3e50 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu .el-menu) {
|
||||
background-color: #fafbfc !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu .el-menu-item) {
|
||||
margin: 0;
|
||||
padding-left: 48px !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu .el-menu-item.is-active) {
|
||||
background-color: rgba(44, 62, 80, 0.12) !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__icon-arrow) {
|
||||
color: #7f8c8d !important;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
|
||||
transform: rotate(180deg);
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -181,34 +181,72 @@ const breadcrumbs = computed(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.breadcrumb-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item) {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__inner) {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
color: #7f8c8d;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__inner a) {
|
||||
color: #606266;
|
||||
font-weight: normal;
|
||||
transition: color 0.2s ease;
|
||||
color: #7f8c8d;
|
||||
font-weight: 400;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__inner a:hover) {
|
||||
color: #1890ff;
|
||||
color: #2c3e50;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__inner.is-link) {
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item:last-child .breadcrumb-icon) {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
margin: 0 8px;
|
||||
margin: 0 10px;
|
||||
color: #bdc3c7;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item:first-child .el-breadcrumb__inner) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-breadcrumb__item:first-child .breadcrumb-icon) {
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
@@ -39,65 +39,49 @@ const hasChildren = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-menu-item, :deep(.el-sub-menu__title) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu .el-menu-item) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
padding-left: 55px !important;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
margin-right: 10px;
|
||||
width: 24px;
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
color: #7f8c8d;
|
||||
transition: color 0.2s ease;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 激活菜单项特效 */
|
||||
.el-menu-item.is-active {
|
||||
position: relative;
|
||||
background-color: #e6f7ff !important;
|
||||
color: #1890ff !important;
|
||||
font-weight: 600;
|
||||
.el-menu-item .el-icon, :deep(.el-sub-menu__title .el-icon) {
|
||||
color: #7f8c8d !important;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background-color: #1890ff;
|
||||
.el-menu-item:hover .el-icon,
|
||||
:deep(.el-sub-menu__title:hover .el-icon) {
|
||||
color: #34495e !important;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
|
||||
color: #1890ff !important;
|
||||
font-weight: 600;
|
||||
/* 激活菜单项图标 */
|
||||
.el-menu-item.is-active .el-icon {
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
.el-menu-item:hover, :deep(.el-sub-menu__title:hover) {
|
||||
background-color: #f5f7fa !important;
|
||||
:deep(.el-sub-menu.is-active > .el-sub-menu__title .el-icon) {
|
||||
color: #2c3e50 !important;
|
||||
}
|
||||
|
||||
/* 修复图标颜色 */
|
||||
.el-menu-item.is-active .el-icon, :deep(.el-sub-menu.is-active > .el-sub-menu__title .el-icon) {
|
||||
color: #1890ff !important;
|
||||
/* 菜单文字样式 */
|
||||
.el-menu-item span, :deep(.el-sub-menu__title span) {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
/* 修复箭头颜色 */
|
||||
:deep(.el-sub-menu.is-active > .el-sub-menu__title .el-sub-menu__icon-arrow) {
|
||||
color: #1890ff !important;
|
||||
/* 子菜单项样式优化 */
|
||||
:deep(.el-sub-menu .el-menu-item) {
|
||||
font-size: 13px;
|
||||
padding-left: 48px !important;
|
||||
}
|
||||
|
||||
/* 子菜单样式 */
|
||||
:deep(.el-menu--inline) {
|
||||
background-color: #fafafa;
|
||||
:deep(.el-sub-menu .el-menu-item .el-icon) {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
+104
-106
@@ -59,14 +59,16 @@ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Close, Refresh, CircleClose, Back, Right, Remove } from '@element-plus/icons-vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useTagsViewStore } from '@/store/tagsViewStore'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
// 访问过的标签 (从 store 获取)
|
||||
const visitedViews = computed(() => tagsViewStore.visitedViews)
|
||||
const affixTags = computed(() => tagsViewStore.affixTags)
|
||||
|
||||
// 固定标签
|
||||
const affixTags = ref([])
|
||||
// 访问过的标签
|
||||
const visitedViews = ref([])
|
||||
// 右键菜单
|
||||
const visible = ref(false)
|
||||
const top = ref(0)
|
||||
@@ -77,101 +79,67 @@ const selectedTag = ref({})
|
||||
const initTags = () => {
|
||||
// 如果当前路由不在访问过的标签中,添加它
|
||||
if (route.name) {
|
||||
addVisitedView(route)
|
||||
tagsViewStore.addVisitedView(route)
|
||||
}
|
||||
// 添加固定标签(仪表盘)
|
||||
const dashboardRoute = router.getRoutes().find(r => r.name === 'Dashboard')
|
||||
if (dashboardRoute) {
|
||||
affixTags.value.push(dashboardRoute)
|
||||
addVisitedView(dashboardRoute)
|
||||
// 注意:这里我们直接修改 store 的 affixTags,或者 store 应该提供一个 action
|
||||
// 简单起见,我们假设 store 的 affixTags 是可以直接修改的 ref,或者我们在 store 中添加初始化逻辑
|
||||
// 但为了保持一致性,我们这里只处理 visitedViews 的添加
|
||||
if (!tagsViewStore.affixTags.some(tag => tag.path === dashboardRoute.path)) {
|
||||
tagsViewStore.affixTags.push(dashboardRoute)
|
||||
}
|
||||
tagsViewStore.addVisitedView(dashboardRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加访问过的标签
|
||||
const addVisitedView = (view) => {
|
||||
if (visitedViews.value.some(v => v.path === view.path)) return
|
||||
|
||||
// 过滤404和登录页
|
||||
if (view.name === 'NotFound' || view.name === 'Login') return
|
||||
|
||||
visitedViews.value.push(
|
||||
Object.assign({}, view, {
|
||||
title: view.meta.title || 'unknown'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// 刷新选中的标签
|
||||
const refreshSelectedTag = (view) => {
|
||||
// 路由刷新的原理是先获取当前路由的全部信息,然后将路由重定向到一个空白页,
|
||||
// 然后立即再将路由重定向回原路由,实现刷新效果
|
||||
const { fullPath } = view
|
||||
router.replace('/redirect' + fullPath)
|
||||
}
|
||||
|
||||
// 关闭选中的标签
|
||||
const closeSelectedTag = (view) => {
|
||||
// 从访问过的标签中移除
|
||||
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||
if (index > -1) {
|
||||
visitedViews.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 如果关闭的是当前标签,则跳转到下一个标签
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews.value, view)
|
||||
}
|
||||
tagsViewStore.delVisitedView(view).then((visitedViews) => {
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews, view)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭其他标签
|
||||
const closeOthersTags = () => {
|
||||
// 保留固定标签和当前选中的标签
|
||||
visitedViews.value = visitedViews.value.filter(v => {
|
||||
return isAffix(v) || v.path === selectedTag.value.path
|
||||
router.push(selectedTag.value)
|
||||
tagsViewStore.delOthersViews(selectedTag.value).then(() => {
|
||||
// moveToCurrentTag() // 如果有滚动逻辑
|
||||
})
|
||||
|
||||
if (!isActive(selectedTag.value)) {
|
||||
router.push(selectedTag.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭左侧标签
|
||||
const closeLeftTags = () => {
|
||||
const selectedIndex = visitedViews.value.findIndex(v => v.path === selectedTag.value.path)
|
||||
if (selectedIndex === -1) return
|
||||
|
||||
// 保留固定标签和右侧标签
|
||||
visitedViews.value = visitedViews.value.filter((v, i) => {
|
||||
return isAffix(v) || i >= selectedIndex
|
||||
tagsViewStore.delLeftViews(selectedTag.value).then((visitedViews) => {
|
||||
if (!visitedViews.find(i => i.path === route.path)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
|
||||
if (!isActive(selectedTag.value)) {
|
||||
router.push(selectedTag.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭右侧标签
|
||||
const closeRightTags = () => {
|
||||
const selectedIndex = visitedViews.value.findIndex(v => v.path === selectedTag.value.path)
|
||||
if (selectedIndex === -1) return
|
||||
|
||||
// 保留固定标签和左侧标签
|
||||
visitedViews.value = visitedViews.value.filter((v, i) => {
|
||||
return isAffix(v) || i <= selectedIndex
|
||||
tagsViewStore.delRightViews(selectedTag.value).then((visitedViews) => {
|
||||
if (!visitedViews.find(i => i.path === route.path)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
|
||||
if (!isActive(selectedTag.value)) {
|
||||
router.push(selectedTag.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭所有标签
|
||||
const closeAllTags = () => {
|
||||
// 仅保留固定标签
|
||||
visitedViews.value = visitedViews.value.filter(v => isAffix(v))
|
||||
|
||||
// 跳转到第一个标签或首页
|
||||
toLastView(visitedViews.value)
|
||||
tagsViewStore.delAllViews().then((visitedViews) => {
|
||||
toLastView(visitedViews)
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到最后一个标签或首页
|
||||
@@ -182,7 +150,6 @@ const toLastView = (visitedViews, view) => {
|
||||
} else {
|
||||
// 如果没有标签,则跳转到首页
|
||||
if (view && view.name === 'Dashboard') {
|
||||
// 如果当前是首页,则刷新页面
|
||||
router.push('/redirect' + '/dashboard')
|
||||
} else {
|
||||
router.push('/')
|
||||
@@ -197,7 +164,7 @@ const isActive = (tag) => {
|
||||
|
||||
// 判断是否是固定标签
|
||||
const isAffix = (tag) => {
|
||||
return affixTags.value.some(t => t.path === tag.path)
|
||||
return tag.meta && tag.meta.affix
|
||||
}
|
||||
|
||||
// 打开右键菜单
|
||||
@@ -222,7 +189,7 @@ const closeMenu = () => {
|
||||
// 监听路由变化,添加标签
|
||||
watch(route, (newRoute) => {
|
||||
if (newRoute.name) {
|
||||
addVisitedView(newRoute)
|
||||
tagsViewStore.addVisitedView(newRoute)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -245,9 +212,8 @@ onBeforeUnmount(() => {
|
||||
.tags-view-container {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@@ -256,7 +222,7 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
padding: 0 12px;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
@@ -270,41 +236,56 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tag, .active-tag {
|
||||
height: 28px;
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
background-color: #f4f4f5;
|
||||
padding: 0 12px;
|
||||
margin-right: 0;
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
border: 1px solid #e8e8e8;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: #7f8c8d;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
color: #1890ff;
|
||||
background-color: #e6f7ff;
|
||||
border-color: #1890ff;
|
||||
color: #34495e;
|
||||
background-color: #e8ecf0;
|
||||
}
|
||||
|
||||
.active-tag {
|
||||
color: #1890ff;
|
||||
background-color: #e6f7ff;
|
||||
border-color: #1890ff;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-bottom: 2px solid #2c3e50;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag-icon {
|
||||
margin-right: 4px;
|
||||
margin-right: 6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: #95a5a6;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.tag:hover .tag-icon {
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
.active-tag .tag-icon {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.tag-title {
|
||||
@@ -315,36 +296,46 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.tag-close {
|
||||
margin-left: 5px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
margin-left: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
color: #95a5a6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.tag-close:hover {
|
||||
color: #e74c3c;
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
}
|
||||
|
||||
.tag:hover .tag-close {
|
||||
color: #666;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.active-tag .tag-close {
|
||||
color: #1890ff;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.active-tag:hover .tag-close {
|
||||
background-color: rgba(24, 144, 255, 0.1);
|
||||
.active-tag .tag-close:hover {
|
||||
color: #e74c3c;
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
background-color: #ffffff;
|
||||
list-style-type: none;
|
||||
padding: 6px 0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 4px 0;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 2px 8px rgba(44, 62, 80, 0.1);
|
||||
font-size: 12px;
|
||||
border: 1px solid #ebeef5;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.contextmenu li {
|
||||
@@ -352,15 +343,22 @@ onBeforeUnmount(() => {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #34495e;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.contextmenu li:hover {
|
||||
background-color: #f5f7fa;
|
||||
color: #1890ff;
|
||||
background-color: #f8f9fa;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.contextmenu li .el-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.contextmenu li:hover .el-icon {
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
+31
-19
@@ -41,7 +41,7 @@ const routes = [
|
||||
},
|
||||
component: () => import('../views/ticket/TicketChat.vue'),
|
||||
},
|
||||
|
||||
|
||||
// ACS管理路由
|
||||
{
|
||||
path: 'acs',
|
||||
@@ -130,7 +130,19 @@ const routes = [
|
||||
meta: {
|
||||
title: '节点管理'
|
||||
}
|
||||
},{
|
||||
},
|
||||
{
|
||||
path: 'nodes/form',
|
||||
name: 'ServerForm',
|
||||
component: () => import('@/views/acs/nodes/ServerForm.vue'),
|
||||
meta: { title: '服务器表单', activeMenu: '/acs/nodes', hidden: true }
|
||||
},
|
||||
{
|
||||
path: 'images/form',
|
||||
name: 'ImageForm',
|
||||
component: () => import('@/views/acs/images/ImageForm.vue'),
|
||||
meta: { title: '镜像表单', activeMenu: '/acs/images/vm', hidden: true }
|
||||
}, {
|
||||
path: 'guacamole',
|
||||
name: 'Guacamole',
|
||||
component: () => import('../views/acs/guacamole/Guacamole.vue'),
|
||||
@@ -365,7 +377,7 @@ const routes = [
|
||||
title: '管理员权限'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: 'file',
|
||||
name: 'SystemFile',
|
||||
@@ -374,7 +386,7 @@ const routes = [
|
||||
title: '文件管理'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: 'domain-whitelist',
|
||||
name: 'DomainWhitelist',
|
||||
@@ -492,21 +504,21 @@ const routes = [
|
||||
title: '容器详情',
|
||||
hidden: true
|
||||
}
|
||||
},{
|
||||
path:'servers/container/console',
|
||||
name:'ContainerConsole',
|
||||
component:()=>import('../views/acs/nodes/containerConsole.vue'),
|
||||
meta:{
|
||||
title:'终端容器',
|
||||
hidden:true
|
||||
}, {
|
||||
path: 'servers/container/console',
|
||||
name: 'ContainerConsole',
|
||||
component: () => import('../views/acs/nodes/containerConsole.vue'),
|
||||
meta: {
|
||||
title: '终端容器',
|
||||
hidden: true
|
||||
}
|
||||
},{
|
||||
path:'servers/container/files',
|
||||
name:'ContainerFiles',
|
||||
component:()=>import('../views/acs/nodes/containFile.vue'),
|
||||
meta:{
|
||||
title:'容器文件管理',
|
||||
hidden:true
|
||||
}, {
|
||||
path: 'servers/container/files',
|
||||
name: 'ContainerFiles',
|
||||
component: () => import('../views/acs/nodes/containFile.vue'),
|
||||
meta: {
|
||||
title: '容器文件管理',
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -540,7 +552,7 @@ const router = createRouter({
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 设置页面标题
|
||||
document.title = to.meta.title ? `${to.meta.title} - 007UI管理系统` : '007UI管理系统'
|
||||
|
||||
|
||||
// 这里可以添加登录验证逻辑
|
||||
const isAuthenticated = localStorage.getItem('token')
|
||||
if (to.path !== '/login' && !isAuthenticated) {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
const visitedViews = ref([])
|
||||
const affixTags = ref([])
|
||||
|
||||
// 添加访问过的标签
|
||||
const addVisitedView = (view) => {
|
||||
if (visitedViews.value.some(v => v.path === view.path)) return
|
||||
|
||||
// 过滤404和登录页
|
||||
if (view.name === 'NotFound' || view.name === 'Login') return
|
||||
|
||||
visitedViews.value.push(
|
||||
Object.assign({}, view, {
|
||||
title: view.meta.title || 'unknown'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// 删除访问过的标签
|
||||
const delVisitedView = (view) => {
|
||||
return new Promise((resolve) => {
|
||||
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||
if (index > -1) {
|
||||
visitedViews.value.splice(index, 1)
|
||||
}
|
||||
resolve([...visitedViews.value])
|
||||
})
|
||||
}
|
||||
|
||||
// 删除其他标签
|
||||
const delOthersViews = (view) => {
|
||||
return new Promise((resolve) => {
|
||||
visitedViews.value = visitedViews.value.filter(v => {
|
||||
return v.meta.affix || v.path === view.path
|
||||
})
|
||||
resolve([...visitedViews.value])
|
||||
})
|
||||
}
|
||||
|
||||
// 删除所有标签
|
||||
const delAllViews = () => {
|
||||
return new Promise((resolve) => {
|
||||
visitedViews.value = visitedViews.value.filter(tag => tag.meta.affix)
|
||||
resolve([...visitedViews.value])
|
||||
})
|
||||
}
|
||||
|
||||
// 删除左侧标签
|
||||
const delLeftViews = (view) => {
|
||||
return new Promise((resolve) => {
|
||||
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||
if (index === -1) {
|
||||
resolve([...visitedViews.value])
|
||||
return
|
||||
}
|
||||
visitedViews.value = visitedViews.value.filter((v, i) => {
|
||||
return v.meta.affix || i >= index
|
||||
})
|
||||
resolve([...visitedViews.value])
|
||||
})
|
||||
}
|
||||
|
||||
// 删除右侧标签
|
||||
const delRightViews = (view) => {
|
||||
return new Promise((resolve) => {
|
||||
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||
if (index === -1) {
|
||||
resolve([...visitedViews.value])
|
||||
return
|
||||
}
|
||||
visitedViews.value = visitedViews.value.filter((v, i) => {
|
||||
return v.meta.affix || i <= index
|
||||
})
|
||||
resolve([...visitedViews.value])
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
visitedViews,
|
||||
affixTags,
|
||||
addVisitedView,
|
||||
delVisitedView,
|
||||
delOthersViews,
|
||||
delAllViews,
|
||||
delLeftViews,
|
||||
delRightViews
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
[
|
||||
{
|
||||
"value": "beijing",
|
||||
"label": "北京",
|
||||
"children": [
|
||||
{
|
||||
"value": "beijing",
|
||||
"label": "北京"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "shanghai",
|
||||
"label": "上海",
|
||||
"children": [
|
||||
{
|
||||
"value": "shanghai",
|
||||
"label": "上海"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "guangdong",
|
||||
"label": "广东",
|
||||
"children": [
|
||||
{
|
||||
"value": "guangzhou",
|
||||
"label": "广州"
|
||||
},
|
||||
{
|
||||
"value": "shenzhen",
|
||||
"label": "深圳"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "zhejiang",
|
||||
"label": "浙江",
|
||||
"children": [
|
||||
{
|
||||
"value": "hangzhou",
|
||||
"label": "杭州"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "jiangsu",
|
||||
"label": "江苏",
|
||||
"children": [
|
||||
{
|
||||
"value": "nanjing",
|
||||
"label": "南京"
|
||||
},
|
||||
{
|
||||
"value": "suzhou",
|
||||
"label": "苏州"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "hongkong",
|
||||
"label": "香港",
|
||||
"children": [
|
||||
{
|
||||
"value": "hongkong",
|
||||
"label": "香港"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "overseas",
|
||||
"label": "海外",
|
||||
"children": [
|
||||
{
|
||||
"value": "usa",
|
||||
"label": "美国"
|
||||
},
|
||||
{
|
||||
"value": "japan",
|
||||
"label": "日本"
|
||||
},
|
||||
{
|
||||
"value": "singapore",
|
||||
"label": "新加坡"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,17 +1,5 @@
|
||||
<template>
|
||||
<div class="guacamole-container">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<div class="page-header">
|
||||
<div class="left">
|
||||
<h2 class="title">远程桌面网关管理</h2>
|
||||
<el-tag type="info" effect="plain" class="count-tag">共 {{ guacamoleStats.total }} 个配置</el-tag>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="handleAdd" :icon="Plus" class="action-btn">添加配置</el-button>
|
||||
<el-button @click="handleRefresh" :icon="Refresh" class="action-btn">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-panel">
|
||||
<div class="stat-card total-card">
|
||||
@@ -37,79 +25,91 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="filter-section">
|
||||
<el-input
|
||||
v-model="filterForm.url"
|
||||
placeholder="搜索 Guacamole URL"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
class="search-input"
|
||||
/>
|
||||
<div class="filter-actions">
|
||||
<el-button type="primary" @click="handleSearch" :icon="Search">搜索</el-button>
|
||||
<el-button @click="resetFilter" :icon="Delete">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Guacamole 配置列表 -->
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="guacamoleData"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
table-layout="auto"
|
||||
class="guacamole-table"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="Guacamole URL" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="username" label="用户名" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="密码" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="togglePasswordVisibility(scope.row)"
|
||||
:icon="scope.row.showPassword ? View : Hide"
|
||||
>
|
||||
{{ scope.row.showPassword ? scope.row.password : '••••••••' }}
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="filterForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="filterForm.url"
|
||||
placeholder="搜索 Guacamole URL"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetFilter">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>添加配置
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="编辑配置" placement="top" :hide-after="1500">
|
||||
<el-button
|
||||
type="warning"
|
||||
:icon="Edit"
|
||||
circle
|
||||
@click="handleEdit(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除配置" placement="top" :hide-after="1500">
|
||||
<el-button
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
circle
|
||||
@click="handleDelete(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-button @click="handleRefresh">
|
||||
<el-icon><refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<!-- Guacamole 配置列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="guacamoleData"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="Guacamole URL" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="username" label="用户名" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="密码" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="togglePasswordVisibility(scope.row)"
|
||||
>
|
||||
<el-icon style="margin-right: 4px"><component :is="scope.row.showPassword ? View : Hide" /></el-icon>
|
||||
{{ scope.row.showPassword ? scope.row.password : '••••••••' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(scope.row)"
|
||||
>
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
<el-icon><delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
@@ -119,9 +119,10 @@
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑配置对话框 -->
|
||||
<el-dialog
|
||||
@@ -510,42 +511,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.guacamole-container {
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 120px);
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 页面标题样式 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-header .title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.count-tag {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
@@ -558,18 +524,18 @@ onMounted(async () => {
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #ebeef5;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
@@ -608,60 +574,64 @@ onMounted(async () => {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.1;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* 搜索和筛选部分 */
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.guacamole-table {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
@@ -676,14 +646,6 @@ onMounted(async () => {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-weight: 600;
|
||||
margin: 16px 0 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #ebeef5;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
/* 对话框底部 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
@@ -696,6 +658,37 @@ onMounted(async () => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 992px) {
|
||||
.stats-panel {
|
||||
@@ -708,17 +701,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -727,13 +709,18 @@ onMounted(async () => {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
max-width: none;
|
||||
.search-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,61 +1,59 @@
|
||||
<template>
|
||||
<div class="container-images-container" v-loading="loading">
|
||||
<div class="page-header">
|
||||
<h2>容器镜像</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="handleRefresh">
|
||||
<el-icon><refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务器选择和搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="服务器">
|
||||
<el-select v-model="selectedServerId" placeholder="请选择服务器" @change="handleServerChange" style="width: 200px">
|
||||
<el-option
|
||||
v-for="server in serverList"
|
||||
:key="server.server_id"
|
||||
:label="server.name"
|
||||
:value="server.server_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="镜像名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入镜像名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 当前服务器镜像列表 -->
|
||||
<div v-if="currentServer" class="server-section">
|
||||
<div class="server-header">
|
||||
<h3>{{ currentServer.name }}</h3>
|
||||
<div class="server-actions">
|
||||
<el-button type="primary" @click="handleAdd(currentServer.server_id)">
|
||||
<el-icon><plus /></el-icon>上传镜像
|
||||
</el-button>
|
||||
<el-button type="success" @click="TosyncMirror(currentServer.server_id)">
|
||||
<el-icon><refresh /></el-icon>同步镜像
|
||||
</el-button>
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="服务器">
|
||||
<el-select v-model="selectedServerId" placeholder="请选择服务器" @change="handleServerChange" style="width: 200px">
|
||||
<el-option
|
||||
v-for="server in serverList"
|
||||
:key="server.server_id"
|
||||
:label="server.name"
|
||||
:value="server.server_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="镜像名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入镜像名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleRefresh">
|
||||
<el-icon><refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card class="table-card">
|
||||
<!-- 当前服务器镜像列表 -->
|
||||
<div v-if="currentServer" class="table-section">
|
||||
<div class="server-header">
|
||||
<h3>{{ currentServer.name }}</h3>
|
||||
<div class="server-actions">
|
||||
<el-button type="primary" @click="handleAdd(currentServer.server_id)">
|
||||
<el-icon><plus /></el-icon>上传镜像
|
||||
</el-button>
|
||||
<el-button type="success" @click="TosyncMirror(currentServer.server_id)">
|
||||
<el-icon><refresh /></el-icon>同步镜像
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="currentMirrorList"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="image_id"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column label="镜像信息" min-width="250">
|
||||
@@ -65,7 +63,7 @@
|
||||
<div class="image-info-content">
|
||||
<div class="image-name-row">
|
||||
<span class="table-image-name">{{ scope.row.name }}</span>
|
||||
<el-tag>{{ scope.row.tag || '无标签' }}</el-tag>
|
||||
<el-tag size="small">{{ scope.row.tag || '无标签' }}</el-tag>
|
||||
</div>
|
||||
<div class="image-desc-row">{{ scope.row.description || '暂无描述' }}</div>
|
||||
</div>
|
||||
@@ -104,17 +102,17 @@
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
:page-size="10"
|
||||
:total="total"
|
||||
layout="prev, pager, next"
|
||||
@current-change="handleCurrentPageChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
:page-size="10"
|
||||
:total="total"
|
||||
layout="prev, pager, next"
|
||||
@current-change="handleCurrentPageChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 镜像详情对话框 -->
|
||||
<el-dialog
|
||||
@@ -201,12 +199,6 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="分类ID" prop="class_id">
|
||||
<el-input v-model="form.class_id" placeholder="请输入分类ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="class_name">
|
||||
<el-input v-model="form.class_name" placeholder="请输入分类名称" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="图标">
|
||||
<div class="image-icon-upload">
|
||||
<img v-if="form.image_ico" :src="mainUrl + form.image_ico" class="preview-icon" />
|
||||
@@ -986,65 +978,65 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.container-images-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: calc(100vh - 60px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background: #fff;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.server-section {
|
||||
margin-bottom: 32px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.server-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fff;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.server-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
@@ -1055,7 +1047,7 @@ onMounted(() => {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
height: 16px;
|
||||
background-color: #409EFF;
|
||||
margin-right: 10px;
|
||||
border-radius: 2px;
|
||||
@@ -1066,12 +1058,6 @@ onMounted(() => {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 24px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.image-info-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1118,11 +1104,12 @@ onMounted(() => {
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
/* 详情对话框样式 */
|
||||
@@ -1263,75 +1250,34 @@ onMounted(() => {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
/* 对话框样式优化 */
|
||||
:deep(.el-dialog__header) {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
border-top: 1px solid #ebeef5;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:hover) {
|
||||
background-color: #ecf5ff !important;
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-button--link) {
|
||||
padding: 4px 8px;
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--link):hover {
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.server-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.server-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-info-cell {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.image-info-content {
|
||||
margin-left: 0;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-image-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,107 +1,105 @@
|
||||
<template>
|
||||
<div class="image-categories-container" v-loading="loading">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header">
|
||||
<h2>镜像分类管理</h2>
|
||||
<p class="page-description">管理不同服务器下的镜像分类</p>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" class="search-form">
|
||||
<el-form-item label="服务器">
|
||||
<el-select
|
||||
v-model="selectedServer"
|
||||
placeholder="请选择服务器"
|
||||
clearable
|
||||
@change="handleServerChange"
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in serverList"
|
||||
:key="item.server_id"
|
||||
:label="item.name"
|
||||
:value="item.server_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称">
|
||||
<el-input
|
||||
v-model="searchKey"
|
||||
placeholder="搜索分类名称"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleAddCategory"
|
||||
:disabled="!selectedServer"
|
||||
>
|
||||
<el-icon><plus /></el-icon>添加分类
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-card class="table-card">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="filteredCategoryList"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column type="index" width="60" align="center" label="序号" />
|
||||
<el-table-column prop="name" label="分类名称" min-width="150" />
|
||||
<el-table-column label="分类图标" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-avatar
|
||||
v-if="scope.row.class_ico"
|
||||
:size="40"
|
||||
:src="scope.row.class_ico"
|
||||
fit="cover"
|
||||
/>
|
||||
<el-icon v-else :size="20"><picture /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属服务器" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-tag type="info">{{ getServerName(scope.row.server_id) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.created_at }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" class="search-form">
|
||||
<el-form-item label="服务器">
|
||||
<el-select
|
||||
v-model="selectedServer"
|
||||
placeholder="请选择服务器"
|
||||
clearable
|
||||
@change="handleServerChange"
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in serverList"
|
||||
:key="item.server_id"
|
||||
:label="item.name"
|
||||
:value="item.server_id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称">
|
||||
<el-input
|
||||
v-model="searchKey"
|
||||
placeholder="搜索分类名称"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
@click="handleEditCategory(scope.row)"
|
||||
type="primary"
|
||||
@click="handleAddCategory"
|
||||
:disabled="!selectedServer"
|
||||
>
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
<el-icon><plus /></el-icon>添加分类
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<div class="pagination-container">
|
||||
<!-- 表格 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="filteredCategoryList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="index" width="60" align="center" label="序号" />
|
||||
<el-table-column prop="name" label="分类名称" min-width="150" />
|
||||
<el-table-column label="分类图标" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-avatar
|
||||
v-if="scope.row.class_ico"
|
||||
:size="40"
|
||||
:src="scope.row.class_ico"
|
||||
fit="cover"
|
||||
style="background-color: #f5f7fa; border: 1px solid #ebeef5;"
|
||||
/>
|
||||
<el-icon v-else :size="20" color="#909399"><picture /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属服务器" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-tag type="info">{{ getServerName(scope.row.server_id) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" min-width="180" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEditCategory(scope.row)"
|
||||
>
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<el-pagination
|
||||
background
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalCount"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -495,58 +493,59 @@ const getServerName = (serverId) => {
|
||||
|
||||
<style scoped>
|
||||
.image-categories-container {
|
||||
padding: 24px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: calc(100vh - 60px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24px;
|
||||
background: #fff;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 8px;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
/* 图片上传区域样式 */
|
||||
@@ -650,59 +649,34 @@ const getServerName = (serverId) => {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
/* 对话框样式优化 */
|
||||
:deep(.el-dialog__header) {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
border-top: 1px solid #ebeef5;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:hover) {
|
||||
background-color: #ecf5ff !important;
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-button--link) {
|
||||
padding: 4px 8px;
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--link):hover {
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.image-icon-upload {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.upload-buttons {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -0,0 +1,825 @@
|
||||
<template>
|
||||
<div class="image-form-container">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<el-button @click="goBack" class="back-btn" circle>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</el-button>
|
||||
<div class="header-title-area">
|
||||
<h1 class="page-title">{{ isEdit ? '编辑镜像' : '添加镜像' }}</h1>
|
||||
<span class="page-subtitle">{{ isEdit ? '修改现有镜像的配置信息' : '上传并配置新的虚拟机镜像' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button @click="goBack" size="large">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitting" size="large" class="submit-btn">
|
||||
{{ isEdit ? '保存修改' : '立即创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主表单区域 -->
|
||||
<div class="form-wrapper">
|
||||
<el-form :model="form" label-position="top" :rules="rules" ref="formRef" class="main-form" size="large">
|
||||
|
||||
<!-- 左侧:主要配置 -->
|
||||
<div class="form-main-col">
|
||||
<el-card class="premium-card" shadow="never">
|
||||
<div class="section-header">
|
||||
<div class="section-icon"><el-icon><Monitor /></el-icon></div>
|
||||
<div class="section-info">
|
||||
<h3>基础信息</h3>
|
||||
<p>配置镜像的基本标识与文件信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="镜像名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入镜像名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="展示名称" prop="show_name">
|
||||
<el-input v-model="form.show_name" placeholder="请输入展示名称" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="文件路径" prop="path">
|
||||
<el-input v-model="form.path" placeholder="请输入镜像文件在服务器上的绝对路径">
|
||||
<template #prefix><el-icon><Folder /></el-icon></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="镜像描述" prop="description">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入关于此镜像的详细描述"
|
||||
resize="none"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="section-header">
|
||||
<div class="section-icon"><el-icon><SetUp /></el-icon></div>
|
||||
<div class="section-info">
|
||||
<h3>分类与版本</h3>
|
||||
<p>管理镜像的分类归属与版本信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="所属分类" prop="class_id">
|
||||
<el-select
|
||||
v-model="form.class_id"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleCategoryChange"
|
||||
>
|
||||
<el-option v-for="item in categoryList" :key="item.class_id" :label="item.name" :value="item.class_id" />
|
||||
<el-option label="+ 创建新分类" value="" class="create-new-option" />
|
||||
</el-select>
|
||||
|
||||
<div class="new-category-input" v-if="showNewCategoryInput">
|
||||
<el-input
|
||||
v-model="form.class_name"
|
||||
placeholder="输入新分类名称"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="createNewCategory">创建</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="版本号" prop="vm_gen">
|
||||
<el-input v-model="form.vm_gen" placeholder="例如:v1.0.0" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="关联套餐" prop="plan_id">
|
||||
<el-select v-model="form.plan_id" placeholder="请选择适用的套餐" style="width: 100%">
|
||||
<el-option v-for="item in planList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:图标与高级 -->
|
||||
<div class="form-side-col">
|
||||
<el-card class="premium-card" shadow="never">
|
||||
<div class="section-header small">
|
||||
<div class="section-info">
|
||||
<h3>镜像图标</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="icon-uploader">
|
||||
<div class="icon-preview" v-if="form.image_ico">
|
||||
<img :src="mainUrl + form.image_ico" />
|
||||
<div class="icon-actions">
|
||||
<el-button size="small" circle @click="form.image_ico = ''"><el-icon><Delete /></el-icon></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-area" v-else>
|
||||
<div class="upload-placeholder">
|
||||
<el-icon class="upload-icon"><Picture /></el-icon>
|
||||
<div class="upload-text">点击上传或选择图标</div>
|
||||
</div>
|
||||
<div class="upload-buttons">
|
||||
<el-button type="primary" size="small" @click="$refs.fileInput.click()">本地上传</el-button>
|
||||
<el-button size="small" @click="openPicLibrary">素材库</el-button>
|
||||
</div>
|
||||
<input ref="fileInput" type="file" style="display: none" @change="onFileSelected" accept="image/*" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 素材库对话框 -->
|
||||
<el-dialog v-model="picSwitch" title="选择图标" width="800px" append-to-body>
|
||||
<div class="pic-search">
|
||||
<el-input
|
||||
v-model="picPagin.key"
|
||||
placeholder="搜索图标..."
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
@change="getpicList"
|
||||
/>
|
||||
</div>
|
||||
<div class="pic-grid" v-loading="picLoading">
|
||||
<div
|
||||
v-for="(item, index) in picList"
|
||||
:key="index"
|
||||
class="pic-item"
|
||||
:class="{ active: currentIndex === index }"
|
||||
@click="selectImage(index)"
|
||||
>
|
||||
<img :src="`${mainUrl}/v1/attachment/get_attachment?aid=${item.attachment_id}`" />
|
||||
<div class="pic-name">{{ item.title || '未命名' }}</div>
|
||||
<div class="pic-check" v-if="currentIndex === index"><el-icon><Check /></el-icon></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:total="total"
|
||||
:current-page="picPagin.page"
|
||||
:page-size="picPagin.count"
|
||||
@current-change="CurrentPageChange"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="picSwitch = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmPicSelection" :disabled="currentIndex === null">确定选择</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import {
|
||||
ArrowLeft, Monitor, Folder, SetUp, Picture, Delete,
|
||||
Search, Check
|
||||
} from '@element-plus/icons-vue'
|
||||
import { getServerPlan } from '@/utils/acs/server'
|
||||
import {
|
||||
editMirror, addVirtualMirror, getImageTypeList, createImageType, getUserMirrorList
|
||||
} from '@/utils/acs/mirror'
|
||||
import { uploadFile, getFileList } from '@/utils/acs/message'
|
||||
import { mainUrl } from '@/utils/request'
|
||||
|
||||
import { useTagsViewStore } from '@/store/tagsViewStore'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
const formRef = ref(null)
|
||||
const submitting = ref(false)
|
||||
|
||||
const goBack = () => {
|
||||
tagsViewStore.delVisitedView(route)
|
||||
router.back()
|
||||
}
|
||||
const isEdit = computed(() => !!route.query.id)
|
||||
const serverId = computed(() => route.query.server_id)
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
show_name: '',
|
||||
description: '',
|
||||
server_type: 'hyperV',
|
||||
plan_id: '',
|
||||
image_ico: '',
|
||||
server_id: '',
|
||||
path: '',
|
||||
class_id: '',
|
||||
class_name: '',
|
||||
vm_gen: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }],
|
||||
path: [{ required: true, message: '请输入文件路径', trigger: 'blur' }],
|
||||
show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const categoryList = ref([])
|
||||
const planList = ref([])
|
||||
const showNewCategoryInput = ref(false)
|
||||
|
||||
// 素材库相关
|
||||
const picSwitch = ref(false)
|
||||
const picLoading = ref(false)
|
||||
const picPagin = reactive({
|
||||
count: 20,
|
||||
page: 1,
|
||||
key: '',
|
||||
user_type: 1
|
||||
})
|
||||
const picList = ref([])
|
||||
const total = ref(0)
|
||||
const currentIndex = ref(null)
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
id: '',
|
||||
name: '',
|
||||
show_name: '',
|
||||
description: '',
|
||||
server_type: 'hyperV',
|
||||
plan_id: '',
|
||||
image_ico: '',
|
||||
server_id: '',
|
||||
path: '',
|
||||
class_id: '',
|
||||
class_name: '',
|
||||
vm_gen: ''
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
resetForm()
|
||||
|
||||
if (!serverId.value) {
|
||||
ElMessage.error('缺少服务器ID参数')
|
||||
return
|
||||
}
|
||||
|
||||
form.server_id = serverId.value
|
||||
|
||||
try {
|
||||
// 获取套餐列表
|
||||
const planRes = await getServerPlan({ server_id: serverId.value })
|
||||
if (planRes.data.code === 200) {
|
||||
planList.value = planRes.data.data.map(item => ({
|
||||
name: item.name,
|
||||
id: item.plan_id
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取分类列表
|
||||
await fetchCategoryList()
|
||||
|
||||
// 如果是编辑模式,填充数据
|
||||
if (isEdit.value) {
|
||||
const id = route.query.id
|
||||
// 尝试从 history.state 获取数据
|
||||
const stateData = history.state.params ? JSON.parse(JSON.stringify(history.state.params)) : null
|
||||
|
||||
if (stateData && stateData.id == id) {
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key in stateData) {
|
||||
form[key] = stateData[key]
|
||||
}
|
||||
})
|
||||
// 确保 ID 类型一致性 (有些时候 API 返回的是数字,有些时候是字符串)
|
||||
if (form.plan_id) form.plan_id = Number(form.plan_id) || form.plan_id
|
||||
if (form.class_id) form.class_id = Number(form.class_id) || form.class_id
|
||||
} else {
|
||||
// Fallback: fetch list and find item
|
||||
const listRes = await getUserMirrorList({
|
||||
server_id: serverId.value,
|
||||
count: 100,
|
||||
page: 1
|
||||
})
|
||||
if (listRes.data.code === 200) {
|
||||
const found = listRes.data.data.find(item => item.id == id)
|
||||
if (found) {
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key in found) form[key] = found[key]
|
||||
})
|
||||
if (form.plan_id) form.plan_id = Number(form.plan_id) || form.plan_id
|
||||
if (form.class_id) form.class_id = Number(form.class_id) || form.class_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
ElMessage.error('数据加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCategoryList = async () => {
|
||||
try {
|
||||
const res = await getImageTypeList(serverId.value)
|
||||
if (res.data.code === 200) {
|
||||
categoryList.value = res.data.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分类失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCategoryChange = (val) => {
|
||||
if (val === '') {
|
||||
// 选择了创建新分类
|
||||
form.class_id = ''
|
||||
showNewCategoryInput.value = true
|
||||
} else {
|
||||
showNewCategoryInput.value = false
|
||||
form.class_name = ''
|
||||
}
|
||||
}
|
||||
|
||||
const createNewCategory = async () => {
|
||||
if (!form.class_name.trim()) {
|
||||
ElMessage.warning('请输入分类名称')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await createImageType(serverId.value, form.class_name.trim(), '')
|
||||
if (res.data.code === 200) {
|
||||
ElMessage.success('分类创建成功')
|
||||
await fetchCategoryList()
|
||||
// 选中新创建的分类
|
||||
const newCat = categoryList.value.find(c => c.name === form.class_name.trim())
|
||||
if (newCat) {
|
||||
form.class_id = newCat.class_id
|
||||
showNewCategoryInput.value = false
|
||||
form.class_name = ''
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || '创建失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('创建分类失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 图片上传与选择
|
||||
const onFileSelected = async (event) => {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
try {
|
||||
const res = await uploadFile({ file })
|
||||
if (res.data.code === 200) {
|
||||
form.image_ico = '/v1/attachment/get_attachment?aid=' + res.data.data.attachment_id
|
||||
ElMessage.success('上传成功')
|
||||
} else {
|
||||
ElMessage.error('上传失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('上传出错')
|
||||
}
|
||||
}
|
||||
|
||||
const openPicLibrary = () => {
|
||||
picSwitch.value = true
|
||||
getpicList()
|
||||
}
|
||||
|
||||
const getpicList = async () => {
|
||||
picLoading.value = true
|
||||
try {
|
||||
const res = await getFileList(picPagin)
|
||||
if (res.data.code === 200) {
|
||||
picList.value = res.data.data
|
||||
total.value = res.data.count
|
||||
}
|
||||
} finally {
|
||||
picLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectImage = (index) => {
|
||||
currentIndex.value = index
|
||||
}
|
||||
|
||||
const confirmPicSelection = () => {
|
||||
if (currentIndex.value !== null) {
|
||||
const item = picList.value[currentIndex.value]
|
||||
form.image_ico = `/v1/attachment/get_attachment?aid=${item.attachment_id}`
|
||||
picSwitch.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const CurrentPageChange = (page) => {
|
||||
picPagin.page = page
|
||||
getpicList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitting.value = true
|
||||
try {
|
||||
const submitData = { ...form }
|
||||
|
||||
// 处理分类逻辑
|
||||
if (submitData.class_id) {
|
||||
submitData.class_name = ''
|
||||
} else if (submitData.class_name) {
|
||||
submitData.class_id = ''
|
||||
} else {
|
||||
submitData.class_id = ''
|
||||
submitData.class_name = ''
|
||||
}
|
||||
|
||||
let res
|
||||
if (isEdit.value) {
|
||||
submitData.image_id = submitData.id
|
||||
delete submitData.id
|
||||
res = await editMirror(submitData)
|
||||
} else {
|
||||
res = await addVirtualMirror(submitData)
|
||||
}
|
||||
|
||||
if (res.data.code === 200) {
|
||||
ElNotification({
|
||||
title: '操作成功',
|
||||
message: isEdit.value ? '镜像更新成功' : '镜像创建成功',
|
||||
type: 'success'
|
||||
})
|
||||
goBack()
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-form-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background: #ffffff;
|
||||
padding: 20px 32px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
border: none;
|
||||
background: #f2f3f5;
|
||||
color: #606266;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #e6e8eb;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-title-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 10px 24px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 表单布局 */
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.main-form {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.form-main-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-side-col {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.premium-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.premium-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.premium-card :deep(.el-card__body) {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* 章节标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.section-header.small {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
|
||||
color: #409EFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.section-info h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-info p {
|
||||
margin: 2px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 表单网格 */
|
||||
.form-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 新分类输入 */
|
||||
.new-category-input {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
/* 图标上传器 */
|
||||
.icon-uploader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.icon-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.icon-actions {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 2px dashed #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #409EFF;
|
||||
background: #f2f6fc;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 32px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.upload-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 素材库网格 */
|
||||
.pic-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 16px;
|
||||
margin: 20px 0;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.pic-item {
|
||||
position: relative;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pic-item:hover {
|
||||
border-color: #409EFF;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.pic-item.active {
|
||||
border-color: #409EFF;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.pic-item img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.pic-name {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pic-check {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media screen and (max-width: 992px) {
|
||||
.main-form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-side-col {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.image-form-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-actions .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-grid-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.pic-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,103 +1,103 @@
|
||||
<template>
|
||||
<div class="image-requests-container">
|
||||
<div class="page-header">
|
||||
<h2>申请镜像</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>申请镜像
|
||||
</el-button>
|
||||
<el-button @click="handleRefresh">
|
||||
<el-icon><refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="镜像名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入镜像名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="镜像类型">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择镜像类型" clearable style="width: 150px">
|
||||
<el-option label="Docker镜像" value="docker" />
|
||||
<el-option label="Windows镜像" value="windows" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 150px">
|
||||
<el-option label="已通过" value="approved" />
|
||||
<el-option label="审核中" value="pending" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>申请镜像
|
||||
</el-button>
|
||||
<el-button @click="handleRefresh">
|
||||
<el-icon><refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<el-alert
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="info-alert"
|
||||
>
|
||||
<el-icon><info-filled /></el-icon>
|
||||
申请的镜像需要经过安全审核,审核通过后可在创建云电脑或容器时使用,审核结果将通过站内信通知。
|
||||
</el-alert>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="镜像名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入镜像名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="镜像类型">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择镜像类型" clearable>
|
||||
<el-option label="Docker镜像" value="docker" />
|
||||
<el-option label="Windows镜像" value="windows" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="已通过" value="approved" />
|
||||
<el-option label="审核中" value="pending" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="table-card">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
<!-- 提示信息 -->
|
||||
<el-alert
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="info-alert"
|
||||
style="margin: 20px 20px 0; width: auto;"
|
||||
>
|
||||
<el-table-column prop="id" label="申请ID" width="150" align="center" />
|
||||
<el-table-column prop="name" label="镜像名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.type === 'docker' ? 'success' : 'primary'">
|
||||
{{ scope.row.type === 'docker' ? 'Docker' : 'Windows' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="requestTime" label="申请时间" width="180" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">
|
||||
<el-icon><view /></el-icon>查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'rejected'"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleResubmit(scope.row)"
|
||||
>
|
||||
<el-icon><refresh /></el-icon>重新提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #title>
|
||||
申请的镜像需要经过安全审核,审核通过后可在创建云电脑或容器时使用,审核结果将通过站内信通知。
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="申请ID" width="150" align="center" />
|
||||
<el-table-column prop="name" label="镜像名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.type === 'docker' ? 'success' : 'primary'">
|
||||
{{ scope.row.type === 'docker' ? 'Docker' : 'Windows' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="requestTime" label="申请时间" width="180" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">
|
||||
<el-icon><view /></el-icon>查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'rejected'"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleResubmit(scope.row)"
|
||||
>
|
||||
<el-icon><refresh /></el-icon>重新提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
@@ -106,6 +106,8 @@
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -589,41 +591,58 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.image-requests-container {
|
||||
padding: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-alert {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -637,12 +656,16 @@ onMounted(() => {
|
||||
/* 环境变量配置样式 */
|
||||
.env-vars-container {
|
||||
margin-bottom: 20px;
|
||||
background-color: #f8f9fa;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.env-vars-header {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.env-vars-item {
|
||||
@@ -665,17 +688,23 @@ onMounted(() => {
|
||||
|
||||
.add-env-btn {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
/* 端口配置样式 */
|
||||
.ports-container {
|
||||
margin-bottom: 20px;
|
||||
background-color: #f8f9fa;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ports-header {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.ports-item {
|
||||
@@ -702,6 +731,8 @@ onMounted(() => {
|
||||
|
||||
.add-port-btn {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
/* 详情样式 */
|
||||
@@ -710,11 +741,13 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.request-reason {
|
||||
background-color: #f8f8f8;
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
white-space: pre-wrap;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.review-comment {
|
||||
@@ -724,5 +757,37 @@ onMounted(() => {
|
||||
margin-top: 10px;
|
||||
white-space: pre-wrap;
|
||||
border-left: 4px solid #67c23a;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+189
-730
File diff suppressed because it is too large
Load Diff
@@ -1,99 +1,93 @@
|
||||
<template>
|
||||
<div class="announcements-container">
|
||||
<div class="page-header">
|
||||
<h2>官方公告</h2>
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布公告
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="公告标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入公告标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 150px">
|
||||
<el-option label="已发布" value="published" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已下线" value="offline" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布公告
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="公告标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入公告标题" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="已发布" value="published" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已下线" value="offline" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="公告标题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="publisher" label="发布人" width="120" />
|
||||
<el-table-column prop="publishTime" label="发布时间" width="180" />
|
||||
<el-table-column prop="viewCount" label="查看数" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
link
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-if="scope.row.status !== 'offline'"
|
||||
>下线</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-if="scope.row.status === 'offline'"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="table-card">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="公告标题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="publisher" label="发布人" width="120" align="center" />
|
||||
<el-table-column prop="publishTime" label="发布时间" width="180" align="center" />
|
||||
<el-table-column prop="viewCount" label="查看数" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">
|
||||
<el-icon><view /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-if="scope.row.status !== 'offline'"
|
||||
>
|
||||
<el-icon><turn-off /></el-icon>下线
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-if="scope.row.status === 'offline'"
|
||||
>
|
||||
<el-icon><delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
@@ -102,6 +96,8 @@
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -365,32 +361,58 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.announcements-container {
|
||||
padding: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -420,4 +442,35 @@ onMounted(() => {
|
||||
line-height: 1.8;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+142
-92
@@ -1,58 +1,61 @@
|
||||
<template>
|
||||
<div class="news-container">
|
||||
<div class="page-header">
|
||||
<h2>新闻咨询</h2>
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布新闻
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="新闻标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入新闻标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新闻分类">
|
||||
<el-select v-model="searchForm.category" placeholder="请选择分类" clearable style="width: 150px">
|
||||
<el-option label="产品动态" value="product" />
|
||||
<el-option label="技术干货" value="technology" />
|
||||
<el-option label="行业资讯" value="industry" />
|
||||
<el-option label="活动公告" value="activity" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布新闻
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="新闻标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入新闻标题" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="新闻分类">
|
||||
<el-select v-model="searchForm.category" placeholder="请选择分类" clearable>
|
||||
<el-option label="产品动态" value="product" />
|
||||
<el-option label="技术干货" value="technology" />
|
||||
<el-option label="行业资讯" value="industry" />
|
||||
<el-option label="活动公告" value="activity" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 新闻列表卡片 -->
|
||||
<div v-loading="loading" class="news-list">
|
||||
<el-card class="table-card">
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="newsData"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="新闻标题" min-width="200" show-overflow-tooltip />
|
||||
@@ -68,33 +71,27 @@
|
||||
<el-table-column prop="viewCount" label="阅读量" width="100" align="center" />
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">
|
||||
<el-icon><view /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(scope.row)">
|
||||
<el-icon><delete /></el-icon>删除
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新闻详情对话框 -->
|
||||
<el-dialog
|
||||
@@ -400,36 +397,58 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.news-container {
|
||||
padding: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.news-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -487,4 +506,35 @@ onMounted(() => {
|
||||
margin-right: 5px;
|
||||
color: #E6A23C;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+170
-117
@@ -1,108 +1,102 @@
|
||||
<template>
|
||||
<div class="policies-container">
|
||||
<div class="page-header">
|
||||
<h2>官方政策</h2>
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布政策
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="政策标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入政策标题" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="政策类型">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择政策类型" clearable style="width: 150px">
|
||||
<el-option label="服务条款" value="terms" />
|
||||
<el-option label="定价政策" value="pricing" />
|
||||
<el-option label="隐私政策" value="privacy" />
|
||||
<el-option label="合规政策" value="compliance" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>发布政策
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="政策标题">
|
||||
<el-input v-model="searchForm.title" placeholder="请输入政策标题" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="政策类型">
|
||||
<el-select v-model="searchForm.type" placeholder="请选择政策类型" clearable>
|
||||
<el-option label="服务条款" value="terms" />
|
||||
<el-option label="定价政策" value="pricing" />
|
||||
<el-option label="隐私政策" value="privacy" />
|
||||
<el-option label="合规政策" value="compliance" />
|
||||
<el-option label="其他" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><search /></el-icon>搜索
|
||||
</el-button>
|
||||
<el-button @click="resetSearch">
|
||||
<el-icon><refresh /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="政策标题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="政策类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getPolicyTypeTag(scope.row.type)">
|
||||
{{ getPolicyTypeText(scope.row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="publisher" label="发布人" width="120" align="center" />
|
||||
<el-table-column prop="publishTime" label="发布时间" width="180" align="center" />
|
||||
<el-table-column prop="effectiveTime" label="生效时间" width="180" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
link
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-if="scope.row.status === 'active'"
|
||||
>下线</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-if="scope.row.status === 'inactive'"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="table-card">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="title" label="政策标题" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="政策类型" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getPolicyTypeTag(scope.row.type)">
|
||||
{{ getPolicyTypeText(scope.row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="publisher" label="发布人" width="120" align="center" />
|
||||
<el-table-column prop="publishTime" label="发布时间" width="180" align="center" />
|
||||
<el-table-column prop="effectiveTime" label="生效时间" width="180" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="handleView(scope.row)">
|
||||
<el-icon><view /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(scope.row)">
|
||||
<el-icon><edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleChangeStatus(scope.row)"
|
||||
v-if="scope.row.status === 'active'"
|
||||
>
|
||||
<el-icon><turn-off /></el-icon>下线
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-if="scope.row.status === 'inactive'"
|
||||
>
|
||||
<el-icon><delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
@@ -111,6 +105,8 @@
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -435,32 +431,58 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.policies-container {
|
||||
padding: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -495,4 +517,35 @@ onMounted(() => {
|
||||
line-height: 1.8;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+194
-772
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,847 @@
|
||||
<template>
|
||||
<div class="server-form-container">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<el-button @click="goBack" class="back-btn" circle>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</el-button>
|
||||
<div class="header-title-area">
|
||||
<h1 class="page-title">{{ isEdit ? '编辑服务器' : '新建服务器' }}</h1>
|
||||
<span class="page-subtitle">{{ isEdit ? '修改服务器配置信息' : '配置并部署新的服务器节点' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button @click="goBack" size="large">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitting" size="large" class="submit-btn">
|
||||
{{ isEdit ? '保存修改' : '立即创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主表单区域 -->
|
||||
<div class="form-wrapper">
|
||||
<el-form :model="form" label-position="top" :rules="rules" ref="formRef" class="main-form" size="large">
|
||||
|
||||
<!-- 左侧:主要配置 -->
|
||||
<div class="form-main-col">
|
||||
<el-card class="premium-card" shadow="never">
|
||||
<div class="section-header">
|
||||
<div class="section-icon"><el-icon><Monitor /></el-icon></div>
|
||||
<div class="section-info">
|
||||
<h3>基础信息</h3>
|
||||
<p>配置服务器的基本标识与网络信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="服务器名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="例如:生产环境-Web节点-01" />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址" prop="server_ip">
|
||||
<el-input v-model="form.server_ip" placeholder="例如:192.168.1.100" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="所在地区" prop="location">
|
||||
<el-cascader
|
||||
v-model="locationArray"
|
||||
:options="regionsBuff"
|
||||
:props="optionProps"
|
||||
placeholder="选择服务器所在的物理位置"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="section-header">
|
||||
<div class="section-icon"><el-icon><Cpu /></el-icon></div>
|
||||
<div class="section-info">
|
||||
<h3>硬件规格</h3>
|
||||
<p>定义服务器的计算资源配额</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-cards">
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">CPU核心</div>
|
||||
<el-input v-model="form.cpu" placeholder="0">
|
||||
<template #suffix>核</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">内存容量</div>
|
||||
<el-input v-model="form.memory" placeholder="0">
|
||||
<template #suffix>MB</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">硬盘空间</div>
|
||||
<el-input v-model="form.disk" placeholder="0">
|
||||
<template #suffix>GB</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">网络带宽</div>
|
||||
<el-input v-model="form.bandwidth" placeholder="0">
|
||||
<template #suffix>Mbps</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="premium-card" shadow="never" style="margin-top: 24px;">
|
||||
<div class="section-header">
|
||||
<div class="section-icon"><el-icon><Connection /></el-icon></div>
|
||||
<div class="section-info">
|
||||
<h3>连接与认证</h3>
|
||||
<p>配置服务器的访问方式与凭证</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form-item label="服务器类型" prop="server_type" style="margin-bottom: 24px;">
|
||||
<div class="type-selector">
|
||||
<div
|
||||
class="type-card"
|
||||
:class="{ active: form.server_type === 'dockerContainer' }"
|
||||
@click="form.server_type = 'dockerContainer'"
|
||||
>
|
||||
<div class="type-icon"><el-icon><Box /></el-icon></div>
|
||||
<div class="type-info">
|
||||
<div class="type-name">容器云服务器</div>
|
||||
<div class="type-desc">基于Docker容器技术的轻量级实例</div>
|
||||
</div>
|
||||
<div class="type-check" v-if="form.server_type === 'dockerContainer'">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="type-card"
|
||||
:class="{ active: form.server_type === 'hyperV' }"
|
||||
@click="form.server_type = 'hyperV'"
|
||||
>
|
||||
<div class="type-icon"><el-icon><Platform /></el-icon></div>
|
||||
<div class="type-info">
|
||||
<div class="type-name">虚拟机云服务器</div>
|
||||
<div class="type-desc">基于Hyper-V技术的完整虚拟化实例</div>
|
||||
</div>
|
||||
<div class="type-check" v-if="form.server_type === 'hyperV'">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 容器云特有 -->
|
||||
<template v-if="form.server_type === 'dockerContainer'">
|
||||
<el-form-item label="Auth-ID" prop="auth_id">
|
||||
<el-input v-model="form.auth_id" placeholder="输入服务器管理ID" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 虚拟机特有 -->
|
||||
<template v-if="form.server_type === 'hyperV'">
|
||||
<el-form-item label="Guacamole网关" prop="guacamole_id">
|
||||
<el-select
|
||||
v-model="form.guacamole_id"
|
||||
placeholder="选择Guacamole连接配置"
|
||||
filterable
|
||||
clearable
|
||||
:loading="guacamoleLoading"
|
||||
@change="handleGuacamoleChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in guacamoleList"
|
||||
:key="item.id"
|
||||
:label="item.url"
|
||||
:value="item.id"
|
||||
>
|
||||
<div class="guacamole-option">
|
||||
<span class="url">{{ item.url }}</span>
|
||||
<span class="user">{{ item.username }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="登录用户名" prop="username">
|
||||
<el-input v-model="form.username" placeholder="例如:Administrator" />
|
||||
</el-form-item>
|
||||
<el-form-item label="登录密码" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
placeholder="输入登录密码"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<div class="feature-switch">
|
||||
<div class="switch-info">
|
||||
<span class="switch-title">端口映射</span>
|
||||
<span class="switch-desc">允许外部网络访问该服务器的特定端口</span>
|
||||
</div>
|
||||
<el-switch
|
||||
v-model="form.allow_port_forward"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="管理Token" prop="server_token">
|
||||
<el-input
|
||||
v-model="form.server_token"
|
||||
placeholder="节点服务器管理员Token"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:高级设置 -->
|
||||
<div class="form-side-col">
|
||||
<el-card class="premium-card" shadow="never">
|
||||
<div class="section-header small">
|
||||
<div class="section-info">
|
||||
<h3>高级设置</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form-item label="控制台连接">
|
||||
<el-input v-model="form.console_url" placeholder="可选,https需反代">
|
||||
<template #prefix><el-icon><Link /></el-icon></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="展示卡片HTML">
|
||||
<el-input
|
||||
v-model="form.html"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="自定义购买页面的展示样式代码"
|
||||
resize="none"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-form-item>
|
||||
<div class="feature-switch">
|
||||
<div class="switch-info">
|
||||
<span class="switch-title">购物车显示</span>
|
||||
<span class="switch-desc">在前端购买页面展示此节点</span>
|
||||
</div>
|
||||
<el-switch
|
||||
v-model="form.hide"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import {
|
||||
ArrowLeft, Monitor, Cpu, Link, Connection,
|
||||
Box, Platform, Check
|
||||
} from '@element-plus/icons-vue'
|
||||
import { addServer, editServer, getServer } from '@/utils/acs/server'
|
||||
import { getGuacamoleList } from '@/utils/acs/guacamole'
|
||||
import regions from '@/utils/regions.json'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const formRef = ref(null)
|
||||
const submitting = ref(false)
|
||||
const isEdit = computed(() => !!route.query.id)
|
||||
|
||||
const form = reactive({
|
||||
server_id: '',
|
||||
name: '',
|
||||
server_ip: '',
|
||||
location: '',
|
||||
bandwidth: '',
|
||||
disk: '',
|
||||
memory: '',
|
||||
cpu: '',
|
||||
state: '',
|
||||
auth_id: '',
|
||||
server_token: '',
|
||||
server_type: 'dockerContainer',
|
||||
html: '',
|
||||
hide: 0,
|
||||
console_url: '',
|
||||
guacamole_id: '',
|
||||
username: '',
|
||||
password: '',
|
||||
allow_port_forward: 0
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入服务器名称', trigger: 'blur' }],
|
||||
server_ip: [
|
||||
{ required: true, message: '请输入IP地址', trigger: 'blur' },
|
||||
{ pattern: /^(\d{1,3}\.){3}\d{1,3}$/, message: '请输入有效的IP地址', trigger: 'blur' }
|
||||
],
|
||||
guacamole_id: [
|
||||
{ required: false, message: '请输入Guacamole服务ID', trigger: 'blur' }
|
||||
],
|
||||
username: [
|
||||
{ required: false, message: '请输入登录用户名', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: false, message: '请输入登录密码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// Guacamole 相关
|
||||
const guacamoleList = ref([])
|
||||
const guacamoleLoading = ref(false)
|
||||
|
||||
const fetchGuacamoleList = async () => {
|
||||
if (guacamoleLoading.value) return
|
||||
|
||||
guacamoleLoading.value = true
|
||||
try {
|
||||
const res = await getGuacamoleList()
|
||||
if (res && res.data && res.data.code === 200) {
|
||||
guacamoleList.value = res.data.data || []
|
||||
} else {
|
||||
guacamoleList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取Guacamole列表失败:', error)
|
||||
guacamoleList.value = []
|
||||
} finally {
|
||||
guacamoleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleGuacamoleChange = (selectedId) => {
|
||||
if (!selectedId) {
|
||||
form.username = ''
|
||||
form.password = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 地区数据处理
|
||||
const regionsBuff = ref(regions)
|
||||
const optionProps = {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
children: 'children',
|
||||
checkStrictly: false,
|
||||
emitPath: true
|
||||
}
|
||||
|
||||
const findValueByLabel = (label, options) => {
|
||||
for (const option of options) {
|
||||
if (option.label === label) return option.value
|
||||
if (option.children) {
|
||||
const result = findValueByLabel(label, option.children)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const findLabelByValue = (value, options) => {
|
||||
for (const option of options) {
|
||||
if (option.value === value) return option.label
|
||||
if (option.children) {
|
||||
const result = findLabelByValue(value, option.children)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const locationArray = computed({
|
||||
get: () => {
|
||||
if (form.location) {
|
||||
try {
|
||||
const labels = form.location.split(' ')
|
||||
const values = labels.map(label => findValueByLabel(label, regionsBuff.value))
|
||||
return values.filter(value => value !== undefined)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return []
|
||||
},
|
||||
set: (newArray) => {
|
||||
try {
|
||||
if (Array.isArray(newArray) && newArray.length > 0) {
|
||||
const labels = newArray.map(value => {
|
||||
const label = findLabelByValue(value, regionsBuff.value)
|
||||
return label || value
|
||||
})
|
||||
form.location = labels.join(' ')
|
||||
} else {
|
||||
form.location = ''
|
||||
}
|
||||
} catch (error) {
|
||||
form.location = ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
if (isEdit.value) {
|
||||
const id = route.query.id
|
||||
if (id) {
|
||||
try {
|
||||
const stateData = history.state.params
|
||||
if (stateData) {
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key in stateData) {
|
||||
form[key] = stateData[key]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const res = await getServer(1, 100, '', route.query.type || 'dockerContainer')
|
||||
if (res && res.data && res.data.data) {
|
||||
const found = res.data.data.find(item => item.server_id == id)
|
||||
if (found) {
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key in found) {
|
||||
form[key] = found[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
form.server_type = route.query.type || 'dockerContainer'
|
||||
}
|
||||
|
||||
if (form.server_type === 'hyperV') {
|
||||
fetchGuacamoleList()
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitting.value = true
|
||||
try {
|
||||
const formData = { ...form }
|
||||
|
||||
const numericFields = ['bandwidth', 'disk', 'memory', 'cpu', 'hide', 'allow_port_forward']
|
||||
numericFields.forEach(field => {
|
||||
if (formData[field] !== '' && formData[field] !== null && formData[field] !== undefined) {
|
||||
formData[field] = Number(formData[field])
|
||||
}
|
||||
})
|
||||
|
||||
let res
|
||||
if (!isEdit.value) {
|
||||
res = await addServer(formData)
|
||||
} else {
|
||||
res = await editServer(formData)
|
||||
}
|
||||
|
||||
if (res && res.data && res.data.code === 200) {
|
||||
ElNotification({
|
||||
title: !isEdit.value ? '添加成功' : '更新成功',
|
||||
message: `服务器"${formData.name}"已${!isEdit.value ? '添加' : '更新'}成功`,
|
||||
type: 'success',
|
||||
duration: 3000
|
||||
})
|
||||
goBack()
|
||||
} else {
|
||||
ElMessage.error(res?.data?.msg || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交表单失败:', error)
|
||||
ElMessage.error('提交失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => form.server_type, (val) => {
|
||||
if (val === 'hyperV') {
|
||||
fetchGuacamoleList()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.server-form-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
background: #ffffff;
|
||||
padding: 20px 32px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
border: none;
|
||||
background: #f2f3f5;
|
||||
color: #606266;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #e6e8eb;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-title-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 10px 24px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 表单布局 */
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.main-form {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.form-main-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-side-col {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.premium-card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.premium-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.premium-card :deep(.el-card__body) {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* 章节标题 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.section-header.small {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
|
||||
color: #409EFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.section-info h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-info p {
|
||||
margin: 2px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 表单网格 */
|
||||
.form-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 资源卡片 */
|
||||
.resource-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.resource-item:hover {
|
||||
border-color: #c6e2ff;
|
||||
background: #f2f6fc;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 类型选择器 */
|
||||
.type-selector {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.type-card {
|
||||
position: relative;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
transition: all 0.2s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.type-card:hover {
|
||||
border-color: #409EFF;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.type-card.active {
|
||||
border-color: #409EFF;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: #f2f6fc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.type-card.active .type-icon {
|
||||
background: #fff;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.type-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.type-check {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 开关样式 */
|
||||
.feature-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.switch-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.switch-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.switch-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.guacamole-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.guacamole-option .url {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.guacamole-option .user {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media screen and (max-width: 992px) {
|
||||
.main-form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-side-col {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resource-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.server-form-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-actions .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-grid-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.type-selector {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+129
-196
@@ -446,8 +446,8 @@
|
||||
<!-- <serverChart v-if="TypeData" :Type="TypeData" class="chart-section" /> -->
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<el-tabs type="border-card" class="main-tabs">
|
||||
<el-card class="main-container" shadow="never">
|
||||
<el-tabs class="main-tabs">
|
||||
<!-- 实例规格列表 -->
|
||||
<el-tab-pane label="实例规格列表">
|
||||
<div class="tab-header">
|
||||
@@ -464,11 +464,11 @@
|
||||
<el-table
|
||||
v-loading="specLoading"
|
||||
:data="spec_list"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
table-layout="auto"
|
||||
class="data-table"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="plan_id" label="规格ID" width="80" />
|
||||
<el-table-column prop="name" label="规格名称" min-width="120" show-overflow-tooltip />
|
||||
@@ -510,24 +510,24 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<div class="table-actions">
|
||||
<el-tooltip content="编辑" placement="top" :hide-after="1500">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
link
|
||||
:icon="Edit"
|
||||
@click="show_spec(scope.row); centerDialogVisible = true; addOrChange = false;"
|
||||
/>
|
||||
>编辑</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" :hide-after="1500">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
link
|
||||
:icon="Delete"
|
||||
@click="deleteSpec(scope.row.plan_id)"
|
||||
/>
|
||||
>删除</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -562,9 +562,9 @@
|
||||
<el-table
|
||||
:data="user_servers"
|
||||
stripe
|
||||
border
|
||||
style="width: 100%"
|
||||
class="data-table"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column label="ID" prop="container_id" width="80" />
|
||||
<el-table-column label="价格" prop="pay" width="100">
|
||||
@@ -598,7 +598,7 @@
|
||||
: scope.row.container_state == 4
|
||||
? 'danger'
|
||||
: 'info'"
|
||||
effect="light"
|
||||
effect="plain"
|
||||
>
|
||||
{{
|
||||
scope.row.container_state == 0
|
||||
@@ -616,11 +616,11 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
:icon="Setting"
|
||||
@click="$router.push('/servers/container?container_id=' + scope.row.container_id)"
|
||||
>
|
||||
@@ -667,9 +667,9 @@
|
||||
<el-table
|
||||
:data="floatList"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
class="data-table"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column label="ID" prop="id" width="80" />
|
||||
<el-table-column label="创建时间" min-width="160">
|
||||
@@ -680,7 +680,7 @@
|
||||
<el-table-column label="浮动IP" prop="floating_ip" min-width="150" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.is_used ? 'success' : 'info'" effect="light">
|
||||
<el-tag :type="scope.row.is_used ? 'success' : 'info'" effect="plain">
|
||||
{{ scope.row.is_used ? "已绑定" : "未绑定" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -690,10 +690,10 @@
|
||||
<el-tooltip content="删除IP" placement="top" :hide-after="1500">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
link
|
||||
:icon="Delete"
|
||||
@click="delFloating(scope.row.id)"
|
||||
/>
|
||||
>删除</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -702,7 +702,7 @@
|
||||
<el-empty v-if="floatList.length === 0" description="暂无浮动IP数据" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑实例规格对话框 -->
|
||||
<el-dialog
|
||||
@@ -2617,9 +2617,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
|
||||
<style scoped>
|
||||
.server-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: calc(100vh - 120px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 页面标题区域 */
|
||||
@@ -2628,6 +2626,9 @@ import { ElMessageBox } from 'element-plus';
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 16px 20px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
@@ -2638,7 +2639,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
|
||||
.page-header .title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
@@ -2647,7 +2648,8 @@ import { ElMessageBox } from 'element-plus';
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
@@ -2669,13 +2671,13 @@ import { ElMessageBox } from 'element-plus';
|
||||
/* 返回按钮样式 */
|
||||
.back-btn {
|
||||
font-size: 16px;
|
||||
color: #409EFF;
|
||||
padding: 8px 0;
|
||||
margin-right: 16px;
|
||||
color: #606266;
|
||||
padding: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
color: #66b1ff;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
/* 服务器信息卡片 */
|
||||
@@ -2684,6 +2686,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* 服务器详细信息卡片 */
|
||||
@@ -2692,16 +2695,17 @@ import { ElMessageBox } from 'element-plus';
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #ebeef5;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.info-card:hover {
|
||||
@@ -2710,10 +2714,10 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
.card-title {
|
||||
background-color: #f5f7fa;
|
||||
background-color: #fafbfc;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
@@ -2722,7 +2726,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
.card-title .el-icon {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
@@ -2745,32 +2749,31 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-value.highlight {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
/* 硬件信息样式 - 符合整体设计风格 */
|
||||
/* 硬件信息样式 */
|
||||
.device-count {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.hardware-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
@@ -2780,21 +2783,21 @@ import { ElMessageBox } from 'element-plus';
|
||||
.hardware-devices {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.device-item {
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
background-color: #fafbfc;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.device-title {
|
||||
@@ -2807,57 +2810,27 @@ import { ElMessageBox } from 'element-plus';
|
||||
|
||||
.device-title .el-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.device-details {
|
||||
margin-top: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.usage-progress {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 使用率状态颜色类 */
|
||||
.info-value.usage-normal {
|
||||
color: #67C23A;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-value.usage-medium {
|
||||
color: #409EFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-value.usage-warning {
|
||||
color: #E6A23C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-value.usage-critical {
|
||||
color: #F56C6C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 实际硬件划分样式 */
|
||||
.highlight-item {
|
||||
padding: 8px;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #409EFF;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 流量信息样式 */
|
||||
.traffic-section {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.traffic-section:last-child {
|
||||
@@ -2865,62 +2838,38 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.traffic-speed-grid,
|
||||
.traffic-total-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.traffic-total-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
}
|
||||
|
||||
.traffic-total-grid,
|
||||
.traffic-distribution {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.raw-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background-color: #fafbfc;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e1e8ed;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.raw-data .info-value {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.traffic-note {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 旧样式保持兼容 */
|
||||
.usage-high {
|
||||
color: #F56C6C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.usage-medium {
|
||||
color: #E6A23C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 错误状态样式 */
|
||||
.error-status {
|
||||
color: #F56C6C;
|
||||
@@ -2929,15 +2878,10 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.chart-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 24px;
|
||||
.main-container {
|
||||
margin: 0 20px 20px 20px;
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
@@ -2945,11 +2889,13 @@ import { ElMessageBox } from 'element-plus';
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
@@ -2969,40 +2915,67 @@ import { ElMessageBox } from 'element-plus';
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.data-table {
|
||||
margin-bottom: 16px;
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.unit {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.price-tag {
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
color: #F56C6C;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
font-size: 13px;
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Tabs 样式优化 */
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
color: #409EFF;
|
||||
background: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -3013,15 +2986,13 @@ import { ElMessageBox } from 'element-plus';
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #ebeef5;
|
||||
border-bottom: 1px dashed #e1e8ed;
|
||||
}
|
||||
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -3124,27 +3095,24 @@ import { ElMessageBox } from 'element-plus';
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.server-detail-info {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.info-card.location-info {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
margin: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -3159,40 +3127,5 @@ import { ElMessageBox } from 'element-plus';
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 硬件信息响应式 */
|
||||
.hardware-summary {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.device-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.device-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 流量信息响应式 */
|
||||
.traffic-speed-grid,
|
||||
.traffic-total-grid,
|
||||
.traffic-distribution {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.raw-data {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+200
-193
@@ -1,17 +1,5 @@
|
||||
<template>
|
||||
<div class="all-sites-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="left">
|
||||
<h2 class="title">所有站点</h2>
|
||||
<el-tag type="info" effect="plain" class="count-tag">共 {{ pagination.total }} 个站点</el-tag>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="handleRefresh" :icon="Refresh" class="action-btn">刷新</el-button>
|
||||
<!-- <el-button type="success" @click="handleExport" :icon="Download" class="action-btn">导出数据</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-panel">
|
||||
<div class="stat-card total-card">
|
||||
@@ -44,102 +32,115 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="搜索容器">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入容器id或服务器id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" :icon="Delete">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="搜索容器">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入容器id或服务器id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<el-icon><Delete /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleRefresh">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 站点列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="siteList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="访问地址" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link :href="row.url" target="_blank" type="primary" v-if="row.url">
|
||||
{{ row.url }}
|
||||
</el-link>
|
||||
<span v-else class="text-muted">无访问地址</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||
{{ getConnectTypeText(row.connect_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="getConnectionStatusType(row.connect)"
|
||||
effect="plain"
|
||||
size="small"
|
||||
>
|
||||
{{ getConnectionStatusText(row.connect) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="违规状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.is_violation ? 'danger' : 'success'"
|
||||
effect="plain"
|
||||
size="small"
|
||||
>
|
||||
{{ row.is_violation ? '违规' : '正常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
|
||||
<!-- 站点列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="siteList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="访问地址" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link :href="row.url" target="_blank" type="primary" v-if="row.url">
|
||||
{{ row.url }}
|
||||
</el-link>
|
||||
<span v-else class="text-muted">无访问地址</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||
{{ getConnectTypeText(row.connect_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="getConnectionStatusType(row.connect)"
|
||||
effect="plain"
|
||||
size="small"
|
||||
>
|
||||
{{ getConnectionStatusText(row.connect) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="违规状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.is_violation ? 'danger' : 'success'"
|
||||
effect="plain"
|
||||
size="small"
|
||||
>
|
||||
{{ row.is_violation ? '违规' : '正常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" fixed="right" align="center" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip content="重新检查" placement="top">
|
||||
<el-button type="warning" :icon="Refresh" circle size="small" @click="handleRecheck(row)" />
|
||||
<el-button type="warning" link @click="handleRecheck(row)">
|
||||
<el-icon><Refresh /></el-icon>检查
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="标记违规" placement="top" v-if="row.status !== 'violation'">
|
||||
<el-button type="danger" :icon="Warning" circle size="small" @click="handleMarkViolation(row)" />
|
||||
<el-button type="danger" link @click="handleMarkViolation(row)">
|
||||
<el-icon><Warning /></el-icon>违规
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="标记正常" placement="top" v-if="row.status === 'violation'">
|
||||
<el-button type="success" :icon="CircleCheck" circle size="small" @click="handleMarkNormal(row)" />
|
||||
<el-button type="success" link @click="handleMarkNormal(row)">
|
||||
<el-icon><CircleCheck /></el-icon>正常
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -650,42 +651,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.all-sites-container {
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 120px);
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 页面标题样式 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-header .title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.count-tag {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
@@ -698,18 +664,18 @@ onMounted(() => {
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #ebeef5;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
@@ -753,88 +719,129 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.1;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 筛选容器 */
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 站点详情 */
|
||||
.site-detail {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.detail-section h4 {
|
||||
margin-bottom: 12px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 对话框底部 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 1200px) {
|
||||
@media screen and (max-width: 992px) {
|
||||
.stats-panel {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.stat-card:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义样式 */
|
||||
.text-muted {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
|
||||
.stat-card:last-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+199
-282
@@ -1,158 +1,101 @@
|
||||
<template>
|
||||
<div class="violation-sites-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="left">
|
||||
<h2 class="title">违规站点</h2>
|
||||
<el-tag type="danger" effect="plain" class="count-tag">共 {{ pagination.total }} 个违规站点</el-tag>
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="搜索容器">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入容器id或服务器id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<el-icon><Delete /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleRefresh">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="handleRefresh" :icon="Refresh" class="action-btn">刷新</el-button>
|
||||
<!-- <el-button type="warning" @click="handleBatchProcess" :disabled="!selectedRows.length" :icon="Warning" class="action-btn">
|
||||
批量处理
|
||||
</el-button>
|
||||
<el-button type="success" @click="handleExport" :icon="Download" class="action-btn">导出违规报告</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 违规统计卡片 -->
|
||||
<!-- <div class="stats-panel">
|
||||
<div class="stat-card total-card">
|
||||
<div class="stat-icon"><el-icon><Warning /></el-icon></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ violationStats.total }}</div>
|
||||
<div class="stat-label">总违规站点</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card severe-card">
|
||||
<div class="stat-icon"><el-icon><CircleClose /></el-icon></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ violationStats.severe }}</div>
|
||||
<div class="stat-label">严重违规</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card moderate-card">
|
||||
<div class="stat-icon"><el-icon><WarningFilled /></el-icon></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ violationStats.moderate }}</div>
|
||||
<div class="stat-label">中度违规</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card pending-card">
|
||||
<div class="stat-icon"><el-icon><Clock /></el-icon></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ violationStats.pending }}</div>
|
||||
<div class="stat-label">待处理</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="搜索容器">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入容器id或服务器id" clearable />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="违规类型">
|
||||
<el-select v-model="queryParams.violationType" placeholder="请选择违规类型" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="内容违规" value="content" />
|
||||
<el-option label="版权侵犯" value="copyright" />
|
||||
<el-option label="恶意软件" value="malware" />
|
||||
<el-option label="钓鱼网站" value="phishing" />
|
||||
<el-option label="其他违规" value="other" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="违规等级">
|
||||
<el-select v-model="queryParams.severity" placeholder="请选择违规等级" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="轻微" value="light" />
|
||||
<el-option label="中度" value="moderate" />
|
||||
<el-option label="严重" value="severe" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理状态">
|
||||
<el-select v-model="queryParams.processStatus" placeholder="请选择处理状态" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="待处理" value="pending" />
|
||||
<el-option label="处理中" value="processing" />
|
||||
<el-option label="已处理" value="processed" />
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" :icon="Delete">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 违规站点列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="violationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="违规地址" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link :href="row.url" target="_blank" type="danger" v-if="row.url">
|
||||
{{ row.url }}
|
||||
</el-link>
|
||||
<span v-else class="text-muted">无访问地址</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="违规类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="danger" size="small" v-if="row.violation_keys && row.violation_keys.length > 0">
|
||||
{{ row.violation_keys.join(', ') }}
|
||||
</el-tag>
|
||||
<el-tag type="warning" size="small" v-else>
|
||||
检测到违规
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||
{{ getConnectTypeText(row.connect_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<!-- 违规站点列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="violationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||
<el-table-column prop="url" label="违规地址" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link :href="row.url" target="_blank" type="danger" v-if="row.url">
|
||||
{{ row.url }}
|
||||
</el-link>
|
||||
<span v-else class="text-muted">无访问地址</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="违规类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="danger" size="small" v-if="row.violation_keys && row.violation_keys.length > 0">
|
||||
{{ row.violation_keys.join(', ') }}
|
||||
</el-tag>
|
||||
<el-tag type="warning" size="small" v-else>
|
||||
检测到违规
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="连接类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||
{{ getConnectTypeText(row.connect_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" fixed="right" align="center" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip content="重新检查" placement="top">
|
||||
<el-button type="warning" :icon="Refresh" circle size="small" @click="handleRecheck(row)" />
|
||||
<el-button type="warning" link @click="handleRecheck(row)">
|
||||
<el-icon><Refresh /></el-icon>检查
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="标记违规" placement="top" v-if="row.status !== 'violation'">
|
||||
<el-button type="danger" :icon="Warning" circle size="small" @click="handleMarkViolation(row)" />
|
||||
<el-button type="danger" link @click="handleMarkViolation(row)">
|
||||
<el-icon><Warning /></el-icon>违规
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="标记正常" placement="top" v-if="row.status === 'violation'">
|
||||
<el-button type="success" :icon="CircleCheck" circle size="small" @click="handleMarkNormal(row)" />
|
||||
<el-button type="success" link @click="handleMarkNormal(row)">
|
||||
<el-icon><CircleCheck /></el-icon>正常
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 违规详情对话框 -->
|
||||
@@ -799,7 +742,13 @@ const handleBatchProcess = () => {
|
||||
|
||||
// 导出数据
|
||||
const handleExport = () => {
|
||||
ElMessage.success('违规报告导出功能开发中...')
|
||||
ElMessage.success('导出功能开发中...')
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row) => {
|
||||
currentSite.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 重新检查
|
||||
@@ -893,12 +842,6 @@ const handleMarkNormal = (row) => {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row) => {
|
||||
currentSite.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 处理违规
|
||||
const handleProcess = (row) => {
|
||||
currentSite.value = row
|
||||
@@ -907,49 +850,14 @@ const handleProcess = (row) => {
|
||||
processDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 封禁站点
|
||||
const handleBlock = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要封禁站点 "${row.domain}" 吗?`,
|
||||
'封禁确认',
|
||||
{
|
||||
confirmButtonText: '确定封禁',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
// 模拟API调用
|
||||
row.isBlocked = true
|
||||
ElMessage.success('站点已封禁')
|
||||
getList()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 解封站点
|
||||
const handleUnblock = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要解封站点 "${row.domain}" 吗?`,
|
||||
'解封确认',
|
||||
{
|
||||
confirmButtonText: '确定解封',
|
||||
cancelButtonText: '取消',
|
||||
type: 'success'
|
||||
}
|
||||
).then(() => {
|
||||
// 模拟API调用
|
||||
row.isBlocked = false
|
||||
ElMessage.success('站点已解封')
|
||||
getList()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 提交处理
|
||||
const submitProcess = () => {
|
||||
if (!processFormRef.value) return
|
||||
|
||||
processFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success('违规处理提交成功')
|
||||
// 模拟提交处理
|
||||
ElMessage.success('处理成功')
|
||||
processDialogVisible.value = false
|
||||
getList()
|
||||
}
|
||||
@@ -964,42 +872,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.violation-sites-container {
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 120px);
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 页面标题样式 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-header .title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.count-tag {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
@@ -1012,18 +885,18 @@ onMounted(() => {
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #ebeef5;
|
||||
border: 1px solid #e1e8ed;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
@@ -1039,8 +912,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.total-card .stat-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #E6A23C;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.severe-card .stat-icon {
|
||||
@@ -1049,8 +922,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.moderate-card .stat-icon {
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
color: #FFC107;
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
.pending-card .stat-icon {
|
||||
@@ -1067,56 +940,67 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.1;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 筛选容器 */
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.domain-cell {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.blocked-tag {
|
||||
margin-left: 8px;
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.report-count {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 分页 */
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 违规详情 */
|
||||
/* 详情样式 */
|
||||
.violation-detail {
|
||||
padding: 10px 0;
|
||||
}
|
||||
@@ -1127,43 +1011,76 @@ onMounted(() => {
|
||||
|
||||
.detail-section h4 {
|
||||
margin-bottom: 12px;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-left: 4px solid #409EFF;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/* 对话框底部 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 1200px) {
|
||||
@media screen and (max-width: 992px) {
|
||||
.stats-panel {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.stat-card:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义样式 */
|
||||
.text-muted {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
|
||||
.stat-card:last-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,91 +1,116 @@
|
||||
<template>
|
||||
<div class="discount-code-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增优惠码
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchDiscountList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增优惠码
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchDiscountList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 优惠码列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="discountList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="code" label="优惠码" min-width="150" />
|
||||
<el-table-column prop="name" label="名称" min-width="180" />
|
||||
<el-table-column label="优惠类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.percentage ? 'success' : 'primary'">
|
||||
{{ row.percentage ? '百分比折扣' : '固定金额' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优惠值" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.percentage" class="discount-value">{{ (row.percentage / 100).toFixed(0) }}%</span>
|
||||
<span v-else class="amount">¥{{ (row.amount / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最低消费" width="120">
|
||||
<template #default="{ row }">
|
||||
¥{{ (row.minAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大抵扣" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.maxAmount">¥{{ (row.maxAmount / 100).toFixed(2) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maxTimes" label="最大使用次数" width="120" />
|
||||
<el-table-column prop="userTimes" label="单用户次数" width="120" />
|
||||
<el-table-column label="可叠加" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon v-if="row.canStacking" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
|
||||
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="续费可用" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon v-if="row.renew" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
|
||||
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 优惠码列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-code"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-type"></div>
|
||||
<div class="skeleton-cell skeleton-value"></div>
|
||||
<div class="skeleton-cell skeleton-min"></div>
|
||||
<div class="skeleton-cell skeleton-max"></div>
|
||||
<div class="skeleton-cell skeleton-times"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="discountList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="code" label="优惠码" min-width="150" />
|
||||
<el-table-column prop="name" label="名称" min-width="180" />
|
||||
<el-table-column label="优惠类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.percentage ? 'success' : 'primary'">
|
||||
{{ row.percentage ? '百分比折扣' : '固定金额' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优惠值" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.percentage" class="discount-value">{{ (row.percentage / 100).toFixed(0) }}%</span>
|
||||
<span v-else class="amount">¥{{ (row.amount / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最低消费" width="120">
|
||||
<template #default="{ row }">
|
||||
¥{{ (row.minAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大抵扣" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.maxAmount">¥{{ (row.maxAmount / 100).toFixed(2) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maxTimes" label="最大使用次数" width="120" />
|
||||
<el-table-column prop="userTimes" label="单用户次数" width="120" />
|
||||
<el-table-column label="可叠加" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon v-if="row.canStacking" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
|
||||
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="续费可用" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-icon v-if="row.renew" color="#67c23a" :size="20"><SuccessFilled /></el-icon>
|
||||
<el-icon v-else color="#f56c6c" :size="20"><CircleCloseFilled /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 优惠码表单对话框 -->
|
||||
@@ -93,6 +118,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增优惠码' : '编辑优惠码'"
|
||||
width="700px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="discountFormRef"
|
||||
@@ -161,8 +187,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -489,18 +517,40 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.amount {
|
||||
@@ -516,9 +566,91 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-checkbox { width: 55px; }
|
||||
.skeleton-id { width: 80px; }
|
||||
.skeleton-code { width: 150px; }
|
||||
.skeleton-name { width: 180px; }
|
||||
.skeleton-type { width: 120px; }
|
||||
.skeleton-value { width: 120px; }
|
||||
.skeleton-min { width: 120px; }
|
||||
.skeleton-max { width: 120px; }
|
||||
.skeleton-times { width: 120px; }
|
||||
.skeleton-action { width: 200px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,108 +1,133 @@
|
||||
<template>
|
||||
<div class="discount-goods-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="代金卷">
|
||||
<el-select
|
||||
v-model="queryParams.code_id"
|
||||
placeholder="请选择代金券"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 280px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in voucherListOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品关联
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGoodsList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<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.code_id"
|
||||
placeholder="请选择代金券"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 280px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in voucherListOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品关联
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGoodsList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品关联列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="goodsList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="discountId" label="代金券ID" width="120" />
|
||||
<el-table-column label="关联对象ID" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.goodId || row.goodGroupId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ row.good?.name || row.goodGroup?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getGoodsTypeTagByRow(row)">
|
||||
{{ getGoodsTypeNameByRow(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.good?.table || row.goodGroup?.note || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.good?.price" class="price">¥{{ (row.good.price / 100).toFixed(2) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 商品关联列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-discount-id"></div>
|
||||
<div class="skeleton-cell skeleton-related-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-type"></div>
|
||||
<div class="skeleton-cell skeleton-note"></div>
|
||||
<div class="skeleton-cell skeleton-price"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="goodsList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="discountId" label="代金券ID" width="120" />
|
||||
<el-table-column label="关联对象ID" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.goodId || row.goodGroupId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ row.good?.name || row.goodGroup?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getGoodsTypeTagByRow(row)">
|
||||
{{ getGoodsTypeNameByRow(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.good?.table || row.goodGroup?.note || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.good?.price" class="price">¥{{ (row.good.price / 100).toFixed(2) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑商品关联对话框 -->
|
||||
@@ -110,6 +135,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增商品关联' : '编辑商品关联'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -198,8 +224,10 @@
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -687,27 +715,52 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
justify-content: flex-end;
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price {
|
||||
@@ -715,5 +768,91 @@ onMounted(() => {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-checkbox { width: 55px; }
|
||||
.skeleton-id { width: 80px; }
|
||||
.skeleton-discount-id { width: 120px; }
|
||||
.skeleton-related-id { width: 120px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-type { width: 120px; }
|
||||
.skeleton-note { width: 150px; }
|
||||
.skeleton-price { width: 120px; }
|
||||
.skeleton-time { width: 180px; }
|
||||
.skeleton-action { width: 200px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,92 +1,114 @@
|
||||
<template>
|
||||
<div class="discount-users-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="代金卷">
|
||||
<el-select
|
||||
v-model="queryParams.code_id"
|
||||
placeholder="请选择代金券"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 280px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in voucherListOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户关联
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchUsersList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<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.code_id"
|
||||
placeholder="请选择代金券"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 280px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in voucherListOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (¥${(item.amount / 100).toFixed(2)})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :disabled="!queryParams.code_id">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户关联
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchUsersList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户关联列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="usersList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="discountId" label="代金券ID" width="120" />
|
||||
<el-table-column label="关联对象ID" width="130">
|
||||
<template #default="{ row }">
|
||||
{{ row.userId || row.userGroupId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getUserTypeTagByRow(row)">
|
||||
{{ getUserTypeNameByRow(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 用户关联列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-discount-id"></div>
|
||||
<div class="skeleton-cell skeleton-related-id"></div>
|
||||
<div class="skeleton-cell skeleton-type"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="usersList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="discountId" label="代金券ID" width="120" />
|
||||
<el-table-column label="关联对象ID" width="130">
|
||||
<template #default="{ row }">
|
||||
{{ row.userId || row.userGroupId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getUserTypeTagByRow(row)">
|
||||
{{ getUserTypeNameByRow(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑用户关联对话框 -->
|
||||
@@ -94,6 +116,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增用户关联' : '编辑用户关联'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -173,11 +196,11 @@
|
||||
<el-form-item v-if="form.select_type === 'user'" label="用户ID" prop="user_id">
|
||||
<div class="user-selector-wrapper">
|
||||
<div class="selected-user-display" v-if="form.user_id">
|
||||
<el-tage type="primary" closable @close="clearSelectedUser">
|
||||
<el-tag type="primary" closable @close="clearSelectedUser">
|
||||
{{ getSelectedUserName() }}
|
||||
</el-tage>
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" plan @click="openUserSelector" style="width: 100%;">
|
||||
<el-button type="primary" plain @click="openUserSelector" style="width: 100%;">
|
||||
<el-icon><User /></el-icon>
|
||||
{{ form.user_id ? '重新选择用户' : '选择用户' }}
|
||||
</el-button>
|
||||
@@ -193,8 +216,10 @@
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -204,6 +229,7 @@
|
||||
title="选择用户"
|
||||
width="800px"
|
||||
class="user-selector-dialog"
|
||||
append-to-body
|
||||
>
|
||||
<!-- 搜索栏 -->
|
||||
<div class="selector-search">
|
||||
@@ -261,10 +287,12 @@
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="userSelectorVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
|
||||
确定选择
|
||||
</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="userSelectorVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
|
||||
确定选择
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -818,29 +846,100 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 用户选择器样式 */
|
||||
.user-selector-wrapper {
|
||||
width: 100%;
|
||||
@@ -879,5 +978,42 @@ onMounted(() => {
|
||||
:deep(.current-row) {
|
||||
background-color: #ecf5ff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-checkbox { width: 55px; }
|
||||
.skeleton-id { width: 80px; }
|
||||
.skeleton-discount-id { width: 120px; }
|
||||
.skeleton-related-id { width: 130px; }
|
||||
.skeleton-type { width: 120px; }
|
||||
.skeleton-time { width: 180px; }
|
||||
.skeleton-action { width: 200px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
+218
-148
@@ -1,131 +1,115 @@
|
||||
<template>
|
||||
<div class="order-list-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<!-- <el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="订单号">
|
||||
<el-input v-model="queryParams.order_no" placeholder="请输入订单号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户ID">
|
||||
<el-input v-model="queryParams.user_id" placeholder="请输入用户ID" clearable style="width: 150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 150px">
|
||||
<el-option label="待支付" value="0" />
|
||||
<el-option label="已支付" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
<el-option label="已取消" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form> -->
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增订单
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchOrderList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<!-- <el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button> -->
|
||||
<!-- <el-button type="success">
|
||||
<el-icon><Download /></el-icon>导出订单
|
||||
</el-button> -->
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增订单
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchOrderList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="orderList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="订单ID" width="100" />
|
||||
<el-table-column prop="name" label="订单名称" min-width="180" />
|
||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||
<el-table-column prop="commodityId" label="商品ID" width="100" />
|
||||
<el-table-column label="表名" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.table || '未知' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="amount">¥{{ (row.price / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="续费价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="renew-price">¥{{ (row.renewPrice / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="80">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.payNum }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.state)">
|
||||
{{ getStatusText(row.state) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付方式" width="100">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.payType || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="170">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDate(row.expireTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="170">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDate(row.CreatedAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||
<!-- <el-button type="danger" link @click="handleDelete(row)">删除</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 订单列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-user"></div>
|
||||
<div class="skeleton-cell skeleton-price"></div>
|
||||
<div class="skeleton-cell skeleton-status"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="orderList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="订单ID" width="100" />
|
||||
<el-table-column prop="name" label="订单名称" min-width="180" />
|
||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||
<el-table-column prop="commodityId" label="商品ID" width="100" />
|
||||
<el-table-column label="表名" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.table || '未知' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="amount">¥{{ (row.price / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="续费价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="renew-price">¥{{ (row.renewPrice / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="80">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.payNum }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.state)">
|
||||
{{ getStatusText(row.state) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付方式" width="100">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.payType || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="170">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDate(row.expireTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="170">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDate(row.CreatedAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 订单详情对话框 -->
|
||||
@@ -133,6 +117,7 @@
|
||||
v-model="detailDialogVisible"
|
||||
title="订单详情"
|
||||
width="800px"
|
||||
append-to-body
|
||||
>
|
||||
<el-descriptions :column="2" border v-if="orderDetail">
|
||||
<el-descriptions-item label="订单ID">{{ orderDetail.id }}</el-descriptions-item>
|
||||
@@ -162,6 +147,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增订单' : '编辑订单'"
|
||||
width="700px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="orderFormRef"
|
||||
@@ -217,8 +203,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -531,37 +519,40 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.amount {
|
||||
@@ -577,8 +568,87 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-checkbox { width: 55px; }
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-name { width: 180px; }
|
||||
.skeleton-user { width: 100px; }
|
||||
.skeleton-price { width: 120px; }
|
||||
.skeleton-status { width: 100px; }
|
||||
.skeleton-time { width: 170px; }
|
||||
.skeleton-action { width: 200px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,57 +1,77 @@
|
||||
<template>
|
||||
<div class="product-group-container">
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品分组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品分组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品分组列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="分组ID" width="100" />
|
||||
<el-table-column prop="name" label="分组名称" min-width="200" />
|
||||
<el-table-column prop="note" label="备注" min-width="250" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.disable"
|
||||
:active-value="false"
|
||||
:inactive-value="true"
|
||||
@change="(val) => handleStatusChange(row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 商品分组列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-note"></div>
|
||||
<div class="skeleton-cell skeleton-status"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="分组ID" width="100" />
|
||||
<el-table-column prop="name" label="分组名称" min-width="200" />
|
||||
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.disable"
|
||||
:active-value="false"
|
||||
:inactive-value="true"
|
||||
@change="(val) => handleStatusChange(row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品分组表单对话框 -->
|
||||
@@ -59,6 +79,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增商品分组' : '编辑商品分组'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="groupFormRef"
|
||||
@@ -80,8 +101,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -253,23 +276,121 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-note { flex: 1; min-width: 250px; }
|
||||
.skeleton-status { width: 100px; }
|
||||
.skeleton-action { width: 180px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
+216
-102
@@ -1,101 +1,123 @@
|
||||
<template>
|
||||
<div class="product-list-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="商品分组">
|
||||
<el-select v-model="queryParams.good_group_id" placeholder="请选择分组" clearable style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in groupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchProductList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 搜索和商品列表 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<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.good_group_id" placeholder="请选择分组" clearable style="width: 200px">
|
||||
<el-option
|
||||
v-for="item in groupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="success" @click="fetchProductList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="productList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="商品ID" width="100" />
|
||||
<el-table-column label="商品图片" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-image
|
||||
:src="row.image || '/logo.svg'"
|
||||
fit="cover"
|
||||
style="width: 60px; height: 60px; border-radius: 4px"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="商品名称" min-width="200" />
|
||||
<el-table-column prop="table" label="商品所属表" width="150" />
|
||||
<el-table-column label="价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="price">¥{{ (row.price / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存控制" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.inventory_control ? 'success' : 'info'">
|
||||
{{ row.inventory_control ? '已启用' : '未启用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="inventory" label="库存" width="100" />
|
||||
<el-table-column prop="payNum" label="单次数量" width="100" />
|
||||
<el-table-column label="推荐" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.recommend ? 'success' : 'info'" size="small">
|
||||
{{ row.recommend ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<!-- <el-button type="warning" link @click="handleSpec(row)">规格</el-button> -->
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 商品列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-image"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-table"></div>
|
||||
<div class="skeleton-cell skeleton-price"></div>
|
||||
<div class="skeleton-cell skeleton-tag"></div>
|
||||
<div class="skeleton-cell skeleton-inventory"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="productList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#f8f9fa', color: '#2c3e50', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="商品ID" width="100" />
|
||||
<el-table-column label="商品图片" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-image
|
||||
:src="row.image || '/logo.svg'"
|
||||
fit="cover"
|
||||
style="width: 60px; height: 60px"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="商品名称" min-width="200" />
|
||||
<el-table-column prop="table" label="商品所属表" width="150" />
|
||||
<el-table-column label="价格" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="price">¥{{ (row.price / 100).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存控制" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.inventory_control ? 'success' : 'info'">
|
||||
{{ row.inventory_control ? '已启用' : '未启用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="inventory" label="库存" width="100" />
|
||||
<el-table-column prop="payNum" label="单次数量" width="100" />
|
||||
<el-table-column label="推荐" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.recommend ? 'success' : 'info'" size="small">
|
||||
{{ row.recommend ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<!-- <el-button type="warning" link @click="handleSpec(row)">规格</el-button> -->
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品表单对话框 -->
|
||||
@@ -465,33 +487,125 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item__label) {
|
||||
margin-right: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
@media (max-width: 768px) {
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f56c6c;
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-checkbox { width: 55px; }
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-image { width: 60px; height: 60px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-table { width: 150px; }
|
||||
.skeleton-price { width: 120px; }
|
||||
.skeleton-tag { width: 100px; }
|
||||
.skeleton-inventory { width: 100px; }
|
||||
.skeleton-action { width: 200px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,94 +1,113 @@
|
||||
<template>
|
||||
<div class="product-parameter-container">
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form ref="queryFormRef" label-width="100px" :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="商品分组">
|
||||
<el-select
|
||||
v-model="queryParams.good_group_id"
|
||||
placeholder="请选择商品分组"
|
||||
clearable
|
||||
@change="handleGroupChange"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in groupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品">
|
||||
<el-select
|
||||
v-model="queryParams.good_id"
|
||||
placeholder="请先选择商品分组"
|
||||
clearable
|
||||
:disabled="!queryParams.good_group_id"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品参数
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchParameterList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form ref="queryFormRef" label-width="80px" :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="商品分组">
|
||||
<el-select
|
||||
v-model="queryParams.good_group_id"
|
||||
placeholder="请选择商品分组"
|
||||
clearable
|
||||
@change="handleGroupChange"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in groupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品">
|
||||
<el-select
|
||||
v-model="queryParams.good_id"
|
||||
placeholder="请先选择商品分组"
|
||||
clearable
|
||||
:disabled="!queryParams.good_group_id"
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in productOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增商品参数
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchParameterList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品参数列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="parameterList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="参数ID" width="100" />
|
||||
<el-table-column prop="name" label="参数名称" min-width="200" />
|
||||
<el-table-column prop="type" label="参数类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getArgTypeTag(row.type)">
|
||||
{{ getArgTypeText(row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleViewValues(row)">查看参数值</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 商品参数列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-type"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="parameterList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="参数ID" width="100" />
|
||||
<el-table-column prop="name" label="参数名称" min-width="200" />
|
||||
<el-table-column prop="type" label="参数类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getArgTypeTag(row.type)">
|
||||
{{ getArgTypeText(row.type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleViewValues(row)">查看参数值</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 商品参数表单对话框 -->
|
||||
@@ -96,6 +115,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增商品参数' : '编辑商品参数'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="parameterFormRef"
|
||||
@@ -115,8 +135,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -125,6 +147,7 @@
|
||||
v-model="valuesDialogVisible"
|
||||
title="参数值管理"
|
||||
width="800px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="values-header">
|
||||
<span>参数:{{ currentParameter?.arg_name }}</span>
|
||||
@@ -137,6 +160,7 @@
|
||||
v-loading="valuesLoading"
|
||||
:data="valuesList"
|
||||
style="width: 100%; margin-top: 20px"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="值ID" width="100" />
|
||||
<el-table-column prop="name" label="值名称" min-width="150" />
|
||||
@@ -148,8 +172,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEditValue(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDeleteValue(row)">删除</el-button>
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEditValue(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDeleteValue(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -160,6 +186,7 @@
|
||||
v-model="valueDialogVisible"
|
||||
:title="valueDialogType === 'add' ? '添加参数值' : '编辑参数值'"
|
||||
width="500px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="valueFormRef"
|
||||
@@ -178,8 +205,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="valueDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitValueForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="valueDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitValueForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -604,22 +633,52 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.values-header {
|
||||
@@ -630,8 +689,83 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-type { width: 120px; }
|
||||
.skeleton-action { width: 250px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
+146
-127
@@ -1,77 +1,76 @@
|
||||
<template>
|
||||
<div class="global-setting-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="left">
|
||||
<h2 class="title">全局设置</h2>
|
||||
<el-tag type="info" effect="plain" class="info-tag">系统全局配置管理</el-tag>
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索筛选 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="设置名称">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入设置名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限">
|
||||
<el-select v-model="queryParams.authority" placeholder="请选择权限" clearable style="width: 120px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="公有" value="0" />
|
||||
<el-option label="私有" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<el-icon><Delete /></el-icon>重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增设置
|
||||
</el-button>
|
||||
<el-button @click="handleRefresh">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="handleAdd" :icon="Plus" class="action-btn">
|
||||
新增设置
|
||||
</el-button>
|
||||
<el-button type="success" @click="handleRefresh" :icon="Refresh" class="action-btn">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索筛选 -->
|
||||
<!-- <el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="设置名称">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入设置名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限">
|
||||
<el-select v-model="queryParams.authority" placeholder="请选择权限" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="公有" value="0" />
|
||||
<el-option label="私有" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :icon="Search">查询</el-button>
|
||||
<el-button @click="resetQuery" :icon="Delete">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card> -->
|
||||
|
||||
<!-- 设置列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="settingsList"
|
||||
style="width: 100%"
|
||||
border
|
||||
stripe
|
||||
>
|
||||
<el-table-column prop="setting_id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="Name值" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="value" label="Value值" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="权限" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getAuthorityType(row.authority)" size="small">
|
||||
{{ getAuthorityText(row.authority) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="notes" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<!-- 设置列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="settingsList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="setting_id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="Name值" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="value" label="Value值" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="权限" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getAuthorityType(row.authority)" size="small">
|
||||
{{ getAuthorityText(row.authority) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="notes" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button type="primary" :icon="Edit" circle size="small" @click="handleEdit(row)" />
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button type="danger" :icon="Delete" circle size="small" @click="handleDelete(row)" />
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑设置对话框 -->
|
||||
@@ -256,13 +255,17 @@ const getAuthorityText = (authority) => {
|
||||
return authority === 0 ? '公有' : '私有'
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
queryParams.name = ''
|
||||
queryParams.authority = ''
|
||||
getList()
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
@@ -387,65 +390,83 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.global-setting-container {
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 120px);
|
||||
background-color: #f5f7fa;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 页面标题样式 */
|
||||
.page-header {
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-header .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-header .title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.info-tag {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 筛选容器 */
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 对话框底部 */
|
||||
.dialog-footer {
|
||||
@@ -455,20 +476,18 @@ onMounted(() => {
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 768px) {
|
||||
.page-header {
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
.search-form {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,67 +1,73 @@
|
||||
<template>
|
||||
<div class="domain-whitelist-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="域名">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入域名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>新增域名
|
||||
</el-button>
|
||||
|
||||
<el-button type="success" @click="getList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="域名">
|
||||
<el-input v-model="queryParams.domain" placeholder="请输入域名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增域名
|
||||
</el-button>
|
||||
<el-button type="success" @click="getList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 域名列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="domainList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="domain" label="域名" min-width="200" >
|
||||
<template #default="{ row }">
|
||||
<el-link :href="`http://${row.domain}`" target="_blank" type="primary">{{ row.domain }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="CreatedAt" label="创建时间" width="180" :formatter="parseCreatedAt" />
|
||||
<el-table-column prop="UpdatedAt" label="更新时间" width="180" :formatter="parseUpdatedAt" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 域名列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="domainList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="domain" label="域名" min-width="200" >
|
||||
<template #default="{ row }">
|
||||
<el-link :href="`http://${row.domain}`" target="_blank" type="primary">{{ row.domain }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="CreatedAt" label="创建时间" width="180" :formatter="parseCreatedAt" />
|
||||
<el-table-column prop="UpdatedAt" label="更新时间" width="180" :formatter="parseUpdatedAt" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 域名表单对话框 -->
|
||||
@@ -94,7 +100,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||
import { Plus, Delete, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { getDomainList, addDomain, deleteDomain, batchDeleteDomain } from '@/api/domain'
|
||||
|
||||
// 查询参数
|
||||
@@ -284,35 +290,93 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.domain-whitelist-container {
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 10px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 10px;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+148
-165
@@ -1,89 +1,98 @@
|
||||
<template>
|
||||
<div class="operation-log">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="操作人">
|
||||
<el-input v-model="queryParams.operator" placeholder="请输入操作人" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择操作类型" clearable>
|
||||
<el-option label="登录" value="login" />
|
||||
<el-option label="新增" value="create" />
|
||||
<el-option label="修改" value="update" />
|
||||
<el-option label="删除" value="delete" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="success">
|
||||
<el-icon><upload /></el-icon>导入
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleExport">
|
||||
<el-icon><download /></el-icon>导出
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="操作人">
|
||||
<el-input v-model="queryParams.operator" placeholder="请输入操作人" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择操作类型" clearable style="width: 160px">
|
||||
<el-option label="登录" value="login" />
|
||||
<el-option label="新增" value="create" />
|
||||
<el-option label="修改" value="update" />
|
||||
<el-option label="删除" value="delete" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="success">
|
||||
<el-icon><Upload /></el-icon>导入
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="logList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="操作人" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="operator-info">
|
||||
<el-avatar :size="32" :src="row.avatar"></el-avatar>
|
||||
<div class="operator-detail">
|
||||
<div class="username">{{ row.operator }}</div>
|
||||
<div class="ip">{{ row.ip }}</div>
|
||||
<!-- 日志列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="logList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="操作人" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="operator-info">
|
||||
<el-avatar :size="32" :src="row.avatar"></el-avatar>
|
||||
<div class="operator-detail">
|
||||
<div class="username">{{ row.operator }}</div>
|
||||
<div class="ip">{{ row.ip }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="操作类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTypeTag(row.type)">{{ getTypeText(row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="操作描述" min-width="200" />
|
||||
<el-table-column prop="createTime" label="操作时间" width="180" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="操作类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTypeTag(row.type)">{{ getTypeText(row.type) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="操作描述" min-width="200" />
|
||||
<el-table-column prop="createTime" label="操作时间" width="180" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleDetail(row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
@@ -117,7 +126,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Upload, Download } from '@element-plus/icons-vue'
|
||||
import { Upload, Download, Search } from '@element-plus/icons-vue'
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
@@ -247,57 +256,87 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f8f9fb !important;
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
height: 50px;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background: #f8fafc;
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover > td) {
|
||||
background-color: #f1f5f9 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr) {
|
||||
transition: all 0.3s ease;
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.operator-info {
|
||||
@@ -322,37 +361,6 @@ onMounted(() => {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 分页样式优化 */
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
:deep(.el-pagination) {
|
||||
--el-pagination-hover-color: #1f2937;
|
||||
}
|
||||
|
||||
:deep(.el-pagination button:disabled) {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li) {
|
||||
border-radius: 4px;
|
||||
margin: 0 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li.active) {
|
||||
background-color: #1f2937;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li:hover:not(.active)) {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -367,29 +375,4 @@ onMounted(() => {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.el-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-bar .el-button {
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,117 +1,120 @@
|
||||
<template>
|
||||
<div class="permission-admin-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="queryParams.owner_type" placeholder="请选择类型" clearable style="width: 150px" @change="handleOwnerTypeChange">
|
||||
<el-option label="用户" value="user" />
|
||||
<el-option label="组" value="group" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户" v-if="queryParams.owner_type === 'user'">
|
||||
<!-- <el-select v-model="queryParams.user_id" placeholder="请选择用户" clearable filterable style="width: 200px">
|
||||
<el-option v-for="item in userOptions" :key="item.UserId" :label="`${item.UserName} (ID: ${item.UserId})`" :value="item.UserId" />
|
||||
</el-select> -->
|
||||
|
||||
<div class="user_selector-inline">
|
||||
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
|
||||
{{ getQueryUserName() }}
|
||||
</el-tag>
|
||||
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
|
||||
<el-icon><User /></el-icon>
|
||||
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<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.owner_type" placeholder="请选择类型" clearable style="width: 150px" @change="handleOwnerTypeChange">
|
||||
<el-option label="用户" value="user" />
|
||||
<el-option label="组" value="group" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户" v-if="queryParams.owner_type === 'user'">
|
||||
<div class="user_selector-inline">
|
||||
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
|
||||
{{ getQueryUserName() }}
|
||||
</el-tag>
|
||||
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
|
||||
<el-icon><User /></el-icon>
|
||||
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="管理员组" v-if="queryParams.owner_type === 'group'">
|
||||
<el-select v-model="queryParams.admin_group_id" placeholder="请选择管理员组" clearable filterable style="width: 200px">
|
||||
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>分配权限
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchAdminPermissionList">
|
||||
<el-icon><Refresh/></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="管理员组" v-if="queryParams.owner_type === 'group'">
|
||||
<el-select v-model="queryParams.admin_group_id" placeholder="请选择管理员组" clearable filterable style="width: 200px">
|
||||
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>分配权限
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchAdminPermissionList">
|
||||
<el-icon><Refresh/></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理员权限列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="adminPermissionList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="拥有者类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.ownerType === 'user' ? 'primary' : 'success'">
|
||||
{{ row.ownerType === 'user' ? '用户' : '组' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="拥有者" width="180">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.ownerType === 'user'">用户ID: {{ row.userId }}</span>
|
||||
<span v-else>管理员组ID: {{ row.groupId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="permissionId" label="路径权限ID" width="120" />
|
||||
<el-table-column label="权限路径" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.permission?.path || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权限名称" width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.permission?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="weight" label="权重" width="100" />
|
||||
<el-table-column label="权限类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPermissionTypeTag(row.permissionType)">
|
||||
{{ getPermissionTypeText(row.permissionType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.expireAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 管理员权限列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="adminPermissionList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="拥有者类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.ownerType === 'user' ? 'primary' : 'success'">
|
||||
{{ row.ownerType === 'user' ? '用户' : '组' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="拥有者" width="180">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.ownerType === 'user'">用户ID: {{ row.userId }}</span>
|
||||
<span v-else>管理员组ID: {{ row.groupId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="permissionId" label="路径权限ID" width="120" />
|
||||
<el-table-column label="权限路径" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.permission?.path || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权限名称" width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.permission?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="weight" label="权重" width="100" />
|
||||
<el-table-column label="权限类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPermissionTypeTag(row.permissionType)">
|
||||
{{ getPermissionTypeText(row.permissionType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.expireAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</el-card>
|
||||
<!-- 用户选择弹窗 -->
|
||||
<el-dialog
|
||||
v-model="userSelectorVisible"
|
||||
@@ -745,26 +748,55 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -774,5 +806,35 @@ onMounted(() => {
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,61 +1,65 @@
|
||||
<template>
|
||||
<div class="permission-route-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词搜索" clearable style="width: 250px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增路由权限
|
||||
</el-button>
|
||||
|
||||
<el-button type="success" @click="fetchPermissionList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词搜索" clearable style="width: 250px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增路由权限
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchPermissionList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 路由权限列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="permissionList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="权限名称" min-width="200" />
|
||||
<el-table-column prop="path" label="路由路径" min-width="300" />
|
||||
<el-table-column prop="note" label="说明" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 路由权限列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="permissionList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="权限名称" min-width="200" />
|
||||
<el-table-column prop="path" label="路由路径" min-width="300" />
|
||||
<el-table-column prop="note" label="说明" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 路由权限表单对话框 -->
|
||||
@@ -91,7 +95,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Search } from '@element-plus/icons-vue'
|
||||
import { Plus, Search, Refresh } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getPermissionList,
|
||||
addPermissionInfo,
|
||||
@@ -255,27 +259,84 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+164
-99
@@ -1,98 +1,104 @@
|
||||
<template>
|
||||
<div class="setting-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="配置组">
|
||||
<el-select v-model="queryParams.group_id" placeholder="请选择配置组" clearable style="width: 200px" @change="handleQuery">
|
||||
<el-option
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
:label="group.name"
|
||||
:value="group.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增配置
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<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.group_id" placeholder="请选择配置组" clearable style="width: 200px" @change="handleQuery">
|
||||
<el-option
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
:label="group.name"
|
||||
:value="group.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增配置
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="settingList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.type === 'bool'">{{ row.value ? '是' : '否' }}</span>
|
||||
<span v-else>{{ row.value }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTypeColor(row.type)">
|
||||
{{ row.type || '未知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="settingGroupID" label="配置组" width="150" />
|
||||
<el-table-column label="是否开放" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.open"
|
||||
@change="handleToggleOpen(row)"
|
||||
:disabled="toggleLoading === row.id"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 配置列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="settingList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.type === 'bool'">{{ row.value ? '是' : '否' }}</span>
|
||||
<span v-else>{{ row.value }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTypeColor(row.type)">
|
||||
{{ row.type || '未知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="settingGroupID" label="配置组" width="150" />
|
||||
<el-table-column label="是否开放" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.open"
|
||||
@change="handleToggleOpen(row)"
|
||||
:disabled="toggleLoading === row.id"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置表单对话框 -->
|
||||
@@ -509,27 +515,86 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,70 +1,76 @@
|
||||
<template>
|
||||
<div class="setting-group-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增配置组
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增配置组
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置组列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="200" />
|
||||
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.UpdatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 配置组列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="200" />
|
||||
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.UpdatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置组表单对话框 -->
|
||||
@@ -318,27 +324,86 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+166
-102
@@ -1,97 +1,102 @@
|
||||
<template>
|
||||
<div class="system-file-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="筛选用户">
|
||||
<el-input-number v-model="queryParams.user_id" placeholder="请输入用户ID" :controls="false" clearable style="width: 150px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleUpload">
|
||||
<el-icon><Upload /></el-icon>上传文件
|
||||
</el-button>
|
||||
|
||||
<el-button type="success" @click="fetchFileList">
|
||||
<el-icon><Refresh/></el-icon>刷新
|
||||
</el-button>
|
||||
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键词筛选">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="筛选用户">
|
||||
<el-input-number v-model="queryParams.user_id" placeholder="请输入用户ID" :controls="false" clearable style="width: 150px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleUpload">
|
||||
<el-icon><Upload /></el-icon>上传文件
|
||||
</el-button>
|
||||
|
||||
<el-button type="success" @click="fetchFileList">
|
||||
<el-icon><Refresh/></el-icon>刷新
|
||||
</el-button>
|
||||
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="fileList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="realName" label="真实文件名" min-width="200" />
|
||||
<el-table-column prop="saveName" label="保存名称" min-width="150" />
|
||||
<el-table-column prop="savePath" label="保存路径" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="size" label="文件大小" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ formatFileSize(row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="文件类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getFileTypeColor(row.type)">
|
||||
{{ row.type || '未知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||
<el-table-column label="是否公开" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.openDow ? 'success' : 'info'">
|
||||
{{ row.openDow ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="success" link @click="handleDownload(row)">下载</el-button>
|
||||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 文件列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="fileList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="realName" label="真实文件名" min-width="200" />
|
||||
<el-table-column prop="saveName" label="保存名称" min-width="150" />
|
||||
<el-table-column prop="savePath" label="保存路径" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="size" label="文件大小" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ formatFileSize(row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="文件类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getFileTypeColor(row.type)">
|
||||
{{ row.type || '未知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" label="用户ID" width="100" />
|
||||
<el-table-column label="是否公开" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.openDow ? 'success' : 'info'">
|
||||
{{ row.openDow ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.CreatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||
<el-button type="success" link @click="handleDownload(row)">下载</el-button>
|
||||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 文件详情对话框 -->
|
||||
@@ -250,7 +255,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Upload, Delete, Search, Document, VideoPlay, Folder, UploadFilled, Picture } from '@element-plus/icons-vue'
|
||||
import { Upload, Delete, Search, Document, VideoPlay, Folder, UploadFilled, Picture, Refresh } from '@element-plus/icons-vue'
|
||||
import { getFileList, getFileDetail, updateFile, deleteFile, uploadFile } from '@/api/admin/file'
|
||||
|
||||
// 查询参数
|
||||
@@ -672,22 +677,56 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
@@ -700,11 +739,6 @@ onMounted(() => {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.file-detail-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
@@ -767,5 +801,35 @@ onMounted(() => {
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+173
-190
@@ -1,103 +1,112 @@
|
||||
<template>
|
||||
<div class="users-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="禁用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><plus /></el-icon>新增用户
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<el-button type="success">
|
||||
<el-icon><upload /></el-icon>导入
|
||||
</el-button>
|
||||
<el-button>
|
||||
<el-icon><download /></el-icon>导出
|
||||
</el-button>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 150px">
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="禁用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户
|
||||
</el-button>
|
||||
<el-button type="success">
|
||||
<el-icon><Upload /></el-icon>导入
|
||||
</el-button>
|
||||
<el-button type="primary" plain>
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="userList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="用户信息" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="40" :src="row.avatar"></el-avatar>
|
||||
<div class="user-detail">
|
||||
<div class="username">{{ row.username }}</div>
|
||||
<div class="email">{{ row.email }}</div>
|
||||
<!-- 用户列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="userList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="用户信息" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="40" :src="row.avatar"></el-avatar>
|
||||
<div class="user-detail">
|
||||
<div class="username">{{ row.username }}</div>
|
||||
<div class="email">{{ row.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="role" label="角色" />
|
||||
<el-table-column prop="phone" label="手机号码" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="(val) => handleStatusChange(row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="primary" link @click="handleRoleAssign(row)">分配角色</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="role" label="角色" />
|
||||
<el-table-column prop="phone" label="手机号码" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="(val) => handleStatusChange(row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="primary" link @click="handleRoleAssign(row)">分配角色</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户表单对话框 -->
|
||||
@@ -168,7 +177,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete, Upload, Download } from '@element-plus/icons-vue'
|
||||
import { Plus, Delete, Upload, Download, Search } from '@element-plus/icons-vue'
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
@@ -439,57 +448,56 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f8f9fb !important;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
height: 50px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover > td) {
|
||||
background-color: #f1f5f9 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr) {
|
||||
transition: all 0.3s ease;
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@@ -514,65 +522,40 @@ onMounted(() => {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 分页样式优化 */
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
:deep(.el-pagination) {
|
||||
--el-pagination-hover-color: #1f2937;
|
||||
}
|
||||
|
||||
:deep(.el-pagination button:disabled) {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li) {
|
||||
border-radius: 4px;
|
||||
margin: 0 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li.active) {
|
||||
background-color: #1f2937;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li:hover:not(.active)) {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.el-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-bar .el-button {
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
padding: 8px 0;
|
||||
}
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -336,9 +336,24 @@ const fetchTicketList = async (append = false) => {
|
||||
}))
|
||||
|
||||
if (append) {
|
||||
ticketList.value = [...ticketList.value, ...mappedTickets]
|
||||
// 翻页时:合并列表,使用work_id去重,然后按work_id从大到小排序
|
||||
const existingIds = new Set(ticketList.value.map(t => t.id))
|
||||
const newTickets = mappedTickets.filter(t => !existingIds.has(t.id))
|
||||
const mergedTickets = [...ticketList.value, ...newTickets]
|
||||
|
||||
// 按work_id从大到小排序
|
||||
ticketList.value = mergedTickets.sort((a, b) => {
|
||||
const idA = typeof a.id === 'string' ? parseInt(a.id) || 0 : a.id
|
||||
const idB = typeof b.id === 'string' ? parseInt(b.id) || 0 : b.id
|
||||
return idB - idA
|
||||
})
|
||||
} else {
|
||||
ticketList.value = mappedTickets
|
||||
// 首次加载或切换类别:直接使用新数据,按work_id从大到小排序
|
||||
ticketList.value = mappedTickets.sort((a, b) => {
|
||||
const idA = typeof a.id === 'string' ? parseInt(a.id) || 0 : a.id
|
||||
const idB = typeof b.id === 'string' ? parseInt(b.id) || 0 : b.id
|
||||
return idB - idA
|
||||
})
|
||||
}
|
||||
|
||||
hasMore.value = ticketList.value.length < res.data.all_count
|
||||
@@ -398,6 +413,18 @@ const fetchAllStats = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 只刷新当前分类的统计数据(用于定时刷新,减少请求)
|
||||
const fetchCurrentStatusStat = async () => {
|
||||
try {
|
||||
// 只获取当前选中分类的统计数据
|
||||
await fetchStatusStat(activeStatus.value)
|
||||
// 同时获取全部工单数量(因为顶部显示需要)
|
||||
await fetchStatusStat('')
|
||||
} catch (error) {
|
||||
console.error('获取当前分类统计数据出错:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多工单
|
||||
const loadMoreTickets = () => {
|
||||
if (!hasMore.value || isLoading.value) return
|
||||
@@ -436,16 +463,16 @@ const filteredTickets = computed(() => {
|
||||
)
|
||||
}
|
||||
|
||||
// 按最后回复时间排序(优先显示有新消息的)
|
||||
// 按work_id从大到小排序(优先显示待处理)
|
||||
return result.sort((a, b) => {
|
||||
// 优先显示待处理
|
||||
if (a.status === 'pending' && b.status !== 'pending') return -1
|
||||
if (a.status !== 'pending' && b.status === 'pending') return 1
|
||||
|
||||
// 然后按最后回复时间
|
||||
const timeA = new Date(a.lastReplyTime || a.createTime)
|
||||
const timeB = new Date(b.lastReplyTime || b.createTime)
|
||||
return timeB - timeA
|
||||
// 然后按work_id从大到小排序
|
||||
const idA = typeof a.id === 'string' ? parseInt(a.id) || 0 : a.id
|
||||
const idB = typeof b.id === 'string' ? parseInt(b.id) || 0 : b.id
|
||||
return idB - idA
|
||||
})
|
||||
})
|
||||
|
||||
@@ -685,7 +712,8 @@ const filterByStatus = (status) => {
|
||||
activeStatus.value = status
|
||||
currentPage.value = 1 // 切换状态后重置页码
|
||||
hasMore.value = true // 重置加载更多标志
|
||||
fetchTicketList() // 重新获取数据
|
||||
ticketList.value = [] // 清空列表,不缓存
|
||||
fetchTicketList(false) // 从头重新获取数据,不追加
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
@@ -775,8 +803,8 @@ const startAutoRefresh = () => {
|
||||
// 静默刷新工单列表,保持当前页码
|
||||
refreshTicketList()
|
||||
|
||||
// 刷新工单统计
|
||||
fetchAllStats()
|
||||
// 只刷新当前分类的统计数据,减少请求数量
|
||||
fetchCurrentStatusStat()
|
||||
}, refreshInterval)
|
||||
}
|
||||
|
||||
@@ -800,7 +828,8 @@ const refreshTicketList = async () => {
|
||||
else if (activeStatus.value === 'completed') statusParam = '3'
|
||||
}
|
||||
|
||||
const res = await getTickerList(pageSize.value, currentPage.value, statusParam)
|
||||
// 刷新时只获取第一页数据,用于更新最新数据
|
||||
const res = await getTickerList(pageSize.value, 1, statusParam)
|
||||
|
||||
if (res.code === 200) {
|
||||
const tickets = res.data.data || []
|
||||
@@ -817,8 +846,27 @@ const refreshTicketList = async () => {
|
||||
content: item.name
|
||||
}))
|
||||
|
||||
// 更新列表
|
||||
ticketList.value = mappedTickets
|
||||
// 合并到现有列表,去重并按work_id从大到小排序
|
||||
// 使用Map来更新已存在的工单信息,添加新的工单
|
||||
const ticketMap = new Map()
|
||||
// 先添加现有列表
|
||||
ticketList.value.forEach(ticket => {
|
||||
ticketMap.set(ticket.id, ticket)
|
||||
})
|
||||
// 更新或添加新数据(新数据会覆盖旧数据)
|
||||
mappedTickets.forEach(ticket => {
|
||||
ticketMap.set(ticket.id, ticket)
|
||||
})
|
||||
|
||||
// 转换为数组并按work_id从大到小排序
|
||||
ticketList.value = Array.from(ticketMap.values()).sort((a, b) => {
|
||||
const idA = typeof a.id === 'string' ? parseInt(a.id) || 0 : a.id
|
||||
const idB = typeof b.id === 'string' ? parseInt(b.id) || 0 : b.id
|
||||
return idB - idA
|
||||
})
|
||||
|
||||
// 不重置页码,保持用户当前的浏览位置
|
||||
// 更新加载更多标志
|
||||
hasMore.value = ticketList.value.length < res.data.all_count
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
+242
-82
@@ -1,65 +1,93 @@
|
||||
<template>
|
||||
<div class="admin-group-container">
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="搜索关键词"
|
||||
clearable
|
||||
@clear="fetchGroupList"
|
||||
@keyup.enter="fetchGroupList"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增管理员组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<div class="search-form">
|
||||
<el-input
|
||||
v-model="queryParams.key"
|
||||
placeholder="搜索关键词"
|
||||
clearable
|
||||
@clear="fetchGroupList"
|
||||
@keyup.enter="fetchGroupList"
|
||||
style="width: 200px"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增管理员组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理员组列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="组ID" width="100" />
|
||||
<el-table-column prop="name" label="组名称" min-width="200" />
|
||||
<el-table-column prop="note" label="备注" min-width="250" />
|
||||
<!-- <el-table-column prop="member_count" label="成员数量" width="120" /> -->
|
||||
<el-table-column prop="CreatedAt" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 管理员组列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-note"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="id" label="组ID" width="100" />
|
||||
<el-table-column prop="name" label="组名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<span class="group-name">{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="CreatedAt" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button type="success" link @click="handleViewMembers(row)">
|
||||
<el-icon><User /></el-icon>成员
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 管理员组表单对话框 -->
|
||||
@@ -67,6 +95,7 @@
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增管理员组' : '编辑管理员组'"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="groupFormRef"
|
||||
@@ -82,8 +111,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -92,26 +123,36 @@
|
||||
v-model="memberDialogVisible"
|
||||
title="管理员组成员"
|
||||
width="900px"
|
||||
append-to-body
|
||||
>
|
||||
<el-input
|
||||
v-model="memberParams.key"
|
||||
placeholder="搜索成员"
|
||||
clearable
|
||||
@clear="fetchMemberList"
|
||||
@keyup.enter="fetchMemberList"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="member-search">
|
||||
<el-input
|
||||
v-model="memberParams.key"
|
||||
placeholder="搜索成员"
|
||||
clearable
|
||||
@clear="fetchMemberList"
|
||||
@keyup.enter="fetchMemberList"
|
||||
style="width: 200px"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="memberLoading"
|
||||
:data="memberList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column prop="UserId" label="用户ID" width="100" />
|
||||
<el-table-column prop="UserName" label="用户名" min-width="150" />
|
||||
<el-table-column prop="UserName" label="用户名" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="user-info">
|
||||
<span class="username">{{ row.UserName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="Email" label="邮箱" min-width="200" />
|
||||
<el-table-column prop="Phone" label="手机号" width="130" />
|
||||
<el-table-column label="性别" width="80">
|
||||
@@ -146,7 +187,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { Plus, Refresh, Search, Edit, User, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getAdminGroupList,
|
||||
getAdminGroupMemberList,
|
||||
@@ -341,18 +382,137 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.member-search {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-note { flex: 1; min-width: 250px; }
|
||||
.skeleton-time { width: 180px; }
|
||||
.skeleton-action { width: 250px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1062,7 +1062,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.balance-info-card {
|
||||
@@ -1071,7 +1072,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -1449,6 +1451,45 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
+423
-1471
File diff suppressed because it is too large
Load Diff
+254
-186
@@ -1,87 +1,120 @@
|
||||
<template>
|
||||
<div class="user-group-container">
|
||||
<!-- 操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-card>
|
||||
<!-- 主容器 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户组
|
||||
</el-button>
|
||||
<el-button type="success" @click="fetchGroupList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户组列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column label="组ID" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.group_id || row.GroupId || row.id || row.Id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="组名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ row.group_name || row.name || row.Name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权限" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info">{{ row.auth || row.Auth || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="升级金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.floor_price || row.FloorPrice">¥{{ row.floor_price || row.FloorPrice }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下一级组ID" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.higher_level_id || row.HigherLevelId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="(row.fixed || row.Fixed) ? 'warning' : 'success'" size="small">
|
||||
{{ (row.fixed || row.Fixed) ? '固定' : '可升级' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成员数量" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.member_count || row.MemberCount || 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || row.CreateTime || row.CreatedAt || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
|
||||
<!-- <el-button type="warning" link @click="handleAddMember(row)">添加成员</el-button> -->
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
<!-- 用户组列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-name"></div>
|
||||
<div class="skeleton-cell skeleton-auth"></div>
|
||||
<div class="skeleton-cell skeleton-price"></div>
|
||||
<div class="skeleton-cell skeleton-level"></div>
|
||||
<div class="skeleton-cell skeleton-type"></div>
|
||||
<div class="skeleton-cell skeleton-count"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="loading"
|
||||
:data="groupList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column label="组ID" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.group_id || row.GroupId || row.id || row.Id }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="组名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<span class="group-name">{{ row.group_name || row.name || row.Name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权限" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" effect="plain">{{ row.auth || row.Auth || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="升级金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.floor_price || row.FloorPrice" class="price-text">¥{{ row.floor_price || row.FloorPrice }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下一级组ID" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.higher_level_id || row.HigherLevelId || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="(row.fixed || row.Fixed) ? 'warning' : 'success'" size="small">
|
||||
{{ (row.fixed || row.Fixed) ? '固定' : '可升级' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成员数量" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small" effect="plain">
|
||||
{{ row.member_count || row.MemberCount || 0 }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || row.CreateTime || row.CreatedAt || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button type="success" link @click="handleViewMembers(row)">
|
||||
<el-icon><User /></el-icon>成员
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.count"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户组表单对话框 -->
|
||||
@@ -90,6 +123,7 @@
|
||||
:title="dialogType === 'add' ? '新增用户组' : '编辑用户组'"
|
||||
width="650px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="groupFormRef"
|
||||
@@ -137,8 +171,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -147,11 +183,13 @@
|
||||
v-model="memberDialogVisible"
|
||||
title="用户组成员"
|
||||
width="800px"
|
||||
append-to-body
|
||||
>
|
||||
<el-table
|
||||
v-loading="memberLoading"
|
||||
:data="memberList"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column label="用户ID" width="100">
|
||||
<template #default="{ row }">
|
||||
@@ -160,7 +198,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="用户名" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.username || row.Username || row.UserName || row.name || row.Name }}
|
||||
<div class="user-info">
|
||||
<span class="username">{{ row.username || row.Username || row.UserName || row.name || row.Name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="邮箱" min-width="200">
|
||||
@@ -193,6 +233,7 @@
|
||||
v-model="addMemberDialogVisible"
|
||||
title="添加用户组成员"
|
||||
width="500px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="memberFormRef"
|
||||
@@ -205,8 +246,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addMemberDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAddMember">确定</el-button>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="addMemberDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitAddMember">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -215,7 +258,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import { Plus, Refresh, Edit, User, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getUserGroupList,
|
||||
getUserGroupMemberList,
|
||||
@@ -286,16 +329,9 @@ const memberFormRef = ref(null)
|
||||
|
||||
// 获取用户组列表
|
||||
const fetchGroupList = async () => {
|
||||
console.log('=== 获取用户组列表 ===')
|
||||
console.log('请求参数:', queryParams)
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getUserGroupList(queryParams)
|
||||
console.log('用户组列表响应:', res)
|
||||
console.log('响应状态码:', res.data?.code || res.code)
|
||||
console.log('响应数据:', res.data?.data || res.data)
|
||||
|
||||
const code = res.data?.code || res.code
|
||||
if (code === 200) {
|
||||
let responseData = res.data?.data || res.data
|
||||
@@ -303,7 +339,6 @@ const fetchGroupList = async () => {
|
||||
item.CreatedAt = formatTime(item.CreatedAt)
|
||||
})
|
||||
|
||||
// 处理不同的数据结构
|
||||
if (Array.isArray(responseData)) {
|
||||
groupList.value = responseData
|
||||
total.value = responseData.length
|
||||
@@ -317,20 +352,10 @@ const fetchGroupList = async () => {
|
||||
groupList.value = []
|
||||
total.value = 0
|
||||
}
|
||||
|
||||
console.log('用户组列表数据:', groupList.value)
|
||||
console.log('总数:', total.value)
|
||||
|
||||
if (groupList.value.length > 0) {
|
||||
console.log('第一个用户组示例:', groupList.value[0])
|
||||
}
|
||||
} else {
|
||||
console.error('获取用户组列表失败:', res.data)
|
||||
ElMessage.error(res.data?.message || '获取用户组列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户组列表错误:', error)
|
||||
console.error('错误详情:', error.response || error.message)
|
||||
ElMessage.error('获取用户组列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -339,21 +364,12 @@ const fetchGroupList = async () => {
|
||||
|
||||
// 获取成员列表
|
||||
const fetchMemberList = async () => {
|
||||
console.log('=== 获取用户组成员列表 ===')
|
||||
console.log('请求参数:', memberParams)
|
||||
|
||||
memberLoading.value = true
|
||||
try {
|
||||
const res = await getUserGroupMemberList(memberParams)
|
||||
console.log('成员列表响应:', res)
|
||||
console.log('响应状态码:', res.data?.code || res.code)
|
||||
console.log('响应数据:', res.data?.data || res.data)
|
||||
|
||||
const code = res.data?.code || res.code
|
||||
if (code === 200) {
|
||||
const responseData = res.data?.data || res.data
|
||||
|
||||
// 处理不同的数据结构
|
||||
if (Array.isArray(responseData)) {
|
||||
memberList.value = responseData
|
||||
memberTotal.value = responseData.length
|
||||
@@ -367,20 +383,10 @@ const fetchMemberList = async () => {
|
||||
memberList.value = []
|
||||
memberTotal.value = 0
|
||||
}
|
||||
|
||||
console.log('成员列表数据:', memberList.value)
|
||||
console.log('成员总数:', memberTotal.value)
|
||||
|
||||
if (memberList.value.length > 0) {
|
||||
console.log('第一个成员示例:', memberList.value[0])
|
||||
}
|
||||
} else {
|
||||
console.error('获取成员列表失败:', res.data)
|
||||
ElMessage.error(res.data?.message || '获取成员列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取成员列表错误:', error)
|
||||
console.error('错误详情:', error.response || error.message)
|
||||
ElMessage.error('获取成员列表失败')
|
||||
} finally {
|
||||
memberLoading.value = false
|
||||
@@ -410,7 +416,6 @@ const handleMemberCurrentChange = (page) => {
|
||||
|
||||
// 新增用户组
|
||||
const handleAdd = () => {
|
||||
console.log('=== 打开新增用户组对话框 ===')
|
||||
dialogType.value = 'add'
|
||||
dialogVisible.value = true
|
||||
Object.assign(groupForm, {
|
||||
@@ -426,13 +431,9 @@ const handleAdd = () => {
|
||||
|
||||
// 编辑用户组
|
||||
const handleEdit = (row) => {
|
||||
console.log('=== 打开编辑用户组对话框 ===')
|
||||
console.log('用户组数据:', row)
|
||||
|
||||
dialogType.value = 'edit'
|
||||
dialogVisible.value = true
|
||||
|
||||
// 适配不同的字段名
|
||||
const groupId = row.group_id || row.GroupId || row.id || row.Id
|
||||
const groupName = row.group_name || row.name || row.Name
|
||||
const groupAuth = row.auth || row.Auth || ''
|
||||
@@ -448,19 +449,11 @@ const handleEdit = (row) => {
|
||||
floor_price: floorPrice,
|
||||
fixed: fixed
|
||||
})
|
||||
|
||||
console.log('表单数据:', groupForm)
|
||||
}
|
||||
|
||||
// 查看成员
|
||||
const handleViewMembers = (row) => {
|
||||
// 适配不同的字段名
|
||||
const groupId = row.group_id || row.GroupId || row.id || row.Id
|
||||
|
||||
console.log('=== 查看用户组成员 ===')
|
||||
console.log('用户组数据:', row)
|
||||
console.log('组ID:', groupId)
|
||||
|
||||
memberParams.group_id = groupId
|
||||
memberParams.page = 1
|
||||
memberDialogVisible.value = true
|
||||
@@ -469,13 +462,7 @@ const handleViewMembers = (row) => {
|
||||
|
||||
// 添加成员
|
||||
const handleAddMember = (row) => {
|
||||
// 适配不同的字段名
|
||||
const groupId = row.group_id || row.GroupId || row.id || row.Id
|
||||
|
||||
console.log('=== 打开添加成员对话框 ===')
|
||||
console.log('用户组数据:', row)
|
||||
console.log('组ID:', groupId)
|
||||
|
||||
memberForm.group_id = groupId
|
||||
memberForm.user_ids = ''
|
||||
addMemberDialogVisible.value = true
|
||||
@@ -483,15 +470,9 @@ const handleAddMember = (row) => {
|
||||
|
||||
// 删除用户组
|
||||
const handleDelete = (row) => {
|
||||
// 适配不同的字段名
|
||||
const groupId = row.group_id || row.GroupId || row.id || row.Id
|
||||
const groupName = row.group_name || row.name || row.Name
|
||||
|
||||
console.log('=== 删除用户组 ===')
|
||||
console.log('用户组数据:', row)
|
||||
console.log('组ID:', groupId)
|
||||
console.log('组名称:', groupName)
|
||||
|
||||
ElMessageBox.confirm(`确认删除用户组 ${groupName} 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
@@ -499,20 +480,14 @@ const handleDelete = (row) => {
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deleteUserGroup({ group_id: groupId })
|
||||
console.log('删除响应:', res)
|
||||
console.log('响应状态码:', res.data?.code || res.code)
|
||||
|
||||
const code = res.data?.code || res.code
|
||||
if (code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
fetchGroupList()
|
||||
} else {
|
||||
console.error('删除失败:', res.data)
|
||||
ElMessage.error(res.data?.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除错误:', error)
|
||||
console.error('错误详情:', error.response || error.message)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
@@ -522,10 +497,6 @@ const handleDelete = (row) => {
|
||||
const submitForm = () => {
|
||||
groupFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
console.log('=== 提交用户组表单 ===')
|
||||
console.log('操作类型:', dialogType.value)
|
||||
console.log('表单数据:', groupForm)
|
||||
|
||||
try {
|
||||
let res
|
||||
const submitData = {
|
||||
@@ -533,40 +504,30 @@ const submitForm = () => {
|
||||
auth: groupForm.auth
|
||||
}
|
||||
|
||||
// 添加可选字段(如果有值才添加)
|
||||
if (groupForm.higher_level_id !== undefined && groupForm.higher_level_id !== null) {
|
||||
submitData.higher_level_id = groupForm.higher_level_id
|
||||
}
|
||||
if (groupForm.floor_price !== undefined && groupForm.floor_price !== null) {
|
||||
submitData.floor_price = groupForm.floor_price
|
||||
}
|
||||
// fixed 字段总是传递(boolean 值)
|
||||
submitData.fixed = groupForm.fixed
|
||||
|
||||
if (dialogType.value === 'add') {
|
||||
console.log('新增用户组,提交数据:', submitData)
|
||||
res = await createUserGroup(submitData)
|
||||
} else {
|
||||
submitData.group_id = groupForm.group_id
|
||||
console.log('更新用户组,提交数据:', submitData)
|
||||
res = await updateUserGroupInfo(submitData)
|
||||
}
|
||||
|
||||
console.log('提交响应:', res)
|
||||
console.log('响应状态码:', res.data?.code || res.code)
|
||||
|
||||
const code = res.data?.code || res.code
|
||||
if (code === 200) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
fetchGroupList()
|
||||
} else {
|
||||
console.error('操作失败:', res.data)
|
||||
ElMessage.error(res.data?.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error)
|
||||
console.error('错误详情:', error.response || error.message)
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
}
|
||||
@@ -577,26 +538,17 @@ const submitForm = () => {
|
||||
const submitAddMember = () => {
|
||||
memberFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
console.log('=== 提交添加成员 ===')
|
||||
console.log('表单数据:', memberForm)
|
||||
|
||||
try {
|
||||
const res = await addUserGroupMember(memberForm)
|
||||
console.log('添加成员响应:', res)
|
||||
console.log('响应状态码:', res.data?.code || res.code)
|
||||
|
||||
const code = res.data?.code || res.code
|
||||
if (code === 200) {
|
||||
ElMessage.success('添加成功')
|
||||
addMemberDialogVisible.value = false
|
||||
fetchGroupList()
|
||||
} else {
|
||||
console.error('添加失败:', res.data)
|
||||
ElMessage.error(res.data?.message || '添加失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加错误:', error)
|
||||
console.error('错误详情:', error.response || error.message)
|
||||
ElMessage.error('添加失败')
|
||||
}
|
||||
}
|
||||
@@ -605,7 +557,6 @@ const submitAddMember = () => {
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
console.log('=== 用户组管理页面初始化 ===')
|
||||
fetchGroupList()
|
||||
})
|
||||
</script>
|
||||
@@ -615,18 +566,135 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-id { width: 100px; }
|
||||
.skeleton-name { width: 200px; }
|
||||
.skeleton-auth { flex: 1; min-width: 200px; }
|
||||
.skeleton-price { width: 120px; }
|
||||
.skeleton-level { width: 120px; }
|
||||
.skeleton-type { width: 100px; }
|
||||
.skeleton-count { width: 100px; }
|
||||
.skeleton-time { width: 180px; }
|
||||
.skeleton-action { width: 280px; height: 32px; }
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
+284
-45
@@ -1,38 +1,68 @@
|
||||
<template>
|
||||
<div class="user-list-container">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键字">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入用户名/邮箱" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="success" @click="fetchUserList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
<!-- 搜索和用户列表 -->
|
||||
<el-card class="main-container" shadow="never">
|
||||
<!-- 搜索和操作栏 -->
|
||||
<div class="filter-section">
|
||||
<div class="filter-content">
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<el-form-item label="关键字">
|
||||
<el-input v-model="queryParams.key" placeholder="请输入用户名/邮箱" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">
|
||||
<el-icon><Search /></el-icon>查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="success" @click="fetchUserList">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增用户
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
<el-icon><Delete /></el-icon>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<el-card class="table-container" shadow="never">
|
||||
<!-- 用户列表 -->
|
||||
<div class="table-section">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="loading" class="skeleton-container">
|
||||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||||
<div class="skeleton-cell skeleton-id"></div>
|
||||
<div class="skeleton-cell skeleton-user">
|
||||
<div class="skeleton-avatar"></div>
|
||||
<div class="skeleton-text-group">
|
||||
<div class="skeleton-text skeleton-text-primary"></div>
|
||||
<div class="skeleton-text skeleton-text-secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="skeleton-cell skeleton-avatar"></div>
|
||||
<div class="skeleton-cell skeleton-phone"></div>
|
||||
<div class="skeleton-cell skeleton-realname">
|
||||
<div class="skeleton-text skeleton-text-primary"></div>
|
||||
<div class="skeleton-text skeleton-text-secondary"></div>
|
||||
</div>
|
||||
<div class="skeleton-cell skeleton-group"></div>
|
||||
<div class="skeleton-cell skeleton-status"></div>
|
||||
<div class="skeleton-cell skeleton-time"></div>
|
||||
<div class="skeleton-cell skeleton-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 数据表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
v-else
|
||||
:data="userList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="UserId" label="用户ID" width="100" />
|
||||
@@ -123,6 +153,7 @@
|
||||
background
|
||||
class="pagination"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 用户编辑对话框 -->
|
||||
@@ -894,22 +925,198 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
.main-container {
|
||||
border: 1px solid #e1e8ed;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 15px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search-form :deep(.el-form-item__label) {
|
||||
margin-right: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-radius: 8px;
|
||||
@media (max-width: 768px) {
|
||||
.filter-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.table-section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.skeleton-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skeleton-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.skeleton-checkbox {
|
||||
width: 55px;
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-id {
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-user {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-text-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 14px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-text-primary {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.skeleton-text-secondary {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.skeleton-phone {
|
||||
width: 130px;
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-realname {
|
||||
width: 150px;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-group {
|
||||
width: 120px;
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-status {
|
||||
width: 100px;
|
||||
height: 24px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-time {
|
||||
width: 180px;
|
||||
height: 20px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skeleton-action {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@@ -926,31 +1133,36 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.email {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.real-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.id-card {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #999;
|
||||
color: #95a5a6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 24px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
background: #fafbfc;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -958,12 +1170,9 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
@@ -980,13 +1189,12 @@ onMounted(() => {
|
||||
.avatar-preview {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.avatar-preview:hover {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.avatar-preview:hover .avatar-overlay {
|
||||
@@ -1006,7 +1214,7 @@ onMounted(() => {
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -1026,5 +1234,36 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: none;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 2px solid #e1e8ed;
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user