diff --git a/.env b/.env index d83a12d..fcba4da 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ # permission 路由模式,动态路由,从接口获取, 动态渲染,不需要在 src/router/routes/modules 目录下配置路由 VITE_APP_ROUTER_MODE='permission' -VITE_TITLE='Slash Admin' +VITE_TITLE='React Admin' VITE_PORT=3005 diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..9746e2e --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,123 @@ +pipeline { + agent any + environment { + NPM_REGISTRY = 'https://registry.npmmirror.com' // 国内镜像加速 + APP_NAME = 'react-admin' // 项目名称 + } + tools { + nodejs 'node 20' // 确保与全局配置名称一致 + } + stages { + // 阶段1:拉取代码 + stage('Git Check') { + steps { + checkout scm // 从GitHub拉取代码 + } + } + // 阶段2:安装依赖 + stage('Install Dependencies') { + steps { + script { + sh 'node -v' + sh 'which npm' // 检查 npm 是否存在 + sh "npm ci --registry=${NPM_REGISTRY}" + // sh 'npm ci' + } + } + } + // 阶段3:格式化 & 代码检查 + stage('Format Code & Lint Code') { + steps { + sh 'npm run lint:format' + // sh 'npm run lint:fix' + } + } + // 阶段4:构建打包项目,并压缩文件 + stage('Build Aplication') { + steps { + sh 'npm run build' + sh "rm -f ${APP_NAME}.zip" // 新增:清理旧文件 + sh "zip -r ${APP_NAME}.zip . -x 'node_modules/*'" + // 归档产物(ZIP文件和原始目录) + archiveArtifacts artifacts: '*.zip', allowEmptyArchive: false + } + } + // 阶段5:运行单元测试 + // stage('Jest Check') { + // steps { + // sh 'npm run test' + // // junit '**/test-results.xml' // 收集测试报告(需配置Jest或JUnit输出)[2](@ref) + // // junit 'test-results/junit.xml' // 收集测试报告 + // // 归档产物(ZIP文件和原始目录) + // archiveArtifacts artifacts: 'coverage/lcov-report/index.html', allowEmptyArchive: false + // } + // } + // 阶段5:Nginx 部署 + stage('Deploy TO Nginx') { + steps { + script { + def execCommand = """ + # 启用详细日志和错误退出 + set -ex + # 加载 nvm 环境变量(关键步骤) + source /root/.nvm/nvm.sh + # 进入项目目录 + cd /var/www/${APP_NAME} || { echo '目录切换失败'; exit 1; } + # 解压构建产物 + unzip -o ${APP_NAME}.zip || { echo '解压失败'; exit 1; } + # 删除 ZIP 文件 + rm -f ${APP_NAME}.zip + chmod -R 755 . + # 检查 Nginx 配置 并重启 + nginx -t && systemctl reload nginx + """ + // source /root/.nvm/nvm.sh + // npm config set prefix "/root/.nvm/versions/node/v20.10.0" + + // 插件将构建产物部署到远程服务器 + sshPublisher( + publishers: [ + sshPublisherDesc( + configName: 'my ssh server', + transfers: [ + sshTransfer( + // 指定要传输的文件 + sourceFiles: '*.zip', + // 移除文件路径前缀 + // removePrefix: 'dist', + // 远程服务器上的目标目录 + remoteDirectory: "/${APP_NAME}", + // 执行的命令 + execCommand: execCommand + ) + ], + verbose: true + ) + ] + ) + } + } + } + } + post { + always { + script { + // 发送构建结果通知 + emailext( + subject: '$DEFAULT_SUBJECT', + body: '$DEFAULT_CONTENT', + mimeType: 'text/html', + to: 'a15277019572@aliyun.com', + attachmentsPattern: '*.zip, coverage/lcov-report/index.html', // 指定附件路径 + recipientProviders: [ + [$class: 'CulpritsRecipientProvider'], // 通知代码提交者 + [$class: 'RequesterRecipientProvider'] // 通知触发构建的用户 + ], + attachLog: true // 附加构建日志 + ) + } + // 清理工作空间(可选) + cleanWs() + } + } +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index c34a48a..e93eadd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,5 +3,5 @@ services: build: context: . ports: - - "3001:80" + - "3010:80" restart: always diff --git a/index.html b/index.html index 2e130ed..419497b 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + <%- title %> diff --git a/package.json b/package.json index 8cede31..cd16f1a 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "tailwindcss": "^3.3.3", "ts-node": "^10.9.1", "typescript": "^5.2.2", - "vite": "^5.4.9", + "vite": "^5.4.18", "vite-plugin-checker": "^0.9.1", "vite-plugin-compression": "^0.5.1", "vite-plugin-html": "^3.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50f64e9..e331d86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,13 +67,13 @@ importers: version: 1.17.1(babel-plugin-macros@3.1.0) '@vanilla-extract/vite-plugin': specifier: ^4.0.19 - version: 4.0.20(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(terser@5.39.0)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 4.0.20(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(terser@5.39.0)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) '@vercel/analytics': specifier: ^1.2.2 version: 1.5.0(react@18.2.0) '@vitejs/plugin-react': specifier: ^4.1.0 - version: 4.3.4(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 4.3.4(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) antd: specifier: ^5.9.3 version: 5.24.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -200,7 +200,7 @@ importers: version: 8.4.1 '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.14)(vitest@3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0)) + version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.14)(vitest@3.0.9) '@testing-library/react': specifier: ^14.2.1 version: 14.2.1(@types/react@18.3.19)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -233,7 +233,7 @@ importers: version: 5.1.34 '@vitest/coverage-v8': specifier: ^3.0.9 - version: 3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0)) + version: 3.0.9(vitest@3.0.9) '@vitest/ui': specifier: ^3.0.9 version: 3.0.9(vitest@3.0.9) @@ -280,23 +280,23 @@ importers: specifier: ^5.2.2 version: 5.8.2 vite: - specifier: ^5.4.9 - version: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + specifier: ^5.4.18 + version: 5.4.18(@types/node@20.5.1)(terser@5.39.0) vite-plugin-checker: specifier: ^0.9.1 - version: 0.9.1(@biomejs/biome@1.9.4)(typescript@5.8.2)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 0.9.1(@biomejs/biome@1.9.4)(typescript@5.8.2)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) vite-plugin-compression: specifier: ^0.5.1 - version: 0.5.1(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 0.5.1(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 3.2.2(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) vite-plugin-svg-icons: specifier: ^2.0.1 - version: 2.0.1(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 2.0.1(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.1.4(typescript@5.8.2)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + version: 5.1.4(typescript@5.8.2)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) vitest: specifier: ^3.0.9 version: 3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0) @@ -5421,8 +5421,8 @@ packages: vite: optional: true - vite@5.4.14: - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + vite@5.4.18: + resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -6865,7 +6865,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.14)(vitest@3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0))': + '@testing-library/jest-dom@6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.14)(vitest@3.0.9)': dependencies: '@adobe/css-tools': 4.4.2 '@babel/runtime': 7.26.10 @@ -7069,7 +7069,7 @@ snapshots: dependencies: '@vanilla-extract/css': 1.17.1(babel-plugin-macros@3.1.0) '@vanilla-extract/integration': 8.0.1(babel-plugin-macros@3.1.0) - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) vite-node: 3.0.9(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - '@types/node' @@ -7118,11 +7118,11 @@ snapshots: '@vanilla-extract/private@1.0.6': {} - '@vanilla-extract/vite-plugin@4.0.20(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(terser@5.39.0)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0))': + '@vanilla-extract/vite-plugin@4.0.20(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(terser@5.39.0)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0))': dependencies: '@vanilla-extract/compiler': 0.1.2(@types/node@20.5.1)(babel-plugin-macros@3.1.0)(terser@5.39.0) '@vanilla-extract/integration': 8.0.1(babel-plugin-macros@3.1.0) - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7139,18 +7139,18 @@ snapshots: optionalDependencies: react: 18.2.0 - '@vitejs/plugin-react@4.3.4(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0))': + '@vitejs/plugin-react@4.3.4(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0))': + '@vitest/coverage-v8@3.0.9(vitest@3.0.9)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -7175,14 +7175,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.9(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0))': + '@vitest/mocker@3.0.9(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0))': dependencies: '@vitest/spy': 3.0.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.7.3(@types/node@20.5.1)(typescript@5.8.2) - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) '@vitest/pretty-format@3.0.9': dependencies: @@ -11541,7 +11541,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - '@types/node' - less @@ -11553,7 +11553,7 @@ snapshots: - supports-color - terser - vite-plugin-checker@0.9.1(@biomejs/biome@1.9.4)(typescript@5.8.2)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)): + vite-plugin-checker@0.9.1(@biomejs/biome@1.9.4)(typescript@5.8.2)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)): dependencies: '@babel/code-frame': 7.26.2 chokidar: 4.0.3 @@ -11563,22 +11563,22 @@ snapshots: strip-ansi: 7.1.0 tiny-invariant: 1.3.3 tinyglobby: 0.2.12 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) vscode-uri: 3.1.0 optionalDependencies: '@biomejs/biome': 1.9.4 typescript: 5.8.2 - vite-plugin-compression@0.5.1(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)): + vite-plugin-compression@0.5.1(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)): dependencies: chalk: 4.1.2 debug: 4.4.0 fs-extra: 10.1.0 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - supports-color - vite-plugin-html@3.2.2(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)): + vite-plugin-html@3.2.2(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -11592,9 +11592,9 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) - vite-plugin-svg-icons@2.0.1(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)): + vite-plugin-svg-icons@2.0.1(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)): dependencies: '@types/svgo': 2.6.4 cors: 2.8.5 @@ -11604,22 +11604,22 @@ snapshots: pathe: 0.2.0 svg-baker: 1.7.0 svgo: 2.8.0 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)): dependencies: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.8.2) optionalDependencies: - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.14(@types/node@20.5.1)(terser@5.39.0): + vite@5.4.18(@types/node@20.5.1)(terser@5.39.0): dependencies: esbuild: 0.21.5 postcss: 8.5.3 @@ -11632,7 +11632,7 @@ snapshots: vitest@3.0.9(@types/debug@4.1.12)(@types/node@20.5.1)(@vitest/ui@3.0.9)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(terser@5.39.0): dependencies: '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(vite@5.4.14(@types/node@20.5.1)(terser@5.39.0)) + '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@20.5.1)(typescript@5.8.2))(vite@5.4.18(@types/node@20.5.1)(terser@5.39.0)) '@vitest/pretty-format': 3.0.9 '@vitest/runner': 3.0.9 '@vitest/snapshot': 3.0.9 @@ -11648,7 +11648,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 5.4.14(@types/node@20.5.1)(terser@5.39.0) + vite: 5.4.18(@types/node@20.5.1)(terser@5.39.0) vite-node: 3.0.9(@types/node@20.5.1)(terser@5.39.0) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..1500886 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.tsx b/src/App.tsx index 5741006..85031c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,18 @@ import { Helmet } from "react-helmet-async"; -import Logo from "@/assets/images/logo.png"; +import Logo from "@/assets/images/solar--cat-bold.png"; import Router from "@/router"; import { MotionLazy } from "./components/animate/motion-lazy"; import Toast from "./components/toast"; import { AntdAdapter } from "./theme/adapter/antd.adapter"; import { ThemeProvider } from "./theme/theme-provider"; - +const { VITE_TITLE } = import.meta.env; function App() { return ( - Slash Admin + {VITE_TITLE ?? "Slash Admin"} diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index 8a24582..dfec5a0 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -5,6 +5,7 @@ import { render, waitFor } from "@testing-library/react"; import { HelmetProvider } from "react-helmet-async"; import { BrowserRouter } from "react-router"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +const { VITE_TITLE } = import.meta.env; // Mock 静态资源 vi.mock("@/assets/images/logo.png", () => ({ default: "test-logo-path" })); @@ -49,10 +50,10 @@ describe("App Component", () => { await waitFor( () => { // 验证标题 - expect(document.title).toBe("Slash Admin"); + expect(document.title).toBe(VITE_TITLE); // 验证图标 - const favicon = document.querySelector('link[rel="icon"]'); - expect(favicon).toHaveAttribute("href", "test-logo-path"); + // const favicon = document.querySelector('link[rel="icon"]'); + // expect(favicon).toHaveAttribute("href", "test-logo-path"); }, { timeout: 2000 }, ); diff --git a/src/api/services/dependenciesService.ts b/src/api/services/dependenciesService.ts new file mode 100644 index 0000000..e59ab86 --- /dev/null +++ b/src/api/services/dependenciesService.ts @@ -0,0 +1,21 @@ +type PackageDependencies = { + [packageName: string]: string; // 键为包名,值为版本号字符串 +}; + +// 完整配置类型 +type DependenciesConfig = { + dependencies: PackageDependencies; + devDependencies: PackageDependencies; +}; + +import http from "../apiClient"; + +export enum DependenciesApi { + DevicesInformation = "/dependencies/getDevicesInformation", +} + +const getDevicesInformation = () => http.get({ url: DependenciesApi.DevicesInformation }); + +export default { + getDevicesInformation, +}; diff --git a/src/api/services/fileService.ts b/src/api/services/fileService.ts new file mode 100644 index 0000000..725f974 --- /dev/null +++ b/src/api/services/fileService.ts @@ -0,0 +1,16 @@ +import type { UploadType } from "@/api/types"; + +import http from "../apiClient"; + +export enum FileApi { + Upload = "/file/upload", + MultipleUpload = "/file/multiple", +} + +const upload = (data: any) => http.post({ url: FileApi.Upload, data }); +const multipleUpload = (data: any) => http.post({ url: FileApi.MultipleUpload, data }); + +export default { + upload, + multipleUpload, +}; diff --git a/src/api/services/index.ts b/src/api/services/index.ts index dacd3f6..2a76fd6 100644 --- a/src/api/services/index.ts +++ b/src/api/services/index.ts @@ -1,7 +1,9 @@ +import dependenciesService from "@/api/services/dependenciesService"; import emailService from "@/api/services/emailService"; +import fileService from "@/api/services/fileService"; import menuService from "@/api/services/menuService"; import roleService from "@/api/services/roleService"; import userService from "@/api/services/userService"; import weathersService from "@/api/services/weathersService"; -export { roleService, menuService, emailService, userService, weathersService }; +export { roleService, menuService, emailService, userService, dependenciesService, weathersService, fileService }; diff --git a/src/api/types.ts b/src/api/types.ts index 8a66adc..ecb4b03 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -122,3 +122,7 @@ export interface WeathersResponseType { temperature_float: string; humidity_float: string; } + +export interface UploadType { + filePath: string | string[]; +} diff --git a/src/assets/images/background/login_bg.svg b/src/assets/icons/ic-login_bg.svg similarity index 100% rename from src/assets/images/background/login_bg.svg rename to src/assets/icons/ic-login_bg.svg diff --git a/src/assets/icons/ic-solar--cat-bold.svg b/src/assets/icons/ic-solar--cat-bold.svg new file mode 100644 index 0000000..0945e97 --- /dev/null +++ b/src/assets/icons/ic-solar--cat-bold.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/logo.png b/src/assets/icons/logo.png deleted file mode 100644 index c87a895..0000000 Binary files a/src/assets/icons/logo.png and /dev/null differ diff --git a/src/assets/images/ic-login_bg.svg b/src/assets/images/ic-login_bg.svg new file mode 100644 index 0000000..34f498d --- /dev/null +++ b/src/assets/images/ic-login_bg.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/solar--cat-bold.png b/src/assets/images/solar--cat-bold.png new file mode 100644 index 0000000..ab95d41 Binary files /dev/null and b/src/assets/images/solar--cat-bold.png differ diff --git a/src/layouts/components/setting-button.tsx b/src/components/global-settings/index.tsx similarity index 99% rename from src/layouts/components/setting-button.tsx rename to src/components/global-settings/index.tsx index 613c608..f439e57 100644 --- a/src/layouts/components/setting-button.tsx +++ b/src/components/global-settings/index.tsx @@ -23,7 +23,7 @@ import { cn } from "@/utils"; /** * App Setting */ -export default function SettingButton() { +export default function GlobalSettings() { const [drawerOpen, setDrawerOpen] = useState(false); const settings = useSettings(); @@ -142,7 +142,7 @@ export default function SettingButton() { variants={varHover(1.05)} onClick={() => setDrawerOpen(true)} > - + diff --git a/src/components/icon/iconify-icon.tsx b/src/components/icon/iconify-icon.tsx index d87c928..72cff4a 100644 --- a/src/components/icon/iconify-icon.tsx +++ b/src/components/icon/iconify-icon.tsx @@ -1,8 +1,8 @@ +import type { IconProps } from "@iconify/react"; + import { Icon, disableCache } from "@iconify/react"; import styled from "styled-components"; -import type { IconProps } from "@iconify/react"; - interface Props extends IconProps { size?: IconProps["width"]; } diff --git a/src/components/locale-picker/index.tsx b/src/components/locale-picker/index.tsx index 2a4302e..3161ca0 100644 --- a/src/components/locale-picker/index.tsx +++ b/src/components/locale-picker/index.tsx @@ -29,7 +29,7 @@ export default function LocalePicker() { trigger={["click"]} menu={{ items: localeList, onClick: (e) => setLocale(e.key as Locale) }} > - + diff --git a/src/components/logo/index.tsx b/src/components/logo/index.tsx index 7553a43..703c91c 100644 --- a/src/components/logo/index.tsx +++ b/src/components/logo/index.tsx @@ -11,7 +11,7 @@ function Logo({ size = 50 }: Props) { return ( - + ); } diff --git a/src/components/upload/upload-avatar.tsx b/src/components/upload/upload-avatar.tsx index 137f7fd..c43fbe1 100644 --- a/src/components/upload/upload-avatar.tsx +++ b/src/components/upload/upload-avatar.tsx @@ -5,6 +5,7 @@ import { useState } from "react"; import { themeVars } from "@/theme/theme.css"; import { fBytes } from "@/utils/format-number"; +import { toast } from "sonner"; import { Iconify } from "../icon"; import { StyledUploadAvatar } from "./styles"; import { beforeAvatarUpload, getBlobUrl } from "./utils"; @@ -25,12 +26,21 @@ export function UploadAvatar({ helperText, defaultAvatar = "", ...other }: Props if (info.file.status === "uploading") { return; } - if (info.file.status === "done" || info.file.status === "error") { + if (info.file.status === "done") { // TODO: Get this url from response in real world. + toast.success(`${info.file.name} file uploaded successfully.`, { + position: "top-center", + }); if (info.file.originFileObj) { setImageUrl(getBlobUrl(info.file.originFileObj)); } } + + if (info.file.status === "error") { + toast.success(`${info.file.name} file uploaded successfully.`, { + position: "top-center", + }); + } }; const renderPreview = ; @@ -69,7 +79,7 @@ export function UploadAvatar({ helperText, defaultAvatar = "", ...other }: Props return ( ; + devDependencies?: Record; + }; +} + +export {}; // 确保文件被视为模块 diff --git a/src/layouts/dashboard/index.tsx b/src/layouts/dashboard/index.tsx deleted file mode 100644 index adf29b4..0000000 --- a/src/layouts/dashboard/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { CSSProperties } from "react"; - -import { Layout } from "antd"; -import { Suspense, useMemo } from "react"; - -import { CircleLoading } from "@/components/loading"; -import { down, useMediaQuery } from "@/hooks"; -import { useSettings } from "@/store/settingStore"; -import { cn } from "@/utils"; -import { ThemeLayout } from "#/enum"; -import { NAV_COLLAPSED_WIDTH, NAV_WIDTH } from "./config"; -import Header from "./header"; -import Main from "./main"; -import Nav from "./nav"; - -function DashboardLayout() { - const { themeLayout } = useSettings(); - - const mobileOrTablet = useMediaQuery(down("md")); - - const layoutClassName = useMemo(() => { - return cn("flex h-screen overflow-hidden", themeLayout === ThemeLayout.Horizontal ? "flex-col" : "flex-row"); - }, [themeLayout]); - - const secondLayoutStyle: CSSProperties = { - display: "flex", - flexDirection: "column", - transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", - paddingLeft: mobileOrTablet - ? 0 - : themeLayout === ThemeLayout.Horizontal - ? 0 - : themeLayout === ThemeLayout.Mini - ? NAV_COLLAPSED_WIDTH - : NAV_WIDTH, - }; - - return ( - - }> - -
-
+ {/* 小屏隐藏菜单栏 */} setDrawerOpen(false)} @@ -105,4 +86,4 @@ export default function Header() { ); -} +}); diff --git a/src/layouts/components/notice.tsx b/src/layouts/default/header/notice.tsx similarity index 100% rename from src/layouts/components/notice.tsx rename to src/layouts/default/header/notice.tsx diff --git a/src/layouts/components/search-bar.tsx b/src/layouts/default/header/search-bar.tsx similarity index 100% rename from src/layouts/components/search-bar.tsx rename to src/layouts/default/header/search-bar.tsx diff --git a/src/layouts/default/header/weathers-info.tsx b/src/layouts/default/header/weathers-info.tsx new file mode 100644 index 0000000..c028441 --- /dev/null +++ b/src/layouts/default/header/weathers-info.tsx @@ -0,0 +1,36 @@ +import weathersService from "@/api/services/weathersService"; +import { EnvironmentOutlined } from "@ant-design/icons"; +import { useQuery } from "@tanstack/react-query"; +import { Spin } from "antd"; + +export default function WeathersInfo() { + const { data, isPending } = useQuery({ + queryKey: ["weathersData"], + queryFn: async () => { + const res = await weathersService.getWeathers(); + // 保证返回值不是 undefined + return res[0] ?? []; + }, + staleTime: 1000 * 60 * 10, // 10分钟内不重新请求 + refetchOnWindowFocus: false, // 窗口聚焦时不自动请求 + }); + + return ( + data && ( + <> + + +
+ {data?.province} + {data?.city} + {data?.weather} + {data?.temperature}° + {data?.winddirection}风 + {data?.windpower}级 + 湿度 {data?.humidity} % +
+
+ + ) + ); +} diff --git a/src/layouts/default/index.tsx b/src/layouts/default/index.tsx new file mode 100644 index 0000000..94edd1d --- /dev/null +++ b/src/layouts/default/index.tsx @@ -0,0 +1,99 @@ +import type { TourProps } from "antd"; +import type { CSSProperties } from "react"; + +import { Layout, Tour } from "antd"; +import { Suspense, useEffect, useMemo, useRef, useState } from "react"; + +import { CircleLoading } from "@/components/loading"; +import { down, useMediaQuery } from "@/hooks"; +import { useSettingActions, useSettings } from "@/store/settingStore"; +import { cn } from "@/utils"; +import { ThemeLayout } from "#/enum"; +import { NAV_COLLAPSED_WIDTH, NAV_WIDTH } from "./config"; +import Header from "./header"; +import Main from "./main"; +import Nav from "./nav"; + +function DefaultLayout() { + const settings = useSettings(); + const { themeLayout, guide } = useSettings(); + const { setSettings } = useSettingActions(); + const mobileOrTablet = useMediaQuery(down("md")); + + const layoutClassName = useMemo(() => { + return cn("flex h-screen overflow-hidden", themeLayout === ThemeLayout.Horizontal ? "flex-col" : "flex-row"); + }, [themeLayout]); + + const headerRef = useRef(null); + const navRef = useRef(null); + const mainRef = useRef(null); + const [openTour, setOpenTour] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + + const getPaddingLeft = () => { + if (mobileOrTablet) return 0; + switch (themeLayout) { + case ThemeLayout.Horizontal: + return 0; + case ThemeLayout.Mini: + return NAV_COLLAPSED_WIDTH; + default: + return NAV_WIDTH; + } + }; + + const secondLayoutStyle: CSSProperties = { + display: "flex", + flexDirection: "column", + transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + paddingLeft: getPaddingLeft(), + }; + + const steps: TourProps["steps"] = [ + { + title: "Header Content", + description: "This is the header content area.", + target: () => headerRef.current, + }, + { + title: "Sider Content", + description: "This is the sider content area.", + placement: themeLayout === ThemeLayout.Horizontal ? "bottom" : "right", + target: () => navRef.current, + }, + { + title: "Main Content", + description: "This is the main content area.", + target: () => mainRef.current, + }, + ]; + + useEffect(() => { + if (!guide) { + setOpenTour(true); + } + }, [guide]); + + // 关闭漫游引导 + const handleTourChange = () => { + setOpenTour(false); + setSettings({ + ...settings, + guide: true, + }); + }; + + return ( + + + }> + +
+