├── .arcconfig ├── .commitlintrc.js ├── .cz-config.js ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── config ├── config.prod.ts ├── config.ts └── routeConfig.ts ├── jest.config.js ├── jest ├── setup.ts └── test-utils.tsx ├── mock ├── .gitkeep ├── other.ts ├── route.ts └── swr.ts ├── package.json ├── pont-config.json ├── pontTemplate.ts ├── public ├── android-chrome-192x192.png ├── android-chrome-256x256.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── readMe.md ├── server.config.ts ├── src ├── api │ ├── api-lock.json │ ├── api.d.ts │ ├── authorization │ │ ├── api.d.ts │ │ ├── baseClass.ts │ │ ├── index.ts │ │ └── mods │ │ │ ├── authResource │ │ │ ├── index.ts │ │ │ ├── save.ts │ │ │ └── saveList.ts │ │ │ ├── authResourceRole │ │ │ ├── hasRole.ts │ │ │ └── index.ts │ │ │ ├── client │ │ │ ├── index.ts │ │ │ ├── log.ts │ │ │ ├── login.ts │ │ │ ├── remove.ts │ │ │ └── save.ts │ │ │ ├── data │ │ │ ├── deleteDataModule.ts │ │ │ ├── deleteRule.ts │ │ │ ├── detail.ts │ │ │ ├── getDataScope.ts │ │ │ ├── getMockData.ts │ │ │ ├── index.ts │ │ │ ├── list.ts │ │ │ ├── listModule.ts │ │ │ ├── listRule.ts │ │ │ ├── remove.ts │ │ │ ├── save.ts │ │ │ ├── saveModule.ts │ │ │ └── saveRule.ts │ │ │ ├── dataRole │ │ │ ├── addForUser.ts │ │ │ ├── bindUser.ts │ │ │ ├── deleteUser.ts │ │ │ ├── detail.ts │ │ │ ├── index.ts │ │ │ ├── list.ts │ │ │ ├── remove.ts │ │ │ └── save.ts │ │ │ ├── deploymentAuthz │ │ │ ├── getBusinessValueListByRole.ts │ │ │ ├── getDataAccessUserList.ts │ │ │ ├── getRoleIdToBusinessValue.ts │ │ │ ├── getUserIdsByRoleId.ts │ │ │ ├── getUserRoleIdList.ts │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── resource │ │ │ ├── deleteResource.ts │ │ │ ├── detail.ts │ │ │ ├── getMenuTreeByRole.ts │ │ │ ├── getPermissionByUrl.ts │ │ │ ├── index.ts │ │ │ ├── listApiUrl.ts │ │ │ ├── listPagination.ts │ │ │ ├── listResource.ts │ │ │ ├── listTree.ts │ │ │ ├── listUserResource.ts │ │ │ ├── listUserResourceData.ts │ │ │ ├── newResource.ts │ │ │ └── postSaveList.ts │ │ │ ├── resourceRole │ │ │ ├── addUserRes.ts │ │ │ ├── clearUserRole.ts │ │ │ ├── copyRole.ts │ │ │ ├── deleteUnusedRole.ts │ │ │ ├── index.ts │ │ │ ├── listByBusinessValueList.ts │ │ │ ├── listByBusinessValueListPagination.ts │ │ │ ├── listByBusinessValues.ts │ │ │ ├── listByUserId.ts │ │ │ ├── listByUserIds.ts │ │ │ ├── listPagination.ts │ │ │ ├── postUserAddList.ts │ │ │ ├── reduce.ts │ │ │ ├── resourceDelete.ts │ │ │ ├── resourceRoleDetail.ts │ │ │ ├── resourceRoleDetailUser.ts │ │ │ ├── resourceRoleList.ts │ │ │ ├── resourceSave.ts │ │ │ ├── resourceSaveAddUser.ts │ │ │ ├── validateRoleName.ts │ │ │ └── validateRoleUser.ts │ │ │ └── role │ │ │ ├── index.ts │ │ │ └── update.ts │ └── index.ts ├── app.ts ├── assets │ ├── logo.png │ ├── pic_404.png │ └── pic_empty.png ├── common.ts ├── components │ ├── DetailValue │ │ ├── index.module.less │ │ └── index.tsx │ ├── FooterToolbar │ │ ├── index.module.less │ │ └── index.tsx │ ├── GlobalLoading.tsx │ ├── Iconfont │ │ └── index.tsx │ ├── Loading.tsx │ ├── LoadingPage │ │ ├── assets │ │ │ ├── loading-bg.jpg │ │ │ ├── loading.gif │ │ │ └── sign_logo.png │ │ └── index.tsx │ └── UploadFormItem │ │ ├── index.module.less │ │ ├── index.tsx │ │ └── utils │ │ └── upload.ts ├── constant.ts ├── enums.ts ├── global.less ├── global.ts ├── hooks │ ├── useRefCallback.ts │ └── useToast.tsx ├── layouts │ ├── BasicLayout │ │ ├── components │ │ │ ├── CustomHeaderRight │ │ │ │ ├── index.tsx │ │ │ │ └── useHeaderService.ts │ │ │ └── Logo │ │ │ │ └── index.tsx │ │ ├── defaultSettings.ts │ │ ├── index.module.less │ │ └── index.tsx │ ├── SignInLayout │ │ ├── assets │ │ │ ├── login_bg.png │ │ │ ├── login_left.png │ │ │ └── sign_logo.png │ │ ├── index.module.less │ │ └── index.tsx │ ├── index.tsx │ └── validateMessages.ts ├── pages │ ├── 404.tsx │ ├── auth │ │ ├── login │ │ │ ├── index.tsx │ │ │ └── useLoginService.ts │ │ ├── useAuthService.ts │ │ └── wrappers │ │ │ ├── auth.tsx │ │ │ └── guard.tsx │ ├── counter │ │ ├── countAtom.test.tsx │ │ ├── countAtom.ts │ │ └── hooks │ │ │ ├── useAsyncCounter.test.ts │ │ │ ├── useAsyncCounter.ts │ │ │ ├── useContextCounter.test.tsx │ │ │ ├── useContextCounter.tsx │ │ │ ├── useCounter.test.ts │ │ │ ├── useCounter.ts │ │ │ ├── useCounterError.test.ts │ │ │ ├── useCounterError.ts │ │ │ ├── useCounterWithProps.test.ts │ │ │ └── useCounterWithProps.ts │ ├── createLocalShare.tsx │ ├── homepage │ │ ├── index.tsx │ │ └── useHomepageService.ts │ └── hotel │ │ ├── HotelInfo │ │ └── index.tsx │ │ └── hotelInfo │ │ └── index.tsx └── utils │ ├── array.ts │ ├── date.ts │ ├── getContextService.ts │ ├── getMockService.ts │ ├── json.ts │ ├── regex.ts │ └── string.ts ├── tsconfig.json ├── typings.d.ts └── yarn.lock /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "project_id" : "thundersdata", 3 | "conduit_uri" : "http://cr.thundersdata.com:26170" 4 | } -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional' 4 | ], 5 | rules: {} 6 | }; -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | types: [ 5 | { 6 | value: 'feat', 7 | name : '✨ feat: 新功能' 8 | }, 9 | { 10 | value: 'fix', 11 | name : '🐛 fix: 修复bug' 12 | }, 13 | { 14 | value: 'refactor', 15 | name : '♻️ refactor: 代码重构(既不是新功能也不是改bug)' 16 | }, 17 | { 18 | value: 'chore', 19 | name: '🎫 chore: 修改流程配置' 20 | }, 21 | { 22 | value: 'docs', 23 | name : '📝 docs: 修改了文档' 24 | }, 25 | { 26 | value: 'test', 27 | name : '✅ test: 更新了测试用例' 28 | }, 29 | { 30 | value: 'style', 31 | name : '💄 style: 修改了样式文件' 32 | }, 33 | { 34 | value: 'perf', 35 | name: '⚡️ perf: 新能优化', 36 | }, 37 | { 38 | value: 'revert', 39 | name: '⏪ revert: 回退提交' 40 | }, 41 | ], 42 | scopes: [], 43 | allowCustomScopes: true, 44 | allowBreakingChanges: ["feat", "fix"], 45 | subjectLimit: 50, 46 | messages: { 47 | type: "请选择你本次改动的修改类型", 48 | customScope: '\n请明确本次改动的范围(可填):', 49 | subject: '简短描述本次改动:\n', 50 | body: '详细描述本次改动 (可填). 使用 "|" 换行:\n', 51 | breaking: '请列出任何 BREAKING CHANGES (可填):\n', 52 | footer: '请列出本次改动关闭的ISSUE (可填). 比如: #31, #34:\n', 53 | confirmCommit: '你确定提交本次改动吗?', 54 | }, 55 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | ESLINT=1 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /scripts 2 | **/node_modules/** 3 | _scripts 4 | /lambda/mock/** 5 | _test_ 6 | api/ 7 | config/ 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | module.exports = { 4 | parser: '@typescript-eslint/parser', 5 | extends: ['plugin:@typescript-eslint/recommended'], //使用推荐的React代码检测规范 6 | plugins: ['@typescript-eslint'], 7 | env: { 8 | browser: true, 9 | node: true, 10 | }, 11 | settings: { 12 | //自动发现React的版本,从而进行规范react代码 13 | react: { 14 | pragma: 'React', 15 | version: 'detect', 16 | }, 17 | }, 18 | parserOptions: { 19 | //指定ESLint可以解析JSX语法 20 | ecmaVersion: 2019, 21 | sourceType: 'module', 22 | ecmaFeatures: { 23 | jsx: true, 24 | }, 25 | }, 26 | rules: { 27 | '@typescript-eslint/explicit-module-boundary-types': 0, 28 | '@typescript-eslint/no-non-null-assertion': 0, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /.vscode 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | npm-debug.log* 17 | yarn-error.log 18 | 19 | /coverage 20 | .idea 21 | *bak 22 | .vscode 23 | 24 | # visual studio code 25 | .history 26 | *.log 27 | 28 | functions/mock 29 | .temp/** 30 | 31 | # umi 32 | .umi 33 | .umi-production 34 | config/*.local.* 35 | 36 | # screenshot 37 | screenshot 38 | .firebase 39 | 40 | .eslintcache 41 | /locales/** 42 | /models/** 43 | 44 | .mocks/ 45 | .jest 46 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | package.json 3 | .umi 4 | .umi-production 5 | /dist 6 | .dockerignore 7 | .DS_Store 8 | .eslintignore 9 | *.png 10 | *.toml 11 | docker 12 | .editorconfig 13 | Dockerfile* 14 | .gitignore 15 | .prettierignore 16 | LICENSE 17 | .eslintcache 18 | *.lock 19 | yarn-error.log 20 | .history 21 | CNAME 22 | /build 23 | umi-block.json 24 | api/ 25 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | semi: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | bracketSpacing: true, 7 | jsxBracketSameLine: false, 8 | arrowParens: 'avoid', 9 | insertPragma: false, 10 | tabWidth: 2, 11 | useTabs: false, 12 | }; 13 | -------------------------------------------------------------------------------- /config/config.prod.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | import AntdDayjsWebpackPlugin from 'antd-dayjs-webpack-plugin'; 3 | import routeConfig from './routeConfig'; 4 | 5 | export default defineConfig({ 6 | antd: {}, 7 | esbuild: {}, 8 | // 配置具体含义见:https://github.com/umijs/umi-webpack-bundle-analyzer#options-for-plugin 9 | analyze: { 10 | analyzerMode: 'server', 11 | analyzerPort: 8888, 12 | openAnalyzer: true, 13 | // generate stats file while ANALYZE_DUMP exist 14 | generateStatsFile: false, 15 | statsFilename: 'stats.json', 16 | logLevel: 'info', 17 | defaultSizes: 'parsed', // stat // gzip 18 | }, 19 | dynamicImport: { 20 | loading: '@/components/LoadingPage/index', 21 | }, 22 | externals: { 23 | react: 'window.React', 24 | 'react-dom': 'window.ReactDOM', 25 | }, 26 | hash: true, 27 | history: { 28 | type: 'hash', 29 | }, 30 | ignoreMomentLocale: true, 31 | inlineLimit: 10, 32 | links: [ 33 | { 34 | rel: 'apple-touch-icon', 35 | sizes: '180x180', 36 | href: '/apple-touch-icon.png', 37 | }, 38 | { 39 | rel: 'icon', 40 | type: 'image/png', 41 | sizes: '32x32', 42 | href: '/favicon-32x32.png', 43 | }, 44 | { 45 | rel: 'icon', 46 | type: 'image/png', 47 | sizes: '16x16', 48 | href: '/favicon-16x16.png', 49 | }, 50 | { rel: 'manifest', href: '/site.webmanifest' }, 51 | { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }, 52 | { 53 | rel: 'stylesheet', 54 | href: '//at.alicdn.com/t/font_1905159_jylqduh3ufd.css', 55 | }, 56 | ], 57 | scripts: [ 58 | 'https://cdn.bootcdn.net/ajax/libs/react/17.0.1/umd/react.production.min.js', 59 | 'https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js', 60 | ], 61 | outputPath: 'build', 62 | routes: routeConfig, 63 | theme: {}, 64 | title: 'PC端开发模板', 65 | webpack5: {}, 66 | chainWebpack(config) { 67 | config.plugin('dayjs').use(AntdDayjsWebpackPlugin); 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | import AntdDayjsWebpackPlugin from 'antd-dayjs-webpack-plugin'; 3 | import routeConfig from './routeConfig'; 4 | 5 | export default defineConfig({ 6 | antd: { 7 | dark: false, 8 | compact: false, 9 | }, 10 | esbuild: {}, 11 | dynamicImportSyntax: {}, 12 | fastRefresh: {}, 13 | hash: true, 14 | history: { 15 | type: 'hash', 16 | }, 17 | ignoreMomentLocale: true, 18 | // 暂时关闭。原因:https://github.com/ant-design/pro-components/issues/3220#issuecomment-905097084 19 | // mfsu: {}, 20 | links: [ 21 | { 22 | rel: 'apple-touch-icon', 23 | sizes: '180x180', 24 | href: '/apple-touch-icon.png', 25 | }, 26 | { 27 | rel: 'icon', 28 | type: 'image/png', 29 | sizes: '32x32', 30 | href: '/favicon-32x32.png', 31 | }, 32 | { 33 | rel: 'icon', 34 | type: 'image/png', 35 | sizes: '16x16', 36 | href: '/favicon-16x16.png', 37 | }, 38 | { rel: 'manifest', href: '/site.webmanifest' }, 39 | { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }, 40 | { 41 | rel: 'stylesheet', 42 | href: '//at.alicdn.com/t/font_1905159_jylqduh3ufd.css', 43 | }, 44 | ], 45 | proxy: {}, 46 | routes: routeConfig, 47 | theme: {}, 48 | title: 'PC端开发模板', 49 | webpack5: {}, 50 | chainWebpack(config) { 51 | config.plugin('dayjs').use(AntdDayjsWebpackPlugin); 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /config/routeConfig.ts: -------------------------------------------------------------------------------- 1 | import { LOGIN_CONFIG } from '../src/constant'; 2 | 3 | export default [ 4 | { 5 | path: '/', 6 | component: '@/layouts', 7 | wrappers: ['@/pages/auth/wrappers/guard'], 8 | routes: [ 9 | ...(LOGIN_CONFIG.isSSO 10 | ? [] 11 | : [ 12 | { 13 | path: '/auth', 14 | component: '@/layouts/SignInLayout', 15 | routes: [{ path: '/auth/login', component: './auth/login', title: '登录' }], 16 | }, 17 | ]), 18 | { 19 | path: '/', 20 | component: '@/layouts/BasicLayout', 21 | wrappers: ['@/pages/auth/wrappers/auth'], 22 | routes: [ 23 | { path: '/', redirect: '/homepage' }, 24 | { path: '/homepage', component: './homepage', title: '首页' }, 25 | { path: '/hotel/hotelInfo', component: './hotel/hotelInfo', title: '酒店基本信息' }, 26 | ], 27 | }, 28 | { path: '*', component: './404' }, 29 | ], 30 | }, 31 | ]; 32 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | preset: 'ts-jest', 4 | clearMocks: true, 5 | coverageDirectory: 'coverage', 6 | setupFilesAfterEnv: ['./jest/setup.ts', 'jest-localstorage-mock'], 7 | transformIgnorePatterns: [], 8 | moduleNameMapper: { 9 | '^@/(.*)$': '/src/$1', 10 | 'test-utils': '/jest/test-utils', 11 | }, 12 | globals: { 13 | 'ts-jest': {}, 14 | }, 15 | cacheDirectory: '.jest/cache', 16 | }; 17 | -------------------------------------------------------------------------------- /jest/setup.ts: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import { defs as authorizationDefs, authorization } from '../src/api/authorization'; 3 | 4 | // jest.useFakeTimers(); 5 | 6 | (global as any).defs = { 7 | authorization: authorizationDefs, 8 | }; 9 | 10 | (global as any).API = { 11 | authorization, 12 | }; 13 | -------------------------------------------------------------------------------- /jest/test-utils.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import '@testing-library/jest-dom'; 3 | import { render } from '@testing-library/react'; 4 | 5 | import useAuthService, { AuthContext } from '../src/pages/auth/useAuthService'; 6 | 7 | const AllTheProviders = ({ children }: { children?: React.ReactNode }) => { 8 | const authService = useAuthService(); 9 | 10 | return {children}; 11 | }; 12 | 13 | const customRender = (ui: React.ReactElement, options?: any) => 14 | render(ui, { wrapper: AllTheProviders, ...options }); 15 | 16 | // re-export everything 17 | export * from '@testing-library/react'; 18 | 19 | // override render method 20 | export { customRender as render }; 21 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/mock/.gitkeep -------------------------------------------------------------------------------- /mock/other.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'POST /login/submit': { 3 | code: 20000, 4 | success: true, 5 | message: '登录成功', 6 | data: { 7 | accessToken: '123', 8 | }, 9 | }, 10 | 'POST /register/submit': { 11 | code: 20000, 12 | success: true, 13 | message: '注册成功', 14 | data: { 15 | accessToken: '123', 16 | }, 17 | }, 18 | 'POST /register/sms': { 19 | code: 20000, 20 | success: true, 21 | message: '注册短信发送成功', 22 | data: 0, 23 | }, 24 | 'GET /user/fetch': { 25 | code: 20000, 26 | message: '获取成功', 27 | success: true, 28 | data: { 29 | name: 'test', 30 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /mock/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/resource': { 3 | success: true, 4 | data: [ 5 | { 6 | name: 'homepage', 7 | apiUrl: '/homepage', 8 | description: '首页', 9 | icon: 'iconyunyingguanli', 10 | }, 11 | { 12 | name: 'hotel', 13 | apiUrl: '/hotel/hotelInfo', 14 | description: '酒店基本信息', 15 | icon: 'icongongsi', 16 | }, 17 | ], 18 | code: 20000, 19 | message: '成功', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /mock/swr.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/fetch/user': { 3 | code: 20000, 4 | success: true, 5 | message: '成功', 6 | data: { 7 | name: 'chenjie', 8 | age: 30, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa-template", 3 | "version": "1.0.0", 4 | "description": "杭州雷数前端中后台管理系统模板", 5 | "license": "MIT", 6 | "author": "陈杰 ", 7 | "homepage": "https://github.com/thundersdata-frontend/spa-template#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/thundersdata-frontend/spa-template.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/thundersdata-frontend/spa-template/issues" 14 | }, 15 | "scripts": { 16 | "nc-u": "npx npm-check --registry=http://npm.taobao.org/ -u", 17 | "commit": "git-cz", 18 | "start": "cross-env umi dev", 19 | "analyze": "cross-env UMI_ENV=prod ANALYZE=1 umi build", 20 | "build": "cross-env UMI_ENV=prod MOCK=none umi build", 21 | "test": "jest --passWithNoTests --detectOpenHandles --watch", 22 | "tsc": "tsc -p ./tsconfig.json", 23 | "eslint": "eslint --fix --ext .ts,.tsx src/", 24 | "prettier": "prettier --check src/**/*.{ts,tsx} --write" 25 | }, 26 | "config": { 27 | "commitizen": { 28 | "path": "node_modules/cz-customizable" 29 | } 30 | }, 31 | "changelog": { 32 | "emojis": true, 33 | "authorName": true, 34 | "authorEmail": true 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "npm run tsc && npm run eslint:fix && npm run style:fix && npm run prettier:fix && lint-staged", 39 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 40 | } 41 | }, 42 | "lint-staged": { 43 | "src/**/*.{ts,tsx}": [ 44 | "git add ." 45 | ] 46 | }, 47 | "dependencies": { 48 | "@ant-design/pro-form": "^1.38.1", 49 | "@ant-design/pro-layout": "^6.25.1", 50 | "@umijs/plugin-access": "^2.4.2", 51 | "@umijs/plugin-antd": "^0.10.0", 52 | "@umijs/plugin-initial-state": "^2.4.0", 53 | "@umijs/plugin-model": "^2.6.1", 54 | "classnames": "^2.3.1", 55 | "dayjs": "^1.10.6", 56 | "immer": "^9.0.5", 57 | "jotai": "^1.3.2", 58 | "optics-ts": "^2.1.0", 59 | "react": "^17.0.2", 60 | "react-dom": "^17.0.2", 61 | "swr": "^1.0.0", 62 | "umi": "^3.5.17", 63 | "umi-request": "^1.3.9", 64 | "use-immer": "^0.6.0" 65 | }, 66 | "devDependencies": { 67 | "@commitlint/cli": "^12.1.1", 68 | "@commitlint/config-conventional": "^12.1.1", 69 | "@td-design/pont-engine": "^1.0.3", 70 | "@testing-library/jest-dom": "^5.14.1", 71 | "@testing-library/react": "^12.0.0", 72 | "@testing-library/react-hooks": "^7.0.1", 73 | "@types/classnames": "^2.2.11", 74 | "@types/jest": "^27.0.1", 75 | "@types/react": "^17.0.19", 76 | "@types/react-dom": "^17.0.9", 77 | "@typescript-eslint/eslint-plugin": "^4.22.0", 78 | "@typescript-eslint/parser": "^4.22.0", 79 | "@umijs/plugin-esbuild": "^1.3.1", 80 | "@umijs/types": "^3.5.17", 81 | "antd-dayjs-webpack-plugin": "^1.0.6", 82 | "babel-jest": "^27.1.0", 83 | "commitizen": "^4.2.3", 84 | "conventional-changelog-cli": "^2.1.0", 85 | "conventional-changelog-custom-config": "^0.3.1", 86 | "cross-env": "^7.0.3", 87 | "cz-customizable": "^6.3.0", 88 | "eslint": "^7.24.0", 89 | "husky": "^6.0.0", 90 | "jest": "^27.1.0", 91 | "jest-fetch-mock": "^3.0.3", 92 | "jest-localstorage-mock": "^2.4.17", 93 | "lint-staged": "^10.5.4", 94 | "pont-engine": "^1.0.13", 95 | "prettier": "^2.2.1", 96 | "react-test-renderer": "^17.0.2", 97 | "standard-version": "^9.2.0", 98 | "ts-jest": "^27.0.5", 99 | "typescript": "^4.2.3" 100 | }, 101 | "engines": { 102 | "node": ">=8.0.0" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pont-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "outDir": "./src/api", 3 | "templatePath": "./pontTemplate", 4 | "usingMultipleOrigins": true, 5 | "mocks": { 6 | "enable": true, 7 | "wrapper": "{\"code\": 20000, \"success\": true, \"data\": {response}, \"message\": \"\"}", 8 | "containDataSources": [] 9 | }, 10 | "origins": [ 11 | { 12 | "name": "authorization", 13 | "originUrl": "http://authorization.dev.thundersdata.com/v2/api-docs" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /pontTemplate.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 定义生成代码的模板 3 | * @公司: thundersdata 4 | * @作者: 黄姗姗 5 | * @Date: 2019-10-28 16:29:26 6 | * @LastEditors: 黄姗姗 7 | * @LastEditTime: 2020-06-19 16:10:02 8 | */ 9 | import { CodeGenerator, Interface, Property } from 'pont-engine'; 10 | 11 | export default class MyGenerator extends CodeGenerator { 12 | enum: Array = []; 13 | 14 | setEnum(enums: Array = []) { 15 | this.enum = enums.map(value => { 16 | if (typeof value === 'string') { 17 | if (!value.startsWith("'")) { 18 | value = `'${value}`; 19 | } 20 | 21 | if (!value.endsWith("'")) { 22 | value = `${value}'`; 23 | } 24 | } 25 | 26 | return value; 27 | }); 28 | } 29 | 30 | /** 获取总的类型定义代码 */ 31 | getDeclaration() { 32 | return ` 33 | type ObjectMap = { 34 | [key in Key]: Value; 35 | } 36 | 37 | interface AjaxResponse { 38 | code: number; 39 | data: T; 40 | message: string; 41 | success: boolean; 42 | } 43 | 44 | ${this.getCommonDeclaration()} 45 | 46 | ${this.getBaseClassesInDeclaration()} 47 | 48 | ${this.getModsDeclaration()} 49 | `; 50 | } 51 | 52 | /** 获取所有基类文件代码 */ 53 | getBaseClassesIndex() { 54 | const clsCodes = this.dataSource.baseClasses.map( 55 | base => ` 56 | class ${base.name} { 57 | ${base.properties 58 | .map(prop => { 59 | return this.toPropertyCodeWithInitValue(prop, base.name); 60 | }) 61 | .filter(id => id) 62 | .join('\n')} 63 | } 64 | `, 65 | ); 66 | 67 | if (this.dataSource.name) { 68 | return ` 69 | ${clsCodes.join('\n')} 70 | export const ${this.dataSource.name} = { 71 | ${this.dataSource.baseClasses.map(bs => bs.name).join(',\n')} 72 | } 73 | `; 74 | } 75 | 76 | return clsCodes.map(cls => `export ${cls}`).join('\n'); 77 | } 78 | 79 | toPropertyCodeWithInitValue(prop: Property, baseName = '') { 80 | this.setEnum(prop.dataType.enum); 81 | const { typeName, isDefsType } = prop.dataType; 82 | let typeWithValue = `= ${this.getInitialValue(typeName, isDefsType, false)}`; 83 | 84 | if (prop.dataType.typeName === baseName) { 85 | typeWithValue = `= {}`; 86 | } 87 | 88 | let propName = prop.name; 89 | if (!propName.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)) { 90 | propName = `'${propName}'`; 91 | } 92 | 93 | return ` 94 | /** ${prop.description || prop.name} */ 95 | ${propName} ${typeWithValue} 96 | `; 97 | } 98 | 99 | initClassValue(isDefsType: boolean, usingDef: boolean, typeName: string) { 100 | const originName = this.dataSource.name; 101 | if (!usingDef) { 102 | return `new ${typeName}()`; 103 | } 104 | return `new ${this.getDefName(originName, typeName, isDefsType)}()`; 105 | } 106 | 107 | initEnumValue() { 108 | const str = this.enum[0]; 109 | if (typeof str === 'string') { 110 | return `${str}`; 111 | } 112 | return `${str}`; 113 | } 114 | 115 | getInitialValue(typeName: string, isDefsType: boolean, usingDef = true) { 116 | if (isDefsType) { 117 | return this.initClassValue(isDefsType, usingDef, typeName); 118 | } 119 | if (this.enum && this.enum.length) { 120 | return this.initEnumValue(); 121 | } 122 | return this.initNormalTypeValue(typeName); 123 | } 124 | 125 | /** 生成的api.d.ts文件中的对应每个接口的内容 */ 126 | getInterfaceContentInDeclaration(inter: Interface) { 127 | const paramsCode = inter.getParamsCode(); 128 | const bodyParamsCode = inter.getBodyParamsCode(); 129 | const hasGetParams = !!inter.parameters.filter(param => param.in !== 'body').length; 130 | let requestParams = bodyParamsCode ? `bodyParams: ${bodyParamsCode}, params: Params` : `params: Params`; 131 | 132 | if (!hasGetParams) { 133 | requestParams = bodyParamsCode ? `bodyParams: ${bodyParamsCode}` : ''; 134 | } 135 | 136 | return ` 137 | export ${paramsCode} 138 | 139 | export type Response = ${inter.responseType} 140 | 141 | export const init: Response; 142 | export const url: string; 143 | 144 | export function fetch(${requestParams}): Promise; 145 | `; 146 | } 147 | 148 | /** 生成的接口请求部分 */ 149 | // eslint-disable-next-line complexity 150 | getInterfaceContent(inter: Interface) { 151 | // type为body的参数 152 | const bodyParamsCode = inter.getBodyParamsCode(); 153 | // 判断是否有params参数 154 | const hasGetParams = !!inter.parameters.filter(param => param.in !== 'body').length; 155 | let requestParams = bodyParamsCode ? `data = {}, params = {}` : `params = {}`; 156 | let requestStr = bodyParamsCode ? `data, params` : `params`; 157 | if (!hasGetParams) { 158 | requestParams = bodyParamsCode ? `data = {}` : 'params = {}'; 159 | requestStr = bodyParamsCode ? `data` : 'params'; 160 | } 161 | const requestObj = this.getRequest(bodyParamsCode, inter.method); 162 | 163 | const { typeName, isDefsType } = inter.response; 164 | const initValue = this.getInitialValue(typeName, isDefsType); 165 | 166 | let defsStr = ''; 167 | if (inter.response.isDefsType) { 168 | defsStr = "import * as defs from '../../baseClass';"; 169 | } 170 | 171 | return ` 172 | /** 173 | * @description ${inter.description} 174 | */ 175 | ${defsStr} 176 | import serverConfig from '../../../../../server.config'; 177 | import { initRequest } from '@/common'; 178 | 179 | const backEndUrl = serverConfig()['${this.dataSource.name}']; 180 | 181 | // 初始值 182 | export const init = ${initValue}; 183 | // 接口地址 184 | export const url = ${inter.path}; 185 | 186 | export async function fetch(${requestParams}) { 187 | const request = await initRequest(); 188 | const result = await request.${requestObj.method}(backEndUrl + '${inter.path}', { 189 | headers: { 190 | 'Content-Type': '${requestObj.contentType}', 191 | }, 192 | ${requestStr}, 193 | }); 194 | if (result) { 195 | if (!result.success) { 196 | throw new Error(JSON.stringify(result)); 197 | } else { 198 | return result.data || ${initValue}; 199 | } 200 | } else { 201 | throw new Error(JSON.stringify({ message: '接口未响应' })); 202 | } 203 | } 204 | `; 205 | } 206 | 207 | getDefName(originName: string, typeName: string, isDefsType: boolean) { 208 | let name = typeName; 209 | 210 | if (isDefsType) { 211 | name = originName ? `defs.${originName}.${typeName}` : `defs.${typeName}`; 212 | } 213 | 214 | return name; 215 | } 216 | 217 | initNormalTypeValue(typeName: string) { 218 | switch (typeName) { 219 | case 'Array': 220 | return '[]'; 221 | 222 | case 'boolean': 223 | return 'false'; 224 | 225 | case 'string': 226 | return "''"; 227 | 228 | case 'number': 229 | default: 230 | return 'undefined'; 231 | } 232 | } 233 | 234 | // eslint-disable-next-line complexity 235 | getRequest(bodyParamsCode: string, method: string) { 236 | // 为避免method匹配不上,全部转化为大写 237 | const upperMethod = method.toUpperCase(); 238 | const fetchMethod = bodyParamsCode ? `${upperMethod}:JSON` : upperMethod; 239 | 240 | let methodTemp = ''; 241 | let contentType = 'application/json'; 242 | switch (fetchMethod) { 243 | case 'GET': 244 | default: 245 | methodTemp = 'get'; 246 | break; 247 | case 'PUT': 248 | methodTemp = 'put'; 249 | break; 250 | case 'DELETE': 251 | methodTemp = 'delete'; 252 | break; 253 | case 'POST': 254 | methodTemp = 'post'; 255 | contentType = 'application/x-www-form-urlencoded'; 256 | break; 257 | case 'PUT:JSON': 258 | methodTemp = 'put'; 259 | break; 260 | case 'POST:JSON': 261 | methodTemp = 'post'; 262 | break; 263 | } 264 | return { 265 | method: methodTemp, 266 | contentType, 267 | }; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/android-chrome-256x256.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /readMe.md: -------------------------------------------------------------------------------- 1 | ## 使用 plugin-initial-state、plugin-model、plugin-access 来配置登录用户路由、菜单和权限 2 | 3 | ### 1. plugin-initial-state 使用 4 | 5 | 在`src/app.ts`中,导出`getInitialState`方法,需要配合`plugin-model`一起使用。 6 | 7 | ```tsx 8 | // app.ts 9 | export async function getInitialState() { 10 | const data = await fetchXXX(); // 请求后端接口,比如拿到菜单、路由、权限、个人信息等数据 11 | return data; 12 | } 13 | 14 | // 其他页面根据useModel('@@initialState')拿到初始值 15 | export default () => { 16 | const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState'); 17 | 18 | // 参数释义参见:https://umijs.org/zh-CN/plugins/plugin-initial-state#initialstate 19 | }; 20 | ``` 21 | 22 | ### 2. plugin-access 使用 23 | 24 | 在`src/access.ts`里面默认导出一个方法,该方法返回一个对象,对象的每一个值就对应一条权限。 25 | 26 | ```tsx 27 | // src/access.ts 28 | export default initialState => { 29 | // initialState是通过上面的`plugin-initial-state`返回的数据 30 | const { userId, role } = initialState; 31 | 32 | return { 33 | canRead: true, 34 | canUpdate: role === 'admin', 35 | canDelete: foo => foo.ownerId === userId, 36 | // ... 37 | }; 38 | }; 39 | // 在其他页面,可以通过`umi`内置的`userAccess`来获取权限相关信息 40 | export default () => { 41 | const access = useAccess(); 42 | 43 | if (access.canRead) { 44 | // do something 45 | } 46 | 47 | return <>xxx; 48 | }; 49 | ``` 50 | 51 | 同时,`umi`还给我们暴露了一个``组件,我们可以用它来对应用进行细粒度的权限控制,比如可以精确地控制某个按钮、某个菜单、某个超链接是否显示。 ``组件的属性有: 52 | 53 | - `accessible`: 是否有权限,通常通过`useAccess`获取后传进来 54 | - `fallback`: 无权限时的显示,默认无权限不显示任何内容 55 | - `children`: 有权限时显示的内容 56 | 57 | ```tsx 58 | import React from 'react'; 59 | import { useAccess, Access } from 'umi'; 60 | 61 | function IndexPage() { 62 | const access = useAccess(); 63 | 64 | if (access.canRead) { 65 | // Do something 66 | } 67 | 68 | return ( 69 |
70 | Cannot read content
}> 71 | Read Content 72 |
73 | Cannot update content}> 74 | Update Content 75 | 76 | Cannot delete content}> 77 | Delete Content 78 | 79 | 80 | ); 81 | } 82 | 83 | return IndexPage; 84 | ``` 85 | 86 | ## pont 生成工具与 umi 的 useRequest 结合使用 87 | 88 | ### pont 配置 89 | 90 | 1. 在 pont-config.json 文件中配置 origins,一个 swagger 地址对应一个 name 和 originUrl,name 的命名没有约束,但是会在接口调用的时候用到,例如 authorization(权限中心),originUrl 就是 api-docs 的地址,例如'http://xxxx/v2/api-docs',可以支持配置多个swagger地址,但是注意:除了origins之外的配置项不要改动 91 | 2. server.config.js 文件是用于从 pont-config.json 文件中读取后端接口地址的,不需要进行改动(如果随意的更改可能会引起调用接口的时候 nameSpace 对应不上) 92 | 3. pontTemplate.ts 文件是定义生成代码的模板文件,不需要进行改动 93 | 4. 在项目的入口文件,即 src 目录下的 global.ts 文件中加入一句'import '@/services';',把 API 引入进来 94 | 95 | ### pont 使用 96 | 97 | 1. 在 vscode 中安装 vscode 插件 td-pont,使用方法参考'https://github.com/nefe/vscode-pont' 98 | 2. 当 vscode-pont 检测到项目中含有合法的 pont-config.json 之后,插件会马上启动生成 services 文件夹 99 | 3. 如果后端接口发生了更新,那么需要手动的点击 VS code 左下方的 sync 按钮,这样才会去比较线上和线下的差异实现和服务端同步变更,但是这个变更是存在于内存中的,all/mod/bo 都是把对应内容更新到 api.lock 中,generate 是根据 lock 生成最后的代码 100 | 4. 当重新打开项目时,会自动调用一次 sync,获取和服务端的差异 101 | 5. 目前 API 已经配置为全局变量,当需要调用接口时,我们不需要再进行 import 操作,只需要 API.[nameSpace].[mod].[方法的文件名].fetch(),nameSpace 即在 pont-config.json 文件中配置的 origins 的 name,mod 即是 module,例如:API.authorization.role.resourceSave.fetch() 102 | 6. 更多细节:'https://github.com/alibaba/pont' 103 | 104 | ### pont 的 mock 功能 105 | 106 | 我们自己基于 pont 的代码进行了改动,实现了多数据源 mock 以及生成的 mock 文件与 umi 的 request 的 mock 功能相结合,具体使用方式如下: 107 | 108 | 1. 卸载 VS code 中的 pont 插件,并搜索 td-pont,进行安装,使用方式相同 109 | 2. 在 pont-config.json 文件中的 mocks 字段中配置 containDataSources,格式为 string[],注意:containDataSources 中的字段名必须是 origins 中的 name 字段,否则识别不到哪些数据源要走 mock 功能 110 | 3. homepage 中有调用的例子,不管是否走 mock 功能,调用方式不变,都为 API.xxx.xxx.xxx.fetch 的形式,就是说只有在 containDataSources 中配置的数据源名称才是走 mock,否则就是正常接口请求 111 | 112 | ### pont 最佳实践 113 | 114 | 基于我们自定义的 pontTemplate,pont 已经帮我们生成了我们需要的 TypeScript 类型声明文件,以及对应的调用后端接口的胶水代码,同时也为我们生成好了初始值。那么我们使用 pont 的最佳实践应该是什么样的呢?我在这里大致总结一下: 115 | 116 | 1. 不要自己定义初始值,直接使用 pont 生成的 init 值作为 useState 或者 store 里面的初始值。例如: 117 | 118 | ```typescript 119 | const [detail, setDetail] = useState( 120 | API.gazelle.companyFinancialIndicator.getById.init, 121 | ); 122 | ``` 123 | 124 | 2. 与 umi 的 useRequest 结合使用 125 | 126 | 我们自定义生成的请求方法的格式如下:当接口请求的 success 为 false 时,我们会把错误 throw 出去,在 useRequest 的 onError 中进行处理,此时返回的 data 是接口数据的默认值 127 | 128 | ```typescript 129 | export async function fetch(params = {}) { 130 | const result = await request.get(backEndUrl + '/interview/getInterviewerDetail', { 131 | headers: { 132 | 'Content-Type': 'application/json', 133 | }, 134 | params, 135 | }); 136 | if (!result.success) throw new Error(result.message); 137 | return result.data || new defs.recruitment.HrmInterviewDTO(); 138 | } 139 | ``` 140 | 141 | ```typescript 142 | useRequest(() => API.recruitment.dict.getAllDict.fetch(), { 143 | onSuccess: data => { 144 | setEnums(data); 145 | }, 146 | onError: error => { 147 | console.log(error.message); 148 | }, 149 | }); 150 | ``` 151 | 152 | 3. 如果前端需要的数据格式和后端返回的格式有区别(最常见的就是日期和文件),那么你需要自己构造一个类型来对这些特殊属性进行处理。这个时候最好是使用 typescript 提供的`Utility Types(工具类型)`来尽可能复用已有的类型。例如: 153 | 154 | ```typescript 155 | export type PolicyDetailDTO = Pick< 156 | defs.gazelle.PolicyDTO, 157 | | 'policyId' 158 | | 'policyType' 159 | | 'title' 160 | | 'indexCode' 161 | | 'issueNumber' 162 | | 'issueOrg' 163 | | 'subjectType' 164 | | 'subjectWord' 165 | | 'tenantCode' 166 | > & { 167 | issueDate: moment.Moment; 168 | finalDate: moment.Moment; 169 | attachment?: UploadFile[]; 170 | }; 171 | ``` 172 | -------------------------------------------------------------------------------- /server.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 根据pont-config.json文件获取到对应的后端请求地址 3 | * @公司: thundersdata 4 | * @作者: 黄姗姗 5 | * @Date: 2019-10-30 18:36:20 6 | * @LastEditors: 黄姗姗 7 | * @LastEditTime: 2020-09-28 10:14:55 8 | */ 9 | import pontConfig from './pont-config.json'; 10 | 11 | export default function() { 12 | const result: { [key: string]: string } = {}; 13 | const mockDatasources: string[] = pontConfig.mocks.containDataSources; 14 | pontConfig.origins.forEach(origin => { 15 | const { name, originUrl } = origin; 16 | if (mockDatasources.includes(name)) { 17 | result[name] = `/${name}`; 18 | } else { 19 | result[name] = originUrl.replace(/\/v[0-9]{1,}\/api-docs/, ''); 20 | } 21 | }); 22 | return result; 23 | } 24 | -------------------------------------------------------------------------------- /src/api/api.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/api/authorization/baseClass.ts: -------------------------------------------------------------------------------- 1 | class BindUserDTO { 2 | /** 客户端标识 */ 3 | clientKey = ''; 4 | 5 | /** 角色id列表 */ 6 | roleIds = []; 7 | 8 | /** 用户id */ 9 | userId = undefined; 10 | } 11 | 12 | class BusinessQueryDTO { 13 | /** 拓展字段列表 */ 14 | businessValueList = []; 15 | 16 | /** 客户端标识 */ 17 | clientKey = ''; 18 | 19 | /** 分页参数 */ 20 | page = undefined; 21 | 22 | /** 分页参数 */ 23 | pageSize = undefined; 24 | 25 | /** 角色名称 */ 26 | roleName = ''; 27 | } 28 | 29 | class ClientConfig { 30 | /** clientKey */ 31 | clientKey = ''; 32 | 33 | /** clientName */ 34 | clientName = ''; 35 | 36 | /** createdAt */ 37 | createdAt = ''; 38 | 39 | /** id */ 40 | id = undefined; 41 | 42 | /** isDeleted */ 43 | isDeleted = false; 44 | 45 | /** 描述 */ 46 | note = ''; 47 | 48 | /** secret */ 49 | secret = ''; 50 | 51 | /** updatedAt */ 52 | updatedAt = ''; 53 | } 54 | 55 | class DataModule { 56 | /** clientKey */ 57 | clientKey = ''; 58 | 59 | /** createdAt */ 60 | createdAt = ''; 61 | 62 | /** id */ 63 | id = undefined; 64 | 65 | /** isDeleted */ 66 | isDeleted = undefined; 67 | 68 | /** moduleName */ 69 | moduleName = ''; 70 | 71 | /** type */ 72 | type = ''; 73 | 74 | /** updatedAt */ 75 | updatedAt = ''; 76 | } 77 | 78 | class DataRole { 79 | /** clientKey */ 80 | clientKey = ''; 81 | 82 | /** comment */ 83 | comment = ''; 84 | 85 | /** createdAt */ 86 | createdAt = ''; 87 | 88 | /** id */ 89 | id = undefined; 90 | 91 | /** isDeleted */ 92 | isDeleted = false; 93 | 94 | /** role */ 95 | role = ''; 96 | 97 | /** updatedAt */ 98 | updatedAt = ''; 99 | } 100 | 101 | class DataRoleInputDTO { 102 | /** clientKey */ 103 | clientKey = ''; 104 | 105 | /** comment */ 106 | comment = ''; 107 | 108 | /** createdAt */ 109 | createdAt = ''; 110 | 111 | /** id */ 112 | id = undefined; 113 | 114 | /** isDeleted */ 115 | isDeleted = false; 116 | 117 | /** role */ 118 | role = ''; 119 | 120 | /** ruleIdList */ 121 | ruleIdList = []; 122 | 123 | /** updatedAt */ 124 | updatedAt = ''; 125 | } 126 | 127 | class DataRoleVO { 128 | /** clientKey */ 129 | clientKey = ''; 130 | 131 | /** comment */ 132 | comment = ''; 133 | 134 | /** createdAt */ 135 | createdAt = ''; 136 | 137 | /** dataRuleDTOList */ 138 | dataRuleDTOList = []; 139 | 140 | /** id */ 141 | id = undefined; 142 | 143 | /** isDeleted */ 144 | isDeleted = false; 145 | 146 | /** role */ 147 | role = ''; 148 | 149 | /** updatedAt */ 150 | updatedAt = ''; 151 | } 152 | 153 | class DataRule { 154 | /** createdAt */ 155 | createdAt = ''; 156 | 157 | /** id */ 158 | id = undefined; 159 | 160 | /** isDeleted */ 161 | isDeleted = undefined; 162 | 163 | /** ruleKeyId */ 164 | ruleKeyId = undefined; 165 | 166 | /** ruleName */ 167 | ruleName = ''; 168 | 169 | /** updatedAt */ 170 | updatedAt = ''; 171 | } 172 | 173 | class DataRuleDTO { 174 | /** businessValueList */ 175 | businessValueList = []; 176 | 177 | /** clientKey */ 178 | clientKey = ''; 179 | 180 | /** createdAt */ 181 | createdAt = ''; 182 | 183 | /** id */ 184 | id = undefined; 185 | 186 | /** isDeleted */ 187 | isDeleted = undefined; 188 | 189 | /** moduleId */ 190 | moduleId = undefined; 191 | 192 | /** moduleName */ 193 | moduleName = ''; 194 | 195 | /** ruleKeyDescription */ 196 | ruleKeyDescription = ''; 197 | 198 | /** ruleKeyId */ 199 | ruleKeyId = undefined; 200 | 201 | /** ruleName */ 202 | ruleName = ''; 203 | 204 | /** updatedAt */ 205 | updatedAt = ''; 206 | } 207 | 208 | class DataRuleDefinition { 209 | /** clientKey */ 210 | clientKey = ''; 211 | 212 | /** createdAt */ 213 | createdAt = ''; 214 | 215 | /** id */ 216 | id = undefined; 217 | 218 | /** isDeleted */ 219 | isDeleted = false; 220 | 221 | /** moduleId */ 222 | moduleId = undefined; 223 | 224 | /** ruleKey */ 225 | ruleKey = ''; 226 | 227 | /** ruleKeyDescription */ 228 | ruleKeyDescription = ''; 229 | 230 | /** updatedAt */ 231 | updatedAt = ''; 232 | } 233 | 234 | class DataScopeDTO { 235 | /** ruleKeyList */ 236 | ruleKeyList = []; 237 | 238 | /** userId */ 239 | userId = undefined; 240 | } 241 | 242 | class OrgTreeDTO { 243 | /** 子部门 */ 244 | children = []; 245 | 246 | /** id */ 247 | id = undefined; 248 | 249 | /** 是否可选 */ 250 | option = undefined; 251 | 252 | /** 顺序 */ 253 | orderValue = undefined; 254 | 255 | /** 组织机构名称 */ 256 | orgName = ''; 257 | 258 | /** 父id */ 259 | parentId = undefined; 260 | 261 | /** 人员列表 */ 262 | personListDTOList = []; 263 | } 264 | 265 | class PagingEntity { 266 | /** list */ 267 | list = []; 268 | 269 | /** page */ 270 | page = undefined; 271 | 272 | /** pageSize */ 273 | pageSize = undefined; 274 | 275 | /** total */ 276 | total = undefined; 277 | } 278 | 279 | class PersonListDTO { 280 | /** 头像图片id */ 281 | avatarId = undefined; 282 | 283 | /** 头像Url */ 284 | avatarUrl = ''; 285 | 286 | /** 常用联系人关联id */ 287 | contactId = undefined; 288 | 289 | /** 性别,枚举值 female、male */ 290 | gender = ''; 291 | 292 | /** 主键id */ 293 | id = undefined; 294 | 295 | /** 是否是主要任职 */ 296 | isPrimary = false; 297 | 298 | /** 姓名 */ 299 | name = ''; 300 | 301 | /** 顺序 */ 302 | orderValue = undefined; 303 | 304 | /** 组织id */ 305 | orgId = undefined; 306 | 307 | /** 组织机构名称 */ 308 | orgName = ''; 309 | 310 | /** 职务 */ 311 | position = ''; 312 | 313 | /** status */ 314 | status = undefined; 315 | 316 | /** statusName */ 317 | statusName = ''; 318 | 319 | /** 手机号 */ 320 | telephone = ''; 321 | 322 | /** 用户id */ 323 | userId = undefined; 324 | } 325 | 326 | class ResourceObjects { 327 | /** api url */ 328 | apiUrl = ''; 329 | 330 | /** 客户端标志 */ 331 | clientKey = ''; 332 | 333 | /** 备注 */ 334 | comment = ''; 335 | 336 | /** 创建时间 */ 337 | createdAt = ''; 338 | 339 | /** 描述 */ 340 | description = ''; 341 | 342 | /** 图标 */ 343 | icon = ''; 344 | 345 | /** id */ 346 | id = undefined; 347 | 348 | /** isDeleted */ 349 | isDeleted = undefined; 350 | 351 | /** 是否默认可见 */ 352 | isVisible = undefined; 353 | 354 | /** 资源顺位 */ 355 | orderValue = undefined; 356 | 357 | /** 父级菜单id */ 358 | parentId = undefined; 359 | 360 | /** 资源码 */ 361 | permissionCode = ''; 362 | 363 | /** 资源拓展字段 */ 364 | resourceBusinessValue = ''; 365 | 366 | /** 资源标志 */ 367 | resourceKey = ''; 368 | 369 | /** 类型 */ 370 | type = undefined; 371 | 372 | /** 更新时间 */ 373 | updatedAt = ''; 374 | } 375 | 376 | class ResourcePageObject { 377 | /** 拓展字段值 */ 378 | businessValue = ''; 379 | 380 | /** 客户端标志 */ 381 | clientKey = ''; 382 | 383 | /** 备注 */ 384 | comment = ''; 385 | 386 | /** 创建时间 */ 387 | createdAt = ''; 388 | 389 | /** id */ 390 | id = undefined; 391 | 392 | /** isDeleted */ 393 | isDeleted = false; 394 | 395 | /** 操作范围(0:可删可编辑 1:不可删可编辑 2:可删不可编辑 3:不可删不可编辑) */ 396 | operationRange = undefined; 397 | 398 | /** ResourceObjects列表 */ 399 | resourceVOList = []; 400 | 401 | /** 角色名称 */ 402 | role = ''; 403 | 404 | /** 角色状态 */ 405 | status = undefined; 406 | 407 | /** 更新时间 */ 408 | updatedAt = ''; 409 | 410 | /** 用户id列表 */ 411 | userIdList = []; 412 | } 413 | 414 | class ResourceRole { 415 | /** 拓展字段值 */ 416 | businessValue = ''; 417 | 418 | /** 客户端标志 */ 419 | clientKey = ''; 420 | 421 | /** 备注 */ 422 | comment = ''; 423 | 424 | /** 创建时间 */ 425 | createdAt = ''; 426 | 427 | /** id */ 428 | id = undefined; 429 | 430 | /** isDeleted */ 431 | isDeleted = false; 432 | 433 | /** 操作范围(0:可删可编辑 1:不可删可编辑 2:可删不可编辑 3:不可删不可编辑) */ 434 | operationRange = undefined; 435 | 436 | /** 角色名称 */ 437 | role = ''; 438 | 439 | /** 角色状态 */ 440 | status = undefined; 441 | 442 | /** 更新时间 */ 443 | updatedAt = ''; 444 | } 445 | 446 | class ResourceTreeObject { 447 | /** api url */ 448 | apiUrl = ''; 449 | 450 | /** 子节点 */ 451 | children = []; 452 | 453 | /** 备注 */ 454 | comment = ''; 455 | 456 | /** 描述 */ 457 | description = ''; 458 | 459 | /** 图标 */ 460 | icon = ''; 461 | 462 | /** id */ 463 | id = undefined; 464 | 465 | /** 是否默认可见 */ 466 | isVisible = undefined; 467 | 468 | /** 资源顺位 */ 469 | orderValue = undefined; 470 | 471 | /** 父级id */ 472 | parentId = undefined; 473 | 474 | /** 页面子元素 */ 475 | privilegeList = []; 476 | 477 | /** 拓展字段 */ 478 | resourceBusinessValue = ''; 479 | 480 | /** 资源标志 */ 481 | resourceKey = ''; 482 | 483 | /** 类型 */ 484 | type = undefined; 485 | } 486 | 487 | class ResourcesForDetails { 488 | /** api url */ 489 | apiUrl = ''; 490 | 491 | /** 客户端标志 */ 492 | clientKey = ''; 493 | 494 | /** 备注 */ 495 | comment = ''; 496 | 497 | /** 创建时间 */ 498 | createdAt = ''; 499 | 500 | /** 描述 */ 501 | description = ''; 502 | 503 | /** 图标 */ 504 | icon = ''; 505 | 506 | /** id */ 507 | id = undefined; 508 | 509 | /** isDeleted */ 510 | isDeleted = undefined; 511 | 512 | /** 是否默认可见 */ 513 | isVisible = undefined; 514 | 515 | /** 资源顺位 */ 516 | orderValue = undefined; 517 | 518 | /** 父级菜单id */ 519 | parentId = undefined; 520 | 521 | /** 父级菜单名称 */ 522 | parentName = ''; 523 | 524 | /** 资源码 */ 525 | permissionCode = ''; 526 | 527 | /** 资源拓展字段 */ 528 | resourceBusinessValue = ''; 529 | 530 | /** 资源标志 */ 531 | resourceKey = ''; 532 | 533 | /** 类型 */ 534 | type = undefined; 535 | 536 | /** 更新时间 */ 537 | updatedAt = ''; 538 | } 539 | 540 | class RoleDTO { 541 | /** 拓展字段值 */ 542 | businessValue = ''; 543 | 544 | /** 客户端标志 */ 545 | clientKey = ''; 546 | 547 | /** 备注 */ 548 | comment = ''; 549 | 550 | /** 创建时间 */ 551 | createdAt = ''; 552 | 553 | /** id */ 554 | id = undefined; 555 | 556 | /** isDeleted */ 557 | isDeleted = false; 558 | 559 | /** 操作范围(0:可删可编辑 1:不可删可编辑 2:可删不可编辑 3:不可删不可编辑) */ 560 | operationRange = undefined; 561 | 562 | /** 资源id列表 */ 563 | resourceIds = []; 564 | 565 | /** 角色名称 */ 566 | role = ''; 567 | 568 | /** 角色状态 */ 569 | status = undefined; 570 | 571 | /** 更新时间 */ 572 | updatedAt = ''; 573 | 574 | /** 用户id列表 */ 575 | userIds = []; 576 | } 577 | 578 | class UserQueryDTO { 579 | /** 客户端标识 */ 580 | clientKey = ''; 581 | 582 | /** 用户id列表 */ 583 | userIds = []; 584 | } 585 | 586 | class UserReduceRoleDTO { 587 | /** 角色id列表 */ 588 | roleIds = []; 589 | 590 | /** 用户id */ 591 | userId = undefined; 592 | } 593 | 594 | export const authorization = { 595 | BindUserDTO, 596 | BusinessQueryDTO, 597 | ClientConfig, 598 | DataModule, 599 | DataRole, 600 | DataRoleInputDTO, 601 | DataRoleVO, 602 | DataRule, 603 | DataRuleDTO, 604 | DataRuleDefinition, 605 | DataScopeDTO, 606 | OrgTreeDTO, 607 | PagingEntity, 608 | PersonListDTO, 609 | ResourceObjects, 610 | ResourcePageObject, 611 | ResourceRole, 612 | ResourceTreeObject, 613 | ResourcesForDetails, 614 | RoleDTO, 615 | UserQueryDTO, 616 | UserReduceRoleDTO, 617 | }; 618 | -------------------------------------------------------------------------------- /src/api/authorization/index.ts: -------------------------------------------------------------------------------- 1 | import { authorization as defs } from './baseClass'; 2 | export { authorization } from './mods/'; 3 | export { defs }; 4 | -------------------------------------------------------------------------------- /src/api/authorization/mods/authResource/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 新的资源管理接口 3 | */ 4 | import * as save from './save'; 5 | import * as saveList from './saveList'; 6 | 7 | export { save, saveList }; 8 | -------------------------------------------------------------------------------- /src/api/authorization/mods/authResource/save.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 添加资源 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/auth/resource/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/authResource/saveList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 批量添加资源 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/auth/resource/saveList', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/authResourceRole/hasRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 校验用户是否已经绑定该角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = false; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/auth/role/resource/hasRole', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || false; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/authResourceRole/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 新的ResourceRole管理接口 3 | */ 4 | import * as hasRole from './hasRole'; 5 | 6 | export { hasRole }; 7 | -------------------------------------------------------------------------------- /src/api/authorization/mods/client/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 客户端管理 3 | */ 4 | import * as remove from './remove'; 5 | import * as log from './log'; 6 | import * as login from './login'; 7 | import * as save from './save'; 8 | 9 | export { remove, log, login, save }; 10 | -------------------------------------------------------------------------------- /src/api/authorization/mods/client/log.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description log 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = ''; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/client/log', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || ''; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/client/login.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description login 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.ClientConfig(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/client/login', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.ClientConfig(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/client/remove.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description delete 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.delete(backEndUrl + '/client/delete', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/client/save.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description save 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/client/save', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/deleteDataModule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除模块 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/module/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/deleteRule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除数据规则 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/rule/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/detail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取数据规则详情 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.DataRuleDTO(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/data/rule/detail', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.DataRuleDTO(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/getDataScope.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取用户数据权限 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/getDataScope', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/getMockData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取mock数据 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/data/mock', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数据管理 3 | */ 4 | import * as getDataScope from './getDataScope'; 5 | import * as getMockData from './getMockData'; 6 | import * as deleteDataModule from './deleteDataModule'; 7 | import * as listModule from './listModule'; 8 | import * as saveModule from './saveModule'; 9 | import * as remove from './remove'; 10 | import * as list from './list'; 11 | import * as save from './save'; 12 | import * as deleteRule from './deleteRule'; 13 | import * as detail from './detail'; 14 | import * as listRule from './listRule'; 15 | import * as saveRule from './saveRule'; 16 | 17 | export { 18 | getDataScope, 19 | getMockData, 20 | deleteDataModule, 21 | listModule, 22 | saveModule, 23 | remove, 24 | list, 25 | save, 26 | deleteRule, 27 | detail, 28 | listRule, 29 | saveRule, 30 | }; 31 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取已经定义的规则列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/data/rule/def/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/listModule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取模块列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/data/module/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/listRule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取数据规则列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/data/rule/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/remove.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除规则定义 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/rule/def/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/save.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存规则定义 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/rule/def/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/saveModule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存数据模块 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/module/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/data/saveRule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存数据规则 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/data/rule/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/addForUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数据角色绑定用户 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/data/add/user', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/bindUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户批量绑定角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/data/bindUser', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/deleteUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数据角色解绑用户 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/data/remove/user', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/detail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取数据角色详情 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.DataRoleVO(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/data/detail', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.DataRoleVO(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数据角色管理 3 | */ 4 | import * as addForUser from './addForUser'; 5 | import * as bindUser from './bindUser'; 6 | import * as remove from './remove'; 7 | import * as detail from './detail'; 8 | import * as list from './list'; 9 | import * as deleteUser from './deleteUser'; 10 | import * as save from './save'; 11 | 12 | export { addForUser, bindUser, remove, detail, list, deleteUser, save }; 13 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取已创建的数据角色列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/data/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/remove.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除数据角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/data/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/dataRole/save.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存数据角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/data/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/getBusinessValueListByRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取角色访问businessValue列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/deployment/authz/getBusinessValueListByRole', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/getDataAccessUserList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取此数据权限的用户列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/deployment/authz/getDataAccessUserList', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/getRoleIdToBusinessValue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取能访问businessValue的RoleId 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/deployment/authz/getRoleIdToBusinessValue', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/getUserIdsByRoleId.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取拥有此角色的所有用户id 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/deployment/authz/getUserIdsByRoleId', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/getUserRoleIdList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取用户所有的数据角色Id 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/deployment/authz/getUserRoleIdList', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/deploymentAuthz/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 部署权限控制中心接口 3 | */ 4 | import * as getBusinessValueListByRole from './getBusinessValueListByRole'; 5 | import * as getDataAccessUserList from './getDataAccessUserList'; 6 | import * as getRoleIdToBusinessValue from './getRoleIdToBusinessValue'; 7 | import * as getUserIdsByRoleId from './getUserIdsByRoleId'; 8 | import * as getUserRoleIdList from './getUserRoleIdList'; 9 | 10 | export { 11 | getBusinessValueListByRole, 12 | getDataAccessUserList, 13 | getRoleIdToBusinessValue, 14 | getUserIdsByRoleId, 15 | getUserRoleIdList, 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/authorization/mods/index.ts: -------------------------------------------------------------------------------- 1 | import * as authResource from './authResource'; 2 | import * as authResourceRole from './authResourceRole'; 3 | import * as client from './client'; 4 | import * as data from './data'; 5 | import * as dataRole from './dataRole'; 6 | import * as deploymentAuthz from './deploymentAuthz'; 7 | import * as resource from './resource'; 8 | import * as resourceRole from './resourceRole'; 9 | import * as role from './role'; 10 | 11 | export const authorization = { 12 | authResource, 13 | authResourceRole, 14 | client, 15 | data, 16 | dataRole, 17 | deploymentAuthz, 18 | resource, 19 | resourceRole, 20 | role, 21 | }; 22 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/deleteResource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除资源 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/resource/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/detail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 查询ResourcesForDetails 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.ResourcesForDetails(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/detail', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.ResourcesForDetails(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/getMenuTreeByRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取角色对应的菜单树 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/role/getMenuTree', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/getPermissionByUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 校验用户是否拥有权限 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/resource/getPermissionByUrl', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 资源管理 3 | */ 4 | import * as deleteResource from './deleteResource'; 5 | import * as detail from './detail'; 6 | import * as getPermissionByUrl from './getPermissionByUrl'; 7 | import * as listResource from './listResource'; 8 | import * as listPagination from './listPagination'; 9 | import * as listTree from './listTree'; 10 | import * as getMenuTreeByRole from './getMenuTreeByRole'; 11 | import * as newResource from './newResource'; 12 | import * as postSaveList from './postSaveList'; 13 | import * as listUserResource from './listUserResource'; 14 | import * as listUserResourceData from './listUserResourceData'; 15 | import * as listApiUrl from './listApiUrl'; 16 | 17 | export { 18 | deleteResource, 19 | detail, 20 | getPermissionByUrl, 21 | listResource, 22 | listPagination, 23 | listTree, 24 | getMenuTreeByRole, 25 | newResource, 26 | postSaveList, 27 | listUserResource, 28 | listUserResourceData, 29 | listApiUrl, 30 | }; 31 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listApiUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取apiUrl列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/user/listApiUrl', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listPagination.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 查询资源列表 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.PagingEntity(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/listPagination', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.PagingEntity(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listResource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listTree.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 全部资源列表(树形) 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/listTree', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listUserResource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户资源列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/user/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/listUserResourceData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户资源列表(树形) 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/resource/user/list/data', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/newResource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存资源-这个接口仍然可以使用,但建议使用新的资源管理接口中/auth/resource/save 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/resource/save', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resource/postSaveList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存资源(批量)-这个接口仍然可以使用,但建议使用新的资源管理接口中/auth/resource/saveList 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/resource/saveList', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/addUserRes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户绑定角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/resource/user/add', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/clearUserRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 清除用户所有角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/clearUserRole', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/x-www-form-urlencoded', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/copyRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 拷贝角色资源新建角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/resource/copyRole', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/deleteUnusedRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除没有用户使用的角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/role/resource/deleteUnusedRole', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description ResourceRole管理 3 | */ 4 | import * as clearUserRole from './clearUserRole'; 5 | import * as copyRole from './copyRole'; 6 | import * as resourceDelete from './resourceDelete'; 7 | import * as deleteUnusedRole from './deleteUnusedRole'; 8 | import * as resourceRoleDetail from './resourceRoleDetail'; 9 | import * as resourceRoleDetailUser from './resourceRoleDetailUser'; 10 | import * as resourceRoleList from './resourceRoleList'; 11 | import * as listByBusinessValueList from './listByBusinessValueList'; 12 | import * as listByBusinessValueListPagination from './listByBusinessValueListPagination'; 13 | import * as listByBusinessValues from './listByBusinessValues'; 14 | import * as listByUserId from './listByUserId'; 15 | import * as listByUserIds from './listByUserIds'; 16 | import * as listPagination from './listPagination'; 17 | import * as resourceSave from './resourceSave'; 18 | import * as resourceSaveAddUser from './resourceSaveAddUser'; 19 | import * as addUserRes from './addUserRes'; 20 | import * as postUserAddList from './postUserAddList'; 21 | import * as reduce from './reduce'; 22 | import * as validateRoleName from './validateRoleName'; 23 | import * as validateRoleUser from './validateRoleUser'; 24 | 25 | export { 26 | clearUserRole, 27 | copyRole, 28 | resourceDelete, 29 | deleteUnusedRole, 30 | resourceRoleDetail, 31 | resourceRoleDetailUser, 32 | resourceRoleList, 33 | listByBusinessValueList, 34 | listByBusinessValueListPagination, 35 | listByBusinessValues, 36 | listByUserId, 37 | listByUserIds, 38 | listPagination, 39 | resourceSave, 40 | resourceSaveAddUser, 41 | addUserRes, 42 | postUserAddList, 43 | reduce, 44 | validateRoleName, 45 | validateRoleUser, 46 | }; 47 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listByBusinessValueList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 根据业务拓展字段查询角色(不带分页) 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/listByBusinessValueList', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | data, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listByBusinessValueListPagination.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 根据业务拓展字段查询角色(带分页) 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.PagingEntity(); 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/listByBusinessValueListPagination', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | data, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || new defs.authorization.PagingEntity(); 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listByBusinessValues.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取ResourceRole列表(含分页) 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.PagingEntity(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/listByBusinessValues', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/x-www-form-urlencoded', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || new defs.authorization.PagingEntity(); 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listByUserId.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取用户已绑定的ResourceRole 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/resource/listByUserId', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listByUserIds.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 根据用户id列表查询其所有角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/listByUserIds', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | data, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/listPagination.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取ResourceRole列表(含分页) 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.PagingEntity(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/role/resource/listPagination', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || new defs.authorization.PagingEntity(); 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/postUserAddList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户绑定ResourceRole(批量) 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/user/addList', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | data, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/reduce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 用户角色解绑 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/resource/user/remove', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceDelete.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 删除ResourceRole 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/resource/delete', { 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceRoleDetail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取ResourceRole详情 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.ResourcePageObject(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/resource/detail', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.ResourcePageObject(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceRoleDetailUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取ResourceRole详情包含对应用户 3 | */ 4 | import * as defs from '../../baseClass'; 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = new defs.authorization.ResourcePageObject(); 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/resource/detail/user', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || new defs.authorization.ResourcePageObject(); 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceRoleList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取ResourceRole列表 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get(backEndUrl + '/role/resource/list', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | params, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || []; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceSave.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存ResourceRole 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/resource/save', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/resourceSaveAddUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 保存ResourceRole并绑定用户角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post( 15 | backEndUrl + '/role/resource/save/addUser', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | data, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/validateRoleName.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 校验角色是否已经存在 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = []; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/role/resource/validateRoleName', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || []; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/resourceRole/validateRoleUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 校验用户是否已经绑定该角色 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(params = {}) { 13 | const request = await initRequest(); 14 | const result = await request.get( 15 | backEndUrl + '/role/resource/validateRoleUser', 16 | { 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | params, 21 | }, 22 | ); 23 | if (result) { 24 | if (!result.success) { 25 | throw new Error(JSON.stringify(result)); 26 | } else { 27 | return result.data || undefined; 28 | } 29 | } else { 30 | throw new Error(JSON.stringify({ message: '接口未响应' })); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/api/authorization/mods/role/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 角色管理 3 | */ 4 | import * as update from './update'; 5 | 6 | export { update }; 7 | -------------------------------------------------------------------------------- /src/api/authorization/mods/role/update.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 更新ResourceRole基本信息 3 | */ 4 | 5 | import serverConfig from '../../../../../server.config'; 6 | import { initRequest } from '@/common'; 7 | 8 | const backEndUrl = serverConfig()['authorization']; 9 | 10 | export const init = undefined; 11 | 12 | export async function fetch(data = {}) { 13 | const request = await initRequest(); 14 | const result = await request.post(backEndUrl + '/role/update', { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | data, 19 | }); 20 | if (result) { 21 | if (!result.success) { 22 | throw new Error(JSON.stringify(result)); 23 | } else { 24 | return result.data || undefined; 25 | } 26 | } else { 27 | throw new Error(JSON.stringify({ message: '接口未响应' })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { defs as authorizationDefs, authorization } from './authorization'; 2 | 3 | (window as any).defs = { 4 | authorization: authorizationDefs, 5 | }; 6 | (window as any).API = { 7 | authorization, 8 | }; 9 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 运行时配置,可以在项目运行过程中执行一些操作。 3 | * @公司: thundersdata 4 | * @作者: 陈杰 5 | * @Date: 2019-10-25 13:43:18 6 | * @LastEditors: 廖军 7 | * @LastEditTime: 2020-10-28 20:51:08 8 | */ 9 | import request from 'umi-request'; 10 | import type { MenuDataItem } from '@ant-design/pro-layout'; 11 | import arrayUtils from '@/utils/array'; 12 | import { LOGIN_CONFIG } from './constant'; 13 | 14 | /** 初始化数据 */ 15 | export async function getInitialState() { 16 | let menus: MenuDataItem[] = []; 17 | const privileges: string[] = []; 18 | const userInfo = {}; 19 | 20 | const accessToken = localStorage.getItem('accessToken'); 21 | if (LOGIN_CONFIG.isSSO || accessToken) { 22 | try { 23 | const result = await request.get('/resource'); 24 | const { code, success, data = [] } = result; 25 | if (code === 20000 && success) { 26 | const routes: PrivilegeResource[] = arrayUtils.deepOrder({ 27 | data, 28 | childKey: 'children', 29 | orderKey: 'orderValue', 30 | type: 'asc', 31 | }); 32 | const flatRoutes = arrayUtils.deepFlatten(routes); 33 | flatRoutes.forEach(route => { 34 | if (route.privilegeList) { 35 | privileges.push(...route.privilegeList); 36 | } 37 | }); 38 | menus = convertResourceToMenu(routes); 39 | } 40 | } catch (error) { 41 | console.error(error); 42 | } 43 | } 44 | return { 45 | menus, 46 | privileges, 47 | userInfo, 48 | }; 49 | } 50 | 51 | /** 52 | * 将后台返回的权限资源,转换成应用的菜单 53 | * @param resources 54 | */ 55 | function convertResourceToMenu(list: PrivilegeResource[]): MenuDataItem[] { 56 | return list.map(item => { 57 | if (item.children && item.children.length > 0) { 58 | return { 59 | name: item.description, 60 | key: `${item.apiUrl}`, 61 | icon: item.icon, 62 | path: item.apiUrl, 63 | children: convertResourceToMenu(item.children), 64 | }; 65 | } 66 | return { 67 | name: item.description, 68 | key: `${item.apiUrl}`, 69 | icon: item.icon, 70 | path: item.apiUrl, 71 | }; 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/pic_404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/assets/pic_404.png -------------------------------------------------------------------------------- /src/assets/pic_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/assets/pic_empty.png -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | import type { ResponseError } from 'umi-request'; 2 | import { extend } from 'umi-request'; 3 | import { history } from 'umi'; 4 | import { LoginFailure } from './enums'; 5 | 6 | const codeMessage: Record = { 7 | 200: '服务器成功返回请求的数据。', 8 | 201: '新建或修改数据成功。', 9 | 202: '一个请求已经进入后台排队(异步任务)。', 10 | 204: '删除数据成功。', 11 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 12 | 401: '用户没有权限(令牌、用户名、密码错误)。', 13 | 403: '用户得到授权,但是访问是被禁止的。', 14 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 15 | 406: '请求的格式不可得。', 16 | 410: '请求的资源被永久删除,且不会再得到的。', 17 | 422: '当创建一个对象时,发生一个验证错误。', 18 | 500: '服务器发生错误,请检查服务器。', 19 | 502: '网关错误。', 20 | 503: '服务不可用,服务器暂时过载或维护。', 21 | 504: '网关超时。', 22 | 405: 'xxxx', 23 | }; 24 | 25 | export function errorHandler(error: ResponseError) { 26 | const { response } = error; 27 | if (response && response.status) { 28 | const errorText = codeMessage[response.status] || response.statusText; 29 | const { status, url } = response; 30 | 31 | throw new Error( 32 | JSON.stringify({ 33 | message: errorText, 34 | description: `请求错误 ${status}: ${url}`, 35 | }), 36 | ); 37 | } 38 | throw error; 39 | } 40 | 41 | const getToken = () => 42 | new Promise((resolve) => { 43 | setTimeout(() => { 44 | const token = localStorage.getItem('accessToken'); 45 | resolve(token); 46 | }, 0); 47 | }); 48 | 49 | export const initRequest = async () => { 50 | const token = await getToken(); 51 | /** 这边可对接口请求做一些统一的封装 */ 52 | const request = extend({ 53 | useCache: false, 54 | ttl: 60000, 55 | credentials: 'same-origin', 56 | headers: { 57 | accessToken: token! as string, 58 | }, 59 | errorHandler, 60 | }); 61 | 62 | request.interceptors.response.use((response) => { 63 | response 64 | .clone() 65 | .json() 66 | .then((res) => { 67 | if ([LoginFailure['不允许登录'], LoginFailure['登录过期']].includes(res.code)) { 68 | history.replace('/user/login'); 69 | } 70 | }); 71 | return response; 72 | }); 73 | 74 | return request; 75 | }; 76 | -------------------------------------------------------------------------------- /src/components/DetailValue/index.module.less: -------------------------------------------------------------------------------- 1 | .file-item-image-card { 2 | width: 160px; 3 | height: 160px; 4 | padding: 4px; 5 | border: 1px solid #ccc; 6 | 7 | &:hover { 8 | .file-item-image-info::before { 9 | opacity: 1; 10 | } 11 | .file-item-image-actions { 12 | opacity: 1; 13 | } 14 | } 15 | } 16 | .file-item-image-info { 17 | position: relative; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | height: 100%; 22 | overflow: hidden; 23 | 24 | &::before { 25 | position: absolute; 26 | z-index: 1; 27 | width: 100%; 28 | height: 100%; 29 | background-color: rgba(0,0,0,.5); 30 | opacity: 0; 31 | transition: all .3s; 32 | content: ' '; 33 | } 34 | } 35 | .file-item-image-actions { 36 | position: absolute; 37 | top: 50%; 38 | left: 50%; 39 | z-index: 10; 40 | white-space: nowrap; 41 | transform: translate(-50%, -50%); 42 | opacity: 0; 43 | transition: all .3s; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/DetailValue/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 详情展示项。可以根据type渲染不同的展示 3 | * type目前支持:default(默认)/file(文件,包括图片) 4 | */ 5 | import React, { forwardRef, useState } from 'react'; 6 | import { Row, Col, Modal } from 'antd'; 7 | import { useToggle } from 'ahooks'; 8 | import { EyeOutlined, DownloadOutlined } from '@ant-design/icons'; 9 | import { FILE_TYPE_MAP, getDownloadUrlWithId } from '../UploadFormItem/utils/upload'; 10 | 11 | import styles from './index.module.less'; 12 | 13 | type DetailValueType = 'default' | 'file'; 14 | 15 | export default forwardRef< 16 | HTMLDivElement, 17 | { 18 | value?: number | string | FileDTO[]; 19 | type?: DetailValueType; 20 | generateUrl?: (file: FileDTO) => string; 21 | } 22 | >(({ value, type = 'default', generateUrl }, ref) => { 23 | const [visible, toggle] = useToggle(false); 24 | const [url, setUrl] = useState(); 25 | 26 | const handleDownload = (file: FileDTO) => { 27 | if (file.fileUrl) { 28 | window.open(file.fileUrl, '_blank'); 29 | } else { 30 | window.open(getDownloadUrlWithId(file.fileId), '_blank'); 31 | } 32 | }; 33 | 34 | if (type === 'default') { 35 | return
{value}
; 36 | } 37 | if (type === 'file') { 38 | const files = (value as FileDTO[]) || []; 39 | const [images, otherTypeFiles] = separateFiles(files.filter((item) => item.fileName)); 40 | 41 | return ( 42 |
43 | 44 | {images.map((file) => { 45 | const { fileUrl, fileId } = file; 46 | const filePreviewUrl = (generateUrl && generateUrl(file)) || fileUrl; 47 | return ( 48 | 49 | 71 | 72 | ); 73 | })} 74 | 75 | {otherTypeFiles.map((file) => ( 76 |
85 | {file.fileName} 86 |
87 | ))} 88 | toggle.toggle()} 97 | width={650} 98 | > 99 | 图片无法访问 100 | 101 |
102 | ); 103 | } 104 | return null; 105 | }); 106 | 107 | /** 108 | * 把图片和其他类型的文件区分开 109 | * @param files 110 | */ 111 | function separateFiles(files: FileDTO[]) { 112 | const images: FileDTO[] = []; 113 | const otherTypeFiles: FileDTO[] = []; 114 | 115 | files.forEach((file) => { 116 | const index = file.fileName.lastIndexOf('.'); 117 | const ext = file.fileName.substring(index); 118 | if (FILE_TYPE_MAP.图片.includes(ext)) { 119 | images.push(file); 120 | } else { 121 | otherTypeFiles.push(file); 122 | } 123 | }); 124 | 125 | return [images, otherTypeFiles]; 126 | } 127 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.module.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .toolbar { 4 | position: fixed; 5 | right: 0; 6 | bottom: 0; 7 | z-index: 9; 8 | width: 100%; 9 | height: 56px; 10 | padding: 0 24px; 11 | line-height: 56px; 12 | border-top: 1px solid @border-color-split; 13 | box-shadow: @box-shadow-base; 14 | 15 | &::after { 16 | display: block; 17 | clear: both; 18 | content: ''; 19 | } 20 | 21 | .left { 22 | float: left; 23 | } 24 | 25 | .right { 26 | float: right; 27 | } 28 | 29 | button + button { 30 | margin-left: 8px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import type { CSSProperties } from 'react'; 3 | import { RouteContext } from '@ant-design/pro-layout'; 4 | import classNames from 'classnames'; 5 | import styles from './index.module.less'; 6 | 7 | export interface FooterToolbarProps { 8 | extra?: React.ReactNode; 9 | style?: CSSProperties; 10 | className?: string; 11 | isMobile?: boolean; 12 | } 13 | 14 | export default class FooterToolbar extends Component { 15 | getWidth = ({ 16 | collapsed, 17 | isMobile, 18 | siderWidth, 19 | }: { 20 | collapsed?: boolean; 21 | isMobile?: boolean; 22 | siderWidth?: number; 23 | }) => { 24 | const sider = document.querySelector('.ant-layout-sider') as HTMLDivElement; 25 | if (!sider) { 26 | return undefined; 27 | } 28 | return isMobile ? undefined : `calc(100% - ${collapsed ? 80 : siderWidth || 256}px)`; 29 | }; 30 | 31 | render() { 32 | const { children, className, extra, ...restProps } = this.props; 33 | return ( 34 | 35 | {value => ( 36 |
41 |
{extra}
42 |
{children}
43 |
44 | )} 45 |
46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/GlobalLoading.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 全局loading操作-用于非页面组件执行的地方,比如app.ts 3 | * @公司: thundersdata 4 | * @作者: 廖军 5 | * @Date: 2020-07-08 11:37:57 6 | * @LastEditors: 廖军 7 | * @LastEditTime: 2020-07-08 11:49:23 8 | */ 9 | 10 | import React from 'react'; 11 | import ReactDOM from 'react-dom'; 12 | import LoadingPage from './LoadingPage'; 13 | 14 | /** 15 | * 加载全局loading 16 | */ 17 | export const showGlobalLoading = () => { 18 | const loadingDom = document.createElement('div'); 19 | loadingDom.setAttribute('id', 'globalLoadingDom'); 20 | loadingDom.style.position = 'absolute'; 21 | loadingDom.style.top = '0'; 22 | loadingDom.style.width = '100vw'; 23 | loadingDom.style.height = '100vh'; 24 | document.body.appendChild(loadingDom); 25 | ReactDOM.render(, loadingDom); 26 | }; 27 | 28 | /** 29 | * 隐藏全局loading 30 | */ 31 | export const hideGlobalLoading = () => { 32 | const loadingDom = document.getElementById('globalLoadingDom'); 33 | if (loadingDom) { 34 | document.body.removeChild(loadingDom); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/Iconfont/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | 3 | interface IconfontProps { 4 | name: string; 5 | className?: string; 6 | style?: CSSProperties; 7 | } 8 | /** 自定义图标 */ 9 | const Iconfont: React.FC = (props: IconfontProps) => { 10 | return ; 11 | }; 12 | 13 | export default Iconfont; 14 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | export default function Loading() { 5 | return ( 6 |
15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/LoadingPage/assets/loading-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/components/LoadingPage/assets/loading-bg.jpg -------------------------------------------------------------------------------- /src/components/LoadingPage/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/components/LoadingPage/assets/loading.gif -------------------------------------------------------------------------------- /src/components/LoadingPage/assets/sign_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/components/LoadingPage/assets/sign_logo.png -------------------------------------------------------------------------------- /src/components/LoadingPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Loading from '../Loading'; 3 | import { hideGlobalLoading } from '../GlobalLoading'; 4 | 5 | import loadingBg from './assets/loading-bg.jpg'; 6 | import logo from './assets/sign_logo.png'; 7 | import loadingGif from './assets/loading.gif'; 8 | 9 | export default () => { 10 | /** 11 | * 分为3个阶段 12 | * 1、app.ts 文件,初始化获取资源菜单、用户数据,会显示全局LoadingPage; 13 | * 2、上一部完成,但layout资源没有加载完成,会继续出现全局loading(这个loading是config里面的配置); 14 | * 3、为了使显示效果的连贯与统一,没有加载到layout时使用LoadingPage效果,否则隐藏LoadingPage,展示普通Loading。 15 | */ 16 | if (document.getElementsByClassName('ant-layout').length > 0) { 17 | hideGlobalLoading(); 18 | return ; 19 | } 20 | return ( 21 |
32 |
33 | LOGO 34 |
42 | loading 43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/UploadFormItem/index.module.less: -------------------------------------------------------------------------------- 1 | .labelWrap div { 2 | white-space: nowrap; 3 | } 4 | 5 | .uploadItemWrap { 6 | :global { 7 | .ant-upload-list-item-list-type-picture-card:hover { 8 | .ant-upload-list-item-name { 9 | position: absolute; 10 | top: 0; 11 | z-index: 1; 12 | display: block; 13 | color: #fff; 14 | } 15 | } 16 | 17 | .ant-upload-list-item-undefined { 18 | &.ant-upload-list-item-list-type-picture-card { 19 | border: 1px solid #ff4d4f; 20 | } 21 | 22 | .ant-upload-list-item-name { 23 | color: #ff4d4f !important; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/UploadFormItem/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 封装 Upload FormItem 组件 3 | * @公司: thundersdata 4 | * @作者: 阮旭松 5 | * @Date: 2020-06-11 10:22:48 6 | * @LastEditors: 廖军 7 | * @LastEditTime: 2020-11-25 17:53:23 8 | */ 9 | import React, { forwardRef } from 'react'; 10 | import { Form, Button, Upload, Tooltip } from 'antd'; 11 | import { 12 | getFileValidators, 13 | getPublicUploadProps, 14 | ATTACHMENT_MAX_FILE_COUNT, 15 | handleUpload, 16 | getFileSizeName, 17 | getBeforeUpload, 18 | ATTACHMENT_MAX_FILE_SIZE, 19 | } from './utils/upload'; 20 | import type { InternalFieldProps } from 'rc-field-form/es/Field'; 21 | import type { UploadProps, UploadChangeParam } from 'antd/lib/upload'; 22 | import type { FormItemLabelProps } from 'antd/lib/form/FormItemLabel'; 23 | import type { FormItemInputProps } from 'antd/lib/form/FormItemInput'; 24 | import { QuestionCircleOutlined } from '@ant-design/icons'; 25 | 26 | import styles from './index.module.less'; 27 | 28 | export interface UploadFormItemProps { 29 | /** formItem 的字段名 */ 30 | name?: string; 31 | /** formItem 的标签 */ 32 | label?: string; 33 | /** 是否隐藏 label 的 tooltip 提示 */ 34 | hiddenTooltip?: boolean; 35 | /** 是否必选 */ 36 | required?: boolean; 37 | /** 必选报错提示信息 */ 38 | requiredMessage?: string; 39 | /** 限制文件后缀,为可选后缀列表(支持string),传入 true 默认为图片 */ 40 | accept?: string | string[]; 41 | /** 限制文件大小,单位为 M,默认 10 M */ 42 | maxSize?: number; 43 | /** 限制文件个数,默认为 10 个 */ 44 | maxCount?: number; 45 | /** 是否禁用 */ 46 | disabled?: boolean; 47 | /** 是否支持多个上传 */ 48 | multiple?: boolean; 49 | /** 改变事件 */ 50 | onChange?: (info: UploadChangeParam) => void; 51 | /** formItem 属性 */ 52 | formItemProps?: InternalFieldProps & FormItemLabelProps & FormItemInputProps; 53 | /** upload 属性 */ 54 | uploadProps?: UploadProps; 55 | /** 上传中根据状态切换 loading 状态 */ 56 | setLoading?: (status: boolean) => void; 57 | } 58 | 59 | /** 60 | * 直接在自定义组件里面检测参数变化 61 | * @param param0 62 | */ 63 | interface CustomUploadProps extends UploadProps { 64 | maxCount: number; 65 | children: React.ReactNode; 66 | } 67 | const CustomUpload = forwardRef( 68 | ({ fileList = [], children, maxCount, ...rest }, ref) => { 69 | const uploadDisabled = fileList?.length >= maxCount; 70 | return ( 71 | 72 | {!uploadDisabled && (children || )} 73 | 74 | ); 75 | }, 76 | ); 77 | 78 | const UploadFormItem: React.FC = (uploadItemProps) => { 79 | const { 80 | name = '', 81 | label = '', 82 | hiddenTooltip = false, 83 | required = false, 84 | requiredMessage = `'${label}' 是必填字段`, 85 | accept, 86 | maxSize = ATTACHMENT_MAX_FILE_SIZE, 87 | maxCount = ATTACHMENT_MAX_FILE_COUNT, 88 | disabled = false, 89 | multiple = false, 90 | formItemProps = {}, 91 | uploadProps = {}, 92 | onChange, 93 | children, 94 | setLoading, 95 | } = uploadItemProps; 96 | if (maxCount < 1) { 97 | throw new Error('maxCount 必须是大于0的整数'); 98 | } 99 | const formattedAccept = Array.isArray(accept) ? accept.join(',') : accept; 100 | const validatorObj = { 101 | maxCount, 102 | maxSize, 103 | accept, 104 | }; 105 | 106 | /** 改变上传文件调用 */ 107 | const handleChange = async (info: UploadChangeParam) => { 108 | onChange?.(info); 109 | if (setLoading) { 110 | setLoading(true); 111 | 112 | if (!info.fileList.find((item) => item.status === 'uploading')) { 113 | setLoading(false); 114 | } 115 | } 116 | }; 117 | 118 | /** 渲染tooltip 的 title */ 119 | const renderLabelTitle = () => ( 120 |
121 | {maxCount &&
个数:最多上传 {maxCount} 个文件
} 122 | {maxSize &&
大小:文件大小限制 {getFileSizeName(maxSize)}
} 123 | {accept && ( 124 |
125 | 可接受的文件格式:
{accept}
126 |
127 | )} 128 |
129 | ); 130 | 131 | /** 渲染表单 label(带 tooltip) */ 132 | const renderLabel = () => 133 | !hiddenTooltip && (maxSize || maxCount || accept) ? ( 134 |
135 | {label}  136 | 137 | 138 | 139 |
140 | ) : ( 141 | {label} 142 | ); 143 | 144 | return ( 145 | { 154 | return { fileList: value }; 155 | }} 156 | {...formItemProps} 157 | > 158 | 167 | {children} 168 | 169 | 170 | ); 171 | }; 172 | 173 | export default UploadFormItem; 174 | -------------------------------------------------------------------------------- /src/components/UploadFormItem/utils/upload.ts: -------------------------------------------------------------------------------- 1 | import type { UploadProps } from 'antd/lib/upload'; 2 | import string from '@/utils/string'; 3 | import type { UploadFile, RcFile } from 'antd/lib/upload/interface'; 4 | import { message } from 'antd'; 5 | 6 | // 文件服务开发环境地址 7 | export const UPLOAD_URL = 'http://object-service.dev.thundersdata.com'; 8 | 9 | /** 文件校验类型 传入 true 时使用默认值,不传或 false 禁用 */ 10 | export interface FileValidatorsProps { 11 | /** 限制文件大小,单位为 M,默认 10 M */ 12 | maxSize?: number; 13 | /** 限制文件后缀,为可选后缀列表(支持string),默认为图片 */ 14 | accept?: string[] | string; 15 | /** 限制文件个数,默认为 10 个 */ 16 | maxCount?: number; 17 | } 18 | 19 | // 文件校验类型函数映射 20 | const VALIDATOR_MAP = Object.freeze({ 21 | maxSize: validatorFileListSizeRule, 22 | accept: validatorFileListSuffixRule, 23 | maxCount: validatorFileListCount, 24 | }); 25 | 26 | // 最大文件个数限制 27 | export const ATTACHMENT_MAX_FILE_COUNT = 10; 28 | 29 | // 基础字节 30 | export const BASE_BYTE = 1024; 31 | 32 | // 最大文件大小限制 20M 33 | export const ATTACHMENT_MAX_FILE_SIZE = 20 * BASE_BYTE; 34 | 35 | // 最大图片大小限制 300 KB 36 | export const MAX_IMAGE_SIZE = 300; 37 | 38 | // 最大缩略图大小限制 50 KB 39 | export const MAX_THUMBNAIL_SIZE = 50; 40 | 41 | /** 文件类型映射 */ 42 | export const FILE_TYPE_MAP = { 43 | 图片: ['.jpg', '.jpeg', '.gif', '.png', '.bmp', '.webp'], 44 | 压缩包: ['.rar', '.zip'], 45 | 文档: ['.doc', '.docx', '.pdf'], 46 | 表格: ['.xls'], 47 | 视频: ['.avi', '.wmv', '.mpg', '.mpeg', '.mov', '.mp4', '.rm', '.ram'], 48 | }; 49 | 50 | /** 51 | * 根据fileId获取下载地址 52 | * @param fileId 53 | */ 54 | export const getDownloadUrlWithId = (fileId: number | string) => { 55 | const regs = /^[0-9]+$/; 56 | // 判断是否加密,若为已加密 57 | if (!regs.test(`${fileId}`)) { 58 | return `${UPLOAD_URL}/download/direct?fileId=${fileId}&access_token=${localStorage.getItem( 59 | 'accessToken', 60 | )}`; 61 | } 62 | // 若没有加密 63 | return `${UPLOAD_URL}/file/download?fileId=${fileId}&access_token=${localStorage.getItem( 64 | 'accessToken', 65 | )}`; 66 | }; 67 | 68 | export const onPreview = (file: UploadFile) => { 69 | window.open(file.url || getDownloadUrlWithId(file.response.data.fileId), '_blank'); 70 | }; 71 | 72 | // 判断是否符合文件格式,不传默认校验type 73 | export const isPermitFile = ( 74 | file: UploadFile, 75 | allowFileList: string[] | string = FILE_TYPE_MAP['图片'], 76 | ) => { 77 | // 文件后缀 78 | const fileSuffix = `.${string.getLastSubstring(file.name, '.')}`; 79 | const formatedAllowFileList = Array.isArray(allowFileList) 80 | ? allowFileList 81 | : allowFileList.split(','); 82 | return formatedAllowFileList.includes(fileSuffix); 83 | }; 84 | 85 | export const getPublicUploadProps: () => UploadProps = () => ({ 86 | accept: '.doc,.docx,.pdf,.rar,.zip,.jpg,.png,.bmp,.gif', 87 | withCredentials: false, 88 | action: `${UPLOAD_URL}/file/uploadToPub`, 89 | data: { 90 | access_token: localStorage.getItem('accessToken'), 91 | }, 92 | showUploadList: { 93 | showDownloadIcon: false, 94 | showPreviewIcon: false, 95 | }, 96 | onPreview, 97 | }); 98 | 99 | export const handleUpload = ({ fileList }: { fileList: UploadFile[] }) => { 100 | return fileList; 101 | }; 102 | 103 | /** 104 | * 对上传类的表单项进行校验 105 | * @param _ 106 | * @param value 107 | * @param callback 108 | */ 109 | export function uploadValidator( 110 | _: unknown, 111 | value: UploadFile[], 112 | callback: (error?: string) => void, 113 | ) { 114 | try { 115 | value.some((file) => { 116 | if (file.response && !file.response.success) { 117 | throw new Error(`上传文件失败: ${file.response.message}`); 118 | } 119 | return file; 120 | }); 121 | callback(); 122 | } catch (error) { 123 | callback(error); 124 | } 125 | } 126 | 127 | /** 得到文件大小的文本 */ 128 | export const getFileSizeName = (size: number | boolean): string => { 129 | if (typeof size === 'number') { 130 | return size > BASE_BYTE ? `${size / 1024} M` : `${size} KB`; 131 | } 132 | if (size) { 133 | return getFileSizeName(ATTACHMENT_MAX_FILE_SIZE); 134 | } 135 | return ''; 136 | }; 137 | 138 | /** 139 | * 对附件大小进行校验 140 | * @param params maxSize (KB) 141 | * @param {object} 返回验证rule对象 142 | */ 143 | export function validatorFileListSizeRule(params = { maxSize: ATTACHMENT_MAX_FILE_SIZE }) { 144 | const { maxSize } = params; 145 | return { 146 | validator: (_rule: unknown, values: UploadFile[], callback: (error?: string) => void) => { 147 | if (values && values.length) { 148 | const validationFailedList = values.filter((file) => file.size / BASE_BYTE > maxSize); 149 | if (validationFailedList.length) { 150 | const names = validationFailedList.map((file) => file.name).join(','); 151 | callback( 152 | `${names},文件大小超过${ 153 | maxSize > BASE_BYTE ? `${maxSize / 1024} M` : `${maxSize} KB` 154 | }`, 155 | ); 156 | } else { 157 | callback(); 158 | } 159 | } else { 160 | callback(); 161 | } 162 | }, 163 | }; 164 | } 165 | 166 | /** 167 | * 对附件后缀进行校验 168 | * @param params maxSize 169 | * @param {object} 返回验证rule对象 170 | */ 171 | export function validatorFileListSuffixRule( 172 | params = { 173 | accept: FILE_TYPE_MAP['图片'], 174 | }, 175 | ) { 176 | const { accept } = params; 177 | return { 178 | validator: (_: unknown, values: UploadFile[], callback: (error?: string) => void) => { 179 | if (values && values.length) { 180 | const typeValidationFailedList = values.filter( 181 | (file: UploadFile) => !isPermitFile(file, accept), 182 | ); 183 | if (typeValidationFailedList.length) { 184 | const names = typeValidationFailedList.map((file: UploadFile) => file.name).join(','); 185 | callback(`${names},文件类型不符合要求`); 186 | } else { 187 | callback(); 188 | } 189 | } else { 190 | callback(); 191 | } 192 | }, 193 | }; 194 | } 195 | 196 | /** 197 | * 对附件个数进行校验 198 | * @param params maxCount 199 | * @param {object} 返回验证rule对象 200 | */ 201 | export function validatorFileListCount( 202 | params = { 203 | maxCount: ATTACHMENT_MAX_FILE_COUNT, 204 | }, 205 | ) { 206 | const { maxCount } = params; 207 | return { 208 | validator: (_: unknown, values: UploadFile[], callback: (error?: string) => void) => { 209 | if (values && values.length) { 210 | if (values.length > maxCount) { 211 | callback(`文件个数超过 ${maxCount} 个!`); 212 | } else { 213 | callback(); 214 | } 215 | } else { 216 | callback(); 217 | } 218 | }, 219 | }; 220 | } 221 | 222 | /** 223 | * 获得文件校验配置数组 224 | * @param params 225 | */ 226 | export const getFileValidators = (params: FileValidatorsProps) => { 227 | const validatorList = Object.keys(params).filter((item) => !!params[item]); 228 | return validatorList.map((item) => 229 | params[item] === true ? VALIDATOR_MAP[item]() : VALIDATOR_MAP[item]({ [item]: params[item] }), 230 | ); 231 | }; 232 | 233 | /** 234 | * 传入校验数组获得 beforeUpload 函数(如果有单独的 Upload 需要类型、大小校验可以用这个函数) 235 | * maxCount 最好不要在 beforeUpload 中处理,因为拿到的 fileList 只是改变的文件列表而不包含原来的 236 | */ 237 | export const getBeforeUpload = (validatorObj: FileValidatorsProps, showMessage?: boolean) => ( 238 | file: RcFile, 239 | ) => { 240 | const errorMessageList: string[] = []; 241 | /** 若不符合要求阻断上传 */ 242 | getFileValidators(validatorObj).forEach((item) => { 243 | item.validator('', [file], (error?: string) => { 244 | if (error) { 245 | errorMessageList.push(error); 246 | if (showMessage) { 247 | message.error(error); 248 | } 249 | } 250 | }); 251 | }); 252 | return errorMessageList.length === 0; 253 | }; 254 | 255 | /** 256 | * 将后端返回的附件转换成上传文件需要的格式 257 | * @param files 258 | */ 259 | export function fileTransform(files?: FileDTO[]): UploadFile[] { 260 | return files && files.length > 0 261 | ? files.map(({ fileId, fileName, fileUrl, ...rest }) => ({ 262 | ...rest, 263 | uid: fileId, 264 | status: 'done', 265 | size: 0, 266 | type: '', 267 | name: fileName || getDownloadUrlWithId(fileId), 268 | url: fileUrl, 269 | response: { 270 | success: true, 271 | data: { 272 | fileId, 273 | fileName: fileName || getDownloadUrlWithId(fileId), 274 | url: fileUrl, 275 | }, 276 | }, 277 | })) 278 | : []; 279 | } 280 | 281 | /** 282 | * 将上传组件里面的文件转成后端需要的FileDTO 283 | * @param files 284 | */ 285 | export function transformFile(files?: UploadFile[]): FileDTO[] { 286 | return files && files.length > 0 287 | ? files.map((file) => { 288 | const { fileId, fileName, url } = file.response.data; 289 | return { 290 | fileId, 291 | fileName, 292 | fileUrl: url, 293 | type: '', 294 | }; 295 | }) 296 | : []; 297 | } 298 | 299 | /** 300 | * 获得图片文件预览地址 301 | * @param fileId 302 | * 图片地址 303 | */ 304 | export const getImgPreviewUrl = (fileId: string) => `${UPLOAD_URL}/file/preview?fileId=${fileId}`; 305 | -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 初始化分页数据 3 | */ 4 | export const initialPagination = { 5 | page: 1, 6 | pageSize: 10, 7 | }; 8 | 9 | /** 10 | * 文件服务地址 11 | */ 12 | export const UPLOAD_URL = 'http://object-service.dev.thundersdata.com'; 13 | 14 | /** 15 | * 认证中心 16 | */ 17 | export const AUTH_API_URL = 'http://api.dev.thundersdata.com'; 18 | 19 | /** 20 | * 登录配置 21 | */ 22 | export const LOGIN_CONFIG = Object.freeze({ 23 | /** 应用ID */ 24 | clientId: '', 25 | /** 是否是单点登录 */ 26 | isSSO: false, 27 | /** 密钥 */ 28 | secret: '', 29 | }); 30 | -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | /** 后端返回的code */ 2 | export enum LoginFailure { 3 | 登录过期 = 50400, 4 | 不允许登录 = 50402, 5 | } 6 | 7 | /** 获取 验证码方式枚举 */ 8 | export enum SmsTypeEnum { 9 | 注册 = 0, 10 | 修改密码 = 1, 11 | 登录 = 2, 12 | } 13 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | html { 2 | /* 火狐下隐藏滚动条 */ 3 | scrollbar-width: none; 4 | height: 100%; 5 | } 6 | 7 | body::-webkit-scrollbar { 8 | display: none; 9 | } 10 | 11 | body { 12 | -ms-overflow-style: none; 13 | width: calc(100vw + 18px); 14 | height: 100%; 15 | overflow-x: hidden; 16 | overflow-y: auto; 17 | 18 | @media screen and (max-width: 750px) and (orientation: portrait) { 19 | width: 100%; 20 | } 21 | } 22 | 23 | #root { 24 | height: 100vh; 25 | 26 | input:-webkit-autofill, 27 | textarea:-webkit-autofill, 28 | select:-webkit-autofill { 29 | box-shadow: inset 0 0 0 1000px #fff; 30 | } 31 | 32 | .ant-menu-inline-collapsed .ant-pro-sider-menu { 33 | .ant-menu-submenu .ant-menu-submenu-title .iconfont + span, 34 | .ant-menu-item span:last-child { 35 | display: inline-block; 36 | max-width: 0; 37 | opacity: 0; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/global.ts: -------------------------------------------------------------------------------- 1 | import '@/api'; 2 | -------------------------------------------------------------------------------- /src/hooks/useRefCallback.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef } from 'react'; 2 | 3 | /** 4 | * 将函数挂到 ref 上,保证永远都是拿到最新状态的函数,往外暴露时使用 useCallback 包裹,保证函数引用不更新 5 | * @param callback 6 | * @returns 7 | */ 8 | export default function useRefCallback any>(callback: T) { 9 | const callbackRef = useRef(callback); 10 | callbackRef.current = callback; 11 | 12 | return useCallback((...args: any[]) => callbackRef.current(...args), []) as T; 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useToast.tsx: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { useCallback } from 'react'; 3 | 4 | export default function useToast() { 5 | const toastSuccess = useCallback((msg: string) => message.success(msg), []); 6 | const toastFailure = useCallback((msg: string) => message.error(msg), []); 7 | 8 | return { 9 | toastSuccess, 10 | toastFailure, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/components/CustomHeaderRight/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar, Dropdown, Menu, Spin } from 'antd'; 3 | import { UserOutlined } from '@ant-design/icons'; 4 | import useSWR from 'swr'; 5 | import useHeaderService from './useHeaderService'; 6 | 7 | function CustomHeaderRight(/** props: HeaderViewProps */) { 8 | const { data, isValidating } = useSWR('/user/fetch'); 9 | const { dropdownClick } = useHeaderService(); 10 | 11 | return ( 12 |
13 |
14 | 欢迎您, 15 | {isValidating && } 16 | {data && {data.data?.name}} 17 |
18 | dropdownClick(key)}> 21 | 个人信息 22 | 退出登录 23 | 24 | } 25 | > 26 | {data ? ( 27 | 28 | ) : ( 29 | } /> 30 | )} 31 | 32 |
33 | ); 34 | } 35 | export default React.memo(CustomHeaderRight); 36 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/components/CustomHeaderRight/useHeaderService.ts: -------------------------------------------------------------------------------- 1 | import type { Key } from 'react'; 2 | import { useCallback, useContext } from 'react'; 3 | import { history } from 'umi'; 4 | import { AuthContext } from '@/pages/auth/useAuthService'; 5 | 6 | export default function useHeaderService() { 7 | const authService = useContext(AuthContext); 8 | 9 | const dropdownClick = useCallback( 10 | (key: Key) => { 11 | switch (key) { 12 | case 'personal': 13 | default: 14 | console.log('你点击的是个人信息'); 15 | break; 16 | case 'logout': 17 | authService.logout(); 18 | history.replace('/auth/login'); 19 | break; 20 | } 21 | }, 22 | [authService], 23 | ); 24 | 25 | return { 26 | dropdownClick, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import logo from '@/assets/logo.png'; 4 | 5 | export default function Logo() { 6 | return 雷数科技; 7 | } 8 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import type { Settings } from '@ant-design/pro-layout'; 2 | 3 | const defaultSettings: Partial & { disableMobile?: boolean } = { 4 | navTheme: 'dark', 5 | primaryColor: '#1890ff', 6 | layout: 'side', 7 | contentWidth: 'Fluid', 8 | fixedHeader: true, 9 | fixSiderbar: true, 10 | colorWeak: false, 11 | menu: { 12 | locale: false, 13 | defaultOpenAll: false, 14 | }, 15 | disableMobile: false, 16 | title: '中台模板', 17 | }; 18 | 19 | export default defaultSettings; 20 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/index.module.less: -------------------------------------------------------------------------------- 1 | 2 | .basic-layout { 3 | width: 100%; 4 | 5 | :global { 6 | .ant-pro-basicLayout-content { 7 | width: calc(100% - 50px); 8 | min-height: calc(100vh - 64px - 32px); 9 | margin: 16px; 10 | } 11 | .ant-menu .ant-menu-item { 12 | padding: 0 16px !important; 13 | } 14 | .ant-menu-item .iconfont { 15 | min-width: 14px; 16 | margin-right: 10px; 17 | font-size: 14px; 18 | transition: font-size 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), margin 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s 19 | } 20 | .ant-menu-item .iconfont + span { 21 | opacity: 1; 22 | transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s; 23 | } 24 | 25 | .ant-menu-dark .ant-menu-item .iconfont { 26 | color: #fff; 27 | } 28 | .ant-menu-inline-collapsed .ant-menu-item .iconfont { 29 | margin: 0; 30 | font-size: 16px; 31 | line-height: 40px; 32 | display: inline-block; 33 | } 34 | 35 | .ant-menu-dark .ant-menu-item span.ant-pro-menu-item-title { 36 | transition: none; 37 | } 38 | .ant-menu-inline-collapsed .ant-menu-item .iconfont + span.ant-pro-menu-item-title { 39 | display: inline-block; 40 | max-width: 0; 41 | opacity: 0; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ProLayout, { MenuDataItem } from '@ant-design/pro-layout'; 3 | import type { IRouteComponentProps } from 'umi'; 4 | import { Link, useModel } from 'umi'; 5 | import Iconfont from '@/components/Iconfont'; 6 | import CustomHeaderRight from './components/CustomHeaderRight'; 7 | import Logo from './components/Logo'; 8 | import defaultSettings from './defaultSettings'; 9 | import styles from './index.module.less'; 10 | 11 | const iconMap = { 12 | iconyunyingguanli: , 13 | icongongsi: , 14 | }; 15 | export default function BasicLayout(props: IRouteComponentProps) { 16 | const [collapsed, handleMenuCollapse] = useState(false); 17 | const { initialState } = useModel('@@initialState'); 18 | 19 | const { menus = [] } = initialState!; 20 | 21 | const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] => 22 | menus.map(({ icon, children, ...item }) => ({ 23 | ...item, 24 | icon: icon && iconMap[icon as string], 25 | children: children && loopMenuItem(children), 26 | })); 27 | 28 | return ( 29 | } 32 | collapsed={collapsed} 33 | onCollapse={handleMenuCollapse} 34 | menuDataRender={() => loopMenuItem(menus)} 35 | menuItemRender={(menuItemProps, defaultDom) => { 36 | return {defaultDom}; 37 | }} 38 | rightContentRender={(/** props: HeaderViewProps */) => } 39 | onMenuHeaderClick={() => props.history.push('/')} 40 | {...defaultSettings} 41 | > 42 | {props.children} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/layouts/SignInLayout/assets/login_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/layouts/SignInLayout/assets/login_bg.png -------------------------------------------------------------------------------- /src/layouts/SignInLayout/assets/login_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/layouts/SignInLayout/assets/login_left.png -------------------------------------------------------------------------------- /src/layouts/SignInLayout/assets/sign_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thundersdata-frontend/spa-template/e29c6721205c7a1abb0d1027f2bf9ae3330a7a87/src/layouts/SignInLayout/assets/sign_logo.png -------------------------------------------------------------------------------- /src/layouts/SignInLayout/index.module.less: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: calc(100vh - 40px); 7 | background-color: white; 8 | } 9 | .footer { 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | height: 40px; 14 | padding: 0; 15 | background: white; 16 | } 17 | .bg { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 50%; 23 | background-image: url('./assets/login_bg.png'); 24 | 25 | @media screen and (max-width: 750px) and (orientation: portrait) { 26 | height: calc(100vh - 40px); 27 | } 28 | } 29 | .wrap { 30 | z-index: 9; 31 | width: 400px; 32 | height: 400px; 33 | padding: 50px; 34 | background-color: white; 35 | border-radius: 8px; 36 | box-shadow: 2px 2px 2px 1px #eee; 37 | 38 | :global { 39 | .ant-form-item-control-input { 40 | background-color: white; 41 | border-bottom: 1px solid #d9d9d9; 42 | } 43 | .ant-btn-primary { 44 | margin-top: 30px; 45 | } 46 | } 47 | 48 | @media screen and (max-width: 750px) and (orientation: portrait) { 49 | width: 100%; 50 | padding: 20px; 51 | background: transparent; 52 | box-shadow: none; 53 | 54 | :global { 55 | .ant-btn-primary { 56 | margin-top: 0; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/layouts/SignInLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout } from 'antd'; 3 | import { CopyrightOutlined } from '@ant-design/icons'; 4 | import type { IRouteComponentProps } from 'umi'; 5 | import styles from './index.module.less'; 6 | 7 | import logo from '@/assets/logo.png'; 8 | 9 | const { Content, Footer } = Layout; 10 | 11 | export default function SignInLayout(props: IRouteComponentProps) { 12 | return ( 13 | 14 | 15 |
16 |
17 |

23 | logo 31 | 雷数科技 32 |

33 | {props.children} 34 |
35 | 36 |
37 | Copyright  38 | 39 |  杭州雷数前端团队出品 40 |
41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { IRouteComponentProps } from 'umi'; 3 | import { ConfigProvider, Empty, message } from 'antd'; 4 | import zhCN from 'antd/es/locale/zh_CN'; 5 | import { SWRConfig } from 'swr'; 6 | 7 | import useAuthService, { AuthContext } from '@/pages/auth/useAuthService'; 8 | import { validateMessages } from './validateMessages'; 9 | 10 | import emptyImg from '@/assets/pic_empty.png'; 11 | 12 | export default (props: IRouteComponentProps) => { 13 | const authService = useAuthService(); 14 | 15 | return ( 16 | } 19 | form={{ validateMessages }} 20 | > 21 | res.json()) 27 | /** 单个hooks返回的初始数据 */ 28 | // initialData 29 | /** 在挂载组件时启用或禁用自动重新验证 (默认情况下,当没有设置 initialData 时,在挂载时进行重新验证,使用该 flag 强制行为) */ 30 | // revalidateOnMount 31 | /** 窗口聚焦时自动重新验证 */ 32 | // revalidateOnFocus = true 33 | /** 浏览器恢复网络连接时自动重新验证 */ 34 | // revalidateOnReconnect = true 35 | /** 轮询间隔 (默认 disabled) */ 36 | // refreshInterval = 0 37 | /** 窗口不可见时进行轮询 (如果启用了 refreshInterval) */ 38 | // refreshWhenHidden = false 39 | /** 浏览器离线时轮询 */ 40 | // refreshWhenOffline = false 41 | /** fetcher 有错误时重试 */ 42 | // shouldRetryOnError = true 43 | /** 在该时间段内使用相同的 key 请求时只发出一个 */ 44 | // dedupingInterval = 2000 45 | /** 在一段时间内只重新验证一次 */ 46 | // focusThrottleInterval = 5000 47 | /** 弱网环境下 errorRetryInterval = 10000, loadingTimeout=5000 */ 48 | // loadingTimeout = 3000 49 | // errorRetryInterval = 5000 50 | /** 错误重试的最大次数 */ 51 | // errorRetryCount 52 | /** 请求加载时间过长时的回调函数 */ 53 | // onLoadingSlow(key, config) 54 | /** 请求成功完成时的回调函数 */ 55 | // onSuccess(data, key, config) 56 | /** 请求返回错误时的回调函数 */ 57 | onError: err => { 58 | message.error(err.message); 59 | }, 60 | /** 错误重试的处理函数 */ 61 | // onErrorRetry(err, key, config, revalidate, revalidateOps) 62 | /** 比较函数,用来检测返回的数据何时已更改,以防止伪造的重新渲染。默认情况下使用 dequal */ 63 | // compare(a, b) 64 | /** 用于检测是否暂停重新验证的函数,当返回 true 时将忽略所请求的数据和错误。默认返回 false */ 65 | // isPaused() 66 | }} 67 | > 68 | {props.children} 69 | 70 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/layouts/validateMessages.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | const typeTemplate = "'${label}' 不是一个合法的 ${type}"; 3 | 4 | export const validateMessages = { 5 | default: "'${label}' 校验失败", 6 | required: "'${label}' 是必填字段", 7 | enum: "'${label}' 必须是 [${enum}] 中的一个", 8 | whitespace: "'${label}' 不能为空", 9 | date: { 10 | format: "'${label}' 不能被日期格式化", 11 | parse: "'${label}' 不能解析成日期", 12 | invalid: "'${label}' 不是一个合法的日期", 13 | }, 14 | types: { 15 | string: typeTemplate, 16 | method: typeTemplate, 17 | array: typeTemplate, 18 | object: typeTemplate, 19 | number: typeTemplate, 20 | date: typeTemplate, 21 | boolean: typeTemplate, 22 | integer: typeTemplate, 23 | float: typeTemplate, 24 | regexp: typeTemplate, 25 | email: typeTemplate, 26 | url: typeTemplate, 27 | hex: typeTemplate, 28 | }, 29 | string: { 30 | len: "'${label}' 必须是 ${len} 个字符", 31 | min: "'${label}' 最少是 ${min} 个字符", 32 | max: "'${label}' 不能超过 ${max} 个字符", 33 | range: "'${label}' 长度在 ${min} 和 ${max} 之间", 34 | }, 35 | number: { 36 | len: "'${label}' 必须等于 ${len}", 37 | min: "'${label}' 不能小于 ${min}", 38 | max: "'${label}' 不能大于 ${max}", 39 | range: "'${label}' 必须在 ${min} 和 ${max} 之间", 40 | }, 41 | array: { 42 | len: "'${label}' 数组长度必须等于 ${len}", 43 | min: "'${label}' 数组长度不能小于 ${min}", 44 | max: "'${label}' 数组长度不能大于 ${max}", 45 | range: "'${label}' 数组长度必须在 ${min} 和 ${max} 之间", 46 | }, 47 | pattern: { 48 | mismatch: "'${label}' 不能匹配正则表达式 ${pattern}", 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { history } from 'umi'; 4 | 5 | import img404 from '@/assets/pic_404.png'; 6 | 7 | export default () => { 8 | return ( 9 |
16 |
17 | 18 |
对不起,您访问的页面不存在
19 | 22 |
23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/pages/auth/login/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useLoginService from './useLoginService'; 3 | import ProForm, { ProFormText } from '@ant-design/pro-form'; 4 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 5 | 6 | export default function Login() { 7 | const loginService = useLoginService(); 8 | 9 | return ( 10 | dom.pop(), 17 | submitButtonProps: { 18 | loading: loginService.loading, 19 | size: 'large', 20 | style: { 21 | width: '100%', 22 | }, 23 | }, 24 | }} 25 | > 26 | , 31 | }} 32 | name="username" 33 | placeholder="请输入用户名" 34 | rules={[ 35 | { 36 | required: true, 37 | message: '请输入用户名!', 38 | }, 39 | ]} 40 | /> 41 | , 46 | }} 47 | name="password" 48 | placeholder="请输入密码" 49 | rules={[{ required: true, message: '请输入密码' }]} 50 | /> 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/pages/auth/login/useLoginService.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext, useState } from 'react'; 2 | import request from 'umi-request'; 3 | import type { Store } from 'rc-field-form/es/interface'; 4 | 5 | import useToast from '@/hooks/useToast'; 6 | import { AuthContext } from '../useAuthService'; 7 | import { useModel, history } from 'umi'; 8 | 9 | export default function useLoginService() { 10 | const [loading, setLoading] = useState(false); 11 | const { toastFailure, toastSuccess } = useToast(); 12 | const authService = useContext(AuthContext); 13 | const { refresh } = useModel('@@initialState'); 14 | 15 | /** 16 | * 登录操作 17 | */ 18 | const login = useCallback( 19 | async (values: Store) => { 20 | setLoading(true); 21 | try { 22 | const { success, data, code, message } = await request.post('/login/submit', { 23 | data: values, 24 | }); 25 | if (success && code === 20000) { 26 | toastSuccess(message ?? '登录成功'); 27 | authService.saveToken(data.accessToken); 28 | await refresh(); 29 | history.replace('/homepage'); 30 | } else { 31 | toastFailure(message); 32 | } 33 | } catch (error) { 34 | toastFailure(error.message); 35 | } finally { 36 | setLoading(false); 37 | } 38 | }, 39 | [authService, refresh, toastFailure, toastSuccess], 40 | ); 41 | 42 | return { 43 | login, 44 | loading, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/auth/useAuthService.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import getContextService from '@/utils/getContextService'; 3 | 4 | /** 5 | * 权限验证服务 6 | * 7 | */ 8 | export default function useAuthService() { 9 | const getToken = useCallback(() => { 10 | return window.localStorage.getItem('accessToken'); 11 | }, []); 12 | 13 | const saveToken = useCallback((token: string) => { 14 | window.localStorage.setItem('accessToken', token); 15 | }, []); 16 | 17 | /** 退出登录 */ 18 | const logout = useCallback(() => { 19 | window.localStorage.setItem('accessToken', ''); 20 | }, []); 21 | 22 | return { 23 | getToken, 24 | saveToken, 25 | logout, 26 | }; 27 | } 28 | 29 | // 这个服务将被注册至全局 30 | export const AuthContext = getContextService(useAuthService); 31 | -------------------------------------------------------------------------------- /src/pages/auth/wrappers/auth.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Redirect } from 'umi'; 3 | import type { IRouteComponentProps } from 'umi'; 4 | import { AuthContext } from '../useAuthService'; 5 | 6 | export default (props: IRouteComponentProps) => { 7 | const authService = useContext(AuthContext); 8 | if (authService.getToken()) { 9 | return props.children; 10 | } 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /src/pages/auth/wrappers/guard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { IRouteComponentProps } from 'umi'; 3 | import { Route } from 'umi'; 4 | import arrayUtils from '@/utils/array'; 5 | 6 | const Guard: React.FC = (props) => { 7 | const { 8 | location: { pathname }, 9 | route: { routes = [] }, 10 | } = props; 11 | 12 | const flattenRoutes = arrayUtils.deepFlatten(routes, 'routes'); 13 | if (flattenRoutes.find((item) => item.path === pathname)) { 14 | return <>{props.children}; 15 | } 16 | const notFoundRoute = flattenRoutes.find((item) => item.path === '/*')!; 17 | return ; 18 | }; 19 | 20 | export default Guard; 21 | -------------------------------------------------------------------------------- /src/pages/counter/countAtom.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderHook, act } from '@testing-library/react-hooks'; 3 | import { useAtom, Provider } from 'jotai'; 4 | import { useAtomValue, useUpdateAtom } from 'jotai/utils'; 5 | 6 | import { countAtom, fetchUserInfoAtom, userInfoAtom } from './countAtom'; 7 | 8 | describe('countAtom', () => { 9 | test('should increment counter', () => { 10 | // 借助Provider,实现同一个atom在不同的Provider下互不影响的效果 11 | const wrapper = ({ children }: { children?: React.ReactNode }) => {children}; 12 | const { result } = renderHook(() => useAtom(countAtom), { wrapper }); 13 | 14 | act(() => { 15 | result.current[1]('INCREASE'); 16 | }); 17 | 18 | expect(result.current[0]).toBe(1); 19 | }); 20 | 21 | test('should increment counter', () => { 22 | const wrapper = ({ children }: { children?: React.ReactNode }) => {children}; 23 | const { result } = renderHook(() => useAtom(countAtom), { wrapper }); 24 | 25 | act(() => { 26 | result.current[1]('DECREASE'); 27 | }); 28 | 29 | expect(result.current[0]).toBe(-1); 30 | }); 31 | 32 | test('should get user info after 2000ms delay', async () => { 33 | const wrapper = ({ children }: { children?: React.ReactNode }) => {children}; 34 | const { result, waitForNextUpdate } = renderHook( 35 | () => ({ 36 | fetch: useUpdateAtom(fetchUserInfoAtom), 37 | value: useAtomValue(userInfoAtom), 38 | }), 39 | { wrapper }, 40 | ); 41 | 42 | result.current.fetch(); 43 | 44 | await waitForNextUpdate({ timeout: 3000 }); 45 | 46 | expect(result.current.value).not.toBeNull(); 47 | // node版本必须要跟typescript的target匹配,https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping 48 | expect(result.current.value?.name).toBe('damon'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/pages/counter/countAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai'; 2 | import { atomWithReducer } from 'jotai/utils'; 3 | 4 | const reducer = (state: number, action?: 'INCREASE' | 'DECREASE') => { 5 | switch (action) { 6 | case 'INCREASE': 7 | return state + 1; 8 | case 'DECREASE': 9 | return state - 1; 10 | case undefined: 11 | return state; 12 | } 13 | }; 14 | 15 | export const countAtom = atomWithReducer(0, reducer); 16 | 17 | type UserInfo = { name: string; age: number }; 18 | 19 | export const userInfoAtom = atom(null); 20 | 21 | export const fetchUserInfoAtom = atom(null, async (_, set) => { 22 | const userInfo = await new Promise(resolve => { 23 | setTimeout(() => { 24 | resolve({ name: 'damon', age: 31 }); 25 | }, 2000); 26 | }); 27 | set(userInfoAtom, userInfo); 28 | }); 29 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useAsyncCounter.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import useAsyncCounter from './useAsyncCounter'; 3 | 4 | describe('useAsyncCounter', () => { 5 | test('should increment counter after delay', async () => { 6 | const { result, waitForNextUpdate } = renderHook(() => useAsyncCounter()); 7 | 8 | result.current.incrementAsync(); // async不需要放在act里 9 | 10 | await waitForNextUpdate(); 11 | 12 | expect(result.current.count).toBe(1); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useAsyncCounter.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export default function useAsyncCounter(initialValue = 0) { 4 | const [count, setCount] = useState(initialValue); 5 | 6 | const increment = useCallback(() => setCount((x) => x + 1), []); 7 | 8 | const incrementAsync = useCallback(() => setTimeout(increment, 200), [increment]); 9 | 10 | return { 11 | count, 12 | incrementAsync, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useContextCounter.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderHook, act } from '@testing-library/react-hooks'; 3 | import { CounterProvider, useContextCounter } from './useContextCounter'; 4 | 5 | test('should use custom step when incrementing', () => { 6 | const wrapper = ({ children }: { children?: React.ReactNode }) => ( 7 | {children} 8 | ); 9 | 10 | const { result } = renderHook(() => useContextCounter(), { wrapper }); 11 | 12 | act(() => { 13 | result.current.increment(); 14 | }); 15 | 16 | expect(result.current.count).toBe(2); 17 | }); 18 | 19 | test('should use custom step when incrementing', () => { 20 | const wrapper = ({ step, children }: { children?: React.ReactNode; step: number }) => ( 21 | {children} 22 | ); 23 | 24 | const { result, rerender } = renderHook(() => useContextCounter(), { 25 | wrapper, 26 | initialProps: { step: 2 }, 27 | }); 28 | 29 | act(() => { 30 | result.current.increment(); 31 | }); 32 | 33 | expect(result.current.count).toBe(2); 34 | 35 | rerender({ step: 8 }); 36 | 37 | act(() => { 38 | result.current.increment(); 39 | }); 40 | 41 | expect(result.current.count).toBe(10); 42 | }); 43 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useContextCounter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useCallback } from 'react'; 2 | 3 | const CounterContext = React.createContext(1); 4 | 5 | export const CounterProvider = ({ 6 | step, 7 | children, 8 | }: { 9 | children?: React.ReactNode; 10 | step: number; 11 | }) => {children}; 12 | 13 | export function useContextCounter(initialValue = 0) { 14 | const [count, setCount] = useState(initialValue); 15 | 16 | const step = useContext(CounterContext); 17 | 18 | const increment = useCallback(() => setCount((x) => x + step), [step]); 19 | 20 | const reset = useCallback(() => setCount(initialValue), [initialValue]); 21 | 22 | return { count, increment, reset }; 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounter.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import useCounter from './useCounter'; 3 | 4 | test('should use counter', () => { 5 | const { result } = renderHook(() => useCounter()); 6 | 7 | expect(result.current.count).toBe(0); 8 | expect(typeof result.current.increment).toBe('function'); 9 | }); 10 | 11 | test('should increment counter', () => { 12 | const { result } = renderHook(() => useCounter()); 13 | 14 | act(() => { 15 | result.current.increment(); 16 | }); 17 | 18 | expect(result.current.count).toBe(1); 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounter.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export default function useCounter() { 4 | const [count, setCount] = useState(0); 5 | const increment = useCallback(() => setCount((x) => x + 1), []); 6 | 7 | return { count, increment }; 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounterError.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import useCounterError from './useCounterError'; 3 | 4 | test('should throw error when over 9000', () => { 5 | const { result } = renderHook(() => useCounterError(9000)); 6 | 7 | act(() => { 8 | result.current.increment(); 9 | }); 10 | 11 | expect(result.error).toEqual(Error("It's over 9000!")); 12 | }); 13 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounterError.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export default function useCounterError(initialValue = 0) { 4 | const [count, setCount] = useState(initialValue); 5 | 6 | const increment = useCallback(() => { 7 | setCount((count) => count + 1); 8 | }, []); 9 | 10 | const reset = useCallback(() => { 11 | setCount(initialValue); 12 | }, [initialValue]); 13 | 14 | if (count > 9000) { 15 | throw Error("It's over 9000!"); 16 | } 17 | 18 | return { 19 | count, 20 | increment, 21 | reset, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounterWithProps.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import useCounterWithProps from './useCounterWithProps'; 3 | 4 | test('should increment counter from custom initial value', () => { 5 | const { result } = renderHook(() => useCounterWithProps(9000)); 6 | 7 | act(() => { 8 | result.current.increment(); 9 | }); 10 | 11 | expect(result.current.count).toBe(9001); 12 | }); 13 | 14 | test('should reset counter to updated initial value', () => { 15 | const { result, rerender } = renderHook(({ initialValue }) => useCounterWithProps(initialValue), { 16 | initialProps: { 17 | initialValue: 0, 18 | }, 19 | }); 20 | 21 | rerender({ initialValue: 10 }); 22 | 23 | act(() => { 24 | result.current.reset(); 25 | }); 26 | 27 | expect(result.current.count).toBe(10); 28 | }); 29 | -------------------------------------------------------------------------------- /src/pages/counter/hooks/useCounterWithProps.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export default function useCounterWithProps(initialValue = 0) { 4 | const [count, setCount] = useState(initialValue); 5 | 6 | const increment = useCallback(() => setCount((x) => x + 1), []); 7 | 8 | const reset = useCallback(() => setCount(initialValue), [initialValue]); 9 | 10 | return { count, increment, reset }; 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/createLocalShare.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | 3 | /** 4 | * 创建局部共享数据 5 | * @param hooks 自定义hooks 6 | * @returns 7 | */ 8 | export function createLocalShare(hooks: (...args: any[]) => any) { 9 | const Context = createContext({}); 10 | 11 | const ModelProvider = ({ children }: { children?: React.ReactNode }) => ( 12 | {children} 13 | ); 14 | 15 | const useModel = () => useContext(Context); 16 | useModel.Provider = ModelProvider; 17 | 18 | return useModel; 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/homepage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { Link } from 'umi'; 4 | import useHomepageService from './useHomepageService'; 5 | import { useAtomValue, useUpdateAtom } from 'jotai/utils'; 6 | import { fetchUserInfoAtom, userInfoAtom } from '../counter/countAtom'; 7 | 8 | export default function Homepage() { 9 | const homepageService = useHomepageService(); 10 | const userInfo = useAtomValue(userInfoAtom); 11 | const fetchUserInfo = useUpdateAtom(fetchUserInfoAtom); 12 | 13 | console.log('111'); 14 | return ( 15 |
16 |
我是首页
17 |
当前step: {homepageService.step}
18 | 19 | 20 | hotel info 21 | 22 |
23 | userInfo: {userInfo?.name} {userInfo?.age} 24 |
25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/homepage/useHomepageService.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export default function useHomepageService() { 4 | const [step, setStep] = useState(0); 5 | 6 | const increment = useCallback(() => setStep((step) => step + 1), []); 7 | 8 | const decrement = useCallback(() => setStep((step) => step - 1), []); 9 | 10 | return { 11 | step, 12 | increment, 13 | decrement, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/hotel/HotelInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'umi'; 3 | 4 | export default function HotelInfo() { 5 | return ( 6 |
7 |
酒店基本信息
8 | homepage 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/hotel/hotelInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'umi'; 3 | 4 | export default function HotelInfo() { 5 | return ( 6 |
7 |
酒店基本信息
8 | homepage 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | import { orderBy } from 'lodash'; 2 | 3 | export type OrderType = 'desc' | 'asc'; 4 | 5 | export interface DeepOrderProps { 6 | data: T[]; 7 | childKey: string; 8 | orderKey: string; 9 | type: OrderType; 10 | } 11 | 12 | export default { 13 | /** 14 | * 递归地将带children的数组展平 15 | */ 16 | deepFlatten(list: T[], key = 'children') { 17 | const result: T[] = []; 18 | const flatten = (arry: T[]) => 19 | arry.forEach((item: T) => { 20 | const newItem = { ...item }; 21 | delete newItem[key]; 22 | result.push(newItem); 23 | if (item[key] && Array.isArray(item[key])) { 24 | flatten(item[key]); 25 | } 26 | }, []); 27 | flatten(list); 28 | return result; 29 | }, 30 | 31 | /** 32 | * 根据某个字段 递归排序 33 | */ 34 | deepOrder(props: DeepOrderProps) { 35 | const { data = [], childKey, orderKey, type = 'asc' } = props; 36 | const loopOrder = (params: DeepOrderProps) => { 37 | const { data = [], childKey, orderKey, type = 'asc' } = params; 38 | return orderBy(data, orderKey, type).map((item: T) => { 39 | const children: T[] = item[childKey] || []; 40 | if (children && children.length > 0) { 41 | item[childKey] = loopOrder({ data: children, childKey, orderKey, type }); 42 | } 43 | return item; 44 | }); 45 | }; 46 | return loopOrder({ data, childKey, orderKey, type }); 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @文件描述: 考虑到APP和PC共用,采用dayjs来实现date相关的工具函数封装 3 | * @公司: thundersdata 4 | * @作者: 陈杰 5 | * @Date: 2019-11-11 13:50:24 6 | * @LastEditors: 陈杰 7 | * @LastEditTime: 2019-11-11 14:58:49 8 | */ 9 | import dayjs from 'dayjs'; 10 | 11 | export default { 12 | /** 13 | * 格式化日期 14 | * @param date 15 | * @param format 16 | */ 17 | formatDate(date: string | Date | number, format = 'YYYY-MM-DD') { 18 | return dayjs(date).format(format); 19 | }, 20 | 21 | /** 22 | * 当前月是否是大月 23 | * @param {*} month 24 | */ 25 | isBigMonth(month: number) { 26 | return [1, 3, 5, 7, 8, 10, 12].includes(month); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/getContextService.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | /** 4 | * 泛型约束,对注入数据的类型推断支持 5 | * 6 | * @export 7 | * @template T 8 | * @param {(...args: any) => T} 9 | * @param {(T | undefined)} [initialData=undefined] 10 | * @returns 11 | */ 12 | export default function getContextService(_: (...args: any) => T, initialData: T | undefined = undefined) { 13 | return createContext(initialData as T); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/getMockService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获得虚拟的服务数据 3 | * 4 | * @export 5 | * @template T 6 | * @param {(T | undefined)} [initialData=undefined] 7 | * @returns 8 | */ 9 | export function getMockService(_: (...args: any) => T, initialData: T | undefined = undefined) { 10 | return initialData as T; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/json.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 反转object的key和value 4 | * @param obj 5 | */ 6 | reverseObj(obj: Record) { 7 | const reversedObj: Record = {}; 8 | Object.keys(obj).forEach(key => { 9 | const value = obj[key]; 10 | reversedObj[value] = key; 11 | }); 12 | return reversedObj; 13 | }, 14 | 15 | /** 16 | * 删除对象中无效属性 17 | * @param params 18 | */ 19 | removeEmpty(obj: any) { 20 | const newObj = {}; 21 | Object.keys(obj).forEach(key => { 22 | if (!['', null, undefined].includes(obj[key])) { 23 | newObj[key] = obj[key]; 24 | } 25 | }); 26 | return newObj; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/regex.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 检验是否是手机号 4 | */ 5 | isPhone(value: string) { 6 | const reg = /^0?(13[0-9]|15[012356789]|17[013678]|18[0-9]|19[0-9]|14[57])[0-9]{8}$/; 7 | return reg.test(value); 8 | }, 9 | 10 | /** 11 | * 检验是否是座机 12 | */ 13 | isTelephone(value: string) { 14 | const reg = /^([0-9]{3,4}-)?[0-9]{7,8}$/; 15 | return reg.test(value); 16 | }, 17 | 18 | /** 19 | * 检验是否是邮箱 20 | */ 21 | isEmail(value: string) { 22 | const reg = /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/; 23 | return reg.test(value); 24 | }, 25 | 26 | /** 27 | * 检验是否是数字 28 | */ 29 | isNumber(value: string) { 30 | const reg = /(^([0-9]{1,})(.[0-9]+)?)$/; 31 | return reg.test(value); 32 | }, 33 | 34 | /* 校验是否包含数字 */ 35 | containNumbers(param: string) { 36 | const reg = /\d/; 37 | return reg.test(param); 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | type ValueType = any; 2 | 3 | export default { 4 | /** 5 | * 为小于10的数字在前面补零。如9补零后为09 6 | * @param val 7 | */ 8 | fillZero(val: number | string) { 9 | if (typeof val === 'number' && val >= 10) { 10 | return val; 11 | } 12 | return `0${val}`; 13 | }, 14 | 15 | /** 16 | * 用于字符长度超过指定个数自动截取并添加... 17 | */ 18 | textEllipsis(text: string, length: number) { 19 | if (text.length > length && length > 0) { 20 | return `${text.substring(0, length)}...`; 21 | } 22 | return text; 23 | }, 24 | 25 | /** 26 | * 获取指定分隔符点后面的最后字符串 27 | * @param (sourceStr splitStr) 源字符串 裁剪字符节点 28 | * @returns {string} 最后一个裁剪字符后面的字符串 29 | */ 30 | getLastSubstring(sourceStr = '', splitStr = '') { 31 | return sourceStr.substring(sourceStr.lastIndexOf(splitStr) + splitStr.length, sourceStr.length); 32 | }, 33 | 34 | /** 35 | * 值格式化为string 36 | * @param value 37 | */ 38 | valueToString(value: ValueType | ValueType[]) { 39 | if (typeof value === 'string') { 40 | return value; 41 | } 42 | return JSON.stringify(value); 43 | }, 44 | 45 | /** 46 | * @功能描述: 复制文本 47 | * @参数: text 复制对象的内容 48 | * @返回值: 49 | */ 50 | copyText(text: string) { 51 | const input = document.createElement('input'); 52 | document.body.appendChild(input); 53 | input.setAttribute('value', text); 54 | input.select(); 55 | document.execCommand('copy'); 56 | document.body.removeChild(input); 57 | }, 58 | /** 59 | * 将在线图片地址转成base64 60 | * @param url 61 | * @param width 62 | * @param height 63 | */ 64 | imgUrlToBase64(url: string, width: number, height: number) { 65 | let canvas: HTMLCanvasElement | null = document.createElement('canvas'); 66 | const ctx = canvas.getContext('2d'); 67 | 68 | return new Promise((resolve, reject) => { 69 | const img = new Image(); 70 | img.crossOrigin = 'Anonymous'; 71 | img.onload = () => { 72 | canvas!.width = width; 73 | canvas!.height = height; 74 | ctx!.drawImage(img, 0, 0, width, height); 75 | 76 | const dataURL = canvas!.toDataURL('image/'); 77 | canvas = null; 78 | resolve(dataURL); 79 | }; 80 | img.onerror = error => { 81 | reject(error); 82 | }; 83 | img.src = url; 84 | }); 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "lib": ["esnext", "dom"], 12 | "noEmit": true, 13 | "strict": true, 14 | "experimentalDecorators": true, 15 | "allowSyntheticDefaultImports": true, 16 | "isolatedModules": false, 17 | "forceConsistentCasingInFileNames": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noImplicitAny": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "strictNullChecks": true, 24 | "suppressImplicitAnyIndexErrors": true, 25 | "downlevelIteration": true, 26 | "resolveJsonModule": true, 27 | "strictPropertyInitialization": true, 28 | "skipLibCheck": true, 29 | "baseUrl": "./", 30 | "paths": { 31 | "@/*": ["src/*"], 32 | "@@/*": ["src/.umi/*"], 33 | "test-utils": ["jest/test-utils"] 34 | }, 35 | "typeRoots": ["./node_modules/@types/", "./typings.d.ts"], 36 | }, 37 | "include": ["./src/api/api.d.ts", "./src/**/*", "typings.d.ts"], 38 | "exclude": ["./mock/*"] 39 | } 40 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.less'; 2 | declare module '*.png'; 3 | declare module '*.jpg'; 4 | declare module '*.jpeg'; 5 | declare module '*.gif'; 6 | declare module '*.webp'; 7 | 8 | interface PrivilegeResource { 9 | apiUrl: string; 10 | description: string; 11 | icon: string; 12 | id: number; 13 | orderValue: number; 14 | resourceKey: string; 15 | type: number; 16 | privilegeList: string[]; 17 | resourceBusinessValue: string; 18 | children: PrivilegeResource[]; 19 | isVisible: boolean; 20 | } 21 | 22 | interface FileDTO { 23 | fileId: string; 24 | fileName: string; 25 | fileUrl?: string; 26 | } 27 | 28 | type IsX = { 29 | [k in keyof T]: T[k] extends X ? k : never; 30 | }[keyof T]; 31 | 32 | type DispatchContext = Pick>>; 33 | type StateContext = Omit>>; 34 | --------------------------------------------------------------------------------