lgh il y a 1 an
commit
04537b1675
82 fichiers modifiés avec 9542 ajouts et 0 suppressions
  1. 2 0
      .env.development
  2. 2 0
      .env.mock
  3. 2 0
      .env.preview
  4. 2 0
      .env.production
  5. 2 0
      .env.test
  6. 24 0
      .gitignore
  7. 0 0
      README.md
  8. 10 0
      auto-imports.d.ts
  9. 59 0
      components.d.ts
  10. 14 0
      index.html
  11. 3010 0
      package-lock.json
  12. 48 0
      package.json
  13. BIN
      public/svg-icon.png
  14. 1 0
      public/vite.svg
  15. 136 0
      src/App.vue
  16. 48 0
      src/apis/basicInformation.js
  17. 8 0
      src/apis/home.js
  18. 31 0
      src/apis/supplier.js
  19. 53 0
      src/apis/systemInstall.js
  20. 49 0
      src/apis/user.js
  21. BIN
      src/assets/img/chart-none-data.png
  22. BIN
      src/assets/img/login-page.png
  23. BIN
      src/assets/img/login.png
  24. BIN
      src/assets/img/logo.png
  25. BIN
      src/assets/img/yonghu.jpg
  26. 132 0
      src/assets/styles/clear.scss
  27. 266 0
      src/assets/styles/common.scss
  28. 161 0
      src/components/echarts/chartOptions.js
  29. 93 0
      src/components/el-table/adaptive/adaptive.js
  30. 98 0
      src/components/full-dialog/index.vue
  31. 131 0
      src/components/quillEditor/index.vue
  32. 68 0
      src/components/secondary-dialog/index.vue
  33. 185 0
      src/components/uploadFile/index.vue
  34. 153 0
      src/components/uploadImag/index.vue
  35. 150 0
      src/components/uploadImage/index.vue
  36. 173 0
      src/components/uploadImages/index.vue
  37. 171 0
      src/components/wangEditor/index.vue
  38. 3 0
      src/config/default.js
  39. 11 0
      src/config/index.js
  40. 4 0
      src/config/modules/dev.js
  41. 5 0
      src/config/modules/mock.js
  42. 3 0
      src/config/modules/pre.js
  43. 3 0
      src/config/modules/prod.js
  44. 4 0
      src/config/modules/test.js
  45. 4 0
      src/install/app.js
  46. 1 0
      src/install/index.js
  47. 4 0
      src/install/installGlobalCss.js
  48. 5 0
      src/install/installRouter.js
  49. 7 0
      src/install/installStore.js
  50. 13 0
      src/install/ployfill.js
  51. 157 0
      src/layout/aside/index.vue
  52. 43 0
      src/layout/aside/menuItem.vue
  53. 118 0
      src/layout/header/index.vue
  54. 74 0
      src/layout/index.vue
  55. 145 0
      src/layout/main/index.vue
  56. 7 0
      src/main.js
  57. 69 0
      src/plugins/request/axiosInstance.js
  58. 124 0
      src/plugins/request/index.js
  59. 23 0
      src/plugins/request/utils.js
  60. 83 0
      src/plugins/storage/index.js
  61. 1 0
      src/plugins/storage/localStorage.js
  62. 1 0
      src/plugins/storage/sessionStorage.js
  63. 13 0
      src/plugins/storage/utils.js
  64. 97 0
      src/request/index.js
  65. 83 0
      src/router/index.js
  66. 46 0
      src/router/permission.js
  67. 22 0
      src/router/router.js
  68. 35 0
      src/router/sub/supplier.js
  69. 7 0
      src/storage/index.js
  70. 31 0
      src/store/channel.js
  71. 38 0
      src/store/main.js
  72. 28 0
      src/store/user.js
  73. 130 0
      src/stores/counter.js
  74. 188 0
      src/utils/index.js
  75. 16 0
      src/utils/loading.js
  76. 1098 0
      src/utils/public.js
  77. 244 0
      src/views/login/index.vue
  78. 271 0
      src/views/supplier/sgccRecord/index.vue
  79. 414 0
      src/views/supplier/suplierListManage/index.vue
  80. 357 0
      src/views/supplier/supplierDashboard/index.vue
  81. 91 0
      vite.config.js
  82. 139 0
      vite.config.js.timestamp-1695799624641-0acee64528f11.mjs

+ 2 - 0
.env.development

@@ -0,0 +1,2 @@
+# 本地开发
+VITE_NODE_ENV=dev

+ 2 - 0
.env.mock

@@ -0,0 +1,2 @@
+# mock
+VITE_NODE_ENV=mock

+ 2 - 0
.env.preview

@@ -0,0 +1,2 @@
+# 预发布
+VITE_NODE_ENV=pre

+ 2 - 0
.env.production

@@ -0,0 +1,2 @@
+// 生产
+VITE_NODE_ENV=prod

+ 2 - 0
.env.test

@@ -0,0 +1,2 @@
+# 测试
+VITE_NODE_ENV=test

+ 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?

+ 0 - 0
README.md


+ 10 - 0
auto-imports.d.ts

@@ -0,0 +1,10 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-auto-import
+export {}
+declare global {
+  const ElLoading: typeof import('element-plus/es')['ElLoading']
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
+  const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
+}

+ 59 - 0
components.d.ts

@@ -0,0 +1,59 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+declare module 'vue' {
+  export interface GlobalComponents {
+    ElAside: typeof import('element-plus/es')['ElAside']
+    ElAvatar: typeof import('element-plus/es')['ElAvatar']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElButtonlink: typeof import('element-plus/es')['ElButtonlink']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
+    ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDrawer: typeof import('element-plus/es')['ElDrawer']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElEmpty: typeof import('element-plus/es')['ElEmpty']
+    ElFore: typeof import('element-plus/es')['ElFore']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElHeader: typeof import('element-plus/es')['ElHeader']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+    ElMain: typeof import('element-plus/es')['ElMain']
+    ElMenu: typeof import('element-plus/es')['ElMenu']
+    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElTag: typeof import('element-plus/es')['ElTag']
+    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
+    FullDialog: typeof import('./src/components/full-dialog/index.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    UploadFile: typeof import('./src/components/uploadFile/index.vue')['default']
+    UploadImage: typeof import('./src/components/uploadImage/index.vue')['default']
+  }
+}

+ 14 - 0
index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <!-- <link rel="icon" type="image/x-icon" href="./static/icon/svg-icon.png" rel="external nofollow"/> -->
+    <link rel="icon" type="image/svg+xml" href="/svg-icon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue</title>
+  </head>
+  <body>
+    <div id="my-vite-app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

Fichier diff supprimé car celui-ci est trop grand
+ 3010 - 0
package-lock.json


+ 48 - 0
package.json

@@ -0,0 +1,48 @@
+{
+  "name": "app",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "mock": "vite --mode mock",
+    "dev": "vite --mode development",
+    "test": "vite --mode test",
+    "pre": "vite --mode preview",
+    "prod": "vite --mode production",
+    "build:dev": "vite build --mode development",
+    "build:test": "vite build --mode test",
+    "build:pre": "vite build --mode preview",
+    "build:prod": "vite build --mode production",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@vueup/vue-quill": "^1.0.0-alpha.40",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^5.1.12",
+    "axios": "^1.4.0",
+    "dayjs": "^1.11.8",
+    "default-passive-events": "^2.0.0",
+    "echarts": "^5.4.2",
+    "element-plus": "^2.3.6",
+    "less": "^4.1.3",
+    "loadsh": "^0.0.4",
+    "nprogress": "^0.2.0",
+    "pinia": "^2.1.3",
+    "pinia-plugin-persist": "^1.0.0",
+    "quill-image-drop-module": "^1.0.3",
+    "quill-image-resize-module": "^3.0.0",
+    "resize-observer-polyfill": "^1.5.1",
+    "vite-plugin-vue-setup-extend": "^0.4.0",
+    "vue": "^3.2.47",
+    "vue-router": "^4.2.2"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "consola": "^3.1.0",
+    "sass": "^1.63.3",
+    "unplugin-auto-import": "^0.16.4",
+    "unplugin-vue-components": "^0.25.1",
+    "vite": "^4.3.9",
+    "vite-plugin-style-import": "^2.0.0"
+  }
+}

BIN
public/svg-icon.png


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
public/vite.svg


+ 136 - 0
src/App.vue

@@ -0,0 +1,136 @@
+<script setup>
+import { RouterView } from 'vue-router'
+import { ElConfigProvider } from 'element-plus';
+import zhCn from 'element-plus/lib/locale/lang/zh-cn'
+import { provide, ref, nextTick } from 'vue'
+const isRouteActive = ref(true);
+provide('reload', ()=>{
+	isRouteActive.value = false;
+	nextTick(()=>{
+		isRouteActive.value = true;
+	})
+})
+</script>
+
+<template>
+  <!-- element Puls 转中文环境配置 -->
+  <el-config-provider :locale="zhCn">
+    <RouterView />
+  </el-config-provider>
+</template>
+
+<style  lang="scss">
+.serch-box {
+  width: 100%;
+  height: 80px;
+  box-sizing: border-box;
+  display: flex;
+  padding: 0 24px 0 0;
+  justify-content: space-between;
+  margin-top: 20px;
+  flex-wrap: nowrap;
+
+  .form-right {
+    display: flex;
+  }
+
+  .form-left {
+    height: 100%;
+    background: #fff;
+    overflow: hidden;
+
+    .fonstSizeitem {
+      .el-form-item__label {
+        font-size: 12px;
+      }
+    }
+
+    .el-form {
+      width: 940px;
+    }
+
+    .el-form-item__label {
+      width: 100px;
+      font-size: 14px;
+      line-height: normal;
+      display: flex;
+      align-items: center;
+    }
+
+    .el-form-item__content {
+      width: 195px;
+
+      .el-range-input {
+        font-size: 12px;
+      }
+
+      .el-range-separator {
+        font-size: 12px;
+      }
+
+      .el-input {
+        width: 100% !important;
+
+        .el-input__inner {
+          font-size: 14px;
+        }
+      }
+    }
+
+    .el-form-item {
+      margin-bottom: 10px;
+      margin-right: 10px;
+
+      .el-range-editor.el-input__wrapper {
+        width: 300px;
+        height: 32px;
+      }
+    }
+  }
+}
+
+.serch-box-40 {
+  height: 40px;
+}
+
+.top-btn {
+  padding: 10px 0 0 24px;
+
+  .el-button {
+    padding: 20px;
+    text-align: center;
+  }
+}
+
+.tabel-data {
+  background: #FAFAFA;
+  color: #152129;
+  font-size: 14px;
+}
+
+.operation {
+  padding-left: 24px;
+  margin-top: 10px;
+}
+
+.image_item {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+body .pagination-style {
+  padding: 14px 5px;
+  background: #fff;
+  float: right;
+  margin-right: 10px;
+}
+
+.table-wrapper {
+  padding: 20px;
+}
+
+.el-overlay-message-box {
+  z-index: 2999;
+}
+</style>

+ 48 - 0
src/apis/basicInformation.js

@@ -0,0 +1,48 @@
+import { getSimple, postSimple, request } from "@/request";
+
+// 用户列表
+export const queryPlatformUserByPage = (data) => getSimple('/starsky-joy-web/platform/user/queryPlatformUserByPage', data);
+// 平台用户分页查询 - 渠道来源列表
+export const queryRegisterSource = () => getSimple('/starsky-joy-web/platform/user/queryRegisterSource');
+
+// 游戏币流水分页查询
+export const queryGameCurrencyAccountLogByPage = (data) => getSimple('/starsky-joy-web/platform/gameCurrency/queryGameCurrencyAccountLogByPage', data);
+// 兑换券流水分页查询
+export const queryCouponAccountLogByPage = (data) => getSimple('/starsky-joy-web/platform/coupon/queryCouponAccountLogByPage', data);
+// 积分流水分页查询
+export const queryPointsAccountLogByPage = (data) => getSimple('/starsky-joy-web/platform/points/queryPointsAccountLogByPage', data);
+// 游戏币流水分页查询接口 - 变更原因条件回显列表查询
+export const queryGameCurrencyAccountLogType = () => getSimple('/starsky-joy-web/platform/gameCurrency/queryGameCurrencyAccountLogType', );
+// 兑换券流水分页查询接口 - 变更原因条件回显列表查询
+export const queryCouponAccountLogType = () => getSimple('/starsky-joy-web/platform/coupon/queryCouponAccountLogType', );
+// 积分流水分页查询接口 - 变更原因条件回显列表查询
+export const queryPointsAccountLogType = () => getSimple('/starsky-joy-web/platform/points/queryPointsAccountLogType', );
+
+// 游戏币补发记录分页查询
+export const gameCurrencySupplyRecordByPage = (data) => getSimple('/starsky-joy-web/platform/gameCurrency/queryGameCurrencySupplyRecordByPage', data);
+// 兑换券补发记录分页查询
+export const couponSupplyRecordByPage = (data) => getSimple('/starsky-joy-web/platform/coupon/couponSupplyRecordByPage', data);
+// 流量补发记录分页查询
+export const flowSupplyRecordByPage = (data) => getSimple('/starsky-joy-web/platform/flow/flowSupplyRecordByPage', data);
+
+// 游戏币补发充值
+export const gameCurrencySupply = (data) => request('post', '/starsky-joy-web/platform/gameCurrency/gameCurrencySupply', data);
+// 流量补发充值
+export const flowSupply = (data) => request('post', '/starsky-joy-web/platform/flow/flowSupply', data);
+// 用户绑定卫星公司主键id和代号查询
+export const queryUserBingingSatCompanyIdAndCode = (userAccount) => getSimple(`/starsky-joy-web/platform/user/queryUserBingingSatCompanyIdAndCode/${userAccount}`);
+// 兑换券补发充值
+export const couponSupply = (data) => request('post', '/starsky-joy-web/platform/coupon/couponSupply', data);
+
+// 卫星公司列表分页查询
+export const querySatCompanyByPage = (data) => getSimple('/starsky-joy-web/satCompany/querySatCompanyByPage', data);
+// 卫星公司主键id和代号查询接口 分页查询条件回显使用
+export const querySatCompanyIdAndCode = (data) => getSimple('/starsky-joy-web/satCompany/querySatCompanyIdAndCode', data);
+// 添加卫星公司
+export const addSatCompany = (data) => request('post', '/starsky-joy-web/satCompany/addSatCompany', data);
+// 编辑卫星公司
+export const editSatCompany = (data) => request('post', '/starsky-joy-web/satCompany/editSatCompany', data);
+// 编辑卫星公司回显
+export const getSatCompany = (data) => getSimple('/starsky-joy-web/satCompany/getSatCompany', data);
+// 紧急下架卫星公司
+export const offShelfSatCompany = (data) => request('put', '/starsky-joy-web/satCompany/offShelfSatCompany', data);

+ 8 - 0
src/apis/home.js

@@ -0,0 +1,8 @@
+import { getSimple, postSimple, request, postFile } from "@/request";
+// 上传图片
+export const uploadImgurl = (data) => request( 'post', '/file/upload/FileV2', data);
+// 上传文件
+export const upliadFile = (data) => request( 'post','/admin/sys-file/upload/FileExt', data);
+// 区域信息
+export const  getRegionList = (data) =>getSimple('/apiManage/common/getRegionList', data);
+export const queryMiniGamesByPage = (data) => getSimple('/starsky-joy-web/pointsMall/queryMiniGamesByPage', data);

+ 31 - 0
src/apis/supplier.js

@@ -0,0 +1,31 @@
+import { getSimple, postSimple, request } from "@/request";
+
+// 游戏币流水分页查询
+export const supplierDashboard = (data) => getSimple('/increase/supplier/manage/supplierDashboard',data)
+
+// 游戏币流水分页查询
+export const selectSupplierGameCurrencyPage = (data) => getSimple('/increase/supplier/manage/selectSupplierGameCurrencyPage',data)
+
+// 渠道来源列表查询
+export const selectSupplierSourceTypeList = (data) => getSimple('/increase/supplier/manage/selectSupplierSourceTypeList',data)
+
+// 渠道仪表盘
+export const providerPage = (data) => getSimple('/provider/providerPage',data)
+
+// 新增-更新 供应商
+export const modifyProvider = (data) => request('post','/provider/modifyProvider',data)
+
+// 新增 供应商用户
+export const insertProviderUser = (data) => request('post','/provider/insertProviderUser',data)
+
+// 获取业务编号列表信息
+export const getBizNoInfo = (data) => getSimple('/provider/getBizNoInfo',data)
+
+// 删除供应商 删除前系统会默认对此供应商进行一次失效操作
+export const deleteProvider = (id) => request('post',`/provider/deleteProvider/${id}`)
+
+// 变更供应商状态 失效供应商时会将下辖用户状态变更为失效反之则不会
+export const changeStatus = (data) => request('post','/provider/changeStatus',data)
+
+// 获取第三方app信息列表
+export const getThirdAppInfoList = (data) => getSimple('/provider/getThirdAppInfoList',data)

+ 53 - 0
src/apis/systemInstall.js

@@ -0,0 +1,53 @@
+import { getSimple, postSimple, request } from "@/request";
+
+// 角色管理
+// 分页查询
+export const getRolePage = (data) => getSimple('/role/page', data);
+// 角色标识查角色
+export const getObjByCode = (code) => getSimple(`/role/code/${code}`)
+// 新增
+export const addRole = (data) => request( 'post', '/role/save', data);
+// 编辑
+export const editRole = (data) => request ('post', '/role/update', data);
+// 删除
+export const delRole = (id) => request('post', `/role/${id}`);
+// 权限标识
+export const fetchRoleTree = (id) => getSimple(`/menu/tree/${id}`);
+// 权限更新
+export const permissionUpd = (data) => request('post', '/role/menu', data)
+// 菜单标识
+export const fetchMenuTree = (data) => getSimple('/menu/tree', data);
+// 角色列表
+export const deptRoleList = (data) => getSimple('/role/list', data);
+
+// 账号管理
+// 分页查询
+export const getUserPage = (data) => getSimple('/user/page', data);
+// 新增
+export const addUser = (data) => request( 'post', '/user/save', data);
+// 更新
+export const editUser = (data) => request('post', '/user/update', data);
+// 筛选用户是否已存在
+export const getUsername = (username) =>getSimple(`/user/details/${username}`)
+// 删除
+export const  delUser = (id) => request( 'post', `/user/${id}`);
+
+// 菜单管理
+// 菜单列表
+export const menuList = (data) => getSimple('/menu/tree', data);
+// 新增
+export const addMenu = (data) => request('post', '/menu/save', data);
+// 更新
+export const editMenu = (data) => request('post', '/menu/update', data);
+// 删除
+export const delMenu = (id) => request('post', `/menu/${id}`)
+// 返回当前用户的树形菜单集合
+export const userMenu = () => request('get', `/menu`)
+
+// 基础配置信息查询
+export const getBasicConfig = () => getSimple('/starsky-joy-web/basicConfig/getBasicConfig')
+// 基础配置信息更新
+export const updateBasicConfig = (data) => request('post', '/starsky-joy-web/basicConfig/updateBasicConfig', data)
+
+// 登入记录分页
+export const loginRecordPage = (data) => getSimple('/increase/common/loginRecordPage', data)

+ 49 - 0
src/apis/user.js

@@ -0,0 +1,49 @@
+import { getSimple, postJson, request } from "@/request";
+
+// 账号密码登录
+export const passwordLogin = (data) => postJson('/user/login', data)
+
+// 查询用户列表
+export const selectUserList = (data) => getSimple('/increase/user/selectUserList', data)
+
+// 编辑用户-获取详情
+export const selectLabourUnionDesc = (data) => getSimple('/increase/user/selectLabourUnionDesc', data)
+
+// 编辑用户-获取详情
+export const dataJson = (data) => request('post','/increase/user/editUser/DataJson', data)
+
+// 编辑用户-冻结解冻
+export const frodataJson = (data) => request('post','/increase/user/frozenUser/DataJson', data)
+
+// 获取钻石充值数据
+export const diamondInfo = (data) => getSimple('/increase/panel/order/diamondInfo', data)
+
+// 运营后台-增值仪表盘 提现相关
+export const withdrawData = (data) => getSimple('/increase/panel/withdraw/info', data)
+
+// 提现统计
+export const selectCurrency = (data) => getSimple('/increase/panel/selectCurrency', data)
+
+export const withdrawStatistics = (data) => getSimple('/increase/panel/withdrawStatistics', data)
+
+export const selectUserActivity = (data) => getSimple('/increase/panel/selectUserActivity', data)
+
+export const selectUser = (data) => getSimple('/increase/panel/selectUser', data)
+
+// 流量消耗记录
+export const increaseRechargeWeekly = (data) => getSimple('/increase/statistics/increaseRechargeWeekly', data)
+
+export const increaseRecharge = (data) => getSimple('/increase/statistics/increaseRecharge', data)
+// 导出
+// 游戏币
+export const gameCurrencyExport = (data) => getSimple('/increase/panel/gameCurrencyExport', data)
+// 用户
+export const selectUserExport = (data) => getSimple('/increase/panel/selectUserExport', data)
+// 活跃用户
+export const selectUserActivityExport = (data) => getSimple('/increase/panel/selectUserActivityExport', data)
+
+// 改变状态
+export const changeUserStatus = (data) => request('post','/increase/user/changeUserStatus', data)
+
+
+

BIN
src/assets/img/chart-none-data.png


BIN
src/assets/img/login-page.png


BIN
src/assets/img/login.png


BIN
src/assets/img/logo.png


BIN
src/assets/img/yonghu.jpg


+ 132 - 0
src/assets/styles/clear.scss

@@ -0,0 +1,132 @@
+* {
+  box-sizing: border-box;
+}
+
+html{
+  width: 100%;
+  height: 100%;
+}
+body {
+  line-height: 1;
+  width: 100%;
+  height: 100%;
+  color: #666;
+  font-size: 14px;
+  font-family: "PingFang SC", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Source Han Sans", "Noto Sans CJK Sc", "Microsoft YaHei", "Microsoft Jhenghei", sans-serif !important;
+}
+
+body,
+input {
+  font-family: 'verdana';
+}
+
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+ul,
+ol,
+li,
+p,
+dl,
+dt,
+dd,
+table,
+th,
+td {
+  margin: 0;
+  padding: 0;
+}
+
+table,
+th,
+td,
+img {
+  border: 0;
+}
+
+img {
+  display: flex;
+}
+
+em,
+i,
+th {
+  font-style: normal;
+  text-decoration: none;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+th {
+  font-size: 100%;
+  font-weight: normal;
+}
+
+input,
+select,
+button,
+textarea,
+table {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+}
+
+input,
+button {
+  outline: none;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+th,
+caption {
+  text-align: left;
+}
+
+a {
+  /* color: #666; */
+  color: #4974f5;
+  text-decoration: none;
+  outline: none;
+  -webkit-tap-highlight-color: transparent;
+}
+
+select {
+  background-color: #fff;
+}
+
+iframe {
+  width: 100%;
+  height: 100%;
+  border: none;
+}
+#my-vite-app{
+  width: 100%;
+  height: 100%;
+}
+
+::-webkit-scrollbar {
+  width: 9px;
+  height: 9px;
+}
+::-webkit-scrollbar-thumb {
+  background-color: #adadad;
+  // border-radius: 9px;
+}

+ 266 - 0
src/assets/styles/common.scss

@@ -0,0 +1,266 @@
+/**
+  * 递归生成 1-100 像素的内外边距 行间距 包括
+  * 使用:margin-top-10  ===  margin-top:10px;
+  * 使用:padding-top-10  ===  padding-top:10px;
+  * 使用:line-height-10  ===  line-height:10px;
+  */
+$i: 100;
+
+@while $i >0 {
+
+  // 宽度
+  .width-#{$i} {
+    width: ($i * 1px);
+  }
+
+  // 高度
+  .height-#{$i} {
+    height: ($i * 1px);
+  }
+
+
+  // 上外边距
+  .margin-top-#{$i} {
+    margin-top: ($i * 1px);
+  }
+
+  // 下外边距
+  .margin-bottom-#{$i} {
+    margin-bottom: ($i * 1px);
+  }
+
+  // 左外边距
+  .margin-left-#{$i} {
+    margin-left: ($i * 1px);
+  }
+
+  // 右外边距
+  .margin-right-#{$i} {
+    margin-right: ($i * 1px);
+  }
+
+  // 全外边距
+  .margin-#{$i} {
+    margin: ($i * 1px);
+  }
+
+  // 上下外边距
+  .margin-top-bottom-#{$i} {
+    margin-top: ($i * 1px);
+    margin-bottom: ($i * 1px);
+  }
+
+  // 左右外边距
+  .margin-left-right-#{$i} {
+    margin-left: ($i * 1px);
+    margin-right: ($i * 1px);
+  }
+
+  // ---------------
+
+  // 上内边距
+  .padding-top-#{$i} {
+    padding-top: ($i * 1px);
+  }
+
+  // 下边距
+  .padding-bottom-#{$i} {
+    padding-bottom: ($i * 1px);
+  }
+
+  // 左边距
+  .padding-left-#{$i} {
+    padding-left: ($i * 1px);
+  }
+
+  // 右边距
+  .padding-right-#{$i} {
+    padding-right: ($i * 1px);
+  }
+
+  // 全外边距
+  .padding-#{$i} {
+    padding: ($i * 1px);
+  }
+
+  // 上下外边距
+  .padding-top-bottom-#{$i} {
+    padding-top: ($i * 1px);
+    padding-bottom: ($i * 1px);
+  }
+
+  // 左右外边距
+  .padding-left-right-#{$i} {
+    padding-left: ($i * 1px);
+    padding-right: ($i * 1px);
+  }
+
+  // 行高
+  .line-height-#{$i} {
+    line-height: ($i * 1px);
+  }
+
+  // 圆角
+  .border-radius-#{$i} {
+    border-radius: ($i * 1px);
+  }
+
+  // 字号大小
+  .font-size-#{$i} {
+    font-size: ($i * 1px);
+  }
+
+  // 层级
+  .z-index-#{$i} {
+    z-index: ($i * 1);
+  }
+
+  // top正值
+  .top-z-#{$i} {
+    top: ($i * 1px);
+  }
+
+  // top负值
+  .top-f-#{$i} {
+    top: -($i * 1px);
+  }
+
+  // left正值
+  .left-z-#{$i} {
+    left: ($i * 1px);
+  }
+
+  // left负值
+  .left-f-#{$i} {
+    left: -($i * 1px);
+  }
+
+  // right正值
+  .right-z-#{$i} {
+    right: ($i * 1px);
+  }
+
+  // right负值
+  .right-f-#{$i} {
+    right: -($i * 1px);
+  }
+
+  // bottom正值
+  .bottom-z-#{$i} {
+    bottom: ($i * 1px);
+  }
+
+  // bottom负值
+  .bottom-f-#{$i} {
+    bottom: -($i * 1px);
+  }
+
+  $i: $i - 1; //注意: 不能写成$i:$i-1
+}
+
+.top-0 {
+  top: 0;
+}
+
+.left-0 {
+  left: 0;
+}
+
+.right-0 {
+  right: 0;
+}
+
+.bottom-0 {
+  bottom: 0;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.flex {
+  display: flex;
+}
+
+.jcc {
+  justify-content: center;
+}
+.jcfe {
+  justify-content: flex-end;
+}
+.aic {
+  align-items: center;
+}
+
+.jcsb {
+  justify-content: space-between;
+}
+
+.aife {
+  align-items: flex-end;
+}
+
+.flex-wrap {
+  flex-wrap: wrap;
+}
+.fdc {
+  flex-direction: column;
+}
+
+.flex-nowrap {
+  flex-wrap: nowrap;
+}
+
+.hidden {
+  overflow: hidden;
+}
+
+.width100 {
+  width: 100%;
+}
+
+.height100 {
+  height: 100%;
+}
+
+.bold {
+  font-weight: bold;
+}
+
+.position-r {
+  position: relative;
+}
+
+.position-a {
+  position: absolute;
+}
+
+.position-f {
+  position: fixed;
+}
+
+.position-s {
+  position: sticky;
+}
+
+.ellipsis {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.btn-text-danger {
+  color: #FF4747 !important;
+}
+
+.btn-text-blue {
+  color: #10AEFF  !important;
+}
+
+.btn-text-success {
+  color: #07C160 !important;
+}
+
+.btn-text-warn {
+  color: #FFC300 !important;
+}

+ 161 - 0
src/components/echarts/chartOptions.js

@@ -0,0 +1,161 @@
+import * as echarts from 'echarts';
+let lineOption = {
+  grid: {
+    left: "6%",
+    right: 30,
+    bottom: 30,
+    top: 20,
+  },
+  tooltip: {
+    show: true,
+    trigger: "axis",
+  },
+  xAxis: {
+    type: "category",
+    data: [],
+    axisTick: {
+      show: false,
+    },
+    axisLine: {
+      lineStyle: {
+        color: "#999",
+      },
+    },
+  },
+  yAxis: {
+    type: "value",
+    name: "",
+    axisLine: {
+      lineStyle: {
+        color: "#999",
+        // width: 0,
+      },
+    },
+
+    axisTick: {
+      length: 0,
+      lineStyle: {
+        color: "#999",
+        // width: 10,
+      },
+    },
+  },
+  series: [{
+    label: {
+      show: true,
+    },
+    symbolSize: 8,
+    itemStyle: {
+      color: "#3BAFFF",
+      borderWidth: 2,
+      borderColor: "#3BAFFF",
+    },
+    data: [],
+    type: "line",
+    areaStyle: {},
+    // smooth: true,
+    areaStyle: {
+      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+          offset: 0,
+          color: "#40C0FF",
+        },
+        {
+          offset: 1,
+          color: "#EAF4FA",
+        },
+      ]),
+    },
+  }, ],
+}
+let scratchOption = {
+  grid: {
+    left: "4%",
+    right: 30,
+    bottom: 30,
+    top: 50,
+  },
+  legend: {
+    right: 0,
+  },
+  tooltip: {
+    show: true,
+    trigger: "axis",
+  },
+  xAxis: {
+    type: "category",
+    data: [],
+    axisTick: {
+      show: false,
+    },
+    axisLine: {
+      lineStyle: {
+        color: "#999",
+      },
+    },
+  },
+  yAxis: {
+    type: "value",
+    name: "",
+    axisLine: {
+      lineStyle: {
+        color: "#999",
+        // width: 0,
+      },
+    },
+
+    axisTick: {
+      length: 0,
+      lineStyle: {
+        color: "#999",
+        // width: 10,
+      },
+    },
+  },
+  series: [{
+    name: '吉祥兔刮刮卡',
+    label: {
+      show: true,
+    },
+    symbolSize: 8,
+    itemStyle: {
+      color: "#3BAFFF",
+      borderWidth: 2,
+      borderColor: "#3BAFFF",
+    },
+    data: [],
+    type: "line",
+    areaStyle: {},
+    smooth: true,
+    areaStyle: {
+      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+        offset: 1,
+        color: "#b1e1da",
+      }, ]),
+    },
+  }, {
+    name: '幸运数字刮刮卡',
+    label: {
+      show: true,
+    },
+    symbolSize: 8,
+    itemStyle: {
+      color: "#00C448",
+      borderWidth: 2,
+      borderColor: "#00C448",
+    },
+    data: [],
+    type: "line",
+    areaStyle: {},
+    smooth: true,
+    areaStyle: {
+      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+        offset: 1,
+        color: "#d4f2dc",
+      }, ]),
+    },
+  }],
+}
+export {
+  lineOption,
+  scratchOption
+};

+ 93 - 0
src/components/el-table/adaptive/adaptive.js

@@ -0,0 +1,93 @@
+import ResizeObserver from "resize-observer-polyfill";
+
+const isServer = typeof window === "undefined";
+
+/* istanbul ignore next */
+const resizeHandler = function (entries) {
+  for (let entry of entries) {
+    const listeners = entry.target.__resizeListeners__ || [];
+    if (listeners.length) {
+      listeners.forEach((fn) => {
+        fn();
+      });
+    }
+  }
+};
+
+/* istanbul ignore next */
+export const addResizeListener = function (element, fn) {
+  if (isServer) {
+    return;
+  }
+  if (!element.__resizeListeners__) {
+    element.__resizeListeners__ = [];
+    element.__ro__ = new ResizeObserver(resizeHandler);
+    element.__ro__.observe(element);
+  }
+  element.__resizeListeners__.push(fn);
+};
+
+/* istanbul ignore next */
+export const removeResizeListener = function (element, fn) {
+  if (!element || !element.__resizeListeners__) {
+    return;
+  }
+  element.__resizeListeners__.splice(
+    element.__resizeListeners__.indexOf(fn),
+    1
+  );
+  if (!element.__resizeListeners__.length) {
+    element.__ro__.disconnect();
+  }
+};
+/**
+ * How to use
+ * <el-table height="100px" v-adaptive="{bottomOffset: 30}">...</el-table>
+ * el-table height is must be set
+ *  bottomOffset: 30(default)   // The height of the table from the bottom of the page.
+ */
+
+const doResize = (el, binding, vnode) => {
+  const { el: $table } = vnode;
+
+  const { value } = binding;
+  if (!value.fixedHeader) {
+    return;
+  }
+  const bottomOffset = (value && value.bottomOffset) || 70;
+  if (!$table) {
+    return;
+  }
+
+  const layout = document.getElementById("my-vite-app");
+  const height =
+    window.innerHeight - el.getBoundingClientRect().top - bottomOffset;
+  const layoutHeight = layout.clientHeight;
+  layoutHeight > 710
+    ? (layout.style.minHeight = "710px")
+    : (layout.style.minHeight = "auto");
+  $table.style.height = height + 'px';
+  // $table.doLayout();
+};
+
+export default {
+  beforeMount(el, binding, vnode) {
+    el.resizeListener = () => {
+      doResize(el, binding, vnode);
+    };
+
+    addResizeListener(window.document.body, el.resizeListener);
+  },
+  mounted(el, binding, vnode) {
+    doResize(el, binding, vnode);
+  },
+  updated(el, binding, vnode) {
+    doResize(el, binding, vnode)
+  },
+  unmounted(el) {
+    const layout = document.getElementById("my-vite-app");
+    layout.style.minHeight = "710px";
+    removeResizeListener(window.document.body, el.resizeListener);
+  },
+};
+

+ 98 - 0
src/components/full-dialog/index.vue

@@ -0,0 +1,98 @@
+<script setup>
+import { computed } from 'vue'
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+const props = defineProps({
+  modelValue: Boolean,
+  title: String,
+  closeTitle: String,
+  tabs: Array,
+  tabsDefalut: [String, Number]
+})
+const emit = defineEmits(['update:modelValue', 'update:tabsDefalut'])
+const show = computed({
+  get() {
+    return props.modelValue
+  },
+  set(value) {
+    emit('update:modelValue', value)
+  }
+})
+const tabsVal = computed({
+  get() {
+    return props.tabsDefalut
+  },
+  set(value) {
+    emit('update:tabsDefalut', value)
+  }
+})
+const close = () => {
+  show.value = false
+}
+const tabClick = () => { }
+</script>
+
+<template>
+  <div class="full-dialog-container" v-show="show">
+    <div class="dia-header">
+      <div class="dia-title" v-if="title && !tabs">{{ title }}</div>
+      <el-tabs v-if="tabs" v-model="tabsVal" @tab-click="tabClick">
+        <el-tab-pane v-for="(item, index) in tabs" :key="index" :label="item.label" :name="item.name"></el-tab-pane>
+      </el-tabs>
+      <el-button plain type="primary" @click="close">
+        <el-icon><ArrowLeftBold /></el-icon> <span>{{ closeTitle || '返回' }}</span> </el-button>
+    </div>
+    <div class="dia-content">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.full-dialog-container {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: #fff;
+  z-index: 900;
+  border-radius: 10px;
+
+  :deep(.el-tabs) {
+    margin: 0;
+  }
+
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+
+  :deep(.el-tabs__nav-wrap::after) {
+    height: 0;
+  }
+
+  .dia-header {
+    position: absolute;
+    top: 0;
+    width: 100%;
+    z-index: 10;
+    height: 60px;
+    border-bottom: 1px solid #eee;
+    padding: 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .dia-title {
+      font-size: 16px;
+      font-weight: bold;
+    }
+  }
+
+  .dia-content {
+    height: calc(100% - 60px);
+    margin-top: 60px;
+    overflow: auto;
+    padding: 20px;
+  }
+}
+</style>

+ 131 - 0
src/components/quillEditor/index.vue

@@ -0,0 +1,131 @@
+<script setup>
+import { QuillEditor } from '@vueup/vue-quill'
+import '@vueup/vue-quill/dist/vue-quill.snow.css'
+import { uploadImgurl } from "@/apis/home.js";
+import { reactive, onMounted, ref, toRaw, watch } from 'vue'
+// import { ImageDrop } from 'quill-image-drop-module';
+// import ImageResize from 'quill-image-resize-module' // 图片缩放组件引用
+// import Quill from 'quill'
+// Quill.register('modules/resizeImage ', resizeImage ) // 注册
+// Quill.register('modules/imageDrop', ImageDrop) // 注册
+const props = defineProps(['value'])
+const emit = defineEmits(['updateValue'])
+const content = ref('')
+const myQuillEditor = ref()
+// 通过watch监听回显,笔者这边使用v-model:content 不能正常回显
+// watch(() => props.value, (val) => {
+//   toRaw(myQuillEditor.value).setHTML(val)
+//   console.log(val,'val1');
+// }, { deep: true })
+
+watch(() => props.value, (val) => { 
+    if (val) {
+        data.content = val //用于监听绑定值进行数据回填
+        // console.log(val,toRaw(myQuillEditor.value).setHTML(val),'val');
+    } else {
+        toRaw(myQuillEditor.value).setContents('') //可用于弹窗使用富文本框关闭弹窗清除值
+    }
+})
+const fileBtn = ref()
+const data = reactive({
+  content: '',
+  editorOption: {
+    modules: {
+      toolbar: [
+        // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike']
+        ['bold', 'italic', 'underline', 'strike'],
+        // 引用  代码块-----['blockquote', 'code-block']
+        ['blockquote', 'code-block'],
+        // 1、2 级标题-----[{ header: 1 }, { header: 2 }]
+        [{ header: 1 }, { header: 2 }],
+        // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }]
+        [{ list: 'ordered' }, { list: 'bullet' }],
+        // 上标/下标-----[{ script: 'sub' }, { script: 'super' }]
+        [{ script: 'sub' }, { script: 'super' }],
+        // 缩进-----[{ indent: '-1' }, { indent: '+1' }]
+        [{ indent: '-1' }, { indent: '+1' }],
+        // 文本方向-----[{'direction': 'rtl'}]
+        [{ direction: 'rtl' }],
+        // 字体大小-----[{ size: ['small', false, 'large', 'huge'] }]
+        [{ size: ['small', false, 'large', 'huge'] }],
+        // 标题-----[{ header: [1, 2, 3, 4, 5, 6, false] }]
+        [{ header: [1, 2, 3, 4, 5, 6, false] }],
+        // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }]
+        [{ color: [] }, { background: [] }],
+        // 字体种类-----[{ font: [] }]
+        [{ font: [] }],
+        // 对齐方式-----[{ align: [] }]
+        [{ align: [] }],
+        // 清除文本格式-----['clean']
+        ['clean'],
+        // 链接、图片、视频-----['link', 'image', 'video']
+        ['link','image','video']
+      ]
+    },
+    placeholder: '请输入内容...'
+  },
+})
+const imgHandler = (state) => {
+  if (state) {
+    fileBtn.value.click()
+  }
+}
+// 抛出更改内容,此处避免出错直接使用文档提供的getHTML方法
+const setValue = () => {
+  const text = toRaw(myQuillEditor.value).getHTML()
+  console.log(text,'text1');
+  emit('updateValue', text)
+}
+const handleUpload = (e) => {
+  const files = Array.prototype.slice.call(e.target.files)
+  // console.log(files, "files")
+  if (!files) {
+    return
+  }
+  const formdata = new FormData()
+  formdata.append('file', files[0])
+  uploadImgurl(formdata)  // 此处使用服务端提供上传接口
+    .then(res => {
+      if (res.data) {
+        const quill = toRaw(myQuillEditor.value).getQuill()
+        const length = quill.getSelection().index
+        quill.insertEmbed(length, 'image', res.data)
+        quill.setSelection(length + 1)
+      }
+    })
+}
+// 初始化编辑器
+onMounted(() => {
+  const quill = toRaw(myQuillEditor.value).getQuill()
+  if (myQuillEditor.value) {
+    quill.getModule('toolbar').addHandler('image', imgHandler)
+  }
+  toRaw(myQuillEditor.value).setHTML(props.value)
+})
+</script>
+
+
+<template>
+    <div>
+        <!-- 此处注意写法v-model:content -->
+        <QuillEditor ref="myQuillEditor"
+            theme="snow"
+            v-model:content="data.content"
+            :options="data.editorOption"
+            contentType="html"
+            @update:content="setValue()"
+        />
+        <!-- 使用自定义图片上传 -->
+        <input type="file" hidden accept=".jpg,.png" ref="fileBtn" @change="handleUpload" />
+    </div>
+</template>
+<style scoped lang="scss">
+// 调整样式
+:deep(.ql-editor) {
+  min-height: 180px;
+}
+:deep(.ql-formats) {
+  height: 21px;
+  line-height: 21px;
+}
+</style>

+ 68 - 0
src/components/secondary-dialog/index.vue

@@ -0,0 +1,68 @@
+<script setup>
+import { computed } from "vue";
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+  },
+  confirm: {
+    type: String,
+    default: "确认",
+  },
+  cancel: {
+    type: String,
+    default: "取消",
+  },
+  title: {
+    type: String,
+    default: "提示",
+  },
+	width: {
+		type: [String, Number],
+		default: '30%'
+	}
+});
+
+const show = computed({
+  get() {
+    return props.modelValue;
+  },
+  set(value) {
+    emit("update:modelValue", value);
+  },
+});
+
+const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
+
+const confirm = () => {
+  emit("confirm");
+};
+const cancel = () => {
+  emit('cancel')
+	show.value = false
+};
+</script>
+<template>
+  <div class="secondary">
+    <el-dialog :title="props.title" v-model="show" :before-close="cancel" :width="props.width">
+      <div class="content">
+        <slot></slot>
+      </div>
+      <div class="btn">
+        <el-button @click="cancel">{{ props.cancel }}</el-button>
+        <el-button type="primary" @click="confirm">{{
+          props.confirm
+        }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<style>
+.content{
+  margin-bottom: 18px;
+}
+.btn {
+  display: flex;
+  justify-content: end;
+}
+</style>

+ 185 - 0
src/components/uploadFile/index.vue

@@ -0,0 +1,185 @@
+<template>
+  <div class="uploadFile">
+    <el-upload ref="uploadRef" :disabled="props.disabled" v-model:file-list="data.fileList" class="upload-demo" action=""
+      multiple :on-preview="handlePreview" :on-change="fileChange" :before-remove="beforeRemove" :limit="props.limit"
+      :auto-upload="false" :on-exceed="handleExceed">
+      <el-button style="width: 100px;" plain type="primary">
+        <el-icon><Download /></el-icon>
+        <span>{{ props.title }}</span>
+      </el-button>
+      <template v-if="props.tipShow" #tip>
+        <span class="el-upload__tip" style="margin-left: 5px;">
+          {{ props.tip }}
+        </span>
+      </template>
+    </el-upload>
+  </div>
+</template>
+
+<script setup>
+import { reactive, ref, nextTick, watch, onMounted } from 'vue';
+import { upliadFile } from '@/apis/home.js';
+import { Download } from '@element-plus/icons-vue'
+import { openLoading, closeLoading } from '@/utils/loading.js'
+const uploadRef = ref();
+const data = reactive({
+  fileList: [],
+})
+let flag = ref(true);
+const emit = defineEmits(["setVal", "update:fileList"]);
+const props = defineProps({
+  // 数据
+  fileList: {
+    type: Array,
+    default: []
+  },
+  // 按钮标题
+  title: {
+    type: String,
+    default: '添加'
+  },
+  // 提示显示
+  tipShow: {
+    type: Boolean,
+    default: true
+  },
+  // 提示
+  tip: {
+    type: String,
+    default: '请上传文件'
+  },
+  // 格式
+  format: {
+    type: Array,
+    default: ['application/pdf']
+  },
+  // 条数
+  limit: {
+    type: Number,
+    default: 1
+  },
+  // 大小
+  size: {
+    type: Number,
+    default: 5
+  },
+  // 是否上传
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+// 初始化进入回显
+onMounted(() => {
+  if (props.fileList && props.fileList.length > 0) {
+    console.log(props.fileList,'file');
+    data.fileList = [];
+    props.fileList.forEach((item) => {
+      data.fileList.push({
+        url: item,
+        name: item.split('/')[item.split('/').length - 1]
+      })
+    })
+  }
+})
+// 针对弹框的回显
+watch(
+  () => props.fileList,
+  (newValue, oldValue) => {
+    if (newValue && flag.value) {
+      data.fileList = [];
+      props.fileList.forEach((item) => {
+        data.fileList.push({
+          url: item,
+          name: item.split('/')[item.split('/').length - 1]
+        })
+      })
+    }
+  }
+);
+// 查看文件
+const handlePreview = (UploadFile) => {
+  if (UploadFile.url) {
+    window.open(UploadFile.url);
+  }
+}
+// 当超出限制的linit时
+const handleExceed = (files, uploadFiles) => {
+  uploadRef.value.clearFiles();
+  nextTick(() => {
+    uploadRef.value.handleStart(files[0])
+  })
+}
+// 文件校验
+const fileChange = (file, fileList) => {
+  if (!props.format.includes(file.raw.type)) {
+    uploadRef.value.clearFiles();
+    ElMessage.error(`只能上传${props.format.join(',')}格式的文件`); //限制文件类型
+    return false;
+  } else if (file.size / 1024 / 1024 > props.size) {
+    ElMessage.error(`仅支持上传${props.size}M以内的文件`)
+  } else {
+    flag.value = false;
+    uploadFile(file)
+  }
+}
+// 上传文件
+const uploadFile = (file) => {
+  let fs = new FormData();
+  fs.append("file", file.raw);
+  fs.append('fileNameType', 2);
+  openLoading()
+  upliadFile(fs).then((res) => {
+    if (res.code === 0) {
+      data.fileList[data.fileList.length - 1].url = res.data;
+      let imagesList = [];
+      data.fileList.forEach((item) => {
+        imagesList.push(item.url);
+      });
+      emit("update:fileList", imagesList);
+      closeLoading()
+      nextTick(() => {
+        flag.value = true;
+      })
+    } else {
+      ElMessage.warning(res.msg);
+      closeLoading()
+    }
+  })
+}
+// 删除文件
+const beforeRemove = (uploadFile, uploadFiles) => {
+  return ElMessageBox.confirm(
+    `你确定删除 ${uploadFile.name} 文件?`
+  ).then(
+    () => {
+      flag.value = false;
+      data.fileList.forEach((item, index) => {
+        if (item.url === uploadFile.url) {
+          data.fileList.splice(index, 1)
+        }
+      })
+      let imagesList = [];
+      data.fileList.forEach((item) => {
+        imagesList.push(item.url);
+      });
+      emit("update:fileList", imagesList);
+      nextTick(() => {
+        flag.value = true;
+      })
+    },
+    () => false
+  )
+}
+</script>
+
+<style lang="scss">
+.upload-demo {
+  width: 240px;
+  text-align-last: left;
+
+  .el-upload-list__item {
+    cursor: pointer;
+  }
+}
+</style>

+ 153 - 0
src/components/uploadImag/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <div>
+    <el-upload class="avatar-uploader" action="" v-model:file-list="data.fileList" multiple :http-request="upload"
+       :limit="1" list-type="picture-card" :disabled="data.disFLag" :on-preview="handlePictureCardPreview" :on-remove="handleRemove">
+      <el-icon class="avatar-uploader-icon"> 
+        <Plus />
+      </el-icon>
+      <!-- <template #file="{ file }">
+        <div style="width: 100%; height: 100%;">
+          <img v-if="props.src" :src.sync="props.src" style="width: 100%; height: 100%;" class="avatar" />
+          <span class="el-upload-list__item-actions">
+            <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
+              预览
+            </span>
+            <span class="el-upload-list__item-delete" @click="handleRemove(file)">
+              删除
+            </span>
+          </span>
+        </div>
+      </template> -->
+    </el-upload>
+    <!-- 图片预览 -->
+    <el-image-viewer v-if="data.dialogVisible" :url-list="[props.src]" @close="closeViewer" />
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, watch } from "vue";
+import { ElMessage } from "element-plus";
+import { uploadImgurl } from "@/apis/home.js";
+import { Plus } from "@element-plus/icons-vue";
+const data = reactive({
+  imgUrl: "",
+  fileList: [],
+  dialogVisible: false,
+  disFLag: false
+});
+const imggUrl = ref();
+const props = defineProps({
+  src: {
+    type: String,
+    default: null
+  },
+  size: {
+    type: Number,
+    default: 1
+  }
+})
+const emit = defineEmits(["setVal"]);
+onMounted(() => {
+  if (props.src) {
+    data.fileList.push({
+      url: props.src
+    })
+  }
+});
+watch(() => props.src, (newValue, oldValue) => {
+  if (newValue) {
+    if (data.fileList.length === 0) {
+      data.fileList.push({
+        url: props.src
+      })
+    }
+  } else {
+    data.fileList = [];
+  }
+})
+// 上传图片类型及大小校验  props.size 自定义上传图片大小限制
+const upload = (param) => {
+  let file = param.file;
+  let isJpgPng = file.type == "image/jpeg" || file.type === "image/png" || file.type === "image/jpeg";
+  if (!isJpgPng) {
+    ElMessage.error("仅支持选择jpg或png或jpeg格式的图片");
+    data.fileList = [];
+    data.disFLag = false;
+  } else if ((file.size  / 1024 / 1024) > props.size) {
+    ElMessage.error(`仅支持上传${props.size}M以内的图片`)
+    data.fileList = [];
+    data.disFLag = false;
+  } else {
+    data.disFLag = true;
+    uploadFile(file);
+  }
+};
+// 图片预览开关
+const handlePictureCardPreview = (file) => {
+  data.dialogVisible = true
+};
+// 上传图片
+const uploadFile = (file) => {
+  const formData = new FormData();
+  formData.append("file", file);
+  uploadImgurl(formData).then((res) => {
+    if (res.code === 1) {
+      console.log(data.disFLag,'data.disFLag');
+      data.disFLag = true;
+      // oss是异步 做延时处理
+      setTimeout(() => {
+        data.imgUrl = res.data;
+        emit("setVal", res.data);
+      }, 500);
+    } else {
+      data.disFLag = false;
+      ElMessage.warning(res.msg)
+    }
+  });
+};
+// 是否可以点击上传
+const uploadDisabled = computed(() => {
+  return props.src != null;
+});
+// 关闭预览图片
+const closeViewer = () => {
+  data.dialogVisible = false
+}
+// 删除图片
+const handleRemove = (file) => {
+  data.imgUrl = '';
+  emit("setVal", null);
+  data.fileList = [];
+  data.disFLag = false;
+  // uploadDisabled.value = true
+};
+</script>
+<style scoped>
+.avatar-uploader .avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+  object-fit: scale-down;
+}
+</style>
+<style lang="scss">
+.el-upload-list--picture-card .el-upload-list__item {
+  width: 200px;
+  height: 200px;
+}
+
+.el-upload--picture-card {
+  width: 200px;
+  height: 200px;
+}
+
+.disabled {
+  .el-upload--picture-card {
+    display: none;
+  }
+}
+
+.el-upload-list--picture-card .el-upload-list__item {
+  margin: 0;
+}
+</style>

+ 150 - 0
src/components/uploadImage/index.vue

@@ -0,0 +1,150 @@
+<template>
+    <div>
+        <el-upload :class="{ disabled: uploadDisabled }" :disabled="props.disabled" class="avatar-uploader"
+            v-model:file-list="data.fileList" action="" multiple :http-request="upload" :limit="props.limit"
+            :list-type="props.listType" :on-preview="handlePictureCardPreview" :on-remove="handleRemove">
+            <el-icon class="avatar-uploader-icon">
+                <Plus />
+            </el-icon>
+        </el-upload>
+        <el-image-viewer v-if="data.dialogVisible" :url-list="[data.dialogImageUrl]" @close="data.dialogVisible = false" />
+    </div>
+</template>
+  
+<script setup>
+import { ref, reactive, onMounted, watch, nextTick, computed } from "vue";
+import { uploadImgurl } from "@/apis/home.js";
+import { Plus } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+const props = defineProps({
+    // 张数
+    limit: {
+        type: Number,
+        default: 1,
+    },
+    // 数据来源
+    fileList: {
+        type: Array,
+        default: [],
+    },
+    // 大小
+    size: {
+        type: Number,
+        default: 5,
+    },
+    // 格式
+    format: {
+        type: Array,
+        default: ['image/png', 'image/jpeg', 'image/jpg']
+    },
+    // 是否上传
+    disabled: {
+        type: Boolean,
+        default: false
+    },
+    //文件列表类型
+    listType:{
+        type:String,
+        default: 'picture-card'
+    }
+});
+const emit = defineEmits(["update:fileList", "uploadSuccess"]);
+const data = reactive({
+    fileList: [],
+    dialogVisible: false,
+    dialogImageUrl: "",
+});
+let flag = ref(true);
+// 初始化回显
+onMounted(() => {
+    if (props.fileList && props.fileList.length > 0) {
+        data.fileList = [];
+        props.fileList.forEach((item) => data.fileList.push({ url: item }));
+    }
+});
+// 针对弹框的回显
+watch(
+    () => props.fileList,
+    (newValue, oldValue) => {
+        if (newValue && flag.value) {
+            data.fileList = [];
+            props.fileList.forEach((item) => data.fileList.push({ url: item }));
+        }
+    }
+);
+// 图片数量大于limit的处理
+const uploadDisabled = computed(() => {
+    return (data.fileList.length >= props.limit);
+});
+// 删除图片
+const handleRemove = (file, fileList) => {
+    data.fileList = fileList;
+    let imagesList = [];
+    data.fileList.forEach((item) => {
+        imagesList.push(item.url);
+    });
+    flag.value = false;
+    emit("update:fileList", imagesList);
+    nextTick(() => {
+        flag.value = true;
+    });
+};
+// 图片校验
+const upload = (params) => {
+    let file = params.file;
+    if (!props.format.includes(file.type)) {
+        ElMessage.error(`仅支持选择${props.format.join(',')}格式的图片`);
+        data.fileList.splice(data.fileList.length - 1, 1);
+    } else if (file.size / 1024 / 1024 > props.size) {
+        ElMessage.error(`仅支持上传${props.size}M以内的图片`);
+        data.fileList.splice(data.fileList.length - 1, 1);
+    } else {
+        flag.value = false;
+        uploadFile(file);
+    }
+};
+// 上传
+const uploadFile = (file) => {
+    console.log(file,'file');
+    const formData = new FormData();
+    formData.append("file", file);
+    console.log(formData,'formData2');
+    uploadImgurl(formData).then((res) => {
+        if (res.code === 0) {
+            setTimeout(() => {
+                data.fileList[data.fileList.length - 1].url = res.data;
+                let imagesList = [];
+                data.fileList.forEach((item) => {
+                    imagesList.push(item.url);
+                });
+                emit("update:fileList", imagesList);
+                emit('uploadSuccess')
+            }, 500);
+            setTimeout(() => {
+                flag.value = true;
+            }, 1000);
+        } else {
+            ElMessage.warning(res.msg);
+        }
+    });
+};
+// 查看图片
+const handlePictureCardPreview = (file) => {
+    data.dialogImageUrl = file.url;
+    data.dialogVisible = true;
+};
+</script>
+  
+<style lang="scss">
+.disabled {
+    .el-upload--picture-card {
+        display: none;
+
+        .el-upload-list__item {
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+</style>
+  

+ 173 - 0
src/components/uploadImages/index.vue

@@ -0,0 +1,173 @@
+<template>
+  <div>
+    <el-upload :class="{ disabled: uploadDisabled }" class="avatar-uploader" v-model:file-list="data.fileList" action=""
+      :http-request="upload" :limit="props.limit" list-type="picture-card" :on-preview="handlePictureCardPreview"
+      :on-remove="handleRemove">
+      <el-icon class="avatar-uploader-icon">
+        <Plus />
+      </el-icon>
+    </el-upload>
+    <!-- <el-dialog v-model="data.dialogVisible">
+      <img w-full :src="data.dialogImageUrl" alt="Preview Image" />
+    </el-dialog> -->
+    <!-- 图片预览 -->
+    <el-image-viewer v-if="data.dialogVisible" :url-list="[data.dialogImageUrl]" @close="data.dialogVisible=false" />
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed, watch } from "vue";
+import { ElMessage } from "element-plus";
+import { uploadImgurl } from "@/apis/home.js";
+import { Plus } from "@element-plus/icons-vue";
+const data = reactive({
+  imgUrl: "",
+  fileList: [],
+  dialogVisible: false,
+  dialogImageUrl: '',
+  disFLag: false
+});
+const imggUrl = ref();
+const props = defineProps({
+  src: {
+    type: Array,
+    default: null
+  },
+  size: {
+    type: Number,
+    default: 1
+  },
+  limit: {
+    type: Number,
+    default: 1
+  }
+})
+const emit = defineEmits(["setVal"]);
+onMounted(() => {
+  if (props.src) {
+    props.src.map((item) => {
+      data.fileList.push({
+        url: item
+      })
+    })
+  }
+});
+watch(() => props.src, (newValue, oldValue) => {
+  if (newValue.length !== 0) {
+    if (data.fileList.length === 0) {
+      props.src.map((item) => {
+        data.fileList.push({
+          url: item
+        })
+      })
+    }
+  } else {
+    data.fileList = [];
+  }
+})
+const upload = (param) => {
+  let file = param.file;
+  let isJpgPng = file.type == "image/jpeg" || file.type === "image/png" || file.type === "image/jpeg";
+  if (!isJpgPng) {
+    ElMessage.error("仅支持选择jpg或png或jpeg格式的图片");
+    data.fileList.splice(data.fileList.length - 1, 1)
+    data.disFLag = false;
+  } else if ((file.size / 1024 / 1024) > props.size) {
+    ElMessage.error(`仅支持上传${props.size}M以内的图片`)
+    data.fileList.splice(data.fileList.length - 1, 1)
+    data.disFLag = false;
+  } else {
+    data.disFLag = true;
+    uploadFile(file);
+  }
+};
+
+const handlePictureCardPreview = (file) => {
+  data.dialogVisible = true
+  data.dialogImageUrl = file.url
+};
+// 上传图片
+const uploadFile = (file) => {
+  const formData = new FormData();
+  formData.append("file", file);
+  uploadImgurl(formData).then((res) => {
+    if (res.code === 0) {
+      data.disFLag = true;
+      setTimeout(() => {
+        data.fileList[data.fileList.length - 1].url = res.data
+        let imagesList = []
+        data.fileList.map(item => {
+          imagesList.push(item.url)
+        })
+        emit("setVal", imagesList);
+      }, 500);
+    } else {
+      data.disFLag = false;
+      ElMessage.warning(res.msg)
+    }
+  });
+};
+const uploadDisabled = computed(() => {
+  return (data.fileList.length >= props.limit);
+});
+
+const handleRemove = (file, fileList) => {
+  data.fileList = fileList
+  let imagesList = []
+  data.fileList.map(item => {
+    imagesList.push(item.url)
+  })
+  emit("setVal", imagesList);
+};
+</script>
+<style scoped>
+.avatar-uploader .avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+  object-fit: scale-down;
+}
+</style>
+<style lang="scss">
+.avatar-uploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+}
+
+.avatar-uploader .el-upload:hover {
+  border-color: var(--el-color-primary);
+}
+
+.el-icon.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  text-align: center;
+}
+
+.el-upload-list--picture-card .el-upload-list__item {
+  width: 171.90px;
+  height: 108px;
+  margin-right: 10px !important;
+  margin-bottom: 10px !important;
+}
+
+
+.el-upload--picture-card {
+  width: 171.90px;
+  height: 108px;
+  margin-right: 10px;
+
+}
+
+.disabled {
+  .el-upload--picture-card {
+    display: none;
+  }
+}
+</style>

+ 171 - 0
src/components/wangEditor/index.vue

@@ -0,0 +1,171 @@
+<template>
+  <div>
+    <div v-if="flag" style="border: 1px solid #ccc; margin-top: 10px">
+      <Toolbar
+        :editor="editorRef"
+        :defaultConfig="toolbarConfig"
+        :mode="mode"
+        style="border-bottom: 1px solid #ccc"
+      />
+      <Editor
+        :defaultConfig="editorConfig"
+        :mode="mode"
+        v-model="valueHtml"
+        style="height: 400px; overflow-y: hidden"
+        @onCreated="handleCreated"
+        @onChange="handleChange"
+        @onDestroyed="handleDestroyed"
+        @onFocus="handleFocus"
+        @onBlur="handleBlur"
+        @customAlert="customAlert"
+        @customPaste="customPaste"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import '@wangeditor/editor/dist/css/style.css';
+import { onBeforeUnmount, ref, shallowRef, onMounted,computed, nextTick } from 'vue';
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
+import config from '@/config'
+import { uploadImgurl } from "@/apis/home.js";
+
+
+export default {
+  components: { Editor, Toolbar },
+  props:{
+    valueHtml:{
+        type:String
+    },
+    modelValue: {
+        type: Boolean,
+    },
+  },
+  setup(props,{ emit }) {
+    // 编辑器实例,必须用 shallowRef,重要!
+    const editorRef = shallowRef();
+    // const flag = ref(true)
+    // 内容 HTML
+    // const valueHtml = ref('<p>hello对方的</p>');
+    const valueHtml = computed({
+        get() {
+            return props.valueHtml
+        },
+        set(value) {
+            emit('update:valueHtml', value)
+        }
+    })
+    const flag = computed({
+        get() {
+            return props.modelValue;
+        },
+        set(value) {
+            emit("update:modelValue", value);
+        },
+    });
+    // 模拟 ajax 异步获取内容
+    onMounted(() => {
+    //   setTimeout(() => {
+    //     valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>';
+    //   }, 1500);
+    });
+
+    const toolbarConfig = {};
+    const editorConfig = { MENU_CONF: {}}
+    editorConfig.placeholder = '请输入内容...'
+    editorConfig.MENU_CONF['uploadImage'] = {
+        async customUpload(file,insertFn){
+            const formData = new FormData();
+            formData.append("file", file);
+            uploadImgurl(formData).then((res) => {
+                if(res.code === 1){
+                    console.log(res,'res');
+                    insertFn(res.data, '', '')
+                }
+                
+            })
+        }
+    }
+    // 组件销毁时,也及时销毁编辑器,重要!
+    onBeforeUnmount(() => {
+      const editor = editorRef.value;
+      flag.value = false
+      if (editor == null) return;
+      editor.destroy();
+      nextTick(() => {
+        flag.value = true
+      })
+    });
+
+    // 编辑器回调函数
+    const handleCreated = (editor) => {
+      console.log('created', editor);
+      editorRef.value = editor; // 记录 editor 实例,重要!
+    };
+    const handleChange = (editor) => {
+      console.log('change:', editor.getHtml());
+    };
+    const handleDestroyed = (editor) => {
+      console.log('destroyed', editor);
+    };
+    const handleFocus = (editor) => {
+      console.log('focus', editor);
+    };
+    const handleBlur = (editor) => {
+      console.log('blur', editor);
+    };
+    const customAlert = (info, type) => {
+      alert(`【自定义提示】${type} - ${info}`);
+    };
+    const customPaste = (editor, event, callback) => {
+      console.log('ClipboardEvent 粘贴事件对象', event);
+
+      // 自定义插入内容
+      editor.insertText('xxx');
+
+      // 返回值(注意,vue 事件的返回值,不能用 return)
+      callback(false); // 返回 false ,阻止默认粘贴行为
+      // callback(true) // 返回 true ,继续默认的粘贴行为
+    };
+
+    const insertText = () => {
+      const editor = editorRef.value;
+      if (editor == null) return;
+
+      editor.insertText('hello world');
+    };
+
+    const printHtml = () => {
+      const editor = editorRef.value;
+      if (editor == null) return;
+      console.log(editor.getHtml());
+    };
+
+    const disable = () => {
+      const editor = editorRef.value;
+      if (editor == null) return;
+      editor.disable();
+    };
+
+    return {
+      editorRef,
+      mode: 'default',
+      valueHtml,
+      toolbarConfig,
+      editorConfig,
+      handleCreated,
+      handleChange,
+      handleDestroyed,
+      handleFocus,
+      handleBlur,
+      customAlert,
+      customPaste,
+      insertText,
+      printHtml,
+      disable,
+      flag
+    };
+  },
+};
+</script>

+ 3 - 0
src/config/default.js

@@ -0,0 +1,3 @@
+export default {
+  prefix: 'bxtdl'
+}

+ 11 - 0
src/config/index.js

@@ -0,0 +1,11 @@
+// 获取当前应用运行变量
+
+const NODE_ENV = import.meta.env.VITE_NODE_ENV;
+// 配置文件模块化
+const files = import.meta.globEager('./modules/*.js')
+const modules = {}
+for (const key in files) {
+  modules[key.replace(/(\.\/modules\/|\.js)/g, '')] = files[key].default
+}
+// 导出对应js模块
+export default modules[NODE_ENV]

+ 4 - 0
src/config/modules/dev.js

@@ -0,0 +1,4 @@
+export default {
+   baseUrl: 'http://debugapi.mstardance.com/api',
+}
+

+ 5 - 0
src/config/modules/mock.js

@@ -0,0 +1,5 @@
+export default {
+    baseUrl: 'http://47.100.78.149:3000/mock/51',
+ }
+ 
+ 

+ 3 - 0
src/config/modules/pre.js

@@ -0,0 +1,3 @@
+export default {
+  baseUrl: '/pre'
+}

+ 3 - 0
src/config/modules/prod.js

@@ -0,0 +1,3 @@
+export default {
+  baseUrl: 'http://api.mstardance.com/api',
+}

+ 4 - 0
src/config/modules/test.js

@@ -0,0 +1,4 @@
+export default {
+  baseUrl: 'http://testapi.mstardance.com/api',
+  // baseUrl: 'http://106.14.221.175:9999'
+}

+ 4 - 0
src/install/app.js

@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from '../App.vue'
+
+export default createApp(App)

+ 1 - 0
src/install/index.js

@@ -0,0 +1 @@
+const _ = import.meta.globEager('./install*.js')

+ 4 - 0
src/install/installGlobalCss.js

@@ -0,0 +1,4 @@
+// 公共样式
+import '@/assets/styles/clear.scss'
+import '@/assets/styles/common.scss'
+// import "@/assets/icon/iconfont.css";

+ 5 - 0
src/install/installRouter.js

@@ -0,0 +1,5 @@
+// 路由
+import app from './app'
+import router from '@/router'
+
+app.use(router)

+ 7 - 0
src/install/installStore.js

@@ -0,0 +1,7 @@
+// 存储库  pinia-plugin-persist 持久化插件
+import app from './app'
+import { createPinia } from 'pinia'
+import piniaPersist from 'pinia-plugin-persist'
+const pinia = createPinia()
+pinia.use(piniaPersist)
+app.use(pinia)

+ 13 - 0
src/install/ployfill.js

@@ -0,0 +1,13 @@
+(function () {
+    if (typeof EventTarget !== "undefined") {
+      let func = EventTarget.prototype.addEventListener;
+      EventTarget.prototype.addEventListener = function (type, fn, capture) {
+        this.func = func;
+        if (typeof capture !== "boolean") {
+          capture = capture || {};
+          capture.passive = false;
+        }
+        this.func(type, fn, capture);
+      };
+    }
+  }())

+ 157 - 0
src/layout/aside/index.vue

@@ -0,0 +1,157 @@
+<template>
+	<div class="aside">
+		<el-menu v-if="store.data.menu || routerList" class="el-menu-vertical-demo" unique-opened @open="handleOpen"
+			@close="handleClose" :router="true" :default-active="$route.path">
+			<template v-for="(item, index) in routerList" :key="index">
+				<el-sub-menu v-if="item.children" :index="item.path">
+					<template #title>
+						<span>{{ item.name }}</span>
+					</template>
+					<!-- 此处调用menu-item组件 -->
+					<menu-item :arrList="item.children"></menu-item>
+				</el-sub-menu>
+				<el-menu-item :index="item.path" v-else @click="addTab(item.name, item.path)">
+					<el-icon>
+						<i :class="'iconfont ' + item.icon"></i>
+					</el-icon>
+					<template #title>{{ item.name }}</template>
+				</el-menu-item>
+			</template>
+		</el-menu>
+		<div v-else style="text-align: center; line-height: auto; padding: 20px;">
+            暂时没有发现菜单
+        </div>
+	</div>
+</template>
+
+<script setup>
+import { watch } from 'vue';
+import { useRouter, useRoute,onBeforeRouteUpdate } from 'vue-router'
+import MAIN from '@/store/main.js';
+import routerList from '@/router/router.js'
+import menuItem from './menuItem.vue';
+import { Eleme } from "@element-plus/icons-vue";
+import { useCounterStore } from "@/stores/counter.js";
+const store = useCounterStore();
+const $router = useRouter();
+const $route = useRoute()
+const main = MAIN()
+
+const handleOpen = () => {
+
+}
+
+const handleClose = () => {
+
+}
+const addTab = (obj) => {
+	
+}
+watch(() => $route.path, ((newOld, cleOld) => {
+	let obj = {
+		name: $router.currentRoute._rawValue.meta.title,
+		path: $router.currentRoute._rawValue.path
+	}
+	main.addMenuTab(obj)
+}))
+</script>
+
+<style lang="scss" scoped>
+.aside {
+	width: 100%;
+	height: 100%;
+	box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.08);
+
+	.el-menu-vertical-demo {
+		height: 100%;
+		width: 100%;
+		overflow-y: auto;
+		border: none;
+
+		.el-menu-item {
+			height: 48px;
+		}
+
+		.el-menu-item [class^=el-icon] {
+			margin-left: 2px !important;
+		}
+
+		:deep(.el-sub-menu) {
+
+			.el-sub-menu__title {
+				color: #152129;
+				height: 48px;
+				letter-spacing: 1px;
+				margin-left: 6px;
+
+				i {
+					margin-right: 10px;
+				}
+			}
+
+			.el-menu--inline {
+				padding: 0;
+
+				.el-menu-item {
+					padding: 0;
+					padding-left: 59px;
+					color: #707476;
+					letter-spacing: 1px;
+					font-size: 14px;
+				}
+
+				.el-menu-item.is-active {
+					background: #f2f3f5;
+					color: #10AEFF;
+					padding-left: 59px;
+				}
+
+
+			}
+		}
+
+		:deep(.el-sub-menu.is-active) {
+			.el-sub-menu__title {
+				.el-icon {
+					color: #1f63ff;
+				}
+
+				span {
+					color: #1f63ff;
+				}
+			}
+
+			.el-sub-menu3 {
+				.el-sub-menu__title {
+					.el-icon {
+						color: #152129;
+					}
+
+					span {
+						color: #152129;
+					}
+
+					span {
+						margin-left: 3px;
+					}
+				}
+			}
+		}
+		:deep(.el-sub-menu.el-sub-menu3.is-active.is-opened){
+			.el-sub-menu__title {
+				.el-icon {
+					color: #1f63ff;
+				}
+
+				span {
+					color: #1f63ff;
+				}
+			}
+		}
+	}
+
+	.el-menu-vertical-demo::-webkit-scrollbar {
+		display: none;
+	}
+}
+</style>

+ 43 - 0
src/layout/aside/menuItem.vue

@@ -0,0 +1,43 @@
+<template>
+    <template v-for="item in arrList">
+        <el-menu-item v-if='!item.children' :key="item.id" :index="item.path">
+            <span>{{ item.name }}</span>
+        </el-menu-item>
+        <el-sub-menu :index="item.path" class="el-sub-menu3" v-else>
+            <template #title> <i :class="'iconfont ' + item.icon"></i><span>{{ item.name }}</span></template>
+            <menu-item :arrList="item.children"></menu-item>
+        </el-sub-menu>
+    </template>
+</template>
+
+<script setup>
+import menuItem from './menuItem.vue';
+
+const props = defineProps({
+    arrList: Array,
+});
+</script>
+
+<style lang="scss">
+.el-sub-menu3 {
+    .el-sub-menu__title {
+        .el-icon {
+            color: #152129;
+        }
+
+        span {
+            color: #152129;
+        }
+
+        span {
+            margin-left: 3px;
+        }
+    }
+
+    .el-menu-item {
+        span {
+            margin-left: 22px;
+        }
+    }
+}
+</style>

+ 118 - 0
src/layout/header/index.vue

@@ -0,0 +1,118 @@
+<template>
+    <div class="header">
+        <div class="header-logo">
+            <div class="header-img">
+                <img src="@/assets/img/logo.png" alt="" />
+            </div>
+        </div>
+        <div class="header-news">
+            <el-avatar :size="30" :src="AccountPicture" />
+            <span>{{info?.username || '开发者'}}</span>
+            <el-dropdown>
+                <span class="el-dropdown-link">
+                    <el-icon class="el-icon--right">
+                        <arrow-down />
+                    </el-icon>
+                </span>
+                <template #dropdown>
+                    <el-dropdown-menu>
+                        <!-- <el-dropdown-item>修改密码</el-dropdown-item> -->
+                        <el-dropdown-item @click="open">退出系统</el-dropdown-item>
+                    </el-dropdown-menu>
+                </template>
+            </el-dropdown>
+
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { reactive, ref } from 'vue'
+import { ArrowDown } from "@element-plus/icons-vue";
+import AccountPicture from '@/assets/img/yonghu.jpg'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useRouter, useRoute } from 'vue-router'
+import { $ls, $ss } from "@/storage";
+const $router = useRouter();
+
+const info = $ls.getItem('info')
+const changePassWord = () => {
+
+}
+const open = () => {
+  ElMessageBox.confirm(
+    '是否确定要退出?',
+    '警告',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+        localStorage.clear()
+        sessionStorage.clear();
+          $router.push({
+            path: "/login"
+          });
+    })
+    .catch(() => {
+    })
+}
+</script>
+
+<style lang="scss" scoped>
+.header {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: space-between;
+    box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15);
+
+    .header-logo {
+        width: 240px;
+        height: 100%;
+        position: relative;
+
+        .header-img {
+            position: absolute;
+            left: 30%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 100px;
+            height: 32px;
+
+            img {
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+
+    .header-news {
+        margin-right: 10px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        letter-spacing: 1px;
+        text-shadow: 0px 1px 0px #EBEEF5;
+
+        span {
+            margin: 0 5px;
+        }
+    }
+
+    .el-dropdown {
+        color: #000;
+    }
+    :deep(.el-tooltip__trigger:focus-visible) {
+      outline: unset;
+    }
+    .example-showcase .el-dropdown-link {
+        cursor: pointer;
+        color: var(--el-color-primary);
+        display: flex;
+        align-items: center;
+    }
+}
+</style>

+ 74 - 0
src/layout/index.vue

@@ -0,0 +1,74 @@
+<template>
+    <div class="common-layout">
+        <el-container>
+            <el-header>
+                <Header />
+            </el-header>
+            <el-container class="main-container">
+                <el-aside>
+                    <Aside />
+                </el-aside>
+                <el-main>
+                    <Main />
+                </el-main>
+            </el-container>
+        </el-container>
+    </div>
+</template>
+
+<script setup>
+import Header from './header/index.vue';
+import Aside from './aside/index.vue'
+import Main from './main/index.vue'
+import { useCounterStore } from "@/stores/counter.js";
+const store = useCounterStore();
+store.GetMenu()
+</script>
+
+<style lang="scss">
+.common-layout {
+    width: 100%;
+    height: 100%;
+
+    .el-container {
+        position: relative;
+        width: 100%;
+        height: 100%;
+
+        .el-header {
+            padding: 0;
+            position: absolute;
+            left: 0;
+            top: 0;
+            height: 56px;
+            width: 100%;
+            background: #fff;
+            box-shadow: 0px 1px 0px 0px #EBEEF5;
+            box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.15);
+            min-width: 1400px;
+            z-index: 11;
+        }
+
+        .main-container {
+            position: absolute;
+            left: 0;
+            top: 56px;
+            height: calc(100% - 56px);
+            min-width: 1400px;
+
+            .el-aside {
+                width: 240px;
+            }
+
+            .el-main {
+                width: calc(100% - 240px);
+                height: 100%;
+                display: flex;
+                background: #F7F7F7;
+                flex-direction: column;
+                padding: 0;
+                position: relative;
+            }
+        }
+    }
+}</style>

+ 145 - 0
src/layout/main/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="main-nav">
+    <el-tabs type="card" class="demo-tabs" :closable="main.mainTabsList.length > 1" @tab-click="changeTab"
+      @tab-remove="removeTab" v-model="main.curActiveTabVal">
+      <el-tab-pane v-for="item in main.mainTabsList" :key="item.path" :label="item.name" :name="item.path"></el-tab-pane>
+    </el-tabs>
+  </div>
+  <div class="main-view">
+    <RouterView />
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from 'vue';
+import { RouterView } from 'vue-router'
+import { useRouter } from 'vue-router';
+import MAIN from '@/store/main.js'
+const $router = useRouter()
+const main = MAIN();
+onMounted(() => {
+  main.remoMenuTab();
+  let obj = {
+    name: $router.currentRoute._rawValue.meta.title,
+    path: $router.currentRoute._rawValue.path
+  }
+  main.addMenuTab(obj)
+})
+
+const changeTab = ((event) => {
+  $router.push(event.props.name);
+})
+
+const removeTab = ((targetName) => {
+  const tabs = main.mainTabsList;
+  let activeName = main.curActiveTabVal;
+  if (activeName === targetName) {
+    tabs.forEach((tab, index) => {
+      if (tab.path === targetName) {
+        const nextTab = tabs[index + 1] || tabs[index - 1]
+        if (nextTab) {
+          activeName = nextTab.path
+        }
+      }
+    })
+  }
+  main.$patch((state) => {
+    state.curActiveTabVal = activeName
+  })
+  $router.push(activeName);
+  main.delMenuTab(targetName);
+})
+</script>
+
+<style lang="scss">
+.main-nav {
+  width: 100%;
+  height: 48px;
+  min-height: 48px;
+  background: #fff;
+  border: none;
+  border-left: 1px solid rgba(0, 0, 0, 0.08);
+  border: solid 0px 1px 4px 0px rgba(0, 0, 0, 0.08);
+  box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.08);
+  padding: 0 24px;
+
+  .demo-tabs {
+    height: 36px;
+    margin-top: 6px;
+
+    .el-tabs__content {
+      display: none;
+    }
+
+    .el-tabs__header {
+      margin: 0;
+      height: 36px;
+      border: none;
+      line-height: 36px;
+
+      .el-tabs__nav-wrap {
+        height: 36px;
+        border-radius: 4px;
+
+        .el-tabs__nav-scroll {
+          height: 100%;
+
+          .el-tabs__nav {
+            height: 36px;
+            line-height: 36px;
+            border: 0;
+            background: #F0F2F5;
+            border-radius: 4px;
+            display: flex;
+            align-items: center;
+            padding: 0 2px;
+
+            .el-tabs__item {
+              border: none;
+            }
+
+            .el-tabs__item.is-active {
+              background: #fff;
+              height: 32px;
+              line-height: 32px;
+              border-radius: 4px;
+
+            }
+          }
+        }
+
+        .el-tabs__nav-prev {
+          height: 36px;
+          line-height: 36px;
+          background: #F0F2F5;
+          border-radius: 4px;
+          padding: 0 8px 0 5px;
+        }
+
+        .el-tabs__nav-next {
+          height: 36px;
+          line-height: 36px;
+          background: #F0F2F5;
+          border-radius: 4px;
+          padding: 0 5px 0 8px;
+        }
+      }
+    }
+  }
+}
+
+.main-view {
+  margin: 24px;
+  height: 100%;
+  background: #fff;
+  border-radius: 10px;
+  position: relative;
+  overflow-y: auto;
+}
+
+
+:deep(.el-tabs--card>.el-tabs__header) {
+  height: 36px;
+  border: none;
+}
+</style>

+ 7 - 0
src/main.js

@@ -0,0 +1,7 @@
+import app from '@/install/app.js'
+import '@/install'
+import adaptive from '@/components/el-table/adaptive/adaptive.js'
+// import "default-passive-events";
+
+app.directive('adaptive', adaptive);
+app.mount('#my-vite-app')

+ 69 - 0
src/plugins/request/axiosInstance.js

@@ -0,0 +1,69 @@
+// 得到一个axios实例生成器函数,可配置,不对外暴露,库内部使用
+
+import axios from 'axios' // axios version exactly 0.19.2
+import config from '@/config'
+import { $ls,$ss } from '@/storage'
+const baseUrl = config.baseUrl
+/**
+ * errorTip 全局错误提示
+ */
+const errorTipDefault = window.console.error
+
+export default (
+  errorTip = errorTipDefault,
+  userDefaultConfig = {}
+) => {
+  // may process the errorTip and userDefaultConfig in later version
+  /**
+   * 传入两个自定义的拦截器
+   */
+  return (
+    requestHandler,
+    responseHander
+  ) => {
+    const internalConfig = {
+      baseURL: baseUrl,
+      timeout: 30e3,
+      headers: {
+        accessToken: $ss.getItem('token')
+      }, // 保证在请求拦截里面可能直接访问到headers而不是报错undefined
+      // withCredentials: true,
+      validateStatus: () => true, // 所有的http返回都收敛
+    }
+    const mergedCondig = {
+      ...internalConfig,
+      ...userDefaultConfig,
+    }
+    const instance = axios.create(mergedCondig)
+    // 请求拦截
+    instance.interceptors.request.use(
+      (config) => {
+        if ($ss.getItem("token") != null) {
+          config.headers["accessToken"] = $ss.getItem("token");
+        }
+        return requestHandler(config)
+      },
+      // 取消此请求
+      (error) =>
+        new Promise(() => {
+          console.error('error in axios.requestInterceptor', error)
+        })
+    )
+    // 响应拦截,收敛全部的请求处理,包括有返回的请求和未返回的请求
+    instance.interceptors.response.use(
+      (response) => responseHander(response, true), // true 表示一次完整的http请求过程(有来有回)(不管是何种状态码)
+      (err) => responseHander(err, false) // 失败的请求(跨域、超时、或其他JS脚本错误导致的请求没法出去)
+    )
+    // 响应拦截,对用户的响应处理器函数进行错误兜底处理
+    instance.interceptors.response.use(
+      undefined,
+      // 中断
+      (error) => {
+        console.error('error in axios.responseInterceptor', error)
+        errorTip(`api error: ${error?.message || 'unknown error'}`)
+        throw error // 重新抛出
+      }
+    )
+    return instance
+  }
+}

+ 124 - 0
src/plugins/request/index.js

@@ -0,0 +1,124 @@
+// 库的暴露出去的方法,需要传入
+// 1. 全局错误提示(默认是console.error)
+// 2. 请求处理器,在发送http请求时,对axios的config做一些全局或针对性的处理
+// 3. 响应处理器,来自服务端的http响应(包括200和非200的全部http status都会到此),以及因为超时被abort取消的Error、因为跨域被取消的Erro以及其他JS代码错误导致请求没法出去的Error
+
+import axiosInstance from './axiosInstance'
+import { object2Qs, file2Formdata, self } from './utils'
+
+export default (
+  errorTip,
+  defaultConfig,
+  requestHander = self,
+  responseHander = self
+) => {
+  const axios = axiosInstance(errorTip, defaultConfig)(
+    requestHander,
+    responseHander
+  )
+  /**
+   *
+   * @param type 类型
+   * getSimple 简单的get请求,需要一个对象,对象以键值对拼接在URL后面
+   * postSimple 简单的post请求,需要一个对象,以www-urlencoded-form方式发送
+   * postJson json请求,需要一个对象,以JSON格式发送
+   * postFile 上传文件,需要一个formData或File或Blob,如果是File自动封装成名为file值为此File的formData
+   * @param url 地址
+   * @param data 数据
+   * @param opt 其他参数
+   */
+  const interalRequest = (
+    type = 'getSimple',
+    url,
+    data,
+    opt = {}
+  ) => {
+    const commonConfig = {
+      // 不同请求方式可能的公共请求参数
+      url,
+      responseType: 'json',
+    }
+
+    let specialConfig = {}
+    switch (type) {
+      case 'getSimple':
+        specialConfig = {
+          method: 'get',
+          params: data,
+          ...opt,
+        }
+        break
+      case 'postSimple':
+        specialConfig = {
+          method: 'post',
+          data: object2Qs(data),
+          ...opt,
+        }
+        break
+      case 'postJson':
+        specialConfig = {
+          method: 'post',
+          data,
+          ...opt,
+        }
+        break
+      case 'postFile':
+        specialConfig = {
+          method: 'post',
+          data: file2Formdata(data),
+          ...opt,
+        }
+        break
+      case 'custom':
+        specialConfig = {
+          // do nothing just pass to what received
+          ...opt,
+        }
+        if ((specialConfig.method) === 'get') {
+          specialConfig.params = data
+        } else {
+          specialConfig.data = data
+        }
+        break
+    }
+
+    const finalConfig = {
+      ...commonConfig,
+      ...specialConfig,
+    }
+
+    // @ts-ignore
+    return axios(finalConfig)
+  }
+
+  const getSimple = (url, data = {}, opt = {}) => {
+    return interalRequest('getSimple', url, data, opt)
+  }
+
+  const postSimple = (url, data = {}, opt = {}) => {
+    return interalRequest('postSimple', url, data, opt)
+  }
+
+  const postJson = (url, data = {}, opt = {}) => {
+    return interalRequest('postJson', url, data, opt)
+  }
+
+  const postFile = (url, data = new window.Blob([]), opt = {}) => {
+    return interalRequest('postFile', url, data, opt)
+  }
+
+  const request = (method = 'get', url, data, opt = {}) => {
+    return interalRequest('custom', url, data, {
+      method: method.toLowerCase(),
+      ...opt,
+    })
+  }
+
+  return {
+    getSimple,
+    postSimple,
+    postJson,
+    postFile,
+    request
+  }
+}

+ 23 - 0
src/plugins/request/utils.js

@@ -0,0 +1,23 @@
+// 库的工具方法
+
+// 对象 -> QuerySearch -> application/x-www-form-urlencoded
+export const object2Qs = (object) => {
+  const qs = new URLSearchParams()
+  Object.keys(object).forEach((key) => {
+    qs.append(key, object[key])
+  })
+  return qs
+}
+
+// 对象 -> formdata
+export const file2Formdata = (file) => {
+  if (file instanceof FormData) return file
+  const formdata = new FormData()
+  formdata.append('file', file, file.name)
+  return formdata
+}
+
+// return self
+export const self = function (v) {
+  return v
+}

+ 83 - 0
src/plugins/storage/index.js

@@ -0,0 +1,83 @@
+import dayjs from 'dayjs'
+import { formateData } from './utils'
+
+import localStorageEngine from './localStorage'
+import sessionStorageEngine from './sessionStorage'
+
+const engineMap = {
+  local: localStorageEngine,
+  session: sessionStorageEngine,
+}
+
+const self = (v) => v
+
+/******
+ *
+ * @param type 存储类型,local = localStorage session = sessionStorage
+ * @param prefix 命名空间 配置 存储名称前缀
+ * @param encrypt 是否支持加密,传入一个对象,方法en用于加密,方法de用于解密
+ *
+ * ******/
+
+export default (
+  type,
+  prefix = '',
+  encrypt
+) => {
+  const engine = engineMap[type]
+  encrypt = encrypt ?? { en: self, de: self }
+  return {
+    engine,
+    prefix,
+    encrypt,
+    /**
+     * @param name
+     * @param value
+     * @param expire 过期时间,单位毫秒
+     * @param encrypt 是否要加密
+     */
+    setItem(name, value, expire = -1, encrypt = false) {
+      name = `${this.prefix}__${name}`
+      const data = formateData(value, expire)
+      if (encrypt) {
+        data.data = this.encrypt.en(data.data)
+        data.encrypt = 1
+      }
+      this.engine.setItem(name, JSON.stringify(data))
+    },
+    getItem(name) {
+      name = `${this.prefix}__${name}`
+      let dataRead = this.engine.getItem(name)
+      if (dataRead === null) return null // 不存在
+      const data = JSON.parse(dataRead)
+      const now = dayjs().valueOf()
+      if (data.expire < now && data.expire !== -1) {
+        // 过期了
+        this.engine.removeItem(name) // 删除
+        return null
+      }
+      if (data.encrypt) {
+        // 存在加密
+        data.data = this.encrypt.de(data.data)
+        data.encrypt = 0
+      }
+      let result = null
+      try {
+        result = JSON.parse(data.data)
+      } catch (error) {
+        result = data.data
+      }
+      return result
+    },
+    removeItem(name) {
+      name = `${this.prefix}__${name}`
+      this.removeItemNative(name)
+    },
+    // 原始的setItem
+    setItemNative: engine.setItem.bind(engine),
+    // 原始的getItem
+    getItemNative: engine.getItem.bind(engine),
+    // 原始的removeItem
+    removeItemNative: engine.removeItem.bind(engine),
+  }
+}

+ 1 - 0
src/plugins/storage/localStorage.js

@@ -0,0 +1 @@
+export default window.localStorage

+ 1 - 0
src/plugins/storage/sessionStorage.js

@@ -0,0 +1 @@
+export default window.sessionStorage

+ 13 - 0
src/plugins/storage/utils.js

@@ -0,0 +1,13 @@
+// 工具
+import dayjs from 'dayjs'
+
+// 封装用于存储的data
+export const formateData = (value, expire) => {
+  expire >= 0 && (expire = dayjs().add(expire, 'ms').valueOf())
+  const t = {
+    expire,
+    encrypt: 0,
+    data: typeof value === 'object' ? JSON.stringify(value) : value + '',
+  }
+  return t
+}

+ 97 - 0
src/request/index.js

@@ -0,0 +1,97 @@
+import getRequest from '@/plugins/request'
+import config from '@/config'
+import { wipeNulish } from '@/utils'
+import { ElMessage } from 'element-plus'
+import { $ls } from "@/storage";
+
+// 全局的错误提示,默认是console.error
+const errorTip = (msg) => {
+  ElMessage.error(msg)
+}
+
+const requestHandler = (config) => {
+  // 检查网络状态
+  if (!window.navigator.onLine) {
+    throw Error('没有网络链接!请检查网络连接!然后刷新重试')
+  }
+  // 如果参数有null和undefined就删除
+  if (config.method === 'get') {
+    config.params = wipeNulish(config.params)
+  } else {
+   if (config.method !== 'put') {
+        const { data } = config
+        const shouldWipeNulish = !(
+        data instanceof File ||
+        data instanceof Blob ||
+        data instanceof FormData ||
+        data instanceof URLSearchParams
+      )
+       config.data = shouldWipeNulish ? wipeNulish(config.data) : data
+   }
+    
+  }
+  return config
+}
+
+const responseHandler = (response, type) => {
+  // // 如果是导出就把头部字段加进去
+  if (response.data instanceof Blob) {
+     response.data = {
+      data: response.data,
+      headers: response.headers
+     }
+  }
+  // type = true response的后端返回的结果,不管这个response的状态码是何种
+  // type = false 表示请求出错了,可能是因为超时,也可能是跨域,也可能是脚本错误,但是两面的2个在开发和测试时候已经避免掉了
+  // 200 ok { code: number, data: Object, message: string }
+  // 201 created
+  // 401 unauthorized
+  // 403 forbidden
+  // 404 notfound
+
+  // 没有收到
+  if (!type) {
+    // 在生产环境只有超时错误,其他错误在开发和测试就被避免掉了
+    const err = response
+    throw Error('网络异常:' + err.message)
+  }
+  // 有response
+  if (type) {
+    // 响应错误
+    const httpStatus = +response?.status
+    switch (httpStatus) {
+      case 200:
+        return response.data
+      case 201:
+        return response
+      case 401:
+        throw Error('没有足够的权限')
+      case 403:
+        throw Error('禁止访问')
+      case 404:
+        throw Error('未找到有效资源')
+      case 424:
+          window.location.href = '/login'
+          throw Error('token已失效,请重新登录');
+      case 500:
+        throw Error('服务器暂时无法连接,请稍后再试')
+    }
+    if (!isNaN(httpStatus) && (httpStatus < 200 || httpStatus > 299)) {
+      throw Error('未知异常,请刷新重试')
+    }
+  }
+  // 兜底,让它成功,返回给外部的处理函数
+  return response
+}
+
+const { getSimple, postSimple, postJson, postFile, request } = getRequest(
+  errorTip,
+  {
+    timeout: 30e3,
+    baseURL: config.baseUrl,
+  },
+  requestHandler,
+  responseHandler
+)
+
+export { getSimple, postSimple, postJson, postFile, request }

+ 83 - 0
src/router/index.js

@@ -0,0 +1,83 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import _ from 'lodash'
+import { filterGlobEagerFilename } from '@/utils'
+import permission from './permission'
+import layout from '@/layout/index.vue'
+import { $ls, $ss } from "@/storage";
+// sub routers
+const subroutersInLevelWithRoot = _(
+  filterGlobEagerFilename([import.meta.globEager('./sub/*')][0])
+)
+  .values()
+  .map((i) =>
+    i instanceof window.Array
+      ? (() => {
+        // 将带有 ~ 的路由平铺,不需要关心下面具体的实现,只需要知道作用就行了
+        // 例子见home.js
+        const first = i.shift()
+        return _(i)
+          .map((i) =>
+            _(i)
+              .mapValues((val, key) =>
+                key === 'path' && val[0] === '~'
+                  ? `${first.path}/${val.substring(1)}`
+                  : val
+              )
+              .value()
+          )
+          .concat(first)
+          .value()
+      })()
+      : [i]
+  )
+  .reduce((acc, cur) => (acc.push.apply(acc, cur), acc), []);
+
+  const homeRedirect = {
+    path: '/home',
+    redirect: (() => {
+      if ($ls.getItem('menu') && $ls.getItem('menu').length > 0) {
+        // if ($ss.getItem('info').sysUser.name == '供应商') {
+        //   return '/supplier/supplierDashboard'
+        // } else {
+          if ($ls.getItem('menu')[0].children) {
+            if ( $ls.getItem('menu')[0].children[0].children) {
+                return $ls.getItem('menu')[0].children[0].children[0].path
+            } else {
+              return $ls.getItem('menu')[0].children[0].path
+            }
+          } else {
+            return $ls.getItem('menu')[0].path
+          }
+        }
+      // } else {
+      //   return '/empty'
+      // }
+    })
+  }
+
+const routes = [
+    {
+        path: '/',
+        redirect: '/login',
+      },
+      {
+        path: '/home',
+        component: layout,
+        children: [...subroutersInLevelWithRoot],
+        meta: {
+          title: '首页'
+        }
+      },
+      {
+        path: '/login',
+        component: () => import('@/views/login/index.vue'),
+        meta: {
+          title: '登录'
+        }
+      },
+]
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+})
+export default permission(router)

+ 46 - 0
src/router/permission.js

@@ -0,0 +1,46 @@
+import { includes } from "lodash";
+import NProgress from 'nprogress' // 进度条
+import 'nprogress/nprogress.css' //样式必须引入
+import { $ls, $ss } from "@/storage";
+
+/**
+ * 设置网页title
+ */
+export const setWebTitle = (title) => {
+  try {
+    window.document.title = title
+  } catch (error) {
+    console.error(error)
+  }
+}
+const whiteList = [] // 白名单
+const blockList = [] // 黑名单
+
+
+export default (router) => {
+  // 路由前置守卫
+  router.beforeEach((to, from, next) => {
+    NProgress.start(); // 开启进度条
+    if (to.meta?.title) {
+      setWebTitle(to.meta.title + '');
+    }
+    if (to.path === '/login') {
+      localStorage.clear();
+      next()
+    } else {
+      let token = $ss.getItem('token');
+      // let info = $ss.getItem('info');
+      if (token) {
+        next()
+      } else {
+        next('/login')
+      }
+    }
+  //  next()
+  })
+  // 路由后置守卫(当你真正进入到某个页面之后才执行)
+  router.afterEach((to, from) => {
+    NProgress.done(); // 关闭进度条
+  })
+  return router
+}

+ 22 - 0
src/router/router.js

@@ -0,0 +1,22 @@
+const routerList = [
+  {
+    name:'供应商管理',
+    path:'/supplier',
+    children:[
+      {
+        name:'供应商仪表盘',
+        path:'/supplier/supplierDashboard'
+      },
+      {
+        name:'供应商列表管理',
+        path:'/supplier/suplierListManage'
+      },
+      {
+        name:'供应商游戏币消耗流水记录',
+        path:'/supplier/sgccRecord'
+      }
+    ]
+  },
+]
+
+export default routerList

+ 35 - 0
src/router/sub/supplier.js

@@ -0,0 +1,35 @@
+export default [
+    {
+        path:'/supplier',
+        meta:{
+            title:'供应商管理'
+        }
+    },
+    {
+        path: '~supplierDashboard',
+        name: 'supplierDashboard',
+        component: () => import('@/views/supplier/supplierDashboard/index.vue'),
+        meta: {
+            title: '供应商仪表盘',
+            keepAlive: true
+        }
+    },
+    {
+        path: '~suplierListManage',
+        name: 'suplierListManage',
+        component: () => import('@/views/supplier/suplierListManage/index.vue'),
+        meta: {
+            title: '供应商列表管理',
+            keepAlive: true
+        }
+    },
+    {
+        path: '~sgccRecord',
+        name: 'sgccRecord',
+        component: () => import('@/views/supplier/sgccRecord/index.vue'),
+        meta: {
+            title: '供应商游戏币消耗流水记录',
+            keepAlive: true
+        }
+    },
+]

+ 7 - 0
src/storage/index.js

@@ -0,0 +1,7 @@
+import getStorage from '@/plugins/storage'
+import defaultConfig from '@/config/default'
+
+const prefix = defaultConfig.prefix
+
+export const $ls = getStorage('local', prefix)
+export const $ss = getStorage('session', prefix)

+ 31 - 0
src/store/channel.js

@@ -0,0 +1,31 @@
+import {
+  defineStore
+} from "pinia"
+import { querySatCompanyIdAndCode } from '@/apis/basicInformation.js'
+
+export default defineStore('CHANNEL', {
+  state: () => {
+      return {
+        channelList: [],
+      }
+  },
+  actions: {
+      // 获取公司渠道
+      // containOfficialPlatform  是否需要官方平台信息 true=需要  false=不需要  默认false不需要
+      getChannel(containOfficialPlatform = false) {
+         let params = {
+           containOfficialPlatform
+         };
+         querySatCompanyIdAndCode(params).then((res) => {
+           if (res.code === 0) {
+              res.result.forEach((item) => {
+                 let obj = {};
+                 obj.label = item.satCompanyCode;
+                 obj.value = item.satCompanyId;
+                 this.channelList.push(obj)
+              })
+           }
+         })
+      }
+  }
+})

+ 38 - 0
src/store/main.js

@@ -0,0 +1,38 @@
+import {
+    defineStore
+} from "pinia"
+// enabled 开启 pinia-plugin-persist 持久化插件 https://blog.csdn.net/weixin_52051337/article/details/129236587
+export default defineStore('MAIN', {
+    state: () => {
+        return {
+            mainTabsList: [],
+            curActiveTabVal: ''
+        }
+    },
+    actions: {
+        addMenuTab(obj) {
+            let flag = this.mainTabsList.some((val) => val.path === obj.path);
+            if (!flag) {
+              this.mainTabsList.push(obj);
+            }
+            this.curActiveTabVal = obj.path;
+        },
+        delMenuTab(val) {
+            this.mainTabsList = this.mainTabsList.filter((item) => item.path !== val);
+        },
+        remoMenuTab() {
+            this.mainTabsList = []
+        }
+    },
+    persist: {
+        enabled: true,
+        strategies: [{
+            // 自定义key
+            key: 'mainTabs',
+            // 自定义存储方式,默认sessionStorage
+            storage: localStorage,
+            // 指定要持久化的数据,默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
+            paths: ['curActiveTabVal']
+        }]
+    }
+})

+ 28 - 0
src/store/user.js

@@ -0,0 +1,28 @@
+import {
+    defineStore
+} from "pinia"
+// enabled 开启 pinia-plugin-persist 持久化插件 https://blog.csdn.net/weixin_52051337/article/details/129236587
+export default defineStore('USER', {
+    state: () => {
+        return {
+            name: 'user用户'
+        }
+    },
+    actions: {
+        setUser(newOld) {
+            this.name = newOld;
+        }
+    },
+    persist: {
+        enabled: true,
+        strategies: [{
+            // 自定义key
+            key: 'Userinfo',
+            // 自定义存储方式,默认sessionStorage
+            storage: localStorage,
+            // 指定要持久化的数据,默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
+            paths: ['name']
+
+        }]
+    }
+})

+ 130 - 0
src/stores/counter.js

@@ -0,0 +1,130 @@
+import { ref, computed, reactive } from 'vue'
+import { defineStore } from 'pinia'
+import { useRouter, useRoute } from 'vue-router'
+import { $ls, $ss } from "@/storage";
+import { ElMessage } from 'element-plus'
+import { userMenu } from "@/apis/systemInstall.js";
+import { passwordLogin } from "@/apis/user.js";
+import { routerPath } from '@/utils/index.js'
+import routerList from '../router/router';
+
+import axios from 'axios' // axios version exactly 0.19.2
+
+
+export const useCounterStore = defineStore('counter', () => {
+  const $router = useRouter()
+  const $route = useRoute()
+  const data = reactive({
+    basic_token1: 'YWdlbnQ6YWdlbnQ=', // 登陆的头部字段
+    isCollapse: false, // 是否水平折叠收起菜单,
+    menu: $ls.getItem('menu') && $ls.getItem('menu') || [],
+    menulist: $ls.getItem('menu') && $ls.getItem('menu') || [],
+    mainTabsList: [],
+    curActiveTabVal: '',
+    pathUrlList: [],
+    userName: $ss.getItem('info') && $ss.getItem('info').sysUser.username || '',
+    curPath: ''
+  })
+
+  // 登陆
+  function LoginUsername(params) {
+    passwordLogin(params).then((res) => {
+      if (res.code === 1) {
+        // 存储token鉴权
+        console.log(res,'resloggin');
+        $ss.setItem('token', res.data)
+        $ls.setItem('info', params)
+        // let token = $ls.getItem('token')
+        // axios.defaults.headers.common['accessToken'] = token
+        GetMenu()
+        ElMessage.success('登录成功')
+        // 跳转主页
+          setTimeout(() => {
+            $router.push('/home')
+          }, 1000)
+      } else {
+        ElMessage.warning('登录失败')
+      }
+    })
+  }
+  // 登出
+  function Logout() {
+    ElMessageBox.confirm(
+      '是否确定要退出?',
+      '警告',
+      {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    )
+      .then(() => {
+          localStorage.clear()
+          sessionStorage.clear();
+            $router.push({
+              path: "/login"
+            });
+      })
+      .catch(() => {
+      })
+  }
+
+  function addMenuTab(obj) {
+    let flag = data.mainTabsList.some((val) => val.path === obj.path);
+    if (!flag) {
+      data.mainTabsList.push(obj);
+    }
+    data.curActiveTabVal = obj.path;
+  }
+
+  function delMenuTab(val) {
+    data.mainTabsList = data.mainTabsList.filter((item) => item.path !== val);
+  }
+  function remoMenuTab() {
+    data.mainTabsList = [];
+  }
+  // 路由跳转
+  function puhsMentab(obj) {
+    addMenuTab({
+      path: obj.path,
+      name: obj.name
+    });
+    console.log(obj);
+    if (obj.query) {
+      $router.push({
+        path: obj.path,
+        query: obj.query
+      })
+    } else {
+      $router.push({
+        path: obj.path,
+      })
+    }
+    data.curPath = obj.path;
+    $ls.setItem("fullPath", { path: obj.path, name: obj.name });
+  }
+
+
+  // 获取系统菜单
+  function GetMenu() {
+    userMenu().then((res) => {
+      data.menu = res.data;
+      data.menulist = res.data;
+      $ls.setItem('menu', res.data );
+    });
+    console.log(data.menu,'menu');
+    // data.pathUrlList = routerPath(data.menu);
+    routerList.forEach((item) => {
+      if (item.children) {
+        item.children.forEach((obj) => {
+          data.pathUrlList.push(obj.path)
+        })
+      } else {
+        data.pathUrlList.push(item.path)
+      }
+    })
+    data.pathUrlList.push("/password");
+  }
+
+  return { LoginUsername, data, GetMenu, Logout, addMenuTab, delMenuTab, puhsMentab, remoMenuTab }
+})

+ 188 - 0
src/utils/index.js

@@ -0,0 +1,188 @@
+import _ from 'lodash'
+import { openLoading, closeLoading } from '@/utils/loading.js'
+import { getSimple, postSimple, request } from "@/request";
+
+
+/**
+ * 抹除api请求的对象的null和undefined
+ */
+export const wipeNulish = (obj) => {
+  obj = Object(obj) // 不是对象类型就转成对象类型
+  obj = _.cloneDeep(obj) // 不改变原来的值
+  _(obj).keys().forEach((key) => {
+    // 删除null和undefined
+    obj[key] == null && delete obj[key]
+    if (
+      typeof obj[key] === 'object' &&
+      Object.prototype.toString.call(obj) === '[object Object]'
+    ) {
+      // 不对Array或Date或RegExp这样的对象处理
+      // 递归删除
+      wipeNulish(obj[key])
+    }
+  })
+  return obj
+}
+
+/**
+ * 格式化globEager返回的文件名,'./xx/yy.xx' -> 'yy'
+ * 再把导入的模块的默认导出(export default)给对应的键值对
+ */
+export const filterGlobEagerFilename = (obj) => {
+  return _(obj)
+    .mapKeys((_, i) => {
+      const arr = i.split('/')
+      return arr[arr.length - 1]
+    })
+    .mapKeys((_, i) => i.match(/(.+)\.\w+$/)[1])
+    .mapValues((i) => i.default)
+    .value()
+}
+
+// 获取assets静态资源
+export const getAssetsFile = (url) => {
+  return new URL(`../assets/img/${url}`,
+    import.meta.url).href
+}
+
+// 精确计算加法
+export function acc(arg1, arg2) {
+  let r1 = deal(arg1)
+  let r2 = deal(arg2)
+  let m = Math.pow(10, Math.max(r1, r2))
+  let res1 = numMulti(arg1, m)
+  let res2 = numMulti(arg2, m)
+  return (res1 + res2) / m
+}
+
+export function deal(arg) {
+  var t = 0
+  try {
+    t = arg.toString().split(".")[1].length
+  } catch (e) { }
+  return t
+}
+
+// 乘法精度计算
+export function numMulti(num1, num2) {
+  var baseNum = 0
+  try {
+    baseNum += num1.toString().split(".")[1].length
+  } catch (e) { }
+  try {
+    baseNum += num2.toString().split(".")[1].length
+  } catch (e) { }
+  return Number(num1.toString().replace(".", "")) * Number(num2.toString().replace(".", "")) / Math.pow(10, baseNum)
+}
+
+export const routerPath = (menuList) => {
+  let arrList = [];
+  function childrenServser(menuList) {
+    menuList && menuList.forEach((item) => {
+      if (item.children) return childrenServser(item.children)
+      else {
+        arrList.push(item.path)
+      }
+    })
+  }
+  childrenServser(menuList)
+  return arrList
+}
+
+// 导出
+export const downloadFn = (allParams) => {
+  const {
+    url,
+    params,
+  } = allParams;
+  let opt = {
+    responseType: 'blob',
+  }
+  openLoading()
+  return new Promise((resolve, reject) => {
+    getSimple(
+      url,
+      params,
+      opt
+    ).then(res => {
+      let fileName = decodeURI(res.headers["content-disposition"].split(";")[1].split("filename=")[1])
+      const link = document.createElement('a');
+      let blob = new Blob([res.data], { type: 'application/vnd.ms-excel' });
+      link.style.display = 'none';
+      link.href = URL.createObjectURL(blob);
+      link.setAttribute('download', fileName);
+      link.download = fileName || `下载文件.xls`;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link)
+      closeLoading()
+      ElMessage({
+        message: '导出成功',
+        type: 'success',
+        duration: 3000,
+      });
+      resolve(res);
+    }).catch((errorMsg) => {
+      closeLoading()
+      ElMessage({
+        message: errorMsg,
+        type: 'error',
+        duration: 3000,
+      });
+      reject(errorMsg);
+    })
+  })
+}
+export const accDivCoupon = (arg1, arg2) => {
+  var t1 = 0, t2 = 0, r1, r2;
+  try { t1 = arg1.toString().split(".")[1].length } catch (e) { }
+  try { t2 = arg2.toString().split(".")[1].length } catch (e) { }
+  r1 = Number(arg1.toString().replace(".", ""));
+  r2 = Number(arg2.toString().replace(".", ""));
+  return (r1 / r2) * Math.pow(10, t2 - t1);
+}
+/**
+ * 保留两位小数点后两位
+ * <!--在双括号中这样使用即可-->
+<div class="col">{{item.dataToday | numFilter}}</div>
+ */
+export const numFilter = (value) => {
+  let realVal = "";
+  if (!isNaN(value) && value !== "") {
+    // 截取当前数据到小数点后两位,改变toFixed的值即可截取你想要的数值
+    realVal = parseFloat(value).toFixed(2);
+  } else {
+    realVal = "--";
+  }
+  return realVal;
+}
+
+// 重置 key
+export const reset = (obj) => {
+  for (var key in obj) {
+    delete obj[key];
+  }
+}
+
+/**
+ * @description 函数节流
+ * @param {Function} func 函数
+ * @param {Number} wait 延迟执行毫秒数,默认200
+ */
+export const VueThrottle = (func, wait = 200) => {
+  let timeout;
+  return function () {
+    let that = this;
+    let args = arguments;
+    if (!timeout) {
+      timeout = setTimeout(() => {
+        timeout = null;
+        func.apply(that, args)
+        ElMessage.closeAll();// 关闭所有弹窗
+      }, wait)
+    }
+    else {
+      ElMessage.warning("操作太频繁了哦,稍后再试");
+    }
+  }
+}

+ 16 - 0
src/utils/loading.js

@@ -0,0 +1,16 @@
+let loading;
+function openLoading() {
+    loading = ElLoading.service({
+        lock: true,
+        text: '加载中...',
+        background: 'rgba(0, 0, 0, 0.7)',
+
+    })
+}
+function closeLoading() {
+    loading.close();
+}
+export {
+    openLoading,
+    closeLoading
+}

Fichier diff supprimé car celui-ci est trop grand
+ 1098 - 0
src/utils/public.js


+ 244 - 0
src/views/login/index.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="login-container">
+    <div class="login-weaper">
+      <div class="login-left">
+        <img src="@/assets/img/login-page.png" alt="">
+      </div>
+      <div class="login-right">
+        <el-tabs v-model="data.activeName" class="demo-tabs">
+          <el-tab-pane label="登录" name="password"></el-tab-pane>
+          <!-- <el-tab-pane label="验证码登录" name="code"></el-tab-pane> -->
+        </el-tabs>
+        <el-form ref="loginForm" :model="data.loginForm" :rules="data.loginRules" :hide-required-asterisk="true"
+          class="login-ruleForm" :size="formSize">
+          <el-form-item label="账号" prop="phone">
+            <el-input v-model="data.loginForm.phone" clearable maxlength="11" placeholder="请输入账号"
+              class="login-input"></el-input>
+          </el-form-item>
+          <el-form-item label="密码" prop="password" v-if="data.activeName == 'password'">
+            <el-input v-model="data.loginForm.password" @keyup.enter.native="login(loginForm)" clearable show-password
+              type="password" placeholder="请输入密码" class="login-input"></el-input>
+          </el-form-item>
+          <el-form-item label="验证码" prop="code" v-if="data.activeName == 'code'">
+            <el-input v-model="data.loginForm.code" clearable placeholder="请输入验证码" class="login-input">
+              <template #append>
+                <el-button @click="setCode" :disabled="data.changeBtn" class="btn-code">{{ data.btnTitle }}</el-button>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="login(loginForm)" class="login-btn"><span>登录</span></el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref } from "vue";
+import { useRouter, useRoute } from 'vue-router'
+import { useCounterStore } from '@/stores/counter.js'
+import { openLoading, closeLoading } from '@/utils/loading.js'
+const $route = useRoute()
+const $router = useRouter()
+import { $ls } from "@/storage";
+import { getAssetsFile } from '@/utils'
+import { passwordLogin } from "@/apis/user.js"
+import { userMenu } from "@/apis/systemInstall.js"
+import { ElMessage } from 'element-plus'
+
+const store = useCounterStore();
+const data = reactive({
+  socialForm: {},
+  loginForm: {
+    phone: '',
+    password: '',
+    code: ''
+  },
+  loginRules: {
+    phone: [
+      { required: true, message: "请输入账号", trigger: "blur" }
+    ],
+    password: [
+      { required: true, message: "请输入密码", trigger: "blur" },
+      { min: 6, max: 15, message: "长度在6 ~ 15个字节", trigger: "blur" }
+    ],
+    code: [
+      { required: true, message: "请输入验证码", trigger: 'blur' }
+    ]
+  },
+  activeName: 'password',
+  btnTitle: '发送验证码',
+  send: true,
+  time: null,
+  count: '',
+  changeBtn: false,
+});
+const loginForm = ref();
+const formSize = ref('default')
+const logo = getAssetsFile('logo.png');
+$ls.setItem('isToken', true)
+
+const setCode = (() => {
+
+})
+
+const getCode = (() => {
+
+})
+
+const login = async (formEl) => {
+  formEl.validate((valid) => {
+    if(valid){
+      let params = {
+        password: data.loginForm.password,
+        username: data.loginForm.phone
+      }
+      store.LoginUsername(params);
+    }
+  })
+  
+  // const res = await passwordLogin(params)
+  // if (res.code === 1) {
+  //   getMenu()
+  //   $ls.setItem('token', res.data)
+  //   $router.push('/home')
+  // } else {
+  //   ElMessage.error(res.message)
+  // }
+}
+
+// const getMenu = async () =>{
+//   const res = await userMenu(data.loginForm.phone)
+//   if(res.code === 1){
+//     $ls.setItem('menu',res.data)
+//   }
+  
+// }
+</script>
+
+<style lang="scss" scoped>
+.login-container {
+  height: 100vh;
+  background: url('@/assets/img/login.png') no-repeat center;
+  background-size: 100% 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  /* position: relative; */
+
+  .login-weaper {
+    width: 718px;
+    height: 399px;
+    background: #FFFFFF;
+    box-shadow: 0px 0px 11px 0px rgba(117, 154, 193, 0.24);
+    border-radius: 5px;
+    display: flex;
+    align-items: center;
+
+    .login-left {
+      width: 399px;
+      height: 100%;
+
+      img {
+        width: 100%;
+      }
+    }
+
+    .login-right {
+      flex: 1;
+      align-self: start;
+      margin-top: 30px;
+
+      :deep(.el-tabs) {
+        .el-tabs__nav {
+          width: 100%;
+          display: flex;
+          justify-content: center;
+        }
+      }
+
+      :deep(.el-tabs__nav-wrap::after) {
+        height: 0;
+      }
+
+      :deep(.el-tabs__content) {
+        display: none;
+      }
+
+      :deep(.el-tabs__item) {
+        font-weight: 500;
+        font-size: 16px;
+        color: #707476;
+      }
+
+      :deep(.el-tabs__active-bar) {
+        height: 3px;
+      }
+
+      :deep(.el-tabs__item.is-active) {
+        color: #17163D;
+        font-weight: bold;
+      }
+
+      h3 {
+        font-size: 20px;
+        font-weight: 600;
+        letter-spacing: 1px;
+        color: #333333;
+      }
+
+      .login-ruleForm {
+        /* margin-left: 39px; */
+        margin-top: 35px;
+        padding: 0 30px;
+        /* width: 76%; */
+
+        :deep(.el-form-item) {
+          display: block;
+          margin-bottom: 19px;
+          .el-form-item__label {
+            font-weight: 600;
+            color: #333333;
+            letter-spacing: 1px;
+          }
+          .el-form-item__content {
+            .el-input {
+              height: 37px;
+            }
+          }
+          .login-btn {
+            width: 100%;
+            height: 37px;
+            margin-top: 37px;
+            font-size: 14px;
+          }
+          .btn-code {
+            color: #0BA0FF;
+            font-size: 12px;
+            letter-spacing: 1px;
+            width: 100px;
+          }
+        }
+      }
+    }
+  }
+
+  @media screen and (max-width: 700px) {
+    .login-weaper {
+      box-sizing: border-box;
+      width: 319px;
+      background: #fff;
+
+      .login-left {
+        display: none;
+      }
+
+      .login-right {
+        width: 100%;
+      }
+    }
+  }
+}
+</style>

+ 271 - 0
src/views/supplier/sgccRecord/index.vue

@@ -0,0 +1,271 @@
+<script setup>
+import { reactive,ref,onMounted } from "vue";
+import { VueThrottle,downloadFn } from '@/utils/index.js'
+import { openLoading, closeLoading } from '@/utils/loading.js'
+import { ElMessage } from 'element-plus'
+import { selectSupplierGameCurrencyPage,selectSupplierSourceTypeList } from '@/apis/supplier.js'
+import { setTime } from '@/utils/public.js'
+
+
+const disabledDate = (time) => {
+    return time.getTime() > new Date().getTime()
+}
+const data = reactive({
+    searchForm:{
+        sourceTypes:[],
+        createTime:[]
+    },
+    tableData: [],
+    pagingData: {
+        page: 1,
+        pageSize: 20,
+        total: 0, 
+    },
+    typeOption:[],
+    sourceOptions:[
+        {
+            type: 2,
+            name: '全民星星乐',
+        },
+        {
+            type: 3,
+            name: '乌龟叠叠乐',
+        },
+        {
+            type: 4,
+            name: '掌上投篮',
+        },
+        {
+            type: 5,
+            name: '疯狂套牛',
+        },
+        {
+            type: 20,
+            name: '娱乐中心',
+        },
+        {
+            type: 50,
+            name: '海上传奇'
+        },
+        {
+            type: 70,
+            name: '九宫格'
+        },
+        {
+            type: 91,
+            name: '吉祥兔刮刮卡'
+        },
+        {
+            type: 93,
+            name: '补发'
+        },
+        {
+            type: 94,
+            name: '兑换商品'
+        },
+        {
+            type: 95,
+            name: '小说'
+        },
+        {
+            type: 96,
+            name: '幸运数字刮刮卡'
+        },
+        {
+            type: 97,
+            name: '波波街机'
+        },
+		{
+			type: 31,
+			name: '过期',
+      	},
+		{
+			type:102,
+			name:'够玩短剧'
+		},
+		{
+			type:103,
+			name:'套图购买'
+		},
+	  	{
+			type:104,
+			name:'游戏币充值'
+	  	},
+		{
+			type:105,
+			name:'星火捕鱼'
+		},
+		{
+			type:106,
+			name:'脑洞大逃亡'
+		},
+		{
+			type: 110,
+			name:'猛鬼宿舍'
+		},
+		{
+			type: 111,
+			name:'泡米看剧'
+		},
+		{
+			type: 112,
+			name:'泡米小说'
+		},
+		{
+			type: 113,
+			name: '美女视频'
+		}
+    ]
+
+})
+onMounted(() => {
+    getSearchData()
+    getGameCurrency()
+})
+const handleSizeChange = (page) => {
+    console.log(page,'zise');
+    // data.pagingData.page = 1;
+    // data.pagingData.pageSize = size;
+    getSearchData();
+}
+const handleCurrentChange = (page) => {
+    getSearchData(page)
+}
+const getSearchData = (page = 1, pageSize = data.pagingData.pageSize) => {
+    openLoading()
+    let params = {
+        sourceTypes: data.searchForm.sourceTypes?.join(',') || undefined
+    }
+    if (data.searchForm.createTime && data.searchForm.createTime.length > 0) {
+        params['startTime'] = data.searchForm.createTime[0] + ' 00:00:00';
+        params['endTime'] = data.searchForm.createTime[1] + ' 23:59:59';
+    }
+    params['pageNum'] = page;
+    params['pageSize'] = pageSize;
+    selectSupplierGameCurrencyPage(params).then((res) => {
+        if (res.code === 1) {
+            let tableList = res.data.result
+            // tableList = tableList.map((item, index) => {
+            //    item.createTime = item.createTime ? setTime(item.createTime * 1000) : '--';
+            //     return item;
+            // }); 
+            // tableList = tableList.map((item, index) => {
+            //    item.updateTime = item.updateTime ? setTime(item.updateTime * 1000) : '--';
+            //     return item;
+            // }); 
+            data.tableData = tableList;
+            data.pagingData.total = res.data.total;
+            data.pagingData.pageSize = res.data.pageSize;
+            // data.pagingData.page = page;
+        }else{
+            ElMessage.error(res.message)
+        }
+        closeLoading()
+    }).catch(err => {
+        console.log(err);
+        closeLoading()
+    }); 
+}
+
+//获取枚举
+const getGameCurrency = async() => {
+    const res = await selectSupplierSourceTypeList()
+    if(res.code === 1){
+        data.sourceOptions = res.data
+    }else{
+        ElMessage.error(res.message)
+    }
+}
+
+const refresh = () => {
+    data.searchForm = {
+        sourceTypes:[],
+        createTime:[]
+    }
+    getSearchData()
+}
+const searchFn = () => {
+    getSearchData()
+}
+
+const exportForm = VueThrottle(() => {
+    console.log(data.pagingData.total,'data.pagingData.total');
+    if (data.pagingData.total > 10000 || data.pagingData.total <= 0) {
+        setTimeout(() => {
+            ElMessage.warning("数据超过10000条或无数据,请重新选择筛选条件");
+        })
+    } else {
+        let params = {
+            sourceTypes: data.searchForm.sourceTypes?.join(',') || undefined
+        }
+        if (data.searchForm.createTime && data.searchForm.createTime.length > 0) {
+            params['startTime'] = data.searchForm.createTime[0] + ' 00:00:00';
+            params['endTime'] = data.searchForm.createTime[1] + ' 23:59:59';
+        }
+        // params['pageNum'] = data.pagingData.page;
+        // params['pageSize'] = data.pagingData.pageSize;
+        downloadFn({
+            url: '/increase/supplier/manage/exportSupplierGameCurrencyRecords', 
+            params,
+        })
+    }
+},1000)
+</script>
+
+<template>
+<!-- 供应商游戏币消耗流水记录 -->
+    <div class="sgcc-record">
+        <div class="serch-box" style="height:40px">
+            <div class="form-left">
+            <el-form :inline="true" :model="data.searchForm">
+                <el-form-item label="来源">
+                    <el-select v-model="data.searchForm.sourceTypes" multiple clearable placeholder="请选择" collapse-tags>
+                        <el-option v-for="item in data.sourceOptions" :key="item.type" :label="item.name"
+                            :value="item.type">
+                        </el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="创建时间">
+                    <el-date-picker v-model="data.searchForm.createTime" :disabled-date="disabledDate"
+                        type="daterange" value-format="YYYY-MM-DD" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" clearable>
+                    </el-date-picker>
+                </el-form-item>
+            </el-form>
+            </div>
+            <div class="form-right">
+                <el-button type="primary" @click="searchFn" class="iconfont icon-chaxun">搜索</el-button>
+                <el-button type="primary" @click="refresh" class="iconfont icon-xinzeng" plain>重置</el-button>
+            </div>
+        </div>
+        <div class="table-wrapper">
+            <div>
+                <el-button type="primary" @click="exportForm" class="iconfont icon-xinzeng" plain>导出</el-button>
+            </div>
+            <el-table :data="data.tableData" v-adaptive="{ fixedHeader: true, bottomOffset: 85 }"
+            :header-cell-style="{ background: '#FAFAFA', color: '#152129', fontSize: '14px' }" :cell-style="{ height: '50px' }"
+            style="width: 100%; margin-top:11px;">
+                <el-table-column type="index" width="60" align="center" label="序号"></el-table-column>
+                <!-- <el-table-column prop="username" label="用户昵称" align="center" min-width="210"></el-table-column> -->
+                <el-table-column prop="phone" width="auto" align="center" min-width="150" label="手机号"></el-table-column>
+                <el-table-column prop="changeSourceTypeName" label="来源" align="center" min-width="160"></el-table-column>
+                <el-table-column prop="jine" label="变化游戏币" align="center" min-width="160"></el-table-column>
+                <el-table-column prop="openId" label="openId" align="center" min-width="160"></el-table-column>
+                <el-table-column prop="createTime" label="创建时间" align="center" min-width="160">
+                    <template v-slot="scope">
+                        <p>{{ scope.row.createTime ? scope.row.createTime : '--' }}</p>
+                    </template>
+                </el-table-column>
+                <!-- <el-table-column prop="operate" label="操作" min-width="120" fixed="right" align="center">
+                    <template v-slot="scope">
+                        <el-button size="small" type="primary" link @click="check(scope.row)">查看</el-button>
+                    </template>
+                </el-table-column> -->
+            </el-table>
+            <el-pagination class="pagination-style" @size-change="handleSizeChange" @current-change="handleCurrentChange"
+            v-model:current-page="data.pagingData.page" :page-sizes="[10, 20, 50]" v-model:page-size="data.pagingData.pageSize"
+            layout="total, sizes, prev, pager, next" :total="data.pagingData.total">
+            </el-pagination>
+        </div>
+    </div>
+</template>

+ 414 - 0
src/views/supplier/suplierListManage/index.vue

@@ -0,0 +1,414 @@
+<script setup>
+import { reactive,ref,onMounted } from "vue";
+import { VueThrottle,downloadFn } from '@/utils/index.js'
+import { openLoading, closeLoading } from '@/utils/loading.js'
+import { ElMessage,ElMessageBox } from 'element-plus'
+import { providerPage,modifyProvider,getBizNoInfo,insertProviderUser,deleteProvider,changeStatus,getThirdAppInfoList } from '@/apis/supplier.js'
+import { setTime } from '@/utils/public.js'
+
+const disabledDate = (time) => {
+  return time.getTime() > new Date().getTime()
+}
+const validateConfirm = (rule, value, callback) => {
+  if (value !== data.useruleForm.password) {
+    callback(new Error('密码不一致'))
+  }
+  callback()
+}
+const data = reactive({
+    searchForm:{
+        name:'',
+        status:'',
+        createTime:[]
+    },
+    statusOptions:[
+        {
+            value:1,
+            label:'正常'
+        },
+        {
+            value:0,
+            label:'失效'
+        }
+    ],
+    tableData: [],
+    pagingData: {
+        current: 1,
+        size: 20,
+        total: 0,
+    },
+    couponBizList:[],//兑换券编号
+    currencyOpions:[],//游戏币编号
+    title:'新增',
+    ruleForm:{
+        currencyBizList: [],
+        name: "",
+        status: undefined,
+        appId:''
+    },
+    rules:{
+        name:[
+            { required: true, message: "请输入", trigger: "blur" },
+        ],
+        status:[
+            { required: true, message: "请选择", trigger: "blur" },
+        ],
+        appId:[
+            { required: true, message: "请选择", trigger: "blur" },
+        ],
+        currencyBizList:[
+            { required: true, message: "请选择", trigger: "blur" },
+        ]
+    },
+    useruleForm:{
+        username:'',//用户名
+        phone:'',//手机号
+        providerId:'',//供应商id
+        password:'',//密码
+        confirmPassword:'',//确认密码
+        name:'',//名称
+    },
+    userules:{
+        username:[
+            { required: true, message: "用户名不能为空", trigger: "blur" },
+        ],
+        phone:[
+            { required: true, message: "手机号不能为空", trigger: "blur" },
+        ],
+        password:[
+            { required: true, message: "密码不能为空", trigger: "blur" },
+        ],
+        confirmPassword:[
+            { required: true, message: "请再次输入密码", trigger: "blur" },
+            { validator: validateConfirm, trigger: 'blur' }
+        ],
+        
+    },
+    appInfoList:[]
+})
+
+
+const getSearchData = (page = 1, size = data.pagingData.size) => {
+    openLoading()
+    let params = {
+        name: data.searchForm.name || undefined,
+        tel: data.searchForm.tel || undefined,
+        status: data.searchForm.status || undefined,
+    }
+    if (data.searchForm.createTime && data.searchForm.createTime.length > 0) {
+        params['startTime'] = data.searchForm.createTime[0] + ' 00:00:00';
+        params['endTime'] = data.searchForm.createTime[1] + ' 23:59:59';
+    }
+    params['current'] = page;
+    params['size'] = size;
+    providerPage(params).then((res) => {
+        if (res.code === 1) {
+            let tableList = res.data.records;
+            tableList.map((i) => {
+                let currencyName = []
+                let currencyNo = []
+                let currencyType = []
+                let currencyTypeName = []
+                i.currencyBizList.map(d => {
+                    currencyName.push(d.bizName)
+                    currencyNo.push(d.bizNo)
+                    d.bizType === 1?d.bizTypeName ='游戏币':d.bizTypeName ='兑换券'
+                    currencyType.push(d.bizType)
+                    currencyTypeName.push(d.bizTypeName)
+                })
+                i.currencyName = currencyName.join(',')
+                i.currencyNo = currencyNo.join(',')
+                i.currencyType = currencyType.join(',')
+                i.currencyTypeName = currencyTypeName.join(',')
+            })
+            
+            data.tableData = tableList;
+            data.pagingData.total = res.data.total;
+        }
+        closeLoading()
+    }).catch(err => {
+        console.log(err);
+        closeLoading()
+    });
+}
+
+const getBizNoInfoList = async () => {
+    const res = await getBizNoInfo()
+    if(res.code === 1){
+        data.couponBizList = res.data.couponBizList
+        data.currencyOpions = res.data.currencyBizList
+    }
+}
+const getThirdAppInfo = async () => { 
+    const res = await getThirdAppInfoList()
+    if(res.code === 1){
+        data.appInfoList = res.data
+    }
+}
+const searchFn = () => {
+    getSearchData()
+    
+}
+const refresh = () => {
+    data.searchForm = {
+        name:'',
+        status:'',
+        createTime:[]
+    }
+    getSearchData()
+}
+onMounted(()=>{
+    getBizNoInfoList()
+    getThirdAppInfo()
+    getSearchData()
+})
+const handleSizeChange = (size) => {
+    // console.log(page,'zise');
+    // data.pagingData.current = 1;
+    // data.pagingData.size = size;
+    getSearchData();
+}
+const handleCurrentChange = (page) => {
+    getSearchData(page)
+}
+
+const showDialog = ref(false)
+const ruleFormRef = ref(null)
+const disabled = ref(false)
+const add = () => {
+    data.ruleForm = {
+        currencyBizList: [],
+        name: "",
+        status: undefined,
+        appId:''
+    }
+    data.title = '新增'
+    disabled.value = false
+    showDialog.value = true
+}
+const edit = (row) => {
+    let arr = JSON.parse(row.currencyBizList[0].thirdAppId)
+    data.ruleForm = {
+        id:row.id,
+        currencyBizList: row.currencyBizList,
+        name: row.name,
+        status: row.status,
+        appId:row.currencyBizList[0].thirdAppId
+    }
+    disabled.value = true
+    data.title = '编辑'
+    showDialog.value = true
+}
+const submit = (formEl) => {
+    formEl.validate(valid => {
+        if(valid){
+            let params = {
+                ...data.ruleForm
+            }
+            modifyProvider(params).then((res) => {
+                if(res.code === 1){
+                    ElMessage.success(res.message)
+                    getSearchData()
+                    closeErr()
+                }else{
+                    ElMessage.error(res.message)
+                }
+                
+            })
+        }
+    })
+}
+const closeErr = () => {
+    ruleFormRef.value.resetFields();
+    showDialog.value = false;
+}
+const adduserDialog = ref(false)
+const addFormRef = ref(null)
+const adduser = (row) => {
+    data.useruleForm = {
+        username:'',//用户名
+        phone:'',//手机号
+        providerId:row.id,//供应商id
+        password:'',//密码
+        confirmPassword:'',//确认密码
+        name:'',//名称
+    }
+    adduserDialog.value = true
+}
+const confirm = (formEl) => {
+    formEl.validate(valid => {
+        if(valid){
+            let params = {
+                ...data.useruleForm
+            }
+            insertProviderUser(params).then(res => {
+                if(res.code === 1){
+                    ElMessage.success(res.message)
+                    closeuseErr()
+                    getSearchData()
+                }else{
+
+                    ElMessage.error(res.message)
+                }
+            })
+        }
+    })
+}
+const closeuseErr = () => {
+    addFormRef.value.resetFields();
+    adduserDialog.value = false;
+}
+
+const del = (row) => {
+    ElMessageBox.confirm('确认是否删除','提示',{confirmButtonText:'确认',cancelButtonText:'取消',type:'warning'}).then(() => {
+        deleteProvider(row.id).then(res => {
+            if(res.code === 1){
+               ElMessage.success(res.message)
+                getSearchData() 
+            }else{
+                ElMessage.error(res.message)
+            }
+        }).catch(() => {
+            
+        })
+    })
+}
+const check = (row) => {
+    ElMessageBox.confirm(`确认是否${row.status === 0?'生效':'失效'}`,'提示',{confirmButtonText:'确认',cancelButtonText:'取消',type:'warning'}).then(() => {
+        let params = {
+            providerId:row.id,
+            status:row.status === 0? 1 : 0
+        }
+        changeStatus(params).then(res => {
+            if(res.code === 1){
+                ElMessage.success(res.message)
+                getSearchData()
+            }else{
+                ElMessage.error(res.message)
+            }
+        })
+    })
+}
+</script>
+<template>
+<!-- 供应商列表管理 -->
+    <div class="suplier-list">
+        <div class="serch-box" style="height:40px">
+            <div class="form-left">
+            <el-form :inline="true" :model="data.searchForm">
+                <el-form-item label="供应商名称">
+                    <el-input v-model="data.searchForm.name" placeholder="请输入" clearable></el-input>
+                </el-form-item>
+                <el-form-item label="状态">
+                    <el-select v-model="data.searchForm.status" clearable placeholder="请选择">
+                        <el-option v-for="item in data.statusOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="创建时间">
+                    <el-date-picker v-model="data.searchForm.createTime" :disabled-date="disabledDate"
+                        type="daterange" value-format="YYYY-MM-DD" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" clearable>
+                    </el-date-picker>
+                </el-form-item>
+            </el-form>
+            </div>
+            <div class="form-right">
+                <el-button type="primary" @click="searchFn" class="iconfont icon-chaxun">搜索</el-button>
+                <el-button type="primary" @click="refresh" class="iconfont icon-xinzeng" plain>重置</el-button>
+            </div>
+        </div>
+        <div class="table-wrapper">
+            <div>
+                <el-button type="primary" @click="add" class="iconfont icon-xinzeng">新增</el-button>
+            </div>
+            <el-table :data="data.tableData" v-adaptive="{ fixedHeader: true, bottomOffset: 85 }"
+            :header-cell-style="{ background: '#FAFAFA', color: '#152129', fontSize: '14px' }" :cell-style="{ height: '50px' }"
+            style="width: 100%; margin-top:11px;">
+                <el-table-column type="index" width="60" align="center" label="序号"></el-table-column>
+                <!-- <el-table-column prop="username" label="用户昵称" align="center" min-width="210"></el-table-column> -->
+                <el-table-column prop="name" width="auto" align="center" min-width="150" label="供应商名称"></el-table-column>
+                <el-table-column prop="status" label="状态" align="center" min-width="160">
+                    <template v-slot="scope">
+                        <el-tag type="success" v-if="scope.row.status === 1">正常</el-tag>
+                        <el-tag type="danger" v-if="scope.row.status === 0">失效</el-tag>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="currencyName" width="auto" align="center" label="游戏业务名称"></el-table-column>
+                <el-table-column prop="currencyNo" width="auto" align="center" label="游戏业务编号"></el-table-column>
+                <!-- <el-table-column prop="currencyTypeName" width="auto" align="center" label="游戏币业务类型"></el-table-column> -->
+                <el-table-column prop="createTime" label="创建时间" align="center" min-width="160">
+                    <template v-slot="scope">
+                        <p>{{ scope.row.createTime ? scope.row.createTime : '--' }}</p>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="operate" label="操作" min-width="150" fixed="right" align="center">
+                    <template v-slot="scope">
+                        <el-button size="small" type="primary" link @click="edit(scope.row)">编辑</el-button>
+                        <el-button size="small" type="danger" link @click="del(scope.row)">删除</el-button>
+                        <el-button size="small" :type="scope.row.status === 0 ?'success':'warning'" link @click="check(scope.row)">{{scope.row.status === 0 ?'生效':'失效'}}</el-button>
+                        <el-button size="small" type="info" link @click="adduser(scope.row)">新增用户</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <el-pagination class="pagination-style" @size-change="handleSizeChange" @current-change="handleCurrentChange"
+            v-model:current-page="data.pagingData.current" :page-sizes="[10, 20, 50]" v-model:page-size="data.pagingData.size"
+            layout="total, sizes, prev, pager, next" :total="data.pagingData.total">
+            </el-pagination>
+        </div>
+        <el-dialog :title="data.title" v-model="showDialog" width="600px" :before-close="closeErr">
+            <el-form ref="ruleFormRef" label-width="120px" :model="data.ruleForm" :rules="data.rules">
+                <el-form-item label="供应商名称" prop="name">
+                    <el-input v-model.trim="data.ruleForm.name" maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+                <el-form-item label="状态" prop="status">
+                    <el-select v-model="data.ruleForm.status" placeholder="请选择">
+                        <el-option v-for="item in data.statusOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                    </el-select>
+                </el-form-item>                
+                <el-form-item label="第三方app" prop="appId">
+                    <el-select v-model="data.ruleForm.appId" placeholder="请选择" :disabled="disabled">
+                        <el-option v-for="item in data.appInfoList" :key="item.appId" :label="item.appName" :value="item.appId"></el-option>
+                    </el-select>
+                </el-form-item>                
+                <el-form-item label="游戏业务名称" prop="currencyBizList">
+                    <el-select v-model="data.ruleForm.currencyBizList" placeholder="请选择" multiple clearable value-key="bizNo">
+                        <el-option v-for="item in data.currencyOpions" :key="item.value" :label="item.bizName" :value="item"></el-option>
+                    </el-select>
+                </el-form-item>
+            </el-form>
+            <div class="btns" slot='footer' style="text-align: center;">
+                <el-button @click='showDialog = false'>取消</el-button>
+                <el-button type='primary' @click="submit(ruleFormRef)">保存</el-button>
+            </div>
+        </el-dialog>
+        <el-dialog title="新增用户" v-model="adduserDialog" width="600px" :before-close="closeuseErr">
+            <el-form ref="addFormRef" label-width="120px" :model="data.useruleForm" :rules="data.userules">
+                <el-form-item label="用户名" prop="username">
+                    <el-input v-model.trim="data.useruleForm.username" maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+                <el-form-item label="手机号" prop="phone">
+                    <el-input v-model.trim="data.useruleForm.phone" maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+                <el-form-item label="名称" prop="name">
+                    <el-input v-model.trim="data.useruleForm.name" maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+                <el-form-item label="密码" prop="password">
+                    <el-input v-model.trim="data.useruleForm.password" show-password maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+                <el-form-item label="确认密码" prop="confirmPassword">
+                    <el-input v-model.trim="data.useruleForm.confirmPassword" show-password maxlength="30" placeholder="请输入"></el-input>
+                </el-form-item>
+            </el-form>
+            <div class="btns" slot='footer' style="text-align: center;">
+                <el-button @click='adduserDialog = false'>取消</el-button>
+                <el-button type='primary' @click="confirm(addFormRef)">保存</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+    
+</style>

+ 357 - 0
src/views/supplier/supplierDashboard/index.vue

@@ -0,0 +1,357 @@
+<script setup>
+import { setTime } from '@/utils/public.js';
+// import { pieOption, lineOption, barOption } from './chartOptions.js';
+import * as echarts from 'echarts';
+import { onBeforeRouteLeave } from 'vue-router'
+import { reactive,ref,onMounted,onBeforeUnmount,onUnmounted,nextTick,watch } from "vue";
+import { supplierDashboard } from '@/apis/supplier.js'
+import { downloadFn, VueThrottle } from '@/utils/index.js'
+
+const data = reactive({
+    itemsLoading:{
+      loadingCurrency:0
+    },
+    dhqtjData: {
+      yesterday:0,
+      today:0,
+      total:0,
+      chartData:[]
+    },
+})
+let yhtjLineChart = null
+
+let lineOption = {
+  grid: {
+    left: '5.5%',
+    right: 30,
+    bottom: 30,
+    top: 50,
+  },
+  title: {
+    text: '',
+    left: '3%'
+  },
+
+  tooltip: {
+    show: true,
+    trigger: 'axis'
+  },
+  xAxis: {
+    type: 'category',
+    data: [],
+    axisTick: {
+      show: false,
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#999',
+      }
+    },
+  },
+  yAxis: {
+    type: 'value',
+    name: '',
+    axisLine: {
+      lineStyle: {
+        color: '#999',
+        nameGap:300
+        // width: 0,
+      }
+    },
+    nameTextStyle:{
+       padding:[0,0,20,0]
+    },
+
+    axisTick: {
+      length: 0,
+      lineStyle: {
+        color: '#999',
+        // width: 10,
+      }
+    }
+  },
+  series: [{
+    label: {
+      show: true,
+    },
+
+    symbolSize: 8,
+
+    itemStyle: {
+      color: '#3BAFFF',
+      borderWidth: 2,
+      borderColor: '#3BAFFF',
+      
+    },
+    // lineStyle: {
+    //   width: 2,
+    // },
+    name: '--',
+    data: [],
+    type: 'line',
+    smooth: true,
+    areaStyle: {
+      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+        offset: 0,
+        color: '#63AFE3 '
+      }, {
+        offset: 1,
+        color: '#fff'
+      }])
+    },
+  }]
+};
+
+const renderUserLineChart = (dateArr, dataArr) => {
+    yhtjLineChart = echarts.init(document.getElementById('userLineChart'));
+    lineOption.series[0].data = dataArr
+    lineOption.series[0].name = '消耗游戏币';
+    lineOption.xAxis.data = dateArr
+    lineOption.yAxis.name = '最近一个月游戏币消耗';
+    yhtjLineChart.setOption(lineOption);
+    yhtjLineChart.resize()
+}
+
+const getSupplierDashboard = () => {
+  data.itemsLoading.loadingCurrency = true
+  supplierDashboard().then((res) => {
+    data.itemsLoading.loadingCurrency = false
+    if(res.data){
+      let datas = res.data
+      data.dhqtjData.yesterday = datas.yesterdayConsume
+      data.dhqtjData.today = datas.todayConsume
+      data.dhqtjData.total = datas.totalConsume
+      let dateArr = [];
+      let dataArr = [];
+      datas.lastMonthConsume.map((item) => {
+        dateArr.push(item.createDate.substr(-5));
+        dataArr.push(item.jine);
+      })
+      data.dhqtjData.chartData = datas.lastMonthConsume
+      nextTick(() => {
+        renderUserLineChart(dateArr, dataArr)
+      })
+    }
+  })
+}
+onMounted(() => {
+  getSupplierDashboard()
+})
+</script>
+
+<template>
+<!-- 供应商仪表盘 -->
+    <div class="supplier">
+        <div class="vasDashboard-item-box" >
+        <div class="chartTop">
+          <div class="chartDiv">
+            <p class="chartTitle">消耗游戏币统计</p>
+            <!-- <el-button @click="exporChartUser()" class="chartBtn" type="primary" plain >导出</el-button> -->
+          </div>
+        </div>
+        <div class="vasDashboard-item">
+          <div class="left-content">
+            <div class="public-statistics">
+              <div class="public-statistics-item vas-db-bg1">
+                <p class="public-text">昨日消耗游戏币</p>
+                <div class="flex-style1">
+                  <p class="vas-db-color1">{{ data.dhqtjData.yesterDay || 0 }}</p>
+                  <p>个</p>
+                </div>
+              </div>
+              <div class="public-statistics-item vas-db-bg2">
+                <p class="public-text">今日消耗游戏币</p>
+                <div class="flex-style1">
+                  <p class="vas-db-color2">{{ data.dhqtjData.today || 0 }}</p>
+                  <p>个</p>
+                </div>
+              </div>
+              <div class="public-statistics-item vas-db-bg3">
+                <p class="public-text">累计消耗游戏币</p>
+                <div class="flex-style1">
+                  <p class="vas-db-color3">{{ data.dhqtjData.total || 0}}</p>
+                  <p>个</p>
+                </div>
+              </div>
+            </div>
+            <div class="line-chart-box">
+              <div class="line-chart-box" id="userLineChart" v-if="data.dhqtjData.chartData && data.dhqtjData.chartData.length">
+              </div>
+              <div class="line-chart-box" v-else>
+                <div class="chart-none-data"></div>
+                <p class="chart-none-text">暂无数据</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+</template>
+<style lang="scss">
+    .vasDashboard-item-box {
+    background: #fff;
+    padding: 10px;
+    margin-bottom: 10px;
+  
+    .chartTop {
+      margin-bottom: 10px;
+      display: flex;
+      align-items: center;
+  
+      .chartDiv {
+        width: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding-right: 20px;
+      }
+    }
+  
+    .public-statistics {
+      margin-bottom: 20px;
+    }
+  
+    .vas-db-bg1 {
+      background: #F3FAFE !important;
+    }
+  
+    .vas-db-bg2 {
+      background: #F8F6FF !important;
+    }
+  
+    .vas-db-bg3 {
+      background: #FFF5F7 !important;
+    }
+  
+    .vas-db-color1 {
+      color: #4DA1FF;
+    }
+  
+    .vas-db-color2 {
+      color: #A12DFF;
+    }
+  
+    .vas-db-color3 {
+      color: #FF557D;
+    }
+  
+    .chartTitle {
+      font-size: 16px;
+      line-height: 40px;
+      font-weight: 400;
+    }
+  
+    .vasDashboard-item {
+      display: flex;
+  
+      .left-content {
+        flex: 1;
+        overflow: hidden;
+      }
+  
+      .right-content {
+        flex: 1;
+        overflow: hidden;
+      }
+  
+      .line-chart-box {
+        height: 260px;
+        background: #fff;
+        overflow: hidden;
+      }
+  
+      .recharge-left-content {
+        flex: 1;
+        display: flex;
+  
+        .recharge-pie-chart {
+          flex: 2;
+          overflow: hidden;
+        }
+      }
+  
+      .recharge-amount-statistics,
+      .public-statistics,
+      .draw-money-statistics {
+        flex: 1;
+  
+        .recharge-amount-statistics-item,
+        .public-statistics-item,
+        .draw-money-statistics-item {
+          margin-bottom: 10px;
+          padding: 14px;
+  
+          .recharge-amount-text,
+          .public-text,
+          .draw-money-text {
+            margin-bottom: 8px;
+            font-size: 14px;
+            color: #323C47;
+          }
+  
+          .draw-money-text {
+            text-align: center;
+          }
+  
+          .flex-style1 {
+            display: flex;
+            justify-content: space-between;
+            align-items: baseline;
+  
+            p:nth-child(1) {
+              font-size: 24px;
+            }
+          }
+  
+          .flex-style2 {
+            display: flex;
+            justify-content: center;
+            align-items: baseline;
+  
+            p:nth-child(1) {
+              font-size: 24px;
+            }
+          }
+        }
+      }
+  
+      .public-statistics {
+        display: flex;
+  
+        .public-statistics-item {
+          flex: 1;
+          margin-right: 20px;
+          background: #F9F9F9;
+        //   height: 46px;
+        }
+      }
+  
+      .draw-money-right-content {
+        .draw-money-statistics {
+          display: flex;
+  
+          .draw-money-statistics-item {
+            flex: 1;
+            margin-bottom: 20px;
+            background: #F9F9F9;
+          }
+        }
+      }
+  
+  
+    }
+  }
+  .chart-none-data {
+    margin: 40px auto 0;
+    background: url('../../../assets/img/chart-none-data.png') no-repeat;
+    width: 229px;
+    height: 171px;
+    background-size: cover;
+  }
+  
+  .chart-none-text {
+    text-align: center;
+    color: #999;
+    font-size: 16px;
+  }
+</style>

+ 91 - 0
vite.config.js

@@ -0,0 +1,91 @@
+import {
+  defineConfig
+} from 'vite'
+import {
+  fileURLToPath,
+  URL
+} from 'node:url'
+import vue from '@vitejs/plugin-vue'
+import VueSetupExtend from 'vite-plugin-vue-setup-extend'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import {
+  ElementPlusResolver
+} from 'unplugin-vue-components/resolvers'
+import {
+  createStyleImportPlugin,
+  ElementPlusResolve,
+} from "vite-plugin-style-import"; // 下载 vite-plugin-style-import 如遇到 Error: Cannot find module 'consola' 因为插件的使用了 consola 包,但是我们没有安装.所以需要安装 consola
+const packageJson = require('./package.json')
+const addr = ''
+// https://vitejs.dev/config/
+// const webpack = require('webpack')
+// module.exports = {
+//   // 在vue.config.js中configureWebpack中配置
+//   // 要引入webpack
+//   configureWebpack: {
+//     plugins: [
+//       new webpack.ProvidePlugin({
+//         'window.Quill': 'quill/dist/quill.js',
+//         Quill: 'quill/dist/quill.js'
+//       })
+//     ]
+//   }
+// }
+export default defineConfig(({
+  mode,
+  command
+}) => {
+  const CONSTANTS = {
+    BUILDTIME: Math.floor(Date.now() / 1000),
+    VERSION: packageJson.version,
+    SHOULD_REMOVE_DEBUG_INFOS: ["production"].includes(mode),
+  };
+  return {
+    base: `${process.env.NODE_ENV === 'prod' ? addr : ''}./`,
+    plugins: [
+      vue(),
+      VueSetupExtend(),
+      // 解决element-Puls自动引入部分样式不生效问题
+      createStyleImportPlugin({
+        resolves: [ElementPlusResolve()],
+      }),
+      AutoImport({
+        resolvers: [ElementPlusResolver()],
+      }),
+      Components({
+        resolvers: [ElementPlusResolver()],
+      }),
+    ],
+    build: {
+      rollupOptions: {
+        output: {
+          // 打包分支创建版本号
+          assetFileNames: `assets/[name].[hash].${CONSTANTS.VERSION}.[extname]`,
+          chunkFileNames: `assets/[name].[hash].${CONSTANTS.VERSION}.js`,
+        },
+      },
+    },
+    esbuild: {
+      drop: CONSTANTS.SHOULD_REMOVE_DEBUG_INFOS ? ["console", "debugger"] : [],
+    },
+    resolve: {
+      alias: {
+        "@": fileURLToPath(new URL("./src",
+          import.meta.url)),
+      },
+    },
+    server: {
+      proxy: {
+        '/api': {
+          target: 'http://debugapi.mashangyl.com',
+          pathRewrite: {
+            '^/api': ''
+          },
+          changeOrigin: true, 
+        },
+      },
+      cors: true,
+    }
+  }
+})

Fichier diff supprimé car celui-ci est trop grand
+ 139 - 0
vite.config.js.timestamp-1695799624641-0acee64528f11.mjs