From 8823de3e8cf8368318135fca91585a41ac92aac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=A6=82=E5=A8=81?= Date: Tue, 30 Dec 2025 17:15:46 +0800 Subject: [PATCH] feat: init --- .commitlintrc.js | 1 + .editorconfig | 16 + .gitignore | 40 + .husky/commit-msg | 1 + .husky/pre-commit | 1 + .lintstagedrc | 5 + .npmrc | 1 + README.md | 57 + biome.json | 48 + config/config.ts | 181 +++ config/defaultSettings.ts | 28 + config/oneapi.json | 593 +++++++++ config/proxy.ts | 43 + config/routes.ts | 63 + jest.config.ts | 21 + mock/listTableList.ts | 187 +++ mock/monitor.mock.ts | 14 + mock/notices.ts | 117 ++ mock/requestRecord.mock.js | 345 ++++++ mock/route.ts | 5 + mock/user.ts | 205 ++++ package.json | 75 ++ public/CNAME | 1 + public/favicon.ico | Bin 0 -> 4286 bytes public/icons/icon-128x128.png | Bin 0 -> 1329 bytes public/icons/icon-192x192.png | Bin 0 -> 1856 bytes public/icons/icon-512x512.png | Bin 0 -> 5082 bytes public/logo.svg | 1 + public/pro_icon.svg | 5 + public/scripts/loading.js | 202 +++ src/access.ts | 11 + src/app.tsx | 155 +++ src/components/Footer/index.tsx | 36 + src/components/HeaderDropdown/index.tsx | 41 + .../RightContent/AvatarDropdown.tsx | 142 +++ src/components/RightContent/index.tsx | 32 + src/components/index.ts | 12 + src/global.less | 94 ++ src/global.style.ts | 42 + src/global.tsx | 93 ++ src/loading.tsx | 7 + src/locales/README.md | 3 + src/locales/___en-US.ts | 25 + src/locales/en-US/component.ts | 5 + src/locales/en-US/globalHeader.ts | 18 + src/locales/en-US/menu.ts | 52 + src/locales/en-US/pages.ts | 76 ++ src/locales/en-US/pwa.ts | 7 + src/locales/en-US/settingDrawer.ts | 32 + src/locales/en-US/settings.ts | 61 + src/locales/zh-CN.ts | 24 + src/locales/zh-CN/component.ts | 5 + src/locales/zh-CN/globalHeader.ts | 17 + src/locales/zh-CN/menu.ts | 52 + src/locales/zh-CN/pages.ts | 71 ++ src/locales/zh-CN/pwa.ts | 6 + src/locales/zh-CN/settingDrawer.ts | 32 + src/locales/zh-CN/settings.ts | 59 + src/manifest.json | 22 + src/pages/404.tsx | 20 + src/pages/Admin.tsx | 51 + src/pages/Welcome.tsx | 166 +++ .../table-list/components/CreateForm.tsx | 80 ++ .../table-list/components/UpdateForm.tsx | 247 ++++ src/pages/table-list/index.tsx | 330 +++++ .../login/__snapshots__/login.test.tsx.snap | 1088 +++++++++++++++++ src/pages/user/login/index.tsx | 279 +++++ src/pages/user/login/login.test.tsx | 102 ++ src/requestErrorConfig.ts | 109 ++ src/service-worker.js | 66 + src/services/ant-design-pro/api.ts | 94 ++ src/services/ant-design-pro/index.ts | 10 + src/services/ant-design-pro/login.ts | 21 + src/services/ant-design-pro/typings.d.ts | 101 ++ src/services/swagger/index.ts | 12 + src/services/swagger/pet.ts | 153 +++ src/services/swagger/store.ts | 48 + src/services/swagger/typings.d.ts | 112 ++ src/services/swagger/user.ts | 100 ++ src/typings.d.ts | 18 + tests/setupTests.jsx | 71 ++ tsconfig.json | 22 + types/index.d.ts | 120 ++ 83 files changed, 6908 insertions(+) create mode 100644 .commitlintrc.js create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .lintstagedrc create mode 100644 .npmrc create mode 100644 README.md create mode 100644 biome.json create mode 100644 config/config.ts create mode 100644 config/defaultSettings.ts create mode 100644 config/oneapi.json create mode 100644 config/proxy.ts create mode 100644 config/routes.ts create mode 100644 jest.config.ts create mode 100644 mock/listTableList.ts create mode 100644 mock/monitor.mock.ts create mode 100644 mock/notices.ts create mode 100644 mock/requestRecord.mock.js create mode 100644 mock/route.ts create mode 100644 mock/user.ts create mode 100644 package.json create mode 100644 public/CNAME create mode 100644 public/favicon.ico create mode 100644 public/icons/icon-128x128.png create mode 100644 public/icons/icon-192x192.png create mode 100644 public/icons/icon-512x512.png create mode 100644 public/logo.svg create mode 100644 public/pro_icon.svg create mode 100644 public/scripts/loading.js create mode 100644 src/access.ts create mode 100644 src/app.tsx create mode 100644 src/components/Footer/index.tsx create mode 100644 src/components/HeaderDropdown/index.tsx create mode 100644 src/components/RightContent/AvatarDropdown.tsx create mode 100644 src/components/RightContent/index.tsx create mode 100644 src/components/index.ts create mode 100644 src/global.less create mode 100644 src/global.style.ts create mode 100644 src/global.tsx create mode 100644 src/loading.tsx create mode 100644 src/locales/README.md create mode 100644 src/locales/___en-US.ts create mode 100644 src/locales/en-US/component.ts create mode 100644 src/locales/en-US/globalHeader.ts create mode 100644 src/locales/en-US/menu.ts create mode 100644 src/locales/en-US/pages.ts create mode 100644 src/locales/en-US/pwa.ts create mode 100644 src/locales/en-US/settingDrawer.ts create mode 100644 src/locales/en-US/settings.ts create mode 100644 src/locales/zh-CN.ts create mode 100644 src/locales/zh-CN/component.ts create mode 100644 src/locales/zh-CN/globalHeader.ts create mode 100644 src/locales/zh-CN/menu.ts create mode 100644 src/locales/zh-CN/pages.ts create mode 100644 src/locales/zh-CN/pwa.ts create mode 100644 src/locales/zh-CN/settingDrawer.ts create mode 100644 src/locales/zh-CN/settings.ts create mode 100644 src/manifest.json create mode 100644 src/pages/404.tsx create mode 100644 src/pages/Admin.tsx create mode 100644 src/pages/Welcome.tsx create mode 100644 src/pages/table-list/components/CreateForm.tsx create mode 100644 src/pages/table-list/components/UpdateForm.tsx create mode 100644 src/pages/table-list/index.tsx create mode 100644 src/pages/user/login/__snapshots__/login.test.tsx.snap create mode 100644 src/pages/user/login/index.tsx create mode 100644 src/pages/user/login/login.test.tsx create mode 100644 src/requestErrorConfig.ts create mode 100644 src/service-worker.js create mode 100644 src/services/ant-design-pro/api.ts create mode 100644 src/services/ant-design-pro/index.ts create mode 100644 src/services/ant-design-pro/login.ts create mode 100644 src/services/ant-design-pro/typings.d.ts create mode 100644 src/services/swagger/index.ts create mode 100644 src/services/swagger/pet.ts create mode 100644 src/services/swagger/store.ts create mode 100644 src/services/swagger/typings.d.ts create mode 100644 src/services/swagger/user.ts create mode 100644 src/typings.d.ts create mode 100644 tests/setupTests.jsx create mode 100644 tsconfig.json create mode 100644 types/index.d.ts diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..422b194 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] }; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08cde4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +**/node_modules +# roadhog-api-doc ignore +/src/utils/request-temp.js +_roadhog-api-doc + +# production +/dist + +# misc +.DS_Store +npm-debug.log* +yarn-error.log + +/coverage +.idea +yarn.lock +package-lock.json +*bak +.vscode + + +# visual studio code +.history +*.log +functions/* +.temp/** + +# umi +.umi +.umi-production +.umi-test + +# screenshot +screenshot +.firebase + +build diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..0a4b97d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..c27d889 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged diff --git a/.lintstagedrc b/.lintstagedrc new file mode 100644 index 0000000..1c2023c --- /dev/null +++ b/.lintstagedrc @@ -0,0 +1,5 @@ +{ + "**/*.{js,jsx,tsx,ts,md,css,less,json}": [ + "npx @biomejs/biome check --write" + ] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1cc76f --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# AI Pipeline + +This project is initialized with [AI Pipeline](https://pro.ant.design). Follow is the quick guide for how to use. + +## Environment Prepare + +Install `node_modules`: + +```bash +npm install +``` + +or + +```bash +yarn +``` + +## Provided Scripts + +AI Pipeline provides some useful script to help you quick start and build with web project, code style check and test. + +Scripts provided in `package.json`. It's safe to modify or add additional script: + +### Start project + +```bash +npm start +``` + +### Build project + +```bash +npm run build +``` + +### Check code style + +```bash +npm run lint +``` + +You can also use script to auto fix some lint error: + +```bash +npm run lint:fix +``` + +### Test code + +```bash +npm test +``` + +## More + +You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..2f4e4e0 --- /dev/null +++ b/biome.json @@ -0,0 +1,48 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "ignoreUnknown": true, + "includes": [ + "**/*", + "!**/.umi/**", + "!**/.umi-production/**", + "!**/.umi-test/**", + "!**/.umi-test-production/**", + "!**/src/services/**", + "!**/mock/**", + "!**/dist/**", + "!**/server/**", + "!**/public/**", + "!**/coverage/**", + "!**/node_modules/**", + "!biome.json" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off" + }, + "correctness": { + "useExhaustiveDependencies": "off" + }, + "a11y": { + "noStaticElementInteractions": "off", + "useValidAnchor": "off", + "useKeyWithClickEvents": "off" + } + } + }, + "javascript": { + "jsxRuntime": "reactClassic", + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/config/config.ts b/config/config.ts new file mode 100644 index 0000000..0afaa80 --- /dev/null +++ b/config/config.ts @@ -0,0 +1,181 @@ +// https://umijs.org/config/ + +import { join } from 'node:path'; +import { defineConfig } from '@umijs/max'; +import defaultSettings from './defaultSettings'; +import proxy from './proxy'; + +import routes from './routes'; + +const { REACT_APP_ENV = 'dev' } = process.env; + +/** + * @name 使用公共路径 + * @description 部署时的路径,如果部署在非根目录下,需要配置这个变量 + * @doc https://umijs.org/docs/api/config#publicpath + */ +const PUBLIC_PATH: string = '/'; + +export default defineConfig({ + /** + * @name 开启 hash 模式 + * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 + * @doc https://umijs.org/docs/api/config#hash + */ + hash: true, + + publicPath: PUBLIC_PATH, + + /** + * @name 兼容性设置 + * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖 + * @doc https://umijs.org/docs/api/config#targets + */ + // targets: { + // ie: 11, + // }, + /** + * @name 路由的配置,不在路由中引入的文件不会编译 + * @description 只支持 path,component,routes,redirect,wrappers,title 的配置 + * @doc https://umijs.org/docs/guides/routes + */ + // umi routes: https://umijs.org/docs/routing + routes, + /** + * @name 主题的配置 + * @description 虽然叫主题,但是其实只是 less 的变量设置 + * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn + * @doc umi 的 theme 配置 https://umijs.org/docs/api/config#theme + */ + // theme: { '@primary-color': '#1DA57A' } + /** + * @name moment 的国际化配置 + * @description 如果对国际化没有要求,打开之后能减少js的包大小 + * @doc https://umijs.org/docs/api/config#ignoremomentlocale + */ + ignoreMomentLocale: true, + /** + * @name 代理配置 + * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了 + * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。 + * @doc 代理介绍 https://umijs.org/docs/guides/proxy + * @doc 代理配置 https://umijs.org/docs/api/config#proxy + */ + proxy: proxy[REACT_APP_ENV as keyof typeof proxy], + /** + * @name 快速热更新配置 + * @description 一个不错的热更新组件,更新时可以保留 state + */ + fastRefresh: true, + //============== 以下都是max的插件配置 =============== + /** + * @name 数据流插件 + * @@doc https://umijs.org/docs/max/data-flow + */ + model: {}, + /** + * 一个全局的初始数据流,可以用它在插件之间共享数据 + * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。 + * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81 + */ + initialState: {}, + /** + * @name layout 插件 + * @doc https://umijs.org/docs/max/layout-menu + */ + title: 'AI Pipeline', + layout: { + locale: true, + ...defaultSettings, + }, + /** + * @name moment2dayjs 插件 + * @description 将项目中的 moment 替换为 dayjs + * @doc https://umijs.org/docs/max/moment2dayjs + */ + moment2dayjs: { + preset: 'antd', + plugins: ['duration'], + }, + /** + * @name 国际化插件 + * @doc https://umijs.org/docs/max/i18n + */ + locale: { + // default zh-CN + default: 'zh-CN', + antd: true, + // default true, when it is true, will use `navigator.language` overwrite default + baseNavigator: true, + }, + /** + * @name antd 插件 + * @description 内置了 babel import 插件 + * @doc https://umijs.org/docs/max/antd#antd + */ + antd: { + appConfig: {}, + configProvider: { + theme: { + cssVar: true, + token: { + fontFamily: 'AlibabaSans, sans-serif', + }, + }, + }, + }, + /** + * @name 网络请求配置 + * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 + * @doc https://umijs.org/docs/max/request + */ + request: {}, + /** + * @name 权限插件 + * @description 基于 initialState 的权限插件,必须先打开 initialState + * @doc https://umijs.org/docs/max/access + */ + access: {}, + /** + * @name 中额外的 script + * @description 配置 中额外的 script + */ + headScripts: [ + // 解决首次加载时白屏的问题 + { src: join(PUBLIC_PATH, 'scripts/loading.js'), async: true }, + ], + //================ pro 插件配置 ================= + presets: ['umi-presets-pro'], + /** + * @name openAPI 插件的配置 + * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码 + * @doc https://pro.ant.design/zh-cn/docs/openapi/ + */ + openAPI: [ + { + requestLibPath: "import { request } from '@umijs/max'", + // 或者使用在线的版本 + // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" + schemaPath: join(__dirname, 'oneapi.json'), + mock: false, + }, + { + requestLibPath: "import { request } from '@umijs/max'", + schemaPath: + 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', + projectName: 'swagger', + }, + ], + mock: { + include: ['mock/**/*', 'src/pages/**/_mock.ts'], + }, + /** + * @name 是否开启 mako + * @description 使用 mako 极速研发 + * @doc https://umijs.org/docs/api/config#mako + */ + mako: {}, + esbuildMinifyIIFE: true, + requestRecord: {}, + exportStatic: {}, +}); diff --git a/config/defaultSettings.ts b/config/defaultSettings.ts new file mode 100644 index 0000000..6e47385 --- /dev/null +++ b/config/defaultSettings.ts @@ -0,0 +1,28 @@ +import type { ProLayoutProps } from '@ant-design/pro-components'; +/** + * @name + */ +// @ts-ignore +const Settings: ProLayoutProps & { + pwa?: boolean; + logo?: string; +} = { + navTheme: 'light', + // 拂晓蓝 + colorPrimary: '#1890ff', + layout: 'mix', + contentWidth: 'Fluid', + fixedHeader: false, + fixSiderbar: true, + colorWeak: false, + title: 'AI Pipeline', + pwa: true, + logo: false, + iconfontUrl: '', + token: { + // 参见ts声明,demo 见文档,通过token 修改样式 + //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F + }, +}; + +export default Settings; diff --git a/config/oneapi.json b/config/oneapi.json new file mode 100644 index 0000000..d97cb43 --- /dev/null +++ b/config/oneapi.json @@ -0,0 +1,593 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "AI Pipeline", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:8000/" + }, + { + "url": "https://localhost:8000/" + } + ], + "paths": { + "/api/currentUser": { + "get": { + "tags": ["api"], + "description": "获取当前的用户", + "operationId": "currentUser", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentUser" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/api/login/captcha": { + "post": { + "description": "发送验证码", + "operationId": "getFakeCaptcha", + "tags": ["login"], + "parameters": [ + { + "name": "phone", + "in": "query", + "description": "手机号", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FakeCaptcha" + } + } + } + } + } + } + }, + "/api/login/outLogin": { + "post": { + "description": "登录接口", + "operationId": "outLogin", + "tags": ["login"], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/api/login/account": { + "post": { + "tags": ["login"], + "description": "登录接口", + "operationId": "login", + "requestBody": { + "description": "登录系统", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginParams" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResult" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "x-codegen-request-body-name": "body" + }, + "x-swagger-router-controller": "api" + }, + "/api/notices": { + "summary": "getNotices", + "description": "NoticeIconItem", + "get": { + "tags": ["api"], + "operationId": "getNotices", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NoticeIconList" + } + } + } + } + } + } + }, + "/api/rule": { + "get": { + "tags": ["rule"], + "description": "获取规则列表", + "operationId": "rule", + "parameters": [ + { + "name": "current", + "in": "query", + "description": "当前的页码", + "schema": { + "type": "number" + } + }, + { + "name": "pageSize", + "in": "query", + "description": "页面的容量", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleList" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": ["rule"], + "description": "新建规则", + "operationId": "addRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleListItem" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "put": { + "tags": ["rule"], + "description": "新建规则", + "operationId": "updateRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RuleListItem" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": ["rule"], + "description": "删除规则", + "operationId": "removeRule", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "x-swagger-router-controller": "api" + }, + "/swagger": { + "x-swagger-pipe": "swagger_raw" + } + }, + "components": { + "schemas": { + "CurrentUser": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "title": { + "type": "string" + }, + "group": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "type": "string" + } + } + } + }, + "notifyCount": { + "type": "integer", + "format": "int32" + }, + "unreadCount": { + "type": "integer", + "format": "int32" + }, + "country": { + "type": "string" + }, + "access": { + "type": "string" + }, + "geographic": { + "type": "object", + "properties": { + "province": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "key": { + "type": "string" + } + } + }, + "city": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + } + }, + "address": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "LoginResult": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "currentAuthority": { + "type": "string" + } + } + }, + "PageParams": { + "type": "object", + "properties": { + "current": { + "type": "number" + }, + "pageSize": { + "type": "number" + } + } + }, + "RuleListItem": { + "type": "object", + "properties": { + "key": { + "type": "integer", + "format": "int32" + }, + "disabled": { + "type": "boolean" + }, + "href": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "callNo": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "integer", + "format": "int32" + }, + "updatedAt": { + "type": "string", + "format": "datetime" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "progress": { + "type": "integer", + "format": "int32" + } + } + }, + "RuleList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RuleListItem" + } + }, + "total": { + "type": "integer", + "description": "列表的内容总数", + "format": "int32" + }, + "success": { + "type": "boolean" + } + } + }, + "FakeCaptcha": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "string" + } + } + }, + "LoginParams": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "autoLogin": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "ErrorResponse": { + "required": ["errorCode"], + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "description": "业务约定的错误码" + }, + "errorMessage": { + "type": "string", + "description": "业务上的错误信息" + }, + "success": { + "type": "boolean", + "description": "业务上的请求是否成功" + } + } + }, + "NoticeIconList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NoticeIconItem" + } + }, + "total": { + "type": "integer", + "description": "列表的内容总数", + "format": "int32" + }, + "success": { + "type": "boolean" + } + } + }, + "NoticeIconItemType": { + "title": "NoticeIconItemType", + "description": "已读未读列表的枚举", + "type": "string", + "properties": {}, + "enum": ["notification", "message", "event"] + }, + "NoticeIconItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "extra": { + "type": "string", + "format": "any" + }, + "key": { "type": "string" }, + "read": { + "type": "boolean" + }, + "avatar": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date" + }, + "description": { + "type": "string" + }, + "type": { + "extensions": { + "x-is-enum": true + }, + "$ref": "#/components/schemas/NoticeIconItemType" + } + } + } + } + } +} diff --git a/config/proxy.ts b/config/proxy.ts new file mode 100644 index 0000000..6631789 --- /dev/null +++ b/config/proxy.ts @@ -0,0 +1,43 @@ +/** + * @name 代理的配置 + * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 + * ------------------------------- + * The agent cannot take effect in the production environment + * so there is no configuration of the production environment + * For details, please see + * https://pro.ant.design/docs/deploy + * + * @doc https://umijs.org/docs/guides/proxy + */ +export default { + // 如果需要自定义本地开发服务器 请取消注释按需调整 + // dev: { + // // localhost:8000/api/** -> https://preview.pro.ant.design/api/** + // '/api/': { + // // 要代理的地址 + // target: 'https://preview.pro.ant.design', + // // 配置了这个可以从 http 代理到 https + // // 依赖 origin 的功能可能需要这个,比如 cookie + // changeOrigin: true, + // }, + // }, + /** + * @name 详细的代理配置 + * @doc https://github.com/chimurai/http-proxy-middleware + */ + test: { + // localhost:8000/api/** -> https://preview.pro.ant.design/api/** + '/api/': { + target: 'https://proapi.azurewebsites.net', + changeOrigin: true, + pathRewrite: { '^': '' }, + }, + }, + pre: { + '/api/': { + target: 'your pre url', + changeOrigin: true, + pathRewrite: { '^': '' }, + }, + }, +}; diff --git a/config/routes.ts b/config/routes.ts new file mode 100644 index 0000000..171c4cb --- /dev/null +++ b/config/routes.ts @@ -0,0 +1,63 @@ +/** + * @name umi 的路由配置 + * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 + * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 + * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 + * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 + * @param redirect 配置路由跳转 + * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 + * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 + * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 user 或者 User + * @doc https://umijs.org/docs/guides/routes + */ +export default [ + { + path: '/user', + layout: false, + routes: [ + { + name: 'login', + path: '/user/login', + component: './user/login', + }, + ], + }, + { + path: '/welcome', + name: 'welcome', + icon: 'smile', + component: './Welcome', + }, + { + path: '/admin', + name: 'admin', + icon: 'crown', + access: 'canAdmin', + routes: [ + { + path: '/admin', + redirect: '/admin/sub-page', + }, + { + path: '/admin/sub-page', + name: 'sub-page', + component: './Admin', + }, + ], + }, + { + name: 'list.table-list', + icon: 'table', + path: '/list', + component: './table-list', + }, + { + path: '/', + redirect: '/welcome', + }, + { + path: '*', + layout: false, + component: './404', + }, +]; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..99051c4 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,21 @@ +import { configUmiAlias, createConfig } from '@umijs/max/test'; + +export default async (): Promise => { + const config = await configUmiAlias({ + ...createConfig({ + target: 'browser', + }), + }); + return { + ...config, + testEnvironmentOptions: { + ...(config?.testEnvironmentOptions || {}), + url: 'http://localhost:8000', + }, + setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'], + globals: { + ...config.globals, + localStorage: null, + }, + }; +}; diff --git a/mock/listTableList.ts b/mock/listTableList.ts new file mode 100644 index 0000000..dd88bf9 --- /dev/null +++ b/mock/listTableList.ts @@ -0,0 +1,187 @@ +import { parse } from 'node:url'; +import dayjs from 'dayjs'; +import type { Request, Response } from 'express'; + +// mock tableListDataSource +const genList = (current: number, pageSize: number) => { + const tableListDataSource: API.RuleListItem[] = []; + + for (let i = 0; i < pageSize; i += 1) { + const index = (current - 1) * 10 + i; + tableListDataSource.push({ + key: index, + disabled: i % 6 === 0, + href: 'https://ant.design', + avatar: [ + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + ][i % 2], + name: `TradeCode ${index}`, + owner: '曲丽丽', + desc: '这是一段描述', + callNo: Math.floor(Math.random() * 1000), + status: Math.floor(Math.random() * 10) % 4, + updatedAt: dayjs().format('YYYY-MM-DD'), + createdAt: dayjs().format('YYYY-MM-DD'), + progress: Math.ceil(Math.random() * 100), + }); + } + tableListDataSource.reverse(); + return tableListDataSource; +}; + +let tableListDataSource = genList(1, 100); + +function getRule(req: Request, res: Response, u: string) { + let realUrl = u; + if ( + !realUrl || + Object.prototype.toString.call(realUrl) !== '[object String]' + ) { + realUrl = req.url; + } + const { current = 1, pageSize = 10 } = req.query; + const params = parse(realUrl, true).query as unknown as API.PageParams & + API.RuleListItem & { + sorter: any; + filter: any; + }; + + let dataSource = [...tableListDataSource].slice( + ((current as number) - 1) * (pageSize as number), + (current as number) * (pageSize as number), + ); + if (params.sorter) { + const sorter = JSON.parse(params.sorter); + dataSource = dataSource.sort((prev, next) => { + let sortNumber = 0; + (Object.keys(sorter) as Array).forEach((key) => { + const nextSort = next?.[key] as number; + const preSort = prev?.[key] as number; + if (sorter[key] === 'descend') { + if (preSort - nextSort > 0) { + sortNumber += -1; + } else { + sortNumber += 1; + } + return; + } + if (preSort - nextSort > 0) { + sortNumber += 1; + } else { + sortNumber += -1; + } + }); + return sortNumber; + }); + } + if (params.filter) { + const filter = JSON.parse(params.filter as any) as { + [key: string]: string[]; + }; + if (Object.keys(filter).length > 0) { + dataSource = dataSource.filter((item) => { + return (Object.keys(filter) as Array).some( + (key) => { + if (!filter[key]) { + return true; + } + if (filter[key].includes(`${item[key]}`)) { + return true; + } + return false; + }, + ); + }); + } + } + + if (params.name) { + dataSource = dataSource.filter((data) => + data?.name?.includes(params.name || ''), + ); + } + const result = { + data: dataSource, + total: tableListDataSource.length, + success: true, + pageSize, + current: parseInt(`${params.current}`, 10) || 1, + }; + + return res.json(result); +} + +function postRule(req: Request, res: Response, u: string, b: Request) { + let realUrl = u; + if ( + !realUrl || + Object.prototype.toString.call(realUrl) !== '[object String]' + ) { + realUrl = req.url; + } + + const body = b?.body || req.body; + const { method, name, desc, key } = body; + + switch (method) { + case 'delete': + tableListDataSource = tableListDataSource.filter( + (item) => key.indexOf(item.key) === -1, + ); + break; + case 'post': + (() => { + const i = Math.ceil(Math.random() * 10000); + const newRule: API.RuleListItem = { + key: tableListDataSource.length, + href: 'https://ant.design', + avatar: [ + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + ][i % 2], + name, + owner: '曲丽丽', + desc, + callNo: Math.floor(Math.random() * 1000), + status: Math.floor(Math.random() * 10) % 2, + updatedAt: dayjs().format('YYYY-MM-DD'), + createdAt: dayjs().format('YYYY-MM-DD'), + progress: Math.ceil(Math.random() * 100), + }; + tableListDataSource.unshift(newRule); + return res.json(newRule); + })(); + return; + + case 'update': + (() => { + let newRule = {}; + tableListDataSource = tableListDataSource.map((item) => { + if (item.key === key) { + newRule = { ...item, desc, name }; + return { ...item, desc, name }; + } + return item; + }); + return res.json(newRule); + })(); + return; + default: + break; + } + + const result = { + list: tableListDataSource, + pagination: { + total: tableListDataSource.length, + }, + }; + + res.json(result); +} + +export default { + 'GET /api/rule': getRule, + 'POST /api/rule': postRule, +}; diff --git a/mock/monitor.mock.ts b/mock/monitor.mock.ts new file mode 100644 index 0000000..dcd1b17 --- /dev/null +++ b/mock/monitor.mock.ts @@ -0,0 +1,14 @@ +import type { Request, Response } from 'express'; +import mockjs from 'mockjs'; + +const getTags = (_: Request, res: Response) => { + return res.json({ + data: mockjs.mock({ + 'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }], + }), + }); +}; + +export default { + 'GET /api/tags': getTags, +}; diff --git a/mock/notices.ts b/mock/notices.ts new file mode 100644 index 0000000..d896311 --- /dev/null +++ b/mock/notices.ts @@ -0,0 +1,117 @@ +import type { Request, Response } from 'express'; + +const getNotices = (_req: Request, res: Response) => { + res.json({ + data: [ + { + id: '000000001', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr', + title: '你收到了 14 份新周报', + datetime: '2017-08-09', + type: 'notification', + }, + { + id: '000000002', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr', + title: '你推荐的 曲妮妮 已通过第三轮面试', + datetime: '2017-08-08', + type: 'notification', + }, + { + id: '000000003', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr', + title: '这种模板可以区分多种通知类型', + datetime: '2017-08-07', + read: true, + type: 'notification', + }, + { + id: '000000004', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr', + title: '左侧图标用于区分不同的类型', + datetime: '2017-08-07', + type: 'notification', + }, + { + id: '000000005', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr', + title: '内容不要超过两行字,超出时自动截断', + datetime: '2017-08-07', + type: 'notification', + }, + { + id: '000000006', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr', + title: '曲丽丽 评论了你', + description: '描述信息描述信息描述信息', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000007', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr', + title: '朱偏右 回复了你', + description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000008', + avatar: + 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr', + title: '标题', + description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', + datetime: '2017-08-07', + type: 'message', + clickClose: true, + }, + { + id: '000000009', + title: '任务名称', + description: '任务需要在 2017-01-12 20:00 前启动', + extra: '未开始', + status: 'todo', + type: 'event', + }, + { + id: '000000010', + title: '第三方紧急代码变更', + description: + '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', + extra: '马上到期', + status: 'urgent', + type: 'event', + }, + { + id: '000000011', + title: '信息安全考试', + description: '指派竹尔于 2017-01-09 前完成更新并发布', + extra: '已耗时 8 天', + status: 'doing', + type: 'event', + }, + { + id: '000000012', + title: 'ABCD 版本发布', + description: + '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', + extra: '进行中', + status: 'processing', + type: 'event', + }, + ], + }); +}; + +export default { + 'GET /api/notices': getNotices, +}; diff --git a/mock/requestRecord.mock.js b/mock/requestRecord.mock.js new file mode 100644 index 0000000..7c8f0de --- /dev/null +++ b/mock/requestRecord.mock.js @@ -0,0 +1,345 @@ +module.exports = { + 'GET /api/currentUser': { + data: { + name: 'Serati Ma', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', + userid: '00000001', + email: 'antdesign@alipay.com', + signature: '海纳百川,有容乃大', + title: '交互专家', + group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', + tags: [ + { key: '0', label: '很有想法的' }, + { key: '1', label: '专注设计' }, + { key: '2', label: '辣~' }, + { key: '3', label: '大长腿' }, + { key: '4', label: '川妹子' }, + { key: '5', label: '海纳百川' }, + ], + notifyCount: 12, + unreadCount: 11, + country: 'China', + geographic: { + province: { label: '浙江省', key: '330000' }, + city: { label: '杭州市', key: '330100' }, + }, + address: '西湖区工专路 77 号', + phone: '0752-268888888', + }, + }, + 'GET /api/rule': { + data: [ + { + key: 99, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 99', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 503, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 81, + }, + { + key: 98, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 98', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 164, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 12, + }, + { + key: 97, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 97', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 174, + status: '1', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 81, + }, + { + key: 96, + disabled: true, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 96', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 914, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 7, + }, + { + key: 95, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 95', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 698, + status: '2', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 82, + }, + { + key: 94, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 94', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 488, + status: '1', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 14, + }, + { + key: 93, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 93', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 580, + status: '2', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 77, + }, + { + key: 92, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 92', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 244, + status: '3', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 58, + }, + { + key: 91, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 91', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 959, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 66, + }, + { + key: 90, + disabled: true, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 90', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 958, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 72, + }, + { + key: 89, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 89', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 301, + status: '2', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 2, + }, + { + key: 88, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 88', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 277, + status: '1', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 12, + }, + { + key: 87, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 87', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 810, + status: '1', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 82, + }, + { + key: 86, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 86', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 780, + status: '3', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 22, + }, + { + key: 85, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 85', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 705, + status: '3', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 12, + }, + { + key: 84, + disabled: true, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 84', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 203, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 79, + }, + { + key: 83, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 83', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 491, + status: '2', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 59, + }, + { + key: 82, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 82', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 73, + status: '0', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 100, + }, + { + key: 81, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', + name: 'TradeCode 81', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 406, + status: '3', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 61, + }, + { + key: 80, + disabled: false, + href: 'https://ant.design', + avatar: + 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', + name: 'TradeCode 80', + owner: '曲丽丽', + desc: '这是一段描述', + callNo: 112, + status: '2', + updatedAt: '2022-12-06T05:00:57.040Z', + createdAt: '2022-12-06T05:00:57.040Z', + progress: 20, + }, + ], + total: 100, + success: true, + pageSize: 20, + current: 1, + }, + 'POST /api/login/outLogin': { data: {}, success: true }, + 'POST /api/login/account': { + status: 'ok', + type: 'account', + currentAuthority: 'admin', + }, +}; diff --git a/mock/route.ts b/mock/route.ts new file mode 100644 index 0000000..418d10f --- /dev/null +++ b/mock/route.ts @@ -0,0 +1,5 @@ +export default { + '/api/auth_routes': { + '/form/advanced-form': { authority: ['admin', 'user'] }, + }, +}; diff --git a/mock/user.ts b/mock/user.ts new file mode 100644 index 0000000..06125bf --- /dev/null +++ b/mock/user.ts @@ -0,0 +1,205 @@ +import type { Request, Response } from 'express'; + +const waitTime = (time: number = 100) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, time); + }); +}; + +async function getFakeCaptcha(_req: Request, res: Response) { + await waitTime(2000); + return res.json('captcha-xxx'); +} + +const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; + +/** + * 当前用户的权限,如果为空代表没登录 + * current user access, if is '', user need login + * 如果是 pro 的预览,默认是有权限的 + */ +let access = + ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; + +const getAccess = () => { + return access; +}; + +// 代码中会兼容本地 service mock 以及部署站点的静态数据 +export default { + // 支持值为 Object 和 Array + 'GET /api/currentUser': (_req: Request, res: Response) => { + if (!getAccess()) { + res.status(401).send({ + data: { + isLogin: false, + }, + errorCode: '401', + errorMessage: '请先登录!', + success: true, + }); + return; + } + res.send({ + success: true, + data: { + name: 'Serati Ma', + avatar: + 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', + userid: '00000001', + email: 'antdesign@alipay.com', + signature: '海纳百川,有容乃大', + title: '交互专家', + group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', + tags: [ + { + key: '0', + label: '很有想法的', + }, + { + key: '1', + label: '专注设计', + }, + { + key: '2', + label: '辣~', + }, + { + key: '3', + label: '大长腿', + }, + { + key: '4', + label: '川妹子', + }, + { + key: '5', + label: '海纳百川', + }, + ], + notifyCount: 12, + unreadCount: 11, + country: 'China', + access: getAccess(), + geographic: { + province: { + label: '浙江省', + key: '330000', + }, + city: { + label: '杭州市', + key: '330100', + }, + }, + address: '西湖区工专路 77 号', + phone: '0752-268888888', + }, + }); + }, + // GET POST 可省略 + 'GET /api/users': [ + { + key: '1', + name: 'John Brown', + age: 32, + address: 'New York No. 1 Lake Park', + }, + { + key: '2', + name: 'Jim Green', + age: 42, + address: 'London No. 1 Lake Park', + }, + { + key: '3', + name: 'Joe Black', + age: 32, + address: 'Sidney No. 1 Lake Park', + }, + ], + 'POST /api/login/account': async (req: Request, res: Response) => { + const { password, username, type } = req.body; + await waitTime(2000); + if (password === 'ant.design' && username === 'admin') { + res.send({ + status: 'ok', + type, + currentAuthority: 'admin', + }); + access = 'admin'; + return; + } + if (password === 'ant.design' && username === 'user') { + res.send({ + status: 'ok', + type, + currentAuthority: 'user', + }); + access = 'user'; + return; + } + if (type === 'mobile') { + res.send({ + status: 'ok', + type, + currentAuthority: 'admin', + }); + access = 'admin'; + return; + } + + res.send({ + status: 'error', + type, + currentAuthority: 'guest', + }); + access = 'guest'; + }, + 'POST /api/login/outLogin': (_req: Request, res: Response) => { + access = ''; + res.send({ data: {}, success: true }); + }, + 'POST /api/register': (_req: Request, res: Response) => { + res.send({ status: 'ok', currentAuthority: 'user', success: true }); + }, + 'GET /api/500': (_req: Request, res: Response) => { + res.status(500).send({ + timestamp: 1513932555104, + status: 500, + error: 'error', + message: 'error', + path: '/base/category/list', + }); + }, + 'GET /api/404': (_req: Request, res: Response) => { + res.status(404).send({ + timestamp: 1513932643431, + status: 404, + error: 'Not Found', + message: 'No message available', + path: '/base/category/list/2121212', + }); + }, + 'GET /api/403': (_req: Request, res: Response) => { + res.status(403).send({ + timestamp: 1513932555104, + status: 403, + error: 'Forbidden', + message: 'Forbidden', + path: '/base/category/list', + }); + }, + 'GET /api/401': (_req: Request, res: Response) => { + res.status(401).send({ + timestamp: 1513932555104, + status: 401, + error: 'Unauthorized', + message: 'Unauthorized', + path: '/base/category/list', + }); + }, + + 'GET /api/login/captcha': getFakeCaptcha, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..bec8bd3 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "ant-design-pro", + "version": "6.0.0", + "private": true, + "description": "An out-of-box UI solution for enterprise applications", + "repository": "git@github.com:ant-design/ant-design-pro.git", + "scripts": { + "analyze": "cross-env ANALYZE=1 max build", + "biome:lint": "npx @biomejs/biome lint", + "build": "max build", + "deploy": "npm run build && npm run gh-pages", + "dev": "npm run start:dev", + "gh-pages": "gh-pages -d dist", + "i18n-remove": "pro i18n-remove --locale=zh-CN --write", + "postinstall": "max setup", + "jest": "jest", + "lint": "npm run biome:lint && npm run tsc", + "lint-staged": "lint-staged", + "openapi": "max openapi", + "prepare": "husky", + "preview": "npm run build && max preview --port 8000", + "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login", + "serve": "umi-serve", + "start": "cross-env UMI_ENV=dev max dev", + "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev", + "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev", + "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", + "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", + "test": "jest", + "test:coverage": "npm run jest -- --coverage", + "test:update": "npm run jest -- -u", + "tsc": "tsc --noEmit" + }, + "browserslist": ["defaults"], + "dependencies": { + "@ant-design/icons": "^5.6.1", + "@ant-design/pro-components": "^2.7.19", + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "antd": "^5.25.4", + "antd-style": "^3.7.0", + "classnames": "^2.5.1", + "dayjs": "^1.11.13", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@ant-design/pro-cli": "^3.3.0", + "@biomejs/biome": "^2.0.6", + "@commitlint/cli": "^19.5.0", + "@commitlint/config-conventional": "^19.5.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.0.1", + "@types/express": "^5.0.3", + "@types/jest": "^30.0.0", + "@types/lodash": "^4.17.10", + "@types/node": "^24.0.10", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@types/react-helmet": "^6.1.11", + "@umijs/max": "^4.3.24", + "cross-env": "^7.0.3", + "express": "^4.21.1", + "gh-pages": "^6.1.1", + "husky": "^9.1.7", + "jest": "^30.0.4", + "jest-environment-jsdom": "^29.7.0", + "lint-staged": "^16.1.2", + "mockjs": "^1.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.3", + "umi-presets-pro": "^2.0.3", + "umi-serve": "^1.9.11" + }, + "engines": { "node": ">=20.0.0" } +} diff --git a/public/CNAME b/public/CNAME new file mode 100644 index 0000000..30c2d4d --- /dev/null +++ b/public/CNAME @@ -0,0 +1 @@ +preview.pro.ant.design \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e2e93252988b5bed120ddeb168eb577bf0caa881 GIT binary patch literal 4286 zcmeHLO(?8k6#ga#Gs=ufvobTd=q@y~nOLdYlqL%nQY4WqC`)z}3y~cqOAAF4VkQd~ z^3$wrC}~D1l%!eMp!|gA&U5sN?~b{2>)yNg-s#Lc-+9k_p7Y<=XqqYcDJs(Vt%dBG z=F~Lpdy*pd!Y zozZT}udJ-#| zqiAVq0sT@kAMMobbULxOw+F6{j*egO8CQ?TgTuo^+}_@zrlv;l$*-)e#PRVl?(Xgo zi9`?zg>ZFsh0DuJ)Ya9!von7LPN@OfP9IfH_4tet&&I~a#O{TK1<3*LO-xJ( zZ*Fc5%v&m4#Z&mZySs8{XJ-fV^Yh@{p`ju8d_J)w6+U%`!(qAC+uJK~qg_o+O|QC` zTVg0Y{jSHqzP^^&G0t4^cwF+$`egj?@9$AtTMLuPB>7!lUKY8&zP?xd=H_PM(U;HS ztNi!(_oJes0<5X6tu4{BxVR{FO)_x3UauFkv$I%RTSHe@7dAIHF+4mhc?|>t(!XbCXVQZk z8yje7Xh2R*&WCwdw&?N4$H!4$Uyq)i9_;V$3;y)^-LKhmkv{Z#(CP^-7Wn# zI5-H>z`y`nTU%u=f7Tbh-mg5fPT0G8`Wx6^vD1(>-r0;AFlyjWYC!!D@UQlt)%}4^ literal 0 HcmV?d00001 diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..48d0e2339a60a637b94319c65e8654289b4f4b6c GIT binary patch literal 1329 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

*C7T$?5!b= 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 28 Copy 5Created with Sketch. \ No newline at end of file diff --git a/public/pro_icon.svg b/public/pro_icon.svg new file mode 100644 index 0000000..e075b78 --- /dev/null +++ b/public/pro_icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/scripts/loading.js b/public/scripts/loading.js new file mode 100644 index 0000000..a4fdb3d --- /dev/null +++ b/public/scripts/loading.js @@ -0,0 +1,202 @@ +/** + * loading 占位 + * 解决首次加载时白屏的问题 + */ +(function () { + const _root = document.querySelector('#root'); + if (_root && _root.innerHTML === '') { + _root.innerHTML = ` + + +

+
+
+ + + + + + +
+
+
+ 正在加载资源 +
+
+ 初次加载资源可能需要较多时间 请耐心等待 +
+
+ `; + } +})(); diff --git a/src/access.ts b/src/access.ts new file mode 100644 index 0000000..373d9fa --- /dev/null +++ b/src/access.ts @@ -0,0 +1,11 @@ +/** + * @see https://umijs.org/docs/max/access#access + * */ +export default function access( + initialState: { currentUser?: API.CurrentUser } | undefined, +) { + const { currentUser } = initialState ?? {}; + return { + canAdmin: currentUser && currentUser.access === 'admin', + }; +} diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000..599b811 --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,155 @@ +import { LinkOutlined } from '@ant-design/icons'; +import type { Settings as LayoutSettings } from '@ant-design/pro-components'; +import { SettingDrawer } from '@ant-design/pro-components'; +import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max'; +import { history, Link } from '@umijs/max'; +import React from 'react'; +import { + AvatarDropdown, + AvatarName, + Footer, + Question, + SelectLang, +} from '@/components'; +import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; +import defaultSettings from '../config/defaultSettings'; +import { errorConfig } from './requestErrorConfig'; +import '@ant-design/v5-patch-for-react-19'; + +const isDev = process.env.NODE_ENV === 'development'; +const loginPath = '/user/login'; + +/** + * @see https://umijs.org/docs/api/runtime-config#getinitialstate + * */ +export async function getInitialState(): Promise<{ + settings?: Partial; + currentUser?: API.CurrentUser; + loading?: boolean; + fetchUserInfo?: () => Promise; +}> { + const fetchUserInfo = async () => { + try { + const msg = await queryCurrentUser({ + skipErrorHandler: true, + }); + return msg.data; + } catch (_error) { + history.push(loginPath); + } + return undefined; + }; + // 如果不是登录页面,执行 + const { location } = history; + if ( + ![loginPath, '/user/register', '/user/register-result'].includes( + location.pathname, + ) + ) { + const currentUser = await fetchUserInfo(); + return { + fetchUserInfo, + currentUser, + settings: defaultSettings as Partial, + }; + } + return { + fetchUserInfo, + settings: defaultSettings as Partial, + }; +} + +// ProLayout 支持的api https://procomponents.ant.design/components/layout +export const layout: RunTimeLayoutConfig = ({ + initialState, + setInitialState, +}) => { + return { + actionsRender: () => [ + , + , + ], + avatarProps: { + src: initialState?.currentUser?.avatar, + title: , + render: (_, avatarChildren) => { + return {avatarChildren}; + }, + }, + waterMarkProps: { + content: initialState?.currentUser?.name, + }, + footerRender: () =>