zhangxing 3 years ago
commit
79661886f8
58 changed files with 8838 additions and 0 deletions
  1. 78 0
      .eslintrc.js
  2. 24 0
      .gitignore
  3. 3 0
      .vscode/extensions.json
  4. 51 0
      README.md
  5. 32 0
      components.d.ts
  6. 32 0
      index.html
  7. 2371 0
      package-lock.json
  8. 43 0
      package.json
  9. 20 0
      prettier.config.js
  10. 52 0
      src/App.vue
  11. 75 0
      src/api/index.ts
  12. 352 0
      src/assets/css/normalize.css
  13. 160 0
      src/assets/css/property.css
  14. 47 0
      src/assets/css/reset.css
  15. BIN
      src/assets/img/logo.png
  16. 259 0
      src/assets/scss/_mixin.scss
  17. 17 0
      src/assets/scss/_setting.scss
  18. 34 0
      src/common/request.ts
  19. 16 0
      src/components/Footer.vue
  20. 113 0
      src/components/IndexList.vue
  21. 120 0
      src/components/MobCateList.vue
  22. 66 0
      src/components/MobList.vue
  23. 96 0
      src/components/MobileCom.vue
  24. 165 0
      src/components/RankList.vue
  25. 82 0
      src/components/loading.vue
  26. 54 0
      src/layout/Layout.vue
  27. 18 0
      src/layout/footer.vue
  28. 221 0
      src/layout/header.vue
  29. 12 0
      src/layout/section.vue
  30. 34 0
      src/main.ts
  31. 152 0
      src/router/index.ts
  32. 20 0
      src/store/header.ts
  33. 17 0
      src/store/index.ts
  34. 72 0
      src/store/search.ts
  35. 24 0
      src/store/user.ts
  36. 10 0
      src/utils/Message.ts
  37. 20 0
      src/utils/bytesFormatter.ts
  38. 23 0
      src/utils/local.ts
  39. 251 0
      src/utils/md5.ts
  40. 9 0
      src/utils/sort.ts
  41. 12 0
      src/utils/throttle.ts
  42. 163 0
      src/view/m_views/categroy/index.vue
  43. 215 0
      src/view/m_views/index/index.vue
  44. 109 0
      src/view/m_views/login/index.vue
  45. 127 0
      src/view/m_views/mine/index.vue
  46. 13 0
      src/view/m_views/rank/index.vue
  47. 92 0
      src/view/m_views/search/index.vue
  48. 203 0
      src/view/p_views/categroy/index.vue
  49. 71 0
      src/view/p_views/index/index.vue
  50. 166 0
      src/view/p_views/login/index.vue
  51. 116 0
      src/view/p_views/mine/index.vue
  52. 238 0
      src/view/p_views/rank/index.vue
  53. 41 0
      src/view/p_views/search/index.vue
  54. 7 0
      src/vite-env.d.ts
  55. 41 0
      tsconfig.json
  56. 9 0
      tsconfig.node.json
  57. 76 0
      vite.config.ts
  58. 1894 0
      yarn.lock

+ 78 - 0
.eslintrc.js

@@ -0,0 +1,78 @@
+// @ts-check
+const { defineConfig } = require("eslint-define-config");
+module.exports = defineConfig({
+  root: true,
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+    'vue/setup-compiler-macros': true
+  },
+  parser: "vue-eslint-parser",
+  parserOptions: {
+    parser: "@typescript-eslint/parser",
+    ecmaVersion: 2020,
+    sourceType: "module",
+    jsxPragma: "React",
+    ecmaFeatures: {
+      jsx: true,
+    },
+  },
+  extends: [
+    "plugin:vue/vue3-recommended",
+    "plugin:@typescript-eslint/recommended",
+    "prettier",
+    "plugin:prettier/recommended",
+  ],
+  rules: {
+    "@typescript-eslint/ban-ts-ignore": "off",
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/no-var-requires": "off",
+    "@typescript-eslint/no-empty-function": "off",
+    "vue/custom-event-name-casing": "off",
+    "no-use-before-define": "off",
+    "@typescript-eslint/no-use-before-define": "off",
+    "@typescript-eslint/ban-ts-comment": "off",
+    "@typescript-eslint/ban-types": "off",
+    "@typescript-eslint/no-non-null-assertion": "off",
+    "@typescript-eslint/explicit-module-boundary-types": "off",
+    "@typescript-eslint/no-unused-vars": [
+      "error",
+      {
+        argsIgnorePattern: "^_",
+        varsIgnorePattern: "^_",
+      },
+    ],
+    "no-unused-vars": [
+      "error",
+      {
+        argsIgnorePattern: "^_",
+        varsIgnorePattern: "^_",
+      },
+    ],
+    "space-before-function-paren": "off",
+
+    "vue/attributes-order": "off",
+    "vue/one-component-per-file": "off",
+    "vue/html-closing-bracket-newline": "off",
+    "vue/max-attributes-per-line": "off",
+    "vue/multiline-html-element-content-newline": "off",
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/attribute-hyphenation": "off",
+    "vue/require-default-prop": "off",
+    "vue/html-self-closing": [
+      "error",
+      {
+        html: {
+          void: "always",
+          normal: "never",
+          component: "always",
+        },
+        svg: "always",
+        math: "always",
+      },
+    ],
+  },
+});
+

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 51 - 0
README.md

@@ -0,0 +1,51 @@
+# Vue 3 + TypeScript + Vite
+
+## js文件下载
+https://blog.csdn.net/weixin_41217541/article/details/109893272?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-109893272-blog-112007458.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-109893272-blog-112007458.pc_relevant_aa&utm_relevant_index=10
+    
+
+## js 下载
+https://blog.csdn.net/weixin_43299180/article/details/112007458
+
+
+## 只刷新一次页面
+if (location.href.indexOf("#reloaded") === -1) {
+    location.href = location.href + "#reloaded";
+    location.reload();
+}
+
+## vite + vue3 低版本浏览器兼容配置
+1. 安装兼容插件 yarn add @vitejs/plugin-legacy
+2. vite.config.ts 中引入
+    import legacy from '@vitejs/plugin-legacy'
+    plugins:[
+        legacy({
+      targets: ['chrome 50', 'ie >= 11'],       //兼容最低版本号50(UC浏览器55)
+      additionalLegacyPolyfills: ['regenerator-runtime/runtime'], // regenerator-runtime/runtime   @dian/polyfill
+      renderLegacyChunks: true,
+      polyfills: [
+        'es.symbol',
+        'es.array.filter',
+        'es.promise',
+        'es.promise.finally',
+        'es/map',
+        'es/set',
+        'es.array.for-each',
+        'es.object.define-properties',
+        'es.object.define-property',
+        'es.object.get-own-property-descriptor',
+        'es.object.get-own-property-descriptors',
+        'es.object.keys',
+        'es.object.to-string',
+        'web.dom-collections.for-each',
+        'esnext.global-this',
+        'esnext.string.match-all',
+        // // 这个无法处理
+        // 'es.string.replace-all'
+      ],
+      modernPolyfills: ['es.string.replace-all']
+    })
+    ]
+
+
+## vite打包时会报错 terser not found. Since Vite v3, terser has become an optional dependency. You need to install it. 解决办法:yarn add terser --legacy-peer-deps 再重新打包

+ 32 - 0
components.d.ts

@@ -0,0 +1,32 @@
+// generated by unplugin-vue-components
+// We suggest you to commit this file into source control
+// Read more: https://github.com/vuejs/core/pull/3399
+import '@vue/runtime-core'
+
+export {}
+
+declare module '@vue/runtime-core' {
+  export interface GlobalComponents {
+    Footer: typeof import('./src/components/Footer.vue')['default']
+    IndexList: typeof import('./src/components/IndexList.vue')['default']
+    Loading: typeof import('./src/components/loading.vue')['default']
+    MobCateList: typeof import('./src/components/MobCateList.vue')['default']
+    MobileCom: typeof import('./src/components/MobileCom.vue')['default']
+    MobList: typeof import('./src/components/MobList.vue')['default']
+    RankList: typeof import('./src/components/RankList.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    VanBackTop: typeof import('vant/es')['BackTop']
+    VanButton: typeof import('vant/es')['Button']
+    VanCell: typeof import('vant/es')['Cell']
+    VanDropdownItem: typeof import('vant/es')['DropdownItem']
+    VanDropdownMenu: typeof import('vant/es')['DropdownMenu']
+    VanEmpty: typeof import('vant/es')['Empty']
+    VanField: typeof import('vant/es')['Field']
+    VanForm: typeof import('vant/es')['Form']
+    VanIcon: typeof import('vant/es')['Icon']
+    VanSearch: typeof import('vant/es')['Search']
+    VanSwipe: typeof import('vant/es')['Swipe']
+    VanSwipeItem: typeof import('vant/es')['SwipeItem']
+  }
+}

+ 32 - 0
index.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <link rel="icon" type="image/svg+xml" href="http://gm.nkfzs.com/favicon.ico" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <script src="http://cdn.bootcss.com/blueimp-md5/1.1.0/js/md5.js"></script>
+  <title>朱雀游戏官网</title>
+
+  <style>
+    body {
+      font-size: 16px;
+      height: 100vh;
+      margin: 0;
+      padding: 0;
+    }
+
+    #app {
+      min-width: 320px;
+      min-height: 100vh;
+      margin: 0 auto;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="app" style="height:100%"></div>
+  <script type="module" src="/src/main.ts"></script>
+</body>
+
+</html>

File diff suppressed because it is too large
+ 2371 - 0
package-lock.json


+ 43 - 0
package.json

@@ -0,0 +1,43 @@
+{
+  "name": "game-web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.0.10",
+    "axios": "^1.2.1",
+    "element-plus": "^2.2.26",
+    "lib-flexible": "^0.3.2",
+    "pinia": "^2.0.28",
+    "postcss-pxtorem": "6.0.0",
+    "terser": "^5.16.1",
+    "vant": "^4.0.3",
+    "vue": "^3.2.45",
+    "vue-router": "^4.1.6",
+    "yarn": "^2.0.27"
+  },
+  "devDependencies": {
+    "@types/node": "^18.11.15",
+    "@typescript-eslint/eslint-plugin": "^5.46.1",
+    "@typescript-eslint/parser": "^5.46.1",
+    "@vitejs/plugin-legacy": "^3.0.1",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "eslint": "^8.29.0",
+    "eslint-config-prettier": "^8.5.0",
+    "eslint-define-config": "^1.12.0",
+    "eslint-plugin-prettier": "^4.2.1",
+    "eslint-plugin-vue": "^9.8.0",
+    "prettier": "^2.8.1",
+    "sass": "^1.56.2",
+    "sass-loader": "^13.2.0",
+    "typescript": "^4.9.4",
+    "unplugin-vue-components": "^0.22.12",
+    "vite": "^4.0.1",
+    "vue-tsc": "^1.0.13"
+  }
+}

+ 20 - 0
prettier.config.js

@@ -0,0 +1,20 @@
+module.exports = {
+    printWidth: 100,
+    tabWidth: 2,
+    semi: true,
+    vueIndentScriptAndStyle: true,
+    singleQuote: false,
+    quoteProps: "as-needed",
+    bracketSpacing: true,
+    jsxBracketSameLine: false,
+    jsxSingleQuote: false,
+    arrowParens: "always",
+    insertPragma: false,
+    requirePragma: false,
+    proseWrap: "never",
+    htmlWhitespaceSensitivity: "strict",
+    endOfLine: "auto",
+    rangeStart: 0,
+  };
+  
+  

+ 52 - 0
src/App.vue

@@ -0,0 +1,52 @@
+<template>
+  <!-- 一级路由出口 -->
+  <router-view></router-view>
+
+</template>
+
+<script setup lang="ts">
+import { onMounted, onUnmounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router';
+import { throttle } from './utils/throttle'
+
+const route = useRoute()
+const router = useRouter()
+
+onMounted(() => {
+  setRem()
+  // 计算屏幕宽度决定pc还是移动端
+  throttle(resizeChange(), 200)
+  window.addEventListener('resize', resizeChange, false)
+  window.addEventListener("resize", setRem, false)
+})
+
+const resizeChange = () => {
+  // 移动端页面
+  if (document.body.clientWidth < 750) {
+    if (route.path.indexOf('/p_') !== -1) {
+      router.replace({ path: route.path.replace('/p_', '/m_') })
+    }
+  } else {
+    // pc端页面
+    if (route.path.indexOf('/m_') !== -1) {
+      router.replace({ path: route.path.replace('/m_', '/p_') })
+    }
+  }
+}
+const setRem = () => {
+  const pWidth = 750;
+  const pre = 100;
+  const windowWidth = document.documentElement.clientWidth;
+  document.documentElement.style.fontSize = (windowWidth / pWidth) * pre + 'px';
+  // console.log('rem适配:', windowWidth, (windowWidth / pWidth) * pre + 'px');
+}
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChange, false)
+  window.removeEventListener("resize", setRem, false)
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 75 - 0
src/api/index.ts

@@ -0,0 +1,75 @@
+// 引入工具函数
+import request from '@/common/request'
+
+// 热门游戏(有参数)
+export const getGameHot = (params:any) => {
+    return request({
+        url: '/game/hot',
+        method: 'get',
+        params
+    })
+}
+export const getIndexGameHot = () => {
+    return request({
+        url: '/game/hot',
+        method: 'get'
+    })
+}
+// 游戏推荐(有参数)
+export const getGameRecommand = (params:any) => {
+    return request({
+        url: '/game/recommand',
+        method: 'get',
+        params
+    })
+}
+export const getIndexGameRecommand = () => {
+    return request({
+        url: '/game/recommand',
+        method: 'get'
+    })
+}
+// 游戏列表
+export const getGameList = (params:any) => {
+    return request({
+        url: '/game/list',
+        method: 'get',
+        params
+    })
+}
+export const getGameLis = () => {
+    return request({
+        url: '/game/list',
+        method: 'get'
+    })
+}
+// 用户登录
+export const getUserLogin = (data:any) => {
+    return request({
+        url: '/user/login',
+        method: 'post',
+        data
+    })
+}
+// 我的游戏
+export const getMineGame = (params:any) => {
+    return request({
+        url: '/game/mine',
+        method: 'get',
+        params
+    })
+}
+// 游戏标签
+export const getGameTag = () => {
+    return request({
+        url: '/game/tag',
+        method: 'get',
+    })
+}
+// 游戏类型
+export const getGameType = () => {
+    return request({
+        url: '/game/type',
+        method: 'get',
+    })
+}

+ 352 - 0
src/assets/css/normalize.css

@@ -0,0 +1,352 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+  line-height: 1.15; /* 1 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+   ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+  margin: 0;
+}
+
+/**
+
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+  display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+  box-sizing: content-box; /* 1 */
+  height: 0; /* 1 */
+  overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+  border-bottom: none; /* 1 */
+  text-decoration: underline; /* 2 */
+  text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+  border-style: none;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit; /* 1 */
+  font-size: 100%; /* 1 */
+  line-height: 1.15; /* 1 */
+  margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+  overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+  text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  border-style: none;
+  padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+  outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+  padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ *    `fieldset` elements in all browsers.
+ */
+
+legend {
+  box-sizing: border-box; /* 1 */
+  color: inherit; /* 2 */
+  display: table; /* 1 */
+  max-width: 100%; /* 1 */
+  padding: 0; /* 3 */
+  white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+  vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button; /* 1 */
+  font: inherit; /* 2 */
+}
+
+/* Interactive
+   ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+  display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+  display: list-item;
+}
+
+/* Misc
+   ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+  display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+  display: none;
+}
+
+

+ 160 - 0
src/assets/css/property.css

@@ -0,0 +1,160 @@
+/*
+ * 面向属性的 CSS
+ */
+
+/* 布局方式 */
+.df {
+  display: flex;
+}
+
+.f1 {
+  flex: 1;
+}
+
+.fn {
+  flex: none;
+}
+
+.aic {
+  align-items: center;
+}
+
+.jcc {
+  justify-content: center;
+}
+
+.fdc {
+  flex-direction: column;
+}
+
+.fw {
+  flex-wrap: wrap;
+}
+
+.jcsb {
+  justify-content: space-between;
+}
+
+.jcsa {
+  justify-content: space-around
+}
+
+.dpb {
+  display: block;
+}
+
+.bs {
+  box-shadow: 2px 2px 5px #ccc;
+}
+
+.ofs {
+  overflow-y: scroll;
+}
+
+.dpn {
+  display: none;
+}
+
+/* 盒模型 */
+.mt0 {
+  margin-top: 0;
+}
+
+.mt8 {
+  margin-top: 8px;
+}
+
+.mt10 {
+  margin-top: 10px;
+}
+
+.mt16 {
+  margin-top: 16px;
+}
+
+.mt20 {
+  margin-top: 20px;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+
+.br15 {
+  border-radius: 15px;
+}
+
+.mt40 {
+  margin-top: 40px;
+}
+
+.mr16 {
+  margin-right: 16px;
+}
+
+.mb16 {
+  margin-bottom: 16px;
+}
+
+.ml12 {
+  margin-left: 12px;
+}
+
+.bgw {
+  background-color: #fff;
+}
+
+.pd16 {
+  padding: 16px;
+}
+
+.p20 {
+  padding: 20px;
+}
+
+.pl8 {
+  padding-left: 8px;
+}
+
+/* 字号 */
+.fs12 {
+  font-size: 12px;
+}
+
+.fs18 {
+  font-size: 18px;
+}
+
+/* 一行显示 */
+.elli {
+  word-break: keep-all;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 版心 */
+.w1000 {
+  margin: 0 auto;
+  width: 1000px;
+}
+
+@media screen and (750px <= width <= 1000px) {
+  .w1000 {
+    margin: 0 auto;
+    width: 800px;
+  }
+}
+
+/* 定位 */
+.psr {
+  position: relative;
+}
+
+.psa {
+  position: absolute;
+}
+
+.psf {
+  position: fixed;
+}

+ 47 - 0
src/assets/css/reset.css

@@ -0,0 +1,47 @@
+/*
+ * 在 normalize.css 基础上进行的其他重置
+ */
+ * {
+  box-sizing: border-box;
+}
+
+html, body {
+  width: 100%;
+  height: 100%;
+  font-size: 14px;
+}
+
+html {
+  /* AntD 字体家族:https://ant.design/docs/spec/font-cn#%E5%AD%97%E4%BD%93%E5%AE%B6%E6%97%8F */
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
+    'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+    'Noto Color Emoji';
+}
+
+a{
+  color: #333;
+}
+
+p {
+  margin: 0;
+  padding: 0;
+}
+
+input, button {
+  border: none; /* 去掉浏览器默认的 input 边框样式 */
+  outline: none; /* 去掉浏览器默认的聚焦时候的蓝色边框 */
+}
+
+a {
+  text-decoration: none;
+}
+
+ul{
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+h1,h2,h3,h4,h5,h6{
+  margin:0;
+  padding:0
+}

BIN
src/assets/img/logo.png


+ 259 - 0
src/assets/scss/_mixin.scss

@@ -0,0 +1,259 @@
+@import '~@/assets/scss/_setting.scss';
+
+@function toUnit($v) {
+  @return $v* 1px;
+}
+
+@function percent($up, $down) {
+  @return ($up/$down);
+}
+
+@mixin wh($w, $h, $l:false, $t:false, $f:false) {
+  width: toUnit($w);
+  height: toUnit($h);
+
+  @if $t {
+    text-align: center;
+  }
+
+  @if $l==true {
+    line-height: toUnit($h);
+  }
+
+  @else if $l {
+    line-height: toUnit($l);
+  }
+
+  @if $f {
+    font-size: toUnit($f);
+  }
+}
+
+
+/* 清除浮动 */
+@mixin clearfix {
+  &:after {
+    clear: both;
+    content: '.';
+    display: block;
+    height: 0;
+    line-height: 0;
+    overflow: hidden;
+  }
+  *height: 1%;
+}
+
+ 
+/*弹性盒子(传入null不设置该属性)*/
+@mixin flex( $justify: 0,$direction: row, $flex-wrap: 0,$align: center) {
+  display: flex;
+  
+  @if ($direction==1) {
+      flex-direction: column;
+  }
+  @if ($direction==2) {
+    flex-direction: column-reverse;
+}
+  @if ($justify == 0) {
+    justify-content: space-between;
+  }
+  @if ($justify == 1) {
+    justify-content: space-around;
+  }
+
+  @if ($align!=null) {
+      align-items: $align;
+  }
+  @if ($flex-wrap ==1) {
+    flex-wrap: nowrap;;
+}
+}
+
+/*绝对定位  参数顺序:上右下左*/
+@mixin positionAbsolute($top:null,$right:null,$bottom:null,$left:null) {
+position: absolute;
+@if ($left!="" & & $left!=null) {
+  left: $left;
+}
+@if ($right!="" & & $right!=null) {
+  right: $right;
+}
+@if ($top!="" & & $top!=null) {
+  top: $top;
+}
+@if ($bottom!="" & & $bottom!=null) {
+  bottom: $bottom;
+}
+}
+
+/*左浮动*/
+@mixin float-left($width:19%,$margin-right:1.2%) {
+width: $width;
+float: left;
+@if ($margin-right!=null) {
+  margin-right: $margin-right;
+}
+}
+
+/*右浮动*/
+@mixin float-Right($width:19%,$margin-left:1.2%) {
+width: $width;
+float: right;
+@if ($margin-left!=null) {
+  margin-left: $margin-left;
+}
+}
+
+/*渐变(从上到下)*/
+@mixin linear-gradient($direction:bottom,$color1:transparent,$color2:#306eff,$color3:transparent) {
+//background: -webkit-linear-gradient($direction,$colorTop, $colorCenter, $colorBottom); /* Safari 5.1 - 6.0 */
+background: -o-linear-gradient($direction, $color1, $color2, $color3); /* Opera 11.1 - 12.0 */
+background: -moz-linear-gradient($direction, $color1, $color2, $color3); /* Firefox 3.6 - 15 */
+background: linear-gradient(to $direction, $color1, $color2, $color3); /* 标准的语法 */
+}
+
+/* 行高 */
+@mixin line-height($height:30px,$line-height:30px) {
+@if ($height != null) {
+  height: $height;
+}
+@if ($line-height!=null) {
+  line-height: $line-height;
+}
+}
+
+/* 画三角形 */
+@mixin triangle($width:10px,$direction:top,$color:$pink) {
+width: 0;
+border: $width solid transparent;
+@if ($direction == top) { // 上三角
+  border-bottom-color: $color;
+}
+@if ($direction == bottom) { // 下三角
+  border-top-color: $color;
+}
+@if ($direction == left) { // 左三角
+  border-right-color: $color;
+}
+@if ($direction == right) { // 右三角
+  border-left-color: $color;
+}
+}
+
+/* 文本阴影 */
+@mixin text-show($h-shadow:0px, $v-shadow:0px, $blur:10px, $color:rgba(0,180,255,0.7)) {
+text-shadow: $h-shadow $v-shadow $blur $color;
+}
+
+/* 链接样式 */
+@mixin hoverStyle($style:(color:#d9fdff),$hoverStyle:(color:#306eff)) {
+text-decoration: none;
+@each $key, $value in $style {
+  #{$key}: #{$value};
+}
+@if ($hoverStyle!=null & & $hoverStyle!="") {
+  &:hover {
+    @each $key, $value in $hoverStyle {
+      #{$key}: #{$value};
+    }
+  }
+}
+}
+
+/* 定义滚动条样式 圆角和阴影不需要则传入null */
+@mixin scrollBar($width:10px,$height:10px,$outColor:$bgColor,$innerColor:$bgGrey,$radius:5px,$shadow:null) {
+/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
+&::-webkit-scrollbar {
+  width: $width;
+  height: $height;
+  background-color: $outColor;
+}
+
+/*定义滚动条轨道 内阴影+圆角*/
+&::-webkit-scrollbar-track {
+  @if ($shadow!=null) {
+    -webkit-box-shadow: $shadow;
+  }
+  @if ($radius!=null) {
+    border-radius: $radius;
+  }
+  background-color: $outColor;
+}
+
+/*定义滑块 内阴影+圆角*/
+&::-webkit-scrollbar-thumb {
+  @if ($shadow!=null) {
+    -webkit-box-shadow: $shadow;
+  }
+  @if ($radius!=null) {
+    border-radius: $radius;
+  }
+  background-color: $innerColor;
+  border: 1px solid $innerColor;
+}
+}
+
+/* css3动画 默认3s宽度到200px */
+@mixin animation($from:(width:0px),$to:(width:200px),$name:mymove,$animate:mymove 2s 1 linear infinite) {
+-webkit-animation: $animate;
+-o-animation: $animate;
+animation: $animate;
+@keyframes #{$name}
+{
+  from {
+    @each $key, $value in $from {
+      #{$key}: #{$value};
+    }
+  }
+  to {
+    @each $key, $value in $to {
+      #{$key}: #{$value};
+    }
+  }
+}
+
+@-webkit-keyframes #{$name}
+{
+  from {
+    @each $key, $value in $from {
+      $key: $value;
+    }
+  }
+  to {
+    @each $key, $value in $to {
+      $key: $value;
+    }
+  }
+}
+}
+
+/* 圆形盒子 */
+@mixin circle($size: 11px,$bg: #fff) {
+border-radius: 50%;
+width: $size;
+height: $size;
+line-height: $size;
+text-align: center;
+background: $bg;
+}
+
+/* placeholder */
+@mixin placeholder($color: #bbb) {
+  // Firefox
+  &::-moz-placeholder {
+    color: $color;
+    opacity: 1; 
+  }
+  // Internet Explorer 10+
+  &:-ms-input-placeholder {
+    color: $color;
+  }
+  // Safari and Chrome
+  &::-webkit-input-placeholder {
+    color: $color;
+  }
+
+  &:placeholder-shown {
+    text-overflow: ellipsis;
+  }
+}

+ 17 - 0
src/assets/scss/_setting.scss

@@ -0,0 +1,17 @@
+$black:                 #000 !default;
+$grayDarker:            #333 !default;
+$grayDark:              #999 !default;
+$gray:                  #ccc !default;
+$grayLight:             #f5f5f5 !default;
+$white:                 #fff !default;
+
+$red:                   #fe3f6c !default;
+$blue:                  #00A3CF !default;
+$blueDark:              #0064cd !default;
+$orange:                #F47837 !default;
+$green:                 #51B148 !default;
+$yellow:                #ffc40d !default;
+$pink:                  #c3325f !default;
+$purple:                #7a43b6 !default;
+$primary:               #0078E7 !default;
+$gray:                  #fafafa !default;

+ 34 - 0
src/common/request.ts

@@ -0,0 +1,34 @@
+import router from '@/router/index'
+import axios from 'axios'
+import Message from '@/utils/Message'
+
+const instance = axios.create({
+    baseURL: 'http://gm.rose.nei.nkfzs.com/api/web',
+    timeout: 6000
+})
+
+// 请求拦截器
+instance.interceptors.request.use((config:any) => {
+    config.headers['Authorization'] = localStorage.getItem('token')
+    return config
+}, err => {
+    return Promise.reject(err)
+})
+
+// 响应拦截器
+instance.interceptors.response.use(response => {
+    return response
+}, err => {
+    if (err.response.status === 401) {
+        localStorage.setItem('token', '')
+        Message.error('登录状态已失效!请重新登录!')
+        setTimeout(() => {
+            window.location.reload()
+            router.push({ path: '/p_login' })
+        }, 1000)
+    }
+    return Promise.reject(err.response)
+})
+
+// 导出工具函数
+export default instance

+ 16 - 0
src/components/Footer.vue

@@ -0,0 +1,16 @@
+<template>
+    <div class="footer df aic jcc">
+        尾部内容
+    </div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style lang="scss" scoped>
+.footer{
+    height: 60px;
+    background-color: #ddd;
+}
+</style>

+ 113 - 0
src/components/IndexList.vue

@@ -0,0 +1,113 @@
+<template>
+    <div v-if="(props.list?.length > 0)" class="list_cont">
+        <div v-for="(item, index) in props.list" :key="('' + item.game_id + index)" class="list df aic jcsb"
+            :class="{ lastStyle: index === props.list.length - 1 }">
+            <div class=" df aic" style="width: 70%;">
+                <img class="left_img" :src="props.prefix + item.logopic">
+                <div class="right_content" style="width:70%">
+                    <h3 class="title df">
+                        <span class="game_title elli">{{ item.screen_name }}
+                        </span>
+                        <span class="game_rate">{{ (item.game_score / 10) > 5 ? '5.0' : (item.game_score /
+                                10).toFixed(1)
+                        }}</span>
+                    </h3>
+                    <p class="elli">{{ item.sketch || '暂无简介' }}</p>
+                    <el-button :disabled="item.download_url === '' ? true : false" @click="downGame(item.download_url)"
+                        color="#ed8c0f" class="downMB" :icon="Download">
+                        {{ bytesChange(item.size) }}
+                    </el-button>
+                </div>
+            </div>
+            <img class="right_img" :src="props.prefix + item.screenshot" />
+
+        </div>
+    </div>
+    <div v-else>
+        <el-empty description="No Data" />
+    </div>
+</template>
+
+<script setup lang="ts">
+import { Download } from '@element-plus/icons-vue'
+import { bytesChange } from '@/utils/bytesFormatter'
+
+const props: any = defineProps({
+    list: Array,
+    prefix: String
+})
+
+const downGame = (url: string) => {
+    // 下载游戏
+    window.open(url)
+    // window.location.href = url;
+}
+</script>
+
+<style lang="scss">
+.list_cont {
+    box-sizing: border-box;
+    background-color: #fff;
+    padding: 30px;
+    // padding: 20px;
+
+    .list {
+        // height: 160px;
+        // margin-bottom: 20px;
+        // cursor: pointer;
+        // padding: 0 20px;
+
+        margin-bottom: 20px;
+        cursor: pointer;
+        border-bottom: 1px solid #eee;
+        padding-bottom: 25px;
+
+        .left_img {
+            height: 130px;
+            width: 130px;
+            border-radius: 10px;
+            margin-right: 25px;
+        }
+
+        .right_content {
+            .title {
+                font-size: 20px;
+                font-weight: normal;
+                margin-bottom: 20px;
+
+                .game_rate {
+                    color: #ed8c0f;
+                    margin-left: 10px;
+                }
+            }
+
+            p {
+                font-size: 14px;
+                margin-bottom: 20px;
+                color: #666;
+            }
+
+            .downMB {
+                width: 100px;
+                padding: 0;
+                color: #fff;
+                border-color: #ed8c0f;
+            }
+        }
+
+    }
+
+    .right_img {
+        width: 280px;
+        height: 150px;
+        border-radius: 10px;
+        // background-color: #0a0;
+    }
+}
+
+.lastStyle {
+    border: none !important;
+    padding-bottom: 0 !important;
+    margin-bottom: 10px !important;
+}
+</style>

+ 120 - 0
src/components/MobCateList.vue

@@ -0,0 +1,120 @@
+<template>
+    <div v-if="(props.gameLis?.length > 0)" class="game_lis">
+        <div class="list_cont df aic jcsb" v-for="(item, index) in props.gameLis" :key="index"
+            :class="{ lastStyle: index === props.gameLis.length - 1 }">
+            <div class="df aic" style="width:80%">
+                <img :src="props.prefix + item.logopic">
+                <div class="game_list df jcsb aic">
+                    <div class=" df fdc" style="width:100%">
+                        <p class="title df aic" style="width:70%">
+                            <span class="title_name elli">{{ item.screen_name }}</span>
+                            <span class="score">{{ (item.game_score / 10) > 5 ? '5.0' : (item.game_score /
+                                    10).toFixed(1)
+                            }}</span>
+                        </p>
+                        <p class="info elli" style="width:70%">{{ item.sketch || '暂无简介' }}</p>
+                        <div class="df fw">
+                            <van-button plain size="mini" class="game_tag" round
+                                v-for="tag in item.tags"><span
+                                    v-for="subTag in props.tagArr.filter(v => { return v.id === tag })">{{ subTag.name
+                                    }}</span>
+                            </van-button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <van-button round :disabled="item.download_url === '' ? true : false" @click="downGame(item.download_url)"
+                color="#ed8c0f" class="downMB">
+                下载
+            </van-button>
+        </div>
+        <!-- <div class="more df aic jcc" style="padding-bottom: 10px;background-color: transparent;" v-if="props.total >= 10">
+            <van-button class="df aic jcc" style="height: 35px;" @click="props.more">更多</van-button>
+        </div> -->
+    </div>
+    <!-- <div v-else>
+        <div class="df aic jcc" style="color: #666;height: 200px;font-size: 22px;">暂无数据</div>
+    </div> -->
+</template>
+
+<script setup lang="ts">
+const props: any = defineProps({
+    gameLis: Array<any>,
+    tagArr: Array<any | undefined>,
+    prefix: String,
+    rank: Boolean,
+    more: Function,
+    total: Number
+})
+const downGame = (url: string) => {
+    // 下载游戏
+    window.open(url)
+    // window.location.href = url;
+}
+</script>
+
+<style scoped lang="scss">
+.game_lis {
+    padding: 10px 15px;
+    box-sizing: border-box;
+}
+
+.list_cont {
+    box-sizing: border-box;
+    margin: 20px 0;
+    border-bottom: 1px solid #eee;
+    padding: 0;
+    padding-bottom: 15px;
+
+    img {
+        width: 90px;
+        height: 80px;
+        margin-right: 20px;
+    }
+
+    .game_list {
+        width: 90%;
+
+        .title {
+            font-size: 18px;
+            color: #323232;
+
+            .title_name {
+                display: inline-block;
+                margin-right: 10px;
+            }
+
+            .score {
+                color: #ed8c0f;
+            }
+        }
+
+        .info {
+            color: rgba(0, 0, 0, .6);
+            margin: 5px 0;
+            font-size: 14px;
+        }
+
+        .game_tag {
+            // margin-right: 3px;
+            color: #ed8c0f;
+            border-color: #ed8c0f;
+            transform: scale(.8);
+            // padding: 2px 3px;
+        }
+    }
+
+    .downMB {
+        width: 60px;
+        height: 30px;
+        color: #fff;
+        border-color: #ed8c0f;
+    }
+}
+
+.lastStyle {
+    border: none !important;
+    padding-bottom: 0 !important;
+    margin-bottom: 10px !important;
+}
+</style>

+ 66 - 0
src/components/MobList.vue

@@ -0,0 +1,66 @@
+<template>
+    <div class="list df aic jcsb" v-for="item in props.gameLis" :key="item.game_id">
+        <div class="df aic" style="width:70%">
+            <img :src="props.prefix + item.logopic">
+            <div class="right_cont" style="width:73%">
+                <p class="title elli">{{ item.screen_name }}</p>
+                <p class="info elli">{{ item.sketch || '暂无简介' }}</p>
+                <p class="down_msg">下载 | <span style="color: #ed8c0f;">{{ bytesChange(item.size) }}</span></p>
+            </div>
+        </div>
+        <van-button :disabled="item.download_url === '' ? true : false"  round color="#ed8c0f" @click="downGame(item.download_url)">下载</van-button>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { bytesChange } from '@/utils/bytesFormatter'
+
+const props: any = defineProps({
+    gameLis: Array,
+    prefix: String
+})
+
+const downGame = (url: string) => {
+    // 下载游戏
+    window.open(url)
+    // window.location.href = url;
+}
+</script>
+
+<style lang="scss" scoped>
+.list {
+    height: 100px;
+    margin-bottom: 0;
+    // padding: 20px 15px 15px;
+
+    img {
+        width: 80px;
+        height: 80px;
+        // background-color: #a00;
+        border-radius: 20px;
+        margin-right: 20px;
+    }
+
+    .right_cont {
+        color: #666;
+
+        .title {
+            font-size: 18px;
+            // font-weight: 600;
+            color: #323332;
+
+        }
+
+        .info {
+            font-size: 14px;
+            margin: 5px 0;
+        }
+
+    }
+}
+
+.van-button {
+    width: 60px;
+    height: 30px;
+}
+</style>

+ 96 - 0
src/components/MobileCom.vue

@@ -0,0 +1,96 @@
+<template>
+    <div class="common df aic">
+        <div class="left_title df jcsb aic">
+            <h1 class="df aic" @click="router.push({ path: 'm_index' })">
+                <img src="http://gm.nkfzs.com/favicon.ico" />
+                <span>朱雀游戏中心</span>
+            </h1>
+            <p><van-icon name="search" size="26" @click="router.push({
+                path
+                    : 'm_search'
+            })" /></p>
+        </div>
+        <div class="right_menu df aic jcc psr">
+            <van-icon name="bars" size="26" />
+            <!-- <van-icon name="apps-o" size="26" /> -->
+            <van-dropdown-menu class="psa" style="left: 30px;">
+                <van-dropdown-item ref="item">
+                    <van-cell class="menu_title" v-for="item in menuPath" :key="item" :title="item.text"
+                        @click="toPath(item.url)"></van-cell>
+                    <van-cell v-if="hasToken" title="退出登录" @click="logOut"></van-cell>
+                </van-dropdown-item>
+            </van-dropdown-menu>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+const router = useRouter()
+const hasToken = ref(localStorage.getItem('token'))
+const item:any = ref(null)
+
+const menuPath: any = ref([
+    { text: '首页', value: 1, url: 'm_index' },
+    // { text: '排行', value: 2, url: 'm_rank' },
+    { text: '分类', value: 3, url: 'm_categroy' },
+    { text: '我的游戏', value: 4, url: 'm_mine' },
+])
+
+const toPath = (url: any) => {
+    router.push({ path: url })
+    item.value.toggle()
+}
+const logOut = () => {
+    localStorage.removeItem('token')
+    router.push({ path: 'm_login' })
+}
+</script>
+
+<style lang="scss" scoped>
+.common {
+    box-sizing: border-box;
+    height: 70px;
+    min-height: 50px;
+    background-color: #fff;
+    padding: 0 15px;
+    border-bottom: 1px solid transparent;
+
+    .left_title {
+        box-sizing: border-box;
+        width: 80%;
+        padding-right: 15px;
+        border-right: 1px dashed #999;
+
+        h1 {
+            font-size: 20px;
+            font-weight: normal;
+        }
+    }
+
+    .right_menu {
+        // padding: 0 20px;
+        padding-left: 25px;
+
+        .menu_title {
+            color: #666;
+            font-size: 16px;
+        }
+    }
+}
+
+:deep(.van-dropdown-menu__bar) {
+    box-shadow: none;
+    background-color: #fff;
+    opacity: -1;
+    width: 0;
+}
+
+@media screen and (max-width: 767px) {
+    .common {
+        border-bottom: 1px solid rgba(0, 0, 0, .1);
+    }
+}
+</style>

+ 165 - 0
src/components/RankList.vue

@@ -0,0 +1,165 @@
+<template>
+    <div v-if="(props.gameLis?.length > 0)" class="list_cont">
+        <div v-for="(item, index) in props.gameLis" :key="index" class="list df aic jcsb"
+            :class="{ lastStyle: index === props.gameLis.length - 1 }">
+            <!-- <div v-if="props.rank" class="category_icon psr"
+                :class="{ firstBg: index === 0, secondBg: index === 1, threeBg: index === 2 }">
+                <span class="num psa">{{ index + 1 }}</span>
+            </div> -->
+            <div class="df aic" style="width: 75%;">
+                <img class="left_img" :src="props.prefix + item.logopic">
+                <div class="right_content" style="width:75%;">
+                    <h3 class="title df">
+                        <span class="game_title elli">{{ item.screen_name }}</span>
+                        <span class="game_rate">{{ (item.game_score / 10) > 5 ? '5.0' : (item.game_score /
+                                10).toFixed(1)
+                        }}</span>
+                    </h3>
+                    <p class="elli">{{ item.sketch || '暂无简介' }}</p>
+                    <el-button plain size="small" class="game_tag" round v-for="tag in item.tags"><span
+                            v-for="subTag in props.tagArr.filter(v => { return v.id === tag })">{{ subTag.name }}</span>
+                    </el-button>
+                </div>
+            </div>
+            <el-button :disabled="item.download_url === '' ? true : false" @click="downGame(item.download_url)"
+                color="#ed8c0f" class="downMB" :icon="Download" >
+                {{ bytesChange(item.size) }}
+            </el-button>
+        </div>
+    </div>
+
+</template>
+
+<script setup lang="ts">
+import { Download } from '@element-plus/icons-vue'
+import { bytesChange } from '@/utils/bytesFormatter'
+
+const props: any = defineProps({
+    gameLis: Array<any>,
+    tagArr: Array<any | undefined>,
+    prefix: String,
+    rank: Boolean,
+    more: Function,
+    total: Number
+})
+
+const downGame = (url: string) => {
+    // 下载游戏
+    window.open(url)
+    // window.location.href = url;
+}
+</script>
+
+<style lang="scss">
+.list_cont {
+    box-sizing: border-box;
+    background-color: #fff;
+    // padding: 30px 20px 20px 40px;
+    padding: 30px;
+
+    .list {
+        margin-bottom: 20px;
+        cursor: pointer;
+        border-bottom: 1px solid #eee;
+        padding-bottom: 25px;
+
+        .category_icon {
+            width: 48px;
+            height: 48px;
+            background-color: #ccc;
+            border-radius: 50%;
+            margin-left: 10px;
+            margin-right: 50px;
+            color: #fff;
+            font-size: 20px;
+
+            .num {
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+                z-index: 1;
+            }
+        }
+
+        .firstBg {
+            background-color: rgb(238, 54, 54);
+        }
+
+        .secondBg {
+            background-color: rgb(239, 154, 94);
+        }
+
+        .threeBg {
+            background-color: rgb(209, 175, 55);
+        }
+
+
+        img {
+            height: 130px;
+            width: 130px;
+            margin-right: 25px;
+            border-radius: 20px;
+        }
+
+        .right_content {
+            .title {
+                font-size: 20px;
+                font-weight: normal;
+                margin-bottom: 20px;
+
+                .game_rate {
+                    color: #ed8c0f;
+                    margin-left: 10px;
+                }
+            }
+
+            p {
+                font-size: 14px;
+                color: #666;
+                margin-bottom: 20px;
+            }
+
+            .game_tag {
+                color: #ed8c0f;
+                border-color: #ed8c0f;
+                font-size: 12px;
+            }
+
+        }
+    }
+    
+}
+.downMB {
+    width: 100px;
+    padding: 0;
+    color: #fff;
+    border-color: #ed8c0f;
+}
+
+.left_img {
+            height: 130px;
+            width: 130px;
+            border-radius: 10px;
+            margin-right: 25px;
+        }
+
+.more {
+    margin: 20px auto;
+    height: 60px;
+    background-color: #f7f7f7;
+
+    .el-button {
+        width: 100px;
+        height: 40px;
+        background-color: transparent;
+        color: #323332;
+        border: 1px solid #c7c7c7;
+    }
+}
+
+.lastStyle {
+    border: none !important;
+    padding-bottom: 0 !important;
+    margin-bottom: 10px !important;
+}
+</style>

+ 82 - 0
src/components/loading.vue

@@ -0,0 +1,82 @@
+<template>
+    <div class='loading df jcc aic'>
+        <div class="loader"></div>
+    </div>
+</template>
+<style scoped lang="scss">
+.loading {
+    width: 100%;
+    height: 100%;
+    // max-height: 700px;
+    min-height: 150px;
+}
+
+.loader {
+    width: 2.5em;
+    height: 2.5em;
+    transform: rotate(165deg);
+
+    &::before,
+    &::after {
+        content: '';
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        display: block;
+        width: 0.5em;
+        height: 0.5em;
+        border-radius: 0.25em;
+        transform: translate(-50%, -50%);
+    }
+
+    &::before {
+        animation: before-ani 2s infinite;
+    }
+
+    &::after {
+        animation: after-ani 2s infinite;
+    }
+}
+
+@keyframes before-ani {
+    0% {
+        width: 0.5em;
+        box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
+    }
+
+    35% {
+        width: 2.5em;
+        box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75), 0 0.5em rgba(111, 202, 220, 0.75);
+    }
+
+    70% {
+        width: 0.5em;
+        box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75), 1em 0.5em rgba(111, 202, 220, 0.75);
+    }
+
+    100% {
+        box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
+    }
+}
+
+@keyframes after-ani {
+    0% {
+        height: 0.5em;
+        box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
+    }
+
+    35% {
+        height: 2.5em;
+        box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75), -0.5em 0 rgba(233, 169, 32, 0.75);
+    }
+
+    70% {
+        height: 0.5em;
+        box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75), -0.5em 1em rgba(233, 169, 32, 0.75);
+    }
+
+    100% {
+        box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
+    }
+}
+</style>

+ 54 - 0
src/layout/Layout.vue

@@ -0,0 +1,54 @@
+<template>
+    <div class="gameIndex df fdc psr">
+        <!-- 头部 -->
+        <header-vue class="header psf"></header-vue>
+        <!-- 内容 -->
+        <section-vue class="main"></section-vue>
+        <!-- 尾部 -->
+        <!-- <footer-vue class="footer"></footer-vue> -->
+        <el-backtop :bottom="100">
+            <div class="df aic jcc" style="
+        height: 100%;
+        width: 100%;
+        background-color: var(--el-bg-color-overlay);
+        box-shadow: var(--el-box-shadow-lighter);
+        color: #ed8c0f;
+        border-radius: 50%;
+      ">
+                <!-- <el-icon><Top /></el-icon> -->
+                UP
+            </div>
+        </el-backtop>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { Top } from '@element-plus/icons-vue';
+import footerVue from './footer.vue';
+import headerVue from './header.vue';
+import sectionVue from './section.vue';
+
+</script>
+
+<style scoped lang="scss">
+.gameIndex {
+    background-color: #f7f7f7;
+
+    .header {
+        width: 100%;
+        height: 80px;
+        top: 0;
+        left: 0;
+        background-color: #fff;
+    }
+
+    .main {
+        margin-top: 80px;
+    }
+
+    .footer {
+        height: 100px;
+        background-color: #e7e7e7;
+    }
+}
+</style>

+ 18 - 0
src/layout/footer.vue

@@ -0,0 +1,18 @@
+<template>
+    <div class="foot">
+        <div class="w1000 footer_cont df aic jcc">
+            尾部内容
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style scoped>
+.footer_cont {
+    height: 100%;
+    /* background-color: rgb(152, 227, 152); */
+}
+</style>

+ 221 - 0
src/layout/header.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="header_nav">
+    <nav class="w1000 df jcsb aic">
+      <!-- 网站title -->
+      <h1 class="game_logo df aic" @click="clearAct">
+        <a class="logo" href="/p_index" alt="朱雀游戏官网" title="朱雀游戏官网">朱雀游戏中心</a>
+      </h1>
+
+      <!-- 首页、分类、排行 -->
+      <router-link v-for="(item, index) in routes" :key="index" class="nav_title" :to="item.path"
+        @click="changeIndex(item.title)" :class="{ active: currentIndex === item.title }">
+        {{ item.title }}
+      </router-link>
+
+      <!-- 搜索框 -->
+      <el-autocomplete v-model="game_name" :fetch-suggestions="searchChange" placeholder="请输入游戏名称" :prefix-icon="Search"
+        clearable @select="handleSelect" @keyup.native.enter="handleKeyEnter" maxlength="30" />
+
+      <!-- 登录 -->
+      <router-link class="nav_title" :to="hasToken ? '' : 'p_login'" @click="clearActive">
+        <img v-if="!hasToken" src="https://static.g.mi.com/game/websites/public/img/portrait_default.06318944.png" />
+        <el-dropdown trigger="click" v-else>
+          <span class="el-dropdown-link">
+            <img src="https://static.g.mi.com/game/websites/public/img/portrait_default.06318944.png" />
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item class="clearfix" @click="logOut">
+                退出登录
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </router-link>
+    </nav>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, watch } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+import { useRouter, useRoute } from 'vue-router'
+import { getGameList } from '@/api/index'
+import Message from '@/utils/Message'
+import local from '@/utils/local'
+import { useStore } from '@/store'
+
+const router = useRouter()
+const route = useRoute()
+const search = useStore('search')
+const header = useStore('header')
+
+// 搜索内容
+const game_name = ref(local.get('searchText') || '')
+// 绑定el-autocomplete组件
+const myAutocomplete = ref(null)
+const hasToken = ref(localStorage.getItem('token'))
+
+const currentIndex = ref(local.get('headerPath') || header.getHeaderTitle)   //取保存的title
+console.log('currentIndex', currentIndex.value);
+
+if (route.query.account) {
+  currentIndex.value = '我的'
+  console.log('1234567', currentIndex.value);
+}
+
+// 顶部导航栏
+const routes = reactive([
+  { id: 0, path: '/p_index', title: '首页' },
+  // { id: 1, path: '/p_rank', title: '排行' },
+  { id: 2, path: '/p_categroy', title: '分类' },
+  { id: 3, path: '/p_mine', title: '我的' }
+])
+
+// 改变字体颜色
+const changeIndex = (val: string) => {
+  currentIndex.value = val
+  local.set('headerPath', currentIndex.value)  //保存当前title
+  local.remove('searchText')
+  game_name.value = ''
+}
+
+// 点击登录清除本地存储
+const clearActive = () => {
+  currentIndex.value = ''
+  local.remove('headerPath')           //删除保存的title
+  local.remove('searchText')
+  game_name.value = ''
+}
+// 点击网页title清除本地存储
+const clearAct = () => {
+  local.remove('headerPath')
+  currentIndex.value = '首页'
+  local.set('headerPath', currentIndex.value)
+  local.remove('searchText')
+  game_name.value = ''
+}
+
+// 退出登录
+const logOut = () => {
+  // console.log(22);
+  localStorage.removeItem('token')
+  router.push({ path: 'p_login' })
+}
+
+interface LinkItem {
+  id: string
+  value: string
+}
+const links: any = ref<LinkItem[]>([])
+
+const searchChange = async (str: string, cb: (arg: any) => void) => {
+  if (str && str.length > 0) {
+    game_name.value = str
+    currentIndex.value = ''
+    local.remove('headerPath')          
+    try {
+      var params = reactive({
+        type: 0,
+        tag_id: 0,
+        page: 1,
+        pagesize: 10,
+        q: str
+      })
+      var { data } = await getGameList(params)
+      links.value = data.data.lists
+        links.value = links.value.map((item, index) => {
+          return {
+            id: `${index}`,
+            value: `${item.screen_name}`
+          }
+        })
+        links.value = links.value.filter(item => {
+          return item.value.indexOf(str) > -1
+        })
+        cb(links.value)
+      
+    } catch (error: any) {
+      // links.value.push({
+      //   id: '-1',
+      //   value: '暂无匹配数据'
+      // })
+      // cb(links.value)
+      console.log(error)
+      Message.error(error.data.msg)
+    }
+  } else {
+    cb([])
+  }
+}
+
+const handleSelect = (item: LinkItem) => {
+  if (item) {
+    let { value } = item
+    router.push({ path: '/p_search', query: { screen_name: value } })
+    search.setSearchText(game_name.value)
+    local.set('searchText', game_name.value)
+    search.setSearchLis(game_name.value)
+  }
+}
+
+// 手动回车跳转搜索页
+const handleKeyEnter = () => {
+  router.push({ path: '/p_search', query: { screen_name: game_name.value } })
+  search.setSearchText(game_name.value)
+  local.set('searchText', game_name.value)
+  search.setSearchLis(game_name.value)
+}
+
+watch(currentIndex, (iold, inew) => { }, { deep: true, immediate: true })
+</script>
+
+<style scoped lang="scss">
+.header_nav{
+  margin: auto;
+}
+nav {
+  height: 100%;
+
+  .game_logo {
+    height: 100%;
+    line-height: 80px;
+    font-weight: normal;
+
+    .logo {
+      font-size: 24px;
+      color: #000;
+      background: url('http://gm.nkfzs.com/favicon.ico') no-repeat center left;
+      background-size: 30px 30px;
+      height: 100%;
+      padding-left: 37px;
+    }
+  }
+
+  .nav_title {
+    font-size: 20px;
+    text-align: center;
+
+    &:hover {
+      color: #ed8c0f
+    }
+  }
+
+  .active {
+    color: #ed8c0f
+  }
+
+}
+
+:deep(.el-input) {
+  width: 200px;
+}
+
+:deep(.el-input__wrapper) {
+  border-radius: 20px;
+}
+
+// .el-dropdown {
+//   margin-top: 1.1rem;
+// }
+</style>

+ 12 - 0
src/layout/section.vue

@@ -0,0 +1,12 @@
+<template>
+    <!-- 二级路由出口 -->
+    <router-view></router-view>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style scoped>
+
+</style>

+ 34 - 0
src/main.ts

@@ -0,0 +1,34 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+
+// 引入element-plus
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+
+//引入样式
+import './assets/css/normalize.css'
+import './assets/css/property.css'
+import './assets/css/reset.css'
+
+// 引入pinia状态管理工具
+import { store } from './store'
+
+// 引入适配库
+import 'lib-flexible'
+
+// 引入全局组件
+import loading from '@/components/loading.vue'
+import MobileCom from '@/components/MobileCom.vue'
+import Footer from './components/Footer.vue'
+
+const app = createApp(App)
+
+app
+.use(ElementPlus)
+.use(router)
+.use(store)
+.component('loading', loading)
+.component('m_header', MobileCom)
+.component('m_footer', Footer)
+.mount('#app')

+ 152 - 0
src/router/index.ts

@@ -0,0 +1,152 @@
+import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
+
+const redirectPath = /Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test( navigator.userAgent) ? "/m_index" : "/p_index"
+
+// 引入页面级别组件
+// 静态加载  初始化可见的页面----首屏加速
+import Layout from '@/layout/Layout.vue'   
+
+const routes:Array<RouteRecordRaw> = [
+  {
+    path: '/',
+    redirect: redirectPath
+  },
+  // pc
+  {
+    path: '/p_index',
+    name: 'p_index',
+    component: Layout,
+    meta: { type: 'pc', title:'朱雀游戏中心' },
+    children: [
+      {
+        path: '/p_index',
+        component: () => import('@/view/p_views/index/index.vue')
+      }
+    ]
+  },
+  // 排行
+  {
+    path: '/p_rank',
+    name: 'p_rank',
+    component: Layout,
+    meta: { type: 'pc', title:'游戏排行' },
+    children: [
+      {
+        path: '/p_rank',
+        component: () => import('@/view/p_views/rank/index.vue')
+      }
+    ]
+  },
+  // 分类
+  {
+    path: '/p_categroy',
+    name: 'p_categroy',
+    component: Layout,
+    meta: { type: 'pc', title:'游戏分类' },
+    children: [
+      {
+        path: '/p_categroy',
+        component: () => import('@/view/p_views/categroy/index.vue')
+      }
+    ]
+  },
+  // 登录
+  {
+    path: '/p_login',
+    name: 'p_login',
+    component: Layout,
+    meta: { type: 'pc', title:'游戏中心登录页' },
+    children: [
+      {
+        path: '/p_login',
+        component: () => import('@/view/p_views/login/index.vue')
+      }
+    ]
+  },
+   // 我的
+   {
+    path: '/p_mine',
+    name: 'p_mine',
+    component: Layout,
+    meta: { type: 'pc', title:'我的游戏' },
+    children: [
+      {
+        path: '/p_mine',
+        component: () => import('@/view/p_views/mine/index.vue')
+      }
+    ]
+  },
+  // 搜索
+  {
+    path: '/p_search',
+    name: 'p_search',
+    component: Layout,
+    meta: { type: 'pc', title:'游戏搜索' },
+    children: [
+      {
+        path: '/p_search',
+        component: () => import('@/view/p_views/search/index.vue')
+      }
+    ]
+  },
+
+  // mobile
+  {
+    path: '/m_index',
+    name: 'm_index',
+    meta: { type: 'mobile', title:'朱雀游戏中心' },
+    component: () => import('@/view/m_views/index/index.vue')
+  },
+  {
+    path: '/m_rank',
+    name: 'm_rank',
+    meta: { type: 'mobile', title:'排行' },
+    component: () => import('@/view/m_views/rank/index.vue')
+  },
+  {
+    path: '/m_categroy',
+    name: 'm_categroy',
+    meta: { type: 'mobile', title:'游戏分类' },
+    component: () => import('@/view/m_views/categroy/index.vue')
+  },
+  {
+    path: '/m_mine',
+    name: 'm_mine',
+    meta: { type: 'mobile', title:'我的游戏' },
+    component: () => import('@/view/m_views/mine/index.vue')
+  },
+  {
+    path: '/m_login',
+    name: 'm_login',
+    meta: { type: 'mobile', title:'游戏中心登录页' },
+    component: () => import('@/view/m_views/login/index.vue')
+  },
+  {
+    path: '/m_search',
+    name: 'm_search',
+    meta: { type: 'mobile', title:'游戏搜索' },
+    component: () => import('@/view/m_views/search/index.vue')
+  }
+]
+
+
+
+const router = createRouter({
+  history: createWebHistory(),    //process.meta.VITE_HOST_API
+  routes,
+})
+
+// 修改浏览器标题
+router.beforeEach((to, form, next) => {
+  
+  const title:any = to.meta.title
+
+  if (to.meta.title) {
+      document.title = title
+  } else {
+      document.title = '朱雀游戏中心' //此处写默认的title
+  }
+  next()
+})
+
+export default router

+ 20 - 0
src/store/header.ts

@@ -0,0 +1,20 @@
+import { defineStore } from 'pinia'
+
+const userHeader = defineStore({
+    id: 'userHeader',
+    state: () => ({
+        headerTitle:''
+    }),
+    actions: {
+        setHeaderTitle(data){
+            this.headerTitle = data
+        }
+    },
+    getters: {
+        getHeaderTitle(state){
+            return state.headerTitle
+        }
+    }
+})
+
+export { userHeader }

+ 17 - 0
src/store/index.ts

@@ -0,0 +1,17 @@
+import { createPinia } from 'pinia'
+import {userInfo} from './user'
+import { useSearch } from './search'
+import { userHeader } from './header'
+
+export const store = createPinia()
+
+const storeObj:any = {
+  user: userInfo,
+  search: useSearch,
+  header: userHeader
+}
+
+// 封装成useStore的形式,这样一看引用就知道是store的数据
+export function useStore(key: string) {
+  return storeObj[key]()
+}

+ 72 - 0
src/store/search.ts

@@ -0,0 +1,72 @@
+import { defineStore } from 'pinia'
+import { getGameList } from '@/api/index'
+import Message from '@/utils/Message'
+
+const useSearch = defineStore({
+    id: 'search',
+    state: (): any => ({
+        searchText: '',
+        searchLis: [],
+        type: 0,
+        tag_id: 0,
+        page: 1,
+        pagesize: 10,
+        q: '',
+        prefix: '',
+        isLoading: true
+    }),
+    actions: {
+        setSearchText(data) {
+            this.searchText = data
+            // console.log('this.searchText', this.searchText);
+        },
+        async setSearchLis(data) {
+            this.q = data
+            // console.log('5555', this.searchText);
+
+            if (this.searchText) {
+                await getGameList({ type: this.type, tag_id: this.tag_id, page: this.page, pagesize: this.pagesize, q: this.q }).then(res => {
+                    if (res.data.code === 200 && res.data.data) {
+                        // console.log('res', res);
+                        this.isLoading = false
+                        this.prefix = res.data.data.prefix
+                        this.searchLis = []
+                        this.searchLis = this.searchLis.concat(res.data.data.lists.filter(item => item.screen_name.includes(this.q)))
+                        console.log('this.searchLis', this.searchLis);
+                    }
+                }).catch(err => {
+                    Message.error(err.data.msg)
+                })
+            }
+            else {
+                console.log(222);
+                await getGameList({ type: this.type, tag_id: this.tag_id, page: this.page, pagesize: this.pagesize, q: this.q }).then(res => {
+                    if (res.data.code === 200 && res.data.data) {
+                        // console.log('res', res);
+                        this.isLoading = false
+                        this.prefix = res.data.data.prefix
+                        this.searchLis = [...this.searchLis, ...res.data.data.lists]
+                    }
+                }).catch(err => {
+                    Message.error(err.data.msg)
+                })
+            }
+        }
+    },
+    getters: {
+        getSearchText(state) {
+            return state.searchText
+        },
+        getSearchLis(state) {
+            return state.searchLis
+        },
+        getLoading(state) {
+            return state.isLoading
+        },
+        getPrefix(state) {
+            return state.prefix
+        }
+    }
+})
+
+export { useSearch }

+ 24 - 0
src/store/user.ts

@@ -0,0 +1,24 @@
+import { defineStore } from 'pinia'
+
+const userInfo = defineStore({
+    id: 'userInfo',
+    state: () => ({
+        UserInfo: {
+            account: '',
+            password: ''
+        }
+    }),
+    actions: {
+        setUerInfo(data: any) {
+            this.UserInfo = data
+            console.log('UserInfo', this.UserInfo);
+        }
+    },
+    getters: {
+        getAccount(state){
+            return state.UserInfo.account
+        }
+    }
+})
+
+export { userInfo }

+ 10 - 0
src/utils/Message.ts

@@ -0,0 +1,10 @@
+import { ElMessage } from 'element-plus'
+
+export default ({
+  success(msgInfo: any) {
+    return ElMessage.success({ message: msgInfo, ...{ duration: 1800, grouping: true }})
+  },
+  error(msgInfo: any) {
+    return ElMessage.error({ message: msgInfo, ...{ duration: 1800, grouping: true }})
+  }
+})

+ 20 - 0
src/utils/bytesFormatter.ts

@@ -0,0 +1,20 @@
+export const bytesChange = (limit:number) => {
+    var size = "";
+    if(limit < 1 * 1024){                            //小于1KB,则转化成B
+        size = limit.toFixed(2) + "B"
+    }else if(limit < 1 * 1024 * 1024){            //小于1MB,则转化成KB
+        size = (limit/1024).toFixed(2) + "KB"
+    }else if(limit < 1 * 1024 * 1024 * 1024){        //小于1GB,则转化成MB
+        size = (limit/(1024 * 1024)).toFixed(2) + "MB"
+    }else{                                            //其他转化成GB
+        size = (limit/(1024 * 1024 * 1024)).toFixed(2) + "GB"
+    }
+ 
+    var sizeStr = size + "";                        //转成字符串
+    var index = sizeStr.indexOf(".");                    //获取小数点处的索引
+    var dou = sizeStr.substr(index + 1 ,2)            //获取小数点后两位的值
+    if(dou == "00"){                                //判断后两位是否为00,如果是则删除00               
+        return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
+    }
+    return size;
+}

+ 23 - 0
src/utils/local.ts

@@ -0,0 +1,23 @@
+/*
+    window.localStorage 本地存储
+*/
+
+export default {
+    //存
+    set(key:string, val:any){
+        sessionStorage.setItem(key, JSON.stringify(val))
+    },
+    //取
+    get(key:string){
+        const json:any =  sessionStorage.getItem(key)
+        return JSON.parse(json)
+    },
+    //删除
+    remove(key:string){
+        sessionStorage.removeItem(key)
+    },
+    //清空
+    clear(){
+        sessionStorage.clear()
+    }
+}

+ 251 - 0
src/utils/md5.ts

@@ -0,0 +1,251 @@
+var KEY = "!@#QWERT";
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function md5(s) {
+    return hex_md5(s);
+}
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+    return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+    /* append padding */
+    x[len >> 5] |= 0x80 << ((len) % 32);
+    x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+    var a =  1732584193;
+    var b = -271733879;
+    var c = -1732584194;
+    var d =  271733878;
+
+    for(var i = 0; i < x.length; i += 16)
+    {
+        var olda = a;
+        var oldb = b;
+        var oldc = c;
+        var oldd = d;
+
+        a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+        d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+        c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+        b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+        a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+        d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+        c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+        b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+        a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+        d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+        c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+        b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+        a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+        d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+        c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+        b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+        a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+        d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+        c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+        b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+        a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+        d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+        c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+        b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+        a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+        d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+        c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+        b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+        a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+        d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+        c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+        b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+        a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+        d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+        c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+        b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+        a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+        d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+        c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+        b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+        a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+        d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+        c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+        b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+        a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+        d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+        c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+        b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+        a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+        d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+        c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+        b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+        a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+        d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+        c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+        b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+        a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+        d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+        c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+        b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+        a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+        d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+        c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+        b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+        a = safe_add(a, olda);
+        b = safe_add(b, oldb);
+        c = safe_add(c, oldc);
+        d = safe_add(d, oldd);
+    }
+    return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+    return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+    return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+    return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+    return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+    return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+    var bkey = str2binl(key);
+    if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+    var ipad = Array(16), opad = Array(16);
+    for(var i = 0; i < 16; i++)
+    {
+        ipad[i] = bkey[i] ^ 0x36363636;
+        opad[i] = bkey[i] ^ 0x5C5C5C5C;
+    }
+
+    var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+    return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+    return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+    return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+    var bin = Array();
+    var mask = (1 << chrsz) - 1;
+    for(var i = 0; i < str.length * chrsz; i += chrsz)
+        bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+    return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+    var str = "";
+    var mask = (1 << chrsz) - 1;
+    for(var i = 0; i < bin.length * 32; i += chrsz)
+        str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+    return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+    var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+    var str = "";
+    for(var i = 0; i < binarray.length * 4; i++)
+    {
+        str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+            hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+    }
+    return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    var str = "";
+    for(var i = 0; i < binarray.length * 4; i += 3)
+    {
+        var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+            | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+            |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+        for(var j = 0; j < 4; j++)
+        {
+            if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+            else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+        }
+    }
+    return str;
+}

+ 9 - 0
src/utils/sort.ts

@@ -0,0 +1,9 @@
+// 数组对象排序
+export const gameLisSort = (arr: Array<object | any>, key: string) => {
+    return arr.sort((a, b) => {
+        let x = a[key]
+        let y = b[key]
+        return ((x > y) ? -1 : (x < y) ? 1 : 0)     //从大到小
+        // return ((x < y) ? -1 : (x > y) ? 1 : 0)     //从小到大
+    })
+}

+ 12 - 0
src/utils/throttle.ts

@@ -0,0 +1,12 @@
+export const throttle = (fu:any,time:number) => {
+    let loading = false
+    return function(){
+        if(loading){
+            fu()
+            loading = true
+            setTimeout(() => {
+                loading = false
+            }, time);
+        }
+    }
+}

+ 163 - 0
src/view/m_views/categroy/index.vue

@@ -0,0 +1,163 @@
+<template>
+    <m_header></m_header>
+    <div class="top_select">
+        <van-dropdown-menu>
+            <van-dropdown-item title="类型" ref="item">
+                <van-cell v-for="item in typeArr" :key="item.id" :title="item.name"
+                    :class="{ active: typeIndex === item.id }" @click="selectType('type', item.id)"></van-cell>
+            </van-dropdown-item>
+            <van-dropdown-item title="标签" ref="tag">
+                <van-cell v-for="item in tagArr" :key="item.id" :title="item.name"
+                    :class="{ active: tag_idIndex === item.id }" @click="selectType('tag_id', item.id)"></van-cell>
+            </van-dropdown-item>
+            <!-- <van-dropdown-item title="排序" ref="order">
+                <van-cell v-for="(item, index) in categoryList" :key="index" :title="item.title"
+                    :class="{ active: orderIndex === item.id }" @click="selectType('order', index)"></van-cell>
+            </van-dropdown-item> -->
+        </van-dropdown-menu>
+    </div>
+    <!-- <MobCateList :gameLis="gameLis" :prefix="prefix" :rank="false" :tagArr="tagArr" :more="loadMore" :total="total">
+    </MobCateList> -->
+    <div v-if="(gameLis.length > 0)">
+        <MobCateList :gameLis="gameLis" :prefix="prefix" :rank="false" :tagArr="tagArr">
+        </MobCateList>
+        <div style="padding: 10px;background-color: #f7f7f7;" class="df aic jcc"
+            v-if="params.page * params.pagesize <= gameLis.length">
+            <el-button class="df aic jcc" plain @click="loadMore">更多</el-button>
+        </div>
+        <div v-else>
+            <div class="df aic jcc" style="color: #999;height: 100px;font-size: 16px;">----- 没有更多数据了 -----</div>
+        </div>
+    </div>
+    <van-empty v-else description="No Data" />
+
+    <van-back-top style="background-color: #ed8c0f;" />
+    <!-- <m_footer></m_footer> -->
+
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, watch } from 'vue'
+import { getGameType, getGameTag, getGameList } from '@/api/index'
+import MobCateList from '@/components/MobCateList.vue'
+import Message from '@/utils/Message'
+
+const typeIndex = ref<number>(0)
+const tag_idIndex = ref<number>(0)
+const gameLis: any = ref([])
+const typeArr: any = ref([])
+const tagArr: any = ref([])
+const prefix = ref<string>('')
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+const total = ref<number>(0)
+const item: any = ref(null)
+const tag: any = ref(null)
+
+interface Params {
+    type: number,
+    tag_id: number,
+    page: number,
+    pagesize: number
+}
+const params = reactive<Params>({
+    type: 0,
+    tag_id: 0,
+    page: 1,
+    pagesize: 15
+})
+
+onMounted(async () => {
+    await getGameType().then(res => {
+        // console.log('类型', res);
+        typeArr.value = res.data.data
+        typeArr.value.unshift({ id: 0, name: '全部' })
+    })
+    await getGameTag().then(res => {
+        // console.log('标签', res);
+        tagArr.value = res.data.data
+        tagArr.value.unshift({ id: 0, name: '全部' })
+    })
+    getGameLists(params)
+})
+
+// 初始化列表数据
+const getGameLists = async (params) => {
+    // console.log('参数', params);
+
+    await getGameList(params).then(res => {
+        // console.log('数据', res);
+        if (res.data.code === 200 && res.data.data) {
+            gameLis.value = gameLis.value.concat(res.data.data.lists)
+            total.value = res.data.data.total
+            prefix.value = res.data.data.prefix
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+}
+
+// 点击类型、标签、排序
+const selectType = (type: any, index: any) => {
+    gameLis.value = []
+    params.page = 1
+    if (type === 'type') {
+        typeIndex.value = index
+        params.type = index
+        item.value.toggle()
+    }
+    if (type === 'tag_id') {
+        tag_idIndex.value = index
+        params.tag_id = index
+        tag.value.toggle()
+    }
+    if (params.type === 0 && params.tag_id === 0) {
+        params.pagesize = 15
+        getGameLists(params)
+    } else {
+        allData()
+    }
+}
+// 全部数据
+const allData = async () => {
+    params.pagesize = total.value
+    // console.log('params', params);
+    await getGameList(params).then(resp => {
+        // console.log('resp===>', resp);
+        if (resp.data.code === 200 && resp.data.data) {
+            gameLis.value = resp.data.data.lists.filter(item => {
+                if (params.tag_id === 0) {
+                    return item.type === params.type
+                }
+                else if (params.type === 0) {
+                    return item.tags.includes(params.tag_id)
+                }
+                else return item.type === params.type && item.tags.includes(params.tag_id)
+            })
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+}
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+    getGameLists(params)
+}
+</script>
+
+<style lang="scss" scoped>
+.active {
+    color: #ed8c0f
+}
+
+:deep(.van-dropdown-menu__title--active) {
+    color: #ed8c0f
+}
+
+:deep(.van-dropdown-menu) {
+    width: 100%
+}
+</style>

+ 215 - 0
src/view/m_views/index/index.vue

@@ -0,0 +1,215 @@
+<template>
+    <div id="index">
+        <!-- 顶部搜索栏 -->
+        <div class="top_nav df aic jcsb">
+            <div class="left_title df aic">
+                <img src="http://gm.nkfzs.com/favicon.ico" />
+                <span>朱雀游戏中心</span>
+            </div>
+            <van-search shape='round' @click="router.push({ path: 'm_search' })" />
+        </div>
+
+        <!-- 轮播图 -->
+        <van-swipe class="my-swipe" indicator-color="white" :autoplay="3000">
+            <van-swipe-item v-for="item in listSwiper" :key="item.game_id" class="psr">
+                <img :src="prefix + item.screenshot">
+                <div class="game_card psa df fdc aic">
+                    <img :src="prefix + item.logopic">
+                    <p class="title elli">{{ item.screen_name }}</p>
+                    <van-button :disabled="item.download_url === '' ? true : false" round color="#ed8c0f"
+                        @click="downGame(item.download_url)">下载</van-button>
+                </div>
+            </van-swipe-item>
+        </van-swipe>
+
+        <!-- 菜单栏 -->
+        <div class="menu_cate df aic jcsa">
+            <div v-for="(item, index) in cateList" :key="index" class="cate_card df fdc aic jcc"
+                @click="toPath(item.pathUrl)">
+                <van-icon :name="item.icon" size="20" class="icon"></van-icon>
+                <span class="title">{{ item.title }}</span>
+            </div>
+        </div>
+
+        <!-- 游戏列表 -->
+        <div class="games">
+            <h3>热门游戏</h3>
+            <MobList :gameLis="list" :prefix="prefix"></MobList>
+            <h3 style="margin-top: 30px;">推荐游戏</h3>
+            <MobList :gameLis="listRecommand" :prefix="prefix"></MobList>
+        </div>
+
+    </div>
+    <van-back-top style="background-color: #ed8c0f;" />
+    <!-- <m_footer></m_footer> -->
+</template>
+
+<script setup lang="ts">
+import { getIndexGameHot, getIndexGameRecommand } from '@/api/index';
+import MobList from '@/components/MobList.vue';
+import { onMounted, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import Message from '@/utils/Message'
+
+const router = useRouter()
+const list: any = ref([])
+const listRecommand: any = ref([])
+const listSwiper: any = ref([])
+const prefix = ref<string>('')
+
+interface ICate {
+    title: string,
+    icon: string,
+    pathUrl: string
+}
+const cateList = ref<Array<ICate>>([
+    // { title: '排行', icon: 'medal-o', pathUrl: 'm_rank' },
+    { title: '分类', icon: 'description', pathUrl: 'm_categroy' },
+    { title: '我的游戏', icon: 'user-o', pathUrl: 'm_mine' }
+])
+
+onMounted(async () => {
+    // 热门游戏
+    await getIndexGameHot().then(res => {
+        if (res.data.code === 200 && res.data.data) {
+            list.value = res.data.data.lists
+            prefix.value = res.data.data.prefix
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+
+    // 推荐游戏
+    await getIndexGameRecommand().then(res => {
+        if (res.data.code === 200 && res.data.data) {
+            listRecommand.value = res.data.data.lists
+            prefix.value = res.data.data.prefix
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+
+    // 轮播图数据
+    await getIndexGameHot().then(res => {
+        // console.log(res);
+        if (res.data.code === 200 && res.data.data) {
+            prefix.value = res.data.data.prefix
+            listSwiper.value = res.data.data.lists.splice(0, 5)
+            // console.log('listSwiper.value', listSwiper.value);
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+})
+
+const toPath = (url: string) => {
+    router.push({ path: url })
+}
+
+const downGame = (url: string) => {
+    // 下载游戏
+    window.open(url)
+    // window.location.href = url;
+}
+</script>
+
+<style lang="scss" scoped>
+#index {
+    box-sizing: border-box;
+    padding: 15px;
+    // background: radial-gradient(circle at 0 -10%,#dcf4f7,rgba(220,244,247,0) 50%),radial-gradient(circle at 100% 10%,#e8ebfc,rgba(232,235,252,0) 50%);
+    background: radial-gradient(circle at 0 -20%, #ed8c0f, rgba(255, 255, 255, .6) 25%), radial-gradient(circle at 100% 10%, #eabf85, rgba(255, 255, 255, .6) 30%);
+
+    .top_nav {
+        margin-bottom: 10px;
+
+        .left_title {
+            font-size: 22px;
+        }
+    }
+
+    .game_card {
+        bottom: 10px;
+        right: 10px;
+        width: 80px;
+        box-sizing: border-box;
+
+        img {
+            width: 35px;
+            height: 35px;
+            border-radius: 10px;
+        }
+
+        .title {
+            font-size: 12px;
+            color: #323332;
+            line-height: 14px;
+            margin: 5px 0;
+            width: 95%;
+        }
+
+        .van-button {
+            width: 60px;
+            height: 25px;
+            font-size: 12px;
+        }
+    }
+
+    .menu_cate {
+        margin-top: 10px;
+
+        .cate_card {
+            height: 80px;
+
+            .icon {
+                margin-bottom: 10px;
+            }
+
+            .title {
+                font-size: 16px;
+            }
+        }
+    }
+
+    .games {
+        margin-top: 10px;
+        box-sizing: border-box;
+
+        h3 {
+            font-size: 20px;
+            font-weight: normal;
+            margin-bottom: 8px;
+        }
+    }
+}
+
+:deep(.van-search) {
+    width: 50%;
+    background-color: transparent;
+    border-color: #b1d5ec;
+}
+
+.my-swipe {
+    border-radius: 20px;
+}
+
+.van-swipe-item {
+    color: #fff;
+    font-size: 20px;
+    line-height: 150px;
+    text-align: center;
+    background-color: rgba(237, 140, 15, .3);
+    width: 100%;
+    height: 180px;
+
+    img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        border-radius: 20px;
+        // background-size: cover;
+        // background-position: center;
+        // background-repeat: no-repeat;
+    }
+}
+</style>

+ 109 - 0
src/view/m_views/login/index.vue

@@ -0,0 +1,109 @@
+<template>
+    <div class="login">
+        <div class="logo">
+            <img src="@/assets/img/logo.png" class="img">
+        </div>
+        <div class="input">
+            <van-form @submit="submitForm">
+                <van-field style="font-size: 16px;" v-model="loginForm.account" name="用户名" label="用户名"
+                    placeholder="请输入用户名" :rules="[{ required: true, message: '请填写用户名' }]" />
+                <van-field style="font-size: 16px;" v-model="loginForm.password" type="password" name="密码"
+                    label="密&emsp;码" placeholder="请输入密码" :rules="[{ required: true, message: '请填写密码' }]" />
+                <van-field style="font-size: 16px;" v-model="loginForm.password" type="hidden" id="md5_password" />
+                <div style="margin: 20px;" class="df jcsb aic">
+                    <van-button round type="primary" color="rgba(25, 137, 250, .7)" plain
+                        style=" width: 45%;height: 38px;font-size: 16px;background-color: rgba(25, 137, 250, .1);"
+                        @click="resetForm">重&emsp;置</van-button>
+                    <van-button type="primary" color="rgba(237, 140, 15, .7)" plain round native-type="submit"
+                        style="width: 45%;height: 38px;font-size: 16px;background-color: rgba(237, 140, 15, .1);">登&emsp;录</van-button>
+                </div>
+            </van-form>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue';
+import { getUserLogin } from '@/api/index'
+import Message from '@/utils/Message'
+import { useRouter } from 'vue-router'
+import { useStore } from '@/store/index'
+import local from '@/utils/local';
+
+const user = useStore('user')
+const router = useRouter()
+
+interface login {
+    account: string,
+    password: string,
+    timestamp: string | number
+}
+const loginForm = reactive<login>({
+    account: '',
+    password: '',
+    timestamp: parseInt(`${Date.now() / 1000}`)
+})
+
+const submitForm = async () => {
+    // 登录密码加密
+    let md5Pwd: any = document.querySelector('#md5_password')
+    md5Pwd = md5(md5(md5(loginForm.password)) + loginForm.timestamp)
+
+    const params = {
+        account: loginForm.account,
+        password: md5Pwd,
+        timestamp: loginForm.timestamp
+    }
+    await getUserLogin(params).then(res => {
+        // console.log('res---->', res);
+        if (res.data.code === 200) {
+            Message.success('登录成功')
+            // 保存用户信息 和 token
+            localStorage.setItem('token', res.data.data.token)
+            localStorage.setItem('account', loginForm.account)
+
+            user.setUerInfo(loginForm)
+            // 浏览器记住账户和密码
+            window.sessionStorage.setItem('token', res.data.data.token)
+            window.sessionStorage.setItem('account', loginForm.account)
+            window.sessionStorage.setItem('password', loginForm.password)
+            // 跳转我的游戏页面
+            setTimeout(() => {
+                router.push({ path: '/m_mine', query: { account: loginForm.account } })
+                local.set('headerPath', '我的')
+            }, 1000);
+        }
+    }).catch(err => {
+        // console.log('err===>', err);
+        Message.error(err.data.msg)
+    })
+}
+
+const resetForm = () => {
+    loginForm.account = ''
+    loginForm.password = ''
+}
+</script>
+
+<style lang="scss" scoped>
+.login {
+    width: 100vw;
+    height: 100%;
+    background-color: #f7f7f7;
+
+    .logo {
+        padding: 30vw 0 5vw;
+        width: 100vw;
+        text-align: center;
+
+        .img {
+            width: 100%;
+            height: 100%;
+        }
+    }
+
+    .input {
+        margin-top: 10vw;
+    }
+}
+</style>

+ 127 - 0
src/view/m_views/mine/index.vue

@@ -0,0 +1,127 @@
+<template>
+    <m_header></m_header>
+    <div v-if="token">
+        <div v-if="mineGame.length > 0">
+            <div style="padding:15px">
+                <MobList :gameLis="mineGame" :prefix="prefix"></MobList>
+            </div>
+            <div style="padding: 10px;background-color: #f7f7f7;" class="df aic jcc"
+                v-if="params.page * params.pagesize <= mineGame.length">
+                <el-button class="df aic jcc" plain @click="loadMore">更多</el-button>
+            </div>
+            <div v-else>
+                <div class="df aic jcc" style="color: #999;height: 100px;font-size: 16px;">----- 没有更多数据了 -----
+                </div>
+            </div>
+        </div>
+        <van-empty v-else description="No Data" />
+    </div>
+    <div v-else :style="{ height: viewWidth + 'px' }" class="df fdc aic jcc">
+        <van-empty>
+            <van-button color="#ed8c0f" @click="goLogin">去登录 &gt;&gt;</van-button>
+        </van-empty>
+    </div>
+
+    <van-back-top style="background-color: #ed8c0f;" />
+    <!-- <m_footer></m_footer> -->
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref, reactive } from 'vue'
+import { getMineGame } from '@/api/index'
+import { useRouter, useRoute } from 'vue-router'
+import Message from '@/utils/Message'
+import MobList from '@/components/MobList.vue';
+
+const router = useRouter()
+const route = useRoute()
+
+const mineGame: any = ref([])
+const prefix = ref<string>('')
+const total = ref<number>(0)
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 100)
+const userAccount = ref<string | null>(localStorage.getItem('account'))
+const token = ref<string | null>(localStorage.getItem('token'))
+
+interface params {
+    page: number,
+    pagesize: number,
+    account: string | null
+}
+const params = reactive<params>({
+    page: 1,
+    pagesize: 10,
+    account: userAccount.value
+})
+
+onMounted(() => {
+    // 进入页面刷新一次
+    if (token.value) {
+        if (route.query.account || userAccount.value) {
+            // if (location.href.indexOf("#reloaded") === -1) {
+            //     location.href = location.href + "#reloaded";
+            //     location.reload();
+            // }
+            getMyGame(params)
+        } else {
+            userAccount.value = ''
+        }
+    }
+})
+
+const getMyGame = async (params) => {
+    // console.log('params', params);
+    await getMineGame(params).then(res => {
+        // console.log('res========>', res);
+        if (res.data.code === 200 && res.data.data) {
+            mineGame.value = mineGame.value.concat(res.data.data.lists)
+            prefix.value = res.data.data.prefix
+            total.value = res.data.data.total
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+}
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+    getMyGame(params)
+}
+
+const goLogin = () => {
+    router.push({ path: 'm_login' })
+}
+
+</script>
+
+<style scoped lang="scss">
+.game_list {
+    box-sizing: border-box;
+    padding: 20px;
+    background-color: #fff;
+    margin: 0 auto 20px;
+
+    h3 {
+        font-size: 22px;
+        font-weight: normal;
+        height: 30px;
+    }
+}
+
+.more {
+    margin: 20px auto 0;
+    height: 60px;
+    background-color: #f7f7f7;
+
+    .el-button {
+        width: 100px;
+        height: 40px;
+        background-color: transparent;
+        color: #323332;
+        border: 1px solid #c7c7c7;
+    }
+}
+</style>

+ 13 - 0
src/view/m_views/rank/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div>
+        <m_header></m_header>
+    </div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style scoped>
+
+</style>

+ 92 - 0
src/view/m_views/search/index.vue

@@ -0,0 +1,92 @@
+<template>
+    <div class="search_cont">
+        <van-search class="search_key" v-model="searchText" placeholder="请输入游戏关键词" shape="round" clearable
+            @search="onSearch" @clear="onClear">
+            <template #left>
+                <van-icon name="arrow-left" @click="onCancel" size="large" style="margin-right: 10px;" />
+            </template>
+        </van-search>
+
+        <div v-if="list.length > 0" style="padding: 0 10px;">
+            <MobList :gameLis="list" :prefix="prefix"></MobList>
+            <div style="margin: 10px;" class="more df aic jcc" v-if="params.page * params.pagesize <= list.length">
+                <el-button class="df aic jcc" plain @click="loadMore">更多</el-button>
+            </div>
+            <div v-else>
+                <div class="df aic jcc" style="color: #999;height: 100px;font-size: 16px;">----- 没有更多数据了 -----</div>
+            </div>
+        </div>
+        <van-empty v-else description="No Data" />
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from 'vue';
+import MobList from '@/components/MobList.vue';
+import { getGameList } from '@/api/index'
+import Message from '@/utils/Message'
+import { useRouter } from 'vue-router';
+
+const router = useRouter()
+const searchText = ref<string>('')
+const list: any = ref([])
+const prefix = ref<string>('')
+const total = ref<number>(0)
+
+interface IParams {
+    type: number,
+    tag_id: number,
+    page: number,
+    pagesize: number
+}
+const params = reactive<IParams>({
+    type: 0,
+    tag_id: 0,
+    page: 1,
+    pagesize: 10
+})
+
+const onSearch = async () => {
+    await getGameList(params).then(res => {
+        if (res.data.code === 200 && res.data.data) {
+            prefix.value = res.data.data.prefix
+            total.value = res.data.data.total
+            params.pagesize = total.value
+            getGameList(params).then(resp => {
+                if (resp.data.code === 200 && resp.data.data) {
+                    list.value = resp.data.data.lists.filter(item => {
+                        return item.screen_name.indexOf(searchText.value) > -1
+                    })
+                }
+            })
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+}
+
+const onCancel = () => {
+    router.go(-1)
+}
+
+const onClear = () => {
+    list.value = []
+}
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+}
+
+watch(params, (nval,oval) => { onSearch() }, {deep:true})
+</script>
+
+<style scoped lang="scss">
+.search_cont {
+    padding: 5px;
+
+    .search_key {
+        margin-bottom: 10px;
+    }
+}
+</style>

+ 203 - 0
src/view/p_views/categroy/index.vue

@@ -0,0 +1,203 @@
+<template>
+    <loading v-if="isLoading" :style="{ height: viewWidth + 'px' }" />
+    <div v-else class="w1000 category">
+        <div class="header_list">
+            <div class="top_type">
+                <div class="type df aic">
+                    <div class="left_title">类型</div>
+                    <div class="right_type f1 df fw">
+                        <span v-for="item in typeArr" :key="item.id" class="cont"
+                            :class="{ active: typeIndex === item.id }" @click="selectType('type', item.id)">
+                            {{ item.name }}
+                        </span>
+                    </div>
+                </div>
+                <div class="size df aic">
+                    <div class="left_title">标签</div>
+                    <div class="right_type f1 df fw">
+                        <span v-for="item in tagArr" :key="item.id" class="cont"
+                            :class="{ active: tag_idIndex === item.id }" @click="selectType('tag_id', item.id)">
+                            {{ item.name }}
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <!-- 按最xxx排序 -->
+            <!-- <el-button :plain="true" round v-for="(item, index) in categoryList" :key="index"
+                @click="selectType('order', index)">
+                <span :class="{ active: orderIndex === index }">{{ item.title }}</span>
+            </el-button> -->
+        </div>
+        <!-- 游戏列表 -->
+        <div v-if="(gameLis.length > 0)">
+            <RankList :gameLis="gameLis" :prefix="prefix" :rank="false" :tagArr="tagArr" />
+            <div class="more df aic jcc" v-if="params.page * params.pagesize <= gameLis.length">
+                <el-button class="df aic jcc" @click="loadMore">更多</el-button>
+            </div>
+            <div v-else>
+                <div class="df aic jcc" style="color: #999;height: 200px;font-size: 18px;">----- 没有更多数据了 -----</div>
+            </div>
+        </div>
+        <el-empty v-else></el-empty>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { getGameType, getGameTag, getGameList } from '@/api/index'
+import RankList from '@/components/RankList.vue';
+import Message from '@/utils/Message'
+
+const typeIndex = ref<number>(0)
+const tag_idIndex = ref<number>(0)
+const gameLis: any = ref([])
+const typeArr: any = ref([])
+const tagArr: any = ref([])
+const prefix = ref<string>('')
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+const total = ref<number>(0)
+interface Params {
+    type: number,
+    tag_id: number,
+    page: number,
+    pagesize: number
+}
+const params = reactive<Params>({
+    type: 0,
+    tag_id: 0,
+    page: 1,
+    pagesize: 15
+})
+
+onMounted(async () => {
+    await getGameType().then(res => {
+        // console.log('类型', res);
+        typeArr.value = res.data.data
+        typeArr.value.unshift({ id: 0, name: '全部' })
+    })
+    await getGameTag().then(res => {
+        // console.log('标签', res);
+        tagArr.value = res.data.data
+        tagArr.value.unshift({ id: 0, name: '全部' })
+    })
+    getGameLists(params)
+})
+
+// 初始化列表数据
+const getGameLists = async (params) => {
+    // console.log('参数', params);
+
+    await getGameList(params).then(res => {
+        // console.log('数据', res);
+        if (res.data.code === 200 && res.data.data) {
+            gameLis.value = gameLis.value.concat(res.data.data.lists)
+            total.value = res.data.data.total
+            prefix.value = res.data.data.prefix
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+}
+
+// 点击类型、标签、排序
+const selectType = (type: any, index: any) => {
+    gameLis.value = []
+    params.page = 1
+
+    if (type === 'type') {
+        typeIndex.value = index
+        params.type = index
+    }
+    if (type === 'tag_id') {
+        tag_idIndex.value = index
+        params.tag_id = index
+    }
+    // 如果都为 全部  则发起请求 页面显示15条数据
+    if (params.type === 0 && params.tag_id === 0) {
+        params.pagesize = 15
+        getGameLists(params)
+    } else {
+        allData()
+    }
+
+}
+// 全部数据
+const allData = async () => {
+    params.pagesize = total.value
+    // console.log('params', params);
+    await getGameList(params).then(resp => {
+        // console.log('resp===>', resp);
+        if (resp.data.code === 200 && resp.data.data) {
+            gameLis.value = resp.data.data.lists.filter(item => {
+                // 如果标签为全部  类型不为全部 返回匹配的类型数据
+                if (params.tag_id === 0) {
+                    return item.type === params.type
+                }
+                // 如果类型为全部  标签不为全部 返回匹配的标签数据
+                else if (params.type === 0) {
+                    return item.tags.includes(params.tag_id)
+                }
+                // 否则返回匹配的标签和类型数据
+                else return item.type === params.type && item.tags.includes(params.tag_id)
+            })
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+}
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+    getGameLists(params)
+}
+
+</script>
+
+<style scoped lang="scss">
+.header_list {
+    .top_type {
+        margin: 10px 0;
+        background-color: #fff;
+        padding: 20px 50px 20px 20px;
+        box-sizing: border-box;
+
+        .left_title {
+            width: 50px;
+            font-size: 14px;
+            color: #666;
+        }
+
+        .right_type {
+
+            // background-color: #ed8c0f;
+            .cont {
+                margin-right: 20px;
+                padding: 3px 5px;
+                cursor: pointer;
+
+                &:hover {
+                    color: #ed8c0f;
+                }
+            }
+        }
+
+        .type {
+            margin-bottom: 20px;
+        }
+    }
+
+    .el-button {
+        margin: 5px 20px 15px 0;
+        padding: 0 20px;
+        border: 0;
+        color: #999;
+    }
+}
+
+.active {
+    color: #ed8c0f
+}
+</style>

+ 71 - 0
src/view/p_views/index/index.vue

@@ -0,0 +1,71 @@
+<template>
+    <loading v-if="isLoading" :style="{ height: viewWidth + 'px' }" />
+
+    <div v-else class="w1000">
+        <!-- 热门游戏 -->
+        <div v-if="gameHot.length > 0" class="game_list">
+            <h2>热门游戏</h2>
+            <IndexList :list="gameHot" :prefix="prefix"></IndexList>
+        </div>
+        <!-- 推荐游戏 -->
+        <div v-if="gameRecommand.length > 0" class="game_list">
+            <h2>推荐游戏</h2>
+            <IndexList :list="gameRecommand" :prefix="prefix"></IndexList>
+        </div>
+
+    </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import { getIndexGameHot, getIndexGameRecommand } from '@/api/index'
+import Message from '@/utils/Message'
+import IndexList from '@/components/IndexList.vue'
+
+const gameHot: any = ref([])
+const gameRecommand: any = ref([])
+const prefix = ref<string>('')
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+
+onMounted(async () => {
+    await getIndexGameHot().then(res => {
+        console.log('热门游戏', res.data);
+        if (res.data.code === 200 && res.data.data) {
+            gameHot.value = res.data.data.lists
+            prefix.value = res.data.data.prefix
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+
+    await getIndexGameRecommand().then(res => {
+        console.log('推荐游戏', res.data.data);
+        if (res.data.code === 200 && res.data.data) {
+            gameRecommand.value = res.data.data.lists
+            prefix.value = res.data.data.prefix
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+})
+
+</script>
+
+<style scoped lang="scss">
+.game_list {
+    box-sizing: border-box;
+    // padding: 20px;
+    background-color: #fff;
+    margin: 15px auto 20px;
+
+    h2 {
+        font-size: 28px;
+        font-weight: normal;
+        height: 30px;
+        padding: 30px;
+    }
+}
+</style>

+ 166 - 0
src/view/p_views/login/index.vue

@@ -0,0 +1,166 @@
+<template>
+    <div class="login df fdc aic" :style="{height: viewWidth + 'px'}">
+        <div class="login_logo">
+            <!-- <img src="http://gm.nkfzs.com/favicon.ico"> -->
+            <img src="@/assets/img/logo.png">
+        </div>
+        <el-form ref="ruleFormRef" :rules="rules" class="login_from df fdc aic jcc" label-position="left"
+            label-width="80px" :model="loginForm" style="max-width: 400px" @submit="submitForm">
+            <el-form-item label="用户名" prop="account">
+                <el-input v-model="loginForm.account" type="text" placeholder="请输入账号" clearable :prefix-icon="User" />
+            </el-form-item>
+            <el-form-item label="密&emsp;码" prop="password">
+                <el-input v-model="loginForm.password" type="password" show-password placeholder="请输入密码"
+                    :prefix-icon="Lock" />
+                <el-input v-model="loginForm.password" type="hidden" id="md5_password" show-password placeholder="请输入密码"
+                    :prefix-icon="Lock" />
+            </el-form-item>
+            <!-- <el-form-item style="margin-top:-10px;margin-bottom:-5px;">
+                    <el-checkbox v-model="checked" style="color:#a0a0a0;margin-top:-10px;">记住我</el-checkbox>
+                </el-form-item> -->
+            <div class="btn df aic jcsb">
+                <el-button type="primary" plain round class="submit_btn" @click="resetForm(ruleFormRef)">重&emsp;置</el-button>
+                <el-button color="#ed8c0f" plain round class="submit_btn"
+                    @click="submitForm(ruleFormRef)">登&emsp;录</el-button>
+            </div>
+        </el-form>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref } from 'vue';
+import { Lock, User } from '@element-plus/icons-vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { getUserLogin } from '@/api/index'
+// import '@/utils/md5'
+import Message from '@/utils/Message'
+import { useRouter } from 'vue-router'
+import { useStore } from '@/store/index'
+import local from '@/utils/local';
+
+const user = useStore('user')
+const router = useRouter()
+
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+
+const ruleFormRef = ref<FormInstance>()
+
+interface login {
+    account: string,
+    password: string,
+    timestamp: number | string
+}
+const loginForm = reactive<login>({
+    account: '',
+    password: '',
+    timestamp: parseInt(`${Date.now() / 1000}`)
+})
+
+const rules = reactive<FormRules>({
+    account: [
+        { required: true, message: '请输入账号', trigger: 'blur' },
+        { min: 3, max: 255, message: '请正确输入账号', trigger: 'blur' }
+    ],
+    password: [
+        { required: true, message: '请输入密码', trigger: 'blur' },
+        { min: 6, max: 20, message: '请输入6-20位密码', trigger: 'blur,change' }
+    ]
+})
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+    if (!formEl) return
+    await formEl.validate(async (valid, fields) => {
+        if (valid) {
+
+            // 登录密码加密
+            let md5Pwd: any = document.querySelector('#md5_password')
+            md5Pwd = md5(md5(md5(loginForm.password)) + loginForm.timestamp)
+
+            const params = {
+                account: loginForm.account,
+                password: md5Pwd,
+                timestamp: loginForm.timestamp
+            }
+            getUserLogin(params).then(res => {
+                // console.log('res---->', res);
+                if (res.data.code === 200) {
+                    Message.success('登录成功')
+                    // 保存用户信息 和 token
+                    localStorage.setItem('token', res.data.data.token)
+                    localStorage.setItem('account', loginForm.account)
+
+                    user.setUerInfo(loginForm)
+                    // 浏览器记住账户和密码
+                    window.sessionStorage.setItem('token', res.data.data.token)
+                    window.sessionStorage.setItem('account', loginForm.account)
+                    window.sessionStorage.setItem('password', loginForm.password)
+                    // 跳转我的游戏页面
+                    setTimeout(() => {
+                        router.push({ path: '/p_mine', query: { account: loginForm.account } })
+                        local.set('headerPath', '我的')
+                    }, 1000);
+                }
+            }).catch(err => {
+                // console.log('err===>', err);
+                Message.error(err.data.msg)
+            })
+        } else {
+            console.log('登录失败', fields)
+        }
+    })
+}
+const resetForm = (formEl: FormInstance | undefined) => {
+    if (!formEl) return
+    formEl.resetFields()
+}
+</script>
+
+<style scoped lang="scss">
+.login {
+    // background-color: #fff;
+    padding-top: 130px;
+    // background: url('https://t7.baidu.com/it/u=3939632370,2791869803&fm=193&f=GIF') no-repeat 100% / 100%;
+
+    .login_logo{
+        // width: 500px;
+        // height: 200px;
+        margin: 30px auto 20px;
+        img{
+            width: 100%;
+            height: 100%;
+        }
+    }
+
+    .login_from {
+        width: 600px;
+        height: 280px;
+        background-color: rgba(255, 255, 255, .7);
+        font-size: 16px;
+        border-radius: 10px;
+        box-sizing: border-box;
+
+        .el-input {
+            width: 280px;
+            height: 45px;
+        }
+
+        .btn {
+            width: 80%;
+            height: 35px;
+            font-size: 16px;
+            margin-top: 20px;
+
+            .submit_btn {
+                width: 45%;
+                height: 100%;
+            }
+
+        }
+    }
+}
+
+.el-form-item {
+    align-items: center;
+    height: 50px;
+}
+</style>

+ 116 - 0
src/view/p_views/mine/index.vue

@@ -0,0 +1,116 @@
+<template>
+    <div v-if="token">
+        <loading v-if="isLoading" :style="{ height: viewWidth + 'px' }" />
+        <div v-else class="w1000" style="margin: 20px auto;">
+                <!-- <h2>我的游戏</h2> -->
+                <!-- <IndexList :list="mineGame" :prefix="prefix"></IndexList>
+                <div class="more df aic jcc" v-if="(total > 10 ? true : false)">
+                    <el-button class="df aic jcc" @click="loadMore">更多</el-button>
+                </div> -->
+                <div v-if="mineGame.length > 0">
+                    <IndexList :list="mineGame" :prefix="prefix"></IndexList>
+                    <div class="more df aic jcc" v-if="params.page * params.pagesize <= mineGame.length">
+                        <el-button class="df aic jcc" @click="loadMore">更多</el-button>
+                    </div>
+                    <div v-else>
+                        <div class="df aic jcc" style="color: #999;height: 200px;font-size: 18px;">----- 没有更多数据了 -----
+                        </div>
+                    </div>
+                </div>
+                <el-empty v-else></el-empty>
+        </div>
+    </div>
+    <div v-else :style="{ height: viewWidth + 'px' }" class="df fdc aic jcc">
+        <el-empty description="您还未登录!登录后可查询数据!">
+            <el-button style="color:#fff" color="#ed8c0f" @click="goLogin">去登录 &gt;&gt;</el-button>
+        </el-empty>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref, reactive, watch } from 'vue'
+import { getMineGame } from '@/api/index'
+import { useStore } from '@/store/index'
+import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'
+import Message from '@/utils/Message'
+import IndexList from '@/components/IndexList.vue'
+import local from '@/utils/local'
+
+const router = useRouter()
+const route = useRoute()
+// const header = useStore('header')
+
+const mineGame: any = ref([])
+const prefix = ref<string>('')
+const total = ref<number>(0)
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+const userAccount = ref<string | null>(localStorage.getItem('account'))
+const token = ref<string | null>(localStorage.getItem('token'))
+
+interface params {
+    page: number,
+    pagesize: number,
+    account: string | null
+}
+const params = reactive<params>({
+    page: 1,
+    pagesize: 10,
+    account: userAccount.value
+})
+onMounted(() => {
+    // 进入页面刷新一次
+    if (token.value) {
+        if (route.query.account || userAccount.value) {
+            if (location.href.indexOf("#reloaded") === -1) {
+                location.href = location.href + "#reloaded";
+                location.reload();
+            }
+            getMyGame(params)
+        }
+    }
+})
+
+const getMyGame = async (params) => {
+    // console.log('params', params);
+    await getMineGame(params).then(res => {
+        // console.log('res========>', res);
+        if (res.data.code === 200 && res.data.data) {
+            mineGame.value = mineGame.value.concat(res.data.data.lists)
+            prefix.value = res.data.data.prefix
+            total.value = res.data.data.total
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data.msg)
+    })
+}
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+    getMyGame(params)
+}
+
+const goLogin = () => {
+    router.push({ path: 'p_login' })
+    local.remove('headerPath')
+}
+
+</script>
+
+<style scoped lang="scss">
+.more {
+    margin: 20px auto 0;
+    height: 60px;
+    background-color: #f7f7f7;
+
+    .el-button {
+        width: 100px;
+        height: 40px;
+        background-color: transparent;
+        color: #323332;
+        border: 1px solid #c7c7c7;
+    }
+}
+</style>

+ 238 - 0
src/view/p_views/rank/index.vue

@@ -0,0 +1,238 @@
+<template>
+    <loading v-if="isLoading" :style="{ height: viewWidth + 'px' }" />
+    <div v-else class="w1000 rank">
+        <div class="header_list">
+            <el-button :plain="true" round v-for="(item, index) in rankList" :key="item.id"
+                @click="selectType('rank', index)">
+                <span :class="{ active: currentIndex === index }">{{ item.title }}</span>
+            </el-button>
+            <el-select v-model="value" class="select_rank" placeholder="请选择分类" size="large">
+                <el-option v-for="item in category" :key="item.id" :label="item.name" :value="item.name"
+                    @click="selectType('category', item.id)" />
+            </el-select>
+        </div>
+        <!-- 游戏列表 -->
+        <RankList :gameLis="gameLis" :prefix="prefix" :rank="true" :tagArr="tagArr" :more="loadMore" :total="total"  />
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, watch } from 'vue';
+import { useRouter } from 'vue-router';
+import { getGameList, getGameTag, getGameType } from '@/api/index'
+import RankList from '@/components/RankList.vue';
+import Message from '@/utils/Message'
+
+const router = useRouter()
+const currentIndex = ref(0)
+const value = ref('')
+const gameLis: any = ref([])
+const prefix = ref('')
+const tagArr: any = ref([])
+const isLoading = ref(true)
+const viewWidth = ref(document.documentElement.clientHeight - 80)
+const params = reactive({
+    type: 0,
+    tag_id: 0,
+    page: 2,
+    pagesize: 10
+})
+const total = ref()
+const category: any = ref([])
+
+const rankList = reactive([
+    { id: 0, title: '下载榜' },
+    { id: 1, title: '高分榜' }
+])
+
+
+
+// 数组对象排序
+const gameLisSort = (arr: Array<object | any>, key: string) => {
+    return arr.sort((a, b) => {
+        let x = a[key]
+        let y = b[key]
+        return ((x > y) ? -1 : (x < y) ? 1 : 0)     //从大到小
+        // return ((x < y) ? -1 : (x > y) ? 1 : 0)     //从小到大
+    })
+}
+
+onMounted(async () => {
+    await getGameTag().then(res => {
+        console.log('标签', res);
+        tagArr.value = res.data.data
+    }).catch(err => {
+        Message.error(err.data)
+    })
+
+    await getGameType().then(res => {
+        console.log('类型', res);
+        category.value = res.data.data
+    }).catch(err => {
+        Message.error(err.data)
+    })
+    // selectType('rank', 0)      //默认初始下载榜为初始数据
+    getGameLists(params)
+})
+
+const getGameLists = async (params) => {
+    await getGameList(params).then(res => {
+        console.log('游戏列表', res);
+        if (res.data.code === 200 && res.data.data) {
+            gameLis.value = gameLis.value.concat(res.data.data.lists)
+            prefix.value = res.data.data.prefix
+            total.value = res.data.data.total
+            isLoading.value = false
+        }
+    }).catch(err => {
+        Message.error(err.data)
+    })
+}
+const selectType = async (type: string, index: number) => {
+    if (type === 'rank') {
+        currentIndex.value = index
+        if (index === 0) {
+            gameLis.value = []
+            getGameLists(params)
+        } else {
+            gameLis.value = gameLisSort(gameLis.value, 'game_score')
+        }
+    }
+    if (type === 'category') {
+        currentIndex.value = index
+        params.type = index
+        gameLis.value = []
+        await getGameList(params).then(res => {
+            if (res.data.code === 200 && res.data.data) {
+                gameLis.value = gameLis.value.concat(res.data.data.lists.filter(item => item.type === params.type))
+                console.log('gameLis.value', gameLis.value);
+                total.value = res.data.data.total
+                prefix.value = res.data.data.prefix
+                isLoading.value = false
+            }
+        }).catch(err => {
+            Message.error(err.data)
+        })
+    }
+}
+
+watch((type: string, index: number) => selectType(type, index), (vold, vnew) => { })
+// watch(params, (vold, vnew) => { getGameLists(params) })
+
+// 加载更多
+const loadMore = () => {
+    params.page += 1
+    getGameLists(params)
+}
+</script>
+
+<style scoped lang="scss">
+.header_list {
+    margin: 15px auto 20px;
+
+    .el-button {
+        margin-right: 20px;
+        border: 0;
+        color: #999;
+
+        &:hover {
+            color: #ed8c0f;
+        }
+    }
+
+    .select_rank {
+        margin-left: 20px;
+    }
+}
+
+.game_list {
+    box-sizing: border-box;
+    background-color: #fff;
+    padding: 20px;
+
+    .list {
+        margin-bottom: 20px;
+        cursor: pointer;
+        border-bottom: 1px solid #ddd;
+        padding-bottom: 25px;
+
+        .rank_icon {
+            width: 48px;
+            height: 48px;
+            background-color: #ccc;
+            border-radius: 50%;
+            margin-left: 10px;
+            margin-right: 50px;
+            color: #fff;
+            font-size: 20px;
+
+            .num {
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+                z-index: 1;
+            }
+        }
+
+        .firstBg {
+            background-color: rgb(238, 54, 54);
+        }
+
+        .secondBg {
+            background-color: rgb(239, 154, 94);
+        }
+
+        .threeBg {
+            background-color: rgb(209, 175, 55);
+        }
+
+
+        img {
+            height: 130px;
+            width: 130px;
+            margin-right: 50px;
+            border-radius: 20px;
+        }
+
+        .right_content {
+            box-sizing: border-box;
+            width: 70%;
+
+            .title {
+                font-size: 20px;
+                font-weight: normal;
+                margin-bottom: 20px;
+
+                .game_rate {
+                    color: #ed8c0f;
+                    margin-left: 10px;
+                }
+            }
+
+            p {
+                font-size: 14px;
+                margin-bottom: 20px;
+            }
+
+            .game_tag {
+                color: #ed8c0f;
+                border-color: #ed8c0f;
+                font-size: 12px;
+            }
+
+            .downMB {
+                width: 100px;
+                padding: 0;
+                color: #fff;
+                border-color: #ed8c0f;
+            }
+        }
+
+    }
+
+}
+
+.active {
+    color: #ed8c0f
+}
+</style>

+ 41 - 0
src/view/p_views/search/index.vue

@@ -0,0 +1,41 @@
+<template>
+    <loading v-if="search.getLoading" :style="{ height: viewWidth + 'px' }" />
+    <div v-else class="w1000">
+        <div class="game_list">
+            <IndexList :list="search.searchLis" :prefix="search.getPrefix"></IndexList>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRoute } from 'vue-router'
+import IndexList from '@/components/IndexList.vue'
+import { useStore } from '@/store'
+
+const search = useStore('search')
+const route = useRoute()
+const isLoading = ref<boolean>(true)
+const viewWidth = ref<number>(document.documentElement.clientHeight - 80)
+
+if (search.searchLis.length !== 0) {
+    isLoading.value = false
+}
+
+if(route.query.screen_name){
+    search.setSearchLis(route.query.screen_name)
+}
+</script>
+
+<style scoped>
+.game_list {
+    box-sizing: border-box;
+    padding: 20px;
+    background-color: #fff;
+    margin: 15px auto 20px;
+}
+.list_cont .list img{
+    width: 0;
+    height: 0;
+}
+</style>

+ 7 - 0
src/vite-env.d.ts

@@ -0,0 +1,7 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}

+ 41 - 0
tsconfig.json

@@ -0,0 +1,41 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "useDefineForClassFields": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "strict": true,
+    "jsx": "preserve",
+    "sourceMap": true,
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "lib": ["esnext", "dom"],
+    "types": [
+      "vite/client"
+    ],
+    "typeRoots": [
+      "./node_modules/@types/",
+      "./types"
+    ],
+    "noImplicitAny": false,
+    "skipLibCheck": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    }
+  },
+  "include": ["src/**/*.ts",
+    "src/**/*.d.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "types/**/*.d.ts",
+    "types/**/*.ts",
+    "build/**/*.ts",
+    "build/**/*.d.ts",
+    "mock/**/*.ts",
+    "vite.config.ts"],
+  "exclude": ["node_modules", "dist", "**/*.js"]
+}
+
+
+

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 76 - 0
vite.config.ts

@@ -0,0 +1,76 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { resolve } from 'path'
+// vant插件
+import Components from "unplugin-vue-components/vite";
+import { VantResolver } from "unplugin-vue-components/resolvers";
+// 引入vite兼容浏览器插件
+import legacy from '@vitejs/plugin-legacy'
+
+function pathResolve(dir) {
+  return resolve(__dirname, '.', dir)
+}
+
+export default defineConfig({
+  base: './',
+  plugins: [
+    vue(),
+    Components({
+      resolvers: [VantResolver()],
+    }),
+    // vite兼容低版本浏览器 如UC浏览器 内核55
+    legacy({
+      targets: ['chrome 50', 'ie >= 11'],
+      additionalLegacyPolyfills: ['regenerator-runtime/runtime'], // regenerator-runtime/runtime   @dian/polyfill
+      renderLegacyChunks: true,
+      polyfills: [
+        'es.symbol',
+        'es.array.filter',
+        'es.promise',
+        'es.promise.finally',
+        'es/map',
+        'es/set',
+        'es.array.for-each',
+        'es.object.define-properties',
+        'es.object.define-property',
+        'es.object.get-own-property-descriptor',
+        'es.object.get-own-property-descriptors',
+        'es.object.keys',
+        'es.object.to-string',
+        'web.dom-collections.for-each',
+        'esnext.global-this',
+        'esnext.string.match-all',
+        // // 这个无法处理
+        // 'es.string.replace-all'
+      ],
+      // 在polyfills外可以执行
+      modernPolyfills: ['es.string.replace-all']
+    })
+  ],
+  resolve: {
+    alias: {
+      '@': pathResolve('src')
+    },
+  },
+  
+  server: {
+    host: '0.0.0.0',
+    cors: true,
+    open: true,
+    proxy: {
+      // 跨域前缀写法 
+      '/api': {
+        target: 'http://localhost:5173/',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, ''),
+      },
+    },
+  },
+
+  // css: {
+  //   preprocessorOptions: {
+  //     scss: {
+  //       additionalData: "@import '@/assets/scss/_index.scss';",
+  //     },
+  //   },
+})

File diff suppressed because it is too large
+ 1894 - 0
yarn.lock