Browse Source

服务器和运营工具页面开发

hjd 1 year ago
parent
commit
3280f2ad6a

+ 273 - 0
src/components/Table/ServerPropTable/index.vue

@@ -0,0 +1,273 @@
+<template>
+  <div class="zb-pro-table">
+    <div class="header">
+      <el-form :inline="true"
+               class="search-form"
+               :model="formInline"  ref="ruleFormRef" >
+        <template v-for="(item, index) in formSearchData" :key="index">
+          <el-form-item :label="item.label" v-show="isExpand ? isExpand : index < 4">
+            <template v-if="item.valueType === 'input'">
+              <el-input v-model="formInline[item.name]" :placeholder="`请输入${item.label}`" />
+            </template>
+            <template v-if="item.valueType === 'select'">
+              <el-select
+                  style="width: 100%"
+                  v-model="formInline[item.name]" :placeholder="`请选择${item.label}`">
+                <el-option
+                    v-for="ite in item.options"
+                    :key="ite.value"
+                    :label="ite.label"
+                    :value="ite.value"
+                />
+              </el-select>
+            </template>
+            <template v-if="item.valueType === 'date'">
+                <el-date-picker
+                    v-model="formInline[item.name]"
+                    type="date"
+                    value-format="YYYY-MM-DD"
+                    clearable
+                    :placeholder="`请选择${item.label}`"
+                />
+            </template>
+            <template v-if="item.valueType === 'datetime'">
+                <el-date-picker
+                    v-model="formInline[item.name]"
+                    type="datetime"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    clearable
+                    :placeholder="`请选择${item.label}`"
+                />
+            </template>
+          </el-form-item>
+        </template>
+      </el-form>
+      <div class="search">
+        <el-button type="primary" @click="onSubmit"  :icon="Search">查询</el-button>
+        <el-button type="warning" @click="onUpdate"  :icon="Edit">编辑</el-button>
+        <el-button @click="reset(ruleFormRef)">重置</el-button>
+        <el-button link type="primary" @click="isExpand = !isExpand">{{ isExpand ? '合并' : '展开'}}<el-icon>
+          <arrow-down v-if="!isExpand" />
+          <arrow-up v-else /> </el-icon
+        ></el-button>
+      </div>
+    </div>
+    <!----------底部---------------------->
+    <div class="footer">
+      <!-----------工具栏操作工具----------------->
+      <div class="operator">
+        <slot name="btn"></slot>
+      </div>
+      <!-- ------------表格--------------->
+      <div class="table">
+        <el-table
+            class="zb-table"
+            v-loading="loading"
+            @selection-change="(val) => emit('selection-change', val)"
+            :data="list"
+            :border="true"
+        >
+          <template v-for="item in columns">
+            <el-table-column
+                v-if="item.type"
+                :type="item.type"
+                :width="item.width"
+                :align="item.align!=null?item.align:'center'"
+                :fixed="item.fixed"
+                :label="item.label"
+            />
+            <el-table-column
+                v-else
+                :prop="item.name"
+                :width="item.width"
+                :align="item.align!=null?item.align:'center'"
+                :fixed="item.fixed"
+                :label="item.label"
+            >
+              <template #default="scope">
+                <span v-if="!item.slot">{{ scope.row[item.name] }}</span>
+                <slot v-else :name="item.name" :item="item" :row="scope.row"></slot>
+              </template>
+            </el-table-column>
+          </template>
+        </el-table>
+      </div>
+      <!-- ------------分页--------------->
+      <div class="pagination">
+        <el-pagination
+            v-model:currentPage="currentPage1"
+            :page-size="10"
+            background
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="data.length"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { computed, ref } from 'vue'
+import {Search, Edit } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const ruleFormRef = ref<FormInstance>()
+const emit = defineEmits(['reset', 'onSubmit', 'onUpdate', 'selection-change'])
+let props = defineProps({
+  columns: {
+    type: Array,
+    default: () => [],
+  },
+  data: {
+    type: Array,
+    default: () => [],
+  },
+  loading: {
+    type: Boolean,
+    default: false,
+  },
+})
+
+const currentPage1 = ref(1)
+// 收缩展开
+const isExpand = ref(false)
+const handleSizeChange = (val: number) => {
+  console.log(`${val} items per page`)
+}
+const handleCurrentChange = (val: number) => {
+  console.log(`current page: ${val}`)
+  currentPage1.value = val
+}
+
+const list = computed(() => {
+  let arr = JSON.parse(JSON.stringify(props.data))
+  return arr.splice((currentPage1.value - 1) * 10, 10)
+})
+
+const listLoading = ref(false)
+const confirmEdit = (row) => {
+  row.edit = false
+}
+const cancelEdit = (row) => {
+  row.edit = false
+}
+
+import { reactive } from 'vue'
+
+let obj = {}
+let search = []
+for (let item of props.columns) {
+  if (item.inSearch) {
+    obj[item.name] = null
+  }
+  if (item.inSearch) {
+    search.push(item)
+  }
+}
+const formSearchData = ref(search)
+const formInline = reactive(obj)
+
+const onSubmit = () => {
+  console.log('submit!', formInline)
+  emit('onSubmit', formInline)
+}
+
+const onUpdate = () => {
+  console.log('update!', formInline)
+  emit('onUpdate', formInline)
+}
+
+const reset = (formEl: FormInstance | undefined) => {
+  formSearchData.value.forEach((item) => {
+    formInline[item.name] = null
+  })
+  emit('reset')
+}
+const deleteAction = (row) => {
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+      .then(() => {
+        list.value = list.value.filter((item) => item.id !== row.id)
+        ElMessage.success('删除成功')
+      })
+      .catch(() => {})
+}
+</script>
+<style scoped lang="scss">
+.edit-input {
+  padding-right: 100px;
+}
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+.zb-pro-table {
+  width: 100%;
+  height: 100%;
+  display:flex;
+  flex-direction:column;
+
+  .header{
+    display: flex;
+    padding: 16px 16px 0 16px;
+    margin-bottom: 16px;
+    border-radius: 4px;
+    background: white;
+    box-shadow: 0 0 12px rgb(0 0 0 / 5%);
+    .search-form{
+      flex: 1;
+      ::v-deep{
+        .el-input--default{
+          width: 200px;
+        }
+      }
+    }
+    .search{
+      flex-shrink: 0;
+      white-space: nowrap;
+    }
+  }
+  .footer{
+    flex: 1;
+    display: flex;
+    padding: 16px;
+    flex-direction: column;
+    border-radius: 4px;
+    overflow: hidden;
+    background: white;
+    box-shadow: 0 0 12px rgb(0 0 0 / 5%);
+    min-height: 300px;
+    .operator{
+      margin-bottom: 15px
+    }
+    .table{
+      position: relative;
+      flex: 1;
+    }
+    .zb-table{
+      position: absolute;
+      height: 100%;
+    }
+  }
+  ::v-deep{
+    .el-table__header th{
+      font-size: 15px;
+      font-weight: 700;
+      color: #252525;
+    }
+  }
+  .pagination{
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    padding-top: 20px;
+    box-sizing: border-box;
+  }
+}
+</style>

+ 4 - 0
src/routers/index.ts

@@ -16,6 +16,8 @@ import othersRouter from './modules/other'
 import externalLink from './modules/externalLink'
 import formRouter from './modules/form'
 import functionPageRouter from './modules/functionPage'
+import serverRouter from './modules/server'
+import operationRouter from './modules/operation'
 
 
 // 异步组件
@@ -31,6 +33,8 @@ export const asyncRoutes = [
     ...excelRouter,
     ...externalLink,
     ...systemRouter,
+    ...serverRouter,
+    ...operationRouter,
 ]
 
 /**

+ 65 - 0
src/routers/modules/operation.ts

@@ -0,0 +1,65 @@
+
+/** When your routing operation is too long, you can split it into small modules**/
+
+import Layout from "@/layout/index.vue";
+
+const operationRouter = [{
+    path: '/operation',
+    component: Layout,
+    redirect: '/operation/giftcode',
+    name: 'operation',
+    meta: {
+        title: '运营工具',
+        icon: 'School'
+    },
+    children: [
+        {
+            path: '/operation/giftcode',
+            component: () => import('@/views/operation/giftcode/index.vue'),
+            name: 'giftcode',
+            meta: { title: '礼包码', icon: 'MenuIcon'},
+            alwaysShow:true,
+            redirect: '/views/operation/giftcode/fixedGeneration',
+            children: [
+                {
+                  path: '/views/operation/giftcode/fixedGeneration',
+                  component: () => import('@/views/operation/giftcode/fixedGeneration/index.vue'),
+                  name: 'fixedGeneration',
+                  meta: { title: '固定生成' , icon: 'MenuIcon'}
+                },
+                {
+                    path: '/views/operation/giftcode/randomGeneration',
+                    component: () => import('@/views/operation/giftcode/randomGeneration/index.vue'),
+                    name: 'randomGeneration',
+                    meta: { title: '随机生成' , icon: 'MenuIcon'}
+                  },
+                  {
+                    path: '/views/operation/giftcode/listTable',
+                    component: () => import('@/views/operation/giftcode/listTable/index.vue'),
+                    name: 'listTable',
+                    meta: { title: '信息列表' , icon: 'MenuIcon'}
+                  },
+            ]
+        },
+        {
+            path: '/operation/whitelist',
+            component: () => import('@/views/operation/whitelist/index.vue'),
+            name: 'whitelist',
+            meta: { title: '白名单', keepAlive: true , icon: 'MenuIcon'}
+        },
+        {
+            path: '/operation/sendmail',
+            component: () => import('@/views/operation/sendmail/index.vue'),
+            name: 'sendmail',
+            meta: { title: '发送邮件', keepAlive: true , icon: 'MenuIcon'}
+        },
+        {
+            path: '/operation/notice',
+            component: () => import('@/views/operation/notice/index.vue'),
+            name: 'notice',
+            meta: { title: '游戏公告', keepAlive: true  , icon: 'MenuIcon'}
+        },
+    ]
+}]
+
+export default operationRouter

+ 56 - 0
src/routers/modules/server.ts

@@ -0,0 +1,56 @@
+
+/** When your routing server is too long, you can split it into small modules**/
+
+import Layout from "@/layout/index.vue";
+
+const serverRouter = [{
+    path: '/server',
+    component: Layout,
+    redirect: '/server/gateway',
+    name: 'server',
+    meta: {
+        title: '服务器',
+        icon: 'School'
+    },
+    children: [
+        {
+            path: '/server/gateway',
+            component: () => import('@/views/server/gatewayTable/index.vue'),
+            name: 'gateway',
+            meta: { title: '网关服', keepAlive: true , icon: 'MenuIcon'}
+        },
+        {
+            path: '/server/login',
+            component: () => import('@/views/server/loginTable/index.vue'),
+            name: 'login',
+            meta: { title: '登录服', keepAlive: true , icon: 'MenuIcon'}
+        },
+        {
+            path: '/server/game',
+            component: () => import('@/views/server/gameTable/index.vue'),
+            name: 'game',
+            meta: { title: '游戏服', keepAlive: true , icon: 'MenuIcon'}
+        },
+        {
+            path: '/server/cross',
+            component: () => import('@/views/server/crossTable/index.vue'),
+            name: 'cross',
+            meta: { title: '区域服', keepAlive: true  , icon: 'MenuIcon'}
+        },
+        {
+            path: '/server/chat',
+            component: () => import('@/views/server/chatTable/index.vue'),
+            name: 'inlinechatTable',
+            meta: { title: '聊天服', keepAlive: true  , icon: 'MenuIcon'}
+        },
+   
+        // {
+        //     path: 'virtualTable',
+        //     component: () => import('@/views/table/VirtualTable.vue'),
+        //     name: 'virtualTable',
+        //     meta: { title: '虚拟表格', keepAlive: true  , icon: 'MenuIcon'}
+        // },
+    ]
+}]
+
+export default serverRouter

+ 3 - 3
src/views/home/index.scss

@@ -45,9 +45,9 @@
   }
 }
 .custom {
-  :deep(.el-card__body) {
-    padding: 0;
-  }
+  position: relative;
+  // margin-bottom: 10px;
+  width: 100%;
 }
 .item {
   margin-bottom: 18px;

+ 60 - 89
src/views/home/index.vue

@@ -1,54 +1,30 @@
 <template>
   <div class="home-container">
-    <el-row class="row-bg" :gutter="10">
-      <el-col :xs="24" :sm="12" :lg="8">
-        <el-card class="box-card" style="height: 100%">
-          <el-image
-              class="wechat"
-              :src="weLogo"
-              :preview-src-list="[weLogo]"
-              style="max-width: 200px"
-              :data-resid="Date.now()"
-          />
-          <div class="personal">
-            <div>
-              <el-avatar :size="50" :src="AvatarLogo" />
-            </div>
-            <div class="name"></div>
-            <div class="description"></div>
-            <div class="list">
-              <div>昵称:小狼</div>
-              <div>职业:前端</div>
-              <div>公司:小公司</div>
-              <div>年龄:~~</div>
-              <div>性别:男</div>
-              <div>现住址:中国-浙江-杭州</div>
-              <div>邮箱:1135957121@qq.com</div>
-              <div>微信:19550102670(欢迎加微信入群)</div>
-              <div>技术栈:JavaScript、HTML、CSS、Vue、Node、React</div>
-            </div>
-            <el-divider></el-divider>
-            <div style="margin-bottom: 15px"><h5>个性标签</h5></div>
-            <div>
-              <el-tag style="margin-right: 10px">怕麻烦</el-tag>
-              <el-tag style="margin-right: 10px">健身运动</el-tag>
-              <el-tag style="margin-right: 10px">睡觉</el-tag>
-              <el-tag style="margin-right: 10px">漫威</el-tag>
-              <el-tag>向往</el-tag>
-            </div>
-            <el-divider></el-divider>
-            <div style="margin-bottom: 15px"><h5>最喜欢的一句话</h5></div>
-            <div>---------- 开心最重要</div>
-            <el-divider></el-divider>
-            <div style="margin-bottom: 15px"
-            ><h5>如果对你有帮助的话,可以麻烦点一颗 Star、Fork、Watch! 你的鼓励是我继续优化的动力~~</h5></div
-            >
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :xs="24" :sm="12" :lg="16">
+     <el-row class="row-bg" :gutter="10">
         <el-row class="custom" :gutter="10">
-          <el-col :xs="24" :sm="12" :lg="8">
+          <el-col :xs="24" :sm="12" :lg="6">
+            <el-card style="margin-bottom: 10px">
+              <div class="grid-content">
+                <div class="left" style="background: #f25e43"
+                ><el-icon style="font-size: 24px; color: white"><Coin /></el-icon
+                ></div>
+                <div class="right">
+                  <div
+                      class="h2"
+                      style="color: #f25e43"
+                  ><count-to
+                      :start-val="0"
+                      :end-val=todayTotalIncome
+                      :duration="2000"
+                      :autoplay="true"
+                  ></count-to
+                  ></div>
+                  <div>当日总充值金额¥ </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :xs="24" :sm="12" :lg="6">
             <el-card style="margin-bottom: 10px">
               <div class="grid-content">
                 <div class="left"
@@ -60,17 +36,17 @@
                       style="color: #2d8cf0"
                   ><count-to
                       :start-val="0"
-                      :end-val="5268"
+                      :end-val=todayChargeCount
                       :duration="2000"
                       :autoplay="true"
                   ></count-to
                   ></div>
-                  <div>用户访问量 </div>
+                  <div>当日总充值人数 </div>
                 </div>
               </div>
             </el-card>
           </el-col>
-          <el-col :xs="24" :sm="12" :lg="8">
+          <el-col :xs="24" :sm="12" :lg="6">
             <el-card style="margin-bottom: 10px">
               <div class="grid-content">
                 <div class="left" style="background: #64d572"
@@ -82,34 +58,34 @@
                       style="color: #64d572"
                   ><count-to
                       :start-val="0"
-                      :end-val="9599"
+                      :end-val=todayRegisterCount
                       :duration="2000"
                       :autoplay="true"
                   ></count-to
                   ></div>
-                  <div>系统消息 </div>
+                  <div>当日总注册人数 </div>
                 </div>
               </div>
             </el-card>
           </el-col>
-          <el-col :xs="24" :sm="12" :lg="8">
+          <el-col :xs="24" :sm="12" :lg="6">
             <el-card style="margin-bottom: 10px">
               <div class="grid-content">
-                <div class="left" style="background: #f25e43"
+                <div class="left" style="background: #9269D6"
                 ><el-icon style="font-size: 24px; color: white"><user /></el-icon
                 ></div>
                 <div class="right">
                   <div
                       class="h2"
-                      style="color: #f25e43"
+                      style="color: #9269D6"
                   ><count-to
                       :start-val="0"
-                      :end-val="595453"
+                      :end-val=currOnlineCount
                       :duration="2000"
                       :autoplay="true"
                   ></count-to
                   ></div>
-                  <div>数 </div>
+                  <div>当前在线人数 </div>
                 </div>
               </div>
             </el-card>
@@ -118,58 +94,53 @@
         <el-card class="box-card">
           <template #header>
             <div class="card-header">
-              <span>系列开源产品</span>
+              <span>30分钟在线人数</span>
             </div>
           </template>
           <div style="display: flex">
-            <el-card
-                style="flex: 1; margin-right: 20px; cursor: pointer"
-                class="card-item"
-                @click="goTo('https://ext.dcloud.net.cn/plugin?id=7511')"
-            >
-              <div style="color: white; margin-bottom: 10px"><h3>zb-table</h3></div>
-              <div style="font-size: 12px; color: white"
-              >uniapp 表格组件
-                支持固定表头和首列、上拉加载更多、及固定多列,表格自适应内容,排序,多选checkbox、可点击删除,编辑、合计功能,兼容多端</div
-              >
-            </el-card>
-            <el-card
-                style="flex: 1; cursor: pointer"
-                class="card-item"
-                @click="goTo('https://github.com/zouzhibin/vue-admin-perfect')"
-            >
-              <div style="color: white; margin-bottom: 10px"><h3>vue-admin-perfect</h3></div>
-              <div style="font-size: 12px; color: white"
-              >系统基于vue3+vuex+ element-plus+ts后台管理系统</div
-              >
-            </el-card>
+            <el-card class="box-card">
+          <line-charts height="200px" width="100%" />
+        </el-card>
           </div>
         </el-card>
         <el-card class="box-card">
           <template #header>
             <div class="card-header">
-              <span>每周用户活跃量</span>
+              <span>各个服务器充值</span>
             </div>
           </template>
           <div>
             <bar-charts height="300px" width="100%" id="bar1"></bar-charts>
           </div>
         </el-card>
-      </el-col>
+
+        <el-card class="box-card">
+          <template #header>
+            <div class="card-header">
+              <span>30天活跃人数</span>
+            </div>
+          </template>
+          <div style="display: flex">
+            <el-card class="box-card">
+          <line-charts height="200px" width="100%" />
+        </el-card>
+          </div>
+        </el-card>
+      <!-- </el-col> -->
     </el-row>
   </div>
 </template>
 <script setup lang="ts">
-  import { User } from '@element-plus/icons-vue'
-  import { ref } from 'vue'
-  import AvatarLogo from '@/assets/image/avatar.png'
-  import weLogo from '@/assets/image/we.png'
+  import {ref} from 'vue'
+  import { Coin, User } from '@element-plus/icons-vue'
   import CountTo from '@/components/CountTo/index.vue'
   import BarCharts from '@/views/echarts/simple/components/bar.vue'
+  import LineCharts from '@/views/echarts/simple/components/line.vue'
+  let todayTotalIncome = ref<number>(9999)
+  let todayChargeCount = ref<number>(1200)
+  let todayRegisterCount = ref<number>(12520)
+  let currOnlineCount = ref<number>(3215)
 
-  const goTo = (url) => {
-    window.open(url, '_blank')
-  }
 </script>
 
 <style scoped lang="scss">

+ 78 - 0
src/views/home/index_bak.scss

@@ -0,0 +1,78 @@
+.personal {
+  .name {
+    margin-top: 15px;
+    font-size: 24px;
+    font-weight: 500;
+    color: rgb(38, 38, 38);
+  }
+  .description {
+    margin-top: 8px;
+  }
+  .list {
+    margin-top: 18px;
+    line-height: 30px;
+    text-align: left;
+  }
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.text {
+  font-size: 14px;
+}
+.grid-content {
+  display: flex;
+  .left {
+    width: 40%;
+    height: 100px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #2d8cf0;
+  }
+  .right {
+    display: flex;
+    flex-direction: column;
+    padding-left: 20px;
+    justify-content: center;
+    .h2{
+      font-size: 24px;
+      margin-bottom: 10px;
+    }
+  }
+}
+.custom {
+  :deep(.el-card__body) {
+    padding: 0;
+  }
+}
+.item {
+  margin-bottom: 18px;
+}
+
+.box-card {
+  //height: 100%;
+  position: relative;
+  margin-bottom: 10px;
+  width: 100%;
+  .wechat{
+    position: absolute;
+    width: 150px;
+    right: 0;
+    top: 0;
+  }
+}
+.card-item {
+  background: linear-gradient(50deg, #1890ff, #77e19d);
+}
+.home-container{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  padding: 16px;
+  box-sizing: border-box;
+  font-size: 14px;
+}

+ 177 - 0
src/views/home/index_bak.vue

@@ -0,0 +1,177 @@
+<template>
+  <div class="home-container">
+    <el-row class="row-bg" :gutter="10">
+      <el-col :xs="24" :sm="12" :lg="8">
+        <el-card class="box-card" style="height: 100%">
+          <el-image
+              class="wechat"
+              :src="weLogo"
+              :preview-src-list="[weLogo]"
+              style="max-width: 200px"
+              :data-resid="Date.now()"
+          />
+          <div class="personal">
+            <div>
+              <el-avatar :size="50" :src="AvatarLogo" />
+            </div>
+            <div class="name"></div>
+            <div class="description"></div>
+            <div class="list">
+              <div>昵称:小狼</div>
+              <div>职业:前端</div>
+              <div>公司:小公司</div>
+              <div>年龄:~~</div>
+              <div>性别:男</div>
+              <div>现住址:中国-浙江-杭州</div>
+              <div>邮箱:1135957121@qq.com</div>
+              <div>微信:19550102670(欢迎加微信入群)</div>
+              <div>技术栈:JavaScript、HTML、CSS、Vue、Node、React</div>
+            </div>
+            <el-divider></el-divider>
+            <div style="margin-bottom: 15px"><h5>个性标签</h5></div>
+            <div>
+              <el-tag style="margin-right: 10px">怕麻烦</el-tag>
+              <el-tag style="margin-right: 10px">健身运动</el-tag>
+              <el-tag style="margin-right: 10px">睡觉</el-tag>
+              <el-tag style="margin-right: 10px">漫威</el-tag>
+              <el-tag>向往</el-tag>
+            </div>
+            <el-divider></el-divider>
+            <div style="margin-bottom: 15px"><h5>最喜欢的一句话</h5></div>
+            <div>---------- 开心最重要</div>
+            <el-divider></el-divider>
+            <div style="margin-bottom: 15px"
+            ><h5>如果对你有帮助的话,可以麻烦点一颗 Star、Fork、Watch! 你的鼓励是我继续优化的动力~~</h5></div
+            >
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :xs="24" :sm="12" :lg="16">
+        <el-row class="custom" :gutter="10">
+          <el-col :xs="24" :sm="12" :lg="8">
+            <el-card style="margin-bottom: 10px">
+              <div class="grid-content">
+                <div class="left"
+                ><el-icon style="font-size: 24px; color: white"><user /></el-icon
+                ></div>
+                <div class="right">
+                  <div
+                      class="h2"
+                      style="color: #2d8cf0"
+                  ><count-to
+                      :start-val="0"
+                      :end-val="5268"
+                      :duration="2000"
+                      :autoplay="true"
+                  ></count-to
+                  ></div>
+                  <div>用户访问量 </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :xs="24" :sm="12" :lg="8">
+            <el-card style="margin-bottom: 10px">
+              <div class="grid-content">
+                <div class="left" style="background: #64d572"
+                ><el-icon style="font-size: 24px; color: white"><user /></el-icon
+                ></div>
+                <div class="right">
+                  <div
+                      class="h2"
+                      style="color: #64d572"
+                  ><count-to
+                      :start-val="0"
+                      :end-val="9599"
+                      :duration="2000"
+                      :autoplay="true"
+                  ></count-to
+                  ></div>
+                  <div>系统消息 </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :xs="24" :sm="12" :lg="8">
+            <el-card style="margin-bottom: 10px">
+              <div class="grid-content">
+                <div class="left" style="background: #f25e43"
+                ><el-icon style="font-size: 24px; color: white"><user /></el-icon
+                ></div>
+                <div class="right">
+                  <div
+                      class="h2"
+                      style="color: #f25e43"
+                  ><count-to
+                      :start-val="0"
+                      :end-val="595453"
+                      :duration="2000"
+                      :autoplay="true"
+                  ></count-to
+                  ></div>
+                  <div>数量 </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-card class="box-card">
+          <template #header>
+            <div class="card-header">
+              <span>系列开源产品</span>
+            </div>
+          </template>
+          <div style="display: flex">
+            <el-card
+                style="flex: 1; margin-right: 20px; cursor: pointer"
+                class="card-item"
+                @click="goTo('https://ext.dcloud.net.cn/plugin?id=7511')"
+            >
+              <div style="color: white; margin-bottom: 10px"><h3>zb-table</h3></div>
+              <div style="font-size: 12px; color: white"
+              >uniapp 表格组件
+                支持固定表头和首列、上拉加载更多、及固定多列,表格自适应内容,排序,多选checkbox、可点击删除,编辑、合计功能,兼容多端</div
+              >
+            </el-card>
+            <el-card
+                style="flex: 1; cursor: pointer"
+                class="card-item"
+                @click="goTo('https://github.com/zouzhibin/vue-admin-perfect')"
+            >
+              <div style="color: white; margin-bottom: 10px"><h3>vue-admin-perfect</h3></div>
+              <div style="font-size: 12px; color: white"
+              >系统基于vue3+vuex+ element-plus+ts后台管理系统</div
+              >
+            </el-card>
+          </div>
+        </el-card>
+        <el-card class="box-card">
+          <template #header>
+            <div class="card-header">
+              <span>每周用户活跃量</span>
+            </div>
+          </template>
+          <div>
+            <bar-charts height="300px" width="100%" id="bar1"></bar-charts>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script setup lang="ts">
+  import { User } from '@element-plus/icons-vue'
+  import { ref } from 'vue'
+  import AvatarLogo from '@/assets/image/avatar.png'
+  import weLogo from '@/assets/image/we.png'
+  import CountTo from '@/components/CountTo/index.vue'
+  import BarCharts from '@/views/echarts/simple/components/bar.vue'
+
+  const goTo = (url) => {
+    window.open(url, '_blank')
+  }
+</script>
+
+<style scoped lang="scss">
+@import "./index";
+</style>

+ 134 - 0
src/views/operation/giftcode/fixedGeneration/index.vue

@@ -0,0 +1,134 @@
+
+<template>
+  <PageWrapLayout>
+    <div style="max-width: 800px">
+      <el-form
+          ref="ruleFormRef"
+          :model="ruleForm"
+          :rules="rules"
+          label-width="120px"
+          class="demo-ruleForm"
+          :size="formSize"
+      >
+        <el-form-item label="礼包码" prop="code">
+          <el-input v-model="ruleForm.code" placeholder="3 到 8 个字符"/>
+        </el-form-item>
+        <el-form-item label="礼包类型" prop="codeType">
+          <el-select v-model="ruleForm.codeType" placeholder="礼包类型">
+            <el-option label="一次性" value="0" />
+            <el-option label="通用" value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="礼包道具ID" prop="itemIds">
+          <el-input v-model="ruleForm.itemIds" placeholder="道具ID列表,逗号间隔" />
+        </el-form-item>
+        <el-form-item label="礼包道具数量" prop="itemCounts">
+          <el-input v-model="ruleForm.itemCounts" placeholder="道具数量列表,逗号间隔" />
+        </el-form-item>
+        <el-form-item label="礼包生效时间" required>
+          <el-col :span="11">
+            <el-form-item prop="validTime">
+              <el-date-picker
+                  v-model="ruleForm.validTime"
+                  type="datetime"
+                  placeholder="选择开始时间"
+                  style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col class="text-center" :span="2" style="text-align: center">
+            <span class="text-gray-500">-</span>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item prop="invalidTime">
+              <el-date-picker v-model="ruleForm.invalidTime"  type="datetime" placeholder="选择结束时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="submitForm(ruleFormRef)">立即生成</el-button>
+          <el-button @click="resetForm(ruleFormRef)">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </PageWrapLayout>
+</template>
+
+<script lang="ts" setup >
+import { reactive, ref } from 'vue'
+import type { FormInstance } from 'element-plus'
+// import Upload from './components/Upload.vue'
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  code: '',
+  codeType: '',
+  validTime: '',
+  invalidTime: '',
+  giftItems: '',
+  itemIds:[],
+  itemCounts:[],
+})
+
+const rules = reactive({
+  code: [
+    {
+      required: true,
+      message: '请选择礼包码',
+      trigger: 'change',
+    },
+    { min: 3, max: 8, message: '长度在 3 到 8 个字符', trigger: 'blur' },
+  ],
+  codeType: [
+    {
+      required: true,
+      message: '请选择礼包类型',
+      trigger: 'change',
+    },
+  ],
+  validTime: [
+    {
+      type: 'date',
+      required: true,
+      message: '请选择时间',
+      trigger: 'change',
+    },
+  ],
+  invalidTime: [
+    {
+      type: 'date',
+      required: true,
+      message: '请选择时间',
+      trigger: 'change',
+    },
+  ],
+  itemIds: [
+    {
+      type: 'array',
+      required: true,
+      message: '请输入礼包道具ID',
+      trigger: 'change',
+    },
+  ],
+  itemCounts: [
+    {
+      type: 'array',
+      required: true,
+      message: '请输入礼包道具数量',
+      trigger: 'change',
+    },
+  ],
+})
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+  console.log('--FORM---', ruleForm)
+  if (!formEl) return
+
+}
+
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+}
+</script>

+ 13 - 0
src/views/operation/giftcode/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div class="app-container">
+     <div class="app-container-inner">
+       <!-- <el-alert title="礼包码" :closable="false">
+  
+       </el-alert> -->
+       <router-view  v-slot="{ Component }">
+         <component :is="Component"></component>
+       </router-view>
+     </div>
+    </div>
+  </template>
+  <script lang="ts" setup name="giftcode"></script>

+ 267 - 0
src/views/operation/giftcode/listTable/index.vue

@@ -0,0 +1,267 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:codeType="scope">{{getStateLabel(scope.row.codeType)}}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="礼包码" prop="code">
+          <el-input readonly :value="ruleForm.code" />
+        </el-form-item>
+        <el-form-item label="礼包的道具ID" prop="itemIds">
+          <el-input v-model="ruleForm.itemIds" />
+        </el-form-item>
+        <el-form-item label="礼包的道具数量" prop="itemCounts">
+          <el-input v-model="ruleForm.itemCounts" />
+        </el-form-item>
+        <el-form-item label="礼包生效时间" prop="validTime">
+          <el-date-picker
+                    v-model="ruleForm.validTime"
+                    type="datetime"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    clearable
+                />
+        </el-form-item>
+        <el-form-item label="礼包失效时间" prop="invalidTime">
+          <el-date-picker
+                    v-model="ruleForm.invalidTime"
+                    type="datetime"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    clearable
+                />
+        </el-form-item>
+
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    code: 'Code_' + i,
+    codeType: i % 2,
+    itemIds:'1001,1002',
+    itemCounts:'100,200',
+    validTime: '2023-8-18 10:00:00',
+    invalidTime: '2023-9-18 10:00:00',
+    usePlayer: '1000001',
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'code', label: '礼包码', inSearch: true, valueType: 'input', width: 80 },
+  {
+    name: 'codeType',
+    label: '礼包类型',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '一次性',
+      },
+      {
+        value: 1,
+        label: '通用',
+      },
+    ],
+    valueType: 'select',
+  },
+  { name: 'itemIds', label: '礼包道具ID'},
+  { name: 'itemCounts', label: '礼包道具数量'},
+  { name: 'validTime', label: '礼包生效时间'},
+  { name: 'invalidTime', label: '礼包失效时间'},
+  { name: 'usePlayer', label: '使用的玩家', inSearch: true, valueType: 'input'},
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const getStateLabel = (val) => {
+  if (val == 0) {
+    return '一次性'
+  } else if (val == 1) {
+    return '通用'
+  }
+}
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  weight: 0,
+  state: null,
+  openTime: null
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.weight = ruleForm.weight
+          item.state = ruleForm.state
+          item.openTime = ruleForm.openTime
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.weight, ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.weight = row.weight
+  ruleForm.state = row.state
+  ruleForm.openTime = row.openTime
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 147 - 0
src/views/operation/giftcode/randomGeneration/index.vue

@@ -0,0 +1,147 @@
+
+<template>
+  <PageWrapLayout>
+    <div style="max-width: 800px">
+      <el-form
+          ref="ruleFormRef"
+          :model="ruleForm"
+          :rules="rules"
+          label-width="120px"
+          class="demo-ruleForm"
+          :size="formSize"
+      >
+        <el-form-item label="礼包类型" prop="codeType">
+          <el-select v-model="ruleForm.codeType" placeholder="礼包类型">
+            <el-option label="一次性" value="0" />
+            <el-option label="通用" value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="礼包道具ID" prop="itemIds">
+          <el-input v-model="ruleForm.itemIds" placeholder="道具ID列表,逗号间隔" />
+        </el-form-item>
+        <el-form-item label="礼包道具数量" prop="itemCounts">
+          <el-input v-model="ruleForm.itemCounts" placeholder="道具数量列表,逗号间隔" />
+        </el-form-item>
+        <el-form-item label="礼包生效时间" required>
+          <el-col :span="11">
+            <el-form-item prop="validTime">
+              <el-date-picker
+                  v-model="ruleForm.validTime"
+                  type="datetime"
+                  placeholder="选择开始时间"
+                  style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col class="text-center" :span="2" style="text-align: center">
+            <span class="text-gray-500">-</span>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item prop="invalidTime">
+              <el-date-picker v-model="ruleForm.invalidTime"  type="datetime" placeholder="选择结束时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-form-item>
+        <el-form-item label="随机生成个数" prop="randomCount">
+          <el-input v-model="ruleForm.randomCount" placeholder="随机生成个数"/>
+        </el-form-item>
+        <el-form-item label="生成的礼包码" prop="result">
+          <el-input readonly v-model="ruleForm.result" :rows="10" type="textarea" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="submitForm(ruleFormRef)">立即生成</el-button>
+          <el-button @click="resetForm(ruleFormRef)">重置</el-button>
+          <el-button type="warning" @click="copyResult(ruleFormRef)">复制礼包码</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </PageWrapLayout>
+</template>
+
+<script lang="ts" setup >
+import { reactive, ref } from 'vue'
+import type { FormInstance } from 'element-plus'
+// import Upload from './components/Upload.vue'
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  code: '',
+  codeType: '',
+  validTime: '',
+  invalidTime: '',
+  giftItems: '',
+  itemIds:[],
+  itemCounts:[],
+  randomCount: 0,
+  result:''
+})
+
+const rules = reactive({
+  codeType: [
+    {
+      required: true,
+      message: '请选择礼包类型',
+      trigger: 'change',
+    },
+  ],
+  validTime: [
+    {
+      type: 'date',
+      required: true,
+      message: '请选择时间',
+      trigger: 'change',
+    },
+  ],
+  invalidTime: [
+    {
+      type: 'date',
+      required: true,
+      message: '请选择时间',
+      trigger: 'change',
+    },
+  ],
+  itemIds: [
+    {
+      type: 'array',
+      required: true,
+      message: '请输入礼包道具ID',
+      trigger: 'change',
+    },
+  ],
+  itemCounts: [
+    {
+      type: 'array',
+      required: true,
+      message: '请输入礼包道具数量',
+      trigger: 'change',
+    },
+  ],
+  randomCount: [
+    {
+      type: 'number',
+      required: true,
+      message: '请输入随机生成个数',
+      trigger: 'change',
+    },
+    { pattern: /^(?!0)(?:[0-9]{1,4}|10000)$/, message: '范围在1-10000',trigger: 'blur'}
+  ],
+})
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+  console.log('--FORM---', ruleForm)
+  if (!formEl) return
+
+}
+
+const copyResult = async (formEl: FormInstance | undefined) => {
+  console.log('--COPY---', ruleForm)
+  if (!formEl) return
+
+}
+
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+}
+</script>

+ 249 - 0
src/views/operation/whitelist/index.vue

@@ -0,0 +1,249 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="primary" @click="add"
+          ><el-icon><plus /></el-icon> 添加</el-button>
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:type="scope">{{ getStateLabel(scope.row.type) }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="白名单类型" prop="type">
+          <el-radio-group v-model="ruleForm.type">
+            <el-radio :label="0">IP</el-radio>
+            <el-radio :label="1">账号</el-radio>
+            <el-radio :label="2">角色</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="白名单值" prop="value">
+          <el-input v-model="ruleForm.value" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    type: i % 3,
+    value: '127.0.0.1',
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', width: 80 },
+  {
+    name: 'type',
+    label: '白名单类型',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: 'IP',
+      },
+      {
+        value: 1,
+        label: '账号',
+      },
+      {
+        value: 1,
+        label: '角色',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'value', label: '白名单值', inSearch: true, valueType: 'input'},
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const getStateLabel = (val) => {
+  if (val == 0) {
+    return 'IP'
+  } else if (val == 1) {
+    return '账号'
+  } else if (val == 2) {
+    return '角色'
+  }
+}
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  state: null,
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const add = () => {
+  title.value = '新增'
+  dialogVisible.value = true
+}
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.state = ruleForm.state
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.state = row.state
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 233 - 0
src/views/server/chatTable/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:state="scope">{{ scope.row.state ? '开启' : '关闭' }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id">
+          <el-input readonly :value="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="ruleForm.state">
+            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="1">开启</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    host: '0.0.0.0',
+    port: 3000 + i,
+    gatewayId: 1,
+    gameIds: '101,102',
+    state: i % 2 ? 1 : 0
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', inSearch: true, valueType: 'input', width: 80 },
+  { name: 'host', label: '主机'},
+  { name: 'port', label: 'UDP端口'},
+  { name: 'gatewayId', label: '连接网关服'},
+  { name: 'gameIds', label: '包含游戏服'},
+  {
+    name: 'state',
+    label: '状态',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '关闭',
+      },
+      {
+        value: 1,
+        label: '开启',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  state: null,
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.state = ruleForm.state
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.state = row.state
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 235 - 0
src/views/server/crossTable/index.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:state="scope">{{scope.row.state ? '开启' : '关闭'}}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id">
+          <el-input readonly :value="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="ruleForm.state">
+            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="1">开启</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    name: i + 1 + '区',
+    gmAddr: '127.0.0.1:3002',
+    state: i % 2,
+    gatewayId: 1,
+    gameIds: '101,102'
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', inSearch: true, valueType: 'input', width: 80 },
+  { name: 'name', label: '名称'},
+  { name: 'gmAddr', label: 'GM地址'},
+  { name: 'gatewayId', label: '连接网关服'},
+  { name: 'gameIds', label: '包含游戏服'},
+  {
+    name: 'state',
+    label: '状态',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '关闭',
+      },
+      {
+        value: 1,
+        label: '开启',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  state: null,
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.state = ruleForm.state
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.state = row.state
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 271 - 0
src/views/server/gameTable/index.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:state="scope">{{getStateLabel(scope.row.state)}}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id">
+          <el-input readonly :value="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="推荐权重" prop="weight">
+          <el-input v-model="ruleForm.weight" />
+        </el-form-item>
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="ruleForm.state">
+            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="1">开启</el-radio>
+            <el-radio :label="2">开放</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="开服时间" prop="openTime">
+          <el-date-picker
+                    v-model="ruleForm.openTime"
+                    type="datetime"
+                    value-format="YYYY-MM-DD HH:mm:ss"
+                    clearable
+                />
+
+          <!-- <el-input v-model="ruleForm.openTime" /> -->
+        </el-form-item>
+
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    name: 'S' + i,
+    gmAddr: '127.0.0.1:2002',
+    weight: 100 + i,
+    gatewayId: 1,
+    state: i % 3,
+    openTime: '2023-08-17 10:00:00'
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', inSearch: true, valueType: 'input', width: 80 },
+  { name: 'name', label: '名称'},
+  { name: 'gmAddr', label: 'GM地址'},
+  { name: 'weight', label: '推荐权重', inSearch: true, valueType: 'input'},
+  { name: 'gatewayId', label: '连接网关服'},
+  {
+    name: 'state',
+    label: '状态',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '关闭',
+      },
+      {
+        value: 1,
+        label: '开启',
+      },
+      {
+        value: 2,
+        label: '开放',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'openTime', label: '开服时间', inSearch: true, valueType: 'datetime'},
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const getStateLabel = (val) => {
+  if (val == 0) {
+    return '关闭'
+  } else if (val == 1) {
+    return '开启'
+  } else if (val == 2) {
+    return '开放'
+  }
+}
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  weight: 0,
+  state: null,
+  openTime: null
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.weight = ruleForm.weight
+          item.state = ruleForm.state
+          item.openTime = ruleForm.openTime
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.weight, ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.weight = row.weight
+  ruleForm.state = row.state
+  ruleForm.openTime = row.openTime
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 233 - 0
src/views/server/gatewayTable/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:state="scope">{{ scope.row.state ? '开启' : '关闭' }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id">
+          <el-input readonly :value="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="ruleForm.state">
+            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="1">开启</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    f_host: '0.0.0.0',
+    f_port: 2000 + i,
+    b_host: '127.0.0.1',
+    b_port: 3000 + i,
+    state: i % 2 ? 1 : 0
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', inSearch: true, valueType: 'input', width: 80 },
+  { name: 'f_host', label: '前端主机'},
+  { name: 'f_port', label: '前端端口'},
+  { name: 'b_host', label: '后端主机'},
+  { name: 'b_port', label: '后端端口'},
+  {
+    name: 'state',
+    label: '状态',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '关闭',
+      },
+      {
+        value: 1,
+        label: '开启',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  state: null,
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.state = ruleForm.state
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.state = row.state
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>

+ 225 - 0
src/views/server/loginTable/index.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <ServerPropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:state="scope">{{ scope.row.state ? '开启' : '关闭' }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </ServerPropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="ruleForm.state">
+            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="1">开启</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import * as dayjs from 'dayjs'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+const loading = ref(true)
+const appContainer = ref(null)
+import ServerPropTable from '@/components/Table/ServerPropTable/index.vue'
+const data = []
+for (let i = 0; i < 100; i++) {
+  data.push({
+    id: i + 1,
+    gatewayId: 1,
+    gameIds: '101,102',
+    state: i % 2 ? 1 : 0
+  })
+}
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', inSearch: true, valueType: 'input', width: 80 },
+  { name: 'gatewayId', label: '连接网关服'},
+  { name: 'gameIds', label: '包含游戏服'},
+  {
+    name: 'state',
+    label: '状态',
+    slot: true,
+    inSearch: true,
+    options: [
+      {
+        value: 0,
+        label: '关闭',
+      },
+      {
+        value: 1,
+        label: '开启',
+      }
+    ],
+    valueType: 'select',
+  },
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = reactive({
+  state: null,
+})
+
+const rules = reactive({
+  state: [
+    {
+      required: true,
+      message: '请选择状态',
+      trigger: 'change',
+    },
+  ],
+})
+
+const dialogVisible = ref(false)
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('编辑')
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      list.value.forEach((item) => {
+        if (item.id === rowObj.value.id) {
+          item.state = ruleForm.state
+        }
+      })
+      dialogVisible.value = false
+      console.log('submit!', ruleForm.state)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      ElMessage.success('模拟删除成功')
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.state = row.state
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      list.value = list.value.filter((item) => item.id !== row.id)
+      ElMessage.success('删除成功')
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('触发重置方法')
+}
+
+const onSubmit = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发查询方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const onUpdate = (val) => {
+  console.log('val===', val)
+  ElMessage.success('触发修改方法')
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+</style>