skyline 2 месяцев назад
Родитель
Сommit
6640c4b28e
100 измененных файлов с 1750 добавлено и 5185 удалено
  1. 24 0
      .gitignore
  2. 0 38
      haha-admin-web/.github/ISSUE_TEMPLATE/bug_report.yml
  3. 0 1
      haha-admin-web/.github/ISSUE_TEMPLATE/config.yml
  4. 0 52
      haha-admin-web/.github/workflows/linter.yml
  5. 0 42
      haha-admin-web/.github/workflows/pages.yml
  6. 0 8
      haha-admin-web/.husky/commit-msg
  7. 0 9
      haha-admin-web/.husky/common.sh
  8. 0 10
      haha-admin-web/.husky/pre-commit
  9. 7 7
      haha-admin-web/build/plugins.ts
  10. 2 280
      haha-admin-web/mock/asyncRoutes.ts
  11. 0 455
      haha-admin-web/mock/list.ts
  12. 0 41
      haha-admin-web/mock/map.ts
  13. 1 1
      haha-admin-web/package.json
  14. 66 0
      haha-admin-web/src/api/activity.ts
  15. 53 0
      haha-admin-web/src/api/admin.ts
  16. 59 0
      haha-admin-web/src/api/announcement.ts
  17. 43 0
      haha-admin-web/src/api/auth.ts
  18. 80 0
      haha-admin-web/src/api/checkin.ts
  19. 60 0
      haha-admin-web/src/api/coupon.ts
  20. 23 0
      haha-admin-web/src/api/dashboard.ts
  21. 49 0
      haha-admin-web/src/api/device.ts
  22. 135 0
      haha-admin-web/src/api/dict.ts
  23. 137 0
      haha-admin-web/src/api/inventory.ts
  24. 0 14
      haha-admin-web/src/api/list.ts
  25. 0 25
      haha-admin-web/src/api/mock.ts
  26. 60 0
      haha-admin-web/src/api/newProductApply.ts
  27. 57 0
      haha-admin-web/src/api/operationLog.ts
  28. 43 0
      haha-admin-web/src/api/order.ts
  29. 29 0
      haha-admin-web/src/api/permission.ts
  30. 53 0
      haha-admin-web/src/api/product.ts
  31. 43 0
      haha-admin-web/src/api/role.ts
  32. 132 0
      haha-admin-web/src/api/shop.ts
  33. 46 0
      haha-admin-web/src/api/sync.ts
  34. 46 58
      haha-admin-web/src/api/user.ts
  35. 59 0
      haha-admin-web/src/components/DictSelect/index.vue
  36. 39 0
      haha-admin-web/src/components/DictTag/index.vue
  37. 0 7
      haha-admin-web/src/components/ReAnimateSelector/index.ts
  38. 0 114
      haha-admin-web/src/components/ReAnimateSelector/src/animate.ts
  39. 0 136
      haha-admin-web/src/components/ReAnimateSelector/src/index.vue
  40. 0 7
      haha-admin-web/src/components/ReBarcode/index.ts
  41. 0 42
      haha-admin-web/src/components/ReBarcode/src/index.vue
  42. 0 2
      haha-admin-web/src/components/ReCountTo/README.md
  43. 3 10
      haha-admin-web/src/components/ReCountTo/index.ts
  44. 112 0
      haha-admin-web/src/components/ReCountTo/index.tsx
  45. 0 179
      haha-admin-web/src/components/ReCountTo/src/normal/index.tsx
  46. 0 32
      haha-admin-web/src/components/ReCountTo/src/normal/props.ts
  47. 0 72
      haha-admin-web/src/components/ReCountTo/src/rebound/index.tsx
  48. 0 15
      haha-admin-web/src/components/ReCountTo/src/rebound/props.ts
  49. 0 77
      haha-admin-web/src/components/ReCountTo/src/rebound/rebound.css
  50. 0 39
      haha-admin-web/src/components/ReFlicker/index.css
  51. 3 43
      haha-admin-web/src/components/ReFlicker/index.ts
  52. 46 0
      haha-admin-web/src/components/ReFlicker/index.tsx
  53. 0 7
      haha-admin-web/src/components/ReFlop/index.ts
  54. 0 184
      haha-admin-web/src/components/ReFlop/src/filpper.css
  55. 0 92
      haha-admin-web/src/components/ReFlop/src/filpper.tsx
  56. 0 135
      haha-admin-web/src/components/ReFlop/src/index.vue
  57. 0 17
      haha-admin-web/src/components/ReFlowChart/index.ts
  58. 0 148
      haha-admin-web/src/components/ReFlowChart/src/Control.vue
  59. 0 17
      haha-admin-web/src/components/ReFlowChart/src/DataDialog.vue
  60. 0 85
      haha-admin-web/src/components/ReFlowChart/src/NodePanel.vue
  61. 0 166
      haha-admin-web/src/components/ReFlowChart/src/adpterForTurbo.ts
  62. 0 6
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.css
  63. BIN
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.eot
  64. 0 8
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.js
  65. 0 58
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.json
  66. 0 0
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.svg
  67. BIN
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.ttf
  68. BIN
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff
  69. BIN
      haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff2
  70. 0 55
      haha-admin-web/src/components/ReFlowChart/src/config.ts
  71. 0 7
      haha-admin-web/src/components/ReMap/index.ts
  72. 0 136
      haha-admin-web/src/components/ReMap/src/Amap.vue
  73. 0 5
      haha-admin-web/src/components/RePerms/index.ts
  74. 0 20
      haha-admin-web/src/components/RePerms/src/perms.tsx
  75. 0 7
      haha-admin-web/src/components/ReSeamlessScroll/index.ts
  76. 0 538
      haha-admin-web/src/components/ReSeamlessScroll/src/index.vue
  77. 0 119
      haha-admin-web/src/components/ReSeamlessScroll/src/utils.ts
  78. 78 0
      haha-admin-web/src/components/ReSegmented/index.scss
  79. 2 6
      haha-admin-web/src/components/ReSegmented/index.ts
  80. 101 0
      haha-admin-web/src/components/ReSegmented/index.tsx
  81. 0 156
      haha-admin-web/src/components/ReSegmented/src/index.css
  82. 59 190
      haha-admin-web/src/components/ReSegmented/src/index.tsx
  83. 0 20
      haha-admin-web/src/components/ReSegmented/src/type.ts
  84. 0 7
      haha-admin-web/src/components/ReSelector/index.ts
  85. 0 28
      haha-admin-web/src/components/ReSelector/src/index.css
  86. 0 327
      haha-admin-web/src/components/ReSelector/src/index.tsx
  87. 0 23
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.css
  88. 0 66
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.js
  89. 0 23
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.json
  90. BIN
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.ttf
  91. BIN
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.woff
  92. BIN
      haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.woff2
  93. 0 49
      haha-admin-web/src/components/ReSplitPane/index.css
  94. 0 136
      haha-admin-web/src/components/ReSplitPane/index.tsx
  95. 0 47
      haha-admin-web/src/components/ReSplitPane/resizer.css
  96. 0 23
      haha-admin-web/src/components/ReSplitPane/resizer.tsx
  97. 0 8
      haha-admin-web/src/components/ReTypeit/index.ts
  98. 0 56
      haha-admin-web/src/components/ReTypeit/src/index.tsx
  99. 0 5
      haha-admin-web/src/components/ReVxeTableBar/index.ts
  100. 0 389
      haha-admin-web/src/components/ReVxeTableBar/src/bar.tsx

+ 24 - 0
.gitignore

@@ -20,5 +20,29 @@ haha-common/target/
 haha-entity/target/
 haha-mapper/target/
 haha-service/target/
+haha-admin/target/
 .qoder/
 .trae/
+
+# ---> Node.js / Frontend (haha-admin-web, haha-admin-mp)
+node_modules/
+.DS_Store
+dist/
+dist-ssr/
+*.local
+.eslintcache
+report.html
+vite.config.*.timestamp*
+
+yarn.lock
+npm-debug.log*
+.pnpm-error.log*
+.pnpm-debug.log
+tests/**/coverage/
+
+# Editor directories and files
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+tsconfig.tsbuildinfo

+ 0 - 38
haha-admin-web/.github/ISSUE_TEMPLATE/bug_report.yml

@@ -1,38 +0,0 @@
-name: "\U0001F41E Bug report"
-description: Report an issue with vue-pure-admin
-body:
-  - type: markdown
-    attributes:
-      value: |
-        感谢您花时间填写此错误报告 (Thanks for taking the time to fill out this bug report)
-  - type: textarea
-    id: bug-description
-    attributes:
-      label: 描述问题 (Describe the problem)
-      placeholder: 请描述您的问题 (Please describe your problem)
-    validations:
-      required: true
-  - type: textarea
-    id: reproduction-steps
-    attributes:
-      label: 如何复现该问题 (How to reproduce the problem)
-      placeholder: 请提供复现问题的具体操作步骤,以便平台快速定位、高效地解决问题。当然如果问题的操作步骤较复杂,您可以fork平台,然后去改动代码复现问题,这样更高效 (Please provide specific steps to reproduce the problem, so that the platform can quickly locate and solve the problem efficiently. Of course, if the operation steps of the problem are more complicated, you can fork the platform, and then modify the code to reproduce the problem, which is more efficient)
-    validations:
-      required: true
-  - type: textarea
-    id: system-info
-    attributes:
-      label: 操作系统和浏览器信息 (Operating system and browser information)
-      placeholder: 如果您遇到操作系统或浏览器兼容性问题,可选填此项 (Optional if you encounter operating system or browser compatibility issues)
-    validations:
-      required: false
-  - type: checkboxes
-    id: checkboxes
-    attributes:
-      label: 验证 (Verify)
-      description: 在提交问题之前,请确保您执行以下操作 (Before submitting an issue, please ensure you do the following)
-      options:
-        - label: 是否仔细阅读过 [文档](https://pure-admin.cn/) (Have you read [documentation](https://pure-admin.cn/) carefully)
-          required: true
-        - label: 检查是否存在相同或类似的问题 [issues](https://github.com/pure-admin/vue-pure-admin/issues) (Check for the same or similar [issues](https://github.com/pure-admin/vue-pure-admin/issues))
-          required: true

+ 0 - 1
haha-admin-web/.github/ISSUE_TEMPLATE/config.yml

@@ -1 +0,0 @@
-blank_issues_enabled: false

+ 0 - 52
haha-admin-web/.github/workflows/linter.yml

@@ -1,52 +0,0 @@
-name: Lint Code
-on:
-  push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-
-jobs:
-  build:
-    name: Lint Code
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version: 20
-
-      - uses: pnpm/action-setup@v3
-        name: Install pnpm
-        id: pnpm-install
-        with:
-          version: 9
-          run_install: false
-
-      - name: Get pnpm store directory
-        id: pnpm-cache
-        shell: bash
-        run: |
-          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
-
-      - uses: actions/cache@v4
-        name: Setup pnpm cache
-        with:
-          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
-          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
-          restore-keys: |
-            ${{ runner.os }}-pnpm-store-
-
-      - name: Start Lint Code
-        run: |
-          pnpm install --no-frozen-lockfile
-          pnpm lint
-          pnpm typecheck
-        env:
-          VALIDATE_ALL_CODEBASE: false
-          DEFAULT_BRANCH: main
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 42
haha-admin-web/.github/workflows/pages.yml

@@ -1,42 +0,0 @@
-name: Build and Deploy
-permissions:
-  contents: write
-on:
-  push:
-    branches:
-      - pages
-      
-jobs:
-  deploy:
-    concurrency: ci-${{ github.ref }}
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout 🛎️
-        uses: actions/checkout@v4
-
-      - name: Install Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version: 20
-          registry-url: https://registry.npmjs.com/
-
-      - uses: pnpm/action-setup@v3
-        name: Install pnpm
-        id: pnpm-install
-        with:
-          version: 9
-          run_install: false
-
-      - name: Deploy 🔧
-        run: |
-          pnpm install --no-frozen-lockfile
-          sed -i "s#VITE_PUBLIC_PATH = /#VITE_PUBLIC_PATH = /vue-pure-admin/#g" $(pwd)/.env.production
-          pnpm build
-          cd dist
-          touch README.md .nojekyll
-
-      - name: Deploy 🚀
-        uses: JamesIves/github-pages-deploy-action@v4
-        with:
-          folder: dist
-          clean: true

+ 0 - 8
haha-admin-web/.husky/commit-msg

@@ -1,8 +0,0 @@
-#!/bin/sh
-
-# shellcheck source=./_/husky.sh
-. "$(dirname "$0")/_/husky.sh"
-
-PATH="/usr/local/bin:$PATH"
-
-npx --no-install commitlint --edit "$1"

+ 0 - 9
haha-admin-web/.husky/common.sh

@@ -1,9 +0,0 @@
-#!/bin/sh
-command_exists () {
-  command -v "$1" >/dev/null 2>&1
-}
-
-# Workaround for Windows 10, Git Bash and Pnpm
-if command_exists winpty && test -t 1; then
-  exec < /dev/tty
-fi

+ 0 - 10
haha-admin-web/.husky/pre-commit

@@ -1,10 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-. "$(dirname "$0")/common.sh"
-
-[ -n "$CI" ] && exit 0
-
-PATH="/usr/local/bin:$PATH"
-
-# Perform lint check on files in the staging area through .lintstagedrc configuration
-pnpm exec lint-staged

+ 7 - 7
haha-admin-web/build/plugins.ts

@@ -51,13 +51,13 @@ export function getPluginsList(
      * vite-plugin-router-warn只在开发环境下启用,只处理vue-router文件并且只在服务启动或重启时运行一次,性能消耗可忽略不计
      */
     removeNoMatch(),
-    // mock支持
-    vitePluginFakeServer({
-      logger: false,
-      include: "mock",
-      infixName: false,
-      enableProd: true
-    }),
+    // mock支持(已禁用,使用真实后端接口)
+    // vitePluginFakeServer({
+    //   logger: false,
+    //   include: "mock",
+    //   infixName: false,
+    //   enableProd: true
+    // }),
     // svg组件化支持
     svgLoader(),
     // 自动按需加载图标

+ 2 - 280
haha-admin-web/mock/asyncRoutes.ts

@@ -1,12 +1,5 @@
-// 模拟后端动态生成路由
 import { defineFakeRoute } from "vite-plugin-fake-server/client";
-import { system, monitor, permission, frame, tabs } from "@/router/enums";
-
-/**
- * roles:页面级别权限,这里模拟二种 "admin"、"common"
- * admin:管理员角色
- * common:普通角色
- */
+import { system } from "@/router/enums";
 
 const systemManagementRouter = {
   path: "/system",
@@ -55,271 +48,6 @@ const systemManagementRouter = {
   ]
 };
 
-const systemMonitorRouter = {
-  path: "/monitor",
-  meta: {
-    icon: "ep:monitor",
-    title: "menus.pureSysMonitor",
-    rank: monitor
-  },
-  children: [
-    {
-      path: "/monitor/online-user",
-      component: "monitor/online/index",
-      name: "OnlineUser",
-      meta: {
-        icon: "ri:user-voice-line",
-        title: "menus.pureOnlineUser",
-        roles: ["admin"]
-      }
-    },
-    {
-      path: "/monitor/login-logs",
-      component: "monitor/logs/login/index",
-      name: "LoginLog",
-      meta: {
-        icon: "ri:window-line",
-        title: "menus.pureLoginLog",
-        roles: ["admin"]
-      }
-    },
-    {
-      path: "/monitor/operation-logs",
-      component: "monitor/logs/operation/index",
-      name: "OperationLog",
-      meta: {
-        icon: "ri:history-fill",
-        title: "menus.pureOperationLog",
-        roles: ["admin"]
-      }
-    },
-    {
-      path: "/monitor/system-logs",
-      component: "monitor/logs/system/index",
-      name: "SystemLog",
-      meta: {
-        icon: "ri:file-search-line",
-        title: "menus.pureSystemLog",
-        roles: ["admin"]
-      }
-    }
-  ]
-};
-
-const permissionRouter = {
-  path: "/permission",
-  meta: {
-    title: "menus.purePermission",
-    icon: "ep:lollipop",
-    rank: permission
-  },
-  children: [
-    {
-      path: "/permission/page/index",
-      name: "PermissionPage",
-      meta: {
-        title: "menus.purePermissionPage",
-        roles: ["admin", "common"]
-      }
-    },
-    {
-      path: "/permission/button",
-      meta: {
-        title: "menus.purePermissionButton",
-        roles: ["admin", "common"]
-      },
-      children: [
-        {
-          path: "/permission/button/router",
-          component: "permission/button/index",
-          name: "PermissionButtonRouter",
-          meta: {
-            title: "menus.purePermissionButtonRouter",
-            auths: [
-              "permission:btn:add",
-              "permission:btn:edit",
-              "permission:btn:delete"
-            ]
-          }
-        },
-        {
-          path: "/permission/button/login",
-          component: "permission/button/perms",
-          name: "PermissionButtonLogin",
-          meta: {
-            title: "menus.purePermissionButtonLogin"
-          }
-        }
-      ]
-    }
-  ]
-};
-
-const frameRouter = {
-  path: "/iframe",
-  meta: {
-    icon: "ri:links-fill",
-    title: "menus.pureExternalPage",
-    rank: frame
-  },
-  children: [
-    {
-      path: "/iframe/embedded",
-      meta: {
-        title: "menus.pureEmbeddedDoc"
-      },
-      children: [
-        {
-          path: "/iframe/colorhunt",
-          name: "FrameColorHunt",
-          meta: {
-            title: "menus.pureColorHuntDoc",
-            frameSrc: "https://colorhunt.co/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/uigradients",
-          name: "FrameUiGradients",
-          meta: {
-            title: "menus.pureUiGradients",
-            frameSrc: "https://uigradients.com/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/ep",
-          name: "FrameEp",
-          meta: {
-            title: "menus.pureEpDoc",
-            frameSrc: "https://element-plus.org/zh-CN/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/tailwindcss",
-          name: "FrameTailwindcss",
-          meta: {
-            title: "menus.pureTailwindcssDoc",
-            frameSrc: "https://tailwindcss.com/docs/installation",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/vue3",
-          name: "FrameVue",
-          meta: {
-            title: "menus.pureVueDoc",
-            frameSrc: "https://cn.vuejs.org/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/vite",
-          name: "FrameVite",
-          meta: {
-            title: "menus.pureViteDoc",
-            frameSrc: "https://cn.vitejs.dev/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/pinia",
-          name: "FramePinia",
-          meta: {
-            title: "menus.purePiniaDoc",
-            frameSrc: "https://pinia.vuejs.org/zh/index.html",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/iframe/vue-router",
-          name: "FrameRouter",
-          meta: {
-            title: "menus.pureRouterDoc",
-            frameSrc: "https://router.vuejs.org/zh/",
-            keepAlive: true,
-            roles: ["admin", "common"]
-          }
-        }
-      ]
-    },
-    {
-      path: "/iframe/external",
-      meta: {
-        title: "menus.pureExternalDoc"
-      },
-      children: [
-        {
-          path: "/external",
-          name: "https://pure-admin.cn/",
-          meta: {
-            title: "menus.pureExternalLink",
-            roles: ["admin", "common"]
-          }
-        },
-        {
-          path: "/pureUtilsLink",
-          name: "https://pure-admin-utils.netlify.app/",
-          meta: {
-            title: "menus.pureUtilsLink",
-            roles: ["admin", "common"]
-          }
-        }
-      ]
-    }
-  ]
-};
-
-const tabsRouter = {
-  path: "/tabs",
-  meta: {
-    icon: "ri:bookmark-2-line",
-    title: "menus.pureTabs",
-    rank: tabs
-  },
-  children: [
-    {
-      path: "/tabs/index",
-      name: "Tabs",
-      meta: {
-        title: "menus.pureTabs",
-        roles: ["admin", "common"]
-      }
-    },
-    // query 传参模式
-    {
-      path: "/tabs/query-detail",
-      name: "TabQueryDetail",
-      meta: {
-        // 不在menu菜单中显示
-        showLink: false,
-        activePath: "/tabs/index",
-        roles: ["admin", "common"]
-      }
-    },
-    // params 传参模式
-    {
-      path: "/tabs/params-detail/:id",
-      component: "params-detail",
-      name: "TabParamsDetail",
-      meta: {
-        // 不在menu菜单中显示
-        showLink: false,
-        activePath: "/tabs/index",
-        roles: ["admin", "common"]
-      }
-    }
-  ]
-};
-
 export default defineFakeRoute([
   {
     url: "/get-async-routes",
@@ -327,13 +55,7 @@ export default defineFakeRoute([
     response: () => {
       return {
         success: true,
-        data: [
-          systemManagementRouter,
-          systemMonitorRouter,
-          permissionRouter,
-          frameRouter,
-          tabsRouter
-        ]
+        data: [systemManagementRouter]
       };
     }
   }

+ 0 - 455
haha-admin-web/mock/list.ts

@@ -1,455 +0,0 @@
-import { defineFakeRoute } from "vite-plugin-fake-server/client";
-
-export default defineFakeRoute([
-  {
-    url: "/get-card-list",
-    method: "post",
-    response: () => {
-      return {
-        success: true,
-        data: {
-          list: [
-            {
-              index: 1,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "SSL证书",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 2,
-              isSetup: false,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "人脸识别",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 3,
-              isSetup: false,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 4,
-              isSetup: false,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "SSL证书",
-              description:
-                "云数据库MySQL为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。"
-            },
-            {
-              index: 5,
-              isSetup: true,
-              type: 3,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "SSL证书",
-              description:
-                "云数据库MySQL为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。"
-            },
-            {
-              index: 6,
-              isSetup: true,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 7,
-              isSetup: false,
-              type: 1,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "CVM",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 8,
-              isSetup: true,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "SSL证书",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 9,
-              isSetup: false,
-              type: 1,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "SSL证书",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 10,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "云数据库MySQL为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。"
-            },
-            {
-              index: 11,
-              isSetup: true,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "云数据库",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 12,
-              isSetup: true,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "SSL证书",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 13,
-              isSetup: true,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-db.jpg",
-              name: "云数据库",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 14,
-              isSetup: false,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "SSL证书",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 15,
-              isSetup: true,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "云数据库",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 16,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "CVM",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 17,
-              isSetup: false,
-              type: 5,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "云数据库",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 18,
-              isSetup: false,
-              type: 4,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "云数据库",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 19,
-              isSetup: true,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 20,
-              isSetup: true,
-              type: 4,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "SSL证书",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 21,
-              isSetup: false,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "云数据库",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 22,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-db.jpg",
-              name: "CVM",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 23,
-              isSetup: true,
-              type: 1,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "人脸识别",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 24,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "人脸识别",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 25,
-              isSetup: false,
-              type: 5,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "CVM",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 26,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "SSL证书",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 27,
-              isSetup: true,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 28,
-              isSetup: false,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "云数据库",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 29,
-              isSetup: false,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-db.jpg",
-              name: "CVM",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 30,
-              isSetup: true,
-              type: 1,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 31,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "CVM",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 32,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 33,
-              isSetup: true,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "CVM",
-              description:
-                "云数据库MySQL为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。"
-            },
-            {
-              index: 34,
-              isSetup: false,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "SSL证书",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 35,
-              isSetup: false,
-              type: 1,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "云数据库",
-              description:
-                "基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、五官定位、人脸搜索、人脸比对、人脸"
-            },
-            {
-              index: 36,
-              isSetup: false,
-              type: 4,
-              banner:
-                "https://tdesign.gtimg.com/tdesign-pro/face-recognition.jpg",
-              name: "SSL证书",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 37,
-              isSetup: true,
-              type: 5,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "CVM",
-              description:
-                "云数据库MySQL为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。"
-            },
-            {
-              index: 38,
-              isSetup: false,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "云数据库",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 39,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "人脸识别",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 40,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "CVM",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 41,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 42,
-              isSetup: true,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 43,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-db.jpg",
-              name: "SSL证书",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 44,
-              isSetup: true,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/t-sec.jpg",
-              name: "SSL证书",
-              description:
-                "云硬盘为您提供用于CVM的持久性数据块级存储服务。云硬盘中的数据自动地可用区内以多副本冗"
-            },
-            {
-              index: 45,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 46,
-              isSetup: true,
-              type: 2,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "SSL证书",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            },
-            {
-              index: 47,
-              isSetup: false,
-              type: 4,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/cloud-server.jpg",
-              name: "SSL证书",
-              description:
-                "腾讯安全云防火墙产品,是腾讯云安全团队结合云原生的优势,自主研发的SaaS化防火墙产品,无需客无需客无需客无需客无需客无需客无需客"
-            },
-            {
-              index: 48,
-              isSetup: false,
-              type: 3,
-              banner: "https://tdesign.gtimg.com/tdesign-pro/ssl.jpg",
-              name: "T-Sec 云防火墙",
-              description:
-                "SSL证书又叫服务器证书,腾讯云为您提供证书的一站式服务,包括免费、付费证书的申请、管理及部"
-            }
-          ]
-        }
-      };
-    }
-  }
-]);

+ 0 - 41
haha-admin-web/mock/map.ts

@@ -1,41 +0,0 @@
-import { defineFakeRoute } from "vite-plugin-fake-server/client";
-import { faker } from "@faker-js/faker/locale/zh_CN";
-
-type mapType = {
-  plateNumber: string;
-  driver: string;
-  orientation: number;
-  lng: number;
-  lat: number;
-};
-
-const mapList = (): Array<mapType> => {
-  const result: Array<mapType> = [];
-  for (let index = 0; index < 200; index++) {
-    result.push({
-      plateNumber: `豫A${faker.string.numeric({
-        length: 5
-      })}${faker.string.alphanumeric({
-        casing: "upper"
-      })}`,
-      driver: faker.person.firstName(),
-      orientation: faker.number.int({ min: 1, max: 360 }),
-      lng: faker.location.latitude({ max: 114.1, min: 113 }),
-      lat: faker.location.latitude({ max: 35.1, min: 34 })
-    });
-  }
-  return result;
-};
-
-export default defineFakeRoute([
-  {
-    url: "/get-map-info",
-    method: "get",
-    response: () => {
-      return {
-        success: true,
-        data: mapList()
-      };
-    }
-  }
-]);

+ 1 - 1
haha-admin-web/package.json

@@ -18,7 +18,7 @@
     "lint:prettier": "prettier --write  \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
     "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
     "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
-    "prepare": "husky",
+    "prepare": "cd .. && husky haha-admin-web/.husky",
     "preinstall": "npx only-allow pnpm"
   },
   "keywords": [

+ 66 - 0
haha-admin-web/src/api/activity.ts

@@ -0,0 +1,66 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getActivityList = (params: {
+  page?: number;
+  pageSize?: number;
+  name?: string;
+  type?: number;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/marketing/activity/list", {
+    params
+  });
+};
+
+export const getActivityById = (id: number) => {
+  return http.request<Result>("get", `/marketing/activity/${id}`);
+};
+
+export const createActivity = (data: any) => {
+  return http.request<Result>("post", "/marketing/activity", { data });
+};
+
+export const updateActivity = (id: number, data: any) => {
+  return http.request<Result>("put", `/marketing/activity/${id}`, { data });
+};
+
+export const publishActivity = (id: number) => {
+  return http.request<Result>("put", `/marketing/activity/${id}/publish`);
+};
+
+export const pauseActivity = (id: number) => {
+  return http.request<Result>("put", `/marketing/activity/${id}/pause`);
+};
+
+export const resumeActivity = (id: number) => {
+  return http.request<Result>("put", `/marketing/activity/${id}/resume`);
+};
+
+export const deleteActivity = (id: number) => {
+  return http.request<Result>("delete", `/marketing/activity/${id}`);
+};
+
+export const getActivityStatistics = (id: number) => {
+  return http.request<Result>("get", `/marketing/activity/${id}/statistics`);
+};
+
+export const getOngoingActivities = () => {
+  return http.request<Result>("get", "/marketing/activity/ongoing");
+};

+ 53 - 0
haha-admin-web/src/api/admin.ts

@@ -0,0 +1,53 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getAdminList = (data: {
+  page?: number;
+  pageSize?: number;
+  username?: string;
+  phone?: string;
+  roleId?: number;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("post", "/user", { data });
+};
+
+export const getAdminById = (id: number) => {
+  return http.request<Result>("get", `/user/${id}`);
+};
+
+export const addAdmin = (data: any) => {
+  return http.request<Result>("post", "/user/add", { data });
+};
+
+export const updateAdmin = (data: any) => {
+  return http.request<Result>("post", "/user/update", { data });
+};
+
+export const deleteAdmin = (id: number) => {
+  return http.request<Result>("delete", `/user/${id}`);
+};
+
+export const resetPassword = (data: { id: number; newPassword: string }) => {
+  return http.request<Result>("post", "/user/reset-password", { data });
+};
+
+export const getAdminStatistics = () => {
+  return http.request<Result>("get", "/user/statistics");
+};

+ 59 - 0
haha-admin-web/src/api/announcement.ts

@@ -0,0 +1,59 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getAnnouncementList = (params: {
+  page?: number;
+  pageSize?: number;
+  title?: string;
+  type?: number;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/announcement/list", { params });
+};
+
+export const getAnnouncementById = (id: number) => {
+  return http.request<Result>("get", `/announcement/${id}`);
+};
+
+export const createAnnouncement = (data: any) => {
+  return http.request<Result>("post", "/announcement", { data });
+};
+
+export const updateAnnouncement = (id: number, data: any) => {
+  return http.request<Result>("put", `/announcement/${id}`, { data });
+};
+
+export const deleteAnnouncement = (id: number) => {
+  return http.request<Result>("delete", `/announcement/${id}`);
+};
+
+export const publishAnnouncement = (id: number) => {
+  return http.request<Result>("put", `/announcement/${id}/publish`);
+};
+
+export const offlineAnnouncement = (id: number) => {
+  return http.request<Result>("put", `/announcement/${id}/offline`);
+};
+
+export const setAnnouncementTop = (id: number, isTop: number) => {
+  return http.request<Result>(
+    "put",
+    `/announcement/${id}/top?isTop=${isTop}`
+  );
+};

+ 43 - 0
haha-admin-web/src/api/auth.ts

@@ -0,0 +1,43 @@
+import { http } from "@/utils/http";
+
+export type LoginResult = {
+  code: number;
+  message: string;
+  data: {
+    token: string;
+    userInfo: {
+      id: number;
+      nickname: string;
+      avatar: string | null;
+      phone: string;
+    };
+  };
+};
+
+export type UserInfoResult = {
+  code: number;
+  message: string;
+  data: {
+    id: number;
+    username: string;
+    realName: string;
+    phone: string;
+    email: string;
+    avatar: string;
+    department: string;
+    roleIds: string;
+    permissions: string[];
+  };
+};
+
+export const login = (data: { username: string; password: string }) => {
+  return http.request<LoginResult>("post", "/login", { data });
+};
+
+export const logout = () => {
+  return http.request("post", "/login/logout");
+};
+
+export const getUserInfo = () => {
+  return http.request<UserInfoResult>("get", "/login/info");
+};

+ 80 - 0
haha-admin-web/src/api/checkin.ts

@@ -0,0 +1,80 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const submitCheckin = (data: {
+  type: string;
+  shopId?: number;
+  deviceId?: string;
+  location: {
+    latitude: number;
+    longitude: number;
+    address: string;
+    accuracy?: number;
+  };
+  photos: Array<{
+    id?: string;
+    path: string;
+    watermarkPath?: string;
+    timestamp?: number;
+  }>;
+  remark?: string;
+  offlineId?: string;
+}) => {
+  return http.request<Result>("post", "/checkin", { data });
+};
+
+export const getCheckinList = (params: {
+  page?: number;
+  pageSize?: number;
+  type?: string;
+  userId?: number;
+  shopId?: number;
+  startDate?: string;
+  endDate?: string;
+  status?: string;
+}) => {
+  return http.request<ResultTable>("get", "/checkin", { params });
+};
+
+export const getCheckinDetail = (id: number) => {
+  return http.request<Result>("get", `/checkin/${id}`);
+};
+
+export const getCheckinStats = () => {
+  return http.request<Result>("get", "/checkin/stats");
+};
+
+export const getTodayCheckin = () => {
+  return http.request<Result>("get", "/checkin/today");
+};
+
+export const syncOfflineCheckins = (data: { records: any[] }) => {
+  return http.request<Result>("post", "/checkin/sync", { data });
+};
+
+export const exportCheckinRecords = (params: {
+  type?: string;
+  userId?: number;
+  shopId?: number;
+  startDate?: string;
+  endDate?: string;
+  status?: string;
+}) => {
+  return http.request<Result>("get", "/checkin/export", { params });
+};

+ 60 - 0
haha-admin-web/src/api/coupon.ts

@@ -0,0 +1,60 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getCouponList = (params: {
+  page?: number;
+  pageSize?: number;
+  name?: string;
+  type?: number;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/marketing/coupon/list", { params });
+};
+
+export const getCouponById = (id: number) => {
+  return http.request<Result>("get", `/marketing/coupon/${id}`);
+};
+
+export const createCoupon = (data: any) => {
+  return http.request<Result>("post", "/marketing/coupon", { data });
+};
+
+export const updateCouponStatus = (id: number, status: number) => {
+  return http.request<Result>("put", `/marketing/coupon/${id}/status?status=${status}`);
+};
+
+export const deleteCoupon = (id: number) => {
+  return http.request<Result>("delete", `/marketing/coupon/${id}`);
+};
+
+export const distributeCoupon = (data: {
+  templateId: number;
+  userIds?: number[];
+  distributeType?: string;
+}) => {
+  return http.request<Result>("post", "/marketing/coupon/distribute", { data });
+};
+
+export const getCouponStatistics = (id: number) => {
+  return http.request<Result>("get", `/marketing/coupon/${id}/statistics`);
+};
+
+export const getAvailableCoupons = () => {
+  return http.request<Result>("get", "/marketing/coupon/available");
+};

+ 23 - 0
haha-admin-web/src/api/dashboard.ts

@@ -0,0 +1,23 @@
+import { http } from "@/utils/http";
+
+export type Result = {
+  code: number;
+  message: string;
+  data: any;
+};
+
+export const getOverview = () => {
+  return http.request<Result>("get", "/dashboard/overview");
+};
+
+export const getStatistics = (type: string = "week") => {
+  return http.request<Result>("get", `/dashboard/statistics?type=${type}`);
+};
+
+export const getQuickLinks = () => {
+  return http.request<Result>("get", "/dashboard/quick-links");
+};
+
+export const getDeviceStatus = () => {
+  return http.request<Result>("get", "/dashboard/device-status");
+};

+ 49 - 0
haha-admin-web/src/api/device.ts

@@ -0,0 +1,49 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getDeviceList = (params: {
+  page?: number;
+  pageSize?: number;
+  deviceId?: string;
+  shopId?: number;
+  status?: number;
+  storeName?: string;
+}) => {
+  return http.request<ResultTable>("get", "/devices/list", { params });
+};
+
+export const getDeviceById = (id: number) => {
+  return http.request<Result>("get", `/devices/${id}`);
+};
+
+export const getDeviceStatistics = () => {
+  return http.request<Result>("get", "/devices/statistics");
+};
+
+export const openDoor = (id: number, data: { doorIndex?: string }) => {
+  return http.request<Result>("post", `/devices/${id}/open`, { data });
+};
+
+export const setTemperature = (id: number, data: { temperature: number }) => {
+  return http.request<Result>("put", `/devices/${id}/temperature`, { data });
+};
+
+export const setVolume = (id: number, data: { volume: number }) => {
+  return http.request<Result>("put", `/devices/${id}/volume`, { data });
+};

+ 135 - 0
haha-admin-web/src/api/dict.ts

@@ -0,0 +1,135 @@
+import { http } from "@/utils/http";
+
+type Result<T = any> = {
+  code: number;
+  message: string;
+  data?: T;
+};
+
+export type DictType = {
+  id?: number;
+  dictCode: string;
+  dictName: string;
+  dictDesc?: string;
+  isSystem?: number;
+  status?: number;
+  sortOrder?: number;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  remark?: string;
+};
+
+export type DictData = {
+  id?: number;
+  dictCode: string;
+  itemValue: string;
+  itemLabel: string;
+  itemDesc?: string;
+  parentId?: number;
+  cssClass?: string;
+  listClass?: string;
+  isDefault?: number;
+  status?: number;
+  sortOrder?: number;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  remark?: string;
+};
+
+export type DictTypeListResult = {
+  list: DictType[];
+  total: number;
+  pageSize: number;
+  currentPage: number;
+};
+
+export type DictDataListResult = {
+  list: DictData[];
+  total: number;
+  pageSize: number;
+  currentPage: number;
+};
+
+export const getDictTypeList = (data: object) => {
+  return http.request<Result<DictTypeListResult>>("post", "/dict/type/list", { data });
+};
+
+export const getDictTypeById = (id: number) => {
+  return http.request<Result<DictType>>("get", `/dict/type/${id}`);
+};
+
+export const getDictTypeByCode = (dictCode: string) => {
+  return http.request<Result<DictType>>("get", `/dict/type/code/${dictCode}`);
+};
+
+export const addDictType = (data: DictType) => {
+  return http.request<Result<void>>("post", "/dict/type/add", { data });
+};
+
+export const updateDictType = (data: DictType) => {
+  return http.request<Result<void>>("post", "/dict/type/update", { data });
+};
+
+export const deleteDictType = (id: number) => {
+  return http.request<Result<void>>("delete", `/dict/type/${id}`);
+};
+
+export const batchDeleteDictType = (ids: number[]) => {
+  return http.request<Result<void>>("post", "/dict/type/batch-delete", { data: ids });
+};
+
+export const getDictDataList = (dictCode: string) => {
+  return http.request<Result<DictData[]>>("get", `/dict/data/list/${dictCode}`);
+};
+
+export const getDictDataPage = (data: object) => {
+  return http.request<Result<DictDataListResult>>("post", "/dict/data/page", { data });
+};
+
+export const getDictDataById = (id: number) => {
+  return http.request<Result<DictData>>("get", `/dict/data/${id}`);
+};
+
+export const addDictData = (data: DictData) => {
+  return http.request<Result<void>>("post", "/dict/data/add", { data });
+};
+
+export const updateDictData = (data: DictData) => {
+  return http.request<Result<void>>("post", "/dict/data/update", { data });
+};
+
+export const deleteDictData = (id: number) => {
+  return http.request<Result<void>>("delete", `/dict/data/${id}`);
+};
+
+export const batchDeleteDictData = (ids: number[]) => {
+  return http.request<Result<void>>("post", "/dict/data/batch-delete", { data: ids });
+};
+
+export const getAllDictData = () => {
+  return http.request<Result<Record<string, DictData[]>>>("get", "/dict/all");
+};
+
+export const getDictLabel = (dictCode: string, itemValue: string) => {
+  return http.request<Result<string>>("get", "/dict/label", { params: { dictCode, itemValue } });
+};
+
+export const getDictValue = (dictCode: string, itemLabel: string) => {
+  return http.request<Result<string>>("get", "/dict/value", { params: { dictCode, itemLabel } });
+};
+
+export const refreshDictCache = () => {
+  return http.request<Result<void>>("post", "/dict/refresh-cache");
+};
+
+export const exportDictData = (dictCode: string) => {
+  return http.request<Result<void>>("get", `/dict/export/${dictCode}`);
+};
+
+export const importDictData = (data: DictData[]) => {
+  return http.request<Result<void>>("post", "/dict/import", { data });
+};

+ 137 - 0
haha-admin-web/src/api/inventory.ts

@@ -0,0 +1,137 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getDeviceInventoryStats = (params: {
+  page?: number;
+  pageSize?: number;
+  deviceId?: string;
+  shopId?: number;
+}) => {
+  return http.request<ResultTable>("get", "/inventory/device-stats", {
+    params
+  });
+};
+
+export const getInventoryList = (params: {
+  page?: number;
+  pageSize?: number;
+  deviceId?: string;
+  productId?: number;
+  lowStock?: boolean;
+}) => {
+  return http.request<ResultTable>("get", "/inventory/list", { params });
+};
+
+export const getDeviceInventory = (deviceId: string) => {
+  return http.request<Result>("get", `/inventory/device/${deviceId}`);
+};
+
+export const getLowStockList = () => {
+  return http.request<Result>("get", "/inventory/low-stock");
+};
+
+export const getInventoryStatistics = () => {
+  return http.request<Result>("get", "/inventory/statistics");
+};
+
+export const increaseStock = (data: {
+  deviceId: string;
+  productId: number;
+  productCode: string;
+  productName: string;
+  quantity: number;
+  shelfNum?: number;
+  position?: string;
+  activityId?: string;
+}) => {
+  return http.request<Result>("post", "/inventory/increase", { data });
+};
+
+export const adjustStock = (data: {
+  deviceId: string;
+  productId: number;
+  newStock: number;
+  remark?: string;
+}) => {
+  return http.request<Result>("post", "/inventory/adjust", { data });
+};
+
+export const getInventoryLogs = (params: {
+  page?: number;
+  pageSize?: number;
+  deviceId?: string;
+  productId?: number;
+  changeType?: string;
+}) => {
+  return http.request<ResultTable>("get", "/inventory/logs", { params });
+};
+
+export const getLogStatistics = (params: {
+  deviceId: string;
+  startTime?: string;
+  endTime?: string;
+}) => {
+  return http.request<Result>("get", "/inventory/logs/statistics", { params });
+};
+
+export const getStockRecords = (params: {
+  page?: number;
+  pageSize?: number;
+  deviceId?: string;
+  stockerId?: number;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/inventory/records", { params });
+};
+
+export const getStockRecordDetail = (id: number) => {
+  return http.request<Result>("get", `/inventory/records/${id}`);
+};
+
+export const createStockRecord = (data: {
+  deviceId: string;
+  stockType?: number;
+  stockerPhone?: string;
+  remark?: string;
+}) => {
+  return http.request<Result>("post", "/inventory/records", { data });
+};
+
+export const completeStockRecord = (
+  id: number,
+  data: { totalItems?: number; totalQuantity?: number; activityId?: string }
+) => {
+  return http.request<Result>("put", `/inventory/records/${id}/complete`, {
+    data
+  });
+};
+
+export const cancelStockRecord = (id: number) => {
+  return http.request<Result>("put", `/inventory/records/${id}/cancel`);
+};
+
+export const getStockerStatistics = (params: {
+  stockerId: number;
+  startTime?: string;
+  endTime?: string;
+}) => {
+  return http.request<Result>("get", "/inventory/records/stocker-statistics", {
+    params
+  });
+};

+ 0 - 14
haha-admin-web/src/api/list.ts

@@ -1,14 +0,0 @@
-import { http } from "@/utils/http";
-
-type Result = {
-  success: boolean;
-  data?: {
-    /** 列表数据 */
-    list: Array<any>;
-  };
-};
-
-/** 卡片列表 */
-export const getCardList = (data?: object) => {
-  return http.request<Result>("post", "/get-card-list", { data });
-};

+ 0 - 25
haha-admin-web/src/api/mock.ts

@@ -1,25 +0,0 @@
-import { http } from "@/utils/http";
-
-type Result = {
-  success: boolean;
-  data: Array<any>;
-};
-
-/** 地图数据 */
-export const mapJson = (params?: object) => {
-  return http.request<Result>("get", "/get-map-info", { params });
-};
-
-/** 文件上传 */
-export const formUpload = data => {
-  return http.request<Result>(
-    "post",
-    "https://run.mocky.io/v3/3aa761d7-b0b3-4a03-96b3-6168d4f7467b",
-    { data },
-    {
-      headers: {
-        "Content-Type": "multipart/form-data"
-      }
-    }
-  );
-};

+ 60 - 0
haha-admin-web/src/api/newProductApply.ts

@@ -0,0 +1,60 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getNewProductApplyList = (params: {
+  page?: number;
+  pageSize?: number;
+  productName?: string;
+  barcode?: string;
+  status?: number;
+  applicantId?: number;
+}) => {
+  return http.request<ResultTable>("get", "/new-product-apply/list", { params });
+};
+
+export const getNewProductApplyById = (id: number) => {
+  return http.request<Result>("get", `/new-product-apply/${id}`);
+};
+
+export const getNewProductApplyStatistics = () => {
+  return http.request<Result>("get", "/new-product-apply/statistics");
+};
+
+export const submitNewProductApply = (data: any) => {
+  return http.request<Result>("post", "/new-product-apply/submit", { data });
+};
+
+export const saveNewProductApplyDraft = (data: any) => {
+  return http.request<Result>("post", "/new-product-apply/save-draft", { data });
+};
+
+export const updateNewProductApply = (id: number, data: any) => {
+  return http.request<Result>("put", `/new-product-apply/${id}`, { data });
+};
+
+export const deleteNewProductApply = (id: number) => {
+  return http.request<Result>("delete", `/new-product-apply/${id}`);
+};
+
+export const checkBarcode = (barcode: string) => {
+  return http.request<Result>(
+    "get",
+    `/new-product-apply/check-barcode?barcode=${barcode}`
+  );
+};

+ 57 - 0
haha-admin-web/src/api/operationLog.ts

@@ -0,0 +1,57 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getOperationLogList = (params: {
+  page?: number;
+  pageSize?: number;
+  module?: string;
+  username?: string;
+  status?: number;
+  startTime?: string;
+  endTime?: string;
+}) => {
+  return http.request<ResultTable>("get", "/operation-log/list", { params });
+};
+
+export const getOperationLogDetail = (id: number) => {
+  return http.request<Result>("get", `/operation-log/${id}`);
+};
+
+export const deleteOperationLogBatch = (ids: number[]) => {
+  return http.request<Result>("delete", "/operation-log/batch", { data: ids });
+};
+
+export const deleteOperationLog = (id: number) => {
+  return http.request<Result>("delete", `/operation-log/${id}`);
+};
+
+export const clearAllOperationLogs = () => {
+  return http.request<Result>("delete", "/operation-log/clear");
+};
+
+export const clearOperationLogsBefore = (beforeTime: string) => {
+  return http.request<Result>(
+    "delete",
+    `/operation-log/clear-before?beforeTime=${beforeTime}`
+  );
+};
+
+export const getOperationLogStatistics = () => {
+  return http.request<Result>("get", "/operation-log/statistics");
+};

+ 43 - 0
haha-admin-web/src/api/order.ts

@@ -0,0 +1,43 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getOrderList = (params: {
+  page?: number;
+  pageSize?: number;
+  orderNo?: string;
+  deviceId?: string;
+  payStatus?: string;
+  status?: number;
+  startDate?: string;
+  endDate?: string;
+}) => {
+  return http.request<ResultTable>("get", "/order/list", { params });
+};
+
+export const getOrderById = (id: number) => {
+  return http.request<Result>("get", `/order/${id}`);
+};
+
+export const getOrderStatistics = () => {
+  return http.request<Result>("get", "/order/statistics");
+};
+
+export const refundOrder = (id: number, data: { reason: string }) => {
+  return http.request<Result>("post", `/order/${id}/refund`, { data });
+};

+ 29 - 0
haha-admin-web/src/api/permission.ts

@@ -0,0 +1,29 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+export const getAllPermissions = () => {
+  return http.request<Result>("get", "/permission/list");
+};
+
+export const getPermissionsGrouped = () => {
+  return http.request<Result>("get", "/permission/list-grouped");
+};
+
+export const getPermissionsByModule = (moduleCode: string) => {
+  return http.request<Result>("get", `/permission/module/${moduleCode}`);
+};
+
+export const getRolePermissionIds = (roleId: number) => {
+  return http.request<Result>("get", `/permission/role/${roleId}`);
+};
+
+export const updateRolePermissions = (roleId: number, permissionIds: number[]) => {
+  return http.request<Result>("post", `/permission/role/${roleId}`, {
+    data: permissionIds
+  });
+};

+ 53 - 0
haha-admin-web/src/api/product.ts

@@ -0,0 +1,53 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getProductList = (params: {
+  page?: number;
+  pageSize?: number;
+  name?: string;
+  barcode?: string;
+  category?: string;
+  syncStatus?: number;
+}) => {
+  return http.request<ResultTable>("get", "/products/list", { params });
+};
+
+export const getProductById = (id: number) => {
+  return http.request<Result>("get", `/products/${id}`);
+};
+
+export const getProductStatistics = () => {
+  return http.request<Result>("get", "/products/statistics");
+};
+
+export const addProduct = (data: any) => {
+  return http.request<Result>("post", "/products", { data });
+};
+
+export const updateProduct = (id: number, data: any) => {
+  return http.request<Result>("put", `/products/${id}`, { data });
+};
+
+export const deleteProduct = (id: number) => {
+  return http.request<Result>("delete", `/products/${id}`);
+};
+
+export const syncProduct = (id: number) => {
+  return http.request<Result>("post", `/products/${id}/sync`);
+};

+ 43 - 0
haha-admin-web/src/api/role.ts

@@ -0,0 +1,43 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getRoleList = (data: {
+  page?: number;
+  pageSize?: number;
+  name?: string;
+  code?: string;
+}) => {
+  return http.request<ResultTable>("post", "/role", { data });
+};
+
+export const getAllRoles = () => {
+  return http.request<Result>("get", "/role/list-all-role");
+};
+
+export const addRole = (data: any) => {
+  return http.request<Result>("post", "/role/add", { data });
+};
+
+export const updateRole = (data: any) => {
+  return http.request<Result>("post", "/role/update", { data });
+};
+
+export const deleteRole = (id: number) => {
+  return http.request<Result>("delete", `/role/${id}`);
+};

+ 132 - 0
haha-admin-web/src/api/shop.ts

@@ -0,0 +1,132 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const getShopList = (params: {
+  page?: number;
+  pageSize?: number;
+  name?: string;
+  city?: string;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/shops/list", { params });
+};
+
+export const getShopById = (id: number) => {
+  return http.request<Result>("get", `/shops/${id}`);
+};
+
+export const getShopStatistics = () => {
+  return http.request<Result>("get", "/shops/statistics");
+};
+
+export const getEnabledShops = () => {
+  return http.request<Result>("get", "/shops/enabled");
+};
+
+export const createShop = (data: any) => {
+  return http.request<Result>("post", "/shops", { data });
+};
+
+export const updateShop = (id: number, data: any) => {
+  return http.request<Result>("put", `/shops/${id}`, { data });
+};
+
+export const deleteShop = (id: number) => {
+  return http.request<Result>("delete", `/shops/${id}`);
+};
+
+export const toggleShopStatus = (id: number, data: { status: number }) => {
+  return http.request<Result>("put", `/shops/${id}/status`, { data });
+};
+
+export const getShopDevices = (id: number) => {
+  return http.request<Result>("get", `/shops/${id}/devices`);
+};
+
+export const linkDevice = (id: number, data: { deviceId: number }) => {
+  return http.request<Result>("post", `/shops/${id}/devices`, { data });
+};
+
+export const batchLinkDevices = (id: number, data: { deviceIds: number[] }) => {
+  return http.request<Result>("post", `/shops/${id}/devices/batch`, { data });
+};
+
+export const unlinkDevice = (shopId: number, deviceId: number) => {
+  return http.request<Result>("delete", `/shops/${shopId}/devices/${deviceId}`);
+};
+
+export const batchUnlinkDevices = (
+  id: number,
+  data: { deviceIds: number[] }
+) => {
+  return http.request<Result>("delete", `/shops/${id}/devices/batch`, { data });
+};
+
+export const getUnlinkedDevices = () => {
+  return http.request<Result>("get", "/shops/unlinked-devices");
+};
+
+export const getShopReplenishers = (id: number) => {
+  return http.request<Result>("get", `/shops/${id}/replenishers`);
+};
+
+export const addReplenisher = (id: number, data: { adminId: number }) => {
+  return http.request<Result>("post", `/shops/${id}/replenishers`, { data });
+};
+
+export const batchAddReplenishers = (
+  id: number,
+  data: { adminIds: number[] }
+) => {
+  return http.request<Result>("post", `/shops/${id}/replenishers/batch`, {
+    data
+  });
+};
+
+export const removeReplenisher = (shopId: number, adminId: number) => {
+  return http.request<Result>(
+    "delete",
+    `/shops/${shopId}/replenishers/${adminId}`
+  );
+};
+
+export const batchRemoveReplenishers = (
+  id: number,
+  data: { adminIds: number[] }
+) => {
+  return http.request<Result>("delete", `/shops/${id}/replenishers/batch`, {
+    data
+  });
+};
+
+export const updateReplenisherStatus = (
+  id: number,
+  data: { status: number }
+) => {
+  return http.request<Result>("put", `/shops/replenishers/${id}/status`, {
+    data
+  });
+};
+
+export const getAvailableUsers = (keyword?: string) => {
+  return http.request<Result>(
+    "get",
+    `/shops/available-users${keyword ? `?keyword=${keyword}` : ""}`
+  );
+};

+ 46 - 0
haha-admin-web/src/api/sync.ts

@@ -0,0 +1,46 @@
+import { http } from "@/utils/http";
+
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
+export const syncDevices = () => {
+  return http.request<Result>("post", "/sync/devices");
+};
+
+export const syncProducts = () => {
+  return http.request<Result>("post", "/sync/products");
+};
+
+export const getSyncRecords = (params: {
+  syncType?: string;
+  status?: number;
+  page?: number;
+  pageSize?: number;
+}) => {
+  return http.request<ResultTable>("get", "/sync/records", { params });
+};
+
+export const getSyncLogs = (recordId: number, params: {
+  page?: number;
+  pageSize?: number;
+}) => {
+  return http.request<ResultTable>("get", `/sync/logs/${recordId}`, { params });
+};
+
+export const getSyncStatistics = () => {
+  return http.request<Result>("get", "/sync/statistics");
+};

+ 46 - 58
haha-admin-web/src/api/user.ts

@@ -1,89 +1,77 @@
 import { http } from "@/utils/http";
 
+type Result = {
+  code: number;
+  message: string;
+  data?: any;
+};
+
+type ResultTable = {
+  code: number;
+  message: string;
+  data?: {
+    list: Array<any>;
+    total: number;
+    pageSize: number;
+    currentPage: number;
+  };
+};
+
 export type UserResult = {
-  success: boolean;
+  code: number;
+  message: string;
   data: {
-    /** 头像 */
     avatar: string;
-    /** 用户名 */
     username: string;
-    /** 昵称 */
     nickname: string;
-    /** 当前登录用户的角色 */
     roles: Array<string>;
-    /** 按钮级别权限 */
     permissions: Array<string>;
-    /** `token` */
-    accessToken: string;
-    /** 用于调用刷新`accessToken`的接口时所需的`token` */
-    refreshToken: string;
-    /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
-    expires: Date;
   };
 };
 
 export type RefreshTokenResult = {
-  success: boolean;
+  code: number;
+  message: string;
   data: {
-    /** `token` */
     accessToken: string;
-    /** 用于调用刷新`accessToken`的接口时所需的`token` */
-    refreshToken: string;
-    /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
-    expires: Date;
+    refreshToken?: string;
+    expires?: Date;
   };
 };
 
-export type UserInfo = {
-  /** 头像 */
-  avatar: string;
-  /** 用户名 */
-  username: string;
-  /** 昵称 */
-  nickname: string;
-  /** 邮箱 */
-  email: string;
-  /** 联系电话 */
-  phone: string;
-  /** 简介 */
-  description: string;
+export const getLogin = (data: { username: string; password: string }) => {
+  return http.request<UserResult>("post", "/login", { data });
 };
 
-export type UserInfoResult = {
-  success: boolean;
-  data: UserInfo;
+export const refreshTokenApi = (data: { refreshToken: string }) => {
+  return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
 };
 
-type ResultTable = {
-  success: boolean;
-  data?: {
-    /** 列表数据 */
-    list: Array<any>;
-    /** 总条目数 */
-    total?: number;
-    /** 每页显示条目个数 */
-    pageSize?: number;
-    /** 当前页数 */
-    currentPage?: number;
-  };
+export const getUserList = (params: {
+  page?: number;
+  pageSize?: number;
+  phone?: string;
+  nickname?: string;
+  status?: number;
+}) => {
+  return http.request<ResultTable>("get", "/users/list", { params });
 };
 
-/** 登录 */
-export const getLogin = (data?: object) => {
-  return http.request<UserResult>("post", "/login", { data });
+export const getUserById = (id: number) => {
+  return http.request<Result>("get", `/users/${id}`);
 };
 
-/** 刷新`token` */
-export const refreshTokenApi = (data?: object) => {
-  return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
+export const getUserStatistics = () => {
+  return http.request<Result>("get", "/users/statistics");
 };
 
-/** 账户设置-个人信息 */
-export const getMine = (data?: object) => {
-  return http.request<UserInfoResult>("get", "/mine", { data });
+export const updateUserStatus = (id: number, data: { status: number }) => {
+  return http.request<Result>("put", `/users/${id}/status`, { data });
 };
 
-/** 账户设置-个人安全日志 */
-export const getMineLogs = (data?: object) => {
-  return http.request<ResultTable>("get", "/mine-logs", { data });
+export const updateUserCreditScore = (
+  id: number,
+  data: { creditScore: number }
+) => {
+  return http.request<Result>("put", `/users/${id}/credit`, { data });
 };

+ 59 - 0
haha-admin-web/src/components/DictSelect/index.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { computed, watch } from "vue";
+import { useDictStore } from "@/store/modules/dict";
+import type { DictData } from "@/api/dict";
+
+const props = defineProps<{
+  dictCode: string;
+  modelValue?: string | number;
+  placeholder?: string;
+  disabled?: boolean;
+  clearable?: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: "update:modelValue", value: string | number): void;
+  (e: "change", value: string | number): void;
+}>();
+
+const dictStore = useDictStore();
+
+const dictList = computed<DictData[]>(() => {
+  return dictStore.getDictList(props.dictCode);
+});
+
+const currentValue = computed({
+  get: () => props.modelValue,
+  set: (val) => {
+    emit("update:modelValue", val as string | number);
+    emit("change", val as string | number);
+  }
+});
+
+watch(
+  () => props.dictCode,
+  () => {
+    if (!dictStore.loaded) {
+      dictStore.loadDictData();
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<template>
+  <el-select
+    v-model="currentValue"
+    :placeholder="placeholder || '请选择'"
+    :disabled="disabled"
+    :clearable="clearable"
+    style="width: 100%"
+  >
+    <el-option
+      v-for="item in dictList"
+      :key="item.itemValue"
+      :label="item.itemLabel"
+      :value="item.itemValue"
+    />
+  </el-select>
+</template>

+ 39 - 0
haha-admin-web/src/components/DictTag/index.vue

@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import { computed, watch } from "vue";
+import { useDictStore } from "@/store/modules/dict";
+
+const props = defineProps<{
+  dictCode: string;
+  value?: string | number;
+}>();
+
+const dictStore = useDictStore();
+
+const label = computed(() => {
+  if (!props.value) return "";
+  return dictStore.getDictLabel(props.dictCode, props.value);
+});
+
+const tagType = computed(() => {
+  if (!props.value) return "";
+  const color = dictStore.getDictColor(props.dictCode, props.value);
+  return color || "info";
+});
+
+watch(
+  () => props.dictCode,
+  () => {
+    if (!dictStore.loaded) {
+      dictStore.loadDictData();
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<template>
+  <el-tag v-if="tagType" :type="tagType as any" size="small">
+    {{ label }}
+  </el-tag>
+  <span v-else>{{ label || value }}</span>
+</template>

+ 0 - 7
haha-admin-web/src/components/ReAnimateSelector/index.ts

@@ -1,7 +0,0 @@
-import { withInstall } from "@pureadmin/utils";
-import reAnimateSelector from "./src/index.vue";
-
-/** [animate.css](https://animate.style/) 选择器组件 */
-export const ReAnimateSelector = withInstall(reAnimateSelector);
-
-export default ReAnimateSelector;

+ 0 - 114
haha-admin-web/src/components/ReAnimateSelector/src/animate.ts

@@ -1,114 +0,0 @@
-export const animates = [
-  /* Attention seekers  */
-  "bounce",
-  "flash",
-  "pulse",
-  "rubberBand",
-  "shakeX",
-  "headShake",
-  "swing",
-  "tada",
-  "wobble",
-  "jello",
-  "heartBeat",
-  /* Back entrances */
-  "backInDown",
-  "backInLeft",
-  "backInRight",
-  "backInUp",
-  /* Back exits */
-  "backOutDown",
-  "backOutLeft",
-  "backOutRight",
-  "backOutUp",
-  /* Bouncing entrances  */
-  "bounceIn",
-  "bounceInDown",
-  "bounceInLeft",
-  "bounceInRight",
-  "bounceInUp",
-  /* Bouncing exits  */
-  "bounceOut",
-  "bounceOutDown",
-  "bounceOutLeft",
-  "bounceOutRight",
-  "bounceOutUp",
-  /* Fading entrances  */
-  "fadeIn",
-  "fadeInDown",
-  "fadeInDownBig",
-  "fadeInLeft",
-  "fadeInLeftBig",
-  "fadeInRight",
-  "fadeInRightBig",
-  "fadeInUp",
-  "fadeInUpBig",
-  "fadeInTopLeft",
-  "fadeInTopRight",
-  "fadeInBottomLeft",
-  "fadeInBottomRight",
-  /* Fading exits */
-  "fadeOut",
-  "fadeOutDown",
-  "fadeOutDownBig",
-  "fadeOutLeft",
-  "fadeOutLeftBig",
-  "fadeOutRight",
-  "fadeOutRightBig",
-  "fadeOutUp",
-  "fadeOutUpBig",
-  "fadeOutTopLeft",
-  "fadeOutTopRight",
-  "fadeOutBottomRight",
-  "fadeOutBottomLeft",
-  /* Flippers */
-  "flip",
-  "flipInX",
-  "flipInY",
-  "flipOutX",
-  "flipOutY",
-  /* Lightspeed */
-  "lightSpeedInRight",
-  "lightSpeedInLeft",
-  "lightSpeedOutRight",
-  "lightSpeedOutLeft",
-  /* Rotating entrances */
-  "rotateIn",
-  "rotateInDownLeft",
-  "rotateInDownRight",
-  "rotateInUpLeft",
-  "rotateInUpRight",
-  /* Rotating exits */
-  "rotateOut",
-  "rotateOutDownLeft",
-  "rotateOutDownRight",
-  "rotateOutUpLeft",
-  "rotateOutUpRight",
-  /* Specials */
-  "hinge",
-  "jackInTheBox",
-  "rollIn",
-  "rollOut",
-  /* Zooming entrances */
-  "zoomIn",
-  "zoomInDown",
-  "zoomInLeft",
-  "zoomInRight",
-  "zoomInUp",
-  /* Zooming exits */
-  "zoomOut",
-  "zoomOutDown",
-  "zoomOutLeft",
-  "zoomOutRight",
-  "zoomOutUp",
-  /* Sliding entrances */
-  "slideInDown",
-  "slideInLeft",
-  "slideInRight",
-  "slideInUp",
-  /* Sliding exits */
-  "slideOutDown",
-  "slideOutLeft",
-  "slideOutRight",
-  "slideOutUp"
-];

+ 0 - 136
haha-admin-web/src/components/ReAnimateSelector/src/index.vue

@@ -1,136 +0,0 @@
-<script setup lang="ts">
-import { ref, computed } from "vue";
-import { animates } from "./animate";
-import { cloneDeep } from "@pureadmin/utils";
-
-defineOptions({
-  name: "ReAnimateSelector"
-});
-
-defineProps({
-  placeholder: {
-    type: String,
-    default: "请选择动画"
-  }
-});
-
-const inputValue = defineModel({ type: String });
-
-const searchVal = ref();
-const animatesList = ref(animates);
-const copyAnimatesList = cloneDeep(animatesList);
-
-const animateClass = computed(() => {
-  return [
-    "mt-1",
-    "flex",
-    "border",
-    "w-[130px]",
-    "h-[100px]",
-    "items-center",
-    "cursor-pointer",
-    "transition-all",
-    "justify-center",
-    "border-[#e5e7eb]",
-    "hover:text-primary",
-    "hover:duration-[700ms]"
-  ];
-});
-
-const animateStyle = computed(
-  () => (i: string) =>
-    inputValue.value === i
-      ? {
-          borderColor: "var(--el-color-primary)",
-          color: "var(--el-color-primary)"
-        }
-      : ""
-);
-
-function onChangeIcon(animate: string) {
-  inputValue.value = animate;
-}
-
-function onClear() {
-  inputValue.value = "";
-}
-
-function filterMethod(value: any) {
-  searchVal.value = value;
-  animatesList.value = copyAnimatesList.value.filter((i: string | any[]) =>
-    i.includes(value)
-  );
-}
-
-const animateMap = ref({});
-function onMouseEnter(index: string | number) {
-  animateMap.value[index] = animateMap.value[index]?.loading
-    ? Object.assign({}, animateMap.value[index], {
-        loading: false
-      })
-    : Object.assign({}, animateMap.value[index], {
-        loading: true
-      });
-}
-function onMouseleave() {
-  animateMap.value = {};
-}
-</script>
-
-<template>
-  <el-select
-    clearable
-    filterable
-    :placeholder="placeholder"
-    popper-class="pure-animate-popper"
-    :model-value="inputValue"
-    :filter-method="filterMethod"
-    @clear="onClear"
-  >
-    <template #empty>
-      <div class="w-[280px]">
-        <el-scrollbar
-          noresize
-          height="212px"
-          :view-style="{ overflow: 'hidden' }"
-          class="border-t border-[#e5e7eb]"
-        >
-          <ul class="flex flex-wrap justify-around mb-1!">
-            <li
-              v-for="(animate, index) in animatesList"
-              :key="index"
-              :class="animateClass"
-              :style="animateStyle(animate)"
-              @mouseenter.prevent="onMouseEnter(index)"
-              @mouseleave.prevent="onMouseleave"
-              @click="onChangeIcon(animate)"
-            >
-              <h4
-                :class="[
-                  `animate__animated animate__${
-                    animateMap[index]?.loading
-                      ? animate + ' animate__infinite'
-                      : ''
-                  } `
-                ]"
-              >
-                {{ animate }}
-              </h4>
-            </li>
-          </ul>
-          <el-empty
-            v-show="animatesList.length === 0"
-            :description="`${searchVal} 动画不存在`"
-            :image-size="60"
-          />
-        </el-scrollbar>
-      </div>
-    </template>
-  </el-select>
-</template>
-
-<style>
-.pure-animate-popper {
-  min-width: 0 !important;
-}
-</style>

+ 0 - 7
haha-admin-web/src/components/ReBarcode/index.ts

@@ -1,7 +0,0 @@
-import { withInstall } from "@pureadmin/utils";
-import reBarcode from "./src/index.vue";
-
-/** 条形码组件 */
-export const ReBarcode = withInstall(reBarcode);
-
-export default ReBarcode;

+ 0 - 42
haha-admin-web/src/components/ReBarcode/src/index.vue

@@ -1,42 +0,0 @@
-<script setup lang="ts">
-import JsBarcode from "jsbarcode";
-import { ref, onMounted } from "vue";
-
-defineOptions({
-  name: "ReBarcode"
-});
-
-const props = defineProps({
-  tag: {
-    type: String,
-    default: "canvas"
-  },
-  text: {
-    type: String,
-    default: null
-  },
-  // 完整配置 https://github.com/lindell/JsBarcode/wiki/Options
-  options: {
-    type: Object,
-    default() {
-      return {};
-    }
-  },
-  // type 相当于 options.format,如果 type 和 options.format 同时存在,type 值优先;
-  type: {
-    type: String,
-    default: "CODE128"
-  }
-});
-
-const wrapEl = ref(null);
-
-onMounted(() => {
-  const opt = { ...props.options, format: props.type };
-  JsBarcode(wrapEl.value, props.text, opt);
-});
-</script>
-
-<template>
-  <component :is="tag" ref="wrapEl" />
-</template>

+ 0 - 2
haha-admin-web/src/components/ReCountTo/README.md

@@ -1,2 +0,0 @@
-normal 普通数字动画组件  
-rebound 回弹式数字动画组件

+ 3 - 10
haha-admin-web/src/components/ReCountTo/index.ts

@@ -1,11 +1,4 @@
-import reNormalCountTo from "./src/normal";
-import reboundCountTo from "./src/rebound";
-import { withInstall } from "@pureadmin/utils";
+import ReNormalCountTo from "./index.tsx";
 
-/** 普通数字动画组件 */
-const ReNormalCountTo = withInstall(reNormalCountTo);
-
-/** 回弹式数字动画组件 */
-const ReboundCountTo = withInstall(reboundCountTo);
-
-export { ReNormalCountTo, ReboundCountTo };
+export { ReNormalCountTo };
+export default ReNormalCountTo;

+ 112 - 0
haha-admin-web/src/components/ReCountTo/index.tsx

@@ -0,0 +1,112 @@
+import { defineComponent, ref, watch, onMounted, computed } from "vue";
+
+const props = {
+  startVal: {
+    type: Number,
+    default: 0
+  },
+  endVal: {
+    type: Number,
+    default: 2021
+  },
+  duration: {
+    type: Number,
+    default: 3000
+  },
+  autoplay: {
+    type: Boolean,
+    default: true
+  },
+  decimals: {
+    type: Number,
+    default: 0
+  },
+  separator: {
+    type: String,
+    default: ","
+  },
+  prefix: {
+    type: String,
+    default: ""
+  },
+  suffix: {
+    type: String,
+    default: ""
+  },
+  fontSize: {
+    type: String,
+    default: "16px"
+  },
+  color: {
+    type: String,
+    default: ""
+  }
+};
+
+export const ReNormalCountTo = defineComponent({
+  name: "ReNormalCountTo",
+  props,
+  emits: ["mounted", "callback"],
+  setup(props, { emit }) {
+    const state = ref(props.startVal);
+    const startTime = ref(0);
+    const rafId = ref(0);
+
+    const formatNumber = computed(() => {
+      const val = state.value.toFixed(props.decimals);
+      const parts = val.split(".");
+      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, props.separator);
+      return props.prefix + parts.join(".") + props.suffix;
+    });
+
+    function count(timestamp: number) {
+      if (!startTime.value) startTime.value = timestamp;
+      const progress = timestamp - startTime.value;
+      const remaining = props.duration - progress;
+
+      if (remaining < 0) {
+        state.value = props.endVal;
+        emit("callback");
+        return;
+      }
+
+      const rate = progress / props.duration;
+      state.value = props.startVal + (props.endVal - props.startVal) * rate;
+      rafId.value = requestAnimationFrame(count);
+    }
+
+    function start() {
+      startTime.value = 0;
+      rafId.value = requestAnimationFrame(count);
+    }
+
+    onMounted(() => {
+      if (props.autoplay) {
+        start();
+      }
+      emit("mounted");
+    });
+
+    watch(
+      () => props.endVal,
+      () => {
+        if (props.autoplay) {
+          start();
+        }
+      }
+    );
+
+    return () => (
+      <span
+        style={{
+          fontSize: props.fontSize,
+          color: props.color
+        }}
+      >
+        {formatNumber.value}
+      </span>
+    );
+  }
+});
+
+export default ReNormalCountTo;

+ 0 - 179
haha-admin-web/src/components/ReCountTo/src/normal/index.tsx

@@ -1,179 +0,0 @@
-import {
-  watch,
-  unref,
-  computed,
-  reactive,
-  onMounted,
-  defineComponent
-} from "vue";
-import { countToProps } from "./props";
-import { isNumber } from "@pureadmin/utils";
-
-export default defineComponent({
-  name: "ReNormalCountTo",
-  props: countToProps,
-  emits: ["mounted", "callback"],
-  setup(props, { emit }) {
-    const state = reactive<{
-      localStartVal: number;
-      printVal: number | null;
-      displayValue: string;
-      paused: boolean;
-      localDuration: number | null;
-      startTime: number | null;
-      timestamp: number | null;
-      rAF: any;
-      remaining: number | null;
-      color: string;
-      fontSize: string;
-    }>({
-      localStartVal: props.startVal,
-      displayValue: formatNumber(props.startVal),
-      printVal: null,
-      paused: false,
-      localDuration: props.duration,
-      startTime: null,
-      timestamp: null,
-      remaining: null,
-      rAF: null,
-      color: null,
-      fontSize: "16px"
-    });
-
-    const getCountDown = computed(() => {
-      return props.startVal > props.endVal;
-    });
-
-    watch([() => props.startVal, () => props.endVal], () => {
-      if (props.autoplay) {
-        start();
-      }
-    });
-
-    function start() {
-      const { startVal, duration, color, fontSize } = props;
-      state.localStartVal = startVal;
-      state.startTime = null;
-      state.localDuration = duration;
-      state.paused = false;
-      state.color = color;
-      state.fontSize = fontSize;
-      state.rAF = requestAnimationFrame(count);
-    }
-
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    function pauseResume() {
-      if (state.paused) {
-        resume();
-        state.paused = false;
-      } else {
-        pause();
-        state.paused = true;
-      }
-    }
-
-    function pause() {
-      cancelAnimationFrame(state.rAF);
-    }
-
-    function resume() {
-      state.startTime = null;
-      state.localDuration = +(state.remaining as number);
-      state.localStartVal = +(state.printVal as number);
-      requestAnimationFrame(count);
-    }
-
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    function reset() {
-      state.startTime = null;
-      cancelAnimationFrame(state.rAF);
-      state.displayValue = formatNumber(props.startVal);
-    }
-
-    function count(timestamp: number) {
-      const { useEasing, easingFn, endVal } = props;
-      if (!state.startTime) state.startTime = timestamp;
-      state.timestamp = timestamp;
-      const progress = timestamp - state.startTime;
-      state.remaining = (state.localDuration as number) - progress;
-      if (useEasing) {
-        if (unref(getCountDown)) {
-          state.printVal =
-            state.localStartVal -
-            easingFn(
-              progress,
-              0,
-              state.localStartVal - endVal,
-              state.localDuration as number
-            );
-        } else {
-          state.printVal = easingFn(
-            progress,
-            state.localStartVal,
-            endVal - state.localStartVal,
-            state.localDuration as number
-          );
-        }
-      } else {
-        if (unref(getCountDown)) {
-          state.printVal =
-            state.localStartVal -
-            (state.localStartVal - endVal) *
-              (progress / (state.localDuration as number));
-        } else {
-          state.printVal =
-            state.localStartVal +
-            (endVal - state.localStartVal) *
-              (progress / (state.localDuration as number));
-        }
-      }
-      if (unref(getCountDown)) {
-        state.printVal = state.printVal < endVal ? endVal : state.printVal;
-      } else {
-        state.printVal = state.printVal > endVal ? endVal : state.printVal;
-      }
-      state.displayValue = formatNumber(state.printVal);
-      if (progress < (state.localDuration as number)) {
-        state.rAF = requestAnimationFrame(count);
-      } else {
-        emit("callback");
-      }
-    }
-
-    function formatNumber(num: number | string) {
-      const { decimals, decimal, separator, suffix, prefix } = props;
-      num = Number(num).toFixed(decimals);
-      num += "";
-      const x = num.split(".");
-      let x1 = x[0];
-      const x2 = x.length > 1 ? decimal + x[1] : "";
-      const rgx = /(\d+)(\d{3})/;
-      if (separator && !isNumber(separator)) {
-        while (rgx.test(x1)) {
-          x1 = x1.replace(rgx, "$1" + separator + "$2");
-        }
-      }
-      return prefix + x1 + x2 + suffix;
-    }
-
-    onMounted(() => {
-      if (props.autoplay) {
-        start();
-      }
-      emit("mounted");
-    });
-
-    return () => (
-      <>
-        <span
-          style={{
-            color: props.color,
-            fontSize: props.fontSize
-          }}
-        >
-          {state.displayValue}
-        </span>
-      </>
-    );
-  }
-});

+ 0 - 32
haha-admin-web/src/components/ReCountTo/src/normal/props.ts

@@ -1,32 +0,0 @@
-import type { PropType } from "vue";
-import propTypes from "@/utils/propTypes";
-
-export const countToProps = {
-  startVal: propTypes.number.def(0),
-  endVal: propTypes.number.def(2020),
-  duration: propTypes.number.def(1300),
-  autoplay: propTypes.bool.def(true),
-  decimals: {
-    type: Number as PropType<number>,
-    required: false,
-    default: 0,
-    validator(value: number) {
-      return value >= 0;
-    }
-  },
-  color: propTypes.string.def(),
-  fontSize: propTypes.string.def(),
-  decimal: propTypes.string.def("."),
-  separator: propTypes.string.def(","),
-  prefix: propTypes.string.def(""),
-  suffix: propTypes.string.def(""),
-  useEasing: propTypes.bool.def(true),
-  easingFn: {
-    type: Function as PropType<
-      (t: number, b: number, c: number, d: number) => number
-    >,
-    default(t: number, b: number, c: number, d: number) {
-      return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
-    }
-  }
-};

+ 0 - 72
haha-admin-web/src/components/ReCountTo/src/rebound/index.tsx

@@ -1,72 +0,0 @@
-import "./rebound.css";
-import {
-  ref,
-  unref,
-  onBeforeMount,
-  defineComponent,
-  onBeforeUnmount
-} from "vue";
-import { reboundProps } from "./props";
-
-export default defineComponent({
-  name: "ReboundCountTo",
-  props: reboundProps,
-  setup(props) {
-    const ulRef = ref();
-    const timer = ref(null);
-
-    onBeforeMount(() => {
-      const ua = navigator.userAgent.toLowerCase();
-      const testUA = regexp => regexp.test(ua);
-      const isSafari = testUA(/safari/g) && !testUA(/chrome/g);
-
-      // Safari浏览器的兼容代码
-      isSafari &&
-        (timer.value = setTimeout(() => {
-          ulRef.value.setAttribute(
-            "style",
-            `
-        animation: none;
-        transform: translateY(calc(var(--i) * -9.09%))
-      `
-          );
-        }, props.delay * 1000));
-    });
-
-    onBeforeUnmount(() => {
-      clearTimeout(unref(timer));
-    });
-
-    return () => (
-      <>
-        <div
-          class="scroll-num"
-          style={{ "--i": props.i, "--delay": props.delay }}
-        >
-          <ul ref="ulRef" style={{ fontSize: "32px" }}>
-            <li>0</li>
-            <li>1</li>
-            <li>2</li>
-            <li>3</li>
-            <li>4</li>
-            <li>5</li>
-            <li>6</li>
-            <li>7</li>
-            <li>8</li>
-            <li>9</li>
-            <li>0</li>
-          </ul>
-
-          <svg width="0" height="0">
-            <filter id="blur">
-              <feGaussianBlur
-                in="SourceGraphic"
-                stdDeviation={`0 ${props.blur}`}
-              />
-            </filter>
-          </svg>
-        </div>
-      </>
-    );
-  }
-});

+ 0 - 15
haha-admin-web/src/components/ReCountTo/src/rebound/props.ts

@@ -1,15 +0,0 @@
-import type { PropType } from "vue";
-import propTypes from "@/utils/propTypes";
-
-export const reboundProps = {
-  delay: propTypes.number.def(1),
-  blur: propTypes.number.def(2),
-  i: {
-    type: Number as PropType<number>,
-    required: false,
-    default: 0,
-    validator(value: number) {
-      return value < 10 && value >= 0 && Number.isInteger(value);
-    }
-  }
-};

+ 0 - 77
haha-admin-web/src/components/ReCountTo/src/rebound/rebound.css

@@ -1,77 +0,0 @@
-.scroll-num {
-  width: var(--width, 20px);
-  height: var(--height, calc(var(--width, 20px) * 1.8));
-  color: var(--color, #333);
-  font-size: var(--height, calc(var(--width, 20px) * 1.1));
-  line-height: var(--height, calc(var(--width, 20px) * 1.8));
-  text-align: center;
-  overflow: hidden;
-  animation: enhance-bounce-in-down 1s calc(var(--delay) * 1s) forwards;
-}
-
-ul {
-  animation:
-    move 0.3s linear infinite,
-    bounce-in-down 1s calc(var(--delay) * 1s) forwards;
-}
-
-@keyframes move {
-  from {
-    transform: translateY(-90%);
-    filter: url(#blur);
-  }
-
-  to {
-    transform: translateY(1%);
-    filter: url(#blur);
-  }
-}
-
-@keyframes bounce-in-down {
-  from {
-    transform: translateY(calc(var(--i) * -9.09% - 7%));
-    filter: none;
-  }
-
-  25% {
-    transform: translateY(calc(var(--i) * -9.09% + 3%));
-  }
-
-  50% {
-    transform: translateY(calc(var(--i) * -9.09% - 1%));
-  }
-
-  70% {
-    transform: translateY(calc(var(--i) * -9.09% + 0.6%));
-  }
-
-  85% {
-    transform: translateY(calc(var(--i) * -9.09% - 0.3%));
-  }
-
-  to {
-    transform: translateY(calc(var(--i) * -9.09%));
-  }
-}
-
-@keyframes enhance-bounce-in-down {
-  25% {
-    transform: translateY(8%);
-  }
-
-  50% {
-    transform: translateY(-4%);
-  }
-
-  70% {
-    transform: translateY(2%);
-  }
-
-  85% {
-    transform: translateY(-1%);
-  }
-
-  to {
-    transform: translateY(0);
-  }
-}

+ 0 - 39
haha-admin-web/src/components/ReFlicker/index.css

@@ -1,39 +0,0 @@
-.point {
-  width: var(--point-width);
-  height: var(--point-height);
-  background: var(--point-background);
-  position: relative;
-  border-radius: var(--point-border-radius);
-}
-
-.point-flicker:after {
-  background: var(--point-background);
-}
-
-.point-flicker:before,
-.point-flicker:after {
-  content: "";
-  width: 100%;
-  height: 100%;
-  top: 0;
-  left: 0;
-  position: absolute;
-  border-radius: var(--point-border-radius);
-  animation: flicker 1.2s ease-out infinite;
-}
-
-@keyframes flicker {
-  0% {
-    transform: scale(0.5);
-    opacity: 1;
-  }
-
-  30% {
-    opacity: 1;
-  }
-
-  100% {
-    transform: scale(var(--point-scale));
-    opacity: 0;
-  }
-}

+ 3 - 43
haha-admin-web/src/components/ReFlicker/index.ts

@@ -1,44 +1,4 @@
-import "./index.css";
-import { type Component, h, defineComponent } from "vue";
+import { useRenderFlicker } from "./index.tsx";
 
-export interface attrsType {
-  width?: string;
-  height?: string;
-  borderRadius?: number | string;
-  background?: string;
-  scale?: number | string;
-}
-
-/**
- * 圆点、方形闪烁动画组件
- * @param width 可选 string 宽
- * @param height 可选 string 高
- * @param borderRadius 可选 number | string 传0为方形、传50%或者不传为圆形
- * @param background 可选 string 闪烁颜色
- * @param scale 可选 number | string 闪烁范围,默认2,值越大闪烁范围越大
- * @returns Component
- */
-export function useRenderFlicker(attrs?: attrsType): Component {
-  return defineComponent({
-    name: "ReFlicker",
-    render() {
-      return h(
-        "div",
-        {
-          class: "point point-flicker",
-          style: {
-            "--point-width": attrs?.width ?? "12px",
-            "--point-height": attrs?.height ?? "12px",
-            "--point-background":
-              attrs?.background ?? "var(--el-color-primary)",
-            "--point-border-radius": attrs?.borderRadius ?? "50%",
-            "--point-scale": attrs?.scale ?? "2"
-          }
-        },
-        {
-          default: () => []
-        }
-      );
-    }
-  });
-}
+export { useRenderFlicker };
+export default useRenderFlicker;

+ 46 - 0
haha-admin-web/src/components/ReFlicker/index.tsx

@@ -0,0 +1,46 @@
+import { defineComponent, ref, onMounted, onUnmounted } from "vue";
+
+const props = {
+  background: {
+    type: String,
+    default: "var(--el-color-primary)"
+  },
+  width: {
+    type: String,
+    default: "10px"
+  },
+  height: {
+    type: String,
+    default: "10px"
+  },
+  borderRadius: {
+    type: String,
+    default: "50%"
+  },
+  duration: {
+    type: Number,
+    default: 1000
+  }
+};
+
+export const useRenderFlicker = (options?: Partial<typeof props>) => {
+  const config = { ...props, ...options };
+  return defineComponent({
+    name: "ReFlicker",
+    setup() {
+      return () => (
+        <span
+          class="flicker"
+          style={{
+            background: config.background,
+            width: config.width,
+            height: config.height,
+            borderRadius: config.borderRadius
+          }}
+        />
+      );
+    }
+  });
+};
+
+export default useRenderFlicker;

+ 0 - 7
haha-admin-web/src/components/ReFlop/index.ts

@@ -1,7 +0,0 @@
-import reFlop from "./src/index.vue";
-import { withInstall } from "@pureadmin/utils";
-
-/** 时间翻牌组件 */
-export const ReFlop = withInstall(reFlop);
-
-export default ReFlop;

+ 0 - 184
haha-admin-web/src/components/ReFlop/src/filpper.css

@@ -1,184 +0,0 @@
-.m-flipper {
-  display: inline-block;
-  position: relative;
-  width: 60px;
-  height: 100px;
-  line-height: 100px;
-  border: solid 1px #000;
-  border-radius: 10px;
-  background: #fff;
-  font-size: 66px;
-  color: #fff;
-  box-shadow: 0 0 6px rgb(0 0 0 / 50%);
-  text-align: center;
-  font-family: "Helvetica Neue";
-}
-
-.m-flipper .digital::before,
-.m-flipper .digital::after {
-  content: "";
-  position: absolute;
-  left: 0;
-  right: 0;
-  background: #000;
-  overflow: hidden;
-  box-sizing: border-box;
-}
-
-.m-flipper .digital::before {
-  top: 0;
-  bottom: 50%;
-  border-radius: 10px 10px 0 0;
-  border-bottom: solid 1px #666;
-}
-
-.m-flipper .digital::after {
-  top: 50%;
-  bottom: 0;
-  border-radius: 0 0 10px 10px;
-  line-height: 0;
-}
-
-/* 向下翻 */
-.m-flipper.down .front::before {
-  z-index: 3;
-}
-
-.m-flipper.down .back::after {
-  z-index: 2;
-  transform-origin: 50% 0%;
-  transform: perspective(160px) rotateX(180deg);
-}
-
-.m-flipper.down .front::after,
-.m-flipper.down .back::before {
-  z-index: 1;
-}
-
-.m-flipper.down.go .front::before {
-  transform-origin: 50% 100%;
-  animation: frontFlipDown 0.6s ease-in-out both;
-  box-shadow: 0 -2px 6px rgb(255 255 255 / 30%);
-  backface-visibility: hidden;
-}
-
-.m-flipper.down.go .back::after {
-  animation: backFlipDown 0.6s ease-in-out both;
-}
-
-/* 向上翻 */
-.m-flipper.up .front::after {
-  z-index: 3;
-}
-
-.m-flipper.up .back::before {
-  z-index: 2;
-  transform-origin: 50% 100%;
-  transform: perspective(160px) rotateX(-180deg);
-}
-
-.m-flipper.up .front::before,
-.m-flipper.up .back::after {
-  z-index: 1;
-}
-
-.m-flipper.up.go .front::after {
-  transform-origin: 50% 0;
-  animation: frontFlipUp 0.6s ease-in-out both;
-  box-shadow: 0 2px 6px rgb(255 255 255 / 30%);
-  backface-visibility: hidden;
-}
-
-.m-flipper.up.go .back::before {
-  animation: backFlipUp 0.6s ease-in-out both;
-}
-
-@keyframes frontFlipDown {
-  0% {
-    transform: perspective(160px) rotateX(0deg);
-  }
-
-  100% {
-    transform: perspective(160px) rotateX(-180deg);
-  }
-}
-
-@keyframes backFlipDown {
-  0% {
-    transform: perspective(160px) rotateX(180deg);
-  }
-
-  100% {
-    transform: perspective(160px) rotateX(0deg);
-  }
-}
-
-@keyframes frontFlipUp {
-  0% {
-    transform: perspective(160px) rotateX(0deg);
-  }
-
-  100% {
-    transform: perspective(160px) rotateX(180deg);
-  }
-}
-
-@keyframes backFlipUp {
-  0% {
-    transform: perspective(160px) rotateX(-180deg);
-  }
-
-  100% {
-    transform: perspective(160px) rotateX(0deg);
-  }
-}
-
-.m-flipper .number0::before,
-.m-flipper .number0::after {
-  content: "0";
-}
-
-.m-flipper .number1::before,
-.m-flipper .number1::after {
-  content: "1";
-}
-
-.m-flipper .number2::before,
-.m-flipper .number2::after {
-  content: "2";
-}
-
-.m-flipper .number3::before,
-.m-flipper .number3::after {
-  content: "3";
-}
-
-.m-flipper .number4::before,
-.m-flipper .number4::after {
-  content: "4";
-}
-
-.m-flipper .number5::before,
-.m-flipper .number5::after {
-  content: "5";
-}
-
-.m-flipper .number6::before,
-.m-flipper .number6::after {
-  content: "6";
-}
-
-.m-flipper .number7::before,
-.m-flipper .number7::after {
-  content: "7";
-}
-
-.m-flipper .number8::before,
-.m-flipper .number8::after {
-  content: "8";
-}
-
-.m-flipper .number9::before,
-.m-flipper .number9::after {
-  content: "9";
-}

+ 0 - 92
haha-admin-web/src/components/ReFlop/src/filpper.tsx

@@ -1,92 +0,0 @@
-import "./filpper.css";
-import propTypes from "@/utils/propTypes";
-import { defineComponent, ref } from "vue";
-
-const props = {
-  // front paper text
-  // 前牌文字
-  frontText: propTypes.number.def(0),
-  // back paper text
-  // 后牌文字
-  backText: propTypes.number.def(1),
-  // flipping duration, please be consistent with the CSS animation-duration value.
-  // 翻牌动画时间,与CSS中设置的animation-duration保持一致
-  duration: propTypes.number.def(600)
-};
-
-export default defineComponent({
-  name: "ReFlop",
-  props,
-  setup(props) {
-    const { frontText, backText, duration } = props;
-    const isFlipping = ref(false);
-    const flipType = ref("down");
-    const frontTextFromData = ref(frontText);
-    const backTextFromData = ref(backText);
-
-    const textClass = (number: number) => {
-      return "number" + number;
-    };
-
-    const flip = (type: string, front: number, back: number) => {
-      // 如果处于翻转中,则不执行
-      if (isFlipping.value) return false;
-      frontTextFromData.value = front;
-      backTextFromData.value = back;
-      // 根据传递过来的type设置翻转方向
-      flipType.value = type;
-      // 设置翻转状态为true
-      isFlipping.value = true;
-
-      setTimeout(() => {
-        // 设置翻转状态为false
-        isFlipping.value = false;
-        frontTextFromData.value = back;
-      }, duration);
-    };
-
-    // 下翻牌
-    const flipDown = (front: any, back: any): void => {
-      flip("down", front, back);
-    };
-
-    // 上翻牌
-    const flipUp = (front: any, back: any): void => {
-      flip("up", front, back);
-    };
-
-    // 设置前牌文字
-    function setFront(text: number): void {
-      frontTextFromData.value = text;
-    }
-
-    // 设置后牌文字
-    const setBack = (text: number): void => {
-      backTextFromData.value = text;
-    };
-
-    return {
-      flipType,
-      isFlipping,
-      frontTextFromData,
-      backTextFromData,
-      textClass,
-      flipDown,
-      flipUp,
-      setFront,
-      setBack
-    };
-  },
-
-  render() {
-    const main = `m-flipper ${this.flipType} ${this.isFlipping ? "go" : ""}`;
-    const front = `digital front ${this.textClass(this.frontTextFromData)}`;
-    const back = `digital back ${this.textClass(this.backTextFromData)}`;
-    return (
-      <div class={main}>
-        <div class={front} />
-        <div class={back} />
-      </div>
-    );
-  }
-});

+ 0 - 135
haha-admin-web/src/components/ReFlop/src/index.vue

@@ -1,135 +0,0 @@
-<script setup lang="ts">
-import flippers from "./filpper";
-import { ref, unref, nextTick, onUnmounted } from "vue";
-
-defineOptions({
-  name: "ReFlop"
-});
-
-const timer = ref(null);
-const flipObjs = ref([]);
-
-const flipperHour1 = ref();
-const flipperHour2 = ref();
-const flipperMinute1 = ref();
-const flipperMinute2 = ref();
-const flipperSecond1 = ref();
-const flipperSecond2 = ref();
-
-// 初始化数字
-const init = () => {
-  const now = new Date();
-  const nowTimeStr = formatDate(new Date(now.getTime()), "hhiiss");
-  for (let i = 0; i < flipObjs.value.length; i++) {
-    flipObjs?.value[i]?.setFront(nowTimeStr[i]);
-  }
-};
-
-// 开始计时
-const run = () => {
-  timer.value = setInterval(() => {
-    // 获取当前时间
-    const now = new Date();
-    const nowTimeStr = formatDate(new Date(now.getTime() - 1000), "hhiiss");
-    const nextTimeStr = formatDate(now, "hhiiss");
-    for (let i = 0; i < flipObjs.value.length; i++) {
-      if (nowTimeStr[i] === nextTimeStr[i]) {
-        continue;
-      }
-      flipObjs?.value[i]?.flipDown(nowTimeStr[i], nextTimeStr[i]);
-    }
-  }, 1000);
-};
-
-// 正则格式化日期
-const formatDate = (date: Date, dateFormat: string) => {
-  /* 单独格式化年份,根据y的字符数量输出年份
-     * 例如:yyyy => 2019
-            yy => 19
-            y => 9
-     */
-  if (/(y+)/.test(dateFormat)) {
-    dateFormat = dateFormat.replace(
-      RegExp.$1,
-      (date.getFullYear() + "").substr(4 - RegExp.$1.length)
-    );
-  }
-  // 格式化月、日、时、分、秒
-  const o = {
-    "m+": date.getMonth() + 1,
-    "d+": date.getDate(),
-    "h+": date.getHours(),
-    "i+": date.getMinutes(),
-    "s+": date.getSeconds()
-  };
-  for (const k in o) {
-    if (new RegExp(`(${k})`).test(dateFormat)) {
-      // 取出对应的值
-      const str = o[k] + "";
-      /* 根据设置的格式,输出对应的字符
-       * 例如: 早上8时,hh => 08,h => 8
-       * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
-       * 例如: 下午15时,hh => 15, h => 15
-       */
-      dateFormat = dateFormat.replace(
-        RegExp.$1,
-        RegExp.$1.length === 1 ? str : padLeftZero(str)
-      );
-    }
-  }
-  return dateFormat;
-};
-
-// 日期时间补零
-const padLeftZero = (str: string | any[]) => {
-  return ("00" + str).substr(str.length);
-};
-
-nextTick(() => {
-  flipObjs.value = [
-    unref(flipperHour1),
-    unref(flipperHour2),
-    unref(flipperMinute1),
-    unref(flipperMinute2),
-    unref(flipperSecond1),
-    unref(flipperSecond2)
-  ];
-
-  init();
-  run();
-});
-
-onUnmounted(() => {
-  if (timer.value) {
-    clearInterval(timer.value);
-    timer.value = null;
-  }
-});
-</script>
-
-<template>
-  <div class="flip-clock">
-    <flippers ref="flipperHour1" />
-    <flippers ref="flipperHour2" />
-    <em>:</em>
-    <flippers ref="flipperMinute1" />
-    <flippers ref="flipperMinute2" />
-    <em>:</em>
-    <flippers ref="flipperSecond1" />
-    <flippers ref="flipperSecond2" />
-  </div>
-</template>
-
-<style>
-.flip-clock .m-flipper {
-  margin: 0 3px;
-}
-
-.flip-clock em {
-  display: inline-block;
-  font-size: 66px;
-  font-style: normal;
-  line-height: 102px;
-  vertical-align: top;
-}
-</style>

+ 0 - 17
haha-admin-web/src/components/ReFlowChart/index.ts

@@ -1,17 +0,0 @@
-import control from "./src/Control.vue";
-import nodePanel from "./src/NodePanel.vue";
-import dataDialog from "./src/DataDialog.vue";
-import { withInstall } from "@pureadmin/utils";
-
-/** LogicFlow流程图-控制面板 */
-const Control = withInstall(control);
-
-/** LogicFlow流程图-拖拽面板 */
-const NodePanel = withInstall(nodePanel);
-
-/** LogicFlow流程图-查看数据 */
-const DataDialog = withInstall(dataDialog);
-
-export { Control, NodePanel, DataDialog };
-
-// LogicFlow流程图文档:http://logic-flow.org/

+ 0 - 148
haha-admin-web/src/components/ReFlowChart/src/Control.vue

@@ -1,148 +0,0 @@
-<script setup lang="ts">
-import { ref, unref, onMounted } from "vue";
-import { LogicFlow } from "@logicflow/core";
-
-interface Props {
-  lf?: LogicFlow;
-  catTurboData?: boolean;
-}
-
-const props = withDefaults(defineProps<Props>(), {
-  lf: null
-});
-
-const emit = defineEmits<{
-  (e: "catData"): void;
-}>();
-
-const controlButton3 = ref();
-const controlButton4 = ref();
-
-const focusIndex = ref<Number>(-1);
-const titleLists = ref([
-  {
-    icon: "icon-zoom-out-hs",
-    text: "缩小",
-    size: "18",
-    disabled: false
-  },
-  {
-    icon: "icon-enlarge-hs",
-    text: "放大",
-    size: "18",
-    disabled: false
-  },
-  {
-    icon: "icon-full-screen-hs",
-    text: "适应",
-    size: "15",
-    disabled: false
-  },
-  {
-    icon: "icon-previous-hs",
-    text: "上一步",
-    size: "15",
-    disabled: true
-  },
-  {
-    icon: "icon-next-step-hs",
-    text: "下一步",
-    size: "17",
-    disabled: true
-  },
-  {
-    icon: "icon-download-hs",
-    text: "下载图片",
-    size: "17",
-    disabled: false
-  },
-  {
-    icon: "icon-watch-hs",
-    text: "查看数据",
-    size: "17",
-    disabled: false
-  }
-]);
-
-const onControl = (item, key) => {
-  ["zoom", "zoom", "resetZoom", "undo", "redo", "getSnapshot"].forEach(
-    (v, i) => {
-      const domControl = props.lf;
-      if (key === 1) {
-        domControl.zoom(true);
-      }
-      if (key === 6) {
-        emit("catData");
-      }
-      if (key === i) {
-        domControl[v]();
-      }
-    }
-  );
-};
-
-const onEnter = key => {
-  focusIndex.value = key;
-};
-
-onMounted(() => {
-  props.lf.on("history:change", ({ data: { undoAble, redoAble } }) => {
-    unref(titleLists)[3].disabled = unref(controlButton3).disabled = !undoAble;
-    unref(titleLists)[4].disabled = unref(controlButton4).disabled = !redoAble;
-  });
-});
-</script>
-
-<template>
-  <div class="control-container">
-    <!-- 功能按钮 -->
-    <ul>
-      <li
-        v-for="(item, key) in titleLists"
-        :key="key"
-        :title="item.text"
-        class="dark:text-bg_color"
-        @mouseenter.prevent="onEnter(key)"
-        @mouseleave.prevent="focusIndex = -1"
-      >
-        <button
-          :ref="'controlButton' + key"
-          v-tippy="{
-            content: item.text
-          }"
-          :disabled="item.disabled"
-          :style="{
-            cursor: item.disabled === false ? 'pointer' : 'not-allowed',
-            color: item.disabled === false ? '' : '#00000040',
-            background: 'transparent',
-            border: 'none'
-          }"
-          @click="onControl(item, key)"
-        >
-          <span
-            :class="'iconfont ' + item.icon"
-            :style="{ fontSize: `${item.size}px` }"
-          />
-        </button>
-      </li>
-    </ul>
-  </div>
-</template>
-
-<style scoped>
-@import url("./assets/iconfont/iconfont.css");
-
-.control-container {
-  background: hsl(0deg 0% 100% / 80%);
-  box-shadow: 0 1px 4px rgb(0 0 0 / 20%);
-}
-
-.control-container ul li {
-  margin: 10px;
-  text-align: center;
-}
-
-.control-container ul li span:hover {
-  color: var(--el-color-primary);
-}
-</style>

+ 0 - 17
haha-admin-web/src/components/ReFlowChart/src/DataDialog.vue

@@ -1,17 +0,0 @@
-<script setup lang="ts">
-import VueJsonPretty from "vue-json-pretty";
-import "vue-json-pretty/lib/styles.css";
-
-defineProps({
-  graphData: Object
-});
-</script>
-
-<template>
-  <vue-json-pretty
-    :path="'res'"
-    :deep="3"
-    :showLength="true"
-    :data="graphData"
-  />
-</template>

Разница между файлами не показана из-за своего большого размера
+ 0 - 85
haha-admin-web/src/components/ReFlowChart/src/NodePanel.vue


+ 0 - 166
haha-admin-web/src/components/ReFlowChart/src/adpterForTurbo.ts

@@ -1,166 +0,0 @@
-const TurboType = {
-  SEQUENCE_FLOW: 1,
-  START_EVENT: 2,
-  END_EVENT: 3,
-  USER_TASK: 4,
-  SERVICE_TASK: 5,
-  EXCLUSIVE_GATEWAY: 6
-};
-
-function getTurboType(type) {
-  switch (type) {
-    case "bpmn:sequenceFlow":
-      return TurboType.SEQUENCE_FLOW;
-    case "bpmn:startEvent":
-      return TurboType.START_EVENT;
-    case "bpmn:endEvent":
-      return TurboType.END_EVENT;
-    case "bpmn:userTask":
-      return TurboType.USER_TASK;
-    case "bpmn:serviceTask":
-      return TurboType.SERVICE_TASK;
-    case "bpmn:exclusiveGateway":
-      return TurboType.EXCLUSIVE_GATEWAY;
-    default:
-      return type;
-  }
-}
-
-function convertNodeToTurboElement(node) {
-  const { id, type, x, y, text = "", properties } = node;
-  return {
-    incoming: [],
-    outgoing: [],
-    dockers: [],
-    type: getTurboType(node.type),
-    properties: {
-      ...properties,
-      name: (text && text.value) || "",
-      x: x,
-      y: y,
-      text,
-      logicFlowType: type
-    },
-    key: id
-  };
-}
-
-function convertEdgeToTurboElement(edge) {
-  const {
-    id,
-    type,
-    sourceNodeId,
-    targetNodeId,
-    startPoint,
-    endPoint,
-    pointsList,
-    text = "",
-    properties
-  } = edge;
-  return {
-    incoming: [sourceNodeId],
-    outgoing: [targetNodeId],
-    type: getTurboType(type),
-    dockers: [],
-    properties: {
-      ...properties,
-      name: (text && text.value) || "",
-      text,
-      startPoint,
-      endPoint,
-      pointsList,
-      logicFlowType: type
-    },
-    key: id
-  };
-}
-
-export function toTurboData(data) {
-  const nodeMap = new Map();
-  const turboData = {
-    flowElementList: []
-  };
-  data.nodes.forEach(node => {
-    const flowElement = convertNodeToTurboElement(node);
-    turboData.flowElementList.push(flowElement);
-    nodeMap.set(node.id, flowElement);
-  });
-  data.edges.forEach(edge => {
-    const flowElement = convertEdgeToTurboElement(edge);
-    const sourceElement = nodeMap.get(edge.sourceNodeId);
-    sourceElement.outgoing.push(flowElement.key);
-    const targetElement = nodeMap.get(edge.targetNodeId);
-    targetElement.incoming.push(flowElement.key);
-    turboData.flowElementList.push(flowElement);
-  });
-  return turboData;
-}
-
-function convertFlowElementToEdge(element) {
-  const { incoming, outgoing, properties, key } = element;
-  const { text, startPoint, endPoint, pointsList, logicFlowType } = properties;
-  const edge = {
-    id: key,
-    type: logicFlowType,
-    sourceNodeId: incoming[0],
-    targetNodeId: outgoing[0],
-    text,
-    startPoint,
-    endPoint,
-    pointsList,
-    properties: {}
-  };
-  const excludeProperties = [
-    "startPoint",
-    "endPoint",
-    "pointsList",
-    "text",
-    "logicFlowType"
-  ];
-  Object.keys(element.properties).forEach(property => {
-    if (excludeProperties.indexOf(property) === -1) {
-      edge.properties[property] = element.properties[property];
-    }
-  });
-  return edge;
-}
-
-function convertFlowElementToNode(element) {
-  const { properties, key } = element;
-  const { x, y, text, logicFlowType } = properties;
-  const node = {
-    id: key,
-    type: logicFlowType,
-    x,
-    y,
-    text,
-    properties: {}
-  };
-  const excludeProperties = ["x", "y", "text", "logicFlowType"];
-  Object.keys(element.properties).forEach(property => {
-    if (excludeProperties.indexOf(property) === -1) {
-      node.properties[property] = element.properties[property];
-    }
-  });
-  return node;
-}
-
-export function toLogicflowData(data) {
-  const lfData = {
-    nodes: [],
-    edges: []
-  };
-  const list = data.flowElementList;
-  list &&
-    list.length > 0 &&
-    list.forEach(element => {
-      if (element.type === TurboType.SEQUENCE_FLOW) {
-        const edge = convertFlowElementToEdge(element);
-        lfData.edges.push(edge);
-      } else {
-        const node = convertFlowElementToNode(element);
-        lfData.nodes.push(node);
-      }
-    });
-  return lfData;
-}

Разница между файлами не показана из-за своего большого размера
+ 0 - 6
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.css


BIN
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.eot


Разница между файлами не показана из-за своего большого размера
+ 0 - 8
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.js


+ 0 - 58
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.json

@@ -1,58 +0,0 @@
-{
-  "id": "2491438",
-  "name": "liu'c'tu",
-  "font_family": "iconfont",
-  "css_prefix_text": "icon-",
-  "description": "",
-  "glyphs": [
-    {
-      "icon_id": "755619",
-      "name": "自适应图标",
-      "font_class": "full-screen-hs",
-      "unicode": "e656",
-      "unicode_decimal": 58966
-    },
-    {
-      "icon_id": "14445801",
-      "name": "查看",
-      "font_class": "watch-hs",
-      "unicode": "e766",
-      "unicode_decimal": 59238
-    },
-    {
-      "icon_id": "9712640",
-      "name": "下载",
-      "font_class": "download-hs",
-      "unicode": "e6af",
-      "unicode_decimal": 59055
-    },
-    {
-      "icon_id": "1029099",
-      "name": "放大",
-      "font_class": "enlarge-hs",
-      "unicode": "e765",
-      "unicode_decimal": 59237
-    },
-    {
-      "icon_id": "20017362",
-      "name": "上一步",
-      "font_class": "previous-hs",
-      "unicode": "e84c",
-      "unicode_decimal": 59468
-    },
-    {
-      "icon_id": "1010015",
-      "name": "缩小",
-      "font_class": "zoom-out-hs",
-      "unicode": "e744",
-      "unicode_decimal": 59204
-    },
-    {
-      "icon_id": "20017363",
-      "name": "下一步",
-      "font_class": "next-step-hs",
-      "unicode": "e84b",
-      "unicode_decimal": 59467
-    }
-  ]
-}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.svg


BIN
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.ttf


BIN
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff


BIN
haha-admin-web/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff2


+ 0 - 55
haha-admin-web/src/components/ReFlowChart/src/config.ts

@@ -1,55 +0,0 @@
-export const nodeList = [
-  {
-    text: "开始",
-    type: "start",
-    class: "node-start"
-  },
-  {
-    text: "矩形",
-    type: "rect",
-    class: "node-rect"
-  },
-  {
-    type: "user",
-    text: "用户",
-    class: "node-user"
-  },
-  {
-    type: "push",
-    text: "推送",
-    class: "node-push"
-  },
-  {
-    type: "download",
-    text: "位置",
-    class: "node-download"
-  },
-  {
-    type: "end",
-    text: "结束",
-    class: "node-end"
-  }
-];
-
-export const BpmnNode = [
-  {
-    type: "bpmn:startEvent",
-    text: "开始",
-    class: "bpmn-start"
-  },
-  {
-    type: "bpmn:endEvent",
-    text: "结束",
-    class: "bpmn-end"
-  },
-  {
-    type: "bpmn:exclusiveGateway",
-    text: "网关",
-    class: "bpmn-exclusiveGateway"
-  },
-  {
-    type: "bpmn:userTask",
-    text: "用户",
-    class: "bpmn-user"
-  }
-];

+ 0 - 7
haha-admin-web/src/components/ReMap/index.ts

@@ -1,7 +0,0 @@
-import amap from "./src/Amap.vue";
-import { withInstall } from "@pureadmin/utils";
-
-/** 高德地图组件 */
-export const Amap = withInstall(amap);
-
-export default Amap;

+ 0 - 136
haha-admin-web/src/components/ReMap/src/Amap.vue

@@ -1,136 +0,0 @@
-<script setup lang="ts">
-import { reactive, getCurrentInstance, onBeforeMount, onUnmounted } from "vue";
-import { deviceDetection } from "@pureadmin/utils";
-import AMapLoader from "@amap/amap-jsapi-loader";
-import { mapJson } from "@/api/mock";
-import car from "@/assets/car.png";
-
-export interface MapConfigureInter {
-  on: Fn;
-  destroy?: Fn;
-  clearEvents?: Fn;
-  addControl?: Fn;
-  setCenter?: Fn;
-  setZoom?: Fn;
-  plugin?: Fn;
-}
-
-defineOptions({
-  name: "Amap"
-});
-
-let MarkerCluster;
-let map: MapConfigureInter;
-
-const instance = getCurrentInstance();
-
-const mapSet = reactive({
-  loading: deviceDetection() ? false : true
-});
-
-// 地图创建完成(动画关闭)
-const complete = (): void => {
-  if (map) {
-    map.on("complete", () => {
-      mapSet.loading = false;
-    });
-  }
-};
-
-onBeforeMount(() => {
-  if (!instance) return;
-  const { MapConfigure } = instance.appContext.config.globalProperties.$config;
-  const { options } = MapConfigure;
-
-  AMapLoader.load({
-    key: MapConfigure.amapKey,
-    version: "2.0",
-    plugins: ["AMap.MarkerCluster"]
-  })
-    .then(AMap => {
-      // 创建地图实例
-      map = new AMap.Map(instance.refs.mapview, options);
-
-      //地图中添加地图操作ToolBar插件
-      map.plugin(["AMap.ToolBar", "AMap.MapType"], () => {
-        map.addControl(new AMap.ToolBar());
-        //地图类型切换
-        map.addControl(
-          new AMap.MapType({
-            defaultType: 0
-          })
-        );
-      });
-
-      MarkerCluster = new AMap.MarkerCluster(map, [], {
-        // 聚合网格像素大小
-        gridSize: 80,
-        maxZoom: 14,
-        renderMarker(ctx) {
-          const { marker, data } = ctx;
-          if (Array.isArray(data) && data[0]) {
-            const { driver, plateNumber, orientation } = data[0];
-            const content = `<img style="transform: scale(1) rotate(${
-              360 - Number(orientation)
-            }deg);" src='${car}' />`;
-            marker.setContent(content);
-            marker.setLabel({
-              direction: "bottom",
-              //设置文本标注偏移量
-              offset: new AMap.Pixel(-4, 0),
-              //设置文本标注内容
-              content: `<div> ${plateNumber}(${driver})</div>`
-            });
-            marker.setOffset(new AMap.Pixel(-18, -10));
-            marker.on("click", ({ lnglat }) => {
-              map.setZoom(13); //设置地图层级
-              map.setCenter(lnglat);
-            });
-          }
-        }
-      });
-
-      // 获取模拟车辆信息
-      mapJson()
-        .then(({ data }) => {
-          const points: object = data.map(v => {
-            return {
-              lnglat: [v.lng, v.lat],
-              ...v
-            };
-          });
-          if (MarkerCluster) MarkerCluster.setData(points);
-        })
-        .catch(err => {
-          console.log("err:", err);
-        });
-
-      complete();
-    })
-    .catch(() => {
-      mapSet.loading = false;
-      throw "地图加载失败,请重新加载";
-    });
-});
-
-onUnmounted(() => {
-  if (map) {
-    // 销毁地图实例
-    map.destroy() && map.clearEvents("click");
-  }
-});
-</script>
-
-<template>
-  <div id="mapview" ref="mapview" v-loading="mapSet.loading" />
-</template>
-
-<style lang="scss" scoped>
-#mapview {
-  height: calc(100vh - 86px);
-}
-
-:deep(.amap-marker-label) {
-  border: none !important;
-}
-</style>

+ 0 - 5
haha-admin-web/src/components/RePerms/index.ts

@@ -1,5 +0,0 @@
-import perms from "./src/perms";
-
-const Perms = perms;
-
-export { Perms };

+ 0 - 20
haha-admin-web/src/components/RePerms/src/perms.tsx

@@ -1,20 +0,0 @@
-import { defineComponent, Fragment } from "vue";
-import { hasPerms } from "@/utils/auth";
-
-export default defineComponent({
-  name: "Perms",
-  props: {
-    value: {
-      type: undefined,
-      default: []
-    }
-  },
-  setup(props, { slots }) {
-    return () => {
-      if (!slots) return null;
-      return hasPerms(props.value) ? (
-        <Fragment>{slots.default?.()}</Fragment>
-      ) : null;
-    };
-  }
-});

+ 0 - 7
haha-admin-web/src/components/ReSeamlessScroll/index.ts

@@ -1,7 +0,0 @@
-import reSeamlessScroll from "./src/index.vue";
-import { withInstall } from "@pureadmin/utils";
-
-/** 无缝滚动组件 */
-export const ReSeamlessScroll = withInstall(reSeamlessScroll);
-
-export default ReSeamlessScroll;

+ 0 - 538
haha-admin-web/src/components/ReSeamlessScroll/src/index.vue

@@ -1,538 +0,0 @@
-<script setup lang="ts">
-import {
-  type PropType,
-  type CSSProperties,
-  ref,
-  unref,
-  nextTick,
-  computed
-} from "vue";
-import {
-  tryOnMounted,
-  tryOnUnmounted,
-  templateRef,
-  useDebounceFn
-} from "@vueuse/core";
-import * as utilsMethods from "./utils";
-const { animationFrame, copyObj } = utilsMethods;
-animationFrame();
-
-defineOptions({
-  name: "ReSeamlessScroll"
-});
-
-const props = defineProps({
-  data: {
-    type: Array as PropType<unknown>
-  },
-  classOption: {
-    type: Object as PropType<unknown>
-  }
-});
-
-const emit = defineEmits<{
-  (e: "scrollEnd"): void;
-}>();
-
-const xPos = ref<number>(0);
-const yPos = ref<number>(0);
-const delay = ref<number>(0);
-const height = ref<number>(0);
-// 外容器宽度
-const width = ref<number>(0);
-// 内容实际宽度
-const realBoxWidth = ref<number>(0);
-const realBoxHeight = ref<number>(0);
-const copyHtml = ref("");
-// single 单步滚动的定时器
-let singleWaitTime = null;
-// move动画的animationFrame定时器
-let reqFrame = null;
-let startPos = null;
-//记录touchStart时候的posY
-let startPosY = null;
-//记录touchStart时候的posX
-let startPosX = null;
-// mouseenter mouseleave 控制scrollMove()的开关
-let isHover = false;
-let ease = "ease-in";
-
-if (props.classOption["key"] === undefined) {
-  // eslint-disable-next-line vue/no-mutating-props
-  props.classOption["key"] = 0;
-}
-
-const wrap = templateRef<HTMLElement | null>(
-  `wrap${props.classOption["key"]}`,
-  null
-);
-const slotList = templateRef<HTMLElement | null>(
-  `slotList${props.classOption["key"]}`,
-  null
-);
-const realBox = templateRef<HTMLElement | null>(
-  `realBox${props.classOption["key"]}`,
-  null
-);
-
-const leftSwitchState = computed(() => {
-  return unref(xPos) < 0;
-});
-
-const rightSwitchState = computed(() => {
-  return Math.abs(unref(xPos)) < unref(realBoxWidth) - unref(width);
-});
-
-const defaultOption = computed(() => {
-  return {
-    //步长
-    step: 1,
-    //启动无缝滚动最小数据数
-    limitMoveNum: 5,
-    //是否启用鼠标hover控制
-    hoverStop: true,
-    // bottom 往下 top 往上(默认) left 向左 right 向右
-    direction: "top",
-    //开启移动端touch
-    openTouch: true,
-    //单条数据高度有值hoverStop关闭
-    singleHeight: 0,
-    //单条数据宽度有值hoverStop关闭
-    singleWidth: 0,
-    //单步停止等待时间
-    waitTime: 1000,
-    switchOffset: 30,
-    autoPlay: true,
-    navigation: false,
-    switchSingleStep: 134,
-    switchDelay: 400,
-    switchDisabledClass: "disabled",
-    // singleWidth/singleHeight 是否开启rem度量
-    isSingleRemUnit: false
-  };
-});
-
-const options = computed(() => {
-  // @ts-expect-error
-  return copyObj({}, unref(defaultOption), props.classOption);
-});
-
-const leftSwitchClass = computed(() => {
-  return unref(leftSwitchState) ? "" : unref(options).switchDisabledClass;
-});
-
-const rightSwitchClass = computed(() => {
-  return unref(rightSwitchState) ? "" : unref(options).switchDisabledClass;
-});
-
-const leftSwitch = computed((): CSSProperties => {
-  return {
-    position: "absolute",
-    margin: `${unref(height) / 2}px 0 0 -${unref(options).switchOffset}px`,
-    transform: "translate(-100%,-50%)"
-  };
-});
-
-const rightSwitch = computed((): CSSProperties => {
-  return {
-    position: "absolute",
-    margin: `${unref(height) / 2}px 0 0 ${
-      unref(width) + unref(options).switchOffset
-    }px`,
-    transform: "translateY(-50%)"
-  };
-});
-
-const isHorizontal = computed(() => {
-  return (
-    unref(options).direction !== "bottom" && unref(options).direction !== "top"
-  );
-});
-
-const float = computed((): CSSProperties => {
-  return unref(isHorizontal)
-    ? { float: "left", overflow: "hidden" }
-    : { overflow: "hidden" };
-});
-
-const pos = computed(() => {
-  return {
-    transform: `translate(${unref(xPos)}px,${unref(yPos)}px)`,
-    transition: `all ${ease} ${unref(delay)}ms`,
-    overflow: "hidden"
-  };
-});
-
-const navigation = computed(() => {
-  return unref(options).navigation;
-});
-
-const autoPlay = computed(() => {
-  if (unref(navigation)) return false;
-  return unref(options).autoPlay;
-});
-
-const scrollSwitch = computed(() => {
-  // 从 props 解构出来的 属性 不再具有响应性.
-  return (props.data as any).length >= unref(options).limitMoveNum;
-});
-
-const hoverStopSwitch = computed(() => {
-  return unref(options).hoverStop && unref(autoPlay) && unref(scrollSwitch);
-});
-
-const canTouchScroll = computed(() => {
-  return unref(options).openTouch;
-});
-
-const baseFontSize = computed(() => {
-  return unref(options).isSingleRemUnit
-    ? parseInt(window.getComputedStyle(document.documentElement, null).fontSize)
-    : 1;
-});
-
-const realSingleStopWidth = computed(() => {
-  return unref(options).singleWidth * unref(baseFontSize);
-});
-
-const realSingleStopHeight = computed(() => {
-  return unref(options).singleHeight * unref(baseFontSize);
-});
-
-const step = computed(() => {
-  let singleStep;
-  const step = unref(options).step;
-  if (unref(isHorizontal)) {
-    singleStep = unref(realSingleStopWidth);
-  } else {
-    singleStep = unref(realSingleStopHeight);
-  }
-  if (singleStep > 0 && singleStep % step > 0) {
-    throw "如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确";
-  }
-  return step;
-});
-
-function reset() {
-  xPos.value = 0;
-  yPos.value = 0;
-  scrollCancle();
-  scrollInitMove();
-}
-
-function leftSwitchClick() {
-  if (!unref(leftSwitchState)) return;
-  // 小于单步距离
-  if (Math.abs(unref(xPos)) < unref(options).switchSingleStep) {
-    xPos.value = 0;
-    return;
-  }
-  xPos.value += unref(options).switchSingleStep;
-}
-
-function rightSwitchClick() {
-  if (!unref(rightSwitchState)) return;
-  // 小于单步距离
-  if (
-    unref(realBoxWidth) - unref(width) + unref(xPos) <
-    unref(options).switchSingleStep
-  ) {
-    xPos.value = unref(width) - unref(realBoxWidth);
-    return;
-  }
-  xPos.value -= unref(options).switchSingleStep;
-}
-
-function scrollCancle() {
-  cancelAnimationFrame(reqFrame || "");
-}
-
-function touchStart(e) {
-  if (!unref(canTouchScroll)) return;
-  let timer;
-  //touches数组对象获得屏幕上所有的touch,取第一个touch
-  const touch = e.targetTouches[0];
-  const { waitTime, singleHeight, singleWidth } = unref(options);
-  //取第一个touch的坐标值
-  startPos = {
-    x: touch.pageX,
-    y: touch.pageY
-  };
-  //记录touchStart时候的posY
-  startPosY = unref(yPos);
-  //记录touchStart时候的posX
-  startPosX = unref(xPos);
-  if (!!singleHeight && !!singleWidth) {
-    if (timer) clearTimeout(timer);
-    timer = setTimeout(() => {
-      scrollCancle();
-    }, waitTime + 20);
-  } else {
-    scrollCancle();
-  }
-}
-
-function touchMove(e) {
-  //当屏幕有多个touch或者页面被缩放过,就不执行move操作
-  if (
-    !unref(canTouchScroll) ||
-    e.targetTouches.length > 1 ||
-    (e.scale && e.scale !== 1)
-  )
-    return;
-  const touch = e.targetTouches[0];
-  const { direction } = unref(options);
-  const endPos = {
-    x: touch.pageX - startPos.x,
-    y: touch.pageY - startPos.y
-  };
-  //阻止触摸事件的默认行为,即阻止滚屏
-  e.preventDefault();
-  //dir,1表示纵向滑动,0为横向滑动
-  const dir = Math.abs(endPos.x) < Math.abs(endPos.y) ? 1 : 0;
-  if (
-    (dir === 1 && direction === "bottom") ||
-    (dir === 1 && direction === "top")
-  ) {
-    // 表示纵向滑动 && 运动方向为上下
-    yPos.value = startPosY + endPos.y;
-  } else if (
-    (dir === 0 && direction === "left") ||
-    (dir === 0 && direction === "right")
-  ) {
-    // 为横向滑动 && 运动方向为左右
-    xPos.value = startPosX + endPos.x;
-  }
-}
-
-function touchEnd() {
-  if (!unref(canTouchScroll)) return;
-
-  let timer: any;
-  const direction = unref(options).direction;
-  delay.value = 50;
-  if (direction === "top") {
-    if (unref(yPos) > 0) yPos.value = 0;
-  } else if (direction === "bottom") {
-    const h = (unref(realBoxHeight) / 2) * -1;
-    if (unref(yPos) < h) yPos.value = h;
-  } else if (direction === "left") {
-    if (unref(xPos) > 0) xPos.value = 0;
-  } else if (direction === "right") {
-    const w = unref(realBoxWidth) * -1;
-    if (unref(xPos) < w) xPos.value = w;
-  }
-  if (timer) clearTimeout(timer);
-  timer = setTimeout(() => {
-    delay.value = 0;
-    scrollMove();
-  }, unref(delay));
-}
-
-function enter() {
-  if (unref(hoverStopSwitch)) scrollStopMove();
-}
-
-function leave() {
-  if (unref(hoverStopSwitch)) scrollStartMove();
-}
-
-function scrollMove() {
-  // 鼠标移入时拦截scrollMove()
-  if (isHover) return;
-  //进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
-  // scrollCancle();
-  reqFrame = requestAnimationFrame(function () {
-    //实际高度
-    const h = unref(realBoxHeight) / 2;
-    //宽度
-    const w = unref(realBoxWidth) / 2;
-    const { direction, waitTime } = unref(options);
-    if (direction === "top") {
-      // 上
-      if (Math.abs(unref(yPos)) >= h) {
-        emit("scrollEnd");
-        yPos.value = 0;
-      }
-      yPos.value -= step.value;
-    } else if (direction === "bottom") {
-      // 下
-      if (unref(yPos) >= 0) {
-        emit("scrollEnd");
-        yPos.value = h * -1;
-      }
-      yPos.value += step.value;
-    } else if (direction === "left") {
-      // 左
-      if (Math.abs(unref(xPos)) >= w) {
-        emit("scrollEnd");
-        xPos.value = 0;
-      }
-      xPos.value -= step.value;
-    } else if (direction === "right") {
-      // 右
-      if (unref(xPos) >= 0) {
-        emit("scrollEnd");
-        xPos.value = w * -1;
-      }
-      xPos.value += step.value;
-    }
-    if (singleWaitTime) clearTimeout(singleWaitTime);
-    if (unref(realSingleStopHeight)) {
-      //是否启动了单行暂停配置
-      if (Math.abs(unref(yPos)) % unref(realSingleStopHeight) < unref(step)) {
-        // 符合条件暂停waitTime
-        singleWaitTime = setTimeout(() => {
-          scrollMove();
-        }, waitTime);
-      } else {
-        scrollMove();
-      }
-    } else if (unref(realSingleStopWidth)) {
-      if (Math.abs(unref(xPos)) % unref(realSingleStopWidth) < unref(step)) {
-        // 符合条件暂停waitTime
-        singleWaitTime = setTimeout(() => {
-          scrollMove();
-        }, waitTime);
-      } else {
-        scrollMove();
-      }
-    } else {
-      scrollMove();
-    }
-  });
-}
-
-function scrollInitMove() {
-  nextTick(() => {
-    const { switchDelay } = unref(options);
-    //清空copy
-    copyHtml.value = "";
-    if (unref(isHorizontal)) {
-      height.value = unref(wrap).offsetHeight;
-      width.value = unref(wrap).offsetWidth;
-      let slotListWidth = unref(slotList).offsetWidth;
-      // 水平滚动设置warp width
-      if (unref(autoPlay)) {
-        // 修正offsetWidth四舍五入
-        slotListWidth = slotListWidth * 2 + 1;
-      }
-      unref(realBox).style.width = slotListWidth + "px";
-      realBoxWidth.value = slotListWidth;
-    }
-
-    if (unref(autoPlay)) {
-      ease = "ease-in";
-      delay.value = 0;
-    } else {
-      ease = "linear";
-      delay.value = switchDelay;
-      return;
-    }
-
-    // 是否可以滚动判断
-    if (unref(scrollSwitch)) {
-      let timer;
-      if (timer) clearTimeout(timer);
-      copyHtml.value = unref(slotList).innerHTML;
-      setTimeout(() => {
-        realBoxHeight.value = unref(realBox)?.offsetHeight;
-        scrollMove();
-      }, 0);
-    } else {
-      scrollCancle();
-      yPos.value = xPos.value = 0;
-    }
-  });
-}
-
-function scrollStartMove() {
-  //开启scrollMove
-  isHover = false;
-  scrollMove();
-}
-
-function scrollStopMove() {
-  //关闭scrollMove
-  isHover = true;
-  // 防止频频hover进出单步滚动,导致定时器乱掉
-  if (singleWaitTime) clearTimeout(singleWaitTime);
-  scrollCancle();
-}
-
-// 鼠标滚轮事件
-function wheel(e) {
-  if (
-    unref(options).direction === "left" ||
-    unref(options).direction === "right"
-  )
-    return;
-  useDebounceFn(() => {
-    e.deltaY > 0 ? (yPos.value -= step.value) : (yPos.value += step.value);
-  }, 50)();
-}
-
-// watchEffect(() => {
-//   const watchData = data;
-//   if (!watchData) return;
-//   nextTick(() => {
-//     reset();
-//   });
-
-//   const watchAutoPlay = unref(autoPlay);
-//   if (watchAutoPlay) {
-//     reset();
-//   } else {
-//     scrollStopMove();
-//   }
-// });
-
-tryOnMounted(() => {
-  scrollInitMove();
-});
-
-tryOnUnmounted(() => {
-  scrollCancle();
-  clearTimeout(singleWaitTime);
-});
-
-defineExpose({
-  reset
-});
-</script>
-
-<template>
-  <div :ref="'wrap' + classOption['key']">
-    <div
-      v-if="navigation"
-      :style="leftSwitch"
-      :class="leftSwitchClass"
-      @click="leftSwitchClick"
-    >
-      <slot name="left-switch" />
-    </div>
-    <div
-      v-if="navigation"
-      :style="rightSwitch"
-      :class="rightSwitchClass"
-      @click="rightSwitchClick"
-    >
-      <slot name="right-switch" />
-    </div>
-    <div
-      :ref="'realBox' + classOption['key']"
-      :style="pos"
-      @mouseenter="enter"
-      @mouseleave="leave"
-      @touchstart.passive="touchStart"
-      @touchmove.passive="touchMove"
-      @touchend="touchEnd"
-      @mousewheel.passive="wheel"
-    >
-      <div :ref="'slotList' + classOption['key']" :style="float">
-        <slot />
-      </div>
-      <div :style="float" v-html="copyHtml" />
-    </div>
-  </div>
-</template>

+ 0 - 119
haha-admin-web/src/components/ReSeamlessScroll/src/utils.ts

@@ -1,119 +0,0 @@
-/**
- * @desc AnimationFrame简单兼容hack
- */
-export const animationFrame = () => {
-  window.cancelAnimationFrame = (() => {
-    return (
-      window.cancelAnimationFrame ||
-      window.webkitCancelAnimationFrame ||
-      window.mozCancelAnimationFrame ||
-      window.oCancelAnimationFrame ||
-      window.msCancelAnimationFrame ||
-      function (id) {
-        return window.clearTimeout(id);
-      }
-    );
-  })();
-  window.requestAnimationFrame = (function () {
-    return (
-      window.requestAnimationFrame ||
-      window.webkitRequestAnimationFrame ||
-      window.mozRequestAnimationFrame ||
-      window.oRequestAnimationFrame ||
-      window.msRequestAnimationFrame ||
-      function (callback) {
-        return window.setTimeout(callback, 1000 / 60);
-      }
-    );
-  })();
-};
-
-/**
- * @desc 判断数组是否相等
- * @return {Boolean}
- * @param arr1
- * @param arr2
- */
-export const arrayEqual = (arr1: Array<any>, arr2: Array<any>) => {
-  if (arr1 === arr2) return true;
-  if (arr1.length !== arr2.length) return false;
-  for (let i = 0; i < arr1.length; ++i) {
-    if (arr1[i] !== arr2[i]) return false;
-  }
-  return true;
-};
-
-/**
- * @desc 深浅合并拷贝
- */
-export function copyObj() {
-  if (!Array.isArray) {
-    // @ts-expect-error
-    Array.isArray = function (arg) {
-      return Object.prototype.toString.call(arg) === "[object Array]";
-    };
-  }
-  let name,
-    options,
-    src,
-    copy,
-    copyIsArray,
-    clone,
-    i = 1,
-    // eslint-disable-next-line prefer-rest-params
-    target = arguments[0] || {}, // 使用||运算符,排除隐式强制类型转换为false的数据类型
-    deep = false,
-    // eslint-disable-next-line prefer-const
-    len = arguments.length;
-  if (typeof target === "boolean") {
-    deep = target;
-
-    // eslint-disable-next-line prefer-rest-params
-    target = arguments[1] || {};
-    i++;
-  }
-  if (typeof target !== "object" && typeof target !== "function") {
-    target = {};
-  }
-  // 如果arguments.length === 1 或typeof arguments[0] === 'boolean',且存在arguments[1],则直接返回target对象
-  if (i === len) {
-    return target;
-  }
-  for (; i < len; i++) {
-    //所以如果源对象中数据类型为Undefined或Null那么就会跳过本次循环,接着循环下一个源对象
-
-    // eslint-disable-next-line prefer-rest-params
-    if ((options = arguments[i]) != null) {
-      // 如果遇到源对象的数据类型为Boolean, Number for in循环会被跳过,不执行for in循环// src用于判断target对象是否存在name属性
-      for (name in options) {
-        // src用于判断target对象是否存在name属性
-        src = target[name];
-        // 需要复制的属性当前源对象的name属性
-        copy = options[name];
-        // 判断copy是否是数组
-        copyIsArray = Array.isArray(copy);
-        // 如果是深复制且copy是一个对象或数组则需要递归直到copy成为一个基本数据类型为止
-        if (deep && copy && (typeof copy === "object" || copyIsArray)) {
-          if (copyIsArray) {
-            copyIsArray = false;
-            // 如果目标对象存在name属性且是一个数组
-            // 则使用目标对象的name属性,否则重新创建一个数组,用于复制
-            clone = src && Array.isArray(src) ? src : [];
-          } else {
-            // 如果目标对象存在name属性且是一个对象则使用目标对象的name属性,否则重新创建一个对象,用于复制
-            clone = src && typeof src === "object" ? src : {};
-          }
-          // 深复制,所以递归调用copyObject函数
-          // 返回值为target对象,即clone对象
-          // copy是一个源对象
-          // @ts-expect-error
-          target[name] = copyObj(deep, clone, copy);
-        } else if (copy !== undefined) {
-          // 浅复制,直接复制到target对象上
-          target[name] = copy;
-        }
-      }
-    }
-  }
-  return target;
-}

+ 78 - 0
haha-admin-web/src/components/ReSegmented/index.scss

@@ -0,0 +1,78 @@
+.pure-segmented {
+  --pure-segmented-bg-color: var(--el-bg-color);
+  --pure-segmented-item-color: var(--el-text-color-regular);
+  --pure-segmented-item-selected-color: var(--el-text-color-primary);
+  --pure-segmented-item-selected-bg-color: var(--el-bg-color-page);
+  --pure-segmented-item-hover-color: var(--el-text-color-primary);
+  --pure-segmented-border-radius: var(--el-border-radius-base);
+  --pure-segmented-padding: 2px;
+
+  position: relative;
+  display: inline-block;
+  padding: var(--pure-segmented-padding);
+  background-color: var(--pure-segmented-bg-color);
+  border-radius: var(--pure-segmented-border-radius);
+  box-sizing: border-box;
+  font-size: 14px;
+  color: var(--pure-segmented-item-color);
+}
+
+.pure-segmented-block {
+  display: flex;
+  width: 100%;
+}
+
+.pure-segmented-disabled {
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+
+.pure-segmented-thumb {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 0;
+  height: 0;
+  background-color: var(--pure-segmented-item-selected-bg-color);
+  border-radius: var(--pure-segmented-border-radius);
+  box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.08),
+    0 4px 12px -4px rgba(0, 0, 0, 0.12);
+  transition: transform 0.2s ease-in-out, width 0.2s ease-in-out;
+}
+
+.pure-segmented-item-container {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.pure-segmented-item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 4px 12px;
+  min-height: 28px;
+  cursor: pointer;
+  border-radius: var(--pure-segmented-border-radius);
+  transition: color 0.2s ease-in-out;
+  z-index: 1;
+}
+
+.pure-segmented-item:hover {
+  color: var(--pure-segmented-item-hover-color);
+}
+
+.pure-segmented-item.is-selected {
+  color: var(--pure-segmented-item-selected-color);
+}
+
+.pure-segmented-item-icon {
+  margin-right: 4px;
+  font-size: 16px;
+}
+
+.pure-segmented-item-label {
+  white-space: nowrap;
+}

+ 2 - 6
haha-admin-web/src/components/ReSegmented/index.ts

@@ -1,8 +1,4 @@
-import reSegmented from "./src/index";
-import { withInstall } from "@pureadmin/utils";
-
-/** 分段控制器组件 */
-export const ReSegmented = withInstall(reSegmented);
+import ReSegmented from "./index.tsx";
 
+export type { OptionsType } from "./index.tsx";
 export default ReSegmented;
-export type { OptionsType } from "./src/type";

+ 101 - 0
haha-admin-web/src/components/ReSegmented/index.tsx

@@ -0,0 +1,101 @@
+import { ref, computed, PropType, defineComponent, h, isVNode } from "vue";
+import "./index.scss";
+
+export type OptionsType = {
+  label?: string;
+  value?: string | number;
+  icon?: any;
+  iconAttrs?: Record<string, any>;
+  tip?: string;
+  theme?: string;
+  disabled?: boolean;
+};
+
+const props = {
+  options: {
+    type: Array as PropType<Array<OptionsType>>,
+    default: () => []
+  },
+  modelValue: {
+    type: [String, Number] as PropType<string | number>,
+    default: ""
+  },
+  resize: {
+    type: Boolean,
+    default: false
+  },
+  block: {
+    type: Boolean,
+    default: false
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+};
+
+export default defineComponent({
+  name: "ReSegmented",
+  props,
+  emits: ["update:modelValue", "change"],
+  setup(props, { emit, slots }) {
+    const currentIndex = ref(0);
+
+    function handleClick(index: number, option: OptionsType) {
+      if (props.disabled) return;
+      currentIndex.value = index;
+      emit("update:modelValue", option.value ?? index);
+      emit("change", { option, index });
+    }
+
+    function handleMouseover(index: number) {
+      if (props.disabled) return;
+    }
+
+    const curMouseClass = computed(() => {
+      return (index: number) => {
+        return index === currentIndex.value
+          ? "is-selected"
+          : index < currentIndex.value
+            ? "is-left-selected"
+            : "";
+      };
+    });
+
+    function renderIcon(option: OptionsType) {
+      if (!option.icon) return null;
+      const iconComponent = option.icon;
+      const iconAttrs = option.iconAttrs || {};
+      if (typeof iconComponent === "object" || typeof iconComponent === "function") {
+        return h(iconComponent, { ...iconAttrs, class: "pure-segmented-item-icon" });
+      }
+      return null;
+    }
+
+    return () => (
+      <div
+        class={[
+          "pure-segmented",
+          { "pure-segmented-block": props.block, "pure-segmented-disabled": props.disabled }
+        ]}
+      >
+        <div class="pure-segmented-thumb" />
+        <div class="pure-segmented-item-container">
+          {props.options.map((option, index) => (
+            <div
+              key={index}
+              class={["pure-segmented-item", curMouseClass.value(index)]}
+              onMouseover={() => handleMouseover(index)}
+              onClick={() => handleClick(index, option)}
+            >
+              {renderIcon(option)}
+              {option.label && (
+                <span class="pure-segmented-item-label">{option.label}</span>
+              )}
+            </div>
+          ))}
+        </div>
+      </div>
+    );
+  }
+});

+ 0 - 156
haha-admin-web/src/components/ReSegmented/src/index.css

@@ -1,156 +0,0 @@
-.pure-segmented {
-  --pure-control-padding-horizontal: 12px;
-  --pure-control-padding-horizontal-sm: 8px;
-  --pure-segmented-track-padding: 2px;
-  --pure-segmented-line-width: 1px;
-
-  --pure-segmented-border-radius-small: 4px;
-  --pure-segmented-border-radius-base: 6px;
-  --pure-segmented-border-radius-large: 8px;
-
-  box-sizing: border-box;
-  display: inline-block;
-  padding: var(--pure-segmented-track-padding);
-  font-size: var(--el-font-size-base);
-  color: rgba(0, 0, 0, 0.65);
-  background-color: rgb(0 0 0 / 4%);
-  border-radius: var(--pure-segmented-border-radius-base);
-}
-
-.pure-segmented-block {
-  display: flex;
-}
-
-.pure-segmented-block .pure-segmented-item {
-  flex: 1;
-  min-width: 0;
-}
-
-.pure-segmented-block .pure-segmented-item > .pure-segmented-item-label > span {
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-}
-
-/* small */
-.pure-segmented.pure-segmented--small {
-  border-radius: var(--pure-segmented-border-radius-small);
-}
-.pure-segmented.pure-segmented--small .pure-segmented-item {
-  border-radius: var(--el-border-radius-small);
-}
-.pure-segmented.pure-segmented--small .pure-segmented-item > div {
-  min-height: calc(
-    var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2
-  );
-  line-height: calc(
-    var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2
-  );
-  padding: 0
-    calc(
-      var(--pure-control-padding-horizontal-sm) -
-        var(--pure-segmented-line-width)
-    );
-}
-
-/* large */
-.pure-segmented.pure-segmented--large {
-  border-radius: var(--pure-segmented-border-radius-large);
-}
-.pure-segmented.pure-segmented--large .pure-segmented-item {
-  border-radius: calc(
-    var(--el-border-radius-base) + var(--el-border-radius-small)
-  );
-}
-.pure-segmented.pure-segmented--large .pure-segmented-item > div {
-  min-height: calc(
-    var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2
-  );
-  line-height: calc(
-    var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2
-  );
-  padding: 0
-    calc(
-      var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width)
-    );
-  font-size: var(--el-font-size-medium);
-}
-
-/* default */
-.pure-segmented-item {
-  position: relative;
-  text-align: center;
-  cursor: pointer;
-  border-radius: var(--el-border-radius-base);
-  transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
-}
-.pure-segmented .pure-segmented-item > div {
-  min-height: calc(
-    var(--el-component-size) - var(--pure-segmented-track-padding) * 2
-  );
-  line-height: calc(
-    var(--el-component-size) - var(--pure-segmented-track-padding) * 2
-  );
-  padding: 0
-    calc(
-      var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width)
-    );
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-}
-
-.pure-segmented-group {
-  position: relative;
-  display: flex;
-  align-items: stretch;
-  justify-items: flex-start;
-  width: 100%;
-}
-
-.pure-segmented-item-selected {
-  position: absolute;
-  top: 0;
-  left: 0;
-  box-sizing: border-box;
-  display: none;
-  width: 0;
-  height: 100%;
-  padding: 4px 0;
-  background-color: #fff;
-  border-radius: 4px;
-  box-shadow:
-    0 2px 8px -2px rgb(0 0 0 / 5%),
-    0 1px 4px -1px rgb(0 0 0 / 7%),
-    0 0 1px rgb(0 0 0 / 7%);
-  transition:
-    transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
-    width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
-  will-change: transform, width;
-}
-
-.pure-segmented-item > input {
-  position: absolute;
-  inset-block-start: 0;
-  inset-inline-start: 0;
-  width: 0;
-  height: 0;
-  opacity: 0;
-  pointer-events: none;
-}
-
-.pure-segmented-item-label {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.pure-segmented-item-icon svg {
-  width: 16px;
-  height: 16px;
-}
-
-.pure-segmented-item-disabled {
-  color: rgba(0, 0, 0, 0.25);
-  cursor: not-allowed;
-}

+ 59 - 190
haha-admin-web/src/components/ReSegmented/src/index.tsx

@@ -1,216 +1,85 @@
-import "./index.css";
-import type { OptionsType } from "./type";
-import { useRenderIcon } from "@/components/ReIcon/src/hooks";
-import {
-  useDark,
-  isNumber,
-  isFunction,
-  useResizeObserver
-} from "@pureadmin/utils";
-import {
-  type PropType,
-  h,
-  ref,
-  toRef,
-  watch,
-  nextTick,
-  defineComponent,
-  getCurrentInstance
-} from "vue";
+import { ref, computed, watch, type PropType } from "vue";
+import { useNamespace } from "@pureadmin/utils";
+
+export type OptionsType = {
+  label?: string;
+  value?: string | number;
+  icon?: any;
+  iconAttrs?: Record<string, any>;
+  tip?: string;
+  theme?: string;
+  disabled?: boolean;
+};
 
 const props = {
   options: {
-    type: Array<OptionsType>,
+    type: Array as PropType<Array<OptionsType>>,
     default: () => []
   },
-  /** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */
   modelValue: {
-    type: undefined,
-    require: false,
-    default: "0"
+    type: [String, Number] as PropType<string | number>,
+    default: ""
   },
-  /** 将宽度调整为父元素宽度	 */
-  block: {
+  size: {
+    type: String as PropType<"small" | "default" | "large">,
+    default: "default"
+  },
+  resize: {
     type: Boolean,
     default: false
   },
-  /** 控件尺寸 */
-  size: {
-    type: String as PropType<"small" | "default" | "large">
-  },
-  /** 是否全局禁用,默认 `false` */
-  disabled: {
+  block: {
     type: Boolean,
     default: false
   },
-  /** 当内容发生变化时,设置 `resize` 可使其自适应容器位置 */
-  resize: {
+  disabled: {
     type: Boolean,
     default: false
   }
 };
 
-export default defineComponent({
-  name: "ReSegmented",
-  props,
-  emits: ["change", "update:modelValue"],
-  setup(props, { emit }) {
-    const width = ref(0);
-    const translateX = ref(0);
-    const { isDark } = useDark();
-    const initStatus = ref(false);
-    const curMouseActive = ref(-1);
-    const segmentedItembg = ref("");
-    const instance = getCurrentInstance()!;
-    const curIndex = isNumber(props.modelValue)
-      ? toRef(props, "modelValue")
-      : ref(0);
+const emit = defineEmits<{
+  (e: "update:modelValue", value: string | number): void;
+  (e: "change", value: { option: OptionsType; index: number }): void;
+}>();
 
-    function handleChange({ option, index }, event: Event) {
-      if (props.disabled || option.disabled) return;
-      event.preventDefault();
-      isNumber(props.modelValue)
-        ? emit("update:modelValue", index)
-        : (curIndex.value = index);
-      segmentedItembg.value = "";
-      emit("change", { index, option });
-    }
-
-    function handleMouseenter({ option, index }, event: Event) {
-      if (props.disabled) return;
-      event.preventDefault();
-      curMouseActive.value = index;
-      if (option.disabled || curIndex.value === index) {
-        segmentedItembg.value = "";
-      } else {
-        segmentedItembg.value = isDark.value
-          ? "#1f1f1f"
-          : "rgba(0, 0, 0, 0.06)";
-      }
-    }
+const ns = useNamespace("segmented");
 
-    function handleMouseleave(_, event: Event) {
-      if (props.disabled) return;
-      event.preventDefault();
-      curMouseActive.value = -1;
-    }
-
-    function handleInit(index = curIndex.value) {
-      nextTick(() => {
-        const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef;
-        if (!curLabelRef) return;
-        width.value = curLabelRef.clientWidth;
-        translateX.value = curLabelRef.offsetLeft;
-        initStatus.value = true;
-      });
-    }
+const currentIndex = ref(0);
+const segmentedRef = ref<HTMLElement>();
+const labelRefs = ref<HTMLElement[]>([]);
 
-    function handleResizeInit() {
-      useResizeObserver(".pure-segmented", () => {
-        nextTick(() => {
-          handleInit(curIndex.value);
-        });
-      });
-    }
+const curMouseOverIndex = ref(0);
 
-    (props.block || props.resize) && handleResizeInit();
+const activeStyle = computed(() => {
+  return {
+    transform: `translateX(${currentIndex.value * 100}%)`,
+    width: `${100 / props.options.length}%`
+  };
+});
 
-    watch(
-      () => curIndex.value,
-      index => {
-        nextTick(() => {
-          handleInit(index);
-        });
-      },
-      {
-        immediate: true
-      }
-    );
+function handleClick(index: number, option: OptionsType) {
+  if (props.disabled || option.disabled) return;
+  currentIndex.value = index;
+  emit("update:modelValue", option.value ?? index);
+  emit("change", { option, index });
+}
 
-    watch(() => props.size, handleResizeInit, {
-      immediate: true
-    });
+function handleMouseover(index: number) {
+  curMouseOverIndex.value = index;
+}
 
-    const rendLabel = () => {
-      return props.options.map((option, index) => {
-        return (
-          <label
-            ref={`labelRef${index}`}
-            class={[
-              "pure-segmented-item",
-              (props.disabled || option?.disabled) &&
-                "pure-segmented-item-disabled"
-            ]}
-            style={{
-              background:
-                curMouseActive.value === index ? segmentedItembg.value : "",
-              color: props.disabled
-                ? null
-                : !option.disabled &&
-                    (curIndex.value === index || curMouseActive.value === index)
-                  ? isDark.value
-                    ? "rgba(255, 255, 255, 0.85)"
-                    : "rgba(0,0,0,.88)"
-                  : ""
-            }}
-            onMouseenter={event => handleMouseenter({ option, index }, event)}
-            onMouseleave={event => handleMouseleave({ option, index }, event)}
-            onClick={event => handleChange({ option, index }, event)}
-          >
-            <input type="radio" name="segmented" />
-            <div
-              class="pure-segmented-item-label"
-              v-tippy={{
-                content: option?.tip,
-                zIndex: 41000
-              }}
-            >
-              {option.icon && !isFunction(option.label) ? (
-                <span
-                  class="pure-segmented-item-icon"
-                  style={{ marginRight: option.label ? "6px" : 0 }}
-                >
-                  {h(
-                    useRenderIcon(option.icon, {
-                      ...option?.iconAttrs
-                    })
-                  )}
-                </span>
-              ) : null}
-              {option.label ? (
-                isFunction(option.label) ? (
-                  h(option.label)
-                ) : (
-                  <span>{option.label}</span>
-                )
-              ) : null}
-            </div>
-          </label>
-        );
-      });
-    };
+function handleMouseout() {
+  curMouseOverIndex.value = currentIndex.value;
+}
 
-    return () => (
-      <div
-        class={{
-          "pure-segmented": true,
-          "pure-segmented-block": props.block,
-          "pure-segmented--large": props.size === "large",
-          "pure-segmented--small": props.size === "small"
-        }}
-      >
-        <div class="pure-segmented-group">
-          <div
-            class="pure-segmented-item-selected"
-            style={{
-              width: `${width.value}px`,
-              transform: `translateX(${translateX.value}px)`,
-              display: initStatus.value ? "block" : "none"
-            }}
-          ></div>
-          {rendLabel()}
-        </div>
-      </div>
-    );
-  }
-});
+watch(
+  () => props.modelValue,
+  newVal => {
+    const index = props.options.findIndex(option => option.value === newVal);
+    if (index !== -1) {
+      currentIndex.value = index;
+    }
+  },
+  { immediate: true }
+);

+ 0 - 20
haha-admin-web/src/components/ReSegmented/src/type.ts

@@ -1,20 +0,0 @@
-import type { VNode, Component } from "vue";
-import type { iconType } from "@/components/ReIcon/src/types.ts";
-
-export interface OptionsType {
-  /** 文字 */
-  label?: string | (() => VNode | Component);
-  /**
-   * @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
-   * @see {@link 用法参考 https://pure-admin.cn/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
-   */
-  icon?: string | Component;
-  /** 图标属性、样式配置 */
-  iconAttrs?: iconType;
-  /** 值 */
-  value?: any;
-  /** 是否禁用 */
-  disabled?: boolean;
-  /** `tooltip` 提示 */
-  tip?: string;
-}

+ 0 - 7
haha-admin-web/src/components/ReSelector/index.ts

@@ -1,7 +0,0 @@
-import reSelector from "./src";
-import { withInstall } from "@pureadmin/utils";
-
-/** 选择器组件 */
-export const ReSelector = withInstall(reSelector);
-
-export default ReSelector;

+ 0 - 28
haha-admin-web/src/components/ReSelector/src/index.css

@@ -1,28 +0,0 @@
-.hs-rate__icon {
-  font-size: 18px;
-  transition: 0.3s;
-}
-
-.hs-item {
-  width: 30px;
-  height: 30px;
-  box-sizing: border-box;
-  line-height: 30px;
-}
-
-.hs-on {
-  background-color: #409eff;
-  border-radius: 50%;
-}
-
-.hs-range {
-  background-color: #f2f6fc;
-}
-
-.both-left-sides {
-  border-radius: 50% 0 0 50%;
-}
-
-.both-right-sides {
-  border-radius: 0 50% 50% 0;
-}

+ 0 - 327
haha-admin-web/src/components/ReSelector/src/index.tsx

@@ -1,327 +0,0 @@
-import "./index.css";
-import {
-  unref,
-  computed,
-  nextTick,
-  onBeforeMount,
-  defineComponent,
-  getCurrentInstance
-} from "vue";
-import { addClass, removeClass, toggleClass } from "@pureadmin/utils";
-
-const stayClass = "stay"; //鼠标点击
-const activeClass = "hs-on"; //鼠标移动上去
-const voidClass = "hs-off"; //鼠标移开
-const inRange = "hs-range"; //当前选中的两个元素之间的背景
-const bothLeftSides = "both-left-sides";
-const bothRightSides = "both-right-sides";
-let selectedDirection = "right"; //默认从左往右,索引变大
-
-let overList = [];
-// 存放第一个选中的元素和最后一个选中元素,只能存放这两个元素
-let selectedList = [];
-
-const props = {
-  HsKey: {
-    type: Number || String,
-    default: 0
-  },
-  disabled: {
-    type: Boolean,
-    default: false
-  },
-  value: {
-    type: Number,
-    default: 0
-  },
-  max: {
-    type: Array,
-    default() {
-      return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
-    }
-  },
-  // 回显数据的索引,长度必须是2
-  echo: {
-    type: Array,
-    default() {
-      return [];
-    }
-  }
-};
-
-export default defineComponent({
-  name: "ReSelector",
-  props,
-  emits: ["selectedVal"],
-  setup(props, { emit }) {
-    const instance = getCurrentInstance();
-    const currentValue = props.value;
-
-    const rateDisabled = computed(() => {
-      return props.disabled;
-    });
-
-    const classes = computed(() => {
-      const result = [];
-      let i = 0;
-      let threshold = currentValue;
-      if (currentValue !== Math.floor(currentValue)) {
-        threshold--;
-      }
-      for (; i < threshold; i++) {
-        result.push(activeClass);
-      }
-      for (; i < props.max.length; i++) {
-        result.push(voidClass);
-      }
-      return result;
-    });
-
-    // 鼠标移入
-    const setCurrentValue = index => {
-      if (props.disabled) return;
-      // 当选中一个元素后,开始添加背景色
-      if (selectedList.length === 1) {
-        if (overList.length < 1) overList.push({ index });
-
-        let firstIndex = overList[0].index;
-
-        // 往右走,索引变大
-        if (index > firstIndex) {
-          selectedDirection = "right";
-          toggleClass(
-            false,
-            bothRightSides,
-            document.querySelector(".hs-select__item" + selectedList[0].index)
-          );
-
-          while (index >= firstIndex) {
-            addClass(
-              document.querySelector(".hs-select__item" + firstIndex),
-              inRange
-            );
-            firstIndex++;
-          }
-        } else {
-          selectedDirection = "left";
-          toggleClass(
-            true,
-            bothRightSides,
-            document.querySelector(".hs-select__item" + selectedList[0].index)
-          );
-
-          while (index <= firstIndex) {
-            addClass(
-              document.querySelector(".hs-select__item" + firstIndex),
-              inRange
-            );
-            firstIndex--;
-          }
-        }
-      }
-
-      addClass(document.querySelector("." + voidClass + index), activeClass);
-    };
-
-    // 鼠标离开
-    const resetCurrentValue = index => {
-      if (props.disabled) return;
-      // 移除先检查是否选中 选中则返回false 不移除
-      const currentHsDom = document.querySelector("." + voidClass + index);
-      if (currentHsDom.className.includes(stayClass)) {
-        return false;
-      } else {
-        removeClass(currentHsDom, activeClass);
-      }
-
-      // 当选中一个元素后,开始移除背景色
-      if (selectedList.length === 1) {
-        const firstIndex = overList[0].index;
-        if (index >= firstIndex) {
-          for (let i = 0; i <= index; i++) {
-            removeClass(
-              document.querySelector(".hs-select__item" + i),
-              inRange
-            );
-          }
-        } else {
-          while (index <= firstIndex) {
-            removeClass(
-              document.querySelector(".hs-select__item" + index),
-              inRange
-            );
-            index++;
-          }
-        }
-      }
-    };
-
-    // 鼠标点击
-    const selectValue = (index, item) => {
-      if (props.disabled) return;
-      const len = selectedList.length;
-
-      if (len < 2) {
-        selectedList.push({ item, index });
-        addClass(document.querySelector("." + voidClass + index), stayClass);
-
-        addClass(
-          document.querySelector(".hs-select__item" + selectedList[0].index),
-          bothLeftSides
-        );
-
-        if (selectedList[1]) {
-          if (selectedDirection === "right") {
-            addClass(
-              document.querySelector(
-                ".hs-select__item" + selectedList[1].index
-              ),
-              bothRightSides
-            );
-          } else {
-            addClass(
-              document.querySelector(
-                ".hs-select__item" + selectedList[1].index
-              ),
-              bothLeftSides
-            );
-          }
-        }
-
-        if (len === 1) {
-          // 顺时针排序
-          if (selectedDirection === "right") {
-            emit("selectedVal", {
-              left: selectedList[0].item,
-              right: selectedList[1].item,
-              whole: selectedList
-            });
-          } else {
-            emit("selectedVal", {
-              left: selectedList[1].item,
-              right: selectedList[0].item,
-              whole: selectedList
-            });
-          }
-        }
-      } else {
-        nextTick(() => {
-          selectedList.forEach(v => {
-            removeClass(
-              document.querySelector("." + voidClass + v.index),
-              activeClass,
-              stayClass
-            );
-
-            removeClass(
-              document.querySelector(".hs-select__item" + v.index),
-              bothLeftSides,
-              bothRightSides
-            );
-          });
-
-          selectedList = [];
-          overList = [];
-          for (let i = 0; i <= props.max.length; i++) {
-            const currentDom = document.querySelector(".hs-select__item" + i);
-            if (currentDom) {
-              removeClass(currentDom, inRange);
-            }
-          }
-
-          selectedList.push({ item, index });
-          addClass(document.querySelector("." + voidClass + index), stayClass);
-
-          addClass(
-            document.querySelector(".hs-select__item" + selectedList[0].index),
-            bothLeftSides
-          );
-        });
-      }
-    };
-
-    // 回显数据
-    const echoView = item => {
-      if (item.length === 0) return;
-
-      if (item.length > 2 || item.length === 1) {
-        throw "传入的数组长度必须是2";
-      }
-
-      item.sort((a, b) => {
-        return a - b;
-      });
-
-      addClass(
-        instance.refs["hsdiv" + props.HsKey + item[0]] as Element,
-        activeClass,
-        stayClass
-      );
-
-      addClass(
-        instance.refs["hstd" + props.HsKey + item[0]] as Element,
-        bothLeftSides
-      );
-
-      addClass(
-        instance.refs["hsdiv" + props.HsKey + item[1]] as Element,
-        activeClass,
-        stayClass
-      );
-
-      addClass(
-        instance.refs["hstd" + props.HsKey + item[1]] as Element,
-        bothRightSides
-      );
-
-      while (item[1] >= item[0]) {
-        addClass(
-          instance.refs["hstd" + props.HsKey + item[0]] as Element,
-          inRange
-        );
-        item[0]++;
-      }
-    };
-
-    onBeforeMount(() => {
-      nextTick(() => {
-        echoView(props.echo);
-      });
-    });
-
-    return () => (
-      <>
-        <table cellspacing="0" cellpadding="0">
-          <tbody>
-            <tr>
-              {props.max.map((item, key) => {
-                return (
-                  <td
-                    data-index={props.HsKey}
-                    ref={`hstd${props.HsKey}${key}`}
-                    class={`hs-select__item${key}`}
-                    onMousemove={() => setCurrentValue(key)}
-                    onMouseleave={() => resetCurrentValue(key)}
-                    onClick={() => selectValue(key, item)}
-                    style={{
-                      cursor: unref(rateDisabled) ? "auto" : "pointer",
-                      textAlign: "center"
-                    }}
-                    key={key}
-                  >
-                    <div
-                      ref={`hsdiv${props.HsKey}${key}`}
-                      class={`hs-item ${[unref(classes)[key] + key]}`}
-                    >
-                      <span>{item}</span>
-                    </div>
-                  </td>
-                );
-              })}
-            </tr>
-          </tbody>
-        </table>
-      </>
-    );
-  }
-});

+ 0 - 23
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.css

@@ -1,23 +0,0 @@
-@font-face {
-  font-family: "iconfont"; /* Project id 3268330 */
-  src:
-    url("iconfont.woff2?t=1647939915215") format("woff2"),
-    url("iconfont.woff?t=1647939915215") format("woff"),
-    url("iconfont.ttf?t=1647939915215") format("truetype");
-}
-
-.iconfont {
-  font-family: "iconfont" !important;
-  font-size: 16px;
-  font-style: normal;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-
-.icon-tuozhuai1:before {
-  content: "\e647";
-}
-
-.icon-tuozhuai1-copy:before {
-  content: "\eda3";
-}

+ 0 - 66
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.js

@@ -1,66 +0,0 @@
-!(function (e) {
-  var t,
-    n,
-    c,
-    o,
-    s,
-    i =
-      '<svg><symbol id="icon-tuozhuai1" viewBox="0 0 1024 1024"><path d="M576 896c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m-256 0c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m256-192c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m-256 0c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m256-192c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m-256 0c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m256-192c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m-256 0c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m256-192c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z m-256 0c0-35.2 28.8-64 64-64s64 28.8 64 64-28.8 64-64 64-64-28.8-64-64z" fill="#2c2c2c" ></path></symbol><symbol id="icon-tuozhuai1-copy" viewBox="0 0 1024 1024"><path d="M128 576c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0-256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m192 256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0-256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m192 256.00000001c35.2 0 64 28.8 64 63.99999999s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-63.99999999z m0-256.00000001c35.2 0 64 28.8 64 64s-28.8 64-64 63.99999999-64-28.8-64-63.99999999 28.8-64 64-64z m192 256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0-256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m192 256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0-256c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z" fill="#2c2c2c" ></path></symbol></svg>',
-    d = (d = document.getElementsByTagName("script"))[
-      d.length - 1
-    ].getAttribute("data-injectcss"),
-    m = function (e, t) {
-      t.parentNode.insertBefore(e, t);
-    };
-  if (d && !e.__iconfont__svg__cssinject__) {
-    e.__iconfont__svg__cssinject__ = !0;
-    try {
-      document.write(
-        "<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
-      );
-    } catch (e) {
-      console && console.log(e);
-    }
-  }
-  function l() {
-    s || ((s = !0), c());
-  }
-  function a() {
-    try {
-      o.documentElement.doScroll("left");
-    } catch (e) {
-      return void setTimeout(a, 50);
-    }
-    l();
-  }
-  ((t = function () {
-    var e,
-      t = document.createElement("div");
-    ((t.innerHTML = i),
-      (i = null),
-      (t = t.getElementsByTagName("svg")[0]) &&
-        ((t.style.position = "absolute"),
-        (t.style.width = 0),
-        (t.style.height = 0),
-        (t.style.overflow = "hidden"),
-        (t = t),
-        (e = document.body).firstChild
-          ? m(t, e.firstChild)
-          : e.appendChild(t)));
-  }),
-    document.addEventListener
-      ? ~["complete", "loaded", "interactive"].indexOf(document.readyState)
-        ? setTimeout(t, 0)
-        : ((n = function () {
-            (document.removeEventListener("DOMContentLoaded", n, !1), t());
-          }),
-          document.addEventListener("DOMContentLoaded", n, !1))
-      : document.attachEvent &&
-        ((c = t),
-        (o = e.document),
-        (s = !1),
-        a(),
-        (o.onreadystatechange = function () {
-          "complete" == o.readyState && ((o.onreadystatechange = null), l());
-        })));
-})(window);

+ 0 - 23
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.json

@@ -1,23 +0,0 @@
-{
-  "id": "3268330",
-  "name": "split",
-  "font_family": "iconfont",
-  "css_prefix_text": "icon-",
-  "description": "",
-  "glyphs": [
-    {
-      "icon_id": "22378774",
-      "name": "拖拽",
-      "font_class": "tuozhuai1",
-      "unicode": "e647",
-      "unicode_decimal": 58951
-    },
-    {
-      "icon_id": "23570521",
-      "name": "拖拽",
-      "font_class": "tuozhuai1-copy",
-      "unicode": "eda3",
-      "unicode_decimal": 60835
-    }
-  ]
-}

BIN
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.ttf


BIN
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.woff


BIN
haha-admin-web/src/components/ReSplitPane/iconfont/iconfont.woff2


+ 0 - 49
haha-admin-web/src/components/ReSplitPane/index.css

@@ -1,49 +0,0 @@
-.clearfix::after {
-  visibility: hidden;
-  display: block;
-  font-size: 0;
-  content: " ";
-  clear: both;
-  height: 0;
-}
-
-.vue-splitter-container {
-  height: 100%;
-  position: relative;
-}
-
-.vue-splitter-container-mask {
-  z-index: 9999;
-  width: 100%;
-  height: 100%;
-  position: absolute;
-  top: 0;
-  left: 0;
-}
-
-.splitter-pane.vertical.splitter-paneL {
-  position: absolute;
-  left: 0;
-  height: 100%;
-  padding-right: 3px;
-}
-
-.splitter-pane.vertical.splitter-paneR {
-  position: absolute;
-  right: 0;
-  height: 100%;
-  padding-left: 3px;
-}
-
-.splitter-pane.horizontal.splitter-paneL {
-  position: absolute;
-  top: 0;
-  width: 100%;
-}
-
-.splitter-pane.horizontal.splitter-paneR {
-  position: absolute;
-  bottom: 0;
-  width: 100%;
-  padding-top: 3px;
-}

+ 0 - 136
haha-admin-web/src/components/ReSplitPane/index.tsx

@@ -1,136 +0,0 @@
-import "./index.css";
-import resizer from "./resizer";
-import { type PropType, defineComponent, ref, unref, computed } from "vue";
-
-export interface ContextProps {
-  minPercent: number;
-  defaultPercent: number;
-  split: string;
-}
-
-/** 切割面板组件 */
-export default defineComponent({
-  name: "SplitPane",
-  components: { resizer },
-  props: {
-    splitSet: {
-      type: Object as PropType<ContextProps>,
-      require: true
-    }
-  },
-  emits: ["resize"],
-  setup(props, ctx) {
-    const active = ref(false);
-    const hasMoved = ref(false);
-    const percent = ref(props.splitSet?.defaultPercent);
-    const type = props.splitSet?.split === "vertical" ? "width" : "height";
-    const resizeType = props.splitSet?.split === "vertical" ? "left" : "top";
-
-    const leftClass = ref([
-      "splitter-pane splitter-paneL",
-      props.splitSet?.split
-    ]);
-
-    const rightClass = ref([
-      "splitter-pane splitter-paneR",
-      props.splitSet?.split
-    ]);
-
-    const cursor = computed(() => {
-      return active.value
-        ? props.splitSet?.split === "vertical"
-          ? { cursor: "col-resize" }
-          : { cursor: "row-resize" }
-        : { cursor: "default" };
-    });
-
-    const onClick = (): void => {
-      if (!hasMoved.value) {
-        percent.value = 50;
-        ctx.emit("resize", percent.value);
-      }
-    };
-
-    const onMouseDown = (): void => {
-      active.value = true;
-      hasMoved.value = false;
-    };
-
-    const onMouseUp = (): void => {
-      active.value = false;
-    };
-
-    const onMouseMove = (e: any): void => {
-      if (e.buttons === 0 || e.which === 0) {
-        active.value = false;
-      }
-
-      if (active.value) {
-        let offset = 0;
-        let target = e.currentTarget;
-        if (props.splitSet?.split === "vertical") {
-          while (target) {
-            offset += target.offsetLeft;
-            target = target.offsetParent;
-          }
-        } else {
-          while (target) {
-            offset += target.offsetTop;
-            target = target.offsetParent;
-          }
-        }
-
-        const currentPage =
-          props.splitSet?.split === "vertical" ? e.pageX : e.pageY;
-        const targetOffset =
-          props.splitSet?.split === "vertical"
-            ? e.currentTarget.offsetWidth
-            : e.currentTarget.offsetHeight;
-        const percents =
-          Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100;
-
-        if (
-          percents > props.splitSet?.minPercent &&
-          percents < 100 - props.splitSet?.minPercent
-        ) {
-          percent.value = percents;
-        }
-
-        ctx.emit("resize", percent.value);
-
-        hasMoved.value = true;
-      }
-    };
-
-    return () => (
-      <>
-        <div
-          class="vue-splitter-container clearfix"
-          style={unref(cursor)}
-          onMouseup={() => onMouseUp()}
-          onMousemove={() => onMouseMove(event)}
-        >
-          <div
-            class={unref(leftClass)}
-            style={{ [unref(type)]: unref(percent) + "%" }}
-          >
-            {ctx.slots.paneL()}
-          </div>
-          <resizer
-            style={`${unref([resizeType])}:${unref(percent)}%`}
-            split={props.splitSet?.split}
-            onMousedown={() => onMouseDown()}
-            onClick={() => onClick()}
-          ></resizer>
-          <div
-            class={unref(rightClass)}
-            style={{ [unref(type)]: 100 - unref(percent) + "%" }}
-          >
-            {ctx.slots.paneR()}
-          </div>
-          <div v-show={unref(active)} class="vue-splitter-container-mask"></div>
-        </div>
-      </>
-    );
-  }
-});

+ 0 - 47
haha-admin-web/src/components/ReSplitPane/resizer.css

@@ -1,47 +0,0 @@
-@import "./iconfont/iconfont.css";
-
-.splitter-pane-resizer {
-  box-sizing: border-box;
-  background: #000;
-  position: absolute;
-  opacity: 0.2;
-  z-index: 1;
-  background-clip: padding;
-  background-clip: padding-box;
-}
-
-.splitter-pane-resizer.horizontal {
-  height: 6px;
-  width: 100%;
-  background: #e5e6eb;
-  cursor: row-resize;
-}
-
-.splitter-pane-resizer.horizontal:before {
-  content: "\eda3";
-  font-family: "iconfont";
-  font-size: 13px;
-  color: #000;
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-}
-
-.splitter-pane-resizer.vertical {
-  width: 6px;
-  height: 100%;
-  background: #e5e6eb;
-  cursor: col-resize;
-}
-
-.splitter-pane-resizer.vertical:before {
-  content: "\e647";
-  font-family: "iconfont";
-  font-size: 13px;
-  color: #000;
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-}

+ 0 - 23
haha-admin-web/src/components/ReSplitPane/resizer.tsx

@@ -1,23 +0,0 @@
-import "./resizer.css";
-import { computed, unref, defineComponent } from "vue";
-
-export default defineComponent({
-  name: "Resizer",
-  props: {
-    split: {
-      type: String,
-      required: true
-    },
-    className: {
-      type: String,
-      default: ""
-    }
-  },
-  setup(props) {
-    const classes = computed(() => {
-      return ["splitter-pane-resizer", props.split, props.className].join(" ");
-    });
-
-    return () => <div class={unref(classes)}></div>;
-  }
-});

+ 0 - 8
haha-admin-web/src/components/ReTypeit/index.ts

@@ -1,8 +0,0 @@
-import typeIt from "./src/index";
-import type { Options as TypeItOptions } from "typeit";
-
-const TypeIt = typeIt;
-
-export { TypeIt, TypeItOptions };
-
-export default TypeIt;

+ 0 - 56
haha-admin-web/src/components/ReTypeit/src/index.tsx

@@ -1,56 +0,0 @@
-import type { El } from "typeit/dist/types";
-import TypeIt, { type Options as TypeItOptions } from "typeit";
-import { type PropType, ref, defineComponent, onMounted } from "vue";
-
-// 打字机效果组件(配置项详情请查阅 https://www.typeitjs.com/docs/vanilla/usage#options)
-export default defineComponent({
-  name: "TypeIt",
-  props: {
-    options: {
-      type: Object as PropType<TypeItOptions>,
-      default: () => ({}) as TypeItOptions
-    }
-  },
-  setup(props, { slots, expose }) {
-    /**
-     * 输出错误信息
-     * @param message 错误信息
-     */
-    function throwError(message: string) {
-      throw new TypeError(message);
-    }
-
-    /**
-     * 获取浏览器默认语言
-     */
-    function getBrowserLanguage() {
-      return navigator.language;
-    }
-
-    const typedItRef = ref<Element | null>(null);
-
-    onMounted(() => {
-      const $typed = typedItRef.value!.querySelector(".type-it") as El;
-
-      if (!$typed) {
-        const errorMsg =
-          getBrowserLanguage() === "zh-CN"
-            ? "请确保有且只有一个具有class属性为 'type-it' 的元素"
-            : "Please make sure that there is only one element with a Class attribute with 'type-it'";
-        throwError(errorMsg);
-      }
-
-      const typeIt = new TypeIt($typed, props.options).go();
-
-      expose({
-        typeIt
-      });
-    });
-
-    return () => (
-      <div ref={typedItRef}>
-        {slots.default?.() ?? <span class="type-it"></span>}
-      </div>
-    );
-  }
-});

+ 0 - 5
haha-admin-web/src/components/ReVxeTableBar/index.ts

@@ -1,5 +0,0 @@
-import vxeTableBar from "./src/bar";
-import { withInstall } from "@pureadmin/utils";
-
-/** 配合 `vxe-table` 实现快速便捷的表格操作 */
-export const VxeTableBar = withInstall(vxeTableBar);

+ 0 - 389
haha-admin-web/src/components/ReVxeTableBar/src/bar.tsx

@@ -1,389 +0,0 @@
-import Sortable from "sortablejs";
-import { transformI18n } from "@/plugins/i18n";
-import { useEpThemeStoreHook } from "@/store/modules/epTheme";
-import { delay, cloneDeep, getKeyList } from "@pureadmin/utils";
-import {
-  type PropType,
-  ref,
-  unref,
-  computed,
-  nextTick,
-  defineComponent,
-  getCurrentInstance
-} from "vue";
-
-import Fullscreen from "~icons/ri/fullscreen-fill";
-import ExitFullscreen from "~icons/ri/fullscreen-exit-fill";
-import DragIcon from "@/assets/table-bar/drag.svg?component";
-import ExpandIcon from "@/assets/table-bar/expand.svg?component";
-import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
-import SettingIcon from "@/assets/table-bar/settings.svg?component";
-import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
-
-const props = {
-  /** 头部最左边的标题 */
-  title: {
-    type: String,
-    default: "列表"
-  },
-  vxeTableRef: {
-    type: Object as PropType<any>
-  },
-  /** 需要展示的列 */
-  columns: {
-    type: Array as PropType<any>,
-    default: () => []
-  },
-  /** 是否为树列表 */
-  tree: {
-    type: Boolean,
-    default: false
-  },
-  isExpandAll: {
-    type: Boolean,
-    default: true
-  },
-  tableKey: {
-    type: [String, Number] as PropType<string | number>,
-    default: "0"
-  }
-};
-
-export default defineComponent({
-  name: "VxeTableBar",
-  props,
-  emits: ["refresh", "fullscreen"],
-  setup(props, { emit, slots, attrs }) {
-    const size = ref("small");
-    const loading = ref(false);
-    const checkAll = ref(true);
-    const isFullscreen = ref(false);
-    const isIndeterminate = ref(false);
-    const instance = getCurrentInstance()!;
-    const isExpandAll = ref(props.isExpandAll);
-    let checkColumnList = getKeyList(cloneDeep(props?.columns), "title");
-    const checkedColumns = ref(getKeyList(cloneDeep(props?.columns), "title"));
-    const dynamicColumns = ref(cloneDeep(props?.columns));
-
-    const getDropdownItemStyle = computed(() => {
-      return s => {
-        return {
-          background:
-            s === size.value ? useEpThemeStoreHook().epThemeColor : "",
-          color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
-        };
-      };
-    });
-
-    const iconClass = computed(() => {
-      return [
-        "text-black",
-        "dark:text-white",
-        "duration-100",
-        "hover:text-primary!",
-        "cursor-pointer",
-        "outline-hidden"
-      ];
-    });
-
-    const topClass = computed(() => {
-      return [
-        "flex",
-        "justify-between",
-        "pt-[3px]",
-        "px-[11px]",
-        "border-b-[1px]",
-        "border-b-solid",
-        "border-[#dcdfe6]",
-        "dark:border-[#303030]"
-      ];
-    });
-
-    function onReFresh() {
-      loading.value = true;
-      emit("refresh");
-      delay(500).then(() => (loading.value = false));
-    }
-
-    function onExpand() {
-      isExpandAll.value = !isExpandAll.value;
-      isExpandAll.value
-        ? props.vxeTableRef.setAllTreeExpand(true)
-        : props.vxeTableRef.clearTreeExpand();
-      props.vxeTableRef.refreshColumn();
-    }
-
-    function onFullscreen() {
-      isFullscreen.value = !isFullscreen.value;
-      emit("fullscreen", isFullscreen.value);
-    }
-
-    function reloadColumn() {
-      const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item =>
-        checkedColumns.value.includes(item.title)
-      );
-      props.vxeTableRef.reloadColumn(curCheckedColumns);
-    }
-
-    function handleCheckAllChange(val: boolean) {
-      checkedColumns.value = val ? checkColumnList : [];
-      isIndeterminate.value = false;
-      reloadColumn();
-    }
-
-    function handleCheckedColumnsChange(value: string[]) {
-      checkedColumns.value = value;
-      const checkedCount = value.length;
-      checkAll.value = checkedCount === checkColumnList.length;
-      isIndeterminate.value =
-        checkedCount > 0 && checkedCount < checkColumnList.length;
-    }
-
-    async function onReset() {
-      checkAll.value = true;
-      isIndeterminate.value = false;
-      dynamicColumns.value = cloneDeep(props?.columns);
-      checkColumnList = [];
-      checkColumnList = await getKeyList(cloneDeep(props?.columns), "title");
-      checkedColumns.value = getKeyList(cloneDeep(props?.columns), "title");
-      props.vxeTableRef.refreshColumn();
-    }
-
-    function changeSize(curSize: string) {
-      size.value = curSize;
-      props.vxeTableRef.refreshColumn();
-    }
-
-    const dropdown = {
-      dropdown: () => (
-        <el-dropdown-menu class="translation">
-          <el-dropdown-item
-            style={getDropdownItemStyle.value("medium")}
-            onClick={() => changeSize("medium")}
-          >
-            宽松
-          </el-dropdown-item>
-          <el-dropdown-item
-            style={getDropdownItemStyle.value("small")}
-            onClick={() => changeSize("small")}
-          >
-            默认
-          </el-dropdown-item>
-          <el-dropdown-item
-            style={getDropdownItemStyle.value("mini")}
-            onClick={() => changeSize("mini")}
-          >
-            紧凑
-          </el-dropdown-item>
-        </el-dropdown-menu>
-      )
-    };
-
-    /** 列展示拖拽排序 */
-    const rowDrop = (event: { preventDefault: () => void }) => {
-      event.preventDefault();
-      nextTick(() => {
-        const wrapper: HTMLElement = (
-          instance?.proxy?.$refs[`VxeGroupRef${unref(props.tableKey)}`] as any
-        ).$el.firstElementChild;
-        Sortable.create(wrapper, {
-          animation: 300,
-          handle: ".drag-btn",
-          onEnd: ({ newIndex, oldIndex, item }) => {
-            const targetThElem = item;
-            const wrapperElem = targetThElem.parentNode as HTMLElement;
-            const oldColumn = dynamicColumns.value[oldIndex];
-            const newColumn = dynamicColumns.value[newIndex];
-            if (oldColumn?.fixed || newColumn?.fixed) {
-              // 当前列存在fixed属性 则不可拖拽
-              const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
-              if (newIndex > oldIndex) {
-                wrapperElem.insertBefore(targetThElem, oldThElem);
-              } else {
-                wrapperElem.insertBefore(
-                  targetThElem,
-                  oldThElem ? oldThElem.nextElementSibling : oldThElem
-                );
-              }
-              return;
-            }
-            const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
-            dynamicColumns.value.splice(newIndex, 0, currentRow);
-            reloadColumn();
-          }
-        });
-      });
-    };
-
-    const isFixedColumn = (title: string) => {
-      return dynamicColumns.value.filter(
-        item => transformI18n(item.title) === transformI18n(title)
-      )[0].fixed
-        ? true
-        : false;
-    };
-
-    const rendTippyProps = (content: string) => {
-      // https://vue-tippy.netlify.app/props
-      return {
-        content,
-        offset: [0, 18],
-        duration: [300, 0],
-        followCursor: true,
-        hideOnClick: "toggle"
-      };
-    };
-
-    const reference = {
-      reference: () => (
-        <SettingIcon
-          class={["w-[16px]", iconClass.value]}
-          v-tippy={rendTippyProps("列设置")}
-        />
-      )
-    };
-
-    return () => (
-      <>
-        <div
-          {...attrs}
-          class={[
-            "w-full",
-            "px-2",
-            "pb-2",
-            "bg-bg_color",
-            isFullscreen.value
-              ? ["h-full!", "z-2002", "fixed", "inset-0"]
-              : "mt-2"
-          ]}
-        >
-          <div class="flex justify-between w-full h-[60px] p-4">
-            {slots?.title ? (
-              slots.title()
-            ) : (
-              <p class="font-bold truncate">{props.title}</p>
-            )}
-            <div class="flex items-center justify-around">
-              {slots?.buttons ? (
-                <div class="flex mr-4">{slots.buttons()}</div>
-              ) : null}
-              {props.tree ? (
-                <>
-                  <ExpandIcon
-                    class={["w-[16px]", iconClass.value]}
-                    style={{
-                      transform: isExpandAll.value ? "none" : "rotate(-90deg)"
-                    }}
-                    v-tippy={rendTippyProps(
-                      isExpandAll.value ? "折叠" : "展开"
-                    )}
-                    onClick={() => onExpand()}
-                  />
-                  <el-divider direction="vertical" />
-                </>
-              ) : null}
-              <RefreshIcon
-                class={[
-                  "w-[16px]",
-                  iconClass.value,
-                  loading.value ? "animate-spin" : ""
-                ]}
-                v-tippy={rendTippyProps("刷新")}
-                onClick={() => onReFresh()}
-              />
-              <el-divider direction="vertical" />
-              <el-dropdown
-                v-slots={dropdown}
-                trigger="click"
-                v-tippy={rendTippyProps("密度")}
-              >
-                <CollapseIcon class={["w-[16px]", iconClass.value]} />
-              </el-dropdown>
-              <el-divider direction="vertical" />
-
-              <el-popover
-                v-slots={reference}
-                placement="bottom-start"
-                popper-style={{ padding: 0 }}
-                width="200"
-                trigger="click"
-              >
-                <div class={[topClass.value]}>
-                  <el-checkbox
-                    class="-mr-1!"
-                    label="列展示"
-                    v-model={checkAll.value}
-                    indeterminate={isIndeterminate.value}
-                    onChange={value => handleCheckAllChange(value)}
-                  />
-                  <el-button type="primary" link onClick={() => onReset()}>
-                    重置
-                  </el-button>
-                </div>
-
-                <div class="pt-[6px] pl-[11px]">
-                  <el-scrollbar max-height="36vh">
-                    <el-checkbox-group
-                      ref={`VxeGroupRef${unref(props.tableKey)}`}
-                      modelValue={checkedColumns.value}
-                      onChange={value => handleCheckedColumnsChange(value)}
-                    >
-                      <el-space
-                        direction="vertical"
-                        alignment="flex-start"
-                        size={0}
-                      >
-                        {checkColumnList.map((item, index) => {
-                          return (
-                            <div class="flex items-center">
-                              <DragIcon
-                                class={[
-                                  "drag-btn w-[16px] mr-2",
-                                  isFixedColumn(item)
-                                    ? "cursor-no-drop!"
-                                    : "cursor-grab!"
-                                ]}
-                                onMouseenter={(event: {
-                                  preventDefault: () => void;
-                                }) => rowDrop(event)}
-                              />
-                              <el-checkbox
-                                key={index}
-                                label={item}
-                                value={item}
-                                onChange={reloadColumn}
-                              >
-                                <span
-                                  title={transformI18n(item)}
-                                  class="inline-block w-[120px] truncate hover:text-text_color_primary"
-                                >
-                                  {transformI18n(item)}
-                                </span>
-                              </el-checkbox>
-                            </div>
-                          );
-                        })}
-                      </el-space>
-                    </el-checkbox-group>
-                  </el-scrollbar>
-                </div>
-              </el-popover>
-              <el-divider direction="vertical" />
-
-              <iconifyIconOffline
-                class={["w-[16px]", iconClass.value]}
-                icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
-                v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
-                onClick={() => onFullscreen()}
-              />
-            </div>
-          </div>
-          {slots.default({
-            size: size.value,
-            dynamicColumns: dynamicColumns.value
-          })}
-        </div>
-      </>
-    );
-  }
-});

Некоторые файлы не были показаны из-за большого количества измененных файлов