This commit is contained in:
qaq
2025-06-26 14:59:03 +08:00
parent bafe5dbbf2
commit f940addacb
10 changed files with 1217 additions and 194 deletions
+2 -2
View File
@@ -24,7 +24,7 @@ export default {
return new Promise(function(resolve, reject) {
axios.get(path, {
params: data,
timeout: 12000,
timeout: 30000,
headers: {
"Authorization": window.localStorage.getItem('token')
}
@@ -53,7 +53,7 @@ export default {
post: function(path = '', data = {}) {
return new Promise(function(resolve, reject) {
axios.post(path, data, {
timeout: 12000,
timeout: 30000,
headers:{
"Authorization": window.localStorage.getItem('token'),
"Content-Type": "application/json"
+129
View File
@@ -0,0 +1,129 @@
<template>
<div class="unified-empty-state" :class="{ 'small': size === 'small', 'large': size === 'large' }">
<el-empty
:description="description"
:image-size="computedImageSize"
:image="image"
>
<template #description>
<p class="empty-description">{{ description }}</p>
</template>
<slot></slot>
</el-empty>
</div>
</template>
<script>
export default {
name: 'EmptyState',
props: {
description: {
type: String,
default: '暂无数据'
},
size: {
type: String,
default: 'default', // 'small', 'default', 'large'
validator: value => ['small', 'default', 'large'].includes(value)
},
image: {
type: String,
default: undefined
},
imageSize: {
type: Number,
default: undefined
}
},
computed: {
computedImageSize() {
if (this.imageSize) return this.imageSize;
switch (this.size) {
case 'small':
return 80;
case 'large':
return 120;
default:
return 100;
}
}
}
}
</script>
<style scoped>
.unified-empty-state {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
padding: 20px;
width: 100%;
}
.unified-empty-state.small {
min-height: 150px;
padding: 15px;
}
.unified-empty-state.large {
min-height: 250px;
padding: 30px;
}
.empty-description {
color: #909399;
font-size: 14px;
margin: 0;
line-height: 1.4;
}
/* 针对网格布局的支持 */
.unified-empty-state {
grid-column: 1 / -1;
}
/* 响应式设计 */
@media (max-width: 768px) {
.unified-empty-state {
min-height: 150px;
padding: 15px 10px;
}
.unified-empty-state.small {
min-height: 120px;
padding: 10px;
}
.unified-empty-state.large {
min-height: 180px;
padding: 20px 15px;
}
.empty-description {
font-size: 13px;
}
}
@media (max-width: 480px) {
.unified-empty-state {
min-height: 120px;
padding: 10px 8px;
}
.unified-empty-state.small {
min-height: 100px;
padding: 8px;
}
.unified-empty-state.large {
min-height: 150px;
padding: 15px 10px;
}
.empty-description {
font-size: 12px;
}
}
</style>
+10 -15
View File
@@ -20,9 +20,10 @@
<div class="table-container">
<div v-loading="loading" class="cards-container">
<!-- 空状态 -->
<div v-if="forwardList.length === 0 && !loading" class="empty-state" style="margin-top: 10px;">
<el-empty description="暂无转发配置"></el-empty>
</div>
<EmptyState
v-if="forwardList.length === 0 && !loading"
description="暂无转发配置"
/>
<div
v-for="forward in forwardList"
@@ -324,9 +325,13 @@ import {
resumeForwardService
} from "@/api";
import { copyWithMessage } from "@/utils/clipboard";
import EmptyState from "@/components/EmptyState.vue";
export default {
name: "Forward",
components: {
EmptyState
},
data() {
return {
loading: false,
@@ -1313,13 +1318,7 @@ export default {
}
}
.empty-state {
grid-column: 1 / -1;
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
/* 空状态使用统一组件,样式已内置 */
.flow-stats-mini {
display: flex;
@@ -1554,11 +1553,7 @@ export default {
max-width: 100%;
}
/* 移动端空状态调整 */
.empty-state {
padding: 20px 15px;
height: 150px;
}
/* 移动端空状态样式已在统一组件中处理 */
/* 移动端form hint */
.form-hint {
+30 -8
View File
@@ -88,8 +88,12 @@
<div class="tunnels-section">
<h3 class="section-title">隧道权限</h3>
<div v-loading="loading" class="tunnels-grid">
<div v-if="userTunnels.length === 0 && !loading" class="empty-state">
<el-empty description="暂无隧道权限"></el-empty>
<!-- 空状态也使用卡片容器,保持样式统一 -->
<div v-if="userTunnels.length === 0 && !loading" class="tunnel-card empty-card">
<EmptyState
description="暂无隧道权限"
size="small"
/>
</div>
<div
@@ -177,9 +181,12 @@
<div class="forwards-section">
<h3 class="section-title">转发详情</h3>
<div v-loading="loading" class="forwards-container">
<div v-if="groupedForwards.length === 0 && !loading" class="empty-state">
<el-empty description="暂无转发配置"></el-empty>
</div>
<!-- 空状态保持在容器内,与有数据时样式统一 -->
<EmptyState
v-if="groupedForwards.length === 0 && !loading"
description="暂无转发配置"
size="small"
/>
<div v-for="group in groupedForwards" :key="group.tunnelName" class="forward-group">
<div class="group-header">
@@ -267,9 +274,13 @@
<script>
import { getUserPackageInfo } from "@/api";
import EmptyState from "@/components/EmptyState.vue";
export default {
name: "Index",
components: {
EmptyState
},
data() {
return {
refreshLoading: false,
@@ -1071,14 +1082,19 @@ export default {
font-weight: 600;
}
.empty-state {
/* 隧道权限空状态卡片样式 */
.tunnel-card.empty-card {
grid-column: 1 / -1;
justify-self: center;
max-width: 400px;
min-height: 160px;
display: flex;
justify-content: center;
align-items: center;
height: 200px;
justify-content: center;
}
/* 转发详情的空状态样式由EmptyState组件内置处理 */
/* 响应式设计 */
@media (max-width: 768px) {
.package-container {
@@ -1155,6 +1171,12 @@ export default {
margin-bottom: 8px;
}
.tunnel-card.empty-card {
max-width: 100%;
min-height: 140px;
margin-bottom: 12px;
}
.tunnel-header {
flex-direction: column;
align-items: flex-start;
+9 -12
View File
@@ -143,11 +143,10 @@
</div>
<!-- 空状态 -->
<div v-if="!loading && filteredSpeedLimitList.length === 0" style="margin-top: 10px;">
<el-empty description="暂无限速规则">
</el-empty>
</div>
<EmptyState
v-if="!loading && filteredSpeedLimitList.length === 0"
description="暂无限速规则"
/>
<!-- 新增/编辑对话框 -->
<el-dialog
@@ -220,9 +219,13 @@
<script>
import { createSpeedLimit, getSpeedLimitList, updateSpeedLimit, deleteSpeedLimit, getTunnelList } from "@/api";
import EmptyState from "@/components/EmptyState.vue";
export default {
name: "Limit",
components: {
EmptyState
},
data() {
return {
speedLimitList: [],
@@ -617,13 +620,7 @@ export default {
flex: 1;
}
.empty-state {
text-align: center;
padding: 60px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}
/* 空状态使用统一组件,样式已内置 */
.form-hint {
font-size: 12px;
File diff suppressed because it is too large Load Diff
+12 -6
View File
@@ -103,9 +103,10 @@
<!-- 移动端卡片视图 -->
<div class="mobile-view" v-loading="loading">
<div v-if="userList.length === 0 && !loading" class="empty-state">
<el-empty description="暂无用户数据"></el-empty>
</div>
<EmptyState
v-if="userList.length === 0 && !loading"
description="暂无用户数据"
/>
<div v-for="user in userList" :key="user.id" class="user-card">
<!-- 卡片头部 -->
@@ -532,9 +533,10 @@
<!-- 移动端卡片视图 -->
<div class="mobile-view" v-loading="tunnelListLoading">
<div v-if="userTunnelList.length === 0 && !tunnelListLoading" class="empty-state">
<el-empty description="暂无隧道权限" :image-size="100"></el-empty>
</div>
<EmptyState
v-if="userTunnelList.length === 0 && !tunnelListLoading"
description="暂无隧道权限"
/>
<div v-for="tunnel in userTunnelList" :key="tunnel.id" class="tunnel-card">
<!-- 卡片头部 -->
@@ -717,9 +719,13 @@
<script>
import { getAllUsers, createUser, updateUser, deleteUser, getTunnelList, assignUserTunnel, getUserTunnelList, removeUserTunnel, updateUserTunnelFlow, updateUserTunnel, getSpeedLimitList } from "@/api";
import EmptyState from "@/components/EmptyState.vue";
export default {
name: "User",
components: {
EmptyState
},
data() {
return {
userList: [],
+8 -26
View File
@@ -144,14 +144,10 @@
</div>
<!-- 空状态 -->
<div v-if="!loading && nodeList.length === 0" class="empty-state">
<i class="el-icon-box"></i>
<p>暂无节点数据</p>
<el-button type="primary" size="small" @click="handleAdd">
<i class="el-icon-plus"></i>
新增第一个节点
</el-button>
</div>
<EmptyState
v-if="!loading && nodeList.length === 0"
description="暂无节点数据"
/>
</div>
<!-- 对话框保持不变 -->
@@ -315,6 +311,7 @@
import {createNode, updateNode, deleteNode, getNodeList, getNodeInstallCommand} from "@/api";
import { copyWithMessage, copyWithFallback } from "@/utils/clipboard";
import VChart from 'vue-echarts';
import EmptyState from "@/components/EmptyState.vue";
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { GaugeChart } from 'echarts/charts';
@@ -330,7 +327,8 @@ use([
export default {
name: "Node",
components: {
VChart
VChart,
EmptyState
},
data() {
return {
@@ -1285,23 +1283,7 @@ export default {
padding-top: 12px;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #909399;
}
.empty-state i {
font-size: 48px;
margin-bottom: 15px;
opacity: 0.5;
}
.empty-state p {
font-size: 14px;
margin-bottom: 15px;
}
/* 空状态使用统一组件,样式已内置 */
/* 响应式设计 */
@media (max-width: 768px) {