├── .gitignore ├── LICENSE ├── README.md ├── conf ├── app.conf └── prometheus.yml ├── controllers ├── base.go └── prometheus.go ├── frontend ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintrc.js ├── README.md ├── config │ ├── config.js │ ├── defaultSettings.js │ ├── plugin.config.js │ └── themePluginConfig.js ├── jest-puppeteer.config.js ├── jest.config.js ├── jsconfig.json ├── mock │ ├── notices.js │ ├── prometheus.js │ ├── route.js │ └── user.js ├── package.json ├── public │ ├── favicon.png │ └── icons │ │ ├── icon-128x128.png │ │ ├── icon-192x192.png │ │ └── icon-512x512.png ├── src │ ├── assets │ │ └── logo.svg │ ├── components │ │ ├── Authorized │ │ │ ├── Authorized.jsx │ │ │ ├── AuthorizedRoute.jsx │ │ │ ├── CheckPermissions.jsx │ │ │ ├── PromiseRender.jsx │ │ │ ├── Secured.jsx │ │ │ ├── index.jsx │ │ │ └── renderAuthorize.js │ │ ├── GlobalHeader │ │ │ ├── AvatarDropdown.jsx │ │ │ ├── NoticeIconView.jsx │ │ │ ├── RightContent.jsx │ │ │ └── index.less │ │ ├── HeaderDropdown │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── HeaderSearch │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── NoticeIcon │ │ │ ├── NoticeList.jsx │ │ │ ├── NoticeList.less │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── PageLoading │ │ │ └── index.jsx │ │ └── SelectLang │ │ │ ├── index.jsx │ │ │ └── index.less │ ├── e2e │ │ ├── __mocks__ │ │ │ └── antd-pro-merge-less.js │ │ ├── baseLayout.e2e.js │ │ └── topMenu.e2e.js │ ├── global.jsx │ ├── global.less │ ├── layouts │ │ ├── BasicLayout.jsx │ │ ├── BlankLayout.jsx │ │ ├── SecurityLayout.jsx │ │ ├── UserLayout.jsx │ │ └── UserLayout.less │ ├── locales │ │ ├── en-US.js │ │ ├── en-US │ │ │ ├── component.js │ │ │ ├── globalHeader.js │ │ │ ├── menu.js │ │ │ ├── pwa.js │ │ │ ├── settingDrawer.js │ │ │ └── settings.js │ │ ├── pt-BR.js │ │ ├── pt-BR │ │ │ ├── component.js │ │ │ ├── globalHeader.js │ │ │ ├── menu.js │ │ │ ├── pwa.js │ │ │ ├── settingDrawer.js │ │ │ └── settings.js │ │ ├── zh-CN.js │ │ ├── zh-CN │ │ │ ├── component.js │ │ │ ├── globalHeader.js │ │ │ ├── menu.js │ │ │ ├── pwa.js │ │ │ ├── settingDrawer.js │ │ │ └── settings.js │ │ ├── zh-TW.js │ │ └── zh-TW │ │ │ ├── component.js │ │ │ ├── globalHeader.js │ │ │ ├── menu.js │ │ │ ├── pwa.js │ │ │ ├── settingDrawer.js │ │ │ └── settings.js │ ├── manifest.json │ ├── models │ │ ├── global.js │ │ ├── login.js │ │ ├── prometheus.js │ │ ├── setting.js │ │ └── user.js │ ├── pages │ │ ├── 404.jsx │ │ ├── Admin.jsx │ │ ├── Authorized.jsx │ │ ├── Welcome.jsx │ │ ├── Welcome.less │ │ ├── account │ │ │ ├── center │ │ │ │ ├── Center.less │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ │ ├── Applications │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── ArticleListContent │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── Articles │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── AvatarList │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ └── Projects │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ └── service.js │ │ │ └── settings │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ ├── BaseView.less │ │ │ │ ├── GeographicView.jsx │ │ │ │ ├── GeographicView.less │ │ │ │ ├── PhoneView.jsx │ │ │ │ ├── PhoneView.less │ │ │ │ ├── base.jsx │ │ │ │ ├── binding.jsx │ │ │ │ ├── notification.jsx │ │ │ │ └── security.jsx │ │ │ │ ├── data.d.ts │ │ │ │ ├── geographic │ │ │ │ ├── city.json │ │ │ │ └── province.json │ │ │ │ ├── index.jsx │ │ │ │ ├── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ ├── dashboard │ │ │ ├── analysis │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ │ ├── Charts │ │ │ │ │ │ ├── Bar │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── ChartCard │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── Field │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── Gauge │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── MiniArea │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── MiniBar │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── MiniProgress │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── Pie │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── TagCloud │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── TimelineChart │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── WaterWave │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── autoHeight.jsx │ │ │ │ │ │ ├── bizcharts.d.ts │ │ │ │ │ │ ├── bizcharts.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── IntroduceRow.jsx │ │ │ │ │ ├── NumberInfo │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── OfflineData.jsx │ │ │ │ │ ├── PageLoading │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── ProportionSales.jsx │ │ │ │ │ ├── SalesCard.jsx │ │ │ │ │ ├── TopSearch.jsx │ │ │ │ │ └── Trend │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── pt-BR.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ │ ├── model.jsx │ │ │ │ ├── service.jsx │ │ │ │ ├── style.less │ │ │ │ └── utils │ │ │ │ │ ├── Yuan.jsx │ │ │ │ │ ├── utils.js │ │ │ │ │ └── utils.less │ │ │ ├── monitor │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ │ ├── ActiveChart │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ └── Charts │ │ │ │ │ │ ├── Gauge │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── Map │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── MiniArea │ │ │ │ │ │ └── index.jsx │ │ │ │ │ │ ├── Pie │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── TagCloud │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── WaterWave │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── autoHeight.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── pt-BR.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ │ └── workplace │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ ├── EditableLinkGroup │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ └── Radar │ │ │ │ │ ├── autoHeight.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ ├── document.ejs │ │ ├── editor │ │ │ ├── flow │ │ │ │ ├── common │ │ │ │ │ └── IconFont │ │ │ │ │ │ └── index.js │ │ │ │ ├── components │ │ │ │ │ ├── EditorContextMenu │ │ │ │ │ │ ├── FlowContextMenu.jsx │ │ │ │ │ │ ├── KoniContextMenu.jsx │ │ │ │ │ │ ├── MenuItem.jsx │ │ │ │ │ │ ├── MindContextMenu.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorDetailPanel │ │ │ │ │ │ ├── DetailForm.jsx │ │ │ │ │ │ ├── FlowDetailPanel.jsx │ │ │ │ │ │ ├── KoniDetailPanel.jsx │ │ │ │ │ │ ├── MindDetailPanel.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorItemPanel │ │ │ │ │ │ ├── FlowItemPanel.jsx │ │ │ │ │ │ ├── KoniItemPanel.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorMinimap │ │ │ │ │ │ └── index.jsx │ │ │ │ │ └── EditorToolbar │ │ │ │ │ │ ├── FlowToolbar.jsx │ │ │ │ │ │ ├── KoniToolbar.jsx │ │ │ │ │ │ ├── MindToolbar.jsx │ │ │ │ │ │ ├── ToolbarButton.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ ├── index.jsx │ │ │ │ ├── index.less │ │ │ │ └── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ └── zh-CN.js │ │ │ ├── koni │ │ │ │ ├── common │ │ │ │ │ └── IconFont │ │ │ │ │ │ └── index.js │ │ │ │ ├── components │ │ │ │ │ ├── EditorContextMenu │ │ │ │ │ │ ├── FlowContextMenu.jsx │ │ │ │ │ │ ├── KoniContextMenu.jsx │ │ │ │ │ │ ├── MenuItem.jsx │ │ │ │ │ │ ├── MindContextMenu.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorDetailPanel │ │ │ │ │ │ ├── DetailForm.jsx │ │ │ │ │ │ ├── FlowDetailPanel.jsx │ │ │ │ │ │ ├── KoniDetailPanel.jsx │ │ │ │ │ │ ├── MindDetailPanel.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorItemPanel │ │ │ │ │ │ ├── FlowItemPanel.jsx │ │ │ │ │ │ ├── KoniItemPanel.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── EditorMinimap │ │ │ │ │ │ └── index.jsx │ │ │ │ │ └── EditorToolbar │ │ │ │ │ │ ├── FlowToolbar.jsx │ │ │ │ │ │ ├── KoniToolbar.jsx │ │ │ │ │ │ ├── MindToolbar.jsx │ │ │ │ │ │ ├── ToolbarButton.jsx │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ ├── index.jsx │ │ │ │ ├── index.less │ │ │ │ └── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ └── zh-CN.js │ │ │ └── mind │ │ │ │ ├── common │ │ │ │ └── IconFont │ │ │ │ │ └── index.js │ │ │ │ ├── components │ │ │ │ ├── EditorContextMenu │ │ │ │ │ ├── FlowContextMenu.jsx │ │ │ │ │ ├── KoniContextMenu.jsx │ │ │ │ │ ├── MenuItem.jsx │ │ │ │ │ ├── MindContextMenu.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── EditorDetailPanel │ │ │ │ │ ├── DetailForm.jsx │ │ │ │ │ ├── FlowDetailPanel.jsx │ │ │ │ │ ├── KoniDetailPanel.jsx │ │ │ │ │ ├── MindDetailPanel.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── EditorItemPanel │ │ │ │ │ ├── FlowItemPanel.jsx │ │ │ │ │ ├── KoniItemPanel.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── EditorMinimap │ │ │ │ │ └── index.jsx │ │ │ │ └── EditorToolbar │ │ │ │ │ ├── FlowToolbar.jsx │ │ │ │ │ ├── KoniToolbar.jsx │ │ │ │ │ ├── MindToolbar.jsx │ │ │ │ │ ├── ToolbarButton.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── index.jsx │ │ │ │ ├── index.less │ │ │ │ ├── locales │ │ │ │ ├── en-US.js │ │ │ │ └── zh-CN.js │ │ │ │ └── worldCup2018.json │ │ ├── exception │ │ │ ├── 403 │ │ │ │ ├── index.jsx │ │ │ │ └── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── pt-BR.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ ├── 404 │ │ │ │ ├── index.jsx │ │ │ │ └── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── pt-BR.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ └── 500 │ │ │ │ ├── index.jsx │ │ │ │ └── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── pt-BR.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ ├── form │ │ │ ├── advanced-form │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ │ ├── FooterToolbar │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ └── index.less │ │ │ │ │ └── TableForm.jsx │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ │ ├── basic-form │ │ │ │ ├── _mock.js │ │ │ │ ├── index.jsx │ │ │ │ ├── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── pt-BR.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ │ └── step-form │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ ├── Step1 │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── Step2 │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ └── Step3 │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ ├── index.jsx │ │ │ │ ├── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── pt-BR.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ ├── list │ │ │ ├── basic-list │ │ │ │ ├── _mock.js │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ ├── style.less │ │ │ │ └── utils │ │ │ │ │ └── utils.less │ │ │ ├── card-list │ │ │ │ ├── _mock.js │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ ├── style.less │ │ │ │ └── utils │ │ │ │ │ └── utils.less │ │ │ ├── search │ │ │ │ ├── applications │ │ │ │ │ ├── _mock.js │ │ │ │ │ ├── components │ │ │ │ │ │ ├── StandardFormRow │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ └── TagSelect │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── data.d.ts │ │ │ │ │ ├── index.jsx │ │ │ │ │ ├── model.js │ │ │ │ │ ├── service.js │ │ │ │ │ ├── style.less │ │ │ │ │ └── utils │ │ │ │ │ │ └── utils.less │ │ │ │ ├── articles │ │ │ │ │ ├── _mock.js │ │ │ │ │ ├── components │ │ │ │ │ │ ├── ArticleListContent │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── StandardFormRow │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ └── TagSelect │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── data.d.ts │ │ │ │ │ ├── index.jsx │ │ │ │ │ ├── model.js │ │ │ │ │ ├── service.js │ │ │ │ │ └── style.less │ │ │ │ ├── index.jsx │ │ │ │ ├── projects │ │ │ │ │ ├── _mock.js │ │ │ │ │ ├── components │ │ │ │ │ │ ├── AvatarList │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ ├── StandardFormRow │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ │ └── TagSelect │ │ │ │ │ │ │ ├── index.jsx │ │ │ │ │ │ │ └── index.less │ │ │ │ │ ├── data.d.ts │ │ │ │ │ ├── index.jsx │ │ │ │ │ ├── model.js │ │ │ │ │ ├── service.js │ │ │ │ │ ├── style.less │ │ │ │ │ └── utils │ │ │ │ │ │ └── utils.less │ │ │ │ └── utils │ │ │ │ │ └── utils.less │ │ │ └── table-list │ │ │ │ ├── _mock.js │ │ │ │ ├── components │ │ │ │ ├── CreateForm.jsx │ │ │ │ └── UpdateForm.jsx │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ └── service.js │ │ ├── profile │ │ │ ├── advanced │ │ │ │ ├── _mock.js │ │ │ │ ├── data.d.ts │ │ │ │ ├── index copy.jsx │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ │ └── basic │ │ │ │ ├── _mock.js │ │ │ │ ├── data.d.ts │ │ │ │ ├── index.jsx │ │ │ │ ├── model.js │ │ │ │ ├── service.js │ │ │ │ └── style.less │ │ ├── prometheus │ │ │ ├── _mock.js │ │ │ ├── components │ │ │ │ ├── MonitorCodeMirror │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ │ └── MonitorEdit │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.less │ │ │ ├── data.d.ts │ │ │ ├── index.jsx │ │ │ ├── model.js │ │ │ ├── service.js │ │ │ └── style.less │ │ ├── result │ │ │ ├── fail │ │ │ │ ├── index.jsx │ │ │ │ ├── index.less │ │ │ │ └── locales │ │ │ │ │ ├── en-US.js │ │ │ │ │ ├── zh-CN.js │ │ │ │ │ └── zh-TW.js │ │ │ └── success │ │ │ │ ├── index.jsx │ │ │ │ ├── index.less │ │ │ │ └── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ └── user │ │ │ ├── login │ │ │ ├── _mock.js │ │ │ ├── components │ │ │ │ └── Login │ │ │ │ │ ├── LoginContext.jsx │ │ │ │ │ ├── LoginItem.jsx │ │ │ │ │ ├── LoginSubmit.jsx │ │ │ │ │ ├── LoginTab.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ ├── index.less │ │ │ │ │ └── map.jsx │ │ │ ├── index.jsx │ │ │ ├── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ │ ├── model.js │ │ │ ├── service.js │ │ │ ├── style.less │ │ │ └── utils │ │ │ │ └── utils.js │ │ │ ├── register-result │ │ │ ├── index.jsx │ │ │ ├── locales │ │ │ │ ├── en-US.js │ │ │ │ ├── zh-CN.js │ │ │ │ └── zh-TW.js │ │ │ └── style.less │ │ │ └── register │ │ │ ├── _mock.js │ │ │ ├── index.jsx │ │ │ ├── locales │ │ │ ├── en-US.js │ │ │ ├── zh-CN.js │ │ │ └── zh-TW.js │ │ │ ├── model.js │ │ │ ├── service.js │ │ │ └── style.less │ ├── service-worker.js │ ├── services │ │ ├── login.js │ │ ├── prometheus.js │ │ └── user.js │ └── utils │ │ ├── Authorized.js │ │ ├── authority.js │ │ ├── authority.test.js │ │ ├── request.js │ │ ├── utils.js │ │ ├── utils.less │ │ └── utils.test.js └── tests │ ├── run-tests.js │ └── setupTests.js ├── go.mod ├── go.sum ├── main.go ├── models └── prometheus.go ├── routers ├── commentsRouter_controllers.go └── router.go ├── services ├── base.go └── prometheus.go └── utils ├── const.go ├── date.go ├── date_test.go ├── errorcode.go ├── errorcode_test.go ├── helper.go └── helper_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lastupdate.tmp 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 vermouth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prometheusPro 2 | prometheus配置管理、alertmanager规则、报警事件管理。使用 beego + ant-design 3 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = proBackend 2 | httpport = 8080 3 | runmode = dev 4 | configFilePath = /Users/xuyasong/go/src/prometheusPro/proBackend/conf/prometheus.yml -------------------------------------------------------------------------------- /frontend/.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 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history -------------------------------------------------------------------------------- /frontend/.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 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/.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 | -------------------------------------------------------------------------------- /frontend/.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 -------------------------------------------------------------------------------- /frontend/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Ant Design Pro 2 | 3 | This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use. 4 | 5 | ## Environment Prepare 6 | 7 | Install `node_modules`: 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | yarn 17 | ``` 18 | 19 | ## Provided Scripts 20 | 21 | Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test. 22 | 23 | Scripts provided in `package.json`. It's safe to modify or add additional script: 24 | 25 | ### Start project 26 | 27 | ```bash 28 | npm start 29 | ``` 30 | 31 | ### Build project 32 | 33 | ```bash 34 | npm run build 35 | ``` 36 | 37 | ### Check code style 38 | 39 | ```bash 40 | npm run lint 41 | ``` 42 | 43 | You can also use script to auto fix some lint error: 44 | 45 | ```bash 46 | npm run lint:fix 47 | ``` 48 | 49 | ### Test code 50 | 51 | ```bash 52 | npm test 53 | ``` 54 | 55 | ## More 56 | 57 | You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). 58 | -------------------------------------------------------------------------------- /frontend/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "navTheme": "light", 3 | "primaryColor": "daybreak", 4 | "layout": "sidemenu", 5 | "contentWidth": "Fluid", 6 | "fixedHeader": true, 7 | "autoHideHeader": false, 8 | "fixSiderbar": true, 9 | "menu": { 10 | "locale": true 11 | }, 12 | "title": "监控中心", 13 | "pwa": false, 14 | "iconfontUrl": "" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | // ps https://github.com/GoogleChrome/puppeteer/issues/3120 2 | module.exports = { 3 | launch: { 4 | args: [ 5 | '--disable-gpu', 6 | '--disable-dev-shm-usage', 7 | '--no-first-run', 8 | '--no-zygote', 9 | '--no-sandbox', 10 | ], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | preset: 'jest-puppeteer', 4 | extraSetupFiles: ['./tests/setupTests.js'], 5 | globals: { 6 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 7 | localStorage: null, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/mock/route.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/auth_routes': { 3 | '/form/advanced-form': { 4 | authority: ['admin', 'user'], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasongxu/prometheusPro/528cf8d7000423a2ca355da7d729f0c375892700/frontend/public/favicon.png -------------------------------------------------------------------------------- /frontend/public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasongxu/prometheusPro/528cf8d7000423a2ca355da7d729f0c375892700/frontend/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /frontend/public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasongxu/prometheusPro/528cf8d7000423a2ca355da7d729f0c375892700/frontend/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /frontend/public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasongxu/prometheusPro/528cf8d7000423a2ca355da7d729f0c375892700/frontend/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /frontend/src/components/Authorized/Authorized.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Result } from 'antd'; 3 | import check from './CheckPermissions'; 4 | 5 | const Authorized = ({ 6 | children, 7 | authority, 8 | noMatch = ( 9 | 14 | ), 15 | }) => { 16 | const childrenRender = typeof children === 'undefined' ? null : children; 17 | const dom = check(authority, childrenRender, noMatch); 18 | return <>{dom}; 19 | }; 20 | 21 | export default Authorized; 22 | -------------------------------------------------------------------------------- /frontend/src/components/Authorized/AuthorizedRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'umi'; 2 | import React from 'react'; 3 | import Authorized from './Authorized'; 4 | 5 | const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => ( 6 | ( 12 | 17 | )} 18 | /> 19 | } 20 | > 21 | (Component ? : render(props))} /> 22 | 23 | ); 24 | 25 | export default AuthorizedRoute; 26 | -------------------------------------------------------------------------------- /frontend/src/components/Authorized/index.jsx: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import Secured from './Secured'; 3 | import check from './CheckPermissions'; 4 | import renderAuthorize from './renderAuthorize'; 5 | Authorized.Secured = Secured; 6 | Authorized.check = check; 7 | const RenderAuthorize = renderAuthorize(Authorized); 8 | export default RenderAuthorize; 9 | -------------------------------------------------------------------------------- /frontend/src/components/Authorized/renderAuthorize.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | 3 | /* eslint-disable import/no-mutable-exports */ 4 | let CURRENT = 'NULL'; 5 | 6 | /** 7 | * use authority or getAuthority 8 | * @param {string|()=>String} currentAuthority 9 | */ 10 | const renderAuthorize = Authorized => currentAuthority => { 11 | if (currentAuthority) { 12 | if (typeof currentAuthority === 'function') { 13 | CURRENT = currentAuthority(); 14 | } 15 | 16 | if ( 17 | Object.prototype.toString.call(currentAuthority) === '[object String]' || 18 | Array.isArray(currentAuthority) 19 | ) { 20 | CURRENT = currentAuthority; 21 | } 22 | } else { 23 | CURRENT = 'NULL'; 24 | } 25 | 26 | return Authorized; 27 | }; 28 | 29 | export { CURRENT }; 30 | export default Authorized => renderAuthorize(Authorized); 31 | -------------------------------------------------------------------------------- /frontend/src/components/GlobalHeader/RightContent.jsx: -------------------------------------------------------------------------------- 1 | import { QuestionCircleOutlined } from '@ant-design/icons'; 2 | import { Tooltip } from 'antd'; 3 | import React from 'react'; 4 | import { connect } from 'dva'; 5 | import Avatar from './AvatarDropdown'; 6 | import HeaderSearch from '../HeaderSearch'; 7 | import SelectLang from '../SelectLang'; 8 | import styles from './index.less'; 9 | import NoticeIconView from './NoticeIconView'; 10 | 11 | const GlobalHeaderRight = props => { 12 | const { theme, layout } = props; 13 | let className = styles.right; 14 | 15 | if (theme === 'dark' && layout === 'topmenu') { 16 | className = `${styles.right} ${styles.dark}`; 17 | } 18 | 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default connect(({ settings }) => ({ 28 | theme: settings.navTheme, 29 | layout: settings.layout, 30 | }))(GlobalHeaderRight); 31 | -------------------------------------------------------------------------------- /frontend/src/components/HeaderDropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import { Dropdown } from 'antd'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => ( 7 | 8 | ); 9 | 10 | export default HeaderDropdown; 11 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/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-scroll { 25 | text-align: center; 26 | } 27 | .ant-tabs-bar { 28 | margin-bottom: 0; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/components/PageLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import { PageLoading } from '@ant-design/pro-layout'; // loading components from code split 2 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 3 | 4 | export default PageLoading; 5 | -------------------------------------------------------------------------------- /frontend/src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .menu { 4 | :global(.anticon) { 5 | margin-right: 8px; 6 | } 7 | :global(.ant-dropdown-menu-item) { 8 | min-width: 160px; 9 | } 10 | } 11 | 12 | .dropDown { 13 | line-height: @layout-header-height; 14 | vertical-align: top; 15 | cursor: pointer; 16 | > span { 17 | font-size: 16px !important; 18 | transform: none !important; 19 | svg { 20 | position: relative; 21 | top: -1px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/e2e/__mocks__/antd-pro-merge-less.js: -------------------------------------------------------------------------------- 1 | export default undefined; 2 | -------------------------------------------------------------------------------- /frontend/src/e2e/topMenu.e2e.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; 2 | 3 | describe('Homepage', () => { 4 | it('topmenu should have footer', async () => { 5 | const params = '?navTheme=light&layout=topmenu'; 6 | await page.goto(`${BASE_URL}${params}`); 7 | await page.waitForSelector('footer', { 8 | timeout: 2000, 9 | }); 10 | const haveFooter = await page.evaluate( 11 | () => document.getElementsByTagName('footer').length > 0, 12 | ); 13 | expect(haveFooter).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/src/global.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | html, 4 | body, 5 | #root { 6 | height: 100%; 7 | } 8 | 9 | .colorWeak { 10 | filter: invert(80%); 11 | } 12 | 13 | .ant-layout { 14 | min-height: 100vh; 15 | } 16 | 17 | canvas { 18 | display: block; 19 | } 20 | 21 | body { 22 | text-rendering: optimizeLegibility; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | ul, 28 | ol { 29 | list-style: none; 30 | } 31 | 32 | @media (max-width: @screen-xs) { 33 | .ant-table { 34 | width: 100%; 35 | overflow-x: auto; 36 | &-thead > tr, 37 | &-tbody > tr { 38 | > th, 39 | > td { 40 | white-space: pre; 41 | > span { 42 | display: block; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | // 兼容IE11 50 | @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { 51 | body .ant-design-pro > .ant-layout { 52 | min-height: 100vh; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/layouts/BlankLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Layout = ({ children }) => <>{children}; 4 | 5 | export default Layout; 6 | -------------------------------------------------------------------------------- /frontend/src/layouts/SecurityLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { PageLoading } from '@ant-design/pro-layout'; 4 | import { Redirect } from 'umi'; 5 | import { stringify } from 'querystring'; 6 | 7 | class SecurityLayout extends React.Component { 8 | state = { 9 | isReady: false, 10 | }; 11 | 12 | componentDidMount() { 13 | this.setState({ 14 | isReady: true, 15 | }); 16 | const { dispatch } = this.props; 17 | 18 | if (dispatch) { 19 | dispatch({ 20 | type: 'user/fetchCurrent', 21 | }); 22 | } 23 | } 24 | 25 | render() { 26 | const { isReady } = this.state; 27 | const { children, loading, currentUser } = this.props; // You can replace it to your authentication rule (such as check token exists) 28 | // 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在) 29 | 30 | const isLogin = currentUser && currentUser.userid; 31 | const queryString = stringify({ 32 | redirect: window.location.href, 33 | }); 34 | 35 | if ((!isLogin && loading) || !isReady) { 36 | return ; 37 | } 38 | 39 | if (!isLogin) { 40 | return ; 41 | } 42 | 43 | return children; 44 | } 45 | } 46 | 47 | export default connect(({ user, loading }) => ({ 48 | currentUser: user.currentUser, 49 | loading: loading.models.user, 50 | }))(SecurityLayout); 51 | -------------------------------------------------------------------------------- /frontend/src/locales/en-US.js: -------------------------------------------------------------------------------- 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 | export default { 8 | 'navBar.lang': 'Languages', 9 | 'layout.user.link.help': 'Help', 10 | 'layout.user.link.privacy': 'Privacy', 11 | 'layout.user.link.terms': 'Terms', 12 | 'app.preview.down.block': 'Download this page to your local project', 13 | 'app.welcome.link.fetch-blocks': 'Get all block', 14 | 'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development', 15 | ...globalHeader, 16 | ...menu, 17 | ...settingDrawer, 18 | ...settings, 19 | ...pwa, 20 | ...component, 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/locales/en-US/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expand', 3 | 'component.tagSelect.collapse': 'Collapse', 4 | 'component.tagSelect.all': 'All', 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/locales/en-US/globalHeader.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/en-US/pwa.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/pt-BR.js: -------------------------------------------------------------------------------- 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 | export default { 8 | 'navBar.lang': 'Idiomas', 9 | 'layout.user.link.help': 'ajuda', 10 | 'layout.user.link.privacy': 'política de privacidade', 11 | 'layout.user.link.terms': 'termos de serviços', 12 | 'app.preview.down.block': 'Download this page to your local project', 13 | ...globalHeader, 14 | ...menu, 15 | ...settingDrawer, 16 | ...settings, 17 | ...pwa, 18 | ...component, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/locales/pt-BR/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expandir', 3 | 'component.tagSelect.collapse': 'Diminuir', 4 | 'component.tagSelect.all': 'Todas', 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/locales/pt-BR/globalHeader.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/pt-BR/pwa.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-CN.js: -------------------------------------------------------------------------------- 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 | export default { 8 | 'navBar.lang': '语言', 9 | 'layout.user.link.help': '帮助', 10 | 'layout.user.link.privacy': '隐私', 11 | 'layout.user.link.terms': '条款', 12 | 'app.preview.down.block': '下载此页面到本地项目', 13 | 'app.welcome.link.fetch-blocks': '获取全部区块', 14 | 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面', 15 | ...globalHeader, 16 | ...menu, 17 | ...settingDrawer, 18 | ...settings, 19 | ...pwa, 20 | ...component, 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-CN/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展开', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-CN/globalHeader.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-CN/pwa.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-CN/settingDrawer.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-TW.js: -------------------------------------------------------------------------------- 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 | export default { 8 | 'navBar.lang': '語言', 9 | 'layout.user.link.help': '幫助', 10 | 'layout.user.link.privacy': '隱私', 11 | 'layout.user.link.terms': '條款', 12 | 'app.preview.down.block': '下載此頁面到本地項目', 13 | ...globalHeader, 14 | ...menu, 15 | ...settingDrawer, 16 | ...settings, 17 | ...pwa, 18 | ...component, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-TW/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展開', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-TW/globalHeader.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-TW/pwa.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/src/locales/zh-TW/settingDrawer.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /frontend/src/models/setting.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '../../config/defaultSettings'; 2 | 3 | const updateColorWeak = colorWeak => { 4 | const root = document.getElementById('root'); 5 | 6 | if (root) { 7 | root.className = colorWeak ? 'colorWeak' : ''; 8 | } 9 | }; 10 | 11 | const SettingModel = { 12 | namespace: 'settings', 13 | state: defaultSettings, 14 | reducers: { 15 | changeSetting(state = defaultSettings, { payload }) { 16 | const { colorWeak, contentWidth } = payload; 17 | 18 | if (state.contentWidth !== contentWidth && window.dispatchEvent) { 19 | window.dispatchEvent(new Event('resize')); 20 | } 21 | 22 | updateColorWeak(!!colorWeak); 23 | return { ...state, ...payload }; 24 | }, 25 | }, 26 | }; 27 | export default SettingModel; 28 | -------------------------------------------------------------------------------- /frontend/src/models/user.js: -------------------------------------------------------------------------------- 1 | import { queryCurrent, query as queryUsers } from '@/services/user'; 2 | const UserModel = { 3 | namespace: 'user', 4 | state: { 5 | currentUser: {}, 6 | }, 7 | effects: { 8 | *fetch(_, { call, put }) { 9 | const response = yield call(queryUsers); 10 | yield put({ 11 | type: 'save', 12 | payload: response, 13 | }); 14 | }, 15 | 16 | *fetchCurrent(_, { call, put }) { 17 | const response = yield call(queryCurrent); 18 | yield put({ 19 | type: 'saveCurrentUser', 20 | payload: response, 21 | }); 22 | }, 23 | }, 24 | reducers: { 25 | saveCurrentUser(state, action) { 26 | return { ...state, currentUser: action.payload || {} }; 27 | }, 28 | 29 | changeNotifyCount( 30 | state = { 31 | currentUser: {}, 32 | }, 33 | action, 34 | ) { 35 | return { 36 | ...state, 37 | currentUser: { 38 | ...state.currentUser, 39 | notifyCount: action.payload.totalCount, 40 | unreadCount: action.payload.unreadCount, 41 | }, 42 | }; 43 | }, 44 | }, 45 | }; 46 | export default UserModel; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/404.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from 'antd'; 2 | import React from 'react'; 3 | import router from 'umi/router'; 4 | 5 | const NoFoundPage = () => ( 6 | router.push('/')}> 12 | Back Home 13 | 14 | } 15 | > 16 | ); 17 | 18 | export default NoFoundPage; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/Admin.jsx: -------------------------------------------------------------------------------- 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 | export default () => ( 6 | 7 | 8 | 18 | 24 | Ant Design Pro You 25 | 26 | 27 |

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

39 |
40 | ); 41 | -------------------------------------------------------------------------------- /frontend/src/pages/Authorized.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Redirect from 'umi/redirect'; 3 | import { connect } from 'dva'; 4 | import Authorized from '@/utils/Authorized'; 5 | import { getRouteAuthority } from '@/utils/utils'; 6 | 7 | const AuthComponent = ({ 8 | children, 9 | route = { 10 | routes: [], 11 | }, 12 | location = { 13 | pathname: '', 14 | }, 15 | user, 16 | }) => { 17 | const { currentUser } = user; 18 | const { routes = [] } = route; 19 | const isLogin = currentUser && currentUser.name; 20 | return ( 21 | : } 24 | > 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | export default connect(({ user }) => ({ 31 | user, 32 | }))(AuthComponent); 33 | -------------------------------------------------------------------------------- /frontend/src/pages/Welcome.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .pre { 4 | margin: 12px 0; 5 | padding: 12px 20px; 6 | background: @input-bg; 7 | box-shadow: @card-shadow; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/Applications/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .filterCardList { 4 | margin-bottom: -24px; 5 | :global { 6 | .ant-card-meta-content { 7 | margin-top: 0; 8 | } 9 | // disabled white space 10 | .ant-card-meta-avatar { 11 | font-size: 0; 12 | } 13 | 14 | .ant-list .ant-list-item-content-single { 15 | max-width: 100%; 16 | } 17 | } 18 | .cardInfo { 19 | margin-top: 16px; 20 | margin-left: 40px; 21 | zoom: 1; 22 | &::before, 23 | &::after { 24 | display: table; 25 | content: ' '; 26 | } 27 | &::after { 28 | clear: both; 29 | height: 0; 30 | font-size: 0; 31 | visibility: hidden; 32 | } 33 | & > div { 34 | position: relative; 35 | float: left; 36 | width: 50%; 37 | text-align: left; 38 | p { 39 | margin: 0; 40 | font-size: 24px; 41 | line-height: 32px; 42 | } 43 | p:first-child { 44 | margin-bottom: 4px; 45 | color: @text-color-secondary; 46 | font-size: 12px; 47 | line-height: 20px; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/ArticleListContent/index.jsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from 'antd'; 2 | import React from 'react'; 3 | import moment from 'moment'; 4 | import styles from './index.less'; 5 | 6 | const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => ( 7 |
8 |
{content}
9 |
10 | 11 | {owner} 发布在 {href} 12 | {moment(updatedAt).format('YYYY-MM-DD HH:mm')} 13 |
14 |
15 | ); 16 | 17 | export default ArticleListContent; 18 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/ArticleListContent/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .listContent { 4 | .description { 5 | max-width: 720px; 6 | line-height: 22px; 7 | } 8 | .extra { 9 | margin-top: 16px; 10 | color: @text-color-secondary; 11 | line-height: 22px; 12 | & > :global(.ant-avatar) { 13 | position: relative; 14 | top: 1px; 15 | width: 20px; 16 | height: 20px; 17 | margin-right: 8px; 18 | vertical-align: top; 19 | } 20 | & > em { 21 | margin-left: 16px; 22 | color: @disabled-color; 23 | font-style: normal; 24 | } 25 | } 26 | } 27 | 28 | @media screen and (max-width: @screen-xs) { 29 | .listContent { 30 | .extra { 31 | & > em { 32 | display: block; 33 | margin-top: 8px; 34 | margin-left: 0; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/Articles/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .articleList { 4 | :global { 5 | .ant-list-item:first-child { 6 | padding-top: 0; 7 | } 8 | } 9 | } 10 | a.listItemMetaTitle { 11 | color: @heading-color; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .avatarList { 4 | display: inline-block; 5 | ul { 6 | display: inline-block; 7 | margin-left: 8px; 8 | font-size: 0; 9 | } 10 | } 11 | 12 | .avatarItem { 13 | display: inline-block; 14 | width: @avatar-size-base; 15 | height: @avatar-size-base; 16 | margin-left: -8px; 17 | font-size: @font-size-base; 18 | :global { 19 | .ant-avatar { 20 | border: 1px solid #fff; 21 | } 22 | } 23 | } 24 | 25 | .avatarItemLarge { 26 | width: @avatar-size-lg; 27 | height: @avatar-size-lg; 28 | } 29 | 30 | .avatarItemSmall { 31 | width: @avatar-size-sm; 32 | height: @avatar-size-sm; 33 | } 34 | 35 | .avatarItemMini { 36 | width: 20px; 37 | height: 20px; 38 | :global { 39 | .ant-avatar { 40 | width: 20px; 41 | height: 20px; 42 | line-height: 20px; 43 | 44 | .ant-avatar-string { 45 | font-size: 12px; 46 | line-height: 18px; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/components/Projects/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .coverCardList { 4 | margin-bottom: -24px; 5 | 6 | .card { 7 | :global { 8 | .ant-card-meta-title { 9 | margin-bottom: 4px; 10 | & > a { 11 | display: inline-block; 12 | max-width: 100%; 13 | color: @heading-color; 14 | } 15 | } 16 | .ant-card-meta-description { 17 | height: 44px; 18 | overflow: hidden; 19 | line-height: 22px; 20 | } 21 | } 22 | 23 | &:hover { 24 | :global { 25 | .ant-card-meta-title > a { 26 | color: @primary-color; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .cardItemContent { 33 | display: flex; 34 | height: 20px; 35 | margin-top: 16px; 36 | margin-bottom: -4px; 37 | line-height: 20px; 38 | & > span { 39 | flex: 1; 40 | color: @text-color-secondary; 41 | font-size: 12px; 42 | } 43 | .avatarList { 44 | flex: 0 1 auto; 45 | } 46 | } 47 | .cardList { 48 | margin-top: 24px; 49 | } 50 | 51 | :global { 52 | .ant-list .ant-list-item-content-single { 53 | max-width: 100%; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/model.js: -------------------------------------------------------------------------------- 1 | import { queryCurrent, queryFakeList } from './service'; 2 | 3 | const Model = { 4 | namespace: 'accountAndcenter', 5 | state: { 6 | currentUser: {}, 7 | list: [], 8 | }, 9 | effects: { 10 | *fetchCurrent(_, { call, put }) { 11 | const response = yield call(queryCurrent); 12 | yield put({ 13 | type: 'saveCurrentUser', 14 | payload: response, 15 | }); 16 | }, 17 | 18 | *fetch({ payload }, { call, put }) { 19 | const response = yield call(queryFakeList, payload); 20 | yield put({ 21 | type: 'queryList', 22 | payload: Array.isArray(response) ? response : [], 23 | }); 24 | }, 25 | }, 26 | reducers: { 27 | saveCurrentUser(state, action) { 28 | return { ...state, currentUser: action.payload || {} }; 29 | }, 30 | 31 | queryList(state, action) { 32 | return { ...state, list: action.payload }; 33 | }, 34 | }, 35 | }; 36 | export default Model; 37 | -------------------------------------------------------------------------------- /frontend/src/pages/account/center/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryCurrent() { 4 | return request('/api/currentUser'); 5 | } 6 | export async function queryFakeList(params) { 7 | return request('/api/fake_list', { 8 | params, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/components/BaseView.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .baseView { 4 | display: flex; 5 | padding-top: 12px; 6 | 7 | :global { 8 | .ant-legacy-form-item .ant-legacy-form-item-control-wrapper { 9 | width: 100%; 10 | } 11 | } 12 | 13 | .left { 14 | min-width: 224px; 15 | max-width: 448px; 16 | } 17 | .right { 18 | flex: 1; 19 | padding-left: 104px; 20 | .avatar_title { 21 | height: 22px; 22 | margin-bottom: 8px; 23 | color: @heading-color; 24 | font-size: @font-size-base; 25 | line-height: 22px; 26 | } 27 | .avatar { 28 | width: 144px; 29 | height: 144px; 30 | margin-bottom: 12px; 31 | overflow: hidden; 32 | img { 33 | width: 100%; 34 | } 35 | } 36 | .button_view { 37 | width: 144px; 38 | text-align: center; 39 | } 40 | } 41 | } 42 | 43 | @media screen and (max-width: @screen-xl) { 44 | .baseView { 45 | flex-direction: column-reverse; 46 | 47 | .right { 48 | display: flex; 49 | flex-direction: column; 50 | align-items: center; 51 | max-width: 448px; 52 | padding: 20px; 53 | .avatar_title { 54 | display: none; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/components/GeographicView.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .row { 4 | .item { 5 | width: 50%; 6 | max-width: 220px; 7 | } 8 | .item:first-child { 9 | width: ~'calc(50% - 8px)'; 10 | margin-right: 8px; 11 | } 12 | } 13 | 14 | @media screen and (max-width: @screen-sm) { 15 | .item:first-child { 16 | margin: 0; 17 | margin-bottom: 8px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/components/PhoneView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'antd'; 3 | import styles from './PhoneView.less'; 4 | 5 | const PhoneView = props => { 6 | const { value, onChange } = props; 7 | let values = ['', '']; 8 | 9 | if (value) { 10 | values = value.split('-'); 11 | } 12 | 13 | return ( 14 | <> 15 | { 19 | if (onChange) { 20 | onChange(`${e.target.value}-${values[1]}`); 21 | } 22 | }} 23 | /> 24 | { 27 | if (onChange) { 28 | onChange(`${values[0]}-${e.target.value}`); 29 | } 30 | }} 31 | value={values[1]} 32 | /> 33 | 34 | ); 35 | }; 36 | 37 | export default PhoneView; 38 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/components/PhoneView.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .area_code { 4 | width: 30%; 5 | max-width: 128px; 6 | margin-right: 8px; 7 | } 8 | .phone_number { 9 | width: ~'calc(70% - 8px)'; 10 | max-width: 312px; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/components/notification.jsx: -------------------------------------------------------------------------------- 1 | import { List, Switch } from 'antd'; 2 | import React, { Component, Fragment } from 'react'; 3 | 4 | class NotificationView extends Component { 5 | getData = () => { 6 | const Action = ; 7 | return [ 8 | { 9 | title: '账户密码', 10 | description: '其他用户的消息将以站内信的形式通知', 11 | actions: [Action], 12 | }, 13 | { 14 | title: '系统消息', 15 | description: '系统消息将以站内信的形式通知', 16 | actions: [Action], 17 | }, 18 | { 19 | title: '待办任务', 20 | description: '待办任务将以站内信的形式通知', 21 | actions: [Action], 22 | }, 23 | ]; 24 | }; 25 | 26 | render() { 27 | const data = this.getData(); 28 | return ( 29 | 30 | ( 34 | 35 | 36 | 37 | )} 38 | /> 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default NotificationView; 45 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface TagType { 2 | key: string; 3 | label: string; 4 | } 5 | 6 | export interface GeographicItemType { 7 | name: string; 8 | id: string; 9 | } 10 | 11 | export interface GeographicType { 12 | province: GeographicItemType; 13 | city: GeographicItemType; 14 | } 15 | 16 | export interface NoticeType { 17 | id: string; 18 | title: string; 19 | logo: string; 20 | description: string; 21 | updatedAt: string; 22 | member: string; 23 | href: string; 24 | memberLink: string; 25 | } 26 | 27 | export interface CurrentUser { 28 | name: string; 29 | avatar: string; 30 | userid: string; 31 | notice: NoticeType[]; 32 | email: string; 33 | signature: string; 34 | title: string; 35 | group: string; 36 | tags: TagType[]; 37 | notifyCount: number; 38 | unreadCount: number; 39 | country: string; 40 | geographic: GeographicType; 41 | address: string; 42 | phone: string; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/pages/account/settings/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryCurrent() { 4 | return request('/api/currentUser'); 5 | } 6 | export async function queryProvince() { 7 | return request('/api/geographic/province'); 8 | } 9 | export async function queryCity(province) { 10 | return request(`/api/geographic/city/${province}`); 11 | } 12 | export async function query() { 13 | return request('/api/users'); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/Field/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.less'; 3 | 4 | const Field = ({ label, value, ...rest }) => ( 5 |
6 | {label} 7 | {value} 8 |
9 | ); 10 | 11 | export default Field; 12 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/Field/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .field { 4 | margin: 0; 5 | overflow: hidden; 6 | white-space: nowrap; 7 | text-overflow: ellipsis; 8 | .label, 9 | .number { 10 | font-size: @font-size-base; 11 | line-height: 22px; 12 | } 13 | .number { 14 | margin-left: 8px; 15 | color: @heading-color; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/MiniBar/index.jsx: -------------------------------------------------------------------------------- 1 | import { Chart, Geom, Tooltip } from 'bizcharts'; 2 | import React from 'react'; 3 | import autoHeight from '../autoHeight'; 4 | import styles from '../index.less'; 5 | 6 | const MiniBar = props => { 7 | const { height = 0, forceFit = true, color = '#1890FF', data = [] } = props; 8 | const scale = { 9 | x: { 10 | type: 'cat', 11 | }, 12 | y: { 13 | min: 0, 14 | }, 15 | }; 16 | const padding = [36, 5, 30, 5]; 17 | const tooltip = [ 18 | 'x*y', 19 | (x, y) => ({ 20 | name: x, 21 | value: y, 22 | }), 23 | ]; // for tooltip not to be hide 24 | 25 | const chartHeight = height + 54; 26 | return ( 27 |
33 |
34 | 35 | 36 | 37 | 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default autoHeight()(MiniBar); 44 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/MiniProgress/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'antd'; 3 | import styles from './index.less'; 4 | 5 | const MiniProgress = ({ 6 | targetLabel, 7 | target, 8 | color = 'rgb(19, 194, 194)', 9 | strokeWidth, 10 | percent, 11 | }) => ( 12 |
13 | 14 |
20 | 25 | 30 |
31 |
32 |
33 |
41 |
42 |
43 | ); 44 | 45 | export default MiniProgress; 46 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/MiniProgress/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .miniProgress { 4 | position: relative; 5 | width: 100%; 6 | padding: 5px 0; 7 | .progressWrap { 8 | position: relative; 9 | background-color: @background-color-base; 10 | } 11 | .progress { 12 | width: 0; 13 | height: 100%; 14 | background-color: @primary-color; 15 | border-radius: 1px 0 0 1px; 16 | transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; 17 | } 18 | .target { 19 | position: absolute; 20 | top: 0; 21 | bottom: 0; 22 | z-index: 9; 23 | width: 20px; 24 | span { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | width: 2px; 29 | height: 4px; 30 | border-radius: 100px; 31 | } 32 | span:last-child { 33 | top: auto; 34 | bottom: 0; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/TagCloud/index.less: -------------------------------------------------------------------------------- 1 | .tagCloud { 2 | overflow: hidden; 3 | canvas { 4 | transform-origin: 0 0; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/TimelineChart/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .timelineChart { 4 | background: @component-background; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .waterWave { 4 | position: relative; 5 | display: inline-block; 6 | transform-origin: left; 7 | .text { 8 | position: absolute; 9 | top: 32px; 10 | left: 0; 11 | width: 100%; 12 | text-align: center; 13 | span { 14 | color: @text-color-secondary; 15 | font-size: 14px; 16 | line-height: 22px; 17 | } 18 | h4 { 19 | color: @heading-color; 20 | font-size: 24px; 21 | line-height: 32px; 22 | } 23 | } 24 | .waterWaveCanvasWrapper { 25 | transform: scale(0.5); 26 | transform-origin: 0 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/bizcharts.d.ts: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export = BizChart; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/bizcharts.jsx: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export default BizChart; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/index.jsx: -------------------------------------------------------------------------------- 1 | import numeral from 'numeral'; 2 | import Bar from './Bar'; 3 | import ChartCard from './ChartCard'; 4 | import Field from './Field'; 5 | import Gauge from './Gauge'; 6 | import MiniArea from './MiniArea'; 7 | import MiniBar from './MiniBar'; 8 | import MiniProgress from './MiniProgress'; 9 | import Pie from './Pie'; 10 | import TagCloud from './TagCloud'; 11 | import TimelineChart from './TimelineChart'; 12 | import WaterWave from './WaterWave'; 13 | 14 | const yuan = val => `¥ ${numeral(val).format('0,0')}`; 15 | 16 | const Charts = { 17 | yuan, 18 | Bar, 19 | Pie, 20 | Gauge, 21 | MiniBar, 22 | MiniArea, 23 | MiniProgress, 24 | ChartCard, 25 | Field, 26 | WaterWave, 27 | TagCloud, 28 | TimelineChart, 29 | }; 30 | export { 31 | Charts as default, 32 | yuan, 33 | Bar, 34 | Pie, 35 | Gauge, 36 | MiniBar, 37 | MiniArea, 38 | MiniProgress, 39 | ChartCard, 40 | Field, 41 | WaterWave, 42 | TagCloud, 43 | TimelineChart, 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Charts/index.less: -------------------------------------------------------------------------------- 1 | .miniChart { 2 | position: relative; 3 | width: 100%; 4 | .chartContent { 5 | position: absolute; 6 | bottom: -28px; 7 | width: 100%; 8 | > div { 9 | margin: 0 -5px; 10 | overflow: hidden; 11 | } 12 | } 13 | .chartLoading { 14 | position: absolute; 15 | top: 16px; 16 | left: 50%; 17 | margin-left: -7px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/PageLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; // loading components from code split 3 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 4 | 5 | export default () => ( 6 |
12 | 13 |
14 | ); 15 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Trend/index.jsx: -------------------------------------------------------------------------------- 1 | import { CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | const Trend = ({ colorful = true, reverseColor = false, flag, children, className, ...rest }) => { 7 | const classString = classNames( 8 | styles.trendItem, 9 | { 10 | [styles.trendItemGrey]: !colorful, 11 | [styles.reverseColor]: reverseColor && colorful, 12 | }, 13 | className, 14 | ); 15 | return ( 16 |
17 | {children} 18 | {flag && ( 19 | 20 | {flag === 'up' ? : } 21 | 22 | )} 23 |
24 | ); 25 | }; 26 | 27 | export default Trend; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .trendItem { 4 | display: inline-block; 5 | font-size: @font-size-base; 6 | line-height: 22px; 7 | 8 | .up, 9 | .down { 10 | position: relative; 11 | top: 1px; 12 | margin-left: 4px; 13 | span { 14 | font-size: 12px; 15 | transform: scale(0.83); 16 | } 17 | } 18 | .up { 19 | color: @red-6; 20 | } 21 | .down { 22 | top: -1px; 23 | color: @green-6; 24 | } 25 | 26 | &.trendItemGrey .up, 27 | &.trendItemGrey .down { 28 | color: @text-color; 29 | } 30 | 31 | &.reverseColor .up { 32 | color: @green-6; 33 | } 34 | &.reverseColor .down { 35 | color: @red-6; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface VisitDataType { 2 | x: string; 3 | y: number; 4 | } 5 | 6 | export interface SearchDataType { 7 | index: number; 8 | keyword: string; 9 | count: number; 10 | range: number; 11 | status: number; 12 | } 13 | 14 | export interface OfflineDataType { 15 | name: string; 16 | cvr: number; 17 | } 18 | 19 | export interface OfflineChartData { 20 | x: any; 21 | y1: number; 22 | y2: number; 23 | } 24 | 25 | export interface RadarData { 26 | name: string; 27 | label: string; 28 | value: number; 29 | } 30 | 31 | export interface AnalysisData { 32 | visitData: VisitDataType[]; 33 | visitData2: VisitDataType[]; 34 | salesData: VisitDataType[]; 35 | searchData: SearchDataType[]; 36 | offlineData: OfflineDataType[]; 37 | offlineChartData: OfflineChartData[]; 38 | salesTypeData: VisitDataType[]; 39 | salesTypeDataOnline: VisitDataType[]; 40 | salesTypeDataOffline: VisitDataType[]; 41 | radarData: RadarData[]; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/model.jsx: -------------------------------------------------------------------------------- 1 | import { fakeChartData } from './service'; 2 | 3 | const initState = { 4 | visitData: [], 5 | visitData2: [], 6 | salesData: [], 7 | searchData: [], 8 | offlineData: [], 9 | offlineChartData: [], 10 | salesTypeData: [], 11 | salesTypeDataOnline: [], 12 | salesTypeDataOffline: [], 13 | radarData: [], 14 | }; 15 | const Model = { 16 | namespace: 'dashboardAndanalysis', 17 | state: initState, 18 | effects: { 19 | *fetch(_, { call, put }) { 20 | const response = yield call(fakeChartData); 21 | yield put({ 22 | type: 'save', 23 | payload: response, 24 | }); 25 | }, 26 | 27 | *fetchSalesData(_, { call, put }) { 28 | const response = yield call(fakeChartData); 29 | yield put({ 30 | type: 'save', 31 | payload: { 32 | salesData: response.salesData, 33 | }, 34 | }); 35 | }, 36 | }, 37 | reducers: { 38 | save(state, { payload }) { 39 | return { ...state, ...payload }; 40 | }, 41 | 42 | clear() { 43 | return initState; 44 | }, 45 | }, 46 | }; 47 | export default Model; 48 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/service.jsx: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function fakeChartData() { 4 | return request('/api/fake_chart_data'); 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/utils/Yuan.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { yuan } from '../components/Charts'; 3 | /** 4 | * 减少使用 dangerouslySetInnerHTML 5 | */ 6 | 7 | export default class Yuan extends React.Component { 8 | main = null; 9 | 10 | componentDidMount() { 11 | this.renderToHtml(); 12 | } 13 | 14 | componentDidUpdate() { 15 | this.renderToHtml(); 16 | } 17 | 18 | renderToHtml = () => { 19 | const { children } = this.props; 20 | 21 | if (this.main) { 22 | this.main.innerHTML = yuan(children); 23 | } 24 | }; 25 | 26 | render() { 27 | return ( 28 | { 30 | this.main = ref; 31 | }} 32 | /> 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/utils/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export function fixedZero(val) { 4 | return val * 1 < 10 ? `0${val}` : val; 5 | } 6 | export function getTimeDistance(type) { 7 | const now = new Date(); 8 | const oneDay = 1000 * 60 * 60 * 24; 9 | 10 | if (type === 'today') { 11 | now.setHours(0); 12 | now.setMinutes(0); 13 | now.setSeconds(0); 14 | return [moment(now), moment(now.getTime() + (oneDay - 1000))]; 15 | } 16 | 17 | if (type === 'week') { 18 | let day = now.getDay(); 19 | now.setHours(0); 20 | now.setMinutes(0); 21 | now.setSeconds(0); 22 | 23 | if (day === 0) { 24 | day = 6; 25 | } else { 26 | day -= 1; 27 | } 28 | 29 | const beginTime = now.getTime() - day * oneDay; 30 | return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))]; 31 | } 32 | 33 | const year = now.getFullYear(); 34 | 35 | if (type === 'month') { 36 | const month = now.getMonth(); 37 | const nextDate = moment(now).add(1, 'months'); 38 | const nextYear = nextDate.year(); 39 | const nextMonth = nextDate.month(); 40 | return [ 41 | moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`), 42 | moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000), 43 | ]; 44 | } 45 | 46 | return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)]; 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/analysis/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/_mock.js: -------------------------------------------------------------------------------- 1 | import mockjs from 'mockjs'; 2 | 3 | export default { 4 | 'GET /api/tags': mockjs.mock({ 5 | 'list|100': [ 6 | { 7 | name: '@city', 8 | 'value|1-100': 150, 9 | 'type|0-2': 1, 10 | }, 11 | ], 12 | }), 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/components/ActiveChart/index.less: -------------------------------------------------------------------------------- 1 | .activeChart { 2 | position: relative; 3 | } 4 | .activeChartGrid { 5 | p { 6 | position: absolute; 7 | top: 80px; 8 | } 9 | p:last-child { 10 | top: 115px; 11 | } 12 | } 13 | .activeChartLegend { 14 | position: relative; 15 | height: 20px; 16 | margin-top: 8px; 17 | font-size: 0; 18 | line-height: 20px; 19 | span { 20 | display: inline-block; 21 | width: 33.33%; 22 | font-size: 12px; 23 | text-align: center; 24 | } 25 | span:first-child { 26 | text-align: left; 27 | } 28 | span:last-child { 29 | text-align: right; 30 | } 31 | } 32 | .dashedLine { 33 | position: relative; 34 | top: -70px; 35 | left: -3px; 36 | height: 1px; 37 | 38 | .line { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | width: 100%; 43 | height: 100%; 44 | background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%); 45 | background-size: 6px; 46 | } 47 | } 48 | 49 | .dashedLine:last-child { 50 | top: -36px; 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/components/Charts/TagCloud/index.less: -------------------------------------------------------------------------------- 1 | .tagCloud { 2 | overflow: hidden; 3 | canvas { 4 | transform-origin: 0 0; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/components/Charts/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .waterWave { 4 | position: relative; 5 | display: inline-block; 6 | transform-origin: left; 7 | .text { 8 | position: absolute; 9 | top: 32px; 10 | left: 0; 11 | width: 100%; 12 | text-align: center; 13 | span { 14 | color: @text-color-secondary; 15 | font-size: 14px; 16 | line-height: 22px; 17 | } 18 | h4 { 19 | color: @heading-color; 20 | font-size: 24px; 21 | line-height: 32px; 22 | } 23 | } 24 | .waterWaveCanvasWrapper { 25 | transform: scale(0.5); 26 | transform-origin: 0 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/components/Charts/index.jsx: -------------------------------------------------------------------------------- 1 | import Gauge from './Gauge'; 2 | import MiniArea from './MiniArea'; 3 | import Pie from './Pie'; 4 | import TagCloud from './TagCloud'; 5 | import WaterWave from './WaterWave'; 6 | import Map from './Map'; 7 | 8 | const Charts = { 9 | Pie, 10 | WaterWave, 11 | Gauge, 12 | MiniArea, 13 | TagCloud, 14 | Map, 15 | }; 16 | export { Charts as default, Pie, WaterWave, Gauge, TagCloud, MiniArea, Map }; 17 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/components/Charts/index.less: -------------------------------------------------------------------------------- 1 | .miniChart { 2 | position: relative; 3 | width: 100%; 4 | .chartContent { 5 | position: absolute; 6 | bottom: -28px; 7 | width: 100%; 8 | > div { 9 | margin: 0 -5px; 10 | overflow: hidden; 11 | } 12 | } 13 | .chartLoading { 14 | position: absolute; 15 | top: 16px; 16 | left: 50%; 17 | margin-left: -7px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface TagType { 2 | name: string; 3 | value: string; 4 | type: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboardandmonitor.monitor.trading-activity': 'Real-Time Trading Activity', 3 | 'dashboardandmonitor.monitor.total-transactions': 'Total transactions today', 4 | 'dashboardandmonitor.monitor.sales-target': 'Sales target completion rate', 5 | 'dashboardandmonitor.monitor.remaining-time': 'Remaining time of activity', 6 | 'dashboardandmonitor.monitor.total-transactions-per-second': 'Total transactions per second', 7 | 'dashboardandmonitor.monitor.activity-forecast': 'Activity forecast', 8 | 'dashboardandmonitor.monitor.efficiency': 'Efficiency', 9 | 'dashboardandmonitor.monitor.ratio': 'Ratio', 10 | 'dashboardandmonitor.monitor.proportion-per-category': 'Proportion Per Category', 11 | 'dashboardandmonitor.monitor.fast-food': 'Fast food', 12 | 'dashboardandmonitor.monitor.western-food': 'Western food', 13 | 'dashboardandmonitor.monitor.hot-pot': 'Hot pot', 14 | 'dashboardandmonitor.monitor.waiting-for-implementation': 'Waiting for implementation', 15 | 'dashboardandmonitor.monitor.popular-searches': 'Popular Searches', 16 | 'dashboardandmonitor.monitor.resource-surplus': 'Resource Surplus', 17 | 'dashboardandmonitor.monitor.fund-surplus': 'Fund Surplus', 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/locales/pt-BR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboardandmonitor.monitor.trading-activity': 'Real-Time Trading Activity', 3 | 'dashboardandmonitor.monitor.total-transactions': 'Total transactions today', 4 | 'dashboardandmonitor.monitor.sales-target': 'Sales target completion rate', 5 | 'dashboardandmonitor.monitor.remaining-time': 'Remaining time of activity', 6 | 'dashboardandmonitor.monitor.total-transactions-per-second': 'Total transactions per second', 7 | 'dashboardandmonitor.monitor.activity-forecast': 'Activity forecast', 8 | 'dashboardandmonitor.monitor.efficiency': 'Efficiency', 9 | 'dashboardandmonitor.monitor.ratio': 'Ratio', 10 | 'dashboardandmonitor.monitor.proportion-per-category': 'Proportion Per Category', 11 | 'dashboardandmonitor.monitor.fast-food': 'Fast food', 12 | 'dashboardandmonitor.monitor.western-food': 'Western food', 13 | 'dashboardandmonitor.monitor.hot-pot': 'Hot pot', 14 | 'dashboardandmonitor.monitor.waiting-for-implementation': 'Waiting for implementation', 15 | 'dashboardandmonitor.monitor.popular-searches': 'Popular Searches', 16 | 'dashboardandmonitor.monitor.resource-surplus': 'Resource Surplus', 17 | 'dashboardandmonitor.monitor.fund-surplus': 'Fund Surplus', 18 | 'dashboardandmonitor.exception.back': 'Back to home', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboardandmonitor.monitor.trading-activity': '活动实时交易情况', 3 | 'dashboardandmonitor.monitor.total-transactions': '今日交易总额', 4 | 'dashboardandmonitor.monitor.sales-target': '销售目标完成率', 5 | 'dashboardandmonitor.monitor.remaining-time': '活动剩余时间', 6 | 'dashboardandmonitor.monitor.total-transactions-per-second': '每秒交易总额', 7 | 'dashboardandmonitor.monitor.activity-forecast': '活动情况预测', 8 | 'dashboardandmonitor.monitor.efficiency': '券核效率', 9 | 'dashboardandmonitor.monitor.ratio': '跳出率', 10 | 'dashboardandmonitor.monitor.proportion-per-category': '各品类占比', 11 | 'dashboardandmonitor.monitor.fast-food': '中式快餐', 12 | 'dashboardandmonitor.monitor.western-food': '西餐', 13 | 'dashboardandmonitor.monitor.hot-pot': '火锅', 14 | 'dashboardandmonitor.monitor.waiting-for-implementation': 'Waiting for implementation', 15 | 'dashboardandmonitor.monitor.popular-searches': '热门搜索', 16 | 'dashboardandmonitor.monitor.resource-surplus': '资源剩余', 17 | 'dashboardandmonitor.monitor.fund-surplus': '补贴资金剩余', 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboardandmonitor.monitor.trading-activity': '活動實時交易情況', 3 | 'dashboardandmonitor.monitor.total-transactions': '今日交易總額', 4 | 'dashboardandmonitor.monitor.sales-target': '銷售目標完成率', 5 | 'dashboardandmonitor.monitor.remaining-time': '活動剩余時間', 6 | 'dashboardandmonitor.monitor.total-transactions-per-second': '每秒交易總額', 7 | 'dashboardandmonitor.monitor.activity-forecast': '活動情況預測', 8 | 'dashboardandmonitor.monitor.efficiency': '券核效率', 9 | 'dashboardandmonitor.monitor.ratio': '跳出率', 10 | 'dashboardandmonitor.monitor.proportion-per-category': '各品類占比', 11 | 'dashboardandmonitor.monitor.fast-food': '中式快餐', 12 | 'dashboardandmonitor.monitor.western-food': '西餐', 13 | 'dashboardandmonitor.monitor.hot-pot': '火鍋', 14 | 'dashboardandmonitor.monitor.waiting-for-implementation': 'Waiting for implementation', 15 | 'dashboardandmonitor.monitor.popular-searches': '熱門搜索', 16 | 'dashboardandmonitor.monitor.resource-surplus': '資源剩余', 17 | 'dashboardandmonitor.monitor.fund-surplus': '補貼資金剩余', 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/model.js: -------------------------------------------------------------------------------- 1 | import { queryTags } from './service'; 2 | 3 | const Model = { 4 | namespace: 'dashboardAndmonitor', 5 | state: { 6 | tags: [], 7 | }, 8 | effects: { 9 | *fetchTags(_, { call, put }) { 10 | const response = yield call(queryTags); 11 | yield put({ 12 | type: 'saveTags', 13 | payload: response.list, 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | saveTags(state, action) { 19 | return { ...state, tags: action.payload }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryTags() { 4 | return request('/api/tags'); 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/monitor/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .mapChart { 4 | height: 452px; 5 | padding-top: 24px; 6 | img { 7 | display: inline-block; 8 | max-width: 100%; 9 | max-height: 437px; 10 | } 11 | } 12 | 13 | .pieCard :global(.pie-stat) { 14 | font-size: 24px !important; 15 | } 16 | 17 | @media screen and (max-width: @screen-lg) { 18 | .mapChart { 19 | height: auto; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/workplace/components/EditableLinkGroup/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import { PlusOutlined } from '@ant-design/icons'; 3 | import { Button } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const EditableLinkGroup = props => { 7 | const { links, linkElement, onAdd } = props; 8 | return ( 9 |
10 | {links.map(link => 11 | createElement( 12 | linkElement, 13 | { 14 | key: `linkGroup-item-${link.id || link.title}`, 15 | to: link.href, 16 | href: link.href, 17 | }, 18 | link.title, 19 | ), 20 | )} 21 | 24 |
25 | ); 26 | }; 27 | 28 | EditableLinkGroup.defaultProps = { 29 | links: [], 30 | onAdd: () => {}, 31 | linkElement: 'a', 32 | }; 33 | export default EditableLinkGroup; 34 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/workplace/components/EditableLinkGroup/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .linkGroup { 4 | padding: 20px 0 8px 24px; 5 | font-size: 0; 6 | & > a { 7 | display: inline-block; 8 | width: 25%; 9 | margin-bottom: 13px; 10 | color: @text-color; 11 | font-size: @font-size-base; 12 | &:hover { 13 | color: @primary-color; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/workplace/components/Radar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .radar { 4 | .legend { 5 | margin-top: 16px; 6 | .legendItem { 7 | position: relative; 8 | color: @text-color-secondary; 9 | line-height: 22px; 10 | text-align: center; 11 | cursor: pointer; 12 | p { 13 | margin: 0; 14 | } 15 | h6 { 16 | margin-top: 4px; 17 | margin-bottom: 0; 18 | padding-left: 16px; 19 | color: @heading-color; 20 | font-size: 24px; 21 | line-height: 32px; 22 | } 23 | &::after { 24 | position: absolute; 25 | top: 8px; 26 | right: 0; 27 | width: 1px; 28 | height: 40px; 29 | background-color: @border-color-split; 30 | content: ''; 31 | } 32 | } 33 | > :last-child .legendItem::after { 34 | display: none; 35 | } 36 | .dot { 37 | position: relative; 38 | top: -1px; 39 | display: inline-block; 40 | width: 6px; 41 | height: 6px; 42 | margin-right: 6px; 43 | border-radius: 6px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/workplace/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryProjectNotice() { 4 | return request('/api/project/notice'); 5 | } 6 | export async function queryActivities() { 7 | return request('/api/activities'); 8 | } 9 | export async function fakeChartData() { 10 | return request('/api/fake_chart_data'); 11 | } 12 | export async function queryCurrent() { 13 | return request('/api/currentUser'); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/common/IconFont/index.js: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from '@ant-design/icons'; 2 | 3 | const IconFont = createFromIconfontCN({ 4 | scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js', 5 | }); 6 | export default IconFont; 7 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/FlowContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const FlowContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | export default FlowContextMenu; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/KoniContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | 3 | export default FlowContextMenu; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import IconFont from '../../common/IconFont'; 4 | import styles from './index.less'; 5 | 6 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 7 | 8 | const MenuItem = props => { 9 | const { command, icon, text } = props; 10 | return ( 11 | 12 |
13 | 14 | {text || upperFirst(command)} 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default MenuItem; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/MindContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const MindContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default MindContextMenu; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | import KoniContextMenu from './KoniContextMenu'; 3 | import MindContextMenu from './MindContextMenu'; 4 | 5 | export { FlowContextMenu, MindContextMenu, KoniContextMenu }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorContextMenu/index.less: -------------------------------------------------------------------------------- 1 | .contextMenu { 2 | display: none; 3 | overflow: hidden; 4 | background: #fff; 5 | border-radius: 4px; 6 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 7 | 8 | .item { 9 | display: flex; 10 | align-items: center; 11 | padding: 5px 12px; 12 | cursor: pointer; 13 | transition: all 0.3s; 14 | user-select: none; 15 | 16 | &:hover { 17 | background: #e6f7ff; 18 | } 19 | 20 | span.anticon { 21 | margin-right: 8px; 22 | } 23 | } 24 | 25 | :global { 26 | .disable { 27 | :local { 28 | .item { 29 | color: rgba(0, 0, 0, 0.25); 30 | cursor: auto; 31 | 32 | &:hover { 33 | background: #fff; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorDetailPanel/FlowDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const FlowDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | export default FlowDetailPanel; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorDetailPanel/KoniDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | 3 | export default FlowDetailPanel; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorDetailPanel/MindDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const MindDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default MindDetailPanel; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorDetailPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | import KoniDetailPanel from './KoniDetailPanel'; 3 | import MindDetailPanel from './MindDetailPanel'; 4 | 5 | export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorDetailPanel/index.less: -------------------------------------------------------------------------------- 1 | .detailPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorItemPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowItemPanel from './FlowItemPanel'; 2 | import KoniItemPanel from './KoniItemPanel'; 3 | 4 | export { FlowItemPanel, KoniItemPanel }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorItemPanel/index.less: -------------------------------------------------------------------------------- 1 | .itemPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | 10 | .ant-card-body { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | 15 | > div { 16 | margin-bottom: 16px; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorMinimap/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'antd'; 2 | import { Minimap } from 'gg-editor'; 3 | import React from 'react'; 4 | 5 | const EditorMinimap = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export default EditorMinimap; 12 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/FlowToolbar.jsx: -------------------------------------------------------------------------------- 1 | import { Divider } from 'antd'; 2 | import React from 'react'; 3 | import { Toolbar } from 'gg-editor'; 4 | import ToolbarButton from './ToolbarButton'; 5 | import styles from './index.less'; 6 | 7 | const FlowToolbar = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default FlowToolbar; 31 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/KoniToolbar.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | 3 | export default FlowToolbar; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/MindToolbar.jsx: -------------------------------------------------------------------------------- 1 | import { Divider } from 'antd'; 2 | import React from 'react'; 3 | import { Toolbar } from 'gg-editor'; 4 | import ToolbarButton from './ToolbarButton'; 5 | import styles from './index.less'; 6 | 7 | const FlowToolbar = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | export default FlowToolbar; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/ToolbarButton.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import { Tooltip } from 'antd'; 4 | import IconFont from '../../common/IconFont'; 5 | import styles from './index.less'; 6 | 7 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 8 | 9 | const ToolbarButton = props => { 10 | const { command, icon, text } = props; 11 | return ( 12 | 13 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default ToolbarButton; 25 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | import KoniToolbar from './KoniToolbar'; 3 | import MindToolbar from './MindToolbar'; 4 | 5 | export { FlowToolbar, MindToolbar, KoniToolbar }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/components/EditorToolbar/index.less: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | display: flex; 3 | align-items: center; 4 | 5 | :global { 6 | .command .anticon { 7 | display: inline-block; 8 | width: 27px; 9 | height: 27px; 10 | margin: 0 6px; 11 | padding-top: 6px; 12 | text-align: center; 13 | border: 1px solid #fff; 14 | cursor: pointer; 15 | 16 | &:hover { 17 | border: 1px solid #e6e9ed; 18 | } 19 | } 20 | 21 | .disable .anticon { 22 | color: rgba(0, 0, 0, 0.25); 23 | cursor: auto; 24 | 25 | &:hover { 26 | border: 1px solid #fff; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .tooltip { 33 | :global { 34 | .ant-tooltip-inner { 35 | font-size: 12px; 36 | border-radius: 0; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/index.jsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from 'antd'; 2 | import GGEditor, { Flow } from 'gg-editor'; 3 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; 4 | import React from 'react'; 5 | import EditorMinimap from './components/EditorMinimap'; 6 | import { FlowContextMenu } from './components/EditorContextMenu'; 7 | import { FlowDetailPanel } from './components/EditorDetailPanel'; 8 | import { FlowItemPanel } from './components/EditorItemPanel'; 9 | import { FlowToolbar } from './components/EditorToolbar'; 10 | import styles from './index.less'; 11 | 12 | GGEditor.setTrackable(false); 13 | export default () => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | width: 100%; 6 | height: calc(100vh - 250px); 7 | background: #fff; 8 | } 9 | 10 | .editorHd { 11 | padding: 8px; 12 | border: 1px solid #e6e9ed; 13 | } 14 | 15 | .editorBd { 16 | flex: 1; 17 | } 18 | 19 | .editorSidebar, 20 | .editorContent { 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .editorSidebar { 26 | background: #fafafa; 27 | 28 | &:first-child { 29 | border-right: 1px solid #e6e9ed; 30 | } 31 | 32 | &:last-child { 33 | border-left: 1px solid #e6e9ed; 34 | } 35 | } 36 | 37 | .flow, 38 | .mind, 39 | .koni { 40 | flex: 1; 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandflow.description': 3 | 'The flow chart is an excellent way to represent the idea of the algorithm', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/flow/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandflow.description': '千言万语不如一张图,流程图是表示算法思路的好方法', 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/common/IconFont/index.js: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from '@ant-design/icons'; 2 | 3 | const IconFont = createFromIconfontCN({ 4 | scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js', 5 | }); 6 | export default IconFont; 7 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/FlowContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const FlowContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | export default FlowContextMenu; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/KoniContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | 3 | export default FlowContextMenu; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import IconFont from '../../common/IconFont'; 4 | import styles from './index.less'; 5 | 6 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 7 | 8 | const MenuItem = props => { 9 | const { command, icon, text } = props; 10 | return ( 11 | 12 |
13 | 14 | {text || upperFirst(command)} 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default MenuItem; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/MindContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const MindContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default MindContextMenu; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | import KoniContextMenu from './KoniContextMenu'; 3 | import MindContextMenu from './MindContextMenu'; 4 | 5 | export { FlowContextMenu, MindContextMenu, KoniContextMenu }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorContextMenu/index.less: -------------------------------------------------------------------------------- 1 | .contextMenu { 2 | display: none; 3 | overflow: hidden; 4 | background: #fff; 5 | border-radius: 4px; 6 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 7 | 8 | .item { 9 | display: flex; 10 | align-items: center; 11 | padding: 5px 12px; 12 | cursor: pointer; 13 | transition: all 0.3s; 14 | user-select: none; 15 | 16 | &:hover { 17 | background: #e6f7ff; 18 | } 19 | 20 | .anticon { 21 | margin-right: 8px; 22 | } 23 | } 24 | 25 | :global { 26 | .disable { 27 | :local { 28 | .item { 29 | color: rgba(0, 0, 0, 0.25); 30 | cursor: auto; 31 | 32 | &:hover { 33 | background: #fff; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorDetailPanel/FlowDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const FlowDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | export default FlowDetailPanel; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorDetailPanel/KoniDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | 3 | export default FlowDetailPanel; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorDetailPanel/MindDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const MindDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default MindDetailPanel; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorDetailPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | import KoniDetailPanel from './KoniDetailPanel'; 3 | import MindDetailPanel from './MindDetailPanel'; 4 | 5 | export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorDetailPanel/index.less: -------------------------------------------------------------------------------- 1 | .detailPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorItemPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowItemPanel from './FlowItemPanel'; 2 | import KoniItemPanel from './KoniItemPanel'; 3 | 4 | export { FlowItemPanel, KoniItemPanel }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorItemPanel/index.less: -------------------------------------------------------------------------------- 1 | .itemPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | 10 | .ant-card-body { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | 15 | > div { 16 | margin-bottom: 16px; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorMinimap/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'antd'; 2 | import { Minimap } from 'gg-editor'; 3 | import React from 'react'; 4 | 5 | const EditorMinimap = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export default EditorMinimap; 12 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorToolbar/KoniToolbar.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | 3 | export default FlowToolbar; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorToolbar/MindToolbar.jsx: -------------------------------------------------------------------------------- 1 | import { Divider } from 'antd'; 2 | import React from 'react'; 3 | import { Toolbar } from 'gg-editor'; 4 | import ToolbarButton from './ToolbarButton'; 5 | import styles from './index.less'; 6 | 7 | const FlowToolbar = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | export default FlowToolbar; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorToolbar/ToolbarButton.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import { Tooltip } from 'antd'; 4 | import IconFont from '../../common/IconFont'; 5 | import styles from './index.less'; 6 | 7 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 8 | 9 | const ToolbarButton = props => { 10 | const { command, icon, text } = props; 11 | return ( 12 | 13 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default ToolbarButton; 25 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorToolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | import KoniToolbar from './KoniToolbar'; 3 | import MindToolbar from './MindToolbar'; 4 | 5 | export { FlowToolbar, MindToolbar, KoniToolbar }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/components/EditorToolbar/index.less: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | display: flex; 3 | align-items: center; 4 | 5 | :global { 6 | .command .anticon { 7 | display: inline-block; 8 | width: 27px; 9 | height: 27px; 10 | margin: 0 6px; 11 | padding-top: 6px; 12 | text-align: center; 13 | border: 1px solid #fff; 14 | cursor: pointer; 15 | 16 | &:hover { 17 | border: 1px solid #e6e9ed; 18 | } 19 | } 20 | 21 | .disable .anticon { 22 | color: rgba(0, 0, 0, 0.25); 23 | cursor: auto; 24 | 25 | &:hover { 26 | border: 1px solid #fff; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .tooltip { 33 | :global { 34 | .ant-tooltip-inner { 35 | font-size: 12px; 36 | border-radius: 0; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | width: 100%; 6 | height: calc(100vh - 250px); 7 | background: #fff; 8 | } 9 | 10 | .editorHd { 11 | padding: 8px; 12 | border: 1px solid #e6e9ed; 13 | } 14 | 15 | .editorBd { 16 | flex: 1; 17 | } 18 | 19 | .editorSidebar, 20 | .editorContent { 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .editorContent { 26 | :global { 27 | .graph-container canvas { 28 | vertical-align: middle; 29 | } 30 | } 31 | } 32 | 33 | .editorSidebar { 34 | background: #fafafa; 35 | 36 | &:first-child { 37 | border-right: 1px solid #e6e9ed; 38 | } 39 | 40 | &:last-child { 41 | border-left: 1px solid #e6e9ed; 42 | } 43 | } 44 | 45 | .flow, 46 | .mind, 47 | .koni { 48 | flex: 1; 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandkoni.description': 3 | 'The topology diagram refers to the network structure diagram composed of network node devices and communication media', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/koni/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandkoni.description': '拓扑结构图是指由网络节点设备和通信介质构成的网络结构图', 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/common/IconFont/index.js: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from '@ant-design/icons'; 2 | 3 | const IconFont = createFromIconfontCN({ 4 | scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js', 5 | }); 6 | export default IconFont; 7 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/FlowContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const FlowContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | 34 | export default FlowContextMenu; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/KoniContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | 3 | export default FlowContextMenu; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import IconFont from '../../common/IconFont'; 4 | import styles from './index.less'; 5 | 6 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 7 | 8 | const MenuItem = props => { 9 | const { command, icon, text } = props; 10 | return ( 11 | 12 |
13 | 14 | {text || upperFirst(command)} 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default MenuItem; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/MindContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor'; 2 | import React from 'react'; 3 | import MenuItem from './MenuItem'; 4 | import styles from './index.less'; 5 | 6 | const MindContextMenu = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default MindContextMenu; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowContextMenu from './FlowContextMenu'; 2 | import KoniContextMenu from './KoniContextMenu'; 3 | import MindContextMenu from './MindContextMenu'; 4 | 5 | export { FlowContextMenu, MindContextMenu, KoniContextMenu }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorContextMenu/index.less: -------------------------------------------------------------------------------- 1 | .contextMenu { 2 | display: none; 3 | overflow: hidden; 4 | background: #fff; 5 | border-radius: 4px; 6 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 7 | 8 | .item { 9 | display: flex; 10 | align-items: center; 11 | padding: 5px 12px; 12 | cursor: pointer; 13 | transition: all 0.3s; 14 | user-select: none; 15 | 16 | &:hover { 17 | background: #e6f7ff; 18 | } 19 | 20 | .anticon { 21 | margin-right: 8px; 22 | } 23 | } 24 | 25 | :global { 26 | .disable { 27 | :local { 28 | .item { 29 | color: rgba(0, 0, 0, 0.25); 30 | cursor: auto; 31 | 32 | &:hover { 33 | background: #fff; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorDetailPanel/FlowDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const FlowDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | export default FlowDetailPanel; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorDetailPanel/KoniDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | 3 | export default FlowDetailPanel; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorDetailPanel/MindDetailPanel.jsx: -------------------------------------------------------------------------------- 1 | import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor'; 2 | import { Card } from 'antd'; 3 | import React from 'react'; 4 | import DetailForm from './DetailForm'; 5 | import styles from './index.less'; 6 | 7 | const MindDetailPanel = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default MindDetailPanel; 19 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorDetailPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowDetailPanel from './FlowDetailPanel'; 2 | import KoniDetailPanel from './KoniDetailPanel'; 3 | import MindDetailPanel from './MindDetailPanel'; 4 | 5 | export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorDetailPanel/index.less: -------------------------------------------------------------------------------- 1 | .detailPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorItemPanel/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowItemPanel from './FlowItemPanel'; 2 | import KoniItemPanel from './KoniItemPanel'; 3 | 4 | export { FlowItemPanel, KoniItemPanel }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorItemPanel/index.less: -------------------------------------------------------------------------------- 1 | .itemPanel { 2 | flex: 1; 3 | background: #fafafa; 4 | 5 | :global { 6 | .ant-card { 7 | background: #fafafa; 8 | } 9 | 10 | .ant-card-body { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | 15 | > div { 16 | margin-bottom: 16px; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorMinimap/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'antd'; 2 | import { Minimap } from 'gg-editor'; 3 | import React from 'react'; 4 | 5 | const EditorMinimap = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export default EditorMinimap; 12 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorToolbar/KoniToolbar.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | 3 | export default FlowToolbar; 4 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorToolbar/MindToolbar.jsx: -------------------------------------------------------------------------------- 1 | import { Divider } from 'antd'; 2 | import React from 'react'; 3 | import { Toolbar } from 'gg-editor'; 4 | import ToolbarButton from './ToolbarButton'; 5 | import styles from './index.less'; 6 | 7 | const FlowToolbar = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | export default FlowToolbar; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorToolbar/ToolbarButton.jsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'gg-editor'; 2 | import React from 'react'; 3 | import { Tooltip } from 'antd'; 4 | import IconFont from '../../common/IconFont'; 5 | import styles from './index.less'; 6 | 7 | const upperFirst = str => str.toLowerCase().replace(/( |^)[a-z]/g, l => l.toUpperCase()); 8 | 9 | const ToolbarButton = props => { 10 | const { command, icon, text } = props; 11 | return ( 12 | 13 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default ToolbarButton; 25 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorToolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import FlowToolbar from './FlowToolbar'; 2 | import KoniToolbar from './KoniToolbar'; 3 | import MindToolbar from './MindToolbar'; 4 | 5 | export { FlowToolbar, MindToolbar, KoniToolbar }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/components/EditorToolbar/index.less: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | display: flex; 3 | align-items: center; 4 | 5 | :global { 6 | .command .anticon { 7 | display: inline-block; 8 | width: 27px; 9 | height: 27px; 10 | margin: 0 6px; 11 | padding-top: 6px; 12 | text-align: center; 13 | border: 1px solid #fff; 14 | cursor: pointer; 15 | 16 | &:hover { 17 | border: 1px solid #e6e9ed; 18 | } 19 | } 20 | 21 | .disable .anticon { 22 | color: rgba(0, 0, 0, 0.25); 23 | cursor: auto; 24 | 25 | &:hover { 26 | border: 1px solid #fff; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .tooltip { 33 | :global { 34 | .ant-tooltip-inner { 35 | font-size: 12px; 36 | border-radius: 0; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/index.jsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from 'antd'; 2 | import GGEditor, { Mind } from 'gg-editor'; 3 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; 4 | import React from 'react'; 5 | import EditorMinimap from './components/EditorMinimap'; 6 | import { MindContextMenu } from './components/EditorContextMenu'; 7 | import { MindDetailPanel } from './components/EditorDetailPanel'; 8 | import { MindToolbar } from './components/EditorToolbar'; 9 | import data from './worldCup2018.json'; 10 | import styles from './index.less'; 11 | 12 | GGEditor.setTrackable(false); 13 | export default () => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | width: 100%; 6 | height: calc(100vh - 250px); 7 | background: #fff; 8 | } 9 | 10 | .editorHd { 11 | padding: 8px; 12 | border: 1px solid #e6e9ed; 13 | } 14 | 15 | .editorBd { 16 | flex: 1; 17 | } 18 | 19 | .editorSidebar, 20 | .editorContent { 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .editorContent { 26 | :global { 27 | .graph-container canvas { 28 | vertical-align: middle; 29 | } 30 | } 31 | } 32 | 33 | .editorSidebar { 34 | background: #fafafa; 35 | 36 | &:first-child { 37 | border-right: 1px solid #e6e9ed; 38 | } 39 | 40 | &:last-child { 41 | border-left: 1px solid #e6e9ed; 42 | } 43 | } 44 | 45 | .flow, 46 | .mind, 47 | .koni { 48 | flex: 1; 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandmind.description': 3 | 'The brain map is an effective graphical thinking tool for expressing divergent thinking. It is simple but effective and is a practical thinking tool', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/editor/mind/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'editorandmind.description': 3 | '脑图是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/403/index.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'umi/link'; 2 | import { Result, Button } from 'antd'; 3 | import React from 'react'; 4 | 5 | export default () => ( 6 | 15 | 16 | 17 | } 18 | /> 19 | ); 20 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/403/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand403.exception.back': 'Back to home', 3 | 'exceptionand403.description.403': "Sorry, you don't have access to this page.", 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/403/locales/pt-BR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand403.exception.back': 'Voltar para Início', 3 | 'exceptionand403.description.403': 'Desculpe, você não tem acesso a esta página.', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/403/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand403.exception.back': '返回首页', 3 | 'exceptionand403.description.403': '抱歉,你无权访问该页面。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/403/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand403.exception.back': '返回首頁', 3 | 'exceptionand403.description.403': '抱歉,妳無權訪問該頁面。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/404/index.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'umi/link'; 2 | import { Result, Button } from 'antd'; 3 | import React from 'react'; 4 | 5 | export default () => ( 6 | 15 | 16 | 17 | } 18 | /> 19 | ); 20 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/404/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand404.exception.back': 'Back to home', 3 | 'exceptionand404.description.404': 'Sorry, the page you visited does not exist.', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/404/locales/pt-BR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand404.exception.back': 'Voltar para Início', 3 | 'exceptionand404.description.404': 'Desculpe, a página que você visitou não existe.', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/404/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand404.exception.back': '返回首页', 3 | 'exceptionand404.description.404': '抱歉,你访问的页面不存在。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/404/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand404.exception.back': '返回首頁', 3 | 'exceptionand404.description.404': '抱歉,妳訪問的頁面不存在。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/500/index.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'umi/link'; 2 | import { Result, Button } from 'antd'; 3 | import React from 'react'; 4 | 5 | export default () => ( 6 | 15 | 16 | 17 | } 18 | /> 19 | ); 20 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/500/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand500.exception.back': 'Back to home', 3 | 'exceptionand500.description.500': 'Sorry, the server is reporting an error.', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/500/locales/pt-BR.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand500.exception.back': 'Voltar para Início', 3 | 'exceptionand500.description.500': 'Desculpe, o servidor está reportando um erro.', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/500/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand500.exception.back': '返回首页', 3 | 'exceptionand500.description.500': '抱歉,服务器出错了。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/exception/500/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'exceptionand500.exception.back': '返回首頁', 3 | 'exceptionand500.description.500': '抱歉,服務器出錯了。', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/form/advanced-form/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'POST /api/forms': (_, res) => { 3 | res.send({ 4 | message: 'Ok', 5 | }); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/form/advanced-form/components/FooterToolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { RouteContext } from '@ant-design/pro-layout'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | export default class FooterToolbar extends Component { 7 | getWidth = ({ collapsed, isMobile, siderWidth }) => { 8 | const sider = document.querySelector('.ant-layout-sider'); 9 | 10 | if (!sider) { 11 | return undefined; 12 | } 13 | 14 | return isMobile ? undefined : `calc(100% - ${collapsed ? 80 : siderWidth || 256}px)`; 15 | }; 16 | 17 | render() { 18 | const { children, className, extra, ...restProps } = this.props; 19 | return ( 20 | 21 | {value => ( 22 |
30 |
{extra}
31 |
{children}
32 |
33 | )} 34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/form/advanced-form/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .toolbar { 4 | position: fixed; 5 | right: 0; 6 | bottom: 0; 7 | z-index: 99; 8 | width: 100%; 9 | height: 56px; 10 | padding: 0 24px; 11 | line-height: 56px; 12 | background: @component-background; 13 | border-top: 1px solid @border-color-split; 14 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 15 | 16 | &::after { 17 | display: block; 18 | clear: both; 19 | content: ''; 20 | } 21 | 22 | .left { 23 | float: left; 24 | } 25 | 26 | .right { 27 | float: right; 28 | } 29 | 30 | button + button { 31 | margin-left: 8px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/pages/form/advanced-form/model.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { fakeSubmitForm } from './service'; 3 | 4 | const Model = { 5 | namespace: 'formAndadvancedForm', 6 | state: {}, 7 | effects: { 8 | *submitAdvancedForm({ payload }, { call }) { 9 | yield call(fakeSubmitForm, payload); 10 | message.success('提交成功'); 11 | }, 12 | }, 13 | }; 14 | export default Model; 15 | -------------------------------------------------------------------------------- /frontend/src/pages/form/advanced-form/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function fakeSubmitForm(params) { 4 | return request('/api/forms', { 5 | method: 'POST', 6 | data: params, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/form/basic-form/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'POST /api/forms': (_, res) => { 3 | res.send({ 4 | message: 'Ok', 5 | }); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/form/basic-form/model.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { fakeSubmitForm } from './service'; 3 | 4 | const Model = { 5 | namespace: 'formAndbasicForm', 6 | state: {}, 7 | effects: { 8 | *submitRegularForm({ payload }, { call }) { 9 | yield call(fakeSubmitForm, payload); 10 | message.success('提交成功'); 11 | }, 12 | }, 13 | }; 14 | export default Model; 15 | -------------------------------------------------------------------------------- /frontend/src/pages/form/basic-form/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function fakeSubmitForm(params) { 4 | return request('/api/forms', { 5 | method: 'POST', 6 | data: params, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/form/step-form/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'POST /api/forms': (_, res) => { 3 | res.send({ 4 | message: 'Ok', 5 | }); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/form/step-form/model.js: -------------------------------------------------------------------------------- 1 | import { fakeSubmitForm } from './service'; 2 | 3 | const Model = { 4 | namespace: 'formAndstepForm', 5 | state: { 6 | current: 'info', 7 | step: { 8 | payAccount: 'ant-design@alipay.com', 9 | receiverAccount: 'test@example.com', 10 | receiverName: 'Alex', 11 | amount: '500', 12 | }, 13 | }, 14 | effects: { 15 | *submitStepForm({ payload }, { call, put }) { 16 | yield call(fakeSubmitForm, payload); 17 | yield put({ 18 | type: 'saveStepFormData', 19 | payload, 20 | }); 21 | yield put({ 22 | type: 'saveCurrentStep', 23 | payload: 'result', 24 | }); 25 | }, 26 | }, 27 | reducers: { 28 | saveCurrentStep(state, { payload }) { 29 | return { ...state, current: payload }; 30 | }, 31 | 32 | saveStepFormData(state, { payload }) { 33 | return { ...state, step: { ...state.step, ...payload } }; 34 | }, 35 | }, 36 | }; 37 | export default Model; 38 | -------------------------------------------------------------------------------- /frontend/src/pages/form/step-form/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function fakeSubmitForm(params) { 4 | return request('/api/forms', { 5 | method: 'POST', 6 | data: params, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/list/basic-list/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | avatar: string; 3 | name: string; 4 | id: string; 5 | } 6 | 7 | export interface BasicListItemDataType { 8 | id: string; 9 | owner: string; 10 | title: string; 11 | avatar: string; 12 | cover: string; 13 | status: 'normal' | 'exception' | 'active' | 'success'; 14 | percent: number; 15 | logo: string; 16 | href: string; 17 | body?: any; 18 | updatedAt: number; 19 | createdAt: number; 20 | subDescription: string; 21 | description: string; 22 | activeUser: number; 23 | newUser: number; 24 | star: number; 25 | like: number; 26 | message: number; 27 | content: string; 28 | members: Member[]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/list/basic-list/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryFakeList(params) { 4 | return request('/api/fake_list', { 5 | params, 6 | }); 7 | } 8 | export async function removeFakeList(params) { 9 | const { count = 5, ...restParams } = params; 10 | return request('/api/fake_list', { 11 | method: 'POST', 12 | params: { 13 | count, 14 | }, 15 | data: { ...restParams, method: 'delete' }, 16 | }); 17 | } 18 | export async function addFakeList(params) { 19 | const { count = 5, ...restParams } = params; 20 | return request('/api/fake_list', { 21 | method: 'POST', 22 | params: { 23 | count, 24 | }, 25 | data: { ...restParams, method: 'post' }, 26 | }); 27 | } 28 | export async function updateFakeList(params) { 29 | const { count = 5, ...restParams } = params; 30 | return request('/api/fake_list', { 31 | method: 'POST', 32 | params: { 33 | count, 34 | }, 35 | data: { ...restParams, method: 'update' }, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/list/basic-list/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/card-list/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | avatar: string; 3 | name: string; 4 | id: string; 5 | } 6 | 7 | export interface CardListItemDataType { 8 | id: string; 9 | owner: string; 10 | title: string; 11 | avatar: string; 12 | cover: string; 13 | status: 'normal' | 'exception' | 'active' | 'success'; 14 | percent: number; 15 | logo: string; 16 | href: string; 17 | body?: any; 18 | updatedAt: number; 19 | createdAt: number; 20 | subDescription: string; 21 | description: string; 22 | activeUser: number; 23 | newUser: number; 24 | star: number; 25 | like: number; 26 | message: number; 27 | content: string; 28 | members: Member[]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/list/card-list/model.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from './service'; 2 | 3 | const Model = { 4 | namespace: 'listAndcardList', 5 | state: { 6 | list: [], 7 | }, 8 | effects: { 9 | *fetch({ payload }, { call, put }) { 10 | const response = yield call(queryFakeList, payload); 11 | yield put({ 12 | type: 'queryList', 13 | payload: Array.isArray(response) ? response : [], 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | queryList(state, action) { 19 | return { ...state, list: action.payload }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/card-list/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryFakeList(params) { 4 | return request('/api/fake_list', { 5 | params, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/pages/list/card-list/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/components/StandardFormRow/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => { 6 | const cls = classNames(styles.standardFormRow, { 7 | [styles.standardFormRowBlock]: block, 8 | [styles.standardFormRowLast]: last, 9 | [styles.standardFormRowGrid]: grid, 10 | }); 11 | return ( 12 |
13 | {title && ( 14 |
15 | {title} 16 |
17 | )} 18 |
{children}
19 |
20 | ); 21 | }; 22 | 23 | export default StandardFormRow; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/components/TagSelect/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .tagSelect { 4 | position: relative; 5 | max-height: 32px; 6 | margin-left: -8px; 7 | overflow: hidden; 8 | line-height: 32px; 9 | transition: all 0.3s; 10 | user-select: none; 11 | :global { 12 | .ant-tag { 13 | margin-right: 24px; 14 | padding: 0 8px; 15 | font-size: @font-size-base; 16 | } 17 | } 18 | &.expanded { 19 | max-height: 200px; 20 | transition: all 0.3s; 21 | } 22 | .trigger { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | 27 | span.anticon { 28 | font-size: 12px; 29 | } 30 | } 31 | &.hasExpandTag { 32 | padding-right: 50px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | avatar: string; 3 | name: string; 4 | id: string; 5 | } 6 | 7 | export interface ListItemDataType { 8 | id: string; 9 | owner: string; 10 | title: string; 11 | avatar: string; 12 | cover: string; 13 | status: 'normal' | 'exception' | 'active' | 'success'; 14 | percent: number; 15 | logo: string; 16 | href: string; 17 | body?: any; 18 | updatedAt: number; 19 | createdAt: number; 20 | subDescription: string; 21 | description: string; 22 | activeUser: number; 23 | newUser: number; 24 | star: number; 25 | like: number; 26 | message: number; 27 | content: string; 28 | members: Member[]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/model.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from './service'; 2 | 3 | const Model = { 4 | namespace: 'listAndsearchAndapplications', 5 | state: { 6 | list: [], 7 | }, 8 | effects: { 9 | *fetch({ payload }, { call, put }) { 10 | const response = yield call(queryFakeList, payload); 11 | yield put({ 12 | type: 'queryList', 13 | payload: Array.isArray(response) ? response : [], 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | queryList(state, action) { 19 | return { ...state, list: action.payload }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryFakeList(params) { 4 | return request('/api/fake_list', { 5 | params, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .filterCardList { 4 | margin-bottom: -24px; 5 | :global { 6 | .ant-card-meta-content { 7 | margin-top: 0; 8 | } 9 | // disabled white space 10 | .ant-card-meta-avatar { 11 | font-size: 0; 12 | } 13 | 14 | .ant-list .ant-list-item-content-single { 15 | max-width: 100%; 16 | } 17 | } 18 | .cardInfo { 19 | margin-top: 16px; 20 | margin-left: 40px; 21 | zoom: 1; 22 | &::before, 23 | &::after { 24 | display: table; 25 | content: ' '; 26 | } 27 | &::after { 28 | clear: both; 29 | height: 0; 30 | font-size: 0; 31 | visibility: hidden; 32 | } 33 | & > div { 34 | position: relative; 35 | float: left; 36 | width: 50%; 37 | text-align: left; 38 | p { 39 | margin: 0; 40 | font-size: 24px; 41 | line-height: 32px; 42 | } 43 | p:first-child { 44 | margin-bottom: 4px; 45 | color: @text-color-secondary; 46 | font-size: 12px; 47 | line-height: 20px; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/applications/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/components/ArticleListContent/index.jsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from 'antd'; 2 | import React from 'react'; 3 | import moment from 'moment'; 4 | import styles from './index.less'; 5 | 6 | const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => ( 7 |
8 |
{content}
9 |
10 | 11 | {owner} 发布在 {href} 12 | {moment(updatedAt).format('YYYY-MM-DD HH:mm')} 13 |
14 |
15 | ); 16 | 17 | export default ArticleListContent; 18 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/components/ArticleListContent/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .listContent { 4 | .description { 5 | max-width: 720px; 6 | line-height: 22px; 7 | } 8 | .extra { 9 | margin-top: 16px; 10 | color: @text-color-secondary; 11 | line-height: 22px; 12 | & > :global(.ant-avatar) { 13 | position: relative; 14 | top: 1px; 15 | width: 20px; 16 | height: 20px; 17 | margin-right: 8px; 18 | vertical-align: top; 19 | } 20 | & > em { 21 | margin-left: 16px; 22 | color: @disabled-color; 23 | font-style: normal; 24 | } 25 | } 26 | } 27 | 28 | @media screen and (max-width: @screen-xs) { 29 | .listContent { 30 | .extra { 31 | & > em { 32 | display: block; 33 | margin-top: 8px; 34 | margin-left: 0; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/components/StandardFormRow/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => { 6 | const cls = classNames(styles.standardFormRow, { 7 | [styles.standardFormRowBlock]: block, 8 | [styles.standardFormRowLast]: last, 9 | [styles.standardFormRowGrid]: grid, 10 | }); 11 | return ( 12 |
13 | {title && ( 14 |
15 | {title} 16 |
17 | )} 18 |
{children}
19 |
20 | ); 21 | }; 22 | 23 | export default StandardFormRow; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/components/TagSelect/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .tagSelect { 4 | position: relative; 5 | max-height: 32px; 6 | margin-left: -8px; 7 | overflow: hidden; 8 | line-height: 32px; 9 | transition: all 0.3s; 10 | user-select: none; 11 | :global { 12 | .ant-tag { 13 | margin-right: 24px; 14 | padding: 0 8px; 15 | font-size: @font-size-base; 16 | } 17 | } 18 | &.expanded { 19 | max-height: 200px; 20 | transition: all 0.3s; 21 | } 22 | .trigger { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | span.anticon { 27 | font-size: 12px; 28 | } 29 | } 30 | &.hasExpandTag { 31 | padding-right: 50px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | avatar: string; 3 | name: string; 4 | id: string; 5 | } 6 | 7 | export interface ListItemDataType { 8 | id: string; 9 | owner: string; 10 | title: string; 11 | avatar: string; 12 | cover: string; 13 | status: 'normal' | 'exception' | 'active' | 'success'; 14 | percent: number; 15 | logo: string; 16 | href: string; 17 | body?: any; 18 | updatedAt: number; 19 | createdAt: number; 20 | subDescription: string; 21 | description: string; 22 | activeUser: number; 23 | newUser: number; 24 | star: number; 25 | like: number; 26 | message: number; 27 | content: string; 28 | members: Member[]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/model.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from './service'; 2 | 3 | const Model = { 4 | namespace: 'listAndsearchAndarticles', 5 | state: { 6 | list: [], 7 | }, 8 | effects: { 9 | *fetch({ payload }, { call, put }) { 10 | const response = yield call(queryFakeList, payload); 11 | yield put({ 12 | type: 'queryList', 13 | payload: Array.isArray(response) ? response : [], 14 | }); 15 | }, 16 | 17 | *appendFetch({ payload }, { call, put }) { 18 | const response = yield call(queryFakeList, payload); 19 | yield put({ 20 | type: 'appendList', 21 | payload: Array.isArray(response) ? response : [], 22 | }); 23 | }, 24 | }, 25 | reducers: { 26 | queryList(state, action) { 27 | return { ...state, list: action.payload }; 28 | }, 29 | 30 | appendList(state, action) { 31 | return { ...state, list: state.list.concat(action.payload) }; 32 | }, 33 | }, 34 | }; 35 | export default Model; 36 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryFakeList(params) { 4 | return request('/api/fake_list', { 5 | params, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/articles/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | a.listItemMetaTitle { 4 | color: @heading-color; 5 | } 6 | .listItemExtra { 7 | width: 272px; 8 | height: 1px; 9 | } 10 | .selfTrigger { 11 | margin-left: 12px; 12 | } 13 | 14 | @media screen and (max-width: @screen-xs) { 15 | .selfTrigger { 16 | display: block; 17 | margin-left: 0; 18 | } 19 | } 20 | @media screen and (max-width: @screen-md) { 21 | .selfTrigger { 22 | display: block; 23 | margin-left: 0; 24 | } 25 | } 26 | @media screen and (max-width: @screen-lg) { 27 | .listItemExtra { 28 | width: 0; 29 | height: 1px; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .avatarList { 4 | display: inline-block; 5 | ul { 6 | display: inline-block; 7 | margin-left: 8px; 8 | font-size: 0; 9 | } 10 | } 11 | 12 | .avatarItem { 13 | display: inline-block; 14 | width: @avatar-size-base; 15 | height: @avatar-size-base; 16 | margin-left: -8px; 17 | font-size: @font-size-base; 18 | :global { 19 | .ant-avatar { 20 | border: 1px solid #fff; 21 | } 22 | } 23 | } 24 | 25 | .avatarItemLarge { 26 | width: @avatar-size-lg; 27 | height: @avatar-size-lg; 28 | } 29 | 30 | .avatarItemSmall { 31 | width: @avatar-size-sm; 32 | height: @avatar-size-sm; 33 | } 34 | 35 | .avatarItemMini { 36 | width: 20px; 37 | height: 20px; 38 | :global { 39 | .ant-avatar { 40 | width: 20px; 41 | height: 20px; 42 | line-height: 20px; 43 | 44 | .ant-avatar-string { 45 | font-size: 12px; 46 | line-height: 18px; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/components/StandardFormRow/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => { 6 | const cls = classNames(styles.standardFormRow, { 7 | [styles.standardFormRowBlock]: block, 8 | [styles.standardFormRowLast]: last, 9 | [styles.standardFormRowGrid]: grid, 10 | }); 11 | return ( 12 |
13 | {title && ( 14 |
15 | {title} 16 |
17 | )} 18 |
{children}
19 |
20 | ); 21 | }; 22 | 23 | export default StandardFormRow; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/components/TagSelect/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .tagSelect { 4 | position: relative; 5 | max-height: 32px; 6 | margin-left: -8px; 7 | overflow: hidden; 8 | line-height: 32px; 9 | transition: all 0.3s; 10 | user-select: none; 11 | :global { 12 | .ant-tag { 13 | margin-right: 24px; 14 | padding: 0 8px; 15 | font-size: @font-size-base; 16 | } 17 | } 18 | &.expanded { 19 | max-height: 200px; 20 | transition: all 0.3s; 21 | } 22 | .trigger { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | span.anticon { 27 | font-size: 12px; 28 | } 29 | } 30 | &.hasExpandTag { 31 | padding-right: 50px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | avatar: string; 3 | name: string; 4 | id: string; 5 | } 6 | 7 | export interface ListItemDataType { 8 | id: string; 9 | owner: string; 10 | title: string; 11 | avatar: string; 12 | cover: string; 13 | status: 'normal' | 'exception' | 'active' | 'success'; 14 | percent: number; 15 | logo: string; 16 | href: string; 17 | body?: any; 18 | updatedAt: number; 19 | createdAt: number; 20 | subDescription: string; 21 | description: string; 22 | activeUser: number; 23 | newUser: number; 24 | star: number; 25 | like: number; 26 | message: number; 27 | content: string; 28 | members: Member[]; 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/model.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from './service'; 2 | 3 | const Model = { 4 | namespace: 'listAndsearchAndprojects', 5 | state: { 6 | list: [], 7 | }, 8 | effects: { 9 | *fetch({ payload }, { call, put }) { 10 | const response = yield call(queryFakeList, payload); 11 | yield put({ 12 | type: 'queryList', 13 | payload: Array.isArray(response) ? response : [], 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | queryList(state, action) { 19 | return { ...state, list: action.payload }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryFakeList(params) { 4 | return request('/api/fake_list', { 5 | params, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | @import './utils/utils.less'; 3 | 4 | .coverCardList { 5 | margin-bottom: -24px; 6 | 7 | .card { 8 | :global { 9 | .ant-card-meta-title { 10 | margin-bottom: 4px; 11 | & > a { 12 | display: inline-block; 13 | max-width: 100%; 14 | color: @heading-color; 15 | } 16 | } 17 | .ant-card-meta-description { 18 | height: 44px; 19 | overflow: hidden; 20 | line-height: 22px; 21 | } 22 | } 23 | 24 | &:hover { 25 | :global { 26 | .ant-card-meta-title > a { 27 | color: @primary-color; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .cardItemContent { 34 | display: flex; 35 | height: 20px; 36 | margin-top: 16px; 37 | margin-bottom: -4px; 38 | line-height: 20px; 39 | & > span { 40 | flex: 1; 41 | color: @text-color-secondary; 42 | font-size: 12px; 43 | } 44 | .avatarList { 45 | flex: 0 1 auto; 46 | } 47 | } 48 | .cardList { 49 | margin-top: 24px; 50 | } 51 | 52 | :global { 53 | .ant-list .ant-list-item-content-single { 54 | max-width: 100%; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/projects/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/search/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/table-list/components/CreateForm.jsx: -------------------------------------------------------------------------------- 1 | import { Form } from '@ant-design/compatible'; 2 | import '@ant-design/compatible/assets/index.css'; 3 | import { Input, Modal } from 'antd'; 4 | import React from 'react'; 5 | 6 | const FormItem = Form.Item; 7 | 8 | const CreateForm = props => { 9 | const { modalVisible, form, onSubmit: handleAdd, onCancel } = props; 10 | 11 | const okHandle = () => { 12 | form.validateFields((err, fieldsValue) => { 13 | if (err) return; 14 | form.resetFields(); 15 | handleAdd(fieldsValue); 16 | }); 17 | }; 18 | 19 | return ( 20 | onCancel()} 26 | > 27 | 36 | {form.getFieldDecorator('desc', { 37 | rules: [ 38 | { 39 | required: true, 40 | message: '请输入至少五个字符的规则描述!', 41 | min: 5, 42 | }, 43 | ], 44 | })()} 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default Form.create()(CreateForm); 51 | -------------------------------------------------------------------------------- /frontend/src/pages/list/table-list/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface TableListItem { 2 | key: number; 3 | disabled?: boolean; 4 | href: string; 5 | avatar: string; 6 | name: string; 7 | title: string; 8 | owner: string; 9 | desc: string; 10 | callNo: number; 11 | status: number; 12 | updatedAt: Date; 13 | createdAt: Date; 14 | progress: number; 15 | } 16 | 17 | export interface TableListPagination { 18 | total: number; 19 | pageSize: number; 20 | current: number; 21 | } 22 | 23 | export interface TableListData { 24 | list: TableListItem[]; 25 | pagination: Partial; 26 | } 27 | 28 | export interface TableListParams { 29 | sorter?: string; 30 | status?: string; 31 | name?: string; 32 | desc?: string; 33 | key?: number; 34 | pageSize?: number; 35 | currentPage?: number; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/pages/list/table-list/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryRule(params) { 4 | return request('/api/rule', { 5 | params, 6 | }); 7 | } 8 | export async function removeRule(params) { 9 | return request('/api/rule', { 10 | method: 'POST', 11 | data: { ...params, method: 'delete' }, 12 | }); 13 | } 14 | export async function addRule(params) { 15 | return request('/api/rule', { 16 | method: 'POST', 17 | data: { ...params, method: 'post' }, 18 | }); 19 | } 20 | export async function updateRule(params) { 21 | return request('/api/rule', { 22 | method: 'POST', 23 | data: { ...params, method: 'update' }, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/advanced/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface AdvancedOperation1 { 2 | key: string; 3 | type: string; 4 | name: string; 5 | status: string; 6 | updatedAt: string; 7 | memo: string; 8 | } 9 | 10 | export interface AdvancedOperation2 { 11 | key: string; 12 | type: string; 13 | name: string; 14 | status: string; 15 | updatedAt: string; 16 | memo: string; 17 | } 18 | 19 | export interface AdvancedOperation3 { 20 | key: string; 21 | type: string; 22 | name: string; 23 | status: string; 24 | updatedAt: string; 25 | memo: string; 26 | } 27 | 28 | export interface AdvancedProfileData { 29 | advancedOperation1: AdvancedOperation1[]; 30 | advancedOperation2: AdvancedOperation2[]; 31 | advancedOperation3: AdvancedOperation3[]; 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/advanced/model.js: -------------------------------------------------------------------------------- 1 | import { queryAdvancedProfile } from './service'; 2 | 3 | const Model = { 4 | namespace: 'profileAndadvanced', 5 | state: { 6 | advancedOperation1: [], 7 | advancedOperation2: [], 8 | advancedOperation3: [], 9 | }, 10 | effects: { 11 | *fetchAdvanced(_, { call, put }) { 12 | const response = yield call(queryAdvancedProfile); 13 | yield put({ 14 | type: 'show', 15 | payload: response, 16 | }); 17 | }, 18 | }, 19 | reducers: { 20 | show(state, { payload }) { 21 | return { ...state, ...payload }; 22 | }, 23 | }, 24 | }; 25 | export default Model; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/advanced/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryAdvancedProfile() { 4 | return request('/api/profile/advanced'); 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/advanced/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | @import '~codemirror/lib/codemirror.css'; 3 | @import '~codemirror/theme/material.css'; 4 | 5 | .main { 6 | :global { 7 | .ant-descriptions-row > td { 8 | padding-bottom: 8px; 9 | } 10 | .ant-page-header-heading-extra { 11 | flex-direction: column; 12 | } 13 | } 14 | } 15 | 16 | .headerList { 17 | margin-bottom: 4px; 18 | :global { 19 | .ant-descriptions-row > td { 20 | padding-bottom: 8px; 21 | } 22 | } 23 | 24 | .stepDescription { 25 | position: relative; 26 | left: 38px; 27 | padding-top: 8px; 28 | font-size: 14px; 29 | text-align: left; 30 | 31 | > div { 32 | margin-top: 8px; 33 | margin-bottom: 4px; 34 | } 35 | } 36 | } 37 | 38 | .pageHeader { 39 | :global { 40 | .ant-page-header-heading-extra > * + * { 41 | margin-left: 8px; 42 | } 43 | } 44 | .moreInfo { 45 | display: flex; 46 | justify-content: space-between; 47 | width: 200px; 48 | } 49 | } 50 | 51 | @media screen and (max-width: @screen-sm) { 52 | .stepDescription { 53 | left: 8px; 54 | } 55 | .pageHeader { 56 | :global { 57 | .ant-pro-page-header-wrap-row { 58 | flex-direction: column; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/basic/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface BasicGood { 2 | id: string; 3 | name?: string; 4 | barcode?: string; 5 | price?: string; 6 | num?: string | number; 7 | amount?: string | number; 8 | } 9 | 10 | export interface BasicProgress { 11 | key: string; 12 | time: string; 13 | rate: string; 14 | status: string; 15 | operator: string; 16 | cost: string; 17 | } 18 | 19 | export interface BasicProfileDataType { 20 | basicGoods: BasicGood[]; 21 | basicProgress: BasicProgress[]; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/basic/model.js: -------------------------------------------------------------------------------- 1 | import { queryBasicProfile } from './service'; 2 | 3 | const Model = { 4 | namespace: 'profileAndbasic', 5 | state: { 6 | basicGoods: [], 7 | }, 8 | effects: { 9 | *fetchBasic(_, { call, put }) { 10 | const response = yield call(queryBasicProfile); 11 | yield put({ 12 | type: 'show', 13 | payload: response, 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | show(state, { payload }) { 19 | return { ...state, ...payload }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/basic/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryBasicProfile() { 4 | return request('/api/profile/basic'); 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/profile/basic/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .title { 4 | margin-bottom: 16px; 5 | color: @heading-color; 6 | font-weight: 500; 7 | font-size: 16px; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/components/MonitorCodeMirror/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { UnControlled as CodeMirror } from 'react-codemirror2' 3 | import styles from './index.less'; 4 | 5 | require('codemirror/mode/yaml/yaml'); 6 | 7 | class MonitorCodeMirror extends Component { 8 | render() { 9 | const {value, onChange} = this.props; 10 | 11 | return ( 12 | 22 | ); 23 | } 24 | } 25 | 26 | export default MonitorCodeMirror; -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/components/MonitorCodeMirror/index.less: -------------------------------------------------------------------------------- 1 | @import '~codemirror/lib/codemirror.css'; 2 | @import '~codemirror/theme/mdn-like.css'; 3 | 4 | .code { 5 | height: 550px; 6 | :global { 7 | .CodeMirror { 8 | height: 100%; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/components/MonitorEdit/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Descriptions } from 'antd'; 3 | 4 | import styles from './index.less'; 5 | 6 | const MonitorEdit = (props) => { 7 | const { global, scrape_configs } = props.value 8 | const globalKeys = Object.keys(global) 9 | const globalMap = { 10 | 'scrape_interval': '采集周期', 11 | 'scrape_timeout': '采集超时', 12 | 'evaluation_interval': '评估周期', 13 | 'external_labels': '自定义标签', 14 | } 15 | console.log(globalKeys) 16 | return ( 17 | <> 18 | 24 | { 25 | globalKeys.map((key, index) => { 26 | // return key 27 | console.log(globalMap[key], global[key]) 28 | if('external_labels' !== key) return {global[key]} 29 | }) 30 | } 31 | 32 | 33 |

表格配置

34 | 35 | ) 36 | } 37 | 38 | export default MonitorEdit -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/components/MonitorEdit/index.less: -------------------------------------------------------------------------------- 1 | .main { 2 | 3 | } -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/data.d.ts: -------------------------------------------------------------------------------- 1 | export interface AdvancedOperation1 { 2 | key: string; 3 | type: string; 4 | name: string; 5 | status: string; 6 | updatedAt: string; 7 | memo: string; 8 | } 9 | 10 | export interface AdvancedOperation2 { 11 | key: string; 12 | type: string; 13 | name: string; 14 | status: string; 15 | updatedAt: string; 16 | memo: string; 17 | } 18 | 19 | export interface AdvancedOperation3 { 20 | key: string; 21 | type: string; 22 | name: string; 23 | status: string; 24 | updatedAt: string; 25 | memo: string; 26 | } 27 | 28 | export interface AdvancedProfileData { 29 | advancedOperation1: AdvancedOperation1[]; 30 | advancedOperation2: AdvancedOperation2[]; 31 | advancedOperation3: AdvancedOperation3[]; 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/model.js: -------------------------------------------------------------------------------- 1 | import { queryAdvancedProfile } from './service'; 2 | 3 | const Model = { 4 | namespace: 'profileAndadvanced', 5 | state: { 6 | advancedOperation1: [], 7 | advancedOperation2: [], 8 | advancedOperation3: [], 9 | }, 10 | effects: { 11 | *fetchAdvanced(_, { call, put }) { 12 | const response = yield call(queryAdvancedProfile); 13 | yield put({ 14 | type: 'show', 15 | payload: response, 16 | }); 17 | }, 18 | }, 19 | reducers: { 20 | show(state, { payload }) { 21 | return { ...state, ...payload }; 22 | }, 23 | }, 24 | }; 25 | export default Model; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function queryAdvancedProfile() { 4 | return request('/api/profile/advanced'); 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/prometheus/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .headerList { 4 | margin-bottom: 4px; 5 | :global { 6 | .ant-descriptions-row > td { 7 | padding-bottom: 8px; 8 | } 9 | } 10 | 11 | .stepDescription { 12 | position: relative; 13 | left: 38px; 14 | padding-top: 8px; 15 | font-size: 14px; 16 | text-align: left; 17 | 18 | > div { 19 | margin-top: 8px; 20 | margin-bottom: 4px; 21 | } 22 | } 23 | } 24 | 25 | .pageHeader { 26 | :global { 27 | .ant-page-header-heading-extra > * + * { 28 | margin-left: 8px; 29 | } 30 | .ant-descriptions-row > td { 31 | padding-bottom: 8px; 32 | } 33 | .ant-page-header-heading-extra { 34 | flex-direction: column; 35 | } 36 | } 37 | 38 | .card { 39 | margin-bottom: 24px; 40 | 41 | :global { 42 | .ant-legacy-form-item .ant-legacy-form-item-control-wrapper { 43 | width: 100%; 44 | } 45 | } 46 | } 47 | .btn { 48 | margin-top: 24px; 49 | } 50 | .moreInfo { 51 | display: flex; 52 | justify-content: space-between; 53 | width: 200px; 54 | } 55 | } 56 | 57 | @media screen and (max-width: @screen-sm) { 58 | .pageHeader { 59 | :global { 60 | .ant-pro-page-header-wrap-row { 61 | flex-direction: column; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /frontend/src/pages/result/fail/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .error_icon { 4 | color: @highlight-color; 5 | } 6 | .title { 7 | margin-bottom: 16px; 8 | color: @heading-color; 9 | font-weight: 500; 10 | font-size: 16px; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/pages/result/fail/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandfail.error.title': 'Submission Failed', 3 | 'resultandfail.error.description': 4 | 'Please check and modify the following information before resubmitting.', 5 | 'resultandfail.error.hint-title': 'The content you submitted has the following error:', 6 | 'resultandfail.error.hint-text1': 'Your account has been frozen', 7 | 'resultandfail.error.hint-btn1': 'Thaw immediately', 8 | 'resultandfail.error.hint-text2': 'Your account is not yet eligible to apply', 9 | 'resultandfail.error.hint-btn2': 'Upgrade immediately', 10 | 'resultandfail.error.btn-text': 'Return to modify', 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/pages/result/fail/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandfail.error.title': '提交失败', 3 | 'resultandfail.error.description': '请核对并修改以下信息后,再重新提交。', 4 | 'resultandfail.error.hint-title': '您提交的内容有如下错误:', 5 | 'resultandfail.error.hint-text1': '您的账户已被冻结', 6 | 'resultandfail.error.hint-btn1': '立即解冻', 7 | 'resultandfail.error.hint-text2': '您的账户还不具备申请资格', 8 | 'resultandfail.error.hint-btn2': '立即升级', 9 | 'resultandfail.error.btn-text': '返回修改', 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/pages/result/fail/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandfail.error.title': '提交失敗', 3 | 'resultandfail.error.description': '請核對並修改以下信息後,再重新提交。', 4 | 'resultandfail.error.hint-title': '您提交的內容有如下錯誤:', 5 | 'resultandfail.error.hint-text1': '您的賬戶已被凍結', 6 | 'resultandfail.error.hint-btn1': '立即解凍', 7 | 'resultandfail.error.hint-text2': '您的賬戶還不具備申請資格', 8 | 'resultandfail.error.hint-btn2': '立即升級', 9 | 'resultandfail.error.btn-text': '返回修改', 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/pages/result/success/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .title { 4 | position: relative; 5 | color: @text-color; 6 | font-size: 12px; 7 | text-align: center; 8 | } 9 | 10 | .head-title { 11 | margin-bottom: 20px; 12 | color: @heading-color; 13 | font-weight: 500px; 14 | font-size: 16px; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/pages/result/success/locales/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandsuccess.success.title': 'Submission Success', 3 | 'resultandsuccess.success.description': 4 | 'The submission results page is used to feed back the results of a series of operational tasks. If it is a simple operation, use the Message global prompt feedback. This text area can show a simple supplementary explanation. If there is a similar requirement for displaying “documents”, the following gray area can present more complicated content.', 5 | 'resultandsuccess.success.operate-title': 'Project Name', 6 | 'resultandsuccess.success.operate-id': 'Project ID', 7 | 'resultandsuccess.success.principal': 'Principal', 8 | 'resultandsuccess.success.operate-time': 'Effective time', 9 | 'resultandsuccess.success.step1-title': 'Create project', 10 | 'resultandsuccess.success.step1-operator': 'Qu Lili', 11 | 'resultandsuccess.success.step2-title': 'Departmental preliminary review', 12 | 'resultandsuccess.success.step2-operator': 'Zhou Maomao', 13 | 'resultandsuccess.success.step2-extra': 'Urge', 14 | 'resultandsuccess.success.step3-title': 'Financial review', 15 | 'resultandsuccess.success.step4-title': 'Finish', 16 | 'resultandsuccess.success.btn-return': 'Back List', 17 | 'resultandsuccess.success.btn-project': 'View Project', 18 | 'resultandsuccess.success.btn-print': 'Print', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/result/success/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandsuccess.success.title': '提交成功', 3 | 'resultandsuccess.success.description': 4 | '提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。', 5 | 'resultandsuccess.success.operate-title': '项目名称', 6 | 'resultandsuccess.success.operate-id': '项目 ID', 7 | 'resultandsuccess.success.principal': '负责人', 8 | 'resultandsuccess.success.operate-time': '生效时间', 9 | 'resultandsuccess.success.step1-title': '创建项目', 10 | 'resultandsuccess.success.step1-operator': '曲丽丽', 11 | 'resultandsuccess.success.step2-title': '部门初审', 12 | 'resultandsuccess.success.step2-operator': '周毛毛', 13 | 'resultandsuccess.success.step2-extra': '催一下', 14 | 'resultandsuccess.success.step3-title': '财务复核', 15 | 'resultandsuccess.success.step4-title': '完成', 16 | 'resultandsuccess.success.btn-return': '返回列表', 17 | 'resultandsuccess.success.btn-project': '查看项目', 18 | 'resultandsuccess.success.btn-print': '打印', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/result/success/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'resultandsuccess.success.title': '提交成功', 3 | 'resultandsuccess.success.description': 4 | '提交結果頁用於反饋壹系列操作任務的處理結果, 如果僅是簡單操作,使用 Message 全局提示反饋即可。 本文字區域可以展示簡單的補充說明,如果有類似展示 “單據”的需求,下面這個灰色區域可以呈現比較復雜的內容。', 5 | 'resultandsuccess.success.operate-title': '項目名稱', 6 | 'resultandsuccess.success.operate-id': '項目 ID', 7 | 'resultandsuccess.success.principal': '負責人', 8 | 'resultandsuccess.success.operate-time': '生效時間', 9 | 'resultandsuccess.success.step1-title': '創建項目', 10 | 'resultandsuccess.success.step1-operator': '曲麗麗', 11 | 'resultandsuccess.success.step2-title': '部門初審', 12 | 'resultandsuccess.success.step2-operator': '周毛毛', 13 | 'resultandsuccess.success.step2-extra': '催壹下', 14 | 'resultandsuccess.success.step3-title': '財務復核', 15 | 'resultandsuccess.success.step4-title': '完成', 16 | 'resultandsuccess.success.btn-return': '返回列表', 17 | 'resultandsuccess.success.btn-project': '查看項目', 18 | 'resultandsuccess.success.btn-print': '打印', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/user/login/_mock.js: -------------------------------------------------------------------------------- 1 | function getFakeCaptcha(req, res) { 2 | return res.json('captcha-xxx'); 3 | } 4 | 5 | export default { 6 | 'POST /api/login/account': (req, res) => { 7 | const { password, userName, type } = req.body; 8 | 9 | if (password === 'ant.design' && userName === 'admin') { 10 | res.send({ 11 | status: 'ok', 12 | type, 13 | currentAuthority: 'admin', 14 | }); 15 | return; 16 | } 17 | 18 | if (password === 'ant.design' && userName === 'user') { 19 | res.send({ 20 | status: 'ok', 21 | type, 22 | currentAuthority: 'user', 23 | }); 24 | return; 25 | } 26 | 27 | res.send({ 28 | status: 'error', 29 | type, 30 | currentAuthority: 'guest', 31 | }); 32 | }, 33 | 'GET /api/login/captcha': getFakeCaptcha, 34 | }; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/user/login/components/Login/LoginContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const LoginContext = createContext({}); 4 | export default LoginContext; 5 | -------------------------------------------------------------------------------- /frontend/src/pages/user/login/components/Login/LoginSubmit.jsx: -------------------------------------------------------------------------------- 1 | import { Form } from '@ant-design/compatible'; 2 | import '@ant-design/compatible/assets/index.css'; 3 | import { Button } from 'antd'; 4 | import React from 'react'; 5 | import classNames from 'classnames'; 6 | import styles from './index.less'; 7 | 8 | const FormItem = Form.Item; 9 | 10 | const LoginSubmit = ({ className, ...rest }) => { 11 | const clsString = classNames(styles.submit, className); 12 | return ( 13 | 14 | 12 | 13 | 14 | 15 | 16 |
17 | ); 18 | 19 | const RegisterResult = ({ location }) => ( 20 | userandregister-result.register-result.msg} 24 | subTitle="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。" 25 | extra={actions} 26 | /> 27 | ); 28 | 29 | export default RegisterResult; 30 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register-result/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'userandregister-result.login.userName': '用户名', 3 | 'userandregister-result.login.password': '密码', 4 | 'userandregister-result.login.message-invalid-credentials': '账户或密码错误(admin/ant.design)', 5 | 'userandregister-result.login.message-invalid-verification-code': '验证码错误', 6 | 'userandregister-result.login.tab-login-credentials': '账户密码登录', 7 | 'userandregister-result.login.tab-login-mobile': '手机号登录', 8 | 'userandregister-result.login.remember-me': '自动登录', 9 | 'userandregister-result.login.forgot-password': '忘记密码', 10 | 'userandregister-result.login.sign-in-with': '其他登录方式', 11 | 'userandregister-result.login.signup': '注册账户', 12 | 'userandregister-result.login.login': '登录', 13 | 'userandregister-result.register.register': '注册', 14 | 'userandregister-result.register.get-verification-code': '获取验证码', 15 | 'userandregister-result.register.sign-in': '使用已有账户登录', 16 | 'userandregister-result.register-result.msg': '你的账户:{email} 注册成功', 17 | 'userandregister-result.register-result.activation-email': 18 | '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。', 19 | 'userandregister-result.register-result.back-home': '返回首页', 20 | 'userandregister-result.register-result.view-mailbox': '查看邮箱', 21 | 'userandregister-result.navBar.lang': '语言', 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register-result/locales/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'userandregister-result.login.userName': '賬戶', 3 | 'userandregister-result.login.password': '密碼', 4 | 'userandregister-result.login.message-invalid-credentials': '賬戶或密碼錯誤(admin/ant.design)', 5 | 'userandregister-result.login.message-invalid-verification-code': '驗證碼錯誤', 6 | 'userandregister-result.login.tab-login-credentials': '賬戶密碼登錄', 7 | 'userandregister-result.login.tab-login-mobile': '手機號登錄', 8 | 'userandregister-result.login.remember-me': '自動登錄', 9 | 'userandregister-result.login.forgot-password': '忘記密碼', 10 | 'userandregister-result.login.sign-in-with': '其他登錄方式', 11 | 'userandregister-result.login.signup': '註冊賬戶', 12 | 'userandregister-result.login.login': '登錄', 13 | 'userandregister-result.register.register': '註冊', 14 | 'userandregister-result.register.get-verification-code': '獲取驗證碼', 15 | 'userandregister-result.register.sign-in': '使用已有賬戶登錄', 16 | 'userandregister-result.register-result.msg': '妳的賬戶:{email} 註冊成功', 17 | 'userandregister-result.register-result.activation-email': 18 | '激活郵件已發送到妳的郵箱中,郵件有效期為24小時。請及時登錄郵箱,點擊郵件中的鏈接激活帳戶。', 19 | 'userandregister-result.register-result.back-home': '返回首頁', 20 | 'userandregister-result.register-result.view-mailbox': '查看郵箱', 21 | 'userandregister-result.navBar.lang': '語言', 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register-result/style.less: -------------------------------------------------------------------------------- 1 | .registerResult { 2 | width: 800px; 3 | min-height: 400px; 4 | margin: auto; 5 | padding: 80px; 6 | background: none; 7 | :global { 8 | .anticon { 9 | font-size: 64px; 10 | } 11 | } 12 | .title { 13 | margin-top: 32px; 14 | font-size: 20px; 15 | line-height: 28px; 16 | } 17 | .actions { 18 | margin-top: 40px; 19 | a + a { 20 | margin-left: 8px; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'POST /api/register': (_, res) => { 3 | res.send({ 4 | status: 'ok', 5 | currentAuthority: 'user', 6 | }); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register/model.js: -------------------------------------------------------------------------------- 1 | import { fakeRegister } from './service'; 2 | 3 | const Model = { 4 | namespace: 'userAndregister', 5 | state: { 6 | status: undefined, 7 | }, 8 | effects: { 9 | *submit({ payload }, { call, put }) { 10 | const response = yield call(fakeRegister, payload); 11 | yield put({ 12 | type: 'registerHandle', 13 | payload: response, 14 | }); 15 | }, 16 | }, 17 | reducers: { 18 | registerHandle(state, { payload }) { 19 | return { ...state, status: payload.status }; 20 | }, 21 | }, 22 | }; 23 | export default Model; 24 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register/service.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function fakeRegister(params) { 4 | return request('/api/register', { 5 | method: 'POST', 6 | data: params, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/pages/user/register/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | 7 | :global { 8 | .ant-form-item { 9 | margin-bottom: 24px; 10 | } 11 | } 12 | 13 | h3 { 14 | margin-bottom: 20px; 15 | font-size: 16px; 16 | } 17 | 18 | .getCaptcha { 19 | display: block; 20 | width: 100%; 21 | } 22 | 23 | .submit { 24 | width: 50%; 25 | } 26 | 27 | .login { 28 | float: right; 29 | line-height: @btn-height-lg; 30 | } 31 | } 32 | 33 | .success, 34 | .warning, 35 | .error { 36 | transition: color 0.3s; 37 | } 38 | 39 | .success { 40 | color: @success-color; 41 | } 42 | 43 | .warning { 44 | color: @warning-color; 45 | } 46 | 47 | .error { 48 | color: @error-color; 49 | } 50 | 51 | .progress-pass > .progress { 52 | :global { 53 | .ant-progress-bg { 54 | background-color: @warning-color; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/services/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | export async function fakeAccountLogin(params) { 3 | return request('/api/login/account', { 4 | method: 'POST', 5 | data: params, 6 | }); 7 | } 8 | export async function getFakeCaptcha(mobile) { 9 | return request(`/api/login/captcha?mobile=${mobile}`); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/services/prometheus.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function query() { 4 | return request('/api/prometheus/yaml'); 5 | } 6 | 7 | export async function save(params) { 8 | return request('/api/prometheus/yaml/save', { 9 | method: 'POST', 10 | data: params, 11 | }); 12 | } -------------------------------------------------------------------------------- /frontend/src/services/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | export async function query() { 3 | return request('/api/users'); 4 | } 5 | export async function queryCurrent() { 6 | return request('/api/currentUser'); 7 | } 8 | export async function queryNotices() { 9 | return request('/api/notices'); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/utils/Authorized.js: -------------------------------------------------------------------------------- 1 | import RenderAuthorize from '@/components/Authorized'; 2 | import { getAuthority } from './authority'; 3 | /* eslint-disable eslint-comments/disable-enable-pair */ 4 | 5 | /* eslint-disable import/no-mutable-exports */ 6 | 7 | let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component 8 | 9 | const reloadAuthorized = () => { 10 | Authorized = RenderAuthorize(getAuthority()); 11 | }; 12 | /** 13 | * hard code 14 | * block need it。 15 | */ 16 | 17 | window.reloadAuthorized = reloadAuthorized; 18 | export { reloadAuthorized }; 19 | export default Authorized; 20 | -------------------------------------------------------------------------------- /frontend/src/utils/authority.js: -------------------------------------------------------------------------------- 1 | import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project. 2 | 3 | export function getAuthority(str) { 4 | const authorityString = 5 | typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"] 6 | 7 | let authority; 8 | 9 | try { 10 | if (authorityString) { 11 | authority = JSON.parse(authorityString); 12 | } 13 | } catch (e) { 14 | authority = authorityString; 15 | } 16 | 17 | if (typeof authority === 'string') { 18 | return [authority]; 19 | } // preview.pro.ant.design only do not use in your production. 20 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 21 | 22 | if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 23 | return ['admin']; 24 | } 25 | 26 | return authority; 27 | } 28 | export function setAuthority(authority) { 29 | const proAuthority = typeof authority === 'string' ? [authority] : authority; 30 | localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload 31 | 32 | reloadAuthorized(); 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/utils/authority.test.js: -------------------------------------------------------------------------------- 1 | import { getAuthority } from './authority'; 2 | describe('getAuthority should be strong', () => { 3 | it('string', () => { 4 | expect(getAuthority('admin')).toEqual(['admin']); 5 | }); 6 | it('array with double quotes', () => { 7 | expect(getAuthority('"admin"')).toEqual(['admin']); 8 | }); 9 | it('array with single item', () => { 10 | expect(getAuthority('["admin"]')).toEqual(['admin']); 11 | }); 12 | it('array with multiple items', () => { 13 | expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/tests/setupTests.js: -------------------------------------------------------------------------------- 1 | import 'jsdom-global/register'; 2 | 3 | // browserMocks.js 4 | const localStorageMock = (() => { 5 | let store = {}; 6 | 7 | return { 8 | getItem(key) { 9 | return store[key] || null; 10 | }, 11 | setItem(key, value) { 12 | store[key] = value.toString(); 13 | }, 14 | clear() { 15 | store = {}; 16 | }, 17 | }; 18 | })(); 19 | 20 | Object.defineProperty(window, 'localStorage', { 21 | value: localStorageMock, 22 | }); 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module prometheusPro/proBackend 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 7 | github.com/astaxie/beego v1.12.0 8 | github.com/ghodss/yaml v1.0.0 9 | github.com/prometheus/prometheus v1.8.2-0.20191017095924-6f92ce560538 10 | github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef 11 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "prometheusPro/proBackend/routers" 5 | "github.com/astaxie/beego" 6 | ) 7 | 8 | func main() { 9 | beego.Run() 10 | } 11 | 12 | -------------------------------------------------------------------------------- /models/prometheus.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xuyasong 3 | * @Description: 4 | * @File: prometheus 5 | * @Version: 1.0.0 6 | * @Date: 2020-01-21 14:36 7 | */ 8 | 9 | package models 10 | 11 | // ListOption definition 12 | type ListOption struct { 13 | Keyword string 14 | KeywordType string 15 | } 16 | -------------------------------------------------------------------------------- /routers/commentsRouter_controllers.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/context/param" 6 | ) 7 | 8 | func init() { 9 | 10 | beego.GlobalControllerRouter["prometheusPro/proBackend/controllers:PrometheusController"] = append(beego.GlobalControllerRouter["prometheusPro/proBackend/controllers:PrometheusController"], 11 | beego.ControllerComments{ 12 | Method: "GetConfig", 13 | Router: `/config`, 14 | AllowHTTPMethods: []string{"get"}, 15 | MethodParams: param.Make(), 16 | Filters: nil, 17 | Params: nil}) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | 6 | "prometheusPro/proBackend/controllers" 7 | ) 8 | 9 | func init() { 10 | ns := beego.NewNamespace("/api/v1", 11 | beego.NSNamespace("/prometheus", 12 | beego.NSInclude( 13 | &controllers.PrometheusController{}, 14 | ), 15 | ), 16 | beego.NSNamespace("/alertmanager", 17 | beego.NSInclude( 18 | &controllers.PrometheusController{}, 19 | ), 20 | ), 21 | ) 22 | beego.AddNamespace(ns) 23 | } 24 | -------------------------------------------------------------------------------- /services/base.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xuyasong 3 | * @Description: 4 | * @File: base 5 | * @Version: 1.0.0 6 | * @Date: 2020-01-21 14:40 7 | */ 8 | 9 | package services 10 | 11 | // BaseService definition 12 | type BaseService struct { 13 | } 14 | -------------------------------------------------------------------------------- /utils/const.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import "time" 5 | 6 | // RetryCount 7 | const RetryCount = 3 8 | 9 | // RetryTimes 10 | const RetryTimes = 5 11 | 12 | // DateFormat 13 | const DateFormat = "2006-01-02" 14 | 15 | // DateTimeFormat 16 | const DateTimeFormat = "2006-01-02 15:04:05" 17 | 18 | // RequestID 19 | const RequestID ContextKeyType = "RequestID" 20 | 21 | // ControllerTimeout 22 | const ControllerTimeout = 30 * time.Second 23 | 24 | // K8sClientTimeout 25 | const K8sClientTimeout = 3 * time.Second 26 | -------------------------------------------------------------------------------- /utils/date.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/araddon/dateparse" 8 | ) 9 | 10 | // GetDateFromNow 获取距离现在n天前的日期 11 | func GetDateFromNow(days int) string { 12 | // 获取距离现在n天前的日期 13 | nTime := time.Now() 14 | yesTime := nTime.AddDate(0, 0, days) 15 | fromDate := yesTime.Format(DateFormat) 16 | return fromDate 17 | } 18 | 19 | // GetDateTimeFromNow 获取距离现在n天前的时间 20 | func GetDateTimeFromNow(days int) string { 21 | nTime := time.Now() 22 | yesTime := nTime.AddDate(0, 0, days) 23 | fromDateTime := yesTime.Format(DateTimeFormat) 24 | return fromDateTime 25 | } 26 | 27 | // FormatToDateTime 将时间格式化为 时间字符串 28 | func FormatToDateTime(srcTime time.Time) (string, error) { 29 | //将时间格式化为 时间字符串 30 | return srcTime.Format(DateTimeFormat), nil 31 | } 32 | 33 | // FormatToDate 将时间格式化为 日期字符串 34 | func FormatToDate(srcTime time.Time) (string, error) { 35 | return srcTime.Format(DateFormat), nil 36 | } 37 | 38 | // ConvertToDateTime 将时间字符串格式化为 时间格式 39 | func ConvertToDateTime(srcString string) (time.Time, error) { 40 | retTime, err := dateparse.ParseAny(srcString) 41 | if err != nil { 42 | return time.Time{}, err 43 | } 44 | return retTime, nil 45 | } 46 | -------------------------------------------------------------------------------- /utils/date_test.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import ( 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestGetDateFromNow(t *testing.T) { 10 | s := GetDateFromNow(10) 11 | if s == "" { 12 | t.Errorf("GetDateFromNow error") 13 | } 14 | } 15 | 16 | func TestGetDateTimeFromNow(t *testing.T) { 17 | s := GetDateTimeFromNow(10) 18 | if s == "" { 19 | t.Errorf("GetDateTimeFromNow error") 20 | } 21 | } 22 | 23 | func TestFormatToDateTime(t *testing.T) { 24 | _, err := FormatToDateTime(time.Now()) 25 | if err != nil { 26 | t.Errorf(err.Error()) 27 | } 28 | } 29 | 30 | func TestFormatToDate(t *testing.T) { 31 | _, err := FormatToDate(time.Now()) 32 | if err != nil { 33 | t.Errorf(err.Error()) 34 | } 35 | } 36 | 37 | func TestConvertToDateTime(t *testing.T) { 38 | _, err := ConvertToDateTime("2006-01-02 15:04:05") 39 | if err != nil { 40 | t.Errorf(err.Error()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /utils/errorcode_test.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import ( 5 | "testing" 6 | ) 7 | 8 | func TestNewInvalidParam(t *testing.T) { 9 | NewInvalidParam("test") 10 | } 11 | 12 | func TestNewNoSuchObject(t *testing.T) { 13 | NewNoSuchObject("test") 14 | } 15 | 16 | func TestNewInternalServerError(t *testing.T) { 17 | NewInternalServerError("test") 18 | } 19 | 20 | func TestNewMalformedJSON(t *testing.T) { 21 | NewMalformedJSON("test") 22 | } 23 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | 8 | "github.com/satori/go.uuid" 9 | ) 10 | 11 | // ContextKeyType for context.WithValue( 12 | type ContextKeyType string 13 | 14 | // GetRandom 返回 64 位随机字符 15 | func GetRandom() string { 16 | newUUID := uuid.NewV4() 17 | return newUUID.String() 18 | } 19 | 20 | // Message 返回打标的信息 21 | func Message(ctx context.Context, msg string) string { 22 | if ctx != nil { 23 | return fmt.Sprintf("[ReqID:%s] %s", ctx.Value(RequestID).(string), msg) 24 | } else { 25 | return fmt.Sprintf(msg) 26 | } 27 | } 28 | 29 | // OffsetLimit2MinMax function 30 | func OffsetLimit2MinMax(offset, limit, length int) (int, int) { 31 | if offset > length { 32 | return length, length 33 | } 34 | var max = 0 35 | if (offset + limit) > length { 36 | max = length 37 | } else { 38 | max = offset + limit 39 | } 40 | return offset, max 41 | } -------------------------------------------------------------------------------- /utils/helper_test.go: -------------------------------------------------------------------------------- 1 | // Package utils definition 2 | package utils 3 | 4 | import ( 5 | "context" 6 | "testing" 7 | ) 8 | 9 | // TestGetRandom function 10 | func TestGetRandom(t *testing.T) { 11 | if GetRandom() == "" { 12 | t.Errorf("GetRandom error") 13 | } 14 | } 15 | 16 | // TestMessage function 17 | func TestMessage(t *testing.T) { 18 | var ctx context.Context 19 | if Message(ctx, "test") != "test" { 20 | t.Errorf("Message error") 21 | } 22 | } 23 | 24 | // TestOffsetLimit2MinMax function 25 | func TestOffsetLimit2MinMax(t *testing.T) { 26 | OffsetLimit2MinMax(10, 5, 5) 27 | OffsetLimit2MinMax(10, 5, 11) 28 | OffsetLimit2MinMax(10, 5, 15) 29 | } 30 | --------------------------------------------------------------------------------