├── public ├── CNAME ├── favicon.ico ├── home_bg.png ├── icons │ ├── icon-128x128.png │ ├── icon-192x192.png │ └── icon-512x512.png └── pro_icon.svg ├── src ├── e2e │ ├── __mocks__ │ │ └── antd-pro-merge-less.js │ └── baseLayout.e2e.js ├── utils │ ├── common.js │ ├── utils.less │ ├── Authorized.ts │ ├── utils.ts │ ├── authority.ts │ ├── utils.test.ts │ └── request.ts ├── locales │ ├── zh-CN │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ ├── settings.ts │ │ └── pages.ts │ ├── zh-TW │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ └── settings.ts │ ├── ja-JP │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ ├── settings.ts │ │ └── pages.ts │ ├── en-US │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ ├── settings.ts │ │ └── pages.ts │ ├── id-ID │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ ├── settings.ts │ │ └── pages.ts │ ├── pt-BR │ │ ├── component.ts │ │ ├── pwa.ts │ │ ├── globalHeader.ts │ │ ├── settingDrawer.ts │ │ ├── menu.ts │ │ └── settings.ts │ ├── zh-TW.ts │ ├── pt-BR.ts │ ├── ja-JP.ts │ ├── zh-CN.ts │ ├── en-US.ts │ └── id-ID.ts ├── components │ ├── PageLoading │ │ └── index.tsx │ ├── Authorized │ │ ├── index.tsx │ │ ├── AuthorizedRoute.tsx │ │ ├── renderAuthorize.ts │ │ ├── Authorized.tsx │ │ ├── Secured.tsx │ │ ├── CheckPermissions.tsx │ │ └── PromiseRender.tsx │ ├── HeaderDropdown │ │ ├── index.less │ │ └── index.tsx │ ├── Detail │ │ └── index.less │ ├── NoticeIcon │ │ ├── index.less │ │ ├── NoticeList.less │ │ └── NoticeList.tsx │ ├── BusDetail │ │ ├── index.less │ │ └── index.tsx │ ├── HeaderSearch │ │ ├── index.less │ │ └── index.tsx │ ├── GlobalHeader │ │ ├── index.less │ │ ├── RightContent.tsx │ │ └── AvatarDropdown.tsx │ └── Broadcast │ │ └── index.tsx ├── pages │ ├── TableList │ │ ├── business │ │ │ ├── service.ts │ │ │ └── index.tsx │ │ ├── classify │ │ │ ├── service.ts │ │ │ ├── components │ │ │ │ └── detail.tsx │ │ │ └── index.tsx │ │ ├── word │ │ │ ├── service.ts │ │ │ ├── components │ │ │ │ └── detail.tsx │ │ │ └── index.tsx │ │ ├── merchant │ │ │ └── service.ts │ │ ├── ad │ │ │ ├── service.ts │ │ │ └── index.tsx │ │ ├── report │ │ │ └── service.ts │ │ ├── advice │ │ │ └── service.ts │ │ ├── user │ │ │ └── service.ts │ │ └── search │ │ │ ├── data.d.ts │ │ │ └── service.ts │ ├── Welcome.less │ ├── 404.tsx │ ├── User │ │ └── login │ │ │ └── index.less │ ├── Admin.tsx │ └── Welcome.tsx ├── layouts │ ├── BlankLayout.tsx │ ├── UserLayout.less │ ├── SecurityLayout.tsx │ └── UserLayout.tsx ├── services │ ├── user.ts │ └── login.ts ├── manifest.json ├── models │ ├── connect.d.ts │ ├── setting.ts │ ├── user.ts │ ├── global.ts │ └── login.ts ├── typings.d.ts ├── global.less ├── service-worker.js ├── global.tsx └── assets │ └── logo.svg ├── .eslintignore ├── .prettierrc.js ├── .stylelintrc.js ├── mock ├── route.ts ├── tableuser.ts ├── notices.ts └── user.ts ├── jsconfig.json ├── .eslintrc.js ├── jest.config.js ├── .editorconfig ├── .prettierignore ├── config ├── config.dev.ts ├── defaultSettings.ts ├── proxy.ts ├── config.ts └── routes.ts ├── README.md ├── .gitignore ├── tsconfig.json ├── tests ├── PuppeteerEnvironment.js ├── getBrowser.js ├── beforeTest.js └── run-tests.js └── package.json /public/CNAME: -------------------------------------------------------------------------------- 1 | preview.pro.ant.design -------------------------------------------------------------------------------- /src/e2e/__mocks__/antd-pro-merge-less.js: -------------------------------------------------------------------------------- 1 | export default undefined; 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history 5 | public 6 | dist 7 | .umi 8 | mock -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengbid/genuineadmin/master/public/favicon.ico -------------------------------------------------------------------------------- /public/home_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengbid/genuineadmin/master/public/home_bg.png -------------------------------------------------------------------------------- /public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengbid/genuineadmin/master/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengbid/genuineadmin/master/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengbid/genuineadmin/master/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /mock/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/auth_routes': { 3 | '/form/advanced-form': { authority: ['admin', 'user'] }, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/common.js: -------------------------------------------------------------------------------- 1 | export const getToken = () => { 2 | const res = JSON.parse(localStorage.getItem('currentUser')) 3 | 4 | if (res) { 5 | return res.token 6 | } 7 | } -------------------------------------------------------------------------------- /src/locales/zh-CN/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展开', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/zh-TW/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展開', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/ja-JP/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展開', 3 | 'component.tagSelect.collapse': '折りたたむ', 4 | 'component.tagSelect.all': 'すべて', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/en-US/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expand', 3 | 'component.tagSelect.collapse': 'Collapse', 4 | 'component.tagSelect.all': 'All', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/id-ID/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Perluas', 3 | 'component.tagSelect.collapse': 'Lipat', 4 | 'component.tagSelect.all': 'Semua', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/pt-BR/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expandir', 3 | 'component.tagSelect.collapse': 'Diminuir', 4 | 'component.tagSelect.all': 'Todas', 5 | }; 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/PageLoading/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageLoading } from '@ant-design/pro-layout'; 2 | 3 | // loading components from code split 4 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 5 | export default PageLoading; 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | globals: { 4 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, 5 | page: true, 6 | REACT_APP_ENV: true, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/locales/zh-CN/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '当前处于离线状态', 3 | 'app.pwa.serviceworker.updated': '有新内容', 4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /src/locales/zh-TW/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '當前處於離線狀態', 3 | 'app.pwa.serviceworker.updated': '有新內容', 4 | 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | testEnvironment: './tests/PuppeteerEnvironment', 4 | verbose: false, 5 | globals: { 6 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 7 | localStorage: null, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/locales/ja-JP/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'あなたは今オフラインです', 3 | 'app.pwa.serviceworker.updated': '新しいコンテンツが利用可能です', 4 | 'app.pwa.serviceworker.updated.hint': 5 | '現在のページをリロードするには、「更新」ボタンを押してください', 6 | 'app.pwa.serviceworker.updated.ok': 'リフレッシュ', 7 | }; 8 | -------------------------------------------------------------------------------- /src/locales/en-US/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'You are offline now', 3 | 'app.pwa.serviceworker.updated': 'New content is available', 4 | 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page', 5 | 'app.pwa.serviceworker.updated.ok': 'Refresh', 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/TableList/business/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTRequirement', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } -------------------------------------------------------------------------------- /src/pages/TableList/classify/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTIndustry', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } -------------------------------------------------------------------------------- /src/utils/utils.less: -------------------------------------------------------------------------------- 1 | // mixins for clearfix 2 | // ------------------------ 3 | .clearfix() { 4 | zoom: 1; 5 | &::before, 6 | &::after { 7 | display: table; 8 | content: ' '; 9 | } 10 | &::after { 11 | clear: both; 12 | height: 0; 13 | font-size: 0; 14 | visibility: hidden; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/pages/TableList/word/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTAdvertisementDynamic', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } -------------------------------------------------------------------------------- /src/locales/id-ID/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'Koneksi anda terputus', 3 | 'app.pwa.serviceworker.updated': 'Konten baru sudah tersedia', 4 | 'app.pwa.serviceworker.updated.hint': 5 | 'Silahkan klik tombol "Refresh" untuk memuat ulang halaman ini', 6 | 'app.pwa.serviceworker.updated.ok': 'Memuat ulang', 7 | }; 8 | -------------------------------------------------------------------------------- /.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 | /public -------------------------------------------------------------------------------- /src/locales/pt-BR/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'Você está offline agora', 3 | 'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível', 4 | 'app.pwa.serviceworker.updated.hint': 5 | 'Por favor, pressione o botão "Atualizar" para recarregar a página atual', 6 | 'app.pwa.serviceworker.updated.ok': 'Atualizar', 7 | }; 8 | -------------------------------------------------------------------------------- /src/pages/TableList/merchant/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTGshUser', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize, 8 | type: 1 9 | } 10 | }) 11 | } -------------------------------------------------------------------------------- /src/layouts/BlankLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Inspector } from 'react-dev-inspector'; 3 | 4 | const InspectorWrapper = process.env.NODE_ENV === 'development' ? Inspector : React.Fragment; 5 | 6 | const Layout: React.FC = ({ children }) => { 7 | return {children}; 8 | }; 9 | 10 | export default Layout; 11 | -------------------------------------------------------------------------------- /src/components/Authorized/index.tsx: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import Secured from './Secured'; 3 | import check from './CheckPermissions'; 4 | import renderAuthorize from './renderAuthorize'; 5 | 6 | Authorized.Secured = Secured; 7 | Authorized.check = check; 8 | 9 | const RenderAuthorize = renderAuthorize(Authorized); 10 | 11 | export default RenderAuthorize; 12 | -------------------------------------------------------------------------------- /src/components/HeaderDropdown/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container > * { 4 | background-color: @popover-bg; 5 | border-radius: 4px; 6 | box-shadow: @shadow-1-down; 7 | } 8 | 9 | @media screen and (max-width: @screen-xs) { 10 | .container { 11 | width: 100% !important; 12 | } 13 | .container > * { 14 | border-radius: 0 !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/TableList/ad/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTAdvertisement', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } 11 | export async function uploadFile() { 12 | return request('/api/upload', {method: 'POST'}) 13 | } -------------------------------------------------------------------------------- /config/config.dev.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from 'umi'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | // https://github.com/zthxxx/react-dev-inspector 7 | 'react-dev-inspector/plugins/umi/react-inspector', 8 | ], 9 | // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props 10 | inspectorConfig: { 11 | exclude: [], 12 | babelPlugins: [], 13 | babelOptions: {}, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/pages/Welcome.less: -------------------------------------------------------------------------------- 1 | .cardcontanier { 2 | .carditem { 3 | margin-bottom: 15px; 4 | } 5 | .cardbody { 6 | font-weight: bold; 7 | font-size: 20px; 8 | text-align: center; 9 | } 10 | } 11 | .lineChart { 12 | height: 450px; 13 | margin-top: 20px; 14 | padding-right: 10px; 15 | background-color: #fff; 16 | .flow { 17 | padding: 20px; 18 | .title { 19 | margin-right: 40px; 20 | font-size: 16px; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from 'antd'; 2 | import React from 'react'; 3 | import { history } from 'umi'; 4 | 5 | const NoFoundPage: React.FC = () => ( 6 | history.push('/')}> 12 | Back Home 13 | 14 | } 15 | /> 16 | ); 17 | 18 | export default NoFoundPage; 19 | -------------------------------------------------------------------------------- /src/pages/TableList/report/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTReport', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } 11 | 12 | export async function setSugestionStatus(id: number | string) { 13 | return request(`/gsh/setReportStatus/${id}`, { 14 | method: 'put' 15 | }) 16 | } -------------------------------------------------------------------------------- /src/pages/TableList/advice/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTSugestion', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize 8 | } 9 | }) 10 | } 11 | 12 | export async function setSugestionStatus(id: number | string) { 13 | return request(`/gsh/setSugestionStatus/${id}`, { 14 | method: 'put' 15 | }) 16 | } -------------------------------------------------------------------------------- /src/services/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function query(): Promise { 4 | return request('/api/users'); 5 | } 6 | 7 | export async function queryCurrent(): Promise { 8 | return request('/api/currentUser'); 9 | } 10 | 11 | export async function queryNotices(): Promise { 12 | return request('/api/notices'); 13 | } 14 | 15 | export async function getUserInfo(id: string | number) { 16 | return request(`/gsh/gshUserDetail/${id}`); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Detail/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .detailcontainer { 4 | .acter { 5 | margin-bottom: 20px; 6 | .name { 7 | margin-left: 15px; 8 | font-size: 16px; 9 | } 10 | } 11 | .cardcontanier { 12 | .cardlist { 13 | &:nth-child(1) { 14 | padding-right: 10px; 15 | } 16 | .carditem { 17 | padding: 5px 0; 18 | .title { 19 | margin-right: 10px; 20 | font-weight: bold; 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ant Design Pro", 3 | "short_name": "Ant Design Pro", 4 | "display": "standalone", 5 | "start_url": "./?utm_source=homescreen", 6 | "theme_color": "#002140", 7 | "background_color": "#001529", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-192x192.png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "icons/icon-128x128.png", 15 | "sizes": "128x128" 16 | }, 17 | { 18 | "src": "icons/icon-512x512.png", 19 | "sizes": "512x512" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## clone 3 | ```bash 4 | git clone https://github.com/shengbid/genuineadmin.git 5 | ``` 6 | 7 | ### install 8 | ```bash 9 | yarn or npm install 10 | ``` 11 | 12 | ### Start project 13 | 14 | ```bash 15 | npm start or yarn start 16 | ``` 17 | 18 | ### Build project 19 | 20 | ```bash 21 | npm run build 22 | ``` 23 | 24 | ### Check code style 25 | 26 | ```bash 27 | npm run lint 28 | ``` 29 | 30 | You can also use script to auto fix some lint error: 31 | 32 | ```bash 33 | npm run lint:fix 34 | ``` 35 | 36 | ### Test code 37 | 38 | ```bash 39 | npm test 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /src/utils/Authorized.ts: -------------------------------------------------------------------------------- 1 | import RenderAuthorize from '@/components/Authorized'; 2 | import { getAuthority } from './authority'; 3 | /* eslint-disable eslint-comments/disable-enable-pair */ 4 | /* eslint-disable import/no-mutable-exports */ 5 | let Authorized = RenderAuthorize(getAuthority()); 6 | 7 | // Reload the rights component 8 | const reloadAuthorized = (): void => { 9 | Authorized = RenderAuthorize(getAuthority()); 10 | }; 11 | 12 | /** Hard code block need it。 */ 13 | window.reloadAuthorized = reloadAuthorized; 14 | 15 | export { reloadAuthorized }; 16 | export default Authorized; 17 | -------------------------------------------------------------------------------- /config/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import { Settings as ProSettings } from '@ant-design/pro-layout'; 2 | 3 | type DefaultSettings = Partial & { 4 | pwa: boolean; 5 | }; 6 | 7 | const proSettings: DefaultSettings = { 8 | navTheme: 'dark', 9 | menu: { 10 | locale: false, //关闭国际化 11 | }, 12 | // 拂晓蓝 13 | primaryColor: '#1890ff', 14 | layout: 'side', 15 | contentWidth: 'Fluid', 16 | fixedHeader: false, 17 | fixSiderbar: true, 18 | colorWeak: false, 19 | title: '商汇', 20 | pwa: false, 21 | iconfontUrl: '', 22 | }; 23 | 24 | export type { DefaultSettings }; 25 | 26 | export default proSettings; 27 | -------------------------------------------------------------------------------- /src/locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import component from './zh-TW/component'; 2 | import globalHeader from './zh-TW/globalHeader'; 3 | import menu from './zh-TW/menu'; 4 | import pwa from './zh-TW/pwa'; 5 | import settingDrawer from './zh-TW/settingDrawer'; 6 | import settings from './zh-TW/settings'; 7 | 8 | export default { 9 | 'navBar.lang': '語言', 10 | 'layout.user.link.help': '幫助', 11 | 'layout.user.link.privacy': '隱私', 12 | 'layout.user.link.terms': '條款', 13 | 'app.preview.down.block': '下載此頁面到本地項目', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .popover { 4 | position: relative; 5 | width: 336px; 6 | } 7 | 8 | .noticeButton { 9 | display: inline-block; 10 | cursor: pointer; 11 | transition: all 0.3s; 12 | } 13 | .icon { 14 | padding: 4px; 15 | vertical-align: middle; 16 | } 17 | 18 | .badge { 19 | font-size: 16px; 20 | } 21 | 22 | .tabs { 23 | :global { 24 | .ant-tabs-nav-list { 25 | margin: auto; 26 | } 27 | 28 | .ant-tabs-nav-scroll { 29 | text-align: center; 30 | } 31 | .ant-tabs-bar { 32 | margin-bottom: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.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 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | .vscode 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | functions/* 29 | .temp/** 30 | 31 | # umi 32 | .umi 33 | .umi-production 34 | 35 | # screenshot 36 | screenshot 37 | .firebase 38 | .eslintcache 39 | 40 | build 41 | -------------------------------------------------------------------------------- /public/pro_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/BusDetail/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .businessdetailcontainer { 4 | .acter { 5 | margin-bottom: 20px; 6 | .name { 7 | margin-left: 15px; 8 | font-size: 18px; 9 | font-weight: bold; 10 | } 11 | } 12 | .cardcontanier { 13 | .cardlist { 14 | &:nth-child(1) { 15 | padding-right: 10px; 16 | } 17 | .carditem { 18 | padding: 5px 0; 19 | .title { 20 | margin-right: 10px; 21 | font-weight: bold; 22 | } 23 | .maintitle { 24 | font-size: 16px; 25 | color: #f86934; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/locales/pt-BR.ts: -------------------------------------------------------------------------------- 1 | import component from './pt-BR/component'; 2 | import globalHeader from './pt-BR/globalHeader'; 3 | import menu from './pt-BR/menu'; 4 | import pwa from './pt-BR/pwa'; 5 | import settingDrawer from './pt-BR/settingDrawer'; 6 | import settings from './pt-BR/settings'; 7 | 8 | export default { 9 | 'navBar.lang': 'Idiomas', 10 | 'layout.user.link.help': 'ajuda', 11 | 'layout.user.link.privacy': 'política de privacidade', 12 | 'layout.user.link.terms': 'termos de serviços', 13 | 'app.preview.down.block': 'Download this page to your local project', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /src/pages/TableList/user/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryList(params: {current: number, pageSize: number}) { 4 | return request('/gsh/listByTGshUser', { 5 | params: { 6 | pageNo: params.current, 7 | pageSize: params.pageSize, 8 | type: 0 9 | } 10 | }) 11 | } 12 | 13 | export async function toFreeze(id: number | string, type: number | string) { 14 | return request(`/gsh/freeze/${id}/${type}`, { 15 | method: 'delete' 16 | }) 17 | } 18 | 19 | export async function toBatchMassage(data: {ids: any[], message: string}) { 20 | return request(`/gsh/batchMassage`, { 21 | method: 'post', 22 | data 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .headerSearch { 4 | .input { 5 | width: 0; 6 | min-width: 0; 7 | overflow: hidden; 8 | background: transparent; 9 | border-radius: 0; 10 | transition: width 0.3s, margin-left 0.3s; 11 | :global(.ant-select-selection) { 12 | background: transparent; 13 | } 14 | input { 15 | padding-right: 0; 16 | padding-left: 0; 17 | border: 0; 18 | box-shadow: none !important; 19 | } 20 | &, 21 | &:hover, 22 | &:focus { 23 | border-bottom: 1px solid @border-color-base; 24 | } 25 | &.show { 26 | width: 210px; 27 | margin-left: 8px; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/HeaderDropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import type { DropDownProps } from 'antd/es/dropdown'; 2 | import { Dropdown } from 'antd'; 3 | import React from 'react'; 4 | import classNames from 'classnames'; 5 | import styles from './index.less'; 6 | 7 | export type HeaderDropdownProps = { 8 | overlayClassName?: string; 9 | overlay: React.ReactNode | (() => React.ReactNode) | any; 10 | placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; 11 | } & Omit; 12 | 13 | const HeaderDropdown: React.FC = ({ overlayClassName: cls, ...restProps }) => ( 14 | 15 | ); 16 | 17 | export default HeaderDropdown; 18 | -------------------------------------------------------------------------------- /src/locales/ja-JP.ts: -------------------------------------------------------------------------------- 1 | import globalHeader from './ja-JP/globalHeader'; 2 | import menu from './ja-JP/menu'; 3 | import settingDrawer from './ja-JP/settingDrawer'; 4 | import settings from './ja-JP/settings'; 5 | import pwa from './ja-JP/pwa'; 6 | import component from './ja-JP/component'; 7 | import pages from './ja-JP/pages'; 8 | 9 | export default { 10 | 'navBar.lang': '言語', 11 | 'layout.user.link.help': 'ヘルプ', 12 | 'layout.user.link.privacy': 'プライバシー', 13 | 'layout.user.link.terms': '利用規約', 14 | 'app.preview.down.block': 'このページをローカルプロジェクトにダウンロードしてください', 15 | 'app.welcome.link.fetch-blocks': '', 16 | 'app.welcome.link.block-list': '', 17 | ...globalHeader, 18 | ...menu, 19 | ...settingDrawer, 20 | ...settings, 21 | ...pwa, 22 | ...component, 23 | ...pages, 24 | }; 25 | -------------------------------------------------------------------------------- /src/locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | import component from './zh-CN/component'; 2 | import globalHeader from './zh-CN/globalHeader'; 3 | import menu from './zh-CN/menu'; 4 | import pwa from './zh-CN/pwa'; 5 | import settingDrawer from './zh-CN/settingDrawer'; 6 | import settings from './zh-CN/settings'; 7 | import pages from './zh-CN/pages'; 8 | 9 | export default { 10 | 'navBar.lang': '语言', 11 | 'layout.user.link.help': '帮助', 12 | 'layout.user.link.privacy': '隐私', 13 | 'layout.user.link.terms': '条款', 14 | 'app.preview.down.block': '下载此页面到本地项目', 15 | 'app.welcome.link.fetch-blocks': '获取全部区块', 16 | 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面', 17 | ...pages, 18 | ...globalHeader, 19 | ...menu, 20 | ...settingDrawer, 21 | ...settings, 22 | ...pwa, 23 | ...component, 24 | }; 25 | -------------------------------------------------------------------------------- /config/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 3 | * The agent cannot take effect in the production environment 4 | * so there is no configuration of the production environment 5 | * For details, please see 6 | * https://pro.ant.design/docs/deploy 7 | */ 8 | export default { 9 | dev: { 10 | '/api/': { 11 | target: 'http://39.108.168.143:6179/', 12 | changeOrigin: true, 13 | pathRewrite: { '^/api': '/' }, 14 | }, 15 | }, 16 | test: { 17 | '/api/': { 18 | target: 'https://preview.pro.ant.design', 19 | changeOrigin: true, 20 | pathRewrite: { '^': '' }, 21 | }, 22 | }, 23 | pre: { 24 | '/api/': { 25 | target: 'your pre url', 26 | changeOrigin: true, 27 | pathRewrite: { '^': '' }, 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/locales/zh-CN/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站内搜索', 3 | 'component.globalHeader.search.example1': '搜索提示一', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用文档', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '你已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已读完所有消息', 11 | 'component.globalHeader.event': '待办', 12 | 'component.globalHeader.event.empty': '你已完成所有待办', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暂无数据', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /src/locales/zh-TW/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站內搜索', 3 | 'component.globalHeader.search.example1': '搜索提示壹', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用手冊', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '妳已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已讀完所有消息', 11 | 'component.globalHeader.event': '待辦', 12 | 'component.globalHeader.event.empty': '妳已完成所有待辦', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暫無資料', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /src/models/connect.d.ts: -------------------------------------------------------------------------------- 1 | import type { MenuDataItem, Settings as ProSettings } from '@ant-design/pro-layout'; 2 | import { GlobalModelState } from './global'; 3 | import { UserModelState } from './user'; 4 | import type { StateType } from './login'; 5 | 6 | export { GlobalModelState, UserModelState }; 7 | 8 | export type Loading = { 9 | global: boolean; 10 | effects: Record; 11 | models: { 12 | global?: boolean; 13 | menu?: boolean; 14 | setting?: boolean; 15 | user?: boolean; 16 | login?: boolean; 17 | }; 18 | }; 19 | 20 | export type ConnectState = { 21 | global: GlobalModelState; 22 | loading: Loading; 23 | settings: ProSettings; 24 | user: UserModelState; 25 | login: StateType; 26 | }; 27 | 28 | export type Route = { 29 | routes?: Route[]; 30 | } & MenuDataItem; 31 | -------------------------------------------------------------------------------- /src/locales/ja-JP/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '検索', 3 | 'component.globalHeader.search.example1': '検索例1', 4 | 'component.globalHeader.search.example2': '検索例2', 5 | 'component.globalHeader.search.example3': '検索例3', 6 | 'component.globalHeader.help': 'ヘルプ', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': 'すべての通知を表示しました。', 9 | 'component.globalHeader.message': 'メッセージ', 10 | 'component.globalHeader.message.empty': 'すべてのメッセージを表示しました。', 11 | 'component.globalHeader.event': 'イベント', 12 | 'component.globalHeader.event.empty': 'すべてのイベントを表示しました。', 13 | 'component.noticeIcon.clear': 'クリア', 14 | 'component.noticeIcon.cleared': 'クリア済み', 15 | 'component.noticeIcon.empty': '通知なし', 16 | 'component.noticeIcon.view-more': 'もっと見る', 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/TableList/search/data.d.ts: -------------------------------------------------------------------------------- 1 | export type TableListItem = { 2 | key: number; 3 | disabled?: boolean; 4 | href: string; 5 | avatar: string; 6 | name: string; 7 | owner: string; 8 | desc: string; 9 | callNo: number; 10 | status: number; 11 | updatedAt: Date; 12 | createdAt: Date; 13 | progress: number; 14 | }; 15 | 16 | export type TableListPagination = { 17 | total: number; 18 | pageSize: number; 19 | current: number; 20 | }; 21 | 22 | export type TableListData = { 23 | list: TableListItem[]; 24 | pagination: Partial; 25 | }; 26 | 27 | export type TableListParams = { 28 | status?: string; 29 | name?: string; 30 | desc?: string; 31 | key?: number; 32 | pageSize?: number; 33 | currentPage?: number; 34 | filter?: Record; 35 | sorter?: Record; 36 | }; 37 | -------------------------------------------------------------------------------- /src/locales/en-US.ts: -------------------------------------------------------------------------------- 1 | import component from './en-US/component'; 2 | import globalHeader from './en-US/globalHeader'; 3 | import menu from './en-US/menu'; 4 | import pwa from './en-US/pwa'; 5 | import settingDrawer from './en-US/settingDrawer'; 6 | import settings from './en-US/settings'; 7 | import pages from './en-US/pages'; 8 | 9 | export default { 10 | 'navBar.lang': 'Languages', 11 | 'layout.user.link.help': 'Help', 12 | 'layout.user.link.privacy': 'Privacy', 13 | 'layout.user.link.terms': 'Terms', 14 | 'app.preview.down.block': 'Download this page to your local project', 15 | 'app.welcome.link.fetch-blocks': 'Get all block', 16 | 'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development', 17 | ...globalHeader, 18 | ...menu, 19 | ...settingDrawer, 20 | ...settings, 21 | ...pwa, 22 | ...component, 23 | ...pages, 24 | }; 25 | -------------------------------------------------------------------------------- /src/locales/id-ID.ts: -------------------------------------------------------------------------------- 1 | import component from './id-ID/component'; 2 | import globalHeader from './id-ID/globalHeader'; 3 | import menu from './id-ID/menu'; 4 | import pwa from './id-ID/pwa'; 5 | import settingDrawer from './id-ID/settingDrawer'; 6 | import settings from './id-ID/settings'; 7 | import pages from './id-ID/pages'; 8 | 9 | export default { 10 | 'navbar.lang': 'Bahasa', 11 | 'layout.user.link.help': 'Bantuan', 12 | 'layout.user.link.privacy': 'Privasi', 13 | 'layout.user.link.terms': 'Ketentuan', 14 | 'app.preview.down.block': 'Unduh halaman ini dalam projek lokal anda', 15 | 'app.welcome.link.fetch-blocks': 'Dapatkan semua blok', 16 | 'app.welcome.link.block-list': 17 | 'Buat standar dengan cepat, halaman-halaman berdasarkan pengembangan `block`', 18 | ...globalHeader, 19 | ...menu, 20 | ...settingDrawer, 21 | ...settings, 22 | ...pwa, 23 | ...component, 24 | ...pages, 25 | }; 26 | -------------------------------------------------------------------------------- /src/pages/User/login/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .main { 4 | width: 328px; 5 | margin: 0 auto; 6 | @media screen and (max-width: @screen-sm) { 7 | width: 95%; 8 | max-width: 328px; 9 | } 10 | 11 | :global { 12 | .@{ant-prefix}-tabs-nav-list { 13 | margin: auto; 14 | font-size: 16px; 15 | } 16 | } 17 | 18 | .icon { 19 | margin-left: 16px; 20 | color: rgba(0, 0, 0, 0.2); 21 | font-size: 24px; 22 | vertical-align: middle; 23 | cursor: pointer; 24 | transition: color 0.3s; 25 | 26 | &:hover { 27 | color: @primary-color; 28 | } 29 | } 30 | 31 | .other { 32 | margin-top: 24px; 33 | line-height: 22px; 34 | text-align: left; 35 | .register { 36 | float: right; 37 | } 38 | } 39 | 40 | .prefixIcon { 41 | color: @primary-color; 42 | font-size: @font-size-base; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/TableList/classify/components/detail.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Image } from 'antd'; 3 | 4 | export type UpdateFormProps = { 5 | onCancel: (flag?: boolean) => void; 6 | // onSubmit: (values: FormValueType) => Promise; 7 | modalVisible: boolean; 8 | }; 9 | 10 | const Detail: React.FC = (props) => { 11 | return ( 12 | { 21 | props.onCancel(); 22 | }} 23 | > 24 | 29 | 30 | ) 31 | } 32 | 33 | export default Detail -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'querystring'; 2 | 3 | /* eslint no-useless-escape:0 import/prefer-default-export:0 */ 4 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; 5 | 6 | export const isUrl = (path: string): boolean => reg.test(path); 7 | 8 | export const isAntDesignPro = (): boolean => { 9 | if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 10 | return true; 11 | } 12 | return window.location.hostname === 'preview.pro.ant.design'; 13 | }; 14 | 15 | // 给官方演示站点用,用于关闭真实开发环境不需要使用的特性 16 | export const isAntDesignProOrDev = (): boolean => { 17 | const { NODE_ENV } = process.env; 18 | if (NODE_ENV === 'development') { 19 | return true; 20 | } 21 | return isAntDesignPro(); 22 | }; 23 | 24 | export const getPageQuery = () => parse(window.location.href.split('?')[1]); 25 | -------------------------------------------------------------------------------- /src/locales/en-US/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Search', 3 | 'component.globalHeader.search.example1': 'Search example 1', 4 | 'component.globalHeader.search.example2': 'Search example 2', 5 | 'component.globalHeader.search.example3': 'Search example 3', 6 | 'component.globalHeader.help': 'Help', 7 | 'component.globalHeader.notification': 'Notification', 8 | 'component.globalHeader.notification.empty': 'You have viewed all notifications.', 9 | 'component.globalHeader.message': 'Message', 10 | 'component.globalHeader.message.empty': 'You have viewed all messsages.', 11 | 'component.globalHeader.event': 'Event', 12 | 'component.globalHeader.event.empty': 'You have viewed all events.', 13 | 'component.noticeIcon.clear': 'Clear', 14 | 'component.noticeIcon.cleared': 'Cleared', 15 | 'component.noticeIcon.empty': 'No notifications', 16 | 'component.noticeIcon.view-more': 'View more', 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/TableList/search/service.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | import type { TableListParams, TableListItem } from './data.d'; 3 | 4 | export async function queryRule(params?: TableListParams) { 5 | return request('/api/rule', { 6 | params, 7 | }); 8 | } 9 | 10 | export async function removeRule(params: { key: number[] }) { 11 | return request('/api/rule', { 12 | method: 'POST', 13 | data: { 14 | ...params, 15 | method: 'delete', 16 | }, 17 | }); 18 | } 19 | 20 | export async function addRule(params: TableListItem) { 21 | return request('/api/rule', { 22 | method: 'POST', 23 | data: { 24 | ...params, 25 | method: 'post', 26 | }, 27 | }); 28 | } 29 | 30 | export async function updateRule(params: TableListParams) { 31 | return request('/api/rule', { 32 | method: 'POST', 33 | data: { 34 | ...params, 35 | method: 'update', 36 | }, 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/locales/id-ID/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Pencarian', 3 | 'component.globalHeader.search.example1': 'Contoh 1 Pencarian', 4 | 'component.globalHeader.search.example2': 'Contoh 2 Pencarian', 5 | 'component.globalHeader.search.example3': 'Contoh 3 Pencarian', 6 | 'component.globalHeader.help': 'Bantuan', 7 | 'component.globalHeader.notification': 'Notifikasi', 8 | 'component.globalHeader.notification.empty': 'Anda telah membaca semua notifikasi', 9 | 'component.globalHeader.message': 'Pesan', 10 | 'component.globalHeader.message.empty': 'Anda telah membaca semua pesan.', 11 | 'component.globalHeader.event': 'Acara', 12 | 'component.globalHeader.event.empty': 'Anda telah melihat semua acara.', 13 | 'component.noticeIcon.clear': 'Kosongkan', 14 | 'component.noticeIcon.cleared': 'Berhasil dikosongkan', 15 | 'component.noticeIcon.empty': 'Tidak ada pemberitahuan', 16 | 'component.noticeIcon.view-more': 'Melihat lebih', 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Authorized/AuthorizedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'umi'; 2 | 3 | import React from 'react'; 4 | import Authorized from './Authorized'; 5 | import type { IAuthorityType } from './CheckPermissions'; 6 | 7 | type AuthorizedRouteProps = { 8 | currentAuthority: string; 9 | component: React.ComponentClass; 10 | render: (props: any) => React.ReactNode; 11 | redirectPath: string; 12 | authority: IAuthorityType; 13 | }; 14 | 15 | const AuthorizedRoute: React.SFC = ({ 16 | component: Component, 17 | render, 18 | authority, 19 | redirectPath, 20 | ...rest 21 | }) => ( 22 | } />} 25 | > 26 | (Component ? : render(props))} 29 | /> 30 | 31 | ); 32 | 33 | export default AuthorizedRoute; 34 | -------------------------------------------------------------------------------- /src/locales/pt-BR/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Busca', 3 | 'component.globalHeader.search.example1': 'Exemplo de busca 1', 4 | 'component.globalHeader.search.example2': 'Exemplo de busca 2', 5 | 'component.globalHeader.search.example3': 'Exemplo de busca 3', 6 | 'component.globalHeader.help': 'Ajuda', 7 | 'component.globalHeader.notification': 'Notificação', 8 | 'component.globalHeader.notification.empty': 'Você visualizou todas as notificações.', 9 | 'component.globalHeader.message': 'Mensagem', 10 | 'component.globalHeader.message.empty': 'Você visualizou todas as mensagens.', 11 | 'component.globalHeader.event': 'Evento', 12 | 'component.globalHeader.event.empty': 'Você visualizou todos os eventos.', 13 | 'component.noticeIcon.clear': 'Limpar', 14 | 'component.noticeIcon.cleared': 'Limpo', 15 | 'component.noticeIcon.empty': 'Sem notificações', 16 | 'component.noticeIcon.loaded': 'Carregado', 17 | 'component.noticeIcon.view-more': 'Veja mais', 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/Authorized/renderAuthorize.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable import/no-mutable-exports */ 3 | let CURRENT: string | string[] = 'NULL'; 4 | 5 | type CurrentAuthorityType = string | string[] | (() => typeof CURRENT); 6 | /** 7 | * Use authority or getAuthority 8 | * 9 | * @param {string|()=>String} currentAuthority 10 | */ 11 | const renderAuthorize = (Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => ( 12 | currentAuthority: CurrentAuthorityType, 13 | ): T => { 14 | if (currentAuthority) { 15 | if (typeof currentAuthority === 'function') { 16 | CURRENT = currentAuthority(); 17 | } 18 | if ( 19 | Object.prototype.toString.call(currentAuthority) === '[object String]' || 20 | Array.isArray(currentAuthority) 21 | ) { 22 | CURRENT = currentAuthority as string[]; 23 | } 24 | } else { 25 | CURRENT = 'NULL'; 26 | } 27 | return Authorized; 28 | }; 29 | 30 | export { CURRENT }; 31 | export default (Authorized: T) => renderAuthorize(Authorized); 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "preserve", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": true, 16 | "allowJs": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "strict": true, 20 | "paths": { 21 | "@/*": ["./src/*"], 22 | "@@/*": ["./src/.umi/*"] 23 | } 24 | }, 25 | "include": [ 26 | "mock/**/*", 27 | "src/**/*", 28 | "tests/**/*", 29 | "test/**/*", 30 | "__test__/**/*", 31 | "typings/**/*", 32 | "config/**/*", 33 | ".eslintrc.js", 34 | ".stylelintrc.js", 35 | ".prettierrc.js", 36 | "jest.config.js", 37 | "mock/*" 38 | ], 39 | "exclude": ["node_modules", "build", "dist", "scripts", "src/.umi/*", "webpack", "jest"] 40 | } 41 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash2'; 2 | declare module '*.css'; 3 | declare module '*.less'; 4 | declare module '*.scss'; 5 | declare module '*.sass'; 6 | declare module '*.svg'; 7 | declare module '*.png'; 8 | declare module '*.jpg'; 9 | declare module '*.jpeg'; 10 | declare module '*.gif'; 11 | declare module '*.bmp'; 12 | declare module '*.tiff'; 13 | declare module 'omit.js'; 14 | 15 | // google analytics interface 16 | type GAFieldsObject = { 17 | eventCategory: string; 18 | eventAction: string; 19 | eventLabel?: string; 20 | eventValue?: number; 21 | nonInteraction?: boolean; 22 | }; 23 | 24 | interface Window { 25 | ga: ( 26 | command: 'send', 27 | hitType: 'event' | 'pageview', 28 | fieldsObject: GAFieldsObject | string, 29 | ) => void; 30 | reloadAuthorized: () => void; 31 | } 32 | 33 | declare let ga: () => void; 34 | 35 | // preview.pro.ant.design only do not use in your production ; 36 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 37 | declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined; 38 | 39 | declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; 40 | -------------------------------------------------------------------------------- /src/components/Authorized/Authorized.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Result } from 'antd'; 3 | import check from './CheckPermissions'; 4 | import type { IAuthorityType } from './CheckPermissions'; 5 | import type AuthorizedRoute from './AuthorizedRoute'; 6 | import type Secured from './Secured'; 7 | 8 | type AuthorizedProps = { 9 | authority: IAuthorityType; 10 | noMatch?: React.ReactNode; 11 | }; 12 | 13 | type IAuthorizedType = React.FunctionComponent & { 14 | Secured: typeof Secured; 15 | check: typeof check; 16 | AuthorizedRoute: typeof AuthorizedRoute; 17 | }; 18 | 19 | const Authorized: React.FunctionComponent = ({ 20 | children, 21 | authority, 22 | noMatch = ( 23 | 28 | ), 29 | }) => { 30 | const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children; 31 | const dom = check(authority, childrenRender, noMatch); 32 | return <>{dom}; 33 | }; 34 | 35 | export default Authorized as IAuthorizedType; 36 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from 'umi'; 3 | import defaultSettings from './defaultSettings'; 4 | import proxy from './proxy'; 5 | import routes from './routes'; 6 | 7 | const { REACT_APP_ENV } = process.env; 8 | 9 | export default defineConfig({ 10 | hash: true, 11 | antd: {}, 12 | dva: { 13 | hmr: true, 14 | }, 15 | history: { 16 | type: 'browser', 17 | }, 18 | locale: { 19 | // default zh-CN 20 | default: 'zh-CN', 21 | antd: true, 22 | // default true, when it is true, will use `navigator.language` overwrite default 23 | baseNavigator: true, 24 | }, 25 | dynamicImport: { 26 | loading: '@/components/PageLoading/index', 27 | }, 28 | targets: { 29 | ie: 11, 30 | }, 31 | // umi routes: https://umijs.org/docs/routing 32 | routes, 33 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 34 | theme: { 35 | 'primary-color': defaultSettings.primaryColor, 36 | }, 37 | title: false, 38 | ignoreMomentLocale: true, 39 | proxy: proxy[REACT_APP_ENV || 'dev'], 40 | manifest: { 41 | basePath: '/', 42 | }, 43 | esbuild: {}, 44 | }); 45 | -------------------------------------------------------------------------------- /src/models/setting.ts: -------------------------------------------------------------------------------- 1 | import type { Reducer } from 'umi'; 2 | import type { DefaultSettings } from '../../config/defaultSettings'; 3 | import defaultSettings from '../../config/defaultSettings'; 4 | 5 | export type SettingModelType = { 6 | namespace: 'settings'; 7 | state: DefaultSettings; 8 | reducers: { 9 | changeSetting: Reducer; 10 | }; 11 | }; 12 | 13 | const updateColorWeak: (colorWeak: boolean) => void = (colorWeak) => { 14 | const root = document.getElementById('root'); 15 | if (root) { 16 | root.className = colorWeak ? 'colorWeak' : ''; 17 | } 18 | }; 19 | 20 | const SettingModel: SettingModelType = { 21 | namespace: 'settings', 22 | state: defaultSettings, 23 | reducers: { 24 | changeSetting(state = defaultSettings, { payload }) { 25 | const { colorWeak, contentWidth } = payload; 26 | 27 | if (state.contentWidth !== contentWidth && window.dispatchEvent) { 28 | window.dispatchEvent(new Event('resize')); 29 | } 30 | updateColorWeak(!!colorWeak); 31 | return { 32 | ...state, 33 | ...payload, 34 | }; 35 | }, 36 | }, 37 | }; 38 | export default SettingModel; 39 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | @import './components/Detail/index.less'; 3 | 4 | html, 5 | body, 6 | #root { 7 | height: 100%; 8 | } 9 | 10 | .colorWeak { 11 | filter: invert(80%); 12 | } 13 | 14 | .ant-layout { 15 | min-height: 100vh; 16 | } 17 | 18 | canvas { 19 | display: block; 20 | } 21 | 22 | body { 23 | text-rendering: optimizeLegibility; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | ul, 29 | ol { 30 | margin: 0; 31 | padding: 0; 32 | list-style: none; 33 | } 34 | 35 | @media (max-width: @screen-xs) { 36 | .ant-table { 37 | width: 100%; 38 | overflow-x: auto; 39 | &-thead > tr, 40 | &-tbody > tr { 41 | > th, 42 | > td { 43 | white-space: pre; 44 | > span { 45 | display: block; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | // 兼容IE11 53 | @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { 54 | body .ant-design-pro > .ant-layout { 55 | min-height: 100vh; 56 | } 57 | } 58 | 59 | .ant-page-header { 60 | .ant-page-header-heading { 61 | display: none; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/services/login.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export type LoginParamsType = { 4 | userName?: string; 5 | password?: string; 6 | mobile?: string; 7 | userAccount?: string; 8 | captcha?: string; 9 | type?: number | string; 10 | code?: number | string; 11 | }; 12 | 13 | export async function fakeAccountLogin(params: LoginParamsType) { 14 | return request('/gsh/login', { 15 | method: 'POST', 16 | data: { 17 | userAccount: params.mobile, 18 | userPwd: params.password 19 | }, 20 | }); 21 | } 22 | // 注册 23 | export async function getRegister(params: LoginParamsType) { 24 | return request('/gsh/saveTGshUser', { 25 | method: 'POST', 26 | data: { 27 | email: params.mobile, 28 | password: params.password, 29 | code: params.captcha 30 | }, 31 | }); 32 | } 33 | // 退出登录 34 | export async function logout(params: LoginParamsType) { 35 | return request('/gsh/logOut', { 36 | method: 'POST', 37 | data: { 38 | userAccount: params.userAccount, 39 | }, 40 | }); 41 | } 42 | 43 | export async function getFakeCaptcha(mobile: string) { 44 | return request(`/email/sendSimpleEmail?toEmail=${mobile}`); 45 | } 46 | -------------------------------------------------------------------------------- /tests/PuppeteerEnvironment.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | const NodeEnvironment = require('jest-environment-node'); 3 | const getBrowser = require('./getBrowser'); 4 | 5 | class PuppeteerEnvironment extends NodeEnvironment { 6 | // Jest is not available here, so we have to reverse engineer 7 | // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823 8 | setTimeout(timeout) { 9 | if (this.global.jasmine) { 10 | // eslint-disable-next-line no-underscore-dangle 11 | this.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout; 12 | } else { 13 | this.global[Symbol.for('TEST_TIMEOUT_SYMBOL')] = timeout; 14 | } 15 | } 16 | 17 | async setup() { 18 | const browser = await getBrowser(); 19 | const page = await browser.newPage(); 20 | this.global.browser = browser; 21 | this.global.page = page; 22 | } 23 | 24 | async teardown() { 25 | const { page, browser } = this.global; 26 | 27 | if (page) { 28 | await page.close(); 29 | } 30 | 31 | if (browser) { 32 | await browser.disconnect(); 33 | } 34 | 35 | if (browser) { 36 | await browser.close(); 37 | } 38 | } 39 | } 40 | 41 | module.exports = PuppeteerEnvironment; 42 | -------------------------------------------------------------------------------- /src/utils/authority.ts: -------------------------------------------------------------------------------- 1 | import { reloadAuthorized } from './Authorized'; 2 | 3 | // use localStorage to store the authority info, which might be sent from server in actual project. 4 | export function getAuthority(str?: string): string | string[] { 5 | const authorityString = 6 | typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; 7 | // authorityString could be admin, "admin", ["admin"] 8 | let authority; 9 | try { 10 | if (authorityString) { 11 | authority = JSON.parse(authorityString); 12 | } 13 | } catch (e) { 14 | authority = authorityString; 15 | } 16 | if (typeof authority === 'string') { 17 | return [authority]; 18 | } 19 | // preview.pro.ant.design only do not use in your production. 20 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 21 | if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 22 | return ['admin']; 23 | } 24 | return authority; 25 | } 26 | 27 | export function setAuthority(authority: string | string[]): void { 28 | const proAuthority = typeof authority === 'string' ? [authority] : authority; 29 | localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); 30 | // auto reload 31 | reloadAuthorized(); 32 | } 33 | -------------------------------------------------------------------------------- /tests/getBrowser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const findChrome = require('carlo/lib/find_chrome'); 4 | 5 | const getBrowser = async () => { 6 | try { 7 | // eslint-disable-next-line import/no-unresolved 8 | const puppeteer = require('puppeteer'); 9 | const browser = await puppeteer.launch({ 10 | args: [ 11 | '--disable-gpu', 12 | '--disable-dev-shm-usage', 13 | '--no-first-run', 14 | '--no-zygote', 15 | '--no-sandbox', 16 | ], 17 | }); 18 | return browser; 19 | } catch (error) { 20 | // console.log(error) 21 | } 22 | 23 | try { 24 | // eslint-disable-next-line import/no-unresolved 25 | const puppeteer = require('puppeteer-core'); 26 | const findChromePath = await findChrome({}); 27 | const { executablePath } = findChromePath; 28 | const browser = await puppeteer.launch({ 29 | executablePath, 30 | args: [ 31 | '--disable-gpu', 32 | '--disable-dev-shm-usage', 33 | '--no-first-run', 34 | '--no-zygote', 35 | '--no-sandbox', 36 | ], 37 | }); 38 | return browser; 39 | } catch (error) { 40 | console.log('🧲 no find chrome'); 41 | } 42 | throw new Error('no find puppeteer'); 43 | }; 44 | 45 | module.exports = getBrowser; 46 | -------------------------------------------------------------------------------- /src/locales/zh-CN/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.dark': '暗色菜单风格', 4 | 'app.setting.pagestyle.light': '亮色菜单风格', 5 | 'app.setting.content-width': '内容区域宽度', 6 | 'app.setting.content-width.fixed': '定宽', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主题色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '极光绿', 14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)', 15 | 'app.setting.themecolor.geekblue': '极客蓝', 16 | 'app.setting.themecolor.purple': '酱紫', 17 | 'app.setting.navigationmode': '导航模式', 18 | 'app.setting.sidemenu': '侧边菜单布局', 19 | 'app.setting.topmenu': '顶部菜单布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定侧边菜单', 22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置', 23 | 'app.setting.hideheader': '下滑时隐藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 时可配置', 25 | 'app.setting.othersettings': '其他设置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷贝设置', 28 | 'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置', 29 | 'app.setting.production.hint': 30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /src/locales/zh-TW/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整體風格設置', 3 | 'app.setting.pagestyle.dark': '暗色菜單風格', 4 | 'app.setting.pagestyle.light': '亮色菜單風格', 5 | 'app.setting.content-width': '內容區域寬度', 6 | 'app.setting.content-width.fixed': '定寬', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主題色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '極光綠', 14 | 'app.setting.themecolor.daybreak': '拂曉藍(默認)', 15 | 'app.setting.themecolor.geekblue': '極客藍', 16 | 'app.setting.themecolor.purple': '醬紫', 17 | 'app.setting.navigationmode': '導航模式', 18 | 'app.setting.sidemenu': '側邊菜單布局', 19 | 'app.setting.topmenu': '頂部菜單布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定側邊菜單', 22 | 'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置', 23 | 'app.setting.hideheader': '下滑時隱藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 時可配置', 25 | 'app.setting.othersettings': '其他設置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷貝設置', 28 | 'app.setting.copyinfo': '拷貝成功,請到 src/defaultSettings.js 中替換默認配置', 29 | 'app.setting.production.hint': 30 | '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /tests/beforeTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const { execSync } = require('child_process'); 4 | const { join } = require('path'); 5 | const findChrome = require('carlo/lib/find_chrome'); 6 | const detectInstaller = require('detect-installer'); 7 | 8 | const installPuppeteer = () => { 9 | // find can use package manger 10 | const packages = detectInstaller(join(__dirname, '../../')); 11 | // get installed package manger 12 | const packageName = packages.find(detectInstaller.hasPackageCommand) || 'npm'; 13 | console.log(`🤖 will use ${packageName} install puppeteer`); 14 | const command = `${packageName} ${packageName.includes('yarn') ? 'add' : 'i'} puppeteer`; 15 | execSync(command, { 16 | stdio: 'inherit', 17 | }); 18 | }; 19 | 20 | const initPuppeteer = async () => { 21 | try { 22 | // eslint-disable-next-line import/no-unresolved 23 | const findChromePath = await findChrome({}); 24 | const { executablePath } = findChromePath; 25 | console.log(`🧲 find you browser in ${executablePath}`); 26 | return; 27 | } catch (error) { 28 | console.log('🧲 no find chrome'); 29 | } 30 | 31 | try { 32 | require.resolve('puppeteer'); 33 | } catch (error) { 34 | // need install puppeteer 35 | await installPuppeteer(); 36 | } 37 | }; 38 | 39 | initPuppeteer(); 40 | -------------------------------------------------------------------------------- /src/locales/ja-JP/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'ページスタイル設定', 3 | 'app.setting.pagestyle.dark': 'ダークスタイル', 4 | 'app.setting.pagestyle.light': 'ライトスタイル', 5 | 'app.setting.content-width': 'コンテンツの幅', 6 | 'app.setting.content-width.fixed': '固定', 7 | 'app.setting.content-width.fluid': '流体', 8 | 'app.setting.themecolor': 'テーマカラー', 9 | 'app.setting.themecolor.dust': 'ダストレッド', 10 | 'app.setting.themecolor.volcano': 'ボルケ-ノ', 11 | 'app.setting.themecolor.sunset': 'サンセットオレンジ', 12 | 'app.setting.themecolor.cyan': 'シアン', 13 | 'app.setting.themecolor.green': 'ポーラーグリーン', 14 | 'app.setting.themecolor.daybreak': '夜明けの青(デフォルト)', 15 | 'app.setting.themecolor.geekblue': 'ギーク ブルー', 16 | 'app.setting.themecolor.purple': 'ゴールデンパープル', 17 | 'app.setting.navigationmode': 'ナビゲーションモード', 18 | 'app.setting.sidemenu': 'サイドメニューのレイアウト', 19 | 'app.setting.topmenu': 'トップメニューのレイアウト', 20 | 'app.setting.fixedheader': '固定ヘッダー', 21 | 'app.setting.fixedsidebar': '固定サイドバー', 22 | 'app.setting.fixedsidebar.hint': 'サイドメニューのレイアウトで動作します', 23 | 'app.setting.hideheader': 'スクロール時の非表示ヘッダー', 24 | 'app.setting.hideheader.hint': '非表示ヘッダーが有効になっている場合に機能します', 25 | 'app.setting.othersettings': 'その他の設定', 26 | 'app.setting.weakmode': 'ウィークモード', 27 | 'app.setting.copy': 'コピー設定', 28 | 'app.setting.copyinfo': 29 | 'コピーが成功しました。src/models/setting.jsのdefaultSettingsを置き換えてください', 30 | 'app.setting.production.hint': '設定パネルは開発環境でのみ表示されます。手動で変更してください', 31 | }; 32 | -------------------------------------------------------------------------------- /src/pages/Admin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons'; 3 | import { Card, Typography, Alert } from 'antd'; 4 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; 5 | import { useIntl } from 'umi'; 6 | 7 | export default (): React.ReactNode => { 8 | const intl = useIntl(); 9 | return ( 10 | 16 | 17 | 30 | 31 | Ant Design Pro You 32 | 33 | 34 |

35 | Want to add more pages? Please refer to{' '} 36 | 37 | use block 38 | 39 | 。 40 |

41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: @layout-body-background; 9 | } 10 | 11 | .lang { 12 | width: 100%; 13 | height: 40px; 14 | line-height: 44px; 15 | text-align: right; 16 | :global(.ant-dropdown-trigger) { 17 | margin-right: 24px; 18 | } 19 | } 20 | 21 | .content { 22 | flex: 1; 23 | padding: 32px 0; 24 | } 25 | 26 | @media (min-width: @screen-md-min) { 27 | .container { 28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 29 | background-repeat: no-repeat; 30 | background-position: center 110px; 31 | background-size: 100%; 32 | } 33 | 34 | .content { 35 | padding: 32px 0 24px; 36 | } 37 | } 38 | 39 | .top { 40 | text-align: center; 41 | } 42 | 43 | .header { 44 | height: 44px; 45 | line-height: 44px; 46 | a { 47 | text-decoration: none; 48 | } 49 | } 50 | 51 | .logo { 52 | height: 44px; 53 | margin-right: 16px; 54 | vertical-align: top; 55 | } 56 | 57 | .title { 58 | position: relative; 59 | top: 2px; 60 | color: @heading-color; 61 | font-weight: 600; 62 | font-size: 33px; 63 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 64 | } 65 | 66 | .desc { 67 | margin-top: 12px; 68 | margin-bottom: 40px; 69 | color: @text-color-secondary; 70 | font-size: @font-size-base; 71 | } 72 | -------------------------------------------------------------------------------- /src/locales/en-US/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Page style setting', 3 | 'app.setting.pagestyle.dark': 'Dark style', 4 | 'app.setting.pagestyle.light': 'Light style', 5 | 'app.setting.content-width': 'Content Width', 6 | 'app.setting.content-width.fixed': 'Fixed', 7 | 'app.setting.content-width.fluid': 'Fluid', 8 | 'app.setting.themecolor': 'Theme Color', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Navigation Mode', 18 | 'app.setting.sidemenu': 'Side Menu Layout', 19 | 'app.setting.topmenu': 'Top Menu Layout', 20 | 'app.setting.fixedheader': 'Fixed Header', 21 | 'app.setting.fixedsidebar': 'Fixed Sidebar', 22 | 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout', 23 | 'app.setting.hideheader': 'Hidden Header when scrolling', 24 | 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled', 25 | 'app.setting.othersettings': 'Other Settings', 26 | 'app.setting.weakmode': 'Weak Mode', 27 | 'app.setting.copy': 'Copy Setting', 28 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js', 29 | 'app.setting.production.hint': 30 | 'Setting panel shows in development environment only, please manually modify', 31 | }; 32 | -------------------------------------------------------------------------------- /tests/run-tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | /* eslint-disable eslint-comments/no-unlimited-disable */ 4 | const { spawn } = require('child_process'); 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | const { kill } = require('cross-port-killer'); 7 | 8 | const env = Object.create(process.env); 9 | env.BROWSER = 'none'; 10 | env.TEST = true; 11 | env.UMI_UI = 'none'; 12 | env.PROGRESS = 'none'; 13 | // flag to prevent multiple test 14 | let once = false; 15 | 16 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], { 17 | env, 18 | }); 19 | 20 | startServer.stderr.on('data', (data) => { 21 | // eslint-disable-next-line 22 | console.log(data.toString()); 23 | }); 24 | 25 | startServer.on('exit', () => { 26 | kill(process.env.PORT || 8000); 27 | }); 28 | 29 | console.log('Starting development server for e2e tests...'); 30 | startServer.stdout.on('data', (data) => { 31 | console.log(data.toString()); 32 | // hack code , wait umi 33 | if ( 34 | (!once && data.toString().indexOf('Compiled successfully') >= 0) || 35 | data.toString().indexOf('Theme generated successfully') >= 0 36 | ) { 37 | // eslint-disable-next-line 38 | once = true; 39 | console.log('Development server is started, ready to run tests.'); 40 | const testCmd = spawn( 41 | /^win/.test(process.platform) ? 'npm.cmd' : 'npm', 42 | ['test', '--', '--maxWorkers=1', '--runInBand'], 43 | { 44 | stdio: 'inherit', 45 | }, 46 | ); 47 | testCmd.on('exit', (code) => { 48 | startServer.kill(); 49 | process.exit(code); 50 | }); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/locales/id-ID/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Pengaturan style Halaman', 3 | 'app.setting.pagestyle.dark': 'Style Gelap', 4 | 'app.setting.pagestyle.light': 'Style Cerah', 5 | 'app.setting.content-width': 'Lebar Konten', 6 | 'app.setting.content-width.fixed': 'Tetap', 7 | 'app.setting.content-width.fluid': 'Fluid', 8 | 'app.setting.themecolor': 'Theme Color', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (bawaan)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Mode Navigasi', 18 | 'app.setting.sidemenu': 'Susunan Menu Samping', 19 | 'app.setting.topmenu': 'Susunan Menu Atas', 20 | 'app.setting.fixedheader': 'Header Tetap', 21 | 'app.setting.fixedsidebar': 'Sidebar Tetap', 22 | 'app.setting.fixedsidebar.hint': 'Berjalan pada Susunan Menu Samping', 23 | 'app.setting.hideheader': 'Sembunyikan Header ketika gulir ke bawah', 24 | 'app.setting.hideheader.hint': 'Bekerja ketika Header tersembunyi dimunculkan', 25 | 'app.setting.othersettings': 'Pengaturan Lainnya', 26 | 'app.setting.weakmode': 'Mode Lemah', 27 | 'app.setting.copy': 'Salin Pengaturan', 28 | 'app.setting.copyinfo': 29 | 'Berhasil disalin,tolong ubah defaultSettings pada src/models/setting.js', 30 | 'app.setting.production.hint': 31 | 'Panel pengaturan hanya muncul pada lingkungan pengembangan, silahkan modifikasi secara menual', 32 | }; 33 | -------------------------------------------------------------------------------- /src/locales/pt-BR/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Configuração de estilo da página', 3 | 'app.setting.pagestyle.dark': 'Dark style', 4 | 'app.setting.pagestyle.light': 'Light style', 5 | 'app.setting.content-width': 'Largura do conteúdo', 6 | 'app.setting.content-width.fixed': 'Fixo', 7 | 'app.setting.content-width.fluid': 'Fluido', 8 | 'app.setting.themecolor': 'Cor do Tema', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Modo de Navegação', 18 | 'app.setting.sidemenu': 'Layout do Menu Lateral', 19 | 'app.setting.topmenu': 'Layout do Menu Superior', 20 | 'app.setting.fixedheader': 'Cabeçalho fixo', 21 | 'app.setting.fixedsidebar': 'Barra lateral fixa', 22 | 'app.setting.fixedsidebar.hint': 'Funciona no layout do menu lateral', 23 | 'app.setting.hideheader': 'Esconder o cabeçalho quando rolar', 24 | 'app.setting.hideheader.hint': 'Funciona quando o esconder cabeçalho está abilitado', 25 | 'app.setting.othersettings': 'Outras configurações', 26 | 'app.setting.weakmode': 'Weak Mode', 27 | 'app.setting.copy': 'Copiar Configuração', 28 | 'app.setting.copyinfo': 29 | 'copiado com sucesso,por favor trocar o defaultSettings em src/models/setting.js', 30 | 'app.setting.production.hint': 31 | 'O painel de configuração apenas é exibido no ambiente de desenvolvimento, por favor modifique manualmente o', 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { isUrl } from './utils'; 2 | 3 | describe('isUrl tests', (): void => { 4 | it('should return false for invalid and corner case inputs', (): void => { 5 | expect(isUrl([] as any)).toBeFalsy(); 6 | expect(isUrl({} as any)).toBeFalsy(); 7 | expect(isUrl(false as any)).toBeFalsy(); 8 | expect(isUrl(true as any)).toBeFalsy(); 9 | expect(isUrl(NaN as any)).toBeFalsy(); 10 | expect(isUrl(null as any)).toBeFalsy(); 11 | expect(isUrl(undefined as any)).toBeFalsy(); 12 | expect(isUrl('')).toBeFalsy(); 13 | }); 14 | 15 | it('should return false for invalid URLs', (): void => { 16 | expect(isUrl('foo')).toBeFalsy(); 17 | expect(isUrl('bar')).toBeFalsy(); 18 | expect(isUrl('bar/test')).toBeFalsy(); 19 | expect(isUrl('http:/example.com/')).toBeFalsy(); 20 | expect(isUrl('ttp://example.com/')).toBeFalsy(); 21 | }); 22 | 23 | it('should return true for valid URLs', (): void => { 24 | expect(isUrl('http://example.com/')).toBeTruthy(); 25 | expect(isUrl('https://example.com/')).toBeTruthy(); 26 | expect(isUrl('http://example.com/test/123')).toBeTruthy(); 27 | expect(isUrl('https://example.com/test/123')).toBeTruthy(); 28 | expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy(); 29 | expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy(); 30 | expect(isUrl('http://www.example.com/')).toBeTruthy(); 31 | expect(isUrl('https://www.example.com/')).toBeTruthy(); 32 | expect(isUrl('http://www.example.com/test/123')).toBeTruthy(); 33 | expect(isUrl('https://www.example.com/test/123')).toBeTruthy(); 34 | expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy(); 35 | expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025); 4 | 5 | .menu { 6 | :global(.anticon) { 7 | margin-right: 8px; 8 | } 9 | :global(.ant-dropdown-menu-item) { 10 | min-width: 160px; 11 | } 12 | } 13 | 14 | .right { 15 | display: flex; 16 | float: right; 17 | height: 48px; 18 | margin-left: auto; 19 | overflow: hidden; 20 | .action { 21 | display: flex; 22 | align-items: center; 23 | height: 100%; 24 | padding: 0 12px; 25 | cursor: pointer; 26 | transition: all 0.3s; 27 | > span { 28 | vertical-align: middle; 29 | } 30 | &:hover { 31 | background: @pro-header-hover-bg; 32 | } 33 | &:global(.opened) { 34 | background: @pro-header-hover-bg; 35 | } 36 | } 37 | .search { 38 | padding: 0 12px; 39 | &:hover { 40 | background: transparent; 41 | } 42 | } 43 | .account { 44 | .avatar { 45 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; 46 | margin-right: 8px; 47 | color: @primary-color; 48 | vertical-align: top; 49 | background: rgba(255, 255, 255, 0.85); 50 | } 51 | } 52 | } 53 | 54 | .dark { 55 | .action { 56 | color: rgba(255, 255, 255, 0.85); 57 | > span { 58 | color: rgba(255, 255, 255, 0.85); 59 | } 60 | &:hover, 61 | &:global(.opened) { 62 | background: @primary-color; 63 | } 64 | } 65 | } 66 | 67 | :global(.ant-pro-global-header) { 68 | .dark { 69 | .action { 70 | color: @text-color; 71 | > span { 72 | color: @text-color; 73 | } 74 | &:hover { 75 | color: rgba(255, 255, 255, 0.85); 76 | > span { 77 | color: rgba(255, 255, 255, 0.85); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/locales/zh-CN/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.more-blocks': '更多区块', 4 | 'menu.home': '首页', 5 | 'menu.admin': '管理页', 6 | 'menu.admin.sub-page': '二级管理页', 7 | 'menu.login': '登录', 8 | 'menu.register': '注册', 9 | 'menu.register.result': '注册结果', 10 | 'menu.dashboard': 'Dashboard', 11 | 'menu.dashboard.analysis': '分析页', 12 | 'menu.dashboard.monitor': '监控页', 13 | 'menu.dashboard.workplace': '工作台', 14 | 'menu.exception.403': '403', 15 | 'menu.exception.404': '404', 16 | 'menu.exception.500': '500', 17 | 'menu.form': '表单页', 18 | 'menu.form.basic-form': '基础表单', 19 | 'menu.form.step-form': '分步表单', 20 | 'menu.form.step-form.info': '分步表单(填写转账信息)', 21 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)', 22 | 'menu.form.step-form.result': '分步表单(完成)', 23 | 'menu.form.advanced-form': '高级表单', 24 | 'menu.list': '列表页', 25 | 'menu.list.table-list': '查询表格', 26 | 'menu.list.basic-list': '标准列表', 27 | 'menu.list.card-list': '卡片列表', 28 | 'menu.list.search-list': '搜索列表', 29 | 'menu.list.search-list.articles': '搜索列表(文章)', 30 | 'menu.list.search-list.projects': '搜索列表(项目)', 31 | 'menu.list.search-list.applications': '搜索列表(应用)', 32 | 'menu.profile': '详情页', 33 | 'menu.profile.basic': '基础详情页', 34 | 'menu.profile.advanced': '高级详情页', 35 | 'menu.result': '结果页', 36 | 'menu.result.success': '成功页', 37 | 'menu.result.fail': '失败页', 38 | 'menu.exception': '异常页', 39 | 'menu.exception.not-permission': '403', 40 | 'menu.exception.not-find': '404', 41 | 'menu.exception.server-error': '500', 42 | 'menu.exception.trigger': '触发错误', 43 | 'menu.account': '个人页', 44 | 'menu.account.center': '个人中心', 45 | 'menu.account.settings': '个人设置', 46 | 'menu.account.trigger': '触发报错', 47 | 'menu.account.logout': '退出登录', 48 | 'menu.editor': '图形编辑器', 49 | 'menu.editor.flow': '流程编辑器', 50 | 'menu.editor.mind': '脑图编辑器', 51 | 'menu.editor.koni': '拓扑编辑器', 52 | }; 53 | -------------------------------------------------------------------------------- /src/locales/zh-TW/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '歡迎', 3 | 'menu.more-blocks': '更多區塊', 4 | 'menu.home': '首頁', 5 | 'menu.login': '登錄', 6 | 'menu.admin': '权限', 7 | 'menu.admin.sub-page': '二级管理页', 8 | 'menu.exception.403': '403', 9 | 'menu.exception.404': '404', 10 | 'menu.exception.500': '500', 11 | 'menu.register': '註冊', 12 | 'menu.register.result': '註冊結果', 13 | 'menu.dashboard': 'Dashboard', 14 | 'menu.dashboard.analysis': '分析頁', 15 | 'menu.dashboard.monitor': '監控頁', 16 | 'menu.dashboard.workplace': '工作臺', 17 | 'menu.form': '表單頁', 18 | 'menu.form.basic-form': '基礎表單', 19 | 'menu.form.step-form': '分步表單', 20 | 'menu.form.step-form.info': '分步表單(填寫轉賬信息)', 21 | 'menu.form.step-form.confirm': '分步表單(確認轉賬信息)', 22 | 'menu.form.step-form.result': '分步表單(完成)', 23 | 'menu.form.advanced-form': '高級表單', 24 | 'menu.list': '列表頁', 25 | 'menu.list.table-list': '查詢表格', 26 | 'menu.list.basic-list': '標淮列表', 27 | 'menu.list.card-list': '卡片列表', 28 | 'menu.list.search-list': '搜索列表', 29 | 'menu.list.search-list.articles': '搜索列表(文章)', 30 | 'menu.list.search-list.projects': '搜索列表(項目)', 31 | 'menu.list.search-list.applications': '搜索列表(應用)', 32 | 'menu.profile': '詳情頁', 33 | 'menu.profile.basic': '基礎詳情頁', 34 | 'menu.profile.advanced': '高級詳情頁', 35 | 'menu.result': '結果頁', 36 | 'menu.result.success': '成功頁', 37 | 'menu.result.fail': '失敗頁', 38 | 'menu.account': '個人頁', 39 | 'menu.account.center': '個人中心', 40 | 'menu.account.settings': '個人設置', 41 | 'menu.account.trigger': '觸發報錯', 42 | 'menu.account.logout': '退出登錄', 43 | 'menu.exception': '异常页', 44 | 'menu.exception.not-permission': '403', 45 | 'menu.exception.not-find': '404', 46 | 'menu.exception.server-error': '500', 47 | 'menu.exception.trigger': '触发错误', 48 | 'menu.editor': '圖形編輯器', 49 | 'menu.editor.flow': '流程編輯器', 50 | 'menu.editor.mind': '腦圖編輯器', 51 | 'menu.editor.koni': '拓撲編輯器', 52 | }; 53 | -------------------------------------------------------------------------------- /src/e2e/baseLayout.e2e.js: -------------------------------------------------------------------------------- 1 | const { uniq } = require('lodash'); 2 | const RouterConfig = require('../../config/config').default.routes; 3 | 4 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; 5 | 6 | function formatter(routes, parentPath = '') { 7 | const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); 8 | let result = []; 9 | routes.forEach((item) => { 10 | if (item.path) { 11 | result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); 12 | } 13 | if (item.routes) { 14 | result = result.concat( 15 | formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), 16 | ); 17 | } 18 | }); 19 | return uniq(result.filter((item) => !!item)); 20 | } 21 | 22 | beforeEach(async () => { 23 | await page.goto(`${BASE_URL}`); 24 | await page.evaluate(() => { 25 | localStorage.setItem('antd-pro-authority', '["admin"]'); 26 | }); 27 | }); 28 | 29 | describe('Ant Design Pro E2E test', () => { 30 | const testPage = (path) => async () => { 31 | await page.goto(`${BASE_URL}${path}`); 32 | await page.waitForSelector('footer', { 33 | timeout: 2000, 34 | }); 35 | const haveFooter = await page.evaluate( 36 | () => document.getElementsByTagName('footer').length > 0, 37 | ); 38 | expect(haveFooter).toBeTruthy(); 39 | }; 40 | 41 | const routers = formatter(RouterConfig); 42 | routers.forEach((route) => { 43 | it(`test pages ${route}`, testPage(route)); 44 | }); 45 | 46 | it('topmenu should have footer', async () => { 47 | const params = '?navTheme=light&layout=topmenu'; 48 | await page.goto(`${BASE_URL}${params}`); 49 | await page.waitForSelector('footer', { 50 | timeout: 2000, 51 | }); 52 | const haveFooter = await page.evaluate( 53 | () => document.getElementsByTagName('footer').length > 0, 54 | ); 55 | expect(haveFooter).toBeTruthy(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/Broadcast/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, 3 | // Button 4 | } from 'antd'; 5 | 6 | export type UpdateFormProps = { 7 | onCancel: (flag?: boolean) => void; 8 | onSubmit: (values: any) => void; 9 | id: string[], 10 | modalVisible: boolean; 11 | }; 12 | 13 | const layout = { 14 | wrapperCol: { span: 20 }, 15 | }; 16 | 17 | const { TextArea } = Input; 18 | 19 | const Detail: React.FC = (props) => { 20 | const [form] = Form.useForm(); 21 | const ids = props.id 22 | console.log(`ids:`, ids, props.modalVisible) 23 | const handleSubmit = async() => { 24 | await form.validateFields() 25 | const values = await form.getFieldsValue() 26 | props.onSubmit(values) 27 | } 28 | 29 | const content = ( 30 |
36 | 40 |