Forráskód Böngészése

封装Table组件

fxs 9 hónapja
szülő
commit
0cc059a944

+ 1 - 1
auto-imports.d.ts

@@ -5,5 +5,5 @@
 // Generated by unplugin-auto-import
 export {}
 declare global {
-
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
 }

+ 13 - 8
components.d.ts

@@ -9,24 +9,29 @@ declare module 'vue' {
   export interface GlobalComponents {
     ElAffix: typeof import('element-plus/es')['ElAffix']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
+    ElInput: typeof import('element-plus/es')['ElInput']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
-    HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
-    IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
-    IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
-    IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     IconEpHistogram: typeof import('~icons/ep/histogram')['default']
     IconEpPieChart: typeof import('~icons/ep/pie-chart')['default']
-    IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
-    IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     Table: typeof import('./src/components/Table.vue')['default']
-    TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
-    WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
   }
 }

+ 209 - 1
package-lock.json

@@ -9,9 +9,12 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.1",
+        "@types/crypto-js": "^4.2.2",
         "axios": "^1.7.4",
+        "crypto-js": "^4.2.0",
         "echarts": "^5.5.1",
         "element-plus": "^2.8.0",
+        "less": "^4.2.0",
         "pinia": "^2.1.7",
         "vue": "^3.4.29",
         "vue-router": "^4.3.3"
@@ -1079,6 +1082,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/crypto-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.2.2.tgz",
+      "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
+      "license": "MIT"
+    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
@@ -1932,6 +1941,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/copy-anything": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz",
+      "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-what": "^3.14.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mesqueeb"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1947,6 +1968,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+      "license": "MIT"
+    },
     "node_modules/cssesc": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
@@ -2093,6 +2120,19 @@
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
     },
+    "node_modules/errno": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz",
+      "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "prr": "~1.0.1"
+      },
+      "bin": {
+        "errno": "cli.js"
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.21.5",
       "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
@@ -2720,6 +2760,13 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "license": "ISC",
+      "optional": true
+    },
     "node_modules/graphemer": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
@@ -2757,6 +2804,19 @@
         "node": ">=10.17.0"
       }
     },
+    "node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/ignore": {
       "version": "5.3.2",
       "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@@ -2767,6 +2827,19 @@
         "node": ">= 4"
       }
     },
+    "node_modules/image-size": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz",
+      "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "image-size": "bin/image-size.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2882,6 +2955,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-what": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz",
+      "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
+      "license": "MIT"
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
@@ -2957,6 +3036,32 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/less": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz",
+      "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "copy-anything": "^2.0.1",
+        "parse-node-version": "^1.0.1",
+        "tslib": "^2.3.0"
+      },
+      "bin": {
+        "lessc": "bin/lessc"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "optionalDependencies": {
+        "errno": "^0.1.1",
+        "graceful-fs": "^4.1.2",
+        "image-size": "~0.5.0",
+        "make-dir": "^2.1.0",
+        "mime": "^1.4.1",
+        "needle": "^3.1.0",
+        "source-map": "~0.6.0"
+      }
+    },
     "node_modules/levn": {
       "version": "0.4.1",
       "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
@@ -3043,6 +3148,30 @@
         "@jridgewell/sourcemap-codec": "^1.5.0"
       }
     },
+    "node_modules/make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/make-dir/node_modules/semver": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
+      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+      "license": "ISC",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
     "node_modules/memoize-one": {
       "version": "6.0.0",
       "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
@@ -3089,6 +3218,19 @@
         "node": ">=8.6"
       }
     },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
@@ -3188,6 +3330,23 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/needle": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
+      "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "iconv-lite": "^0.6.3",
+        "sax": "^1.2.4"
+      },
+      "bin": {
+        "needle": "bin/needle"
+      },
+      "engines": {
+        "node": ">= 4.4.x"
+      }
+    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -3368,6 +3527,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse-node-version": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
+      "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/path-browserify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -3454,6 +3622,16 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/pinia": {
       "version": "2.2.2",
       "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.2.2.tgz",
@@ -3605,6 +3783,13 @@
       "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
       "license": "MIT"
     },
+    "node_modules/prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/punycode": {
       "version": "2.3.1",
       "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
@@ -3761,6 +3946,20 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/sax": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
+      "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+      "license": "ISC",
+      "optional": true
+    },
     "node_modules/scule": {
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
@@ -3831,6 +4030,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "license": "BSD-3-Clause",
+      "optional": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/source-map-js": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz",
@@ -3975,7 +4184,6 @@
       "version": "2.6.3",
       "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.3.tgz",
       "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
-      "dev": true,
       "license": "0BSD"
     },
     "node_modules/type-check": {

+ 3 - 0
package.json

@@ -14,9 +14,12 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@types/crypto-js": "^4.2.2",
     "axios": "^1.7.4",
+    "crypto-js": "^4.2.0",
     "echarts": "^5.5.1",
     "element-plus": "^2.8.0",
+    "less": "^4.2.0",
     "pinia": "^2.1.7",
     "vue": "^3.4.29",
     "vue-router": "^4.3.3"

+ 5 - 169
src/App.vue

@@ -1,175 +1,11 @@
 <script setup lang="ts">
-import { RouterView } from 'vue-router'
-import { ref } from 'vue'
-import { fi } from 'element-plus/es/locales.mjs'
-
-const isCollapse = ref(false)
-const menuList = [
-  {
-    title: '数据总览',
-    icon: 'PieChart',
-    children: [
-      {
-        pathName: 'OverView',
-        title: '工作台'
-      }
-    ]
-  },
-  {
-    title: '信息管理',
-    icon: 'Histogram',
-    children: [
-      {
-        pathName: 'GameManageView',
-        title: '游戏管理'
-      },
-      {
-        pathName: 'PlayerManageView',
-        title: '玩家管理'
-      }
-    ]
-  }
-]
-
-const changeCollapse = () => {
-  isCollapse.value = !isCollapse.value
-}
+import { zhCn } from 'element-plus/es/locales.mjs'
 </script>
 
 <template>
-  <div class="body">
-    <div class="sideBarBox">
-      <el-menu :router="true" :default-active="$route.name" class="sideBar" :collapse="isCollapse">
-        <el-menu-item index="/" class="logoBox">
-          <el-image :fit="'fill'" class="logoImg" src="/src/assets/logo.svg"></el-image>
-          <template #title><span class="logoText">淳皓科技</span></template>
-        </el-menu-item>
-        <el-sub-menu v-for="item in menuList" :index="item.title">
-          <template #title>
-            <el-icon><component :is="item.icon" /></el-icon>
-            <span class="menuTitle">{{ item.title }}</span>
-          </template>
-          <el-menu-item v-for="v in item.children" :index="v.pathName">{{ v.title }}</el-menu-item>
-        </el-sub-menu>
-        <div class="sideBarFold" @click="changeCollapse">
-          <el-icon :size="25"><Fold /></el-icon>
-        </div>
-      </el-menu>
-    </div>
-    <div class="navBarBox">
-      <div class="headPortraitBox">
-        <!-- <el-avatar :size="40" src="/src/assets/default/defaultHead.png" /> -->
-        <el-image class="headPortrait" src="/src/assets/default/defaultHead.png"></el-image>
-      </div>
-    </div>
-    <div class="content">
-      <RouterView />
-    </div>
-  </div>
+  <el-config-provider :locale="zhCn">
+    <RouterView />
+  </el-config-provider>
 </template>
 
-<style scoped>
-.body {
-  width: 100%;
-  display: flex;
-  height: 100vh;
-}
-
-/* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
-.sideBarBox {
-  position: relative;
-  /* width: 12%; */
-  z-index: 2;
-  height: 100vh;
-  top: 0;
-}
-
-.sideBar {
-  /* width: 12vw; */
-  height: 100vh;
-  position: relative;
-  overflow: scroll;
-}
-
-/* 设置弹出层的样式 */
-.el-popper > .logoText {
-  width: 100px;
-  font-size: 16px;
-  /* color: red; */
-}
-
-/* 调整LOGO */
-.logoBox {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.logoImg {
-  display: flex;
-  align-items: center;
-  width: 33px;
-  /* margin-right: 20px; */
-  /* height: 50px; */
-}
-
-.logoText {
-  width: 80%;
-  height: 100%;
-  margin-left: 15%;
-  display: flex;
-  font-size: 18px;
-  align-items: center;
-  /* background-color: lightcoral; */
-}
-
-/* 主要用来调整整个menu的宽度 */
-.menuTitle {
-  margin-right: 40px;
-}
-
-.sideBarFold {
-  width: 5%;
-  height: 3%;
-  position: absolute;
-  right: 40px;
-  bottom: 20px;
-}
-
-.navBarBox {
-  position: fixed;
-  width: 100vw;
-  z-index: 1;
-  height: 7vh;
-  top: 0;
-  background-color: white;
-  right: 0;
-  border-bottom: 1px solid gainsboro;
-}
-
-.headPortraitBox {
-  /* width: 5vw; */
-  /* height: 5vh; */
-  position: absolute;
-  right: 3%;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-.headPortrait {
-  cursor: pointer;
-  width: 50px;
-}
-
-.content {
-  /* flex-grow: 1; */
-  /* position: absolute; */
-
-  width: 100%;
-  height: 100%;
-  overflow: scroll;
-  background-color: lightcoral;
-  right: 0vw;
-  top: 0vh;
-}
-</style>
+<style scoped></style>

+ 4 - 0
src/assets/base.css

@@ -23,3 +23,7 @@ img {
   margin: 0;
   padding: 0;
 }
+
+.el-button + .el-button {
+  margin-left: 0 !important;
+}

+ 264 - 5
src/components/Table.vue

@@ -1,17 +1,276 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import type { PropsParams, TablePaginationSetting, QueryInfo } from '@/types/table'
+import { FilterType } from '@/types/table'
+
+import { computed, onMounted, reactive, ref } from 'vue'
+import { useTable } from '@/hooks/useTable'
+
+// 表格工具图标大小
+const toolsIconSize = ref(25)
+
+// 传过来的配置
+const props = defineProps<PropsParams>()
+
+const emits = defineEmits(['addNewItem'])
+
+// 表格数据
+const tableData: Array<any> = reactive([])
+
+// 查询表单的数据
+const queryFormData = reactive<any>({})
+
+// 一些公用方法
+const { getTableData } = useTable(tableData, props.paginationConfig)
+
+// 查询操作
+const queryTable = () => {}
+
+// 所有类型为input的表单控件信息
+const inputFieldsList = computed(() => {
+  return props.queryInfo?.filter((item) => item.type === FilterType.INPUT)
+})
+
+// 所有类型为select的表单控件信息
+const selectFieldsList = computed(() => {
+  return props.queryInfo?.filter((item) => item.type === FilterType.SELECT)
+})
+
+// 所有类型为date的表单控件信息
+const dateFieldsList = computed(() => {
+  return props.queryInfo?.filter((item) => item.type === FilterType.DATE)
+})
+
+// 改变页码
+const handleCurrentChange = (val: number) => {
+  props.paginationConfig.currentPage = val
+}
+
+// 改变每页大小
+const handleSizeChange = (val: number) => {
+  props.paginationConfig.limit = val
+}
+
+const getData = () => {
+  getTableData(props.requestConfig.url, props.requestConfig.other)
+}
+
+// 定义暴露出去的方法
+defineExpose({
+  getData
+})
+
+onMounted(() => {
+  getData()
+})
 </script>
 
 <template>
   <div class="tableContent">
-    <div class="filterBox">
+    <div class="filterBox" v-if="isOpenQuery">
       <!-- slot -->
+      <div class="filterHeader">
+        <span>查询条件</span>
+      </div>
+      <div class="filterBody">
+        <!-- <slot name="filterBody"></slot> -->
+        <el-form :inline="true" :model="queryFormData" class="queryForm" :label-position="'left'">
+          <!-- 所有的input查询框 -->
+          <el-form-item :label="item.label" v-for="item in inputFieldsList" class="filterItem">
+            <el-input
+              v-model="queryFormData[item.name]"
+              :placeholder="item.placeholder"
+              clearable
+            />
+          </el-form-item>
+
+          <!-- 所有选择框 -->
+          <el-form-item :label="item.label" v-for="item in selectFieldsList" class="filterItem">
+            <!-- {{ item.placeholder }} -->
+            <el-select v-model="queryFormData[item.name]" :placeholder="item.placeholder">
+              <el-option v-for="val in item.otherOption" :label="val.cnName" :value="val.name" />
+            </el-select>
+          </el-form-item>
+
+          <!-- 所有日期选择框 -->
+          <el-form-item :label="item.label" v-for="item in dateFieldsList" class="filterItem">
+            <el-date-picker
+              v-model="queryFormData[item.name]"
+              type="date"
+              :placeholder="item.placeholder"
+              clearable
+            />
+          </el-form-item>
+        </el-form>
+        <!-- 分割线 -->
+        <!-- <el-divider class="queryPartition" content-position="center" direction="vertical" /> -->
+        <div class="queryBox">
+          <el-divider class="queryPartition" content-position="center" direction="vertical" />
+          <div class="queryBtnBox">
+            <el-button class="queryBtn" color="#165dff">
+              <el-icon><Search /></el-icon>查询
+            </el-button>
+            <el-button class="refreshBtn" color="#f2f3f5">
+              <el-icon><RefreshRight /></el-icon>重置
+            </el-button>
+          </div>
+        </div>
+      </div>
     </div>
+    <!-- 分割线 -->
+    <el-divider class="partition" content-position="center" />
+    <div class="tableTools">
+      <div class="leftTools">
+        <el-button type="primary" color="#165dff" @click="emits('addNewItem')">
+          <el-icon><Plus /></el-icon>新增
+        </el-button>
+      </div>
+      <div class="rightTools">
+        <el-button color="#f0f1f3" size="default" class="rightToolsItem">
+          <el-icon><Download /></el-icon>下载
+        </el-button>
+        <el-icon :size="toolsIconSize" class="rightToolsItem"><RefreshRight /></el-icon>
+        <el-icon :size="toolsIconSize" class="rightToolsItem"><Setting /></el-icon>
+      </div>
+    </div>
+
     <div class="tableBox">
-      <div class="tableTools"></div>
-      <div class="tableBody"></div>
+      <el-table :data="tableData" style="width: 100%">
+        <el-table-column label="#" type="index" :index="1" />
+        <el-table-column
+          v-for="item in tableFieldsInfo"
+          :prop="item.name"
+          :label="item.cnName"
+          width="auto"
+          show-overflow-tooltip
+        />
+        <slot name="tableOperation"></slot>
+      </el-table>
+      <div class="userTablePaginationBox">
+        <el-pagination
+          class="userTablePagination"
+          background
+          :page-size="paginationConfig.limit"
+          :page-sizes="paginationConfig.pagesizeList"
+          table-layout="fixed"
+          layout="prev, pager, next ,jumper ,sizes,total,"
+          :total="paginationConfig.total"
+          :current-page.sync="paginationConfig.currentPage"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
     </div>
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.tableContent {
+  margin: 0 auto;
+  width: 98%;
+  height: 98%;
+  border: 1px solid #e5e6eb;
+}
+.filterBox,
+.tableBox {
+  width: 100%;
+}
+
+.filterBox {
+  margin-top: 1%;
+  min-height: 18%;
+  display: flex;
+  flex-direction: column;
+}
+
+.filterHeader,
+.filterBody {
+  width: 98%;
+  margin: 0 auto;
+}
+
+.filterHeader {
+  display: flex;
+  align-items: center;
+  color: black;
+  font-size: 18px;
+  font-weight: bold;
+  /* background-color: lightblue; */
+  margin-bottom: 30px;
+}
+
+.filterBody {
+  display: flex;
+}
+
+.queryBox {
+  width: 10%;
+  display: flex;
+  justify-content: space-around;
+}
+
+.queryBtnBox {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.refreshBtn {
+  margin-top: 10%;
+}
+
+.queryPartition {
+  height: 90%;
+}
+
+.queryForm {
+  width: 90%;
+  display: flex;
+  flex-wrap: wrap;
+  /* justify-content: space-between; */
+}
+
+.filterItem {
+  width: 30%;
+  display: flex;
+  align-items: center;
+}
+
+.partition {
+  width: 98%;
+  margin: 0 auto;
+}
+
+.tableTools {
+  width: 98%;
+  margin: 0 auto;
+  display: flex;
+  justify-content: space-between;
+  margin-top: 1%;
+}
+
+.leftTools,
+.rightTools {
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+}
+
+.rightTools {
+  width: 10%;
+}
+
+.tableBox {
+  width: 98%;
+  margin: 0 auto;
+  margin-top: 0.5%;
+}
+
+.userTablePaginationBox {
+  width: 98%;
+  margin: 0.5% auto;
+  display: flex;
+  justify-content: center;
+}
+</style>

+ 93 - 0
src/hooks/useDialog.ts

@@ -0,0 +1,93 @@
+import type { DialogSetting } from '@/types/table'
+import type { FormInstance } from 'element-plus'
+import { useRequest } from './useRequest'
+import axiosInstance from '@/utils/axios/axiosInstance'
+import { ElMessage } from 'element-plus'
+import { nextTick } from 'vue'
+import 'element-plus/theme-chalk/el-message.css'
+import 'element-plus/theme-chalk/el-message-box.css'
+
+export function useDialog() {
+  const { AllApi, analysisResCode } = useRequest()
+
+  // 对话框关闭
+  const dialogClose = (formEl: FormInstance | undefined, dialogConfig: DialogSetting) => {
+    if (!formEl) return
+    console.log('asdas')
+    dialogConfig.dialogVisible = false
+    formEl.resetFields()
+  }
+
+  // 对话框提交
+  const submitDialog = (
+    formEl: FormInstance | undefined,
+    dialogConfig: DialogSetting,
+    url: string,
+    formData: any
+  ) => {
+    return new Promise((reslove, reject) => {
+      try {
+        if (!formEl) {
+          reject(new Error('no formEl'))
+          return
+        }
+        formEl.validate(async (valid, field) => {
+          if (valid) {
+            let result = await axiosInstance.post(url, formData)
+            let info = JSON.parse(JSON.stringify(result))
+
+            analysisResCode(info)
+              .then(() => {
+                dialogConfig.dialogVisible = false
+                reslove(true)
+              })
+              .catch((err) => {
+                reject(err)
+                console.log(err)
+              })
+          } else {
+            console.log(field)
+            console.log('表单校验不通过')
+          }
+        })
+      } catch (err) {
+        console.log(err)
+        ElMessage({
+          type: 'error',
+          message: '未知错误'
+        })
+        throw new Error('other err')
+      }
+    })
+  }
+
+  // 修改按钮
+  const handleEdit = (row: any, formData: any, dialogConfig: DialogSetting) => {
+    dialogConfig.type = 1
+    dialogConfig.dialogVisible = true
+
+    // 这里放到nextTick,因为resetFields原理是将表单重置到dom刚渲染时的数据
+    // 而这个表单,如果第一次使用就点击编辑的话,会将初始值直接设置为这行的数据,导致无法重置
+    nextTick(() => {
+      for (const key in row) {
+        if (key in formData) {
+          formData[key] = row[key]
+        } else {
+          console.log(key)
+        }
+      }
+    })
+  }
+
+  const addNeweItem = (dialogConfig: DialogSetting) => {
+    dialogConfig.type = 0
+    dialogConfig.dialogVisible = true
+  }
+
+  return {
+    dialogClose,
+    submitDialog,
+    handleEdit,
+    addNeweItem
+  }
+}

+ 12 - 5
src/hooks/useRequest.ts

@@ -1,4 +1,7 @@
 import { ElMessage } from 'element-plus'
+import 'element-plus/theme-chalk/el-message.css'
+import 'element-plus/theme-chalk/el-message-box.css'
+import { MessageType } from '@/types/res'
 
 import type { AxiosResponse } from 'axios'
 import type { ResponseInfo } from '@/types/res'
@@ -23,23 +26,27 @@ export function useRequest() {
   const analysisResCode = (data: AxiosResponse, kind?: string): Promise<ResponseInfo> => {
     return new Promise((resolve, reject) => {
       let info = JSON.parse(JSON.stringify(data)) as ResponseInfo
-      let type: string = 'success'
-      let message: string = info.msg
+      let type: MessageType = MessageType.Success
+      let message = info.msg
       let kindText = kind === 'login' ? '登录' : '请求'
       switch (info.code) {
         case 0:
           {
-            type = 'success'
+            type = MessageType.Success
             message = `${kindText}成功`
             resolve(info)
           }
           break
         default: {
-          type = 'error'
+          type = MessageType.Error
           reject(info.msg)
         }
       }
-      ElMessage.success(message)
+      ElMessage({
+        type,
+        message,
+        duration: 1000
+      })
     })
   }
 

+ 20 - 4
src/hooks/useTable.ts

@@ -1,6 +1,7 @@
 import axiosInstance from '../utils/axios/axiosInstance'
 import { useRequest } from './useRequest'
-import type { TablePaginationSetting } from '@/types/table'
+import type { TablePaginationSetting, DialogSetting } from '@/types/table'
+import { type FormInstance } from 'element-plus'
 
 export function useTable(tableData: Array<any>, paginationSetting: TablePaginationSetting) {
   const { AllApi, analysisResCode } = useRequest()
@@ -8,6 +9,7 @@ export function useTable(tableData: Array<any>, paginationSetting: TablePaginati
   const getTableData = (url: string, option: any, isPagination: boolean = false) => {
     return new Promise(async (reslove, reject) => {
       try {
+        console.log(url, option)
         await axiosInstance.post(url, option).then((data) => {
           analysisResCode(data)
             .then((info) => {
@@ -17,18 +19,32 @@ export function useTable(tableData: Array<any>, paginationSetting: TablePaginati
               } else {
                 tableData.splice(0, tableData.length, ...data)
               }
-              paginationSetting.total = data.count
+              console.log(tableData)
+              paginationSetting.total = info.count ? info.count : info.data.length
               reslove(true)
             })
             .catch((err) => {
               console.log(err)
+              throw new Error('请求失败')
             })
         })
-      } catch {}
+      } catch (err) {
+        console.log(err)
+        throw new Error('网络请求错误')
+      }
     })
   }
 
+  // 对话框关闭
+  const dialogClose = (formEl: FormInstance | undefined, dialogParam: DialogSetting) => {
+    if (!formEl) return
+
+    dialogParam.dialogVisible = false
+    formEl.resetFields()
+  }
+
   return {
-    getTableData
+    getTableData,
+    dialogClose
   }
 }

+ 4 - 1
src/router/home.ts

@@ -1,10 +1,13 @@
 import GameManageView from '../views/Home/GameManageView.vue'
-import OverView from '../views/Home/OverView.vue'
+
 import PlayerManageView from '../views/Home/PlayerManageView.vue'
+import HomeView from '@/views/Home/HomeView.vue'
+import OverView from '@/views/Home/OverView.vue'
 
 export default [
   {
     path: '/home',
+    component: HomeView,
     children: [
       {
         path: '',

+ 18 - 0
src/router/index.ts

@@ -1,5 +1,7 @@
 import { createRouter, createWebHashHistory } from 'vue-router'
 
+import { authToken } from '@/utils/axios/auth'
+
 import HomeRoutes from './home'
 import LoginRoutes from './login'
 
@@ -20,4 +22,20 @@ const router = createRouter({
   history: createWebHashHistory(),
   routes
 })
+
+router.beforeEach((to, from, next) => {
+  if (from) {
+  }
+  if (to.name === 'Login') {
+    next()
+  } else {
+    authToken()
+      .then(() => {
+        next()
+      })
+      .catch(() => {
+        router.push('login')
+      })
+  }
+})
 export default router

+ 8 - 0
src/types/res.ts

@@ -2,4 +2,12 @@ export interface ResponseInfo {
   code: number
   msg: string
   data: any
+  count: number
+}
+
+export enum MessageType {
+  Success = 'success',
+  Warning = 'warning',
+  Info = 'info',
+  Error = 'error'
 }

+ 59 - 0
src/types/table.ts

@@ -1,3 +1,13 @@
+export interface GameTableData {
+  gameName: string //游戏名称
+  gid: string //游戏ID
+  ttAppid: string //游戏 tt 平台id
+  ttSecret: string //游戏 tt 平台密钥
+  wxAppid: string //游戏 微信平台 id
+  wxSecret: string // 游戏 微信 平台密钥
+}
+
+// 表格分页的数据类型
 export interface TablePaginationSetting {
   limit: number // 每页展示个数
   currentPage: number // 当前页码
@@ -6,3 +16,52 @@ export interface TablePaginationSetting {
   loading: boolean // 加载图标
   hasLodingData: number // 已经加载的数据
 }
+
+// 表格信息过滤时,需要用到的过滤类型
+export enum FilterType {
+  INPUT = 'input',
+  SELECT = 'select',
+  DATE = 'date'
+}
+
+// 筛选信息的格式
+export interface QueryInfo {
+  name: string
+  label: string
+  type: FilterType
+  placeholder: string
+  otherOption?: any
+}
+
+// 表格字段信息格式
+export interface TableFieldInfo {
+  name: string
+  cnName: string
+  isShow: boolean
+}
+
+// props的参数格式
+export interface PropsParams {
+  isOpenQuery: boolean
+  queryInfo?: Array<QueryInfo>
+  paginationConfig: TablePaginationSetting
+  tableFieldsInfo: Array<TableFieldInfo>
+  requestConfig: {
+    url: string
+    other: any
+  }
+}
+
+// 选择下拉框的信息格式
+export interface SelectInfo {
+  name: string
+  cnName: string
+}
+
+// 对话框设置
+export interface DialogSetting {
+  dialogVisible: boolean
+  title: string
+  formLabelWidth: string
+  type: number // 0 是新增 1是修改
+}

+ 31 - 0
src/utils/axios/auth.ts

@@ -0,0 +1,31 @@
+// function getToken() {}
+
+import axiosInstance from './axiosInstance'
+import { useRequest } from '@/hooks/useRequest'
+
+const { AllApi } = useRequest()
+
+export const authToken = () => {
+  return new Promise((reslove, reject) => {
+    let refreshToken = localStorage.getItem('refreshToken')
+    if (refreshToken) {
+      axiosInstance
+        .post(AllApi.gerRefreshToken)
+        .then((data) => {
+          let result = JSON.parse(JSON.stringify(data))
+          if (result.code === 0) {
+            localStorage.setItem('token', result.data.token)
+            reslove(true)
+          } else {
+            reject(false)
+          }
+        })
+        .catch((err) => {
+          reject(false)
+          console.log(err)
+        })
+    } else {
+      reject(false)
+    }
+  })
+}

+ 5 - 5
src/utils/axios/axiosInstance.ts

@@ -1,10 +1,10 @@
 // 引入axios
 import axios from 'axios'
-import router from '../router'
+import router from '@/router'
 import { ElMessage } from 'element-plus'
-import { useReq } from '../hooks/useReq'
+import { useRequest } from '@/hooks/useRequest'
 
-const { AllGameTableAPI } = useReq()
+const { AllApi } = useRequest()
 // import qs from "qs";
 // 创建axios实例
 const axiosInstance = axios.create()
@@ -12,12 +12,12 @@ const axiosInstance = axios.create()
 axiosInstance.interceptors.request.use(
   function (config) {
     // console.log(config);
-    if (config.url === AllGameTableAPI.gerRefreshToken) {
+    if (config.url === AllApi.gerRefreshToken) {
       let refreshToken = localStorage.getItem('refreshToken')
       if (refreshToken) {
         config.headers.Authorization = refreshToken
       }
-    } else if (config.url !== AllGameTableAPI.userLogin) {
+    } else if (config.url !== AllApi.userLogin) {
       let token = localStorage.getItem('token')
 
       if (token) {

+ 304 - 9
src/views/Home/GameManageView.vue

@@ -1,15 +1,310 @@
+<script setup lang="ts">
+import {
+  type TablePaginationSetting,
+  type QueryInfo,
+  FilterType,
+  type SelectInfo,
+  type TableFieldInfo
+} from '@/types/table'
+
+import Table from '@/components/Table.vue'
+import { reactive, ref } from 'vue'
+
+import type { FormRules, FormInstance } from 'element-plus'
+
+import { useRequest } from '@/hooks/useRequest'
+import { useDialog } from '@/hooks/useDialog'
+
+const { AllApi } = useRequest()
+const { dialogClose, submitDialog, handleEdit, addNeweItem } = useDialog()
+
+interface GameDialogFormData {
+  gid: string
+  wxAppid: string
+  wxSecret: string
+  ttAppid: string
+  ttSecret: string
+  gameName: string
+  appSecret: string
+}
+
+const gameTableRef = ref()
+
+// 游戏配置对话框对象
+const gameDialogFormRef = ref<FormInstance>()
+
+// 配置请求参数
+const requestConfig = reactive({
+  url: AllApi.getGameTable,
+  other: {
+    appSecret: '6YJSuc50uJ18zj45'
+  }
+})
+
+// 配置分页数据
+const paginationConfig = reactive<TablePaginationSetting>({
+  limit: 10, // 每页展示个数
+  currentPage: 1, // 当前页码
+  total: 0, // 数据总数
+  pagesizeList: [10, 15], // 页数大小列表
+  loading: true, // 加载图标
+  hasLodingData: 0 // 已经加载的数据
+})
+
+// 所有平台
+const allPf = reactive<Array<SelectInfo>>([
+  {
+    name: 'wx',
+    cnName: '微信'
+  },
+  {
+    name: 'tt',
+    cnName: '抖音'
+  },
+  {
+    name: 'web',
+    cnName: 'Web'
+  }
+])
+
+// 查询字段设置
+const queryInfo = reactive<Array<QueryInfo>>([
+  {
+    name: 'gameName',
+    label: '游戏名',
+    type: FilterType.INPUT,
+    placeholder: '请输入游戏名查询'
+  },
+  {
+    name: 'pf',
+    label: '平台',
+    type: FilterType.SELECT,
+    placeholder: '请选择平台',
+    otherOption: allPf
+  }
+])
+
+// 字段信息
+const filedsInfo = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'gameName',
+    cnName: '游戏名',
+    isShow: true
+  },
+  {
+    name: 'gid',
+    cnName: '游戏ID',
+    isShow: true
+  },
+  {
+    name: 'ttAppid',
+    cnName: '抖音App ID',
+    isShow: true
+  },
+  {
+    name: 'ttSecret',
+    cnName: '抖音App Secret',
+    isShow: true
+  },
+  {
+    name: 'wxAppid',
+    cnName: '微信App ID',
+    isShow: true
+  },
+  {
+    name: 'wxSecret',
+    cnName: '微信App Secret',
+    isShow: true
+  }
+])
+
+// 游戏配置对话框设置
+const dialogConfig = reactive({
+  dialogVisible: false,
+  title: '游戏配置',
+  formLabelWidth: '150px',
+  type: 0 // 0 是新增 1是修改
+})
+
+// 表单校验规则
+const gameFormRule = reactive({
+  gameName: '',
+  gid: '',
+  ttAppid: '',
+  ttSecret: '',
+  wxAppid: '',
+  wxSecret: ''
+})
+
+// 表单规则
+const gameRules = reactive<FormRules<typeof gameFormRule>>({
+  gameName: [
+    {
+      required: true,
+      message: '请输入游戏名',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ],
+  gid: [
+    {
+      required: true,
+      message: '请输入gid',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ],
+  ttAppid: [
+    {
+      required: true,
+      message: '请输入ttAppid',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ],
+  ttSecret: [
+    {
+      required: true,
+      message: '请输入ttSecret',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ],
+  wxAppid: [
+    {
+      required: true,
+      message: '请输入wxAppid',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ],
+  wxSecret: [
+    {
+      required: true,
+      message: '请输入wxSecret',
+      trigger: 'blur'
+    },
+    {
+      min: 1,
+      max: 64,
+      trigger: 'blur',
+      message: '最短1位,最长64位'
+    }
+  ]
+})
+
+// 对话框表单数据
+const gameFormData = reactive<GameDialogFormData>({
+  gid: '',
+  wxAppid: '',
+  wxSecret: '',
+  ttAppid: '',
+  ttSecret: '',
+  gameName: '',
+  appSecret: ''
+})
+
+// 进入用户页面
+const enterUserPage = (row: any) => {
+  console.log(row)
+}
+
+// 游戏配置提交
+const submiteGameChange = () => {
+  gameFormData.appSecret = '6YJSuc50uJ18zj45'
+  submitDialog(gameDialogFormRef.value, dialogConfig, AllApi.addGame, gameFormData).then(() => {
+    gameTableRef.value.getData()
+  })
+}
+</script>
 <template>
-  <div class="about">
-    <h1>This is an about page</h1>
+  <div class="gameMangeBox">
+    <Table
+      ref="gameTableRef"
+      :is-open-query="true"
+      :table-fields-info="filedsInfo"
+      :query-info="queryInfo"
+      :request-config="requestConfig"
+      :pagination-config="paginationConfig"
+      @addNewItem="addNeweItem(dialogConfig)"
+    >
+      <template #tableOperation>
+        <el-table-column label="操作" align="center">
+          <template #default="scope">
+            <el-button
+              size="small"
+              type="primary"
+              @click="handleEdit(scope.row, gameFormData, dialogConfig)"
+            >
+              修改
+            </el-button>
+            <el-button size="small" type="success" @click="enterUserPage(scope.row)">
+              进入
+            </el-button>
+          </template>
+        </el-table-column>
+      </template>
+    </Table>
+    <div class="optionDialog">
+      <el-dialog
+        @close="dialogClose(gameDialogFormRef, dialogConfig)"
+        v-model="dialogConfig.dialogVisible"
+        :title="dialogConfig.title"
+      >
+        <el-form :rules="gameRules" :model="gameFormData" ref="gameDialogFormRef">
+          <template v-for="item in filedsInfo">
+            <el-form-item
+              :prop="item.name"
+              :label="item.cnName"
+              :label-width="dialogConfig.formLabelWidth"
+            >
+              <el-input
+                v-model="gameFormData[item.name as keyof GameDialogFormData]"
+                autocomplete="off"
+              />
+            </el-form-item>
+          </template>
+        </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button type="primary" @click="submiteGameChange()"> 确认 </el-button>
+            <el-button @click="dialogClose(gameDialogFormRef, dialogConfig)">取消</el-button>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
   </div>
 </template>
 
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
+<style scoped>
+.gameMangeBox {
+  width: 100%;
+  height: 100%;
+  padding-top: 2%;
 }
 </style>

+ 175 - 0
src/views/Home/HomeView.vue

@@ -0,0 +1,175 @@
+<script setup lang="ts">
+import { RouterView } from 'vue-router'
+import { ref } from 'vue'
+
+const isCollapse = ref(false)
+const menuList = [
+  {
+    title: '数据总览',
+    icon: 'PieChart',
+    children: [
+      {
+        pathName: 'OverView',
+        title: '工作台'
+      }
+    ]
+  },
+  {
+    title: '信息管理',
+    icon: 'Histogram',
+    children: [
+      {
+        pathName: 'GameManageView',
+        title: '游戏管理'
+      },
+      {
+        pathName: 'PlayerManageView',
+        title: '玩家管理'
+      }
+    ]
+  }
+]
+
+const changeCollapse = () => {
+  isCollapse.value = !isCollapse.value
+}
+</script>
+
+<template>
+  <div class="body">
+    <div class="sideBarBox">
+      <el-menu :router="true" :default-active="$route.name" class="sideBar" :collapse="isCollapse">
+        <el-menu-item index="/" class="logoBox">
+          <el-image :fit="'fill'" class="logoImg" src="/src/assets/logo.svg"></el-image>
+          <template #title><span class="logoText">淳皓科技</span></template>
+        </el-menu-item>
+        <el-sub-menu v-for="item in menuList" :index="item.title">
+          <template #title>
+            <el-icon><component :is="item.icon" /></el-icon>
+            <span class="menuTitle">{{ item.title }}</span>
+          </template>
+          <el-menu-item v-for="v in item.children" :index="v.pathName">{{ v.title }}</el-menu-item>
+        </el-sub-menu>
+        <div class="sideBarFold" @click="changeCollapse">
+          <el-icon :size="25"><Fold /></el-icon>
+        </div>
+      </el-menu>
+    </div>
+    <div class="navBarBox">
+      <div class="headPortraitBox">
+        <!-- <el-avatar :size="40" src="/src/assets/default/defaultHead.png" /> -->
+        <el-image class="headPortrait" src="/src/assets/default/defaultHead.png"></el-image>
+      </div>
+    </div>
+    <div class="content">
+      <RouterView />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.body {
+  width: 100%;
+  display: flex;
+  height: 100vh;
+}
+
+/* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
+.sideBarBox {
+  position: relative;
+  /* width: 12%; */
+  z-index: 2;
+  height: 100vh;
+  top: 0;
+}
+
+.sideBar {
+  /* width: 12vw; */
+  height: 100vh;
+  position: relative;
+  overflow: scroll;
+}
+
+/* 设置弹出层的样式 */
+.el-popper > .logoText {
+  width: 100px;
+  font-size: 16px;
+  /* color: red; */
+}
+
+/* 调整LOGO */
+.logoBox {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.logoImg {
+  display: flex;
+  align-items: center;
+  width: 33px;
+  /* margin-right: 20px; */
+  /* height: 50px; */
+}
+
+.logoText {
+  width: 80%;
+  height: 100%;
+  margin-left: 15%;
+  display: flex;
+  font-size: 18px;
+  align-items: center;
+  /* background-color: lightcoral; */
+}
+
+/* 主要用来调整整个menu的宽度 */
+.menuTitle {
+  margin-right: 40px;
+}
+
+.sideBarFold {
+  width: 5%;
+  height: 3%;
+  position: absolute;
+  right: 40px;
+  bottom: 20px;
+}
+
+.navBarBox {
+  position: fixed;
+  width: 100vw;
+  z-index: 1;
+  height: 7vh;
+  top: 0;
+  background-color: white;
+  right: 0;
+  border-bottom: 1px solid gainsboro;
+}
+
+.headPortraitBox {
+  /* width: 5vw; */
+  /* height: 5vh; */
+  position: absolute;
+  right: 3%;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.headPortrait {
+  cursor: pointer;
+  width: 50px;
+}
+
+.content {
+  /* flex-grow: 1; */
+  /* position: absolute; */
+
+  width: 100%;
+  height: 93%;
+  margin-top: 7vh;
+  overflow: scroll;
+  /* background-color: lightcoral; */
+  right: 0vw;
+  top: 0vh;
+}
+</style>

+ 6 - 12
src/views/Home/OverView.vue

@@ -1,15 +1,9 @@
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue'
+// let a = ref(0)
+</script>
 <template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
+  <div class="gameMangeBox">test</div>
 </template>
 
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
-}
-</style>
+<style></style>

+ 252 - 9
src/views/Home/PlayerManageView.vue

@@ -1,15 +1,258 @@
+<script setup lang="ts">
+import {
+  type TablePaginationSetting,
+  type QueryInfo,
+  FilterType,
+  type SelectInfo,
+  type TableFieldInfo
+} from '@/types/table'
+
+import Table from '@/components/Table.vue'
+import CryptoJS from 'crypto-js'
+
+import { reactive, ref } from 'vue'
+
+import type { FormRules, FormInstance } from 'element-plus'
+
+import { useRequest } from '@/hooks/useRequest'
+import { useDialog } from '@/hooks/useDialog'
+
+const { AllApi } = useRequest()
+const { dialogClose, submitDialog, handleEdit, addNeweItem } = useDialog()
+
+interface PlayerDialogFormData {
+  gid: string
+  openId: string
+  pf: string
+  option: string
+  userId: string
+}
+
+const playerTableRef = ref()
+
+// 游戏配置对话框对象
+const playerDialogFormRef = ref<FormInstance>()
+
+// 配置请求参数
+const requestConfig = reactive({
+  url: AllApi.getUserTable,
+  other: {
+    offset: 0,
+    limit: 10,
+    gid: '',
+    pf: ''
+  }
+})
+
+// 配置分页数据
+const paginationConfig = reactive<TablePaginationSetting>({
+  limit: 10, // 每页展示个数
+  currentPage: 1, // 当前页码
+  total: 0, // 数据总数
+  pagesizeList: [10, 15], // 页数大小列表
+  loading: true, // 加载图标
+  hasLodingData: 0 // 已经加载的数据
+})
+
+// 所有平台
+const allPf = reactive<Array<SelectInfo>>([
+  {
+    name: 'wx',
+    cnName: '微信'
+  },
+  {
+    name: 'tt',
+    cnName: '抖音'
+  },
+  {
+    name: 'web',
+    cnName: 'Web'
+  }
+])
+
+// 查询字段设置
+const queryInfo = reactive<Array<QueryInfo>>([
+  {
+    name: 'gameName',
+    label: '游戏名',
+    type: FilterType.INPUT,
+    placeholder: '请输入游戏名查询'
+  },
+  {
+    name: 'pf',
+    label: '平台',
+    type: FilterType.SELECT,
+    placeholder: '请选择平台',
+    otherOption: allPf
+  }
+])
+
+// 字段信息
+const filedsInfo = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'gid',
+    cnName: '游戏ID',
+    isShow: true
+  },
+  {
+    name: 'head',
+    cnName: '头像',
+    isShow: true
+  },
+  {
+    name: 'inBlack',
+    cnName: '是否在黑名单',
+    isShow: true
+  },
+  {
+    name: 'nickName',
+    cnName: '昵称',
+    isShow: true
+  },
+  {
+    name: 'openId',
+    cnName: 'Open ID',
+    isShow: true
+  },
+  {
+    name: 'option',
+    cnName: '权限',
+    isShow: true
+  },
+  {
+    name: 'pf',
+    cnName: '平台',
+    isShow: true
+  },
+  {
+    name: 'userId',
+    cnName: '玩家ID',
+    isShow: true
+  }
+])
+
+// 游戏配置对话框设置
+const dialogConfig = reactive({
+  dialogVisible: false,
+  title: '用户权限配置',
+  formLabelWidth: '150px',
+  type: 0 // 0 是新增 1是修改
+})
+
+// 表单校验规则
+const optionFormRule = reactive({
+  option: ''
+})
+
+// 表单规则
+const gameRules = reactive<FormRules<typeof optionFormRule>>({
+  option: [
+    { required: true, message: '请输入权限', trigger: 'blur' },
+    { min: 1, max: 255, message: '最短1位,最长255位', trigger: 'blur' }
+  ]
+})
+
+// 对话框表单数据
+const optionFormData = reactive<PlayerDialogFormData>({
+  gid: '',
+  openId: '',
+  pf: '',
+  option: '',
+  userId: ''
+})
+
+// 进入用户页面
+const blockedPlayer = (row: any) => {
+  console.log(row)
+}
+
+// 游戏配置提交
+const submiteOptionChange = (isEncrypt: boolean = false) => {
+  if (isEncrypt) {
+    let message = `${optionFormData.gid}${optionFormData.userId}${optionFormData.pf}`
+    optionFormData.option = CryptoJS.HmacMD5(message, optionFormData.option).toString()
+  }
+  submitDialog(playerDialogFormRef.value, dialogConfig, AllApi.addOption, optionFormData).then(
+    () => {
+      playerTableRef.value.getData()
+    }
+  )
+}
+</script>
 <template>
-  <div class="about">
-    <h1>This is an about page</h1>
+  <div class="gameMangeBox">
+    <Table
+      ref="playerTableRef"
+      :is-open-query="true"
+      :table-fields-info="filedsInfo"
+      :query-info="queryInfo"
+      :request-config="requestConfig"
+      :pagination-config="paginationConfig"
+    >
+      <template #tableOperation>
+        <el-table-column label="操作" align="center">
+          <template #default="scope">
+            <el-button
+              size="small"
+              type="primary"
+              @click="handleEdit(scope.row, optionFormData, dialogConfig)"
+            >
+              修改权限
+            </el-button>
+            <el-button size="small" type="success" @click="blockedPlayer(scope.row)">
+              封禁
+            </el-button>
+          </template>
+        </el-table-column>
+      </template>
+    </Table>
+    <div class="optionDialog">
+      <el-dialog
+        @close="dialogClose(playerDialogFormRef, dialogConfig)"
+        v-model="dialogConfig.dialogVisible"
+        :title="dialogConfig.title"
+      >
+        <el-form :rules="gameRules" :model="optionFormData" ref="playerDialogFormRef">
+          <template v-for="item in filedsInfo">
+            <el-form-item
+              :prop="item.name"
+              :label="item.cnName"
+              :label-width="dialogConfig.formLabelWidth"
+              v-if="item.name === 'option'"
+            >
+              <el-input
+                v-model="optionFormData[item.name as keyof PlayerDialogFormData]"
+                autocomplete="off"
+              />
+            </el-form-item>
+          </template>
+        </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button class="subBtnItem" type="warning" @click="submiteOptionChange(true)"
+              >加密上传</el-button
+            >
+            <el-button class="subBtnItem" type="primary" @click="submiteOptionChange()">
+              确认
+            </el-button>
+            <el-button class="subBtnItem" @click="dialogClose(playerDialogFormRef, dialogConfig)"
+              >取消</el-button
+            >
+          </div>
+        </template>
+      </el-dialog>
+    </div>
   </div>
 </template>
 
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
+<style scoped>
+.gameMangeBox {
+  width: 100%;
+  height: 100%;
+  padding-top: 2%;
+}
+
+.subBtnItem {
+  margin-right: 1%;
 }
 </style>

+ 111 - 9
src/views/Login/LoginView.vue

@@ -1,15 +1,117 @@
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue'
+import type { FormRules, FormInstance } from 'element-plus'
+import { useRequest } from '@/hooks/useRequest'
+import router from '@/router'
+import axiosInstance from '@/utils/axios/axiosInstance'
+
+const { AllApi, analysisResCode } = useRequest()
+
+interface loginRuleType {
+  userName: string
+  password: string
+}
+
+const rules = reactive<FormRules<loginRuleType>>({
+  userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
+})
+
+const loginInfo = reactive<loginRuleType>({
+  userName: '',
+  password: ''
+})
+
+const loginFormRef = ref<FormInstance>()
+
+const userLogin = () => {
+  axiosInstance
+    .post(AllApi.userLogin, loginInfo)
+    .then((data) => {
+      let result = JSON.parse(JSON.stringify(data))
+      analysisResCode(result, 'login')
+        .then(() => {
+          localStorage.setItem('token', result.token)
+          localStorage.setItem('refreshToken', result.refreshToken)
+          console.log('设置了')
+          router.push('/')
+        })
+        .catch((err) => {
+          console.log(err)
+        })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
+const onSubmit = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate((valid, fields) => {
+    if (valid) {
+      userLogin()
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// 加载数据
+onMounted(() => {})
+</script>
+
 <template>
-  <div class="about">
-    <h1>This is an about page</h1>
+  <div class="loginBox">
+    <el-form
+      ref="loginFormRef"
+      class="loginForm"
+      :rules="rules"
+      :model="loginInfo"
+      label-width="auto"
+      style="max-width: 600px"
+    >
+      <el-form-item label="用户名" prop="userName">
+        <el-input class="formItem" v-model="loginInfo.userName" />
+      </el-form-item>
+      <el-form-item label="密码" prop="password">
+        <el-input class="formItem" v-model="loginInfo.password" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="onSubmit(loginFormRef)">登录</el-button>
+      </el-form-item>
+    </el-form>
   </div>
 </template>
 
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
+<style scoped lang="less">
+.loginBox {
+  width: 100vw;
+  height: 100vh;
+  background-color: lightblue;
+  //   position: relative;
+  //   left: 50%;
+  //   top: 50%;
+  //   transform: translate(-50%, -50%);
+  //   background-color: lightblue;
+}
+
+.loginForm {
+  width: 25vw;
+  height: 40vh;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  background: rgba(255, 255, 255, 0.7);
+  backdrop-filter: blur(30px);
+  border-radius: 15px;
+}
+
+.formItem {
+  width: 100%;
 }
 </style>

+ 20 - 1
tsconfig.app.json

@@ -1,10 +1,29 @@
 {
   "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
   "exclude": ["src/**/__tests__/*"],
   "compilerOptions": {
     "composite": true,
     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "preserve",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
 
     "baseUrl": ".",
     "paths": {