Ver Fonte

feat(sdk详情页): 实现SDK详情页组件及动态标签切换功能

添加ElProgress组件类型声明
新增主题样式变量--admin-header-form-bg
重构detail.vue使用动态组件加载
创建SDKDetail组件展示详情信息
piks há 5 dias atrás
pai
commit
75fc345b74
4 ficheiros alterados com 469 adições e 19 exclusões
  1. 1 0
      components.d.ts
  2. 2 0
      src/styles/theme.scss
  3. 427 0
      src/views/sdk/components/SDKDetail.vue
  4. 39 19
      src/views/sdk/detail.vue

+ 1 - 0
components.d.ts

@@ -34,6 +34,7 @@ declare module 'vue' {
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
     ElRadio: typeof import('element-plus/es')['ElRadio']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']

+ 2 - 0
src/styles/theme.scss

@@ -28,6 +28,7 @@
   --admin-sidebar-bg: #ffffff;
   --admin-header-bg: #ffffff;
   --admin-card-bg: #ffffff;
+  --admin-header-form-bg: #554fa1;
   --admin-text-primary: #303133;
   --admin-text-secondary: #909399;
   --admin-border-color: #e4e7ed;
@@ -62,6 +63,7 @@ html.dark {
   --admin-bg: #050505;
   --admin-sidebar-bg: #050505;
   --admin-header-bg: #050505;
+  --admin-header-form-bg: #554fa1;
   --admin-card-bg: #1d1e1f;
   --admin-text-primary: #e5eaf3;
   --admin-text-secondary: #a3a6ad;

+ 427 - 0
src/views/sdk/components/SDKDetail.vue

@@ -0,0 +1,427 @@
+<template>
+  <div class="sdk-detail-container">
+    <!-- SDK详情 -->
+    <div class="panel">
+      <div class="panel-header">SDK详情</div>
+      <div class="panel-body">
+        <el-form :model="sdkForm" class="sdk-form" label-width="auto" label-position="left" @submit.prevent>
+          <div class="grid-row">
+            <div class="grid-item">
+              <span class="label">实例唯一标识:</span>
+              <span class="value">{{ sdkData.id }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">实例名称:</span>
+              <span class="value">{{ sdkData.name }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">实例类型:</span>
+              <span class="value">{{ sdkData.type }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">实例状态:</span>
+              <span class="value">{{ sdkData.status }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">无心跳通讯:</span>
+              <span class="value">{{ sdkData.noHeartbeat }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">无数据超时时间:</span>
+              <span class="value">{{ sdkData.noDataTimeout }}</span>
+            </div>
+            <div class="grid-item form-item">
+              <el-form-item label="是否启用LTS:">
+                <el-switch v-model="sdkForm.enableLTS" @change="handleSdkFormChange" />
+              </el-form-item>
+            </div>
+            <div class="grid-item form-item">
+              <el-form-item label="断线自动重连:">
+                <el-switch v-model="sdkForm.autoReconnect" @change="handleSdkFormChange" />
+              </el-form-item>
+            </div>
+            <div class="grid-item">
+              <span class="label">当前在线设备:</span>
+              <span class="value">{{ sdkData.onlineDevices }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">传流量汇总:</span>
+              <span class="value">{{ sdkData.trafficSum }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">日下载流量汇总:</span>
+              <span class="value">{{ sdkData.downloadTrafficSum }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">中发连接历史峰值:</span>
+              <span class="value">{{ sdkData.concurrentHistoryPeak }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">上传速率历史峰值:</span>
+              <span class="value">{{ sdkData.uploadSpeedPeak }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">下载速率历史峰值:</span>
+              <span class="value">{{ sdkData.downloadSpeedPeak }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">创建时间:</span>
+              <span class="value">{{ sdkData.createTime }}</span>
+            </div>
+            <div class="grid-item">
+              <span class="label">到期时间:</span>
+              <span class="value">{{ sdkData.expireTime }}</span>
+            </div>
+            <div class="grid-item form-item">
+              <el-form-item label="限制单连接流量每秒NKbps:">
+                <el-input v-model="sdkForm.limitConnTraffic" @blur="handleSdkFormChange" />
+              </el-form-item>
+            </div>
+            <div class="grid-item form-item">
+              <el-form-item label="限制单连接每秒发送包数:">
+                <el-input v-model="sdkForm.limitConnPackets" @blur="handleSdkFormChange" />
+              </el-form-item>
+            </div>
+            <div class="grid-item">
+              <span class="label">套餐:</span>
+              <span class="value">{{ sdkData.package }}</span>
+            </div>
+            <div class="grid-item form-item">
+              <el-form-item label="回环IP:">
+                <el-input v-model="sdkForm.loopbackIp" @blur="handleSdkFormChange" />
+              </el-form-item>
+            </div>
+          </div>
+        </el-form>
+      </div>
+    </div>
+
+    <!-- 套餐详情 -->
+    <div class="panel">
+      <div class="panel-header">套餐详情</div>
+      <div class="panel-body">
+        <div class="grid-row package-grid">
+          <div class="grid-item">
+            <span class="label">应用服务器数:</span>
+            <span class="value">{{ packageData.appServers }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">转发规则数:</span>
+            <span class="value">{{ packageData.forwardRules }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">设备数:</span>
+            <span class="value">{{ packageData.devices }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">并发数:</span>
+            <span class="value">{{ packageData.concurrent }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">带宽(M):</span>
+            <span class="value">{{ packageData.bandwidth }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">防护IP数:</span>
+            <span class="value">{{ packageData.protectIps }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">防护机房:</span>
+            <span class="value">{{ packageData.protectRooms }}</span>
+          </div>
+          <div class="grid-item">
+            <span class="label">切换时间(毫秒):</span>
+            <span class="value">{{ packageData.switchTime }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 主节点规则 -->
+    <div class="panel">
+      <div class="panel-header">主节点规则</div>
+      <div class="panel-body">
+        <el-form :model="mainNodeForm" label-position="top">
+          <div v-for="(item, index) in mainNodeForm.rules" :key="item.id || index" class="rule-row">
+            <el-form-item label="分组节点" class="node-select">
+              <el-select v-model="item.groupNode" placeholder="请选择分组节点">
+                <el-option label="香港" value="香港" />
+                <el-option label="新加坡" value="新加坡" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="IP个数" class="node-select">
+              <el-select v-model="item.ipCount" placeholder="请选择IP个数">
+                <el-option label="1" :value="1" />
+                <el-option label="2" :value="2" />
+                <el-option label="3" :value="3" />
+              </el-select>
+            </el-form-item>
+            <div class="rule-action">
+              <el-button type="danger" @click="removeMainNodeRule(index)">
+                <el-icon>
+                  <Close />
+                </el-icon>
+              </el-button>
+            </div>
+          </div>
+          <div class="panel-actions">
+            <el-button type="primary" class="purple-btn" @click="addMainNodeRule">追加</el-button>
+            <el-button type="info" class="gray-btn" @click="saveMainNodeRules">保存更改</el-button>
+          </div>
+        </el-form>
+      </div>
+    </div>
+
+    <!-- 备用节点规则 -->
+    <div class="panel">
+      <div class="panel-header">备用节点规则</div>
+      <div class="panel-body">
+        <el-form :model="backupNodeForm" label-position="top">
+          <div v-for="(item, index) in backupNodeForm.rules" :key="item.id || index" class="rule-row">
+            <el-form-item label="分组节点" class="node-select">
+              <el-select v-model="item.groupNode" placeholder="请选择分组节点">
+                <el-option label="香港" value="香港" />
+                <el-option label="新加坡" value="新加坡" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="IP个数" class="node-select">
+              <el-select v-model="item.ipCount" placeholder="请选择IP个数">
+                <el-option label="1" :value="1" />
+                <el-option label="2" :value="2" />
+                <el-option label="3" :value="3" />
+              </el-select>
+            </el-form-item>
+            <div class="rule-action">
+              <el-button type="danger" @click="removeBackupNodeRule(index)">
+                <el-icon>
+                  <Close />
+                </el-icon>
+              </el-button>
+            </div>
+          </div>
+          <div class="panel-actions">
+            <el-button type="primary" class="purple-btn" @click="addBackupNodeRule">追加</el-button>
+            <el-button type="info" class="gray-btn" @click="saveBackupNodeRules">保存更改</el-button>
+          </div>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue'
+import { Close } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+// Mock Data for SDK Details
+const sdkData = reactive({
+  id: '7d8d1415-b3744597-8827-e1663bed6c',
+  name: 'socke服务',
+  type: 'socket',
+  status: '正常',
+  noHeartbeat: 0,
+  noDataTimeout: 0,
+  onlineDevices: 0,
+  trafficSum: 0,
+  downloadTrafficSum: '0B',
+  concurrentHistoryPeak: 18,
+  uploadSpeedPeak: '5.36Mbps/秒',
+  downloadSpeedPeak: '6.75Kbps/秒',
+  createTime: '2026-02-03 17:05:23',
+  expireTime: '2025-10-08 15:17:36',
+  package: '定制版'
+})
+
+// Form Data for SDK Details (editable fields)
+const sdkForm = reactive({
+  enableLTS: false,
+  autoReconnect: false,
+  limitConnTraffic: '',
+  limitConnPackets: '',
+  loopbackIp: ''
+})
+
+const handleSdkFormChange = () => {
+  // Trigger API request here
+  console.log('SDK Form changed, triggering save request:', sdkForm)
+  ElMessage.success('SDK详情已自动保存')
+}
+
+// Mock Data for Package Details
+const packageData = reactive({
+  appServers: 1,
+  forwardRules: 1,
+  devices: 1,
+  concurrent: 1,
+  bandwidth: 1,
+  protectIps: 1,
+  protectRooms: '1个',
+  switchTime: ''
+})
+
+// Main Node Rules
+const mainNodeForm = reactive({
+  rules: [
+    { id: Date.now(), groupNode: '香港', ipCount: 2 }
+  ]
+})
+
+const addMainNodeRule = () => {
+  mainNodeForm.rules.push({ id: Date.now(), groupNode: '', ipCount: null as any })
+}
+
+const removeMainNodeRule = (index: number) => {
+  mainNodeForm.rules.splice(index, 1)
+}
+
+const saveMainNodeRules = () => {
+  console.log('Save main node rules:', mainNodeForm.rules)
+  ElMessage.success('主节点规则保存成功')
+}
+
+// Backup Node Rules
+const backupNodeForm = reactive({
+  rules: [
+    { id: Date.now() + 1, groupNode: '香港', ipCount: 2 }
+  ]
+})
+
+const addBackupNodeRule = () => {
+  backupNodeForm.rules.push({ id: Date.now(), groupNode: '', ipCount: null as any })
+}
+
+const removeBackupNodeRule = (index: number) => {
+  backupNodeForm.rules.splice(index, 1)
+}
+
+const saveBackupNodeRules = () => {
+  console.log('Save backup node rules:', backupNodeForm.rules)
+  ElMessage.success('备用节点规则保存成功')
+}
+</script>
+
+<style lang="scss" scoped>
+.sdk-detail-container {
+  color: var(--admin-text-primary);
+  padding-top: 20px;
+  background-color: var(--admin-bg);
+}
+
+.panel {
+  background-color: var(--admin-card-bg);
+  border-radius: 8px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  border: 1px solid var(--admin-border-color);
+}
+
+.panel-header {
+  background-color: var(--admin-header-form-bg);
+  color: #fff;
+  padding: 12px 20px;
+  font-size: 16px;
+  font-weight: 500;
+}
+
+.panel-body {
+  padding: 20px;
+}
+
+.grid-row {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 24px 16px;
+}
+
+.grid-item {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  min-height: 32px;
+
+  .label {
+    color: var(--admin-text-secondary);
+    margin-right: 8px;
+    white-space: nowrap;
+  }
+
+  .value {
+    color: var(--admin-text-primary);
+  }
+}
+
+.form-item {
+  .el-form-item {
+    margin-bottom: 0;
+    width: 100%;
+
+    :deep(.el-form-item__label) {
+      color: var(--admin-text-secondary);
+      padding-right: 8px;
+      line-height: 32px;
+      height: 32px;
+    }
+
+    :deep(.el-form-item__content) {
+      line-height: 32px;
+    }
+  }
+
+  .el-input {
+    width: 140px;
+  }
+}
+
+.rule-row {
+  display: flex;
+  align-items: flex-end;
+  gap: 20px;
+  margin-bottom: 20px;
+
+  .node-select {
+    margin-bottom: 0;
+    flex: 1;
+    max-width: 48%;
+
+    :deep(.el-form-item__label) {
+      color: var(--admin-text-secondary);
+      padding-bottom: 8px;
+      line-height: normal;
+    }
+  }
+
+  .rule-action {
+    padding-bottom: 0px;
+  }
+}
+
+.panel-actions {
+  margin-top: 20px;
+  display: flex;
+  gap: 12px;
+}
+
+.purple-btn {
+  background: var(--btn-primary-gradient);
+  border: none;
+  color: white;
+
+  &:hover {
+    background: var(--btn-primary-gradient-hover);
+    box-shadow: 0 4px 12px var(--btn-primary-shadow);
+  }
+}
+
+.gray-btn {
+  background-color: var(--admin-table-header-bg);
+  border-color: var(--admin-border-color);
+  color: var(--admin-text-primary);
+
+  &:hover {
+    background-color: var(--admin-border-color);
+    border-color: var(--admin-text-secondary);
+    color: var(--admin-text-primary);
+  }
+}
+</style>

+ 39 - 19
src/views/sdk/detail.vue

@@ -1,28 +1,48 @@
 <template>
-	<Breadcrumb />
-	<TabFilter v-model="activeTab" :options="options" @change="handleTabChange">
-		<template #actions>
-			<el-button :icon="ArrowLeft" type="primary" class="is-gradient">返回</el-button>
-		</template>
-	</TabFilter>
-
+  <Breadcrumb />
+  <TabFilter v-model="activeTab" :options="options" @change="handleTabChange">
+    <template #actions>
+      <el-button :icon="ArrowLeft" type="primary" class="is-gradient">返回</el-button>
+    </template>
+  </TabFilter>
+  <component :is="currentComponent" />
 </template>
 <script setup lang="ts">
 import { ArrowLeft } from '@element-plus/icons-vue'
-const activeTab = ref('all')
-const options = [
-	{ label: 'SDK详情', value: 'all' },
-	{ label: '转发规则', value: 'normal' },
-	{ label: '连接清单', value: 'expiring' },
-	{ label: '用量分析', value: 'disabled' },
-	{ label: '限制多开', value: 'disabled' },
-	{ label: '风险IP段', value: 'disabled' },
-	{ label: '封禁IP段', value: 'disabled' },
-	{ label: '生成SDK', value: 'disabled' },
+import { ref, computed } from 'vue'
+import SDKDetail from './components/SDKDetail.vue'
+type TabKey = keyof typeof componentMap
+
+const activeTab = ref<TabKey>('sdkDetail')
+const options: Array<{ label: string; value: TabKey }> = [
+  { label: 'SDK详情', value: 'sdkDetail' },
+  { label: '转发规则', value: 'forwardRules' },
+  { label: '连接清单', value: 'connectionList' },
+  { label: '用量分析', value: 'usageAnalysis' },
+  { label: '限制多开', value: 'multiOpenRestriction' },
+  { label: '风险IP段', value: 'riskIpSegments' },
+  { label: '封禁IP段', value: 'blockedIpSegments' },
+  { label: '生成SDK', value: 'generateSdk' },
 ]
 
-function handleTabChange(value: string | number) {
-	console.log('切换到:', value)
+// 组件映射对象
+const componentMap = {
+  sdkDetail: SDKDetail,
+  forwardRules: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  connectionList: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  usageAnalysis: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  multiOpenRestriction: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  riskIpSegments: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  blockedIpSegments: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+  generateSdk: SDKDetail, // 临时映射到SDKDetail,后续可替换为实际组件
+}
+
+// 计算当前要显示的组件
+const currentComponent = computed(() => {
+  return componentMap[activeTab.value] || SDKDetail
+})
+
+function handleTabChange(value: TabKey) {
 }
 
 </script>