diff --git a/config/config.ts b/config/config.ts index 7c27b33..6416cc7 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,159 +1,159 @@ -// https://umijs.org/config/ -import { defineConfig } from '@umijs/max'; -import { join } from 'path'; -import defaultSettings from './defaultSettings'; -import proxy from './proxy'; -import routes from './routes'; - -const { REACT_APP_ENV = 'dev' } = process.env; - -export default defineConfig({ - /** - * @name 开启 hash 模式 - * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 - * @doc https://umijs.org/docs/api/config#hash - */ - hash: true, - - /** - * @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: { - // 如果不想要 configProvide 动态设置主题需要把这个设置为 default - // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 - 'root-entry-name': 'variable', - }, - /** - * @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: '青岛智慧就业服务系统', - layout: { - locale: false, - ...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: false, - // default true, when it is true, will use `navigator.language` overwrite default - baseNavigator: false, - }, - /** - * @name antd 插件 - * @description 内置了 babel import 插件 - * @doc https://umijs.org/docs/max/antd#antd - */ - antd: {}, - /** - * @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: '/qingdao/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', - }, - ], - mfsu: { - strategy: 'normal', - }, - outputPath: 'qingdao', - base: '/qingdao/', - publicPath: '/qingdao/', - esbuildMinifyIIFE: true, - requestRecord: {}, -}); +// https://umijs.org/config/ +import { defineConfig } from '@umijs/max'; +import { join } from 'path'; +import defaultSettings from './defaultSettings'; +import proxy from './proxy'; +import routes from './routes'; + +const { REACT_APP_ENV = 'dev' } = process.env; + +export default defineConfig({ + /** + * @name 开启 hash 模式 + * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 + * @doc https://umijs.org/docs/api/config#hash + */ + hash: true, + + /** + * @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: { + // 如果不想要 configProvide 动态设置主题需要把这个设置为 default + // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 + 'root-entry-name': 'variable', + }, + /** + * @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: '青岛智慧就业服务系统', + layout: { + locale: false, + ...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: false, + // default true, when it is true, will use `navigator.language` overwrite default + baseNavigator: false, + }, + /** + * @name antd 插件 + * @description 内置了 babel import 插件 + * @doc https://umijs.org/docs/max/antd#antd + */ + antd: {}, + /** + * @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: '/qingdao/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', + }, + ], + mfsu: { + strategy: 'normal', + }, + outputPath: 'qingdao', + base: '/qingdao/', + publicPath: '/qingdao/', + esbuildMinifyIIFE: true, + requestRecord: {}, +}); diff --git a/config/routes.ts b/config/routes.ts index b14a729..70645ef 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -1,119 +1,120 @@ -/** - * @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: '/', - redirect: '/account/center', - }, - { - path: '*', - layout: false, - component: './404', - }, - { - path: '/user', - layout: false, - routes: [ - { - name: 'login', - path: '/user/login', - component: './User/Login', - }, - ], - }, - { - path: '/account', - routes: [ - { - name: 'acenter', - path: '/account/center', - component: './User/Center', - }, - { - name: 'asettings', - path: '/account/settings', - component: './User/Settings', - }, - ], - }, - { - name: 'area', - path: '/area', - routes: [ - { - name: '字典数据', - path: '/area/updata-router/index/:id', - component: './Area/Subway/UpLine', - }, - ], - }, - { - name: 'management', - path: '/management', - routes: [ - { - name: '字典数据', - path: '/management/see-matching/index/:id', - component: './Management/List/SeeMatching', - }, - ], - }, - { - name: 'system', - path: '/system', - routes: [ - { - name: '字典数据', - path: '/system/dict-data/index/:id', - component: './System/DictData', - }, - { - name: '字典数据', - path: '/system/admin-dict-data/index/:id', - component: './System/AdminDictData', - }, - { - name: '分配用户', - path: '/system/role-auth/user/:id', - component: './System/Role/authUser', - }, - ], - }, - { - name: 'monitor', - path: '/monitor', - routes: [ - { - name: '任务日志', - path: '/monitor/job-log/index/:id', - component: './Monitor/JobLog', - }, - ], - }, - { - name: 'tool', - path: '/tool', - routes: [ - { - name: '导入表', - path: '/tool/gen/import', - component: './Tool/Gen/import', - }, - { - name: '编辑表', - path: '/tool/gen/edit.tsx', - component: './Tool/Gen/edit.tsx', - }, - ], - }, -]; +/** + * @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: '/', + redirect: '/account/center', + }, + { + path: '*', + layout: false, + component: './404', + }, + { + path: '/user', + layout: false, + routes: [ + { + name: 'login', + path: '/user/login', + component: './User/Login', + }, + ], + }, + { + path: '/account', + routes: [ + { + name: 'acenter', + path: '/account/center', + component: './User/Center', + }, + { + name: 'asettings', + path: '/account/settings', + component: './User/Settings', + }, + ], + }, + { + name: 'area', + path: '/area', + routes: [ + { + name: '字典数据', + path: '/area/updata-router/index/:id', + component: './Area/Subway/UpLine', + }, + ], + }, + { + name: 'management', + path: '/management', + routes: [ + { + name: '字典数据', + path: '/management/see-matching/index/:id', + component: './Management/List/SeeMatching', + }, + ], + }, + { + name: 'system', + path: '/system', + routes: [ + { + name: '字典数据', + path: '/system/dict-data/index/:id', + component: './System/DictData', + }, + { + name: '字典数据', + path: '/system/admin-dict-data/index/:id', + component: './System/AdminDictData', + }, + { + name: '分配用户', + path: '/system/role-auth/user/:id', + component: './System/Role/authUser', + }, + ], + }, + { + name: 'monitor', + path: '/monitor', + routes: [ + { + name: '任务日志', + path: '/monitor/job-log/index/:id', + component: './Monitor/JobLog', + }, + ], + }, + { + name: 'tool', + path: '/tool', + routes: [ + { + name: '导入表', + path: '/tool/gen/import', + component: './Tool/Gen/import', + }, + { + name: '编辑表', + path: '/tool/gen/edit.tsx', + component: './Tool/Gen/edit.tsx', + }, + ], + }, + +]; diff --git a/package.json b/package.json index d6e21ad..e0b9123 100644 --- a/package.json +++ b/package.json @@ -1,145 +1,146 @@ -{ - "name": "ant-design-pro", - "version": "6.0.0", - "private": true, - "description": "An out-of-box UI solution for enterprise applications", - "scripts": { - "dev": "npm run start:dev", - "build": "max build", - "deploy": "npm run build && npm run gh-pages", - "preview": "npm run build && max preview --port 8000", - "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", - "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", - "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", - "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", - "docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build", - "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", - "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", - "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", - "analyze": "cross-env ANALYZE=1 max build", - "gh-pages": "gh-pages -d dist", - "i18n-remove": "pro i18n-remove --locale=zh-CN --write", - "postinstall": "max setup", - "jest": "jest", - "lint": "npm run lint:js && npm run lint:prettier && npm run tsc", - "lint-staged": "lint-staged", - "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", - "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ", - "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", - "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", - "openapi": "max openapi", - "prepare": "cd .. && husky install", - "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", - "tsc": "tsc --noEmit", - "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login" - }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", - "**/*.{js,jsx,tsx,ts,less,md,json}": [ - "prettier --write" - ] - }, - "browserslist": [ - "> 1%", - "last 2 versions", - "not ie <= 10" - ], - "dependencies": { - "@amap/amap-jsapi-loader": "^1.0.1", - "@ant-design/icons": "^5.5.0", - "@ant-design/plots": "^2.3.2", - "@ant-design/pro-components": "^2.7.19", - "@ant-design/use-emotion-css": "1.0.4", - "@testing-library/dom": "^10.4.0", - "@umijs/route-utils": "^4.0.1", - "ant-design-pro": "file:", - "antd": "^5.21.1", - "antd-style": "^3.6.2", - "classnames": "^2.5.1", - "dayjs": "^1.11.13", - "echarts": "^5.6.0", - "fabric": "^6.4.0", - "highlight.js": "^11.10.0", - "lodash": "^4.17.21", - "moment": "^2.30.1", - "omit.js": "^2.0.2", - "query-string": "^9.1.0", - "rc-menu": "^9.15.0", - "rc-util": "^5.43.0", - "react": "^18.3.0", - "react-cropper": "^2.3.3", - "react-dev-inspector": "^2.0.1", - "react-dom": "^18.3.0", - "react-helmet-async": "^2.0.0", - "react-highlight": "^0.15.0" - }, - "devDependencies": { - "@ant-design/pro-cli": "^3.3.0", - "@testing-library/react": "^16.0.1", - "@types/classnames": "^2.3.1", - "@types/express": "^4.17.21", - "@types/history": "^4.7.11", - "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.4", - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", - "@types/react-helmet": "^6.1.11", - "@umijs/fabric": "^2.14.1", - "@umijs/lint": "^4.2.9", - "@umijs/max": "^4.2.9", - "cross-env": "^7.0.3", - "eslint": "^9.11.0", - "express": "^4.21.0", - "gh-pages": "^6.1.0", - "husky": "^9.1.3", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "lint-staged": "^15.2.0", - "mockjs": "^1.1.0", - "prettier": "^3.3.0", - "swagger-ui-dist": "^5.17.14", - "ts-node": "^10.9.1", - "typescript": "^5.6.2", - "umi-presets-pro": "^2.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "create-umi": { - "ignoreScript": [ - "docker*", - "functions*", - "site", - "generateMock" - ], - "ignoreDependencies": [ - "netlify*", - "serverless" - ], - "ignore": [ - ".dockerignore", - ".git", - ".github", - ".gitpod.yml", - "CODE_OF_CONDUCT.md", - "Dockerfile", - "Dockerfile.*", - "lambda", - "LICENSE", - "netlify.toml", - "README.*.md", - "azure-pipelines.yml", - "docker", - "CNAME", - "create-umi" - ] - } -} +{ + "name": "ant-design-pro", + "version": "6.0.0", + "private": true, + "description": "An out-of-box UI solution for enterprise applications", + "scripts": { + "dev": "npm run start:dev", + "build": "max build", + "deploy": "npm run build && npm run gh-pages", + "preview": "npm run build && max preview --port 8000", + "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", + "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", + "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", + "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", + "docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build", + "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", + "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", + "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", + "analyze": "cross-env ANALYZE=1 max build", + "gh-pages": "gh-pages -d dist", + "i18n-remove": "pro i18n-remove --locale=zh-CN --write", + "postinstall": "max setup", + "jest": "jest", + "lint": "npm run lint:js && npm run lint:prettier && npm run tsc", + "lint-staged": "lint-staged", + "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", + "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ", + "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", + "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", + "openapi": "max openapi", + "prepare": "cd .. && husky install", + "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", + "tsc": "tsc --noEmit", + "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", + "**/*.{js,jsx,tsx,ts,less,md,json}": [ + "prettier --write" + ] + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 10" + ], + "dependencies": { + "@amap/amap-jsapi-loader": "^1.0.1", + "@ant-design/charts": "^2.3.0", + "@ant-design/icons": "^5.5.0", + "@ant-design/plots": "^2.3.2", + "@ant-design/pro-components": "^2.8.7", + "@ant-design/use-emotion-css": "1.0.4", + "@testing-library/dom": "^10.4.0", + "@umijs/route-utils": "^4.0.1", + "ant-design-pro": "file:", + "antd": "^5.21.1", + "antd-style": "^3.6.2", + "classnames": "^2.5.1", + "dayjs": "^1.11.13", + "echarts": "^5.6.0", + "fabric": "^6.4.0", + "highlight.js": "^11.10.0", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "omit.js": "^2.0.2", + "query-string": "^9.1.0", + "rc-menu": "^9.15.0", + "rc-util": "^5.43.0", + "react": "^18.3.0", + "react-cropper": "^2.3.3", + "react-dev-inspector": "^2.0.1", + "react-dom": "^18.3.0", + "react-helmet-async": "^2.0.0", + "react-highlight": "^0.15.0" + }, + "devDependencies": { + "@ant-design/pro-cli": "^3.3.0", + "@testing-library/react": "^16.0.1", + "@types/classnames": "^2.3.1", + "@types/express": "^4.17.21", + "@types/history": "^4.7.11", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.17.4", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@types/react-helmet": "^6.1.11", + "@umijs/fabric": "^2.14.1", + "@umijs/lint": "^4.2.9", + "@umijs/max": "^4.2.9", + "cross-env": "^7.0.3", + "eslint": "^9.11.0", + "express": "^4.21.0", + "gh-pages": "^6.1.0", + "husky": "^9.1.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "lint-staged": "^15.2.0", + "mockjs": "^1.1.0", + "prettier": "^3.3.0", + "swagger-ui-dist": "^5.17.14", + "ts-node": "^10.9.1", + "typescript": "^5.6.2", + "umi-presets-pro": "^2.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "create-umi": { + "ignoreScript": [ + "docker*", + "functions*", + "site", + "generateMock" + ], + "ignoreDependencies": [ + "netlify*", + "serverless" + ], + "ignore": [ + ".dockerignore", + ".git", + ".github", + ".gitpod.yml", + "CODE_OF_CONDUCT.md", + "Dockerfile", + "Dockerfile.*", + "lambda", + "LICENSE", + "netlify.toml", + "README.*.md", + "azure-pipelines.yml", + "docker", + "CNAME", + "create-umi" + ] + } +} diff --git a/src/global.less b/src/global.less index 6b69bce..8d30145 100644 --- a/src/global.less +++ b/src/global.less @@ -20,24 +20,14 @@ body, .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed { left: unset; } -.ant-table-cell .ant-table-row-expand-icon { - vertical-align: middle; - margin-right: 8px; -} .ant-table-row { - &-level-0 .ant-table-cell:first-child { - padding-left: 16px !important; - } &-level-1 .ant-table-cell:first-child { - padding-left: 40px !important; + padding-left: 24px !important; } &-level-2 .ant-table-cell:first-child { - padding-left: 64px !important; + padding-left: 48px !important; } -} -.ant-table-row .ant-table-cell:first-child { - display: flex; - align-items: center; + // 可根据需要添加更多层级 } .ant-table-row-level-2 .ant-table-row-expand-icon { display: none; @@ -74,4 +64,4 @@ ol { } } } -} +} \ No newline at end of file diff --git a/src/pages/Analysis/Industrytrend/index.tsx b/src/pages/Analysis/Industrytrend/index.tsx new file mode 100644 index 0000000..1d793b7 --- /dev/null +++ b/src/pages/Analysis/Industrytrend/index.tsx @@ -0,0 +1,289 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { Card, Select, Button, Space, Spin, Empty, Row, Col, message } from 'antd'; +import { Line } from '@ant-design/charts'; +import { getIndustryTrend } from '@/services/analysis/industry'; +import dayjs from 'dayjs'; +import { useRequest } from '@umijs/max'; + +const { Option } = Select; +type IndustryDataItem = { + date: string; + category: string; + value: number; +}; +const IndustryTrendPage: React.FC = () => { + const [params, setParams] = useState({ + timeDimension: '月' as '月' | '季度' | '年', + type: '岗位发布数量' as '岗位发布数量' | '招聘增长率', + startTime: dayjs().subtract(6, 'month').format('YYYY-MM'), + endTime: dayjs().format('YYYY-MM'), + selectedIndustry: '' + }); + const [allData, setAllData] = useState([]); + const [prevSelectedIndustry, setPrevSelectedIndustry] = useState(''); + const { loading, run } = useRequest( + () => getIndustryTrend({ + ...params, + startTime: formatTimeParam(params.startTime, params.timeDimension), + endTime: formatTimeParam(params.endTime, params.timeDimension) + }), + { + manual: true, + onError: (error) => message.error('数据加载失败: ' + error.message), + onSuccess: (responseData) => { + const formattedData = convertApiData(responseData); + setAllData(formattedData); + const industryToSelect = prevSelectedIndustry || + (formattedData.length > 0 ? formattedData[0].category : ''); + setParams(prev => ({ ...prev, selectedIndustry: industryToSelect })); + } + } + ); + const convertApiData = (apiData: any): IndustryDataItem[] => { + if (!apiData) return []; + + try { + const result: IndustryDataItem[] = []; + if (typeof apiData === 'object' && !Array.isArray(apiData)) { + Object.entries(apiData).forEach(([month, monthData]) => { + if (Array.isArray(monthData)) { + (monthData as any[]).forEach(item => { + const convertedItem = { + date: month, + category: item.name || item.category, + value: Number(item.data || item.value) || 0 + }; + console.log('转换后的数据项:', convertedItem); + result.push(convertedItem); + }); + } + }); + return result; + } + if (Array.isArray(apiData)) { + return apiData.map(item => { + const convertedItem = { + date: item.time || item.date, + category: item.name || item.category, + value: Number(item.data || item.value) || 0 + }; + console.log('转换后的数据项:', convertedItem); + return convertedItem; + }); + } + + return []; + } catch (error) { + console.error('数据转换错误:', error); + return []; + } + }; + const industries = useMemo(() => { + if (allData.length === 0) return []; + const categories = new Set(); + allData.forEach(item => categories.add(item.category)); + return Array.from(categories).sort(); + }, [allData]); + const formatTimeParam = (dateStr: string, dimension: string) => { + const date = dayjs(dateStr); + switch (dimension) { + case '季度': return `${date.year()}-Q${Math.ceil((date.month() + 1) / 3)}`; + case '年': return `${date.year()}`; + default: return date.format('YYYY-MM'); + } + }; + const handleIndustryChange = (value: string) => { + setPrevSelectedIndustry(value); + setParams(p => ({ ...p, selectedIndustry: value })); + }; + + const currentIndustryData = useMemo(() => { + if (!params.selectedIndustry) return []; + + const filteredData = allData + .filter(item => item.category === params.selectedIndustry) + .map(item => ({ + date: item.date, + category: item.category, + value: item.value ?? 0, + })) + .sort((a, b) => dayjs(a.date).valueOf() - dayjs(b.date).valueOf()); + console.log("当前行业数据 (过滤后):", filteredData); + return filteredData; + }, [allData, params.selectedIndustry]); + console.log("最终图表数据:", currentIndustryData); + const chartConfig = { + data: currentIndustryData, + height: 180, + xField: 'date', + yField: 'value', + seriesField: 'category', + xAxis: { + type: 'cat', + tickCount: 5, + label: { + formatter: (text: string) => { + if (params.timeDimension === '年') return text.split('-')[0]; + if (params.timeDimension === '季度') return text.split('-')[1]; + return text.split('-')[1]; + }, + }, + }, + yAxis: { + label: { + formatter: (val: string) => `${val}${params.type === '招聘增长率' ? '%' : '个'}`, + }, + }, + tooltip: false, + point: { + size: 4, + shape: 'circle', + }, + animation: { + appear: { + animation: 'path-in', + duration: 1000, + }, + }, + }; + + + useEffect(() => { + run(); + }, [params.timeDimension, params.startTime, params.endTime]); + + return ( +
+ +
+ + + + + + + + + +
+ + + + + {currentIndustryData.length > 0 ? ( + + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +export default IndustryTrendPage; \ No newline at end of file diff --git a/src/pages/Analysis/User/index.tsx b/src/pages/Analysis/User/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/services/analysis/industry.ts b/src/services/analysis/industry.ts new file mode 100644 index 0000000..8f10341 --- /dev/null +++ b/src/services/analysis/industry.ts @@ -0,0 +1,8 @@ +import { request } from '@umijs/max'; + +export async function getIndustryTrend(params?: API.Analysis.IndustryParams) { + return request('/api/cms/statics/industry', { + method: 'GET', + params + }); +} \ No newline at end of file diff --git a/src/services/session.ts b/src/services/session.ts index 8b4e0d3..b78c63b 100644 --- a/src/services/session.ts +++ b/src/services/session.ts @@ -1,159 +1,159 @@ -import { createIcon } from '@/utils/IconUtil'; -import { MenuDataItem } from '@ant-design/pro-components'; -import { request } from '@umijs/max'; -import React, { lazy } from 'react'; - - -let remoteMenu: any = null; - -export function getRemoteMenu() { - return remoteMenu; -} - -export function setRemoteMenu(data: any) { - remoteMenu = data; -} - - -function patchRouteItems(route: any, menu: any, parentPath: string) { - for (const menuItem of menu) { - if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') { - if (menuItem.routes) { - let hasItem = false; - let newItem = null; - for (const routeChild of route.routes) { - if (routeChild.path === menuItem.path) { - hasItem = true; - newItem = routeChild; - } - } - if (!hasItem) { - newItem = { - path: menuItem.path, - routes: [], - children: [] - } - route.routes.push(newItem) - } - patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/'); - } - } else { - const names: string[] = menuItem.component.split('/'); - let path = ''; - names.forEach(name => { - if (path.length > 0) { - path += '/'; - } - if (name !== 'index') { - path += name.at(0)?.toUpperCase() + name.substr(1); - } else { - path += name; - } - }) - if (!path.endsWith('.tsx')) { - path += '.tsx' - } - if (route.routes === undefined) { - route.routes = []; - } - if (route.children === undefined) { - route.children = []; - } - const newRoute = { - element: React.createElement(lazy(() => import('@/pages/' + path))), - path: parentPath + menuItem.path, - } - route.children.push(newRoute); - route.routes.push(newRoute); - } - } -} - -export function patchRouteWithRemoteMenus(routes: any) { - if (remoteMenu === null) { return; } - let proLayout = null; - for (const routeItem of routes) { - if (routeItem.id === 'ant-design-pro-layout') { - proLayout = routeItem; - break; - } - } - patchRouteItems(proLayout, remoteMenu, ''); -} - -/** 获取当前的用户 GET /api/getUserInfo */ -export async function getUserInfo(options?: Record) { - return request('/api/getInfo', { - method: 'GET', - ...(options || {}), - }); -} - -// 刷新方法 -export async function refreshToken() { - return request('/api/auth/refresh', { - method: 'post' - }) -} - -export async function getRouters(): Promise { - return request('/api/getRouters'); -} - -export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] { - return childrens.map((item: API.RoutersMenuItem) => { - return { - path: item.path, - icon: createIcon(item.meta.icon), - // icon: item.meta.icon, - name: item.meta.title, - routes: item.children ? convertCompatRouters(item.children) : undefined, - hideChildrenInMenu: item.hidden, - hideInMenu: item.hidden, - component: item.component, - authority: item.perms, - }; - }); -} - -export async function getRoutersInfo(): Promise { - return getRouters().then((res) => { - if (res.code === 200) { - return convertCompatRouters(res.data); - } else { - return []; - } - }); -} - -export function getMatchMenuItem( - path: string, - menuData: MenuDataItem[] | undefined, -): MenuDataItem[] { - if (!menuData) return []; - let items: MenuDataItem[] = []; - menuData.forEach((item) => { - if (item.path) { - if (item.path === path) { - items.push(item); - return; - } - if (path.length >= item.path?.length) { - const exp = `${item.path}/*`; - if (path.match(exp)) { - if (item.routes) { - const subpath = path.substr(item.path.length + 1); - const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes); - items = items.concat(subItem); - } else { - const paths = path.split('/'); - if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') { - items.push(item); - } - } - } - } - } - }); - return items; -} +import { createIcon } from '@/utils/IconUtil'; +import { MenuDataItem } from '@ant-design/pro-components'; +import { request } from '@umijs/max'; +import React, { lazy } from 'react'; + + +let remoteMenu: any = null; + +export function getRemoteMenu() { + return remoteMenu; +} + +export function setRemoteMenu(data: any) { + remoteMenu = data; +} + + +function patchRouteItems(route: any, menu: any, parentPath: string) { + for (const menuItem of menu) { + if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') { + if (menuItem.routes) { + let hasItem = false; + let newItem = null; + for (const routeChild of route.routes) { + if (routeChild.path === menuItem.path) { + hasItem = true; + newItem = routeChild; + } + } + if (!hasItem) { + newItem = { + path: menuItem.path, + routes: [], + children: [] + } + route.routes.push(newItem) + } + patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/'); + } + } else { + const names: string[] = menuItem.component.split('/'); + let path = ''; + names.forEach(name => { + if (path.length > 0) { + path += '/'; + } + if (name !== 'index') { + path += name.at(0)?.toUpperCase() + name.substr(1); + } else { + path += name; + } + }) + if (!path.endsWith('.tsx')) { + path += '.tsx' + } + if (route.routes === undefined) { + route.routes = []; + } + if (route.children === undefined) { + route.children = []; + } + const newRoute = { + element: React.createElement(lazy(() => import('@/pages/' + path))), + path: parentPath + menuItem.path, + } + route.children.push(newRoute); + route.routes.push(newRoute); + } + } +} + +export function patchRouteWithRemoteMenus(routes: any) { + if (remoteMenu === null) { return; } + let proLayout = null; + for (const routeItem of routes) { + if (routeItem.id === 'ant-design-pro-layout') { + proLayout = routeItem; + break; + } + } + patchRouteItems(proLayout, remoteMenu, ''); +} + +/** 获取当前的用户 GET /api/getUserInfo */ +export async function getUserInfo(options?: Record) { + return request('/api/getInfo', { + method: 'GET', + ...(options || {}), + }); +} + +// 刷新方法 +export async function refreshToken() { + return request('/api/auth/refresh', { + method: 'post' + }) +} + +export async function getRouters(): Promise { + return request('/api/getRouters'); +} + +export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] { + return childrens.map((item: API.RoutersMenuItem) => { + return { + path: item.path, + icon: createIcon(item.meta.icon), + // icon: item.meta.icon, + name: item.meta.title, + routes: item.children ? convertCompatRouters(item.children) : undefined, + hideChildrenInMenu: item.hidden, + hideInMenu: item.hidden, + component: item.component, + authority: item.perms, + }; + }); +} + +export async function getRoutersInfo(): Promise { + return getRouters().then((res) => { + if (res.code === 200) { + return convertCompatRouters(res.data); + } else { + return []; + } + }); +} + +export function getMatchMenuItem( + path: string, + menuData: MenuDataItem[] | undefined, +): MenuDataItem[] { + if (!menuData) return []; + let items: MenuDataItem[] = []; + menuData.forEach((item) => { + if (item.path) { + if (item.path === path) { + items.push(item); + return; + } + if (path.length >= item.path?.length) { + const exp = `${item.path}/*`; + if (path.match(exp)) { + if (item.routes) { + const subpath = path.substr(item.path.length + 1); + const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes); + items = items.concat(subItem); + } else { + const paths = path.split('/'); + if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') { + items.push(item); + } + } + } + } + } + }); + return items; +} diff --git a/src/types/analysis/industry.d.ts b/src/types/analysis/industry.d.ts new file mode 100644 index 0000000..dcb266e --- /dev/null +++ b/src/types/analysis/industry.d.ts @@ -0,0 +1,20 @@ +declare namespace API.Analysis { + export interface IndustryResult { + data: TrendItem[]; + code: number; + msg: string; + } + + export interface TrendItem { + date: string; + category: string; + value: number; + } + + export interface IndustryParams { + timeDimension: '月' | '季度' | '年'; + type: '岗位发布数量' | '招聘增长率'; + startTime: string; + endTime: string; + } + } \ No newline at end of file