Просмотр исходного кода

feat(sdk): 新增SDK生成和IP封禁配置组件

- 添加GenerateSdk.vue组件,包含SDK生成说明、名词解释和密钥管理表格
- 添加BlockedIpSegments.vue组件,提供IP封禁设置和IP段管理功能
- 在详情页路由映射中注册新组件,替换原有的占位引用
piks 2 дней назад
Родитель
Сommit
2ef770fa31

+ 195 - 0
src/views/sdk/components/BlockedIpSegments.vue

@@ -0,0 +1,195 @@
+<template>
+  <el-card class="blocked-ip-segments" shadow="never">
+    <div class="header">
+      <h3 class="title">IP封禁说明</h3>
+    </div>
+
+    <div class="description">
+      <p>1、屏蔽数据中心</p>
+      <p class="indent">勾选后,如果来访IP属于数据中心类型,将被屏蔽,无法连接。由于IPv4的地址经常调整,任何IP库均无法保证100%的准确度。但数据中心的IP相对固定,准确度在99.5%以上,请知悉。</p>
+
+      <p>2、每个实例可以设置500组封禁IP(支持IP段)</p>
+      <p class="indent">45.45.45.45/32 (D类地址) 对应45.45.45.45 共涉及1个IP</p>
+      <p class="indent">45.45.45.0/24 (C类地址) 对应45.45.45.xx 共涉及256个IP</p>
+      <p class="indent">45.45.0.0/16 (B类地址) 对应45.45.xx.xx 共涉及65535个IP</p>
+      <p class="indent">45.0.0.0/8 (A类地址) 对应45.xx.xx.xx 共涉及1677万个IP</p>
+      <p class="indent">封禁措施:禁止该IP访问节点,并立刻阻断已建立的连接。</p>
+
+      <p>3、封禁模式</p>
+      <p class="indent">弹窗模式:被封禁IP段的用户会有明确的提示进入黑名单。 静默模式:不做任何提示。</p>
+
+      <p>4、API封禁阈值</p>
+      <p class="indent">每分钟同个IP触发封禁的数值,可通过sp_flg传递高倍值迅速提高数值。静默模式:不做任何提示。</p>
+    </div>
+
+    <div class="divider"></div>
+
+    <el-form class="setting-form" label-position="top">
+      <el-row :gutter="40">
+        <el-col :span="8">
+          <el-form-item label="数据中心">
+            <el-radio-group v-model="form.dataCenter">
+              <el-radio label="none">不屏蔽</el-radio>
+              <el-radio label="domestic">屏蔽国内</el-radio>
+              <el-radio label="foreign">屏蔽国外</el-radio>
+              <el-radio label="all">屏蔽全部</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="10">
+          <el-form-item label="API阈值">
+            <el-radio-group v-model="form.apiThreshold">
+              <el-radio label="disabled">不启用</el-radio>
+              <el-radio label="200">宽松(200)</el-radio>
+              <el-radio label="120">普通(120)</el-radio>
+              <el-radio label="90">严格I (90)</el-radio>
+              <el-radio label="60">严格II (60)</el-radio>
+              <el-radio label="30">严格III (30)</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="封禁模式">
+            <el-radio-group v-model="form.blockMode">
+              <el-radio label="popup">弹窗方式</el-radio>
+              <el-radio label="silent">静默方式</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="40" class="ip-segments-row">
+        <el-col :span="12" v-for="(item, index) in ipSegments" :key="item.id">
+          <el-form-item :label="`封禁IP段(${index + 1})`">
+            <div class="input-with-delete">
+              <el-input v-model="item.value" placeholder="请输入封禁IP段" />
+              <el-button type="danger" class="btn-delete" @click="handleRemove(index)">
+                ×
+              </el-button>
+            </div>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <div class="form-actions">
+        <el-button @click="handleAdd">添加IP段</el-button>
+        <el-button type="primary" class="is-gradient" @click="handleUpdate">更新设置</el-button>
+      </div>
+    </el-form>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+
+const form = reactive({
+  dataCenter: 'none',
+  apiThreshold: 'disabled',
+  blockMode: 'popup'
+})
+
+const ipSegments = ref([
+  { id: 1, value: '' },
+  { id: 2, value: '' }
+])
+
+const handleAdd = () => {
+  ipSegments.value.push({ id: Date.now(), value: '' })
+}
+
+const handleRemove = (index: number) => {
+  ipSegments.value.splice(index, 1)
+}
+
+const handleUpdate = () => {
+  console.log('Update settings:', form, ipSegments.value)
+}
+</script>
+
+<style lang="scss" scoped>
+.blocked-ip-segments {
+  background-color: var(--admin-card-bg);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 8px;
+  color: var(--admin-text-primary);
+
+  :deep(.el-card__body) {
+    padding: 32px;
+  }
+
+  .header {
+    margin-bottom: 24px;
+
+    .title {
+      font-size: 16px;
+      font-weight: 500;
+      margin: 0;
+      color: var(--admin-text-primary);
+    }
+  }
+
+  .description {
+    font-size: 13px;
+    color: var(--admin-text-secondary);
+    line-height: 1.8;
+
+    p {
+      margin: 0 0 4px 0;
+    }
+
+    .indent {
+      padding-left: 12px;
+      margin-bottom: 8px;
+    }
+  }
+
+  .divider {
+    height: 1px;
+    background-color: var(--admin-border-color);
+    margin: 32px 0;
+  }
+
+  .setting-form {
+    :deep(.el-form-item__label) {
+      color: var(--admin-text-secondary);
+      padding-bottom: 8px;
+      font-size: 13px;
+    }
+
+    .el-radio {
+      color: var(--admin-text-primary);
+      margin-right: 16px;
+      margin-bottom: 8px;
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+
+    .ip-segments-row {
+      margin-top: 16px;
+    }
+
+    .input-with-delete {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      width: 100%;
+
+      .btn-delete {
+        padding: 8px 12px;
+        font-size: 18px;
+        line-height: 1;
+        background-color: #f56c6c;
+        border-color: #f56c6c;
+      }
+    }
+
+    .form-actions {
+      display: flex;
+      justify-content: flex-end;
+      gap: 16px;
+      margin-top: 24px;
+    }
+  }
+}
+</style>

+ 185 - 0
src/views/sdk/components/GenerateSdk.vue

@@ -0,0 +1,185 @@
+<template>
+  <el-card class="generate-sdk" shadow="never">
+    <div class="instructions-section">
+      <el-row :gutter="40">
+        <el-col :span="12">
+          <div class="header">
+            <h3 class="title">SDK生成说明</h3>
+          </div>
+          <div class="description">
+            <p>1、每个实例可以同时生成10个SDK密钥串,用于SDK启动时候调用。</p>
+            <p class="indent">启用中:该SDK生效,可正常使用。</p>
+            <p class="indent">已停用:停用的SDK无法获得调度信息,停用超过24小时可删除。</p>
+
+            <p>2、开场动画</p>
+            <p class="indent">移动端SDK接入无开场动画,开场动画仅作为PC版开启显示。</p>
+            <p class="indent">如套餐已购买跳过开场动画,请在开启功能后生成新的SDK,将自动跳过。</p>
+
+            <p>3、端口冲突</p>
+            <p class="indent">当发生端口冲突时,SDK的处理方式选择,推荐为【提示】</p>
+
+            <p>4、调度区域</p>
+            <p class="indent">您的用户主要分布于国内,请选择国内,如果大部分分布于海外,请选择全球。</p>
+          </div>
+        </el-col>
+
+        <el-col :span="12">
+          <div class="header">
+            <h3 class="title">SDK名词解释</h3>
+          </div>
+          <div class="description">
+            <p>Q:SDK是什么:</p>
+            <p>A:SDK是软件开发工具包(Software Development Kit)</p>
+            <p class="indent">接入SDK需要有程序的源代码,比如登录器,插件辅助等等。</p>
+            <p class="indent">如果您是软件的开发者,我们推荐您通过SDK的方式接入游戏盾。</p>
+            <p class="indent">游戏盾还为开发者们保留权限推荐人功能,持续长久收益,欢迎接入。</p>
+            <p>Q:登录器生成器上有填写SDK的位置,可以生成后填写吗?</p>
+            <p>A:请与开发者确认是否该SDK属于游戏盾。</p>
+          </div>
+
+          <div class="actions-right">
+            <el-button type="primary" class="is-gradient">一键生成SDK</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <div class="divider"></div>
+
+    <el-table :data="tableData" style="width: 100%" class="sdk-table">
+      <el-table-column prop="id" label="#" width="60" />
+      <el-table-column prop="animationType" label="动画类型" width="100" />
+      <el-table-column prop="dispatchRegion" label="调度区域" width="100" />
+      <el-table-column prop="portConflict" label="端口冲突" width="100" />
+      <el-table-column prop="followExit" label="跟随退出" width="100" />
+      <el-table-column prop="generateTime" label="生成时间" width="180" />
+      <el-table-column prop="secretKey" label="密钥" show-overflow-tooltip />
+      <el-table-column label="SDK状态" width="260" fixed="right">
+        <template #default="{ row }">
+          <el-button v-if="row.status === 'enabled'" class="status-btn" size="small" @click="toggleStatus(row)">
+            启用(点击切换禁用)
+          </el-button>
+          <el-button v-else type="danger" size="small" @click="toggleStatus(row)">
+            已禁用(点击切换启用)
+          </el-button>
+          <el-button type="primary" size="small" class="is-gradient" @click="copyKey(row.secretKey)">
+            复制密钥
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const tableData = ref([
+  {
+    id: 31,
+    animationType: '静态',
+    dispatchRegion: '全球',
+    portConflict: '静默',
+    followExit: '关闭',
+    generateTime: '2026-02-25 20:34:30',
+    secretKey: 'Prrwr5SO86ft73mvPqblp/g1cjfV16eyw+PlVvhjSl3FuR+kL26qN1jN7CLz7BkxggGDES//3bl',
+    status: 'enabled'
+  }
+])
+
+const toggleStatus = (row: any) => {
+  row.status = row.status === 'enabled' ? 'disabled' : 'enabled'
+  ElMessage.success(`SDK状态已${row.status === 'enabled' ? '启用' : '禁用'}`)
+}
+
+const copyKey = (key: string) => {
+  navigator.clipboard.writeText(key).then(() => {
+    ElMessage.success('密钥已复制到剪贴板')
+  }).catch(() => {
+    ElMessage.error('复制失败')
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.generate-sdk {
+  background-color: var(--admin-card-bg);
+  border: 1px solid var(--admin-border-color);
+  border-radius: 8px;
+  color: var(--admin-text-primary);
+
+  :deep(.el-card__body) {
+    padding: 32px;
+  }
+
+  .instructions-section {
+    .header {
+      margin-bottom: 24px;
+
+      .title {
+        font-size: 16px;
+        font-weight: 500;
+        margin: 0;
+        color: var(--admin-text-primary);
+      }
+    }
+
+    .description {
+      font-size: 13px;
+      color: var(--admin-text-secondary);
+      line-height: 1.8;
+
+      p {
+        margin: 0 0 4px 0;
+      }
+
+      .indent {
+        padding-left: 12px;
+        margin-bottom: 8px;
+      }
+    }
+
+    .actions-right {
+      display: flex;
+      justify-content: flex-end;
+      margin-top: 16px;
+    }
+  }
+
+  .divider {
+    height: 1px;
+    background-color: var(--admin-border-color);
+    margin: 32px 0;
+  }
+
+  .sdk-table {
+    :deep(th.el-table__cell) {
+      background-color: transparent;
+      color: var(--admin-text-secondary);
+      border-bottom: 1px solid var(--admin-border-color);
+      font-weight: 500;
+    }
+
+    :deep(td.el-table__cell) {
+      border-bottom: 1px solid var(--admin-border-color);
+      background-color: transparent;
+    }
+
+    :deep(.el-table__inner-wrapper::before) {
+      display: none;
+    }
+
+    .status-btn {
+      background-color: #06b6d4;
+      border-color: #06b6d4;
+      color: #fff;
+
+      &:hover {
+        background-color: #0891b2;
+        border-color: #0891b2;
+      }
+    }
+  }
+}
+</style>

+ 4 - 2
src/views/sdk/detail.vue

@@ -20,6 +20,8 @@ import ConnectionList from './components/ConnectionList.vue'
 import UsageAnalysis from './components/UsageAnalysis.vue'
 import UsageAnalysis from './components/UsageAnalysis.vue'
 import MultiOpenRestriction from './components/MultiOpenRestriction.vue'
 import MultiOpenRestriction from './components/MultiOpenRestriction.vue'
 import RiskIpSegments from './components/RiskIpSegments.vue'
 import RiskIpSegments from './components/RiskIpSegments.vue'
+import BlockedIpSegments from './components/BlockedIpSegments.vue'
+import GenerateSdk from './components/GenerateSdk.vue'
 
 
 const componentMap = {
 const componentMap = {
   sdkDetail: SDKDetail,
   sdkDetail: SDKDetail,
@@ -28,8 +30,8 @@ const componentMap = {
   usageAnalysis: UsageAnalysis,
   usageAnalysis: UsageAnalysis,
   multiOpenRestriction: MultiOpenRestriction,
   multiOpenRestriction: MultiOpenRestriction,
   riskIpSegments: RiskIpSegments,
   riskIpSegments: RiskIpSegments,
-  blockedIpSegments: SDKDetail,
-  generateSdk: SDKDetail,
+  blockedIpSegments: BlockedIpSegments,
+  generateSdk: GenerateSdk,
 }
 }
 
 
 type TabKey = keyof typeof componentMap
 type TabKey = keyof typeof componentMap