= (props) => {
+ const { links, linkElement, onAdd } = props;
+ return (
+
+ {links.map((link) =>
+ createElement(
+ linkElement,
+ {
+ key: `linkGroup-item-${link.id || link.title}`,
+ to: link.href,
+ href: link.href,
+ },
+ link.title,
+ ),
+ )}
+
+
+ );
+};
+
+EditableLinkGroup.defaultProps = {
+ links: [],
+ onAdd: () => {},
+ linkElement: 'a',
+};
+
+export default EditableLinkGroup;
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/autoHeight.tsx b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/autoHeight.tsx
new file mode 100644
index 0000000..ed9acd7
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/autoHeight.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+
+export type IReactComponent =
+ | React.StatelessComponent
+ | React.ComponentClass
+ | React.ClassicComponentClass
;
+
+function computeHeight(node: HTMLDivElement) {
+ const { style } = node;
+ style.height = '100%';
+ const totalHeight = parseInt(`${getComputedStyle(node).height}`, 10);
+ const padding =
+ parseInt(`${getComputedStyle(node).paddingTop}`, 10) +
+ parseInt(`${getComputedStyle(node).paddingBottom}`, 10);
+ return totalHeight - padding;
+}
+
+function getAutoHeight(n: HTMLDivElement | undefined) {
+ if (!n) {
+ return 0;
+ }
+
+ const node = n;
+
+ let height = computeHeight(node);
+ const parentNode = node.parentNode as HTMLDivElement;
+ if (parentNode) {
+ height = computeHeight(parentNode);
+ }
+
+ return height;
+}
+
+interface AutoHeightProps {
+ height?: number;
+}
+
+function autoHeight() {
+ return
(
+ WrappedComponent: React.ComponentClass
| React.FC
,
+ ): React.ComponentClass
=> {
+ class AutoHeightComponent extends React.Component
{
+ state = {
+ computedHeight: 0,
+ };
+
+ root: HTMLDivElement | undefined = undefined;
+
+ componentDidMount() {
+ const { height } = this.props;
+ if (!height) {
+ let h = getAutoHeight(this.root);
+ this.setState({ computedHeight: h });
+ if (h < 1) {
+ h = getAutoHeight(this.root);
+ this.setState({ computedHeight: h });
+ }
+ }
+ }
+
+ handleRoot = (node: HTMLDivElement) => {
+ this.root = node;
+ };
+
+ render() {
+ const { height } = this.props;
+ const { computedHeight } = this.state;
+ const h = height || computedHeight;
+ return (
+
+ {h > 0 && }
+
+ );
+ }
+ }
+ return AutoHeightComponent;
+ };
+}
+export default autoHeight;
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.less b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.less
new file mode 100644
index 0000000..f3c1af3
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.less
@@ -0,0 +1,46 @@
+@import '~antd/es/style/themes/default.less';
+
+.radar {
+ .legend {
+ margin-top: 16px;
+ .legendItem {
+ position: relative;
+ color: @text-color-secondary;
+ line-height: 22px;
+ text-align: center;
+ cursor: pointer;
+ p {
+ margin: 0;
+ }
+ h6 {
+ margin-top: 4px;
+ margin-bottom: 0;
+ padding-left: 16px;
+ color: @heading-color;
+ font-size: 24px;
+ line-height: 32px;
+ }
+ &::after {
+ position: absolute;
+ top: 8px;
+ right: 0;
+ width: 1px;
+ height: 40px;
+ background-color: @border-color-split;
+ content: '';
+ }
+ }
+ > :last-child .legendItem::after {
+ display: none;
+ }
+ .dot {
+ position: relative;
+ top: -1px;
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ margin-right: 6px;
+ border-radius: 6px;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.tsx b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.tsx
new file mode 100644
index 0000000..95fca0b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/components/Radar/index.tsx
@@ -0,0 +1,219 @@
+import { Axis, Chart, Coord, Geom, Tooltip } from 'bizcharts';
+import { Col, Row } from 'antd';
+import React, { Component } from 'react';
+
+import autoHeight from './autoHeight';
+import styles from './index.less';
+
+export interface RadarProps {
+ title?: React.ReactNode;
+ height?: number;
+ padding?: [number, number, number, number];
+ hasLegend?: boolean;
+ data: {
+ name: string;
+ label: string;
+ value: string | number;
+ }[];
+ colors?: string[];
+ animate?: boolean;
+ forceFit?: boolean;
+ tickCount?: number;
+ style?: React.CSSProperties;
+}
+interface RadarState {
+ legendData: {
+ checked: boolean;
+ name: string;
+ color: string;
+ percent: number;
+ value: string;
+ }[];
+}
+/* eslint react/no-danger:0 */
+class Radar extends Component {
+ state: RadarState = {
+ legendData: [],
+ };
+
+ chart: G2.Chart | undefined = undefined;
+
+ node: HTMLDivElement | undefined = undefined;
+
+ componentDidMount() {
+ this.getLegendData();
+ }
+
+ componentDidUpdate(preProps: RadarProps) {
+ const { data } = this.props;
+ if (data !== preProps.data) {
+ this.getLegendData();
+ }
+ }
+
+ getG2Instance = (chart: G2.Chart) => {
+ this.chart = chart;
+ };
+
+ // for custom lengend view
+ getLegendData = () => {
+ if (!this.chart) return;
+ const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
+ if (!geom) return;
+ const items = (geom as any).get('dataArray') || []; // 获取图形对应的
+
+ const legendData = items.map((item: { color: any; _origin: any }[]) => {
+ // eslint-disable-next-line no-underscore-dangle
+ const origins = item.map((t) => t._origin);
+ const result = {
+ name: origins[0].name,
+ color: item[0].color,
+ checked: true,
+ value: origins.reduce((p, n) => p + n.value, 0),
+ };
+
+ return result;
+ });
+
+ this.setState({
+ legendData,
+ });
+ };
+
+ handleRef = (n: HTMLDivElement) => {
+ this.node = n;
+ };
+
+ handleLegendClick = (
+ item: {
+ checked: boolean;
+ name: string;
+ },
+ i: string | number,
+ ) => {
+ const newItem = item;
+ newItem.checked = !newItem.checked;
+
+ const { legendData } = this.state;
+ legendData[i] = newItem;
+
+ const filteredLegendData = legendData.filter((l) => l.checked).map((l) => l.name);
+
+ if (this.chart) {
+ this.chart.filter('name', (val) => filteredLegendData.indexOf(`${val}`) > -1);
+ this.chart.repaint();
+ }
+
+ this.setState({
+ legendData,
+ });
+ };
+
+ render() {
+ const defaultColors = [
+ '#1890FF',
+ '#FACC14',
+ '#2FC25B',
+ '#8543E0',
+ '#F04864',
+ '#13C2C2',
+ '#fa8c16',
+ '#a0d911',
+ ];
+
+ const {
+ data = [],
+ height = 0,
+ title,
+ hasLegend = false,
+ forceFit = true,
+ tickCount = 5,
+ padding = [35, 30, 16, 30] as [number, number, number, number],
+ animate = true,
+ colors = defaultColors,
+ } = this.props;
+
+ const { legendData } = this.state;
+
+ const scale = {
+ value: {
+ min: 0,
+ tickCount,
+ },
+ };
+
+ const chartHeight = height - (hasLegend ? 80 : 22);
+
+ return (
+
+ {title &&
{title}
}
+
+
+
+
+
+
+
+
+ {hasLegend && (
+
+ {legendData.map((item, i) => (
+ this.handleLegendClick(item, i)}
+ >
+
+
+
+ {item.name}
+
+
{item.value}
+
+
+ ))}
+
+ )}
+
+ );
+ }
+}
+
+export default autoHeight()(Radar);
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/data.d.ts b/service/stackweb/frontend/src/pages/dashboard/workplace/data.d.ts
new file mode 100644
index 0000000..e4482a9
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/data.d.ts
@@ -0,0 +1,116 @@
+export interface TagType {
+ key: string;
+ label: string;
+}
+export interface VisitDataType {
+ x: string;
+ y: number;
+}
+
+export interface SearchDataType {
+ index: number;
+ keyword: string;
+ count: number;
+ range: number;
+ status: number;
+}
+
+export interface OfflineDataType {
+ name: string;
+ cvr: number;
+}
+
+export interface OfflineChartData {
+ x: any;
+ y1: number;
+ y2: number;
+}
+
+export interface RadarData {
+ name: string;
+ label: string;
+ value: number;
+}
+
+export interface AnalysisData {
+ visitData: VisitDataType[];
+ visitData2: VisitDataType[];
+ salesData: VisitDataType[];
+ searchData: SearchDataType[];
+ offlineData: OfflineDataType[];
+ offlineChartData: OfflineChartData[];
+ salesTypeData: VisitDataType[];
+ salesTypeDataOnline: VisitDataType[];
+ salesTypeDataOffline: VisitDataType[];
+ radarData: RadarData[];
+}
+
+export interface GeographicType {
+ province: {
+ label: string;
+ key: string;
+ };
+ city: {
+ label: string;
+ key: string;
+ };
+}
+
+export interface NoticeType {
+ id: string;
+ title: string;
+ logo: string;
+ description: string;
+ updatedAt: string;
+ member: string;
+ href: string;
+ memberLink: string;
+}
+
+export interface CurrentUser {
+ name: string;
+ avatar: string;
+ userid: string;
+ notice: NoticeType[];
+ email: string;
+ signature: string;
+ title: string;
+ group: string;
+ tags: TagType[];
+ notifyCount: number;
+ unreadCount: number;
+ country: string;
+ geographic: GeographicType;
+ address: string;
+ phone: string;
+}
+export interface Member {
+ avatar: string;
+ name: string;
+ id: string;
+}
+
+export interface ActivitiesType {
+ id: string;
+ updatedAt: string;
+ user: {
+ name: string;
+ avatar: string;
+ };
+ group: {
+ name: string;
+ link: string;
+ };
+ project: {
+ name: string;
+ link: string;
+ };
+
+ template: string;
+}
+
+export interface RadarDataType {
+ label: string;
+ name: string;
+ value: number;
+}
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/index.tsx b/service/stackweb/frontend/src/pages/dashboard/workplace/index.tsx
new file mode 100644
index 0000000..8a1d522
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/index.tsx
@@ -0,0 +1,270 @@
+import { Avatar, Card, Col, List, Skeleton, Row, Statistic } from 'antd';
+import React, { Component } from 'react';
+
+import { Link, Dispatch, connect } from 'umi';
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import moment from 'moment';
+import Radar from './components/Radar';
+import { ModalState } from './model';
+import EditableLinkGroup from './components/EditableLinkGroup';
+import styles from './style.less';
+import { ActivitiesType, CurrentUser, NoticeType, RadarDataType } from './data.d';
+
+const links = [
+ {
+ title: '操作一',
+ href: '',
+ },
+ {
+ title: '操作二',
+ href: '',
+ },
+ {
+ title: '操作三',
+ href: '',
+ },
+ {
+ title: '操作四',
+ href: '',
+ },
+ {
+ title: '操作五',
+ href: '',
+ },
+ {
+ title: '操作六',
+ href: '',
+ },
+];
+
+interface WorkplaceProps {
+ currentUser?: CurrentUser;
+ projectNotice: NoticeType[];
+ activities: ActivitiesType[];
+ radarData: RadarDataType[];
+ dispatch: Dispatch;
+ currentUserLoading: boolean;
+ projectLoading: boolean;
+ activitiesLoading: boolean;
+}
+
+const PageHeaderContent: React.FC<{ currentUser: CurrentUser }> = ({ currentUser }) => {
+ const loading = currentUser && Object.keys(currentUser).length;
+ if (!loading) {
+ return ;
+ }
+ return (
+
+
+
+
+ 早安,
+ {currentUser.name}
+ ,祝你开心每一天!
+
+
+ {currentUser.title} |{currentUser.group}
+
+
+
+ );
+};
+
+const ExtraContent: React.FC<{}> = () => (
+
+);
+
+class Workplace extends Component {
+ componentDidMount() {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'dashboardAndworkplace/init',
+ });
+ }
+
+ componentWillUnmount() {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'dashboardAndworkplace/clear',
+ });
+ }
+
+ renderActivities = (item: ActivitiesType) => {
+ const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => {
+ if (item[key]) {
+ return (
+
+ {item[key].name}
+
+ );
+ }
+ return key;
+ });
+ return (
+
+ }
+ title={
+
+ {item.user.name}
+
+ {events}
+
+ }
+ description={
+
+ {moment(item.updatedAt).fromNow()}
+
+ }
+ />
+
+ );
+ };
+
+ render() {
+ const {
+ currentUser,
+ activities,
+ projectNotice,
+ projectLoading,
+ activitiesLoading,
+ radarData,
+ } = this.props;
+
+ if (!currentUser || !currentUser.userid) {
+ return null;
+ }
+ return (
+ }
+ extraContent={}
+ >
+
+
+ 全部项目}
+ loading={projectLoading}
+ bodyStyle={{ padding: 0 }}
+ >
+ {projectNotice.map((item) => (
+
+
+
+
+ {item.title}
+
+ }
+ description={item.description}
+ />
+
+ {item.member || ''}
+ {item.updatedAt && (
+
+ {moment(item.updatedAt).fromNow()}
+
+ )}
+
+
+
+ ))}
+
+
+
+ loading={activitiesLoading}
+ renderItem={(item) => this.renderActivities(item)}
+ dataSource={activities}
+ className={styles.activitiesList}
+ size="large"
+ />
+
+
+
+
+ {}} links={links} linkElement={Link} />
+
+
+
+
+
+
+
+
+
+ {projectNotice.map((item) => (
+
+
+
+ {item.member}
+
+
+ ))}
+
+
+
+
+
+
+ );
+ }
+}
+
+export default connect(
+ ({
+ dashboardAndworkplace: { currentUser, projectNotice, activities, radarData },
+ loading,
+ }: {
+ dashboardAndworkplace: ModalState;
+ loading: {
+ effects: {
+ [key: string]: boolean;
+ };
+ };
+ }) => ({
+ currentUser,
+ projectNotice,
+ activities,
+ radarData,
+ currentUserLoading: loading.effects['dashboardAndworkplace/fetchUserCurrent'],
+ projectLoading: loading.effects['dashboardAndworkplace/fetchProjectNotice'],
+ activitiesLoading: loading.effects['dashboardAndworkplace/fetchActivitiesList'],
+ }),
+)(Workplace);
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/model.ts b/service/stackweb/frontend/src/pages/dashboard/workplace/model.ts
new file mode 100644
index 0000000..3a2513e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/model.ts
@@ -0,0 +1,98 @@
+import { Effect, Reducer } from 'umi';
+import { ActivitiesType, CurrentUser, NoticeType, RadarDataType } from './data.d';
+import { fakeChartData, queryActivities, queryCurrent, queryProjectNotice } from './service';
+
+export interface ModalState {
+ currentUser?: CurrentUser;
+ projectNotice: NoticeType[];
+ activities: ActivitiesType[];
+ radarData: RadarDataType[];
+}
+
+export interface ModelType {
+ namespace: string;
+ state: ModalState;
+ reducers: {
+ save: Reducer;
+ clear: Reducer;
+ };
+ effects: {
+ init: Effect;
+ fetchUserCurrent: Effect;
+ fetchProjectNotice: Effect;
+ fetchActivitiesList: Effect;
+ fetchChart: Effect;
+ };
+}
+
+const Model: ModelType = {
+ namespace: 'dashboardAndworkplace',
+ state: {
+ currentUser: undefined,
+ projectNotice: [],
+ activities: [],
+ radarData: [],
+ },
+ effects: {
+ *init(_, { put }) {
+ yield put({ type: 'fetchUserCurrent' });
+ yield put({ type: 'fetchProjectNotice' });
+ yield put({ type: 'fetchActivitiesList' });
+ yield put({ type: 'fetchChart' });
+ },
+ *fetchUserCurrent(_, { call, put }) {
+ const response = yield call(queryCurrent);
+ yield put({
+ type: 'save',
+ payload: {
+ currentUser: response,
+ },
+ });
+ },
+ *fetchProjectNotice(_, { call, put }) {
+ const response = yield call(queryProjectNotice);
+ yield put({
+ type: 'save',
+ payload: {
+ projectNotice: Array.isArray(response) ? response : [],
+ },
+ });
+ },
+ *fetchActivitiesList(_, { call, put }) {
+ const response = yield call(queryActivities);
+ yield put({
+ type: 'save',
+ payload: {
+ activities: Array.isArray(response) ? response : [],
+ },
+ });
+ },
+ *fetchChart(_, { call, put }) {
+ const { radarData } = yield call(fakeChartData);
+ yield put({
+ type: 'save',
+ payload: {
+ radarData,
+ },
+ });
+ },
+ },
+ reducers: {
+ save(state, { payload }) {
+ return {
+ ...state,
+ ...payload,
+ };
+ },
+ clear() {
+ return {
+ currentUser: undefined,
+ projectNotice: [],
+ activities: [],
+ radarData: [],
+ };
+ },
+ },
+};
+
+export default Model;
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/service.ts b/service/stackweb/frontend/src/pages/dashboard/workplace/service.ts
new file mode 100644
index 0000000..fcd96eb
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/service.ts
@@ -0,0 +1,17 @@
+import request from 'umi-request';
+
+export async function queryProjectNotice() {
+ return request('/api/project/notice');
+}
+
+export async function queryActivities() {
+ return request('/api/activities');
+}
+
+export async function fakeChartData() {
+ return request('/api/fake_chart_data');
+}
+
+export async function queryCurrent() {
+ return request('/api/currentUser');
+}
diff --git a/service/stackweb/frontend/src/pages/dashboard/workplace/style.less b/service/stackweb/frontend/src/pages/dashboard/workplace/style.less
new file mode 100644
index 0000000..46c10e1
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/dashboard/workplace/style.less
@@ -0,0 +1,250 @@
+@import '~antd/es/style/themes/default.less';
+
+.textOverflow() {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ word-break: break-all;
+}
+
+// mixins for clearfix
+// ------------------------
+.clearfix() {
+ zoom: 1;
+ &::before,
+ &::after {
+ display: table;
+ content: ' ';
+ }
+ &::after {
+ clear: both;
+ height: 0;
+ font-size: 0;
+ visibility: hidden;
+ }
+}
+
+.activitiesList {
+ padding: 0 24px 8px 24px;
+ .username {
+ color: @text-color;
+ }
+ .event {
+ font-weight: normal;
+ }
+}
+
+.pageHeaderContent {
+ display: flex;
+ .avatar {
+ flex: 0 1 72px;
+ & > span {
+ display: block;
+ width: 72px;
+ height: 72px;
+ border-radius: 72px;
+ }
+ }
+ .content {
+ position: relative;
+ top: 4px;
+ flex: 1 1 auto;
+ margin-left: 24px;
+ color: @text-color-secondary;
+ line-height: 22px;
+ .contentTitle {
+ margin-bottom: 12px;
+ color: @heading-color;
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 28px;
+ }
+ }
+}
+
+.extraContent {
+ .clearfix();
+
+ float: right;
+ white-space: nowrap;
+ .statItem {
+ position: relative;
+ display: inline-block;
+ padding: 0 32px;
+ > p:first-child {
+ margin-bottom: 4px;
+ color: @text-color-secondary;
+ font-size: @font-size-base;
+ line-height: 22px;
+ }
+ > p {
+ margin: 0;
+ color: @heading-color;
+ font-size: 30px;
+ line-height: 38px;
+ > span {
+ color: @text-color-secondary;
+ font-size: 20px;
+ }
+ }
+ &::after {
+ position: absolute;
+ top: 8px;
+ right: 0;
+ width: 1px;
+ height: 40px;
+ background-color: @border-color-split;
+ content: '';
+ }
+ &:last-child {
+ padding-right: 0;
+ &::after {
+ display: none;
+ }
+ }
+ }
+}
+
+.members {
+ a {
+ display: block;
+ height: 24px;
+ margin: 12px 0;
+ color: @text-color;
+ transition: all 0.3s;
+ .textOverflow();
+ .member {
+ margin-left: 12px;
+ font-size: @font-size-base;
+ line-height: 24px;
+ vertical-align: top;
+ }
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
+
+.projectList {
+ :global {
+ .ant-card-meta-description {
+ height: 44px;
+ overflow: hidden;
+ color: @text-color-secondary;
+ line-height: 22px;
+ }
+ }
+ .cardTitle {
+ font-size: 0;
+ a {
+ display: inline-block;
+ height: 24px;
+ margin-left: 12px;
+ color: @heading-color;
+ font-size: @font-size-base;
+ line-height: 24px;
+ vertical-align: top;
+ &:hover {
+ color: @primary-color;
+ }
+ }
+ }
+ .projectGrid {
+ width: 33.33%;
+ }
+ .projectItemContent {
+ display: flex;
+ height: 20px;
+ margin-top: 8px;
+ overflow: hidden;
+ font-size: 12px;
+ line-height: 20px;
+ .textOverflow();
+ a {
+ display: inline-block;
+ flex: 1 1 0;
+ color: @text-color-secondary;
+ .textOverflow();
+ &:hover {
+ color: @primary-color;
+ }
+ }
+ .datetime {
+ flex: 0 0 auto;
+ float: right;
+ color: @disabled-color;
+ }
+ }
+}
+
+.datetime {
+ color: @disabled-color;
+}
+
+@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) {
+ .activeCard {
+ margin-bottom: 24px;
+ }
+ .members {
+ margin-bottom: 0;
+ }
+ .extraContent {
+ margin-left: -44px;
+ .statItem {
+ padding: 0 16px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .activeCard {
+ margin-bottom: 24px;
+ }
+ .members {
+ margin-bottom: 0;
+ }
+ .extraContent {
+ float: none;
+ margin-right: 0;
+ .statItem {
+ padding: 0 16px;
+ text-align: left;
+ &::after {
+ display: none;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .extraContent {
+ margin-left: -16px;
+ }
+ .projectList {
+ .projectGrid {
+ width: 50%;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .pageHeaderContent {
+ display: block;
+ .content {
+ margin-left: 0;
+ }
+ }
+ .extraContent {
+ .statItem {
+ float: none;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .projectList {
+ .projectGrid {
+ width: 100%;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/document.ejs b/service/stackweb/frontend/src/pages/document.ejs
new file mode 100644
index 0000000..f476cbd
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/document.ejs
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+ Ant Design Pro
+
+
+
+
+
+
+
+
+
+
+
+ Micro 中国站
+
+
+
+
+
diff --git a/service/stackweb/frontend/src/pages/editor/flow/common/IconFont/index.ts b/service/stackweb/frontend/src/pages/editor/flow/common/IconFont/index.ts
new file mode 100644
index 0000000..987647c
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/common/IconFont/index.ts
@@ -0,0 +1,7 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+const IconFont = createFromIconfontCN({
+ scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js',
+});
+
+export default IconFont;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/FlowContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/FlowContextMenu.tsx
new file mode 100644
index 0000000..32f17aa
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/FlowContextMenu.tsx
@@ -0,0 +1,35 @@
+import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const FlowContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/KoniContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/KoniContextMenu.tsx
new file mode 100644
index 0000000..8b049a5
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/KoniContextMenu.tsx
@@ -0,0 +1,3 @@
+import FlowContextMenu from './FlowContextMenu';
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MenuItem.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MenuItem.tsx
new file mode 100644
index 0000000..a30a658
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MenuItem.tsx
@@ -0,0 +1,27 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface MenuItemProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const MenuItem: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+ {text || upperFirst(command)}
+
+
+ );
+};
+
+export default MenuItem;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MindContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MindContextMenu.tsx
new file mode 100644
index 0000000..2dc5247
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/MindContextMenu.tsx
@@ -0,0 +1,23 @@
+import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const MindContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default MindContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.less b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.less
new file mode 100644
index 0000000..5206a51
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.less
@@ -0,0 +1,39 @@
+.contextMenu {
+ display: none;
+ overflow: hidden;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+ .item {
+ display: flex;
+ align-items: center;
+ padding: 5px 12px;
+ cursor: pointer;
+ transition: all 0.3s;
+ user-select: none;
+
+ &:hover {
+ background: #e6f7ff;
+ }
+
+ span.anticon {
+ margin-right: 8px;
+ }
+ }
+
+ :global {
+ .disable {
+ :local {
+ .item {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ background: #fff;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.tsx
new file mode 100644
index 0000000..6ee9342
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorContextMenu/index.tsx
@@ -0,0 +1,5 @@
+import FlowContextMenu from './FlowContextMenu';
+import KoniContextMenu from './KoniContextMenu';
+import MindContextMenu from './MindContextMenu';
+
+export { FlowContextMenu, MindContextMenu, KoniContextMenu };
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/DetailForm.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/DetailForm.tsx
new file mode 100644
index 0000000..db1901e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/DetailForm.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Card, Input, Select, Form } from 'antd';
+import { withPropsAPI } from 'gg-editor';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+const { Item } = Form;
+const { Option } = Select;
+
+const inlineFormItemLayout = {
+ labelCol: {
+ sm: { span: 8 },
+ },
+ wrapperCol: {
+ sm: { span: 16 },
+ },
+};
+
+interface DetailFormProps {
+ type: string;
+ propsAPI?: any;
+}
+
+class DetailForm extends React.Component {
+ get item() {
+ const { propsAPI } = this.props;
+ return propsAPI.getSelected()[0];
+ }
+
+ handleFieldChange = (values: any) => {
+ const { propsAPI } = this.props;
+ const { getSelected, executeCommand, update } = propsAPI;
+
+ setTimeout(() => {
+ const item = getSelected()[0];
+ if (!item) {
+ return;
+ }
+ executeCommand(() => {
+ update(item, {
+ ...values,
+ });
+ });
+ }, 0);
+ };
+
+ handleInputBlur = (type: string) => (e: React.FormEvent) => {
+ e.preventDefault();
+ this.handleFieldChange({
+ [type]: e.currentTarget.value,
+ });
+ };
+
+ renderNodeDetail = () => {
+ const { label } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderEdgeDetail = () => {
+ const { label = '', shape = 'flow-smooth' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderGroupDetail = () => {
+ const { label = '新建分组' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ render() {
+ const { type } = this.props;
+ if (!this.item) {
+ return null;
+ }
+
+ return (
+
+ {type === 'node' && this.renderNodeDetail()}
+ {type === 'edge' && this.renderEdgeDetail()}
+ {type === 'group' && this.renderGroupDetail()}
+
+ );
+ }
+}
+
+export default withPropsAPI(DetailForm as any);
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/FlowDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/FlowDetailPanel.tsx
new file mode 100644
index 0000000..ce38ea1
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/FlowDetailPanel.tsx
@@ -0,0 +1,28 @@
+import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const FlowDetailPanel = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/KoniDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/KoniDetailPanel.tsx
new file mode 100644
index 0000000..18aea9a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/KoniDetailPanel.tsx
@@ -0,0 +1,3 @@
+import FlowDetailPanel from './FlowDetailPanel';
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/MindDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/MindDetailPanel.tsx
new file mode 100644
index 0000000..bf8f483
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/MindDetailPanel.tsx
@@ -0,0 +1,19 @@
+import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const MindDetailPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default MindDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.less b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.less
new file mode 100644
index 0000000..081945b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.less
@@ -0,0 +1,10 @@
+.detailPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.tsx
new file mode 100644
index 0000000..50aa37a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorDetailPanel/index.tsx
@@ -0,0 +1,5 @@
+import FlowDetailPanel from './FlowDetailPanel';
+import KoniDetailPanel from './KoniDetailPanel';
+import MindDetailPanel from './MindDetailPanel';
+
+export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/FlowItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/FlowItemPanel.tsx
new file mode 100644
index 0000000..dce9855
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/FlowItemPanel.tsx
@@ -0,0 +1,54 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const FlowItemPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default FlowItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/KoniItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/KoniItemPanel.tsx
new file mode 100644
index 0000000..ad446f8
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/KoniItemPanel.tsx
@@ -0,0 +1,53 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const KoniItemPanel = () => (
+
+
+
+
+
+
+
+);
+
+export default KoniItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.less b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.less
new file mode 100644
index 0000000..a7acc36
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.less
@@ -0,0 +1,20 @@
+.itemPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+
+ .ant-card-body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ > div {
+ margin-bottom: 16px;
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.tsx
new file mode 100644
index 0000000..2ba03fb
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorItemPanel/index.tsx
@@ -0,0 +1,4 @@
+import FlowItemPanel from './FlowItemPanel';
+import KoniItemPanel from './KoniItemPanel';
+
+export { FlowItemPanel, KoniItemPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorMinimap/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorMinimap/index.tsx
new file mode 100644
index 0000000..ca27e2f
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorMinimap/index.tsx
@@ -0,0 +1,11 @@
+import { Card } from 'antd';
+import { Minimap } from 'gg-editor';
+import React from 'react';
+
+const EditorMinimap = () => (
+
+
+
+);
+
+export default EditorMinimap;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/FlowToolbar.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/FlowToolbar.tsx
new file mode 100644
index 0000000..fe888be
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/FlowToolbar.tsx
@@ -0,0 +1,30 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/KoniToolbar.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/KoniToolbar.tsx
new file mode 100644
index 0000000..f222007
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/KoniToolbar.tsx
@@ -0,0 +1,3 @@
+import FlowToolbar from './FlowToolbar';
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/MindToolbar.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/MindToolbar.tsx
new file mode 100644
index 0000000..536c06d
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/MindToolbar.tsx
@@ -0,0 +1,25 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/ToolbarButton.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/ToolbarButton.tsx
new file mode 100644
index 0000000..65f7e28
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/ToolbarButton.tsx
@@ -0,0 +1,31 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import { Tooltip } from 'antd';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface ToolbarButtonProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const ToolbarButton: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default ToolbarButton;
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.less b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.less
new file mode 100644
index 0000000..3cef83b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.less
@@ -0,0 +1,39 @@
+.toolbar {
+ display: flex;
+ align-items: center;
+
+ :global {
+ .command .anticon {
+ display: inline-block;
+ width: 27px;
+ height: 27px;
+ margin: 0 6px;
+ padding-top: 6px;
+ text-align: center;
+ border: 1px solid #fff;
+ cursor: pointer;
+
+ &:hover {
+ border: 1px solid #e6e9ed;
+ }
+ }
+
+ .disable .anticon {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ border: 1px solid #fff;
+ }
+ }
+ }
+}
+
+.tooltip {
+ :global {
+ .ant-tooltip-inner {
+ font-size: 12px;
+ border-radius: 0;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.tsx
new file mode 100644
index 0000000..58f1d27
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/components/EditorToolbar/index.tsx
@@ -0,0 +1,5 @@
+import FlowToolbar from './FlowToolbar';
+import KoniToolbar from './KoniToolbar';
+import MindToolbar from './MindToolbar';
+
+export { FlowToolbar, MindToolbar, KoniToolbar };
diff --git a/service/stackweb/frontend/src/pages/editor/flow/index.less b/service/stackweb/frontend/src/pages/editor/flow/index.less
new file mode 100644
index 0000000..aeffa82
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/index.less
@@ -0,0 +1,41 @@
+.editor {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ width: 100%;
+ height: calc(100vh - 250px);
+ background: #fff;
+}
+
+.editorHd {
+ padding: 8px;
+ border: 1px solid #e6e9ed;
+}
+
+.editorBd {
+ flex: 1;
+}
+
+.editorSidebar,
+.editorContent {
+ display: flex;
+ flex-direction: column;
+}
+
+.editorSidebar {
+ background: #fafafa;
+
+ &:first-child {
+ border-right: 1px solid #e6e9ed;
+ }
+
+ &:last-child {
+ border-left: 1px solid #e6e9ed;
+ }
+}
+
+.flow,
+.mind,
+.koni {
+ flex: 1;
+}
diff --git a/service/stackweb/frontend/src/pages/editor/flow/index.tsx b/service/stackweb/frontend/src/pages/editor/flow/index.tsx
new file mode 100644
index 0000000..5d65f64
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/index.tsx
@@ -0,0 +1,44 @@
+import { Col, Row } from 'antd';
+import GGEditor, { Flow } from 'gg-editor';
+
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import React from 'react';
+import { formatMessage } from 'umi';
+import EditorMinimap from './components/EditorMinimap';
+import { FlowContextMenu } from './components/EditorContextMenu';
+import { FlowDetailPanel } from './components/EditorDetailPanel';
+import { FlowItemPanel } from './components/EditorItemPanel';
+import { FlowToolbar } from './components/EditorToolbar';
+import styles from './index.less';
+
+GGEditor.setTrackable(false);
+
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/service/stackweb/frontend/src/pages/editor/flow/locales/en-US.ts b/service/stackweb/frontend/src/pages/editor/flow/locales/en-US.ts
new file mode 100644
index 0000000..5c55a5d
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/locales/en-US.ts
@@ -0,0 +1,4 @@
+export default {
+ 'editorandflow.description':
+ 'The flow chart is an excellent way to represent the idea of the algorithm',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/flow/locales/zh-CN.ts b/service/stackweb/frontend/src/pages/editor/flow/locales/zh-CN.ts
new file mode 100644
index 0000000..5269294
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/flow/locales/zh-CN.ts
@@ -0,0 +1,3 @@
+export default {
+ 'editorandflow.description': '千言万语不如一张图,流程图是表示算法思路的好方法',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/koni/common/IconFont/index.ts b/service/stackweb/frontend/src/pages/editor/koni/common/IconFont/index.ts
new file mode 100644
index 0000000..987647c
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/common/IconFont/index.ts
@@ -0,0 +1,7 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+const IconFont = createFromIconfontCN({
+ scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js',
+});
+
+export default IconFont;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/FlowContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/FlowContextMenu.tsx
new file mode 100644
index 0000000..32f17aa
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/FlowContextMenu.tsx
@@ -0,0 +1,35 @@
+import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const FlowContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/KoniContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/KoniContextMenu.tsx
new file mode 100644
index 0000000..8b049a5
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/KoniContextMenu.tsx
@@ -0,0 +1,3 @@
+import FlowContextMenu from './FlowContextMenu';
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MenuItem.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MenuItem.tsx
new file mode 100644
index 0000000..a30a658
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MenuItem.tsx
@@ -0,0 +1,27 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface MenuItemProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const MenuItem: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+ {text || upperFirst(command)}
+
+
+ );
+};
+
+export default MenuItem;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MindContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MindContextMenu.tsx
new file mode 100644
index 0000000..2dc5247
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/MindContextMenu.tsx
@@ -0,0 +1,23 @@
+import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const MindContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default MindContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.less b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.less
new file mode 100644
index 0000000..3784f4b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.less
@@ -0,0 +1,39 @@
+.contextMenu {
+ display: none;
+ overflow: hidden;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+ .item {
+ display: flex;
+ align-items: center;
+ padding: 5px 12px;
+ cursor: pointer;
+ transition: all 0.3s;
+ user-select: none;
+
+ &:hover {
+ background: #e6f7ff;
+ }
+
+ .anticon {
+ margin-right: 8px;
+ }
+ }
+
+ :global {
+ .disable {
+ :local {
+ .item {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ background: #fff;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.tsx
new file mode 100644
index 0000000..6ee9342
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorContextMenu/index.tsx
@@ -0,0 +1,5 @@
+import FlowContextMenu from './FlowContextMenu';
+import KoniContextMenu from './KoniContextMenu';
+import MindContextMenu from './MindContextMenu';
+
+export { FlowContextMenu, MindContextMenu, KoniContextMenu };
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/DetailForm.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/DetailForm.tsx
new file mode 100644
index 0000000..db1901e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/DetailForm.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Card, Input, Select, Form } from 'antd';
+import { withPropsAPI } from 'gg-editor';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+const { Item } = Form;
+const { Option } = Select;
+
+const inlineFormItemLayout = {
+ labelCol: {
+ sm: { span: 8 },
+ },
+ wrapperCol: {
+ sm: { span: 16 },
+ },
+};
+
+interface DetailFormProps {
+ type: string;
+ propsAPI?: any;
+}
+
+class DetailForm extends React.Component {
+ get item() {
+ const { propsAPI } = this.props;
+ return propsAPI.getSelected()[0];
+ }
+
+ handleFieldChange = (values: any) => {
+ const { propsAPI } = this.props;
+ const { getSelected, executeCommand, update } = propsAPI;
+
+ setTimeout(() => {
+ const item = getSelected()[0];
+ if (!item) {
+ return;
+ }
+ executeCommand(() => {
+ update(item, {
+ ...values,
+ });
+ });
+ }, 0);
+ };
+
+ handleInputBlur = (type: string) => (e: React.FormEvent) => {
+ e.preventDefault();
+ this.handleFieldChange({
+ [type]: e.currentTarget.value,
+ });
+ };
+
+ renderNodeDetail = () => {
+ const { label } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderEdgeDetail = () => {
+ const { label = '', shape = 'flow-smooth' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderGroupDetail = () => {
+ const { label = '新建分组' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ render() {
+ const { type } = this.props;
+ if (!this.item) {
+ return null;
+ }
+
+ return (
+
+ {type === 'node' && this.renderNodeDetail()}
+ {type === 'edge' && this.renderEdgeDetail()}
+ {type === 'group' && this.renderGroupDetail()}
+
+ );
+ }
+}
+
+export default withPropsAPI(DetailForm as any);
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/FlowDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/FlowDetailPanel.tsx
new file mode 100644
index 0000000..ce38ea1
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/FlowDetailPanel.tsx
@@ -0,0 +1,28 @@
+import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const FlowDetailPanel = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/KoniDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/KoniDetailPanel.tsx
new file mode 100644
index 0000000..18aea9a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/KoniDetailPanel.tsx
@@ -0,0 +1,3 @@
+import FlowDetailPanel from './FlowDetailPanel';
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/MindDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/MindDetailPanel.tsx
new file mode 100644
index 0000000..bf8f483
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/MindDetailPanel.tsx
@@ -0,0 +1,19 @@
+import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const MindDetailPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default MindDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.less b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.less
new file mode 100644
index 0000000..081945b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.less
@@ -0,0 +1,10 @@
+.detailPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.tsx
new file mode 100644
index 0000000..50aa37a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorDetailPanel/index.tsx
@@ -0,0 +1,5 @@
+import FlowDetailPanel from './FlowDetailPanel';
+import KoniDetailPanel from './KoniDetailPanel';
+import MindDetailPanel from './MindDetailPanel';
+
+export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/FlowItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/FlowItemPanel.tsx
new file mode 100644
index 0000000..dce9855
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/FlowItemPanel.tsx
@@ -0,0 +1,54 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const FlowItemPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default FlowItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/KoniItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/KoniItemPanel.tsx
new file mode 100644
index 0000000..ad446f8
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/KoniItemPanel.tsx
@@ -0,0 +1,53 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const KoniItemPanel = () => (
+
+
+
+
+
+
+
+);
+
+export default KoniItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.less b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.less
new file mode 100644
index 0000000..a7acc36
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.less
@@ -0,0 +1,20 @@
+.itemPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+
+ .ant-card-body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ > div {
+ margin-bottom: 16px;
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.tsx
new file mode 100644
index 0000000..2ba03fb
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorItemPanel/index.tsx
@@ -0,0 +1,4 @@
+import FlowItemPanel from './FlowItemPanel';
+import KoniItemPanel from './KoniItemPanel';
+
+export { FlowItemPanel, KoniItemPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorMinimap/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorMinimap/index.tsx
new file mode 100644
index 0000000..ca27e2f
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorMinimap/index.tsx
@@ -0,0 +1,11 @@
+import { Card } from 'antd';
+import { Minimap } from 'gg-editor';
+import React from 'react';
+
+const EditorMinimap = () => (
+
+
+
+);
+
+export default EditorMinimap;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/FlowToolbar.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/FlowToolbar.tsx
new file mode 100644
index 0000000..fe888be
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/FlowToolbar.tsx
@@ -0,0 +1,30 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/KoniToolbar.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/KoniToolbar.tsx
new file mode 100644
index 0000000..f222007
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/KoniToolbar.tsx
@@ -0,0 +1,3 @@
+import FlowToolbar from './FlowToolbar';
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/MindToolbar.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/MindToolbar.tsx
new file mode 100644
index 0000000..536c06d
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/MindToolbar.tsx
@@ -0,0 +1,25 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/ToolbarButton.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/ToolbarButton.tsx
new file mode 100644
index 0000000..65f7e28
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/ToolbarButton.tsx
@@ -0,0 +1,31 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import { Tooltip } from 'antd';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface ToolbarButtonProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const ToolbarButton: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default ToolbarButton;
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.less b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.less
new file mode 100644
index 0000000..3cef83b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.less
@@ -0,0 +1,39 @@
+.toolbar {
+ display: flex;
+ align-items: center;
+
+ :global {
+ .command .anticon {
+ display: inline-block;
+ width: 27px;
+ height: 27px;
+ margin: 0 6px;
+ padding-top: 6px;
+ text-align: center;
+ border: 1px solid #fff;
+ cursor: pointer;
+
+ &:hover {
+ border: 1px solid #e6e9ed;
+ }
+ }
+
+ .disable .anticon {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ border: 1px solid #fff;
+ }
+ }
+ }
+}
+
+.tooltip {
+ :global {
+ .ant-tooltip-inner {
+ font-size: 12px;
+ border-radius: 0;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.tsx
new file mode 100644
index 0000000..58f1d27
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/components/EditorToolbar/index.tsx
@@ -0,0 +1,5 @@
+import FlowToolbar from './FlowToolbar';
+import KoniToolbar from './KoniToolbar';
+import MindToolbar from './MindToolbar';
+
+export { FlowToolbar, MindToolbar, KoniToolbar };
diff --git a/service/stackweb/frontend/src/pages/editor/koni/index.less b/service/stackweb/frontend/src/pages/editor/koni/index.less
new file mode 100644
index 0000000..22c1ac0
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/index.less
@@ -0,0 +1,49 @@
+.editor {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ width: 100%;
+ height: calc(100vh - 250px);
+ background: #fff;
+}
+
+.editorHd {
+ padding: 8px;
+ border: 1px solid #e6e9ed;
+}
+
+.editorBd {
+ flex: 1;
+}
+
+.editorSidebar,
+.editorContent {
+ display: flex;
+ flex-direction: column;
+}
+
+.editorContent {
+ :global {
+ .graph-container canvas {
+ vertical-align: middle;
+ }
+ }
+}
+
+.editorSidebar {
+ background: #fafafa;
+
+ &:first-child {
+ border-right: 1px solid #e6e9ed;
+ }
+
+ &:last-child {
+ border-left: 1px solid #e6e9ed;
+ }
+}
+
+.flow,
+.mind,
+.koni {
+ flex: 1;
+}
diff --git a/service/stackweb/frontend/src/pages/editor/koni/index.tsx b/service/stackweb/frontend/src/pages/editor/koni/index.tsx
new file mode 100644
index 0000000..72d6216
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/index.tsx
@@ -0,0 +1,44 @@
+import { Col, Row } from 'antd';
+import GGEditor, { Koni } from 'gg-editor';
+
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import React from 'react';
+import { formatMessage } from 'umi';
+import EditorMinimap from './components/EditorMinimap';
+import { KoniContextMenu } from './components/EditorContextMenu';
+import { KoniDetailPanel } from './components/EditorDetailPanel';
+import { KoniItemPanel } from './components/EditorItemPanel';
+import { KoniToolbar } from './components/EditorToolbar';
+import styles from './index.less';
+
+GGEditor.setTrackable(false);
+
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/service/stackweb/frontend/src/pages/editor/koni/locales/en-US.ts b/service/stackweb/frontend/src/pages/editor/koni/locales/en-US.ts
new file mode 100644
index 0000000..c02fd0e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/locales/en-US.ts
@@ -0,0 +1,4 @@
+export default {
+ 'editorandkoni.description':
+ 'The topology diagram refers to the network structure diagram composed of network node devices and communication media',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/koni/locales/zh-CN.ts b/service/stackweb/frontend/src/pages/editor/koni/locales/zh-CN.ts
new file mode 100644
index 0000000..5e5cd6e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/koni/locales/zh-CN.ts
@@ -0,0 +1,3 @@
+export default {
+ 'editorandkoni.description': '拓扑结构图是指由网络节点设备和通信介质构成的网络结构图',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/mind/common/IconFont/index.ts b/service/stackweb/frontend/src/pages/editor/mind/common/IconFont/index.ts
new file mode 100644
index 0000000..987647c
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/common/IconFont/index.ts
@@ -0,0 +1,7 @@
+import { createFromIconfontCN } from '@ant-design/icons';
+
+const IconFont = createFromIconfontCN({
+ scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js',
+});
+
+export default IconFont;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/FlowContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/FlowContextMenu.tsx
new file mode 100644
index 0000000..32f17aa
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/FlowContextMenu.tsx
@@ -0,0 +1,35 @@
+import { CanvasMenu, ContextMenu, EdgeMenu, GroupMenu, MultiMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const FlowContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/KoniContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/KoniContextMenu.tsx
new file mode 100644
index 0000000..8b049a5
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/KoniContextMenu.tsx
@@ -0,0 +1,3 @@
+import FlowContextMenu from './FlowContextMenu';
+
+export default FlowContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MenuItem.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MenuItem.tsx
new file mode 100644
index 0000000..a30a658
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MenuItem.tsx
@@ -0,0 +1,27 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface MenuItemProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const MenuItem: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+ {text || upperFirst(command)}
+
+
+ );
+};
+
+export default MenuItem;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MindContextMenu.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MindContextMenu.tsx
new file mode 100644
index 0000000..2dc5247
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/MindContextMenu.tsx
@@ -0,0 +1,23 @@
+import { CanvasMenu, ContextMenu, NodeMenu } from 'gg-editor';
+
+import React from 'react';
+import MenuItem from './MenuItem';
+import styles from './index.less';
+
+const MindContextMenu = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default MindContextMenu;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.less b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.less
new file mode 100644
index 0000000..3784f4b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.less
@@ -0,0 +1,39 @@
+.contextMenu {
+ display: none;
+ overflow: hidden;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+ .item {
+ display: flex;
+ align-items: center;
+ padding: 5px 12px;
+ cursor: pointer;
+ transition: all 0.3s;
+ user-select: none;
+
+ &:hover {
+ background: #e6f7ff;
+ }
+
+ .anticon {
+ margin-right: 8px;
+ }
+ }
+
+ :global {
+ .disable {
+ :local {
+ .item {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ background: #fff;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.tsx
new file mode 100644
index 0000000..6ee9342
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorContextMenu/index.tsx
@@ -0,0 +1,5 @@
+import FlowContextMenu from './FlowContextMenu';
+import KoniContextMenu from './KoniContextMenu';
+import MindContextMenu from './MindContextMenu';
+
+export { FlowContextMenu, MindContextMenu, KoniContextMenu };
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/DetailForm.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/DetailForm.tsx
new file mode 100644
index 0000000..db1901e
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/DetailForm.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Card, Input, Select, Form } from 'antd';
+import { withPropsAPI } from 'gg-editor';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+const { Item } = Form;
+const { Option } = Select;
+
+const inlineFormItemLayout = {
+ labelCol: {
+ sm: { span: 8 },
+ },
+ wrapperCol: {
+ sm: { span: 16 },
+ },
+};
+
+interface DetailFormProps {
+ type: string;
+ propsAPI?: any;
+}
+
+class DetailForm extends React.Component {
+ get item() {
+ const { propsAPI } = this.props;
+ return propsAPI.getSelected()[0];
+ }
+
+ handleFieldChange = (values: any) => {
+ const { propsAPI } = this.props;
+ const { getSelected, executeCommand, update } = propsAPI;
+
+ setTimeout(() => {
+ const item = getSelected()[0];
+ if (!item) {
+ return;
+ }
+ executeCommand(() => {
+ update(item, {
+ ...values,
+ });
+ });
+ }, 0);
+ };
+
+ handleInputBlur = (type: string) => (e: React.FormEvent) => {
+ e.preventDefault();
+ this.handleFieldChange({
+ [type]: e.currentTarget.value,
+ });
+ };
+
+ renderNodeDetail = () => {
+ const { label } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderEdgeDetail = () => {
+ const { label = '', shape = 'flow-smooth' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ renderGroupDetail = () => {
+ const { label = '新建分组' } = this.item.getModel();
+
+ return (
+
+ );
+ };
+
+ render() {
+ const { type } = this.props;
+ if (!this.item) {
+ return null;
+ }
+
+ return (
+
+ {type === 'node' && this.renderNodeDetail()}
+ {type === 'edge' && this.renderEdgeDetail()}
+ {type === 'group' && this.renderGroupDetail()}
+
+ );
+ }
+}
+
+export default withPropsAPI(DetailForm as any);
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/FlowDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/FlowDetailPanel.tsx
new file mode 100644
index 0000000..ce38ea1
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/FlowDetailPanel.tsx
@@ -0,0 +1,28 @@
+import { CanvasPanel, DetailPanel, EdgePanel, GroupPanel, MultiPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const FlowDetailPanel = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/KoniDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/KoniDetailPanel.tsx
new file mode 100644
index 0000000..18aea9a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/KoniDetailPanel.tsx
@@ -0,0 +1,3 @@
+import FlowDetailPanel from './FlowDetailPanel';
+
+export default FlowDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/MindDetailPanel.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/MindDetailPanel.tsx
new file mode 100644
index 0000000..bf8f483
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/MindDetailPanel.tsx
@@ -0,0 +1,19 @@
+import { CanvasPanel, DetailPanel, NodePanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import DetailForm from './DetailForm';
+import styles from './index.less';
+
+const MindDetailPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default MindDetailPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.less b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.less
new file mode 100644
index 0000000..081945b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.less
@@ -0,0 +1,10 @@
+.detailPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.tsx
new file mode 100644
index 0000000..50aa37a
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorDetailPanel/index.tsx
@@ -0,0 +1,5 @@
+import FlowDetailPanel from './FlowDetailPanel';
+import KoniDetailPanel from './KoniDetailPanel';
+import MindDetailPanel from './MindDetailPanel';
+
+export { FlowDetailPanel, MindDetailPanel, KoniDetailPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/FlowItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/FlowItemPanel.tsx
new file mode 100644
index 0000000..dce9855
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/FlowItemPanel.tsx
@@ -0,0 +1,54 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const FlowItemPanel = () => (
+
+
+
+
+
+
+
+
+);
+
+export default FlowItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/KoniItemPanel.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/KoniItemPanel.tsx
new file mode 100644
index 0000000..ad446f8
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/KoniItemPanel.tsx
@@ -0,0 +1,53 @@
+import { Item, ItemPanel } from 'gg-editor';
+
+import { Card } from 'antd';
+import React from 'react';
+import styles from './index.less';
+
+const KoniItemPanel = () => (
+
+
+
+
+
+
+
+);
+
+export default KoniItemPanel;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.less b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.less
new file mode 100644
index 0000000..a7acc36
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.less
@@ -0,0 +1,20 @@
+.itemPanel {
+ flex: 1;
+ background: #fafafa;
+
+ :global {
+ .ant-card {
+ background: #fafafa;
+ }
+
+ .ant-card-body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ > div {
+ margin-bottom: 16px;
+ }
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.tsx
new file mode 100644
index 0000000..2ba03fb
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorItemPanel/index.tsx
@@ -0,0 +1,4 @@
+import FlowItemPanel from './FlowItemPanel';
+import KoniItemPanel from './KoniItemPanel';
+
+export { FlowItemPanel, KoniItemPanel };
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorMinimap/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorMinimap/index.tsx
new file mode 100644
index 0000000..ca27e2f
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorMinimap/index.tsx
@@ -0,0 +1,11 @@
+import { Card } from 'antd';
+import { Minimap } from 'gg-editor';
+import React from 'react';
+
+const EditorMinimap = () => (
+
+
+
+);
+
+export default EditorMinimap;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/FlowToolbar.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/FlowToolbar.tsx
new file mode 100644
index 0000000..fe888be
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/FlowToolbar.tsx
@@ -0,0 +1,30 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/KoniToolbar.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/KoniToolbar.tsx
new file mode 100644
index 0000000..f222007
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/KoniToolbar.tsx
@@ -0,0 +1,3 @@
+import FlowToolbar from './FlowToolbar';
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/MindToolbar.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/MindToolbar.tsx
new file mode 100644
index 0000000..536c06d
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/MindToolbar.tsx
@@ -0,0 +1,25 @@
+import { Divider } from 'antd';
+import React from 'react';
+import { Toolbar } from 'gg-editor';
+import ToolbarButton from './ToolbarButton';
+import styles from './index.less';
+
+const FlowToolbar = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default FlowToolbar;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/ToolbarButton.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/ToolbarButton.tsx
new file mode 100644
index 0000000..65f7e28
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/ToolbarButton.tsx
@@ -0,0 +1,31 @@
+import { Command } from 'gg-editor';
+import React from 'react';
+import { Tooltip } from 'antd';
+import IconFont from '../../common/IconFont';
+import styles from './index.less';
+
+const upperFirst = (str: string) =>
+ str.toLowerCase().replace(/( |^)[a-z]/g, (l: string) => l.toUpperCase());
+
+interface ToolbarButtonProps {
+ command: string;
+ icon?: string;
+ text?: string;
+}
+const ToolbarButton: React.FC = (props) => {
+ const { command, icon, text } = props;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default ToolbarButton;
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.less b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.less
new file mode 100644
index 0000000..3cef83b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.less
@@ -0,0 +1,39 @@
+.toolbar {
+ display: flex;
+ align-items: center;
+
+ :global {
+ .command .anticon {
+ display: inline-block;
+ width: 27px;
+ height: 27px;
+ margin: 0 6px;
+ padding-top: 6px;
+ text-align: center;
+ border: 1px solid #fff;
+ cursor: pointer;
+
+ &:hover {
+ border: 1px solid #e6e9ed;
+ }
+ }
+
+ .disable .anticon {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: auto;
+
+ &:hover {
+ border: 1px solid #fff;
+ }
+ }
+ }
+}
+
+.tooltip {
+ :global {
+ .ant-tooltip-inner {
+ font-size: 12px;
+ border-radius: 0;
+ }
+ }
+}
diff --git a/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.tsx
new file mode 100644
index 0000000..58f1d27
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/components/EditorToolbar/index.tsx
@@ -0,0 +1,5 @@
+import FlowToolbar from './FlowToolbar';
+import KoniToolbar from './KoniToolbar';
+import MindToolbar from './MindToolbar';
+
+export { FlowToolbar, MindToolbar, KoniToolbar };
diff --git a/service/stackweb/frontend/src/pages/editor/mind/index.less b/service/stackweb/frontend/src/pages/editor/mind/index.less
new file mode 100644
index 0000000..22c1ac0
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/index.less
@@ -0,0 +1,49 @@
+.editor {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ width: 100%;
+ height: calc(100vh - 250px);
+ background: #fff;
+}
+
+.editorHd {
+ padding: 8px;
+ border: 1px solid #e6e9ed;
+}
+
+.editorBd {
+ flex: 1;
+}
+
+.editorSidebar,
+.editorContent {
+ display: flex;
+ flex-direction: column;
+}
+
+.editorContent {
+ :global {
+ .graph-container canvas {
+ vertical-align: middle;
+ }
+ }
+}
+
+.editorSidebar {
+ background: #fafafa;
+
+ &:first-child {
+ border-right: 1px solid #e6e9ed;
+ }
+
+ &:last-child {
+ border-left: 1px solid #e6e9ed;
+ }
+}
+
+.flow,
+.mind,
+.koni {
+ flex: 1;
+}
diff --git a/service/stackweb/frontend/src/pages/editor/mind/index.tsx b/service/stackweb/frontend/src/pages/editor/mind/index.tsx
new file mode 100644
index 0000000..7daf15b
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/index.tsx
@@ -0,0 +1,41 @@
+import { Col, Row } from 'antd';
+import GGEditor, { Mind } from 'gg-editor';
+
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import React from 'react';
+import { formatMessage } from 'umi';
+import EditorMinimap from './components/EditorMinimap';
+import { MindContextMenu } from './components/EditorContextMenu';
+import { MindDetailPanel } from './components/EditorDetailPanel';
+import { MindToolbar } from './components/EditorToolbar';
+import data from './worldCup2018.json';
+import styles from './index.less';
+
+GGEditor.setTrackable(false);
+
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/service/stackweb/frontend/src/pages/editor/mind/locales/en-US.ts b/service/stackweb/frontend/src/pages/editor/mind/locales/en-US.ts
new file mode 100644
index 0000000..110366c
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/locales/en-US.ts
@@ -0,0 +1,4 @@
+export default {
+ 'editorandmind.description':
+ 'The brain map is an effective graphical thinking tool for expressing divergent thinking. It is simple but effective and is a practical thinking tool',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/mind/locales/zh-CN.ts b/service/stackweb/frontend/src/pages/editor/mind/locales/zh-CN.ts
new file mode 100644
index 0000000..35ca1f5
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/locales/zh-CN.ts
@@ -0,0 +1,4 @@
+export default {
+ 'editorandmind.description':
+ '脑图是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具',
+};
diff --git a/service/stackweb/frontend/src/pages/editor/mind/worldCup2018.json b/service/stackweb/frontend/src/pages/editor/mind/worldCup2018.json
new file mode 100644
index 0000000..44f3e63
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/editor/mind/worldCup2018.json
@@ -0,0 +1,129 @@
+{
+ "roots": [
+ {
+ "label": "法国",
+ "children": [
+ {
+ "label": "克罗地亚",
+ "side": "left",
+ "children": [
+ {
+ "label": "克罗地亚",
+ "children": [
+ {
+ "label": "克罗地亚",
+ "children": [
+ {
+ "label": "克罗地亚"
+ },
+ {
+ "label": "丹麦"
+ }
+ ]
+ },
+ {
+ "label": "俄罗斯",
+ "children": [
+ {
+ "label": "俄罗斯"
+ },
+ {
+ "label": "西班牙"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "label": "英格兰",
+ "children": [
+ {
+ "label": "英格兰",
+ "children": [
+ {
+ "label": "英格兰"
+ },
+ {
+ "label": "哥伦比亚"
+ }
+ ]
+ },
+ {
+ "label": "瑞典",
+ "children": [
+ {
+ "label": "瑞士"
+ },
+ {
+ "label": "瑞典"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "label": "法国",
+ "side": "right",
+ "children": [
+ {
+ "label": "法国",
+ "children": [
+ {
+ "label": "法国",
+ "children": [
+ {
+ "label": "法国"
+ },
+ {
+ "label": "阿根廷"
+ }
+ ]
+ },
+ {
+ "label": "乌拉圭",
+ "children": [
+ {
+ "label": "乌拉圭"
+ },
+ {
+ "label": "葡萄牙"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "label": "比利时",
+ "children": [
+ {
+ "label": "比利时",
+ "children": [
+ {
+ "label": "比利时"
+ },
+ {
+ "label": "日本"
+ }
+ ]
+ },
+ {
+ "label": "巴西",
+ "children": [
+ {
+ "label": "巴西"
+ },
+ {
+ "label": "墨西哥"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/service/stackweb/frontend/src/pages/exception/403/index.tsx b/service/stackweb/frontend/src/pages/exception/403/index.tsx
new file mode 100644
index 0000000..bba61dc
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/403/index.tsx
@@ -0,0 +1,19 @@
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+import React from 'react';
+
+export default () => (
+
+
+
+ }
+ />
+);
diff --git a/service/stackweb/frontend/src/pages/exception/404/index.tsx b/service/stackweb/frontend/src/pages/exception/404/index.tsx
new file mode 100644
index 0000000..c8d77e9
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/404/index.tsx
@@ -0,0 +1,19 @@
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+import React from 'react';
+
+export default () => (
+
+
+
+ }
+ />
+);
diff --git a/service/stackweb/frontend/src/pages/exception/500/index.tsx b/service/stackweb/frontend/src/pages/exception/500/index.tsx
new file mode 100644
index 0000000..aae6b71
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/500/index.tsx
@@ -0,0 +1,19 @@
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+import React from 'react';
+
+export default () => (
+
+
+
+ }
+ />
+);
diff --git a/service/stackweb/frontend/src/pages/exception/500/locales/en-US.ts b/service/stackweb/frontend/src/pages/exception/500/locales/en-US.ts
new file mode 100644
index 0000000..8df6935
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/500/locales/en-US.ts
@@ -0,0 +1,4 @@
+export default {
+ 'exceptionand500.exception.back': 'Back to home',
+ 'exceptionand500.description.500': 'Sorry, the server is reporting an error.',
+};
diff --git a/service/stackweb/frontend/src/pages/exception/500/locales/pt-BR.ts b/service/stackweb/frontend/src/pages/exception/500/locales/pt-BR.ts
new file mode 100644
index 0000000..adf53b6
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/500/locales/pt-BR.ts
@@ -0,0 +1,4 @@
+export default {
+ 'exceptionand500.exception.back': 'Voltar para Início',
+ 'exceptionand500.description.500': 'Desculpe, o servidor está reportando um erro.',
+};
diff --git a/service/stackweb/frontend/src/pages/exception/500/locales/zh-CN.ts b/service/stackweb/frontend/src/pages/exception/500/locales/zh-CN.ts
new file mode 100644
index 0000000..7665563
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/500/locales/zh-CN.ts
@@ -0,0 +1,4 @@
+export default {
+ 'exceptionand500.exception.back': '返回首页',
+ 'exceptionand500.description.500': '抱歉,服务器出错了。',
+};
diff --git a/service/stackweb/frontend/src/pages/exception/500/locales/zh-TW.ts b/service/stackweb/frontend/src/pages/exception/500/locales/zh-TW.ts
new file mode 100644
index 0000000..c3fcd87
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/exception/500/locales/zh-TW.ts
@@ -0,0 +1,4 @@
+export default {
+ 'exceptionand500.exception.back': '返回首頁',
+ 'exceptionand500.description.500': '抱歉,服務器出錯了。',
+};
diff --git a/service/stackweb/frontend/src/pages/form/advanced-form/DemoTest/index.less b/service/stackweb/frontend/src/pages/form/advanced-form/DemoTest/index.less
new file mode 100644
index 0000000..e69de29
diff --git a/service/stackweb/frontend/src/pages/form/advanced-form/DemoTest/index.tsx b/service/stackweb/frontend/src/pages/form/advanced-form/DemoTest/index.tsx
new file mode 100644
index 0000000..ba061c3
--- /dev/null
+++ b/service/stackweb/frontend/src/pages/form/advanced-form/DemoTest/index.tsx
@@ -0,0 +1,285 @@
+import React, {useState} from "react";
+import styles from "./index.less";
+import {
+ Form,
+ Input,
+ Tooltip,
+ Cascader,
+ Select,
+ Row,
+ Col,
+ Checkbox,
+ Button,
+ AutoComplete,
+} from "antd";
+import {QuestionCircleOutlined} from "@ant-design/icons";
+
+const {Option} = Select;
+const AutoCompleteOption = AutoComplete.Option;
+
+const residences = [
+ {
+ value: "zhejiang",
+ label: "Zhejiang",
+ children: [
+ {
+ value: "hangzhou",
+ label: "Hangzhou",
+ children: [
+ {
+ value: "xihu",
+ label: "West Lake",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ value: "jiangsu",
+ label: "Jiangsu",
+ children: [
+ {
+ value: "nanjing",
+ label: "Nanjing",
+ children: [
+ {
+ value: "zhonghuamen",
+ label: "Zhong Hua Men",
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const formItemLayout = {
+ labelCol: {
+ xs: {span: 24},
+ sm: {span: 8},
+ },
+ wrapperCol: {
+ xs: {span: 24},
+ sm: {span: 16},
+ },
+};
+const tailFormItemLayout = {
+ wrapperCol: {
+ xs: {
+ span: 24,
+ offset: 0,
+ },
+ sm: {
+ span: 16,
+ offset: 8,
+ },
+ },
+};
+
+const RegistrationForm = () => {
+ const [form] = Form.useForm();
+
+ const onFinish = (values) => {
+ console.log("Received values of form: ", values);
+ };
+
+ const prefixSelector = (
+
+
+
+ );
+
+ const [autoCompleteResult, setAutoCompleteResult] = useState([]);
+
+ const onWebsiteChange = (value) => {
+ if (!value) {
+ setAutoCompleteResult([]);
+ } else {
+ setAutoCompleteResult(
+ [".com", ".org", ".net"].map((domain) => `${value}${domain}`)
+ );
+ }
+ };
+
+ const websiteOptions = autoCompleteResult.map((website) => ({
+ label: website,
+ value: website,
+ }));
+
+ return (
+
+
+
+
+
+
+
+
+ ({
+ validator(rule, value) {
+ if (!value || getFieldValue("password") === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ "The two passwords that you entered do not match!"
+ );
+ },
+ }),
+ ]}
+ >
+
+
+
+
+ Nickname
+
+
+
+