init:使用acro-cli创建项目,引入reactflow编排引擎
commit
59ffa644ba
@ -0,0 +1 @@
|
||||
node_modules
|
||||
@ -0,0 +1,34 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"prettier",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "16.8"
|
||||
}
|
||||
},
|
||||
"plugins": ["react", "babel", "@typescript-eslint/eslint-plugin"],
|
||||
"rules": {
|
||||
"react/display-name": 0,
|
||||
"react/prop-types": 0
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# local file
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
# eslint
|
||||
.eslintcache
|
||||
|
||||
# stylelint
|
||||
.stylelintcache
|
||||
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
**/*.ts
|
||||
**/*.tsx
|
||||
**/*.jsx
|
||||
**/*.js
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
|
||||
"customSyntax": "postcss-less",
|
||||
"rules": {
|
||||
"selector-class-pattern": null,
|
||||
"no-descending-specificity": null,
|
||||
"no-duplicate-selectors": null,
|
||||
"color-function-notation": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["global"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@ -0,0 +1,46 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/** @type {import('next').NextConfig} */
|
||||
const path = require('path');
|
||||
const withLess = require('next-with-less');
|
||||
const withTM = require('next-transpile-modules')([
|
||||
'@arco-design/web-react',
|
||||
'@arco-themes/react-arco-pro',
|
||||
]);
|
||||
|
||||
const setting = require("./src/settings.json");
|
||||
|
||||
module.exports = withLess(
|
||||
withTM({
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {
|
||||
'arcoblue-6': setting.themeColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
config.resolve.alias['@/assets'] = path.resolve(
|
||||
__dirname,
|
||||
'./src/public/assets'
|
||||
);
|
||||
config.resolve.alias['@'] = path.resolve(__dirname, './src');
|
||||
|
||||
return config;
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
destination: '/dashboard/workplace',
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
pageExtensions: ['tsx'],
|
||||
})
|
||||
);
|
||||
@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "next-app-ts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -H 0.0.0.0",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
"eslint": "eslint src/ --ext .ts,.tsx,.js,.jsx --fix --cache",
|
||||
"stylelint": "stylelint 'src/**/*.less' 'src/**/*.css' --fix --cache",
|
||||
"pre-commit": "pretty-quick --staged && npm run eslint && npm run stylelint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/data-set": "^0.11.8",
|
||||
"@arco-design/color": "^0.4.0",
|
||||
"@arco-design/web-react": "^2.32.2",
|
||||
"@arco-themes/react-arco-pro": "^0.0.7",
|
||||
"@loadable/component": "^5.13.2",
|
||||
"@tinyflow-ai/react": "^1.1.0",
|
||||
"@turf/turf": "^6.5.0",
|
||||
"@xyflow/react": "^12.8.2",
|
||||
"axios": "^0.24.0",
|
||||
"bizcharts": "^4.1.15",
|
||||
"classnames": "^2.3.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"install": "^0.13.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mockjs": "^1.1.0",
|
||||
"next": "12.0.4",
|
||||
"next-cookies": "^2.0.3",
|
||||
"nprogress": "^0.2.0",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "17.0.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"redux": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-design/web-react": "^2.0.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/node": "16.11.7",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"eslint": "7",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"next-with-less": "^2.0.4",
|
||||
"postcss-less": "^5.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"pretty-quick": "^3.1.2",
|
||||
"stylelint": "^14.1.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-standard": "^24.0.0",
|
||||
"typescript": "4.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@arco-design/web-react": ">=2.0.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Chart,
|
||||
Line,
|
||||
Axis,
|
||||
Area,
|
||||
Tooltip,
|
||||
Coordinate,
|
||||
Legend,
|
||||
} from 'bizcharts';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import DataSet from '@antv/data-set';
|
||||
|
||||
interface AreaPolarProps {
|
||||
data: any[];
|
||||
loading: boolean;
|
||||
fields: string[];
|
||||
height: number;
|
||||
}
|
||||
function AreaPolar(props: AreaPolarProps) {
|
||||
const { data, loading, fields, height } = props;
|
||||
|
||||
const { DataView } = DataSet;
|
||||
const dv = new DataView().source(data);
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: fields, // 展开字段集
|
||||
key: 'category', // key字段
|
||||
value: 'score', // value字段
|
||||
});
|
||||
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
height={height || 400}
|
||||
padding={0}
|
||||
data={dv.rows}
|
||||
autoFit
|
||||
scale={{
|
||||
score: {
|
||||
min: 0,
|
||||
max: 80,
|
||||
},
|
||||
}}
|
||||
interactions={['legend-highlight']}
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Coordinate type="polar" radius={0.8} />
|
||||
<Tooltip shared>
|
||||
{(title, items) => {
|
||||
return <CustomTooltip title={title} data={items} />;
|
||||
}}
|
||||
</Tooltip>
|
||||
<Line
|
||||
position="item*score"
|
||||
size="2"
|
||||
color={['category', ['#313CA9', '#21CCFF', '#249EFF']]}
|
||||
/>
|
||||
<Area
|
||||
position="item*score"
|
||||
tooltip={false}
|
||||
color={[
|
||||
'category',
|
||||
[
|
||||
'rgba(49, 60, 169, 0.4)',
|
||||
'rgba(33, 204, 255, 0.4)',
|
||||
'rgba(36, 158, 255, 0.4)',
|
||||
],
|
||||
]}
|
||||
/>
|
||||
<Axis name="score" label={false} />
|
||||
<Legend
|
||||
position="right"
|
||||
marker={(_, index) => {
|
||||
return {
|
||||
symbol: 'circle',
|
||||
style: {
|
||||
r: 4,
|
||||
lineWidth: 0,
|
||||
fill: ['#313CA9', '#21CCFF', '#249EFF'][index],
|
||||
},
|
||||
};
|
||||
}}
|
||||
name="category"
|
||||
/>
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default AreaPolar;
|
||||
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Typography, Badge } from '@arco-design/web-react';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
interface TooltipProps {
|
||||
title: string;
|
||||
data: {
|
||||
name: string;
|
||||
value: string;
|
||||
color: string;
|
||||
}[];
|
||||
color?: string;
|
||||
name?: string;
|
||||
formatter?: (value: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
function CustomTooltip(props: TooltipProps) {
|
||||
const { formatter = (value) => value, color, name } = props;
|
||||
return (
|
||||
<div className={styles['customer-tooltip']}>
|
||||
<div className={styles['customer-tooltip-title']}>
|
||||
<Text bold>{props.title}</Text>
|
||||
</div>
|
||||
<div>
|
||||
{props.data.map((item, index) => (
|
||||
<div className={styles['customer-tooltip-item']} key={index}>
|
||||
<div>
|
||||
<Badge color={color || item.color} />
|
||||
{name || item.name}
|
||||
</div>
|
||||
<div>
|
||||
<Text bold>{formatter(item.value)}</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTooltip;
|
||||
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { Chart, Legend, Facet } from 'bizcharts';
|
||||
import useBizTheme from '@/utils/useChartTheme';
|
||||
|
||||
interface FactMultiPieProps {
|
||||
data: any[];
|
||||
loading: boolean;
|
||||
height: number;
|
||||
}
|
||||
function FactMultiPie(props: FactMultiPieProps) {
|
||||
return (
|
||||
<Chart
|
||||
theme={useBizTheme()}
|
||||
forceUpdate
|
||||
autoFit
|
||||
data={props.data}
|
||||
height={props.height || 400}
|
||||
padding={[0, 0, 10, 0]}
|
||||
>
|
||||
<Legend visible={true} />
|
||||
<Facet
|
||||
fields={['category']}
|
||||
type="rect"
|
||||
showTitle={false}
|
||||
eachView={(view, facet) => {
|
||||
const data = facet.data;
|
||||
view.coordinate({
|
||||
type: 'theta',
|
||||
cfg: {
|
||||
radius: 0.8,
|
||||
innerRadius: 0.7,
|
||||
},
|
||||
});
|
||||
view
|
||||
.interval()
|
||||
.adjust('stack')
|
||||
.position('value')
|
||||
.color('type', [
|
||||
'#249eff',
|
||||
'#846BCE',
|
||||
'#21CCFF',
|
||||
' #86DF6C',
|
||||
'#0E42D2',
|
||||
])
|
||||
.label('value', {
|
||||
content: (content) => {
|
||||
return `${(content.value * 100).toFixed(2)} %`;
|
||||
},
|
||||
}),
|
||||
view.annotation().text({
|
||||
position: ['50%', '46%'],
|
||||
content: data[0].category,
|
||||
style: {
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
textAlign: 'center',
|
||||
},
|
||||
offsetY: 10,
|
||||
});
|
||||
view.interaction('element-single-selected');
|
||||
}}
|
||||
/>
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
|
||||
export default FactMultiPie;
|
||||
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import { Chart, Tooltip, Interval, Axis, Coordinate, G2 } from 'bizcharts';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
|
||||
function HorizontalInterval({
|
||||
data,
|
||||
loading,
|
||||
height,
|
||||
}: {
|
||||
data: any[];
|
||||
loading: boolean;
|
||||
height?: number;
|
||||
}) {
|
||||
G2.registerShape('interval', 'border-radius', {
|
||||
draw(cfg, container) {
|
||||
const points = cfg.points as unknown as { x: string; y: number };
|
||||
let path = [];
|
||||
path.push(['M', points[0].x, points[0].y]);
|
||||
path.push(['L', points[1].x, points[1].y]);
|
||||
path.push(['L', points[2].x, points[2].y]);
|
||||
path.push(['L', points[3].x, points[3].y]);
|
||||
path.push('Z');
|
||||
path = this.parsePath(path); // 将 0 - 1 转化为画布坐标
|
||||
|
||||
const group = container.addGroup();
|
||||
const radius = (path[1][2] - path[2][2]) / 2;
|
||||
group.addShape('rect', {
|
||||
attrs: {
|
||||
x: path[0][1], // 矩形起始点为左上角
|
||||
y: path[0][2] - radius * 2,
|
||||
width: path[1][1] - path[0][1],
|
||||
height: path[1][2] - path[2][2],
|
||||
fill: cfg.color,
|
||||
radius: radius,
|
||||
},
|
||||
});
|
||||
return group;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
height={height || 370}
|
||||
padding="auto"
|
||||
data={data}
|
||||
autoFit
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Coordinate transpose />
|
||||
<Interval
|
||||
color="#4086FF"
|
||||
position="name*count"
|
||||
size={10}
|
||||
shape="border-radius"
|
||||
/>
|
||||
<Tooltip>
|
||||
{(title, items) => {
|
||||
return <CustomTooltip title={title} data={items} />;
|
||||
}}
|
||||
</Tooltip>
|
||||
<Axis
|
||||
name="count"
|
||||
label={{
|
||||
formatter(text) {
|
||||
return `${Number(text) / 1000}k`;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default HorizontalInterval;
|
||||
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { Chart, Line, Axis, Legend, Area, Tooltip } from 'bizcharts';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
|
||||
const areaColorMap = [
|
||||
'l (90) 0:rgba(131, 100, 255, 0.5) 1:rgba(80, 52, 255, 0.001)',
|
||||
'l (90) 0:rgba(100, 255, 236, 0.5) 1:rgba(52, 255, 243, 0.001)',
|
||||
'l (90) 0:rgba(255, 211, 100, 0.5) 1:rgba(255, 235, 52, 0.001)',
|
||||
'l (90) 0:rgba(100, 162, 255, 0.5) 1:rgba(52, 105, 255, 0.001)',
|
||||
];
|
||||
|
||||
const lineColorMap = ['#722ED1', '#33D1C9', '#F77234', '#165DFF'];
|
||||
|
||||
function MultiAreaLine({ data, loading }: { data: any[]; loading: boolean }) {
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
height={352}
|
||||
data={data}
|
||||
padding={[10, 0, 30, 30]}
|
||||
autoFit
|
||||
scale={{ time: 'time' }}
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Line
|
||||
shape="smooth"
|
||||
position="time*count"
|
||||
color={['name', lineColorMap]}
|
||||
/>
|
||||
<Area
|
||||
position="time*count"
|
||||
shape="smooth"
|
||||
color={['name', areaColorMap]}
|
||||
tooltip={false}
|
||||
/>
|
||||
<Tooltip
|
||||
crosshairs={{ type: 'x' }}
|
||||
showCrosshairs
|
||||
shared
|
||||
showMarkers={true}
|
||||
>
|
||||
{(title, items) => {
|
||||
return (
|
||||
<CustomTooltip
|
||||
title={title}
|
||||
data={items.sort((a, b) => b.value - a.value)}
|
||||
formatter={(value) => Number(value).toLocaleString()}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Tooltip>
|
||||
<Axis
|
||||
name="count"
|
||||
label={{ formatter: (value) => `${Number(value) / 100} k` }}
|
||||
/>
|
||||
<Legend visible={false} />
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default MultiAreaLine;
|
||||
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Chart, Tooltip, Interval, Axis, Legend } from 'bizcharts';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
|
||||
function MultiInterval({ data, loading }: { data: any[]; loading: boolean }) {
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
height={370}
|
||||
padding="auto"
|
||||
data={data}
|
||||
autoFit
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Interval
|
||||
adjust="stack"
|
||||
color={['name', ['#81E2FF', '#00B2FF', '#246EFF']]}
|
||||
position="time*count"
|
||||
size={16}
|
||||
style={{
|
||||
radius: [2, 2, 0, 0],
|
||||
}}
|
||||
/>
|
||||
<Tooltip crosshairs={{ type: 'x' }} showCrosshairs shared>
|
||||
{(title, items) => {
|
||||
return <CustomTooltip title={title} data={items} />;
|
||||
}}
|
||||
</Tooltip>
|
||||
<Axis
|
||||
name="count"
|
||||
label={{
|
||||
formatter(text) {
|
||||
return `${Number(text) / 1000}k`;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Legend name="name" marker={{ symbol: 'circle' }} />
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default MultiInterval;
|
||||
@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Chart, Line, Axis, Area, Tooltip } from 'bizcharts';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
|
||||
function OverviewAreaLine({
|
||||
data,
|
||||
loading,
|
||||
name = '总内容量',
|
||||
color = '#4080FF',
|
||||
}: {
|
||||
data: any[];
|
||||
loading: boolean;
|
||||
name?: string;
|
||||
color?: string;
|
||||
}) {
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
scale={{ value: { min: 0 } }}
|
||||
padding={[10, 20, 50, 40]}
|
||||
autoFit
|
||||
height={300}
|
||||
data={data}
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Axis
|
||||
name="count"
|
||||
title
|
||||
grid={{
|
||||
line: {
|
||||
style: {
|
||||
lineDash: [4, 4],
|
||||
},
|
||||
},
|
||||
}}
|
||||
label={{
|
||||
formatter(text) {
|
||||
return `${Number(text) / 1000}k`;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Axis name="date" grid={{ line: { style: { stroke: '#E5E8EF' } } }} />
|
||||
<Line
|
||||
shape="smooth"
|
||||
position="date*count"
|
||||
size={3}
|
||||
color="l (0) 0:#1EE7FF .57:#249AFF .85:#6F42FB"
|
||||
/>
|
||||
<Area
|
||||
position="date*count"
|
||||
shape="smooth"
|
||||
color="l (90) 0:rgba(17, 126, 255, 0.5) 1:rgba(17, 128, 255, 0)"
|
||||
/>
|
||||
<Tooltip
|
||||
showCrosshairs={true}
|
||||
showMarkers={true}
|
||||
marker={{
|
||||
lineWidth: 3,
|
||||
stroke: color,
|
||||
fill: '#ffffff',
|
||||
symbol: 'circle',
|
||||
r: 8,
|
||||
}}
|
||||
>
|
||||
{(title, items) => {
|
||||
return (
|
||||
<CustomTooltip
|
||||
title={title}
|
||||
data={items}
|
||||
color={color}
|
||||
name={name}
|
||||
formatter={(value) => Number(value).toLocaleString()}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Tooltip>
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default OverviewAreaLine;
|
||||
@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { Chart, Line, Axis, Tooltip, Legend, Slider } from 'bizcharts';
|
||||
import { Spin } from '@arco-design/web-react';
|
||||
import CustomTooltip from './customer-tooltip';
|
||||
import useBizTheme from '@/utils/useChartTheme';
|
||||
|
||||
const lineColor = ['#21CCFF', '#313CA9', '#249EFF'];
|
||||
function PeriodLine({ data, loading }: { data: any[]; loading: boolean }) {
|
||||
return (
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<Chart
|
||||
theme={useBizTheme()}
|
||||
forceUpdate
|
||||
height={370}
|
||||
padding={[10, 20, 120, 60]}
|
||||
data={data}
|
||||
autoFit
|
||||
scale={{ time: 'time' }}
|
||||
className={'chart-wrapper'}
|
||||
>
|
||||
<Line shape="smooth" position="time*rate" color={['name', lineColor]} />
|
||||
<Tooltip crosshairs={{ type: 'x' }} showCrosshairs shared>
|
||||
{(title, items) => {
|
||||
return <CustomTooltip title={title} data={items} />;
|
||||
}}
|
||||
</Tooltip>
|
||||
<Axis
|
||||
name="rate"
|
||||
label={{
|
||||
formatter(text) {
|
||||
return `${Number(text)} %`;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Legend
|
||||
name="name"
|
||||
marker={(_, index) => {
|
||||
return {
|
||||
symbol: 'circle',
|
||||
style: {
|
||||
fill: lineColor[index],
|
||||
r: 4,
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
<Slider
|
||||
foregroundStyle={{
|
||||
borderRadius: ' 4px',
|
||||
fill: 'l (180) 0:rgba(206, 224, 255, 0.9) 1:rgba(146, 186, 255, 0.8)',
|
||||
opacity: 0.3,
|
||||
}}
|
||||
trendCfg={{
|
||||
data: data.map((item) => item.rate),
|
||||
isArea: true,
|
||||
areaStyle: {
|
||||
fill: 'rgba(4, 135, 255, 0.15)',
|
||||
opacity: 1,
|
||||
},
|
||||
backgroundStyle: {
|
||||
fill: '#F2F3F5',
|
||||
},
|
||||
lineStyle: {
|
||||
stroke: 'rgba(36, 158, 255, 0.3)',
|
||||
lineWidth: 2,
|
||||
},
|
||||
}}
|
||||
handlerStyle={{
|
||||
fill: '#ffffff',
|
||||
opacity: 1,
|
||||
width: 22,
|
||||
height: 22,
|
||||
stroke: '#165DFF',
|
||||
}}
|
||||
/>
|
||||
</Chart>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
export default PeriodLine;
|
||||
@ -0,0 +1,36 @@
|
||||
.customer-tooltip {
|
||||
&-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&-item {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px;
|
||||
background: rgb(255 255 255 / 90%);
|
||||
box-shadow: 6px 0 20px rgb(34 87 188 / 10%);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-2);
|
||||
|
||||
:global(.arco-badge-status-dot) {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-item:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
body[arco-theme='dark'] {
|
||||
.customer-tooltip {
|
||||
&-item {
|
||||
background: #2a2a2b;
|
||||
box-shadow: 6px 0px 20px rgba(34, 87, 188, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Layout } from '@arco-design/web-react';
|
||||
import { FooterProps } from '@arco-design/web-react/es/Layout/interface';
|
||||
import cs from 'classnames';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
function Footer(props: FooterProps = {}) {
|
||||
const { className, ...restProps } = props;
|
||||
return "";
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
@ -0,0 +1,8 @@
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import {
|
||||
Trigger,
|
||||
Badge,
|
||||
Tabs,
|
||||
Avatar,
|
||||
Spin,
|
||||
Button,
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
IconMessage,
|
||||
IconCustomerService,
|
||||
IconFile,
|
||||
IconDesktop,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import useLocale from '../../utils/useLocale';
|
||||
import MessageList, { MessageListType } from './list';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
function DropContent() {
|
||||
const t = useLocale();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [groupData, setGroupData] = useState<{
|
||||
[key: string]: MessageListType;
|
||||
}>({});
|
||||
const [sourceData, setSourceData] = useState<MessageListType>([]);
|
||||
|
||||
function fetchSourceData(showLoading = true) {
|
||||
showLoading && setLoading(true);
|
||||
axios
|
||||
.get('/api/message/list')
|
||||
.then((res) => {
|
||||
setSourceData(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
showLoading && setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function readMessage(data: MessageListType) {
|
||||
const ids = data.map((item) => item.id);
|
||||
axios
|
||||
.post('/api/message/read', {
|
||||
ids,
|
||||
})
|
||||
.then(() => {
|
||||
fetchSourceData();
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchSourceData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const groupData: { [key: string]: MessageListType } = groupBy(
|
||||
sourceData,
|
||||
'type'
|
||||
);
|
||||
setGroupData(groupData);
|
||||
}, [sourceData]);
|
||||
|
||||
const tabList = [
|
||||
{
|
||||
key: 'message',
|
||||
title: t['message.tab.title.message'],
|
||||
titleIcon: <IconMessage />,
|
||||
},
|
||||
{
|
||||
key: 'notice',
|
||||
title: t['message.tab.title.notice'],
|
||||
titleIcon: <IconCustomerService />,
|
||||
},
|
||||
{
|
||||
key: 'todo',
|
||||
title: t['message.tab.title.todo'],
|
||||
titleIcon: <IconFile />,
|
||||
avatar: (
|
||||
<Avatar style={{ backgroundColor: '#0FC6C2' }}>
|
||||
<IconDesktop />
|
||||
</Avatar>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles['message-box']}>
|
||||
<Spin loading={loading} style={{ display: 'block' }}>
|
||||
<Tabs
|
||||
overflow="dropdown"
|
||||
type="rounded"
|
||||
defaultActiveTab="message"
|
||||
destroyOnHide
|
||||
extra={
|
||||
<Button type="text" onClick={() => setSourceData([])}>
|
||||
{t['message.empty']}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{tabList.map((item) => {
|
||||
const { key, title, avatar } = item;
|
||||
const data = groupData[key] || [];
|
||||
const unReadData = data.filter((item) => !item.status);
|
||||
return (
|
||||
<Tabs.TabPane
|
||||
key={key}
|
||||
title={
|
||||
<span>
|
||||
{title}
|
||||
{unReadData.length ? `(${unReadData.length})` : ''}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<MessageList
|
||||
data={data}
|
||||
unReadData={unReadData}
|
||||
onItemClick={(item) => {
|
||||
readMessage([item]);
|
||||
}}
|
||||
onAllBtnClick={(unReadData) => {
|
||||
readMessage(unReadData);
|
||||
}}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MessageBox({ children }) {
|
||||
return (
|
||||
<Trigger
|
||||
trigger="hover"
|
||||
popup={() => <DropContent />}
|
||||
position="br"
|
||||
unmountOnExit={false}
|
||||
popupAlign={{ bottom: 4 }}
|
||||
>
|
||||
<Badge count={9} dot>
|
||||
{children}
|
||||
</Badge>
|
||||
</Trigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageBox;
|
||||
@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
List,
|
||||
Avatar,
|
||||
Typography,
|
||||
Button,
|
||||
Space,
|
||||
Result,
|
||||
Tag,
|
||||
} from '@arco-design/web-react';
|
||||
import useLocale from '../../utils/useLocale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
export interface MessageItemData {
|
||||
id: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
avatar?: string;
|
||||
content: string;
|
||||
time?: string;
|
||||
status: number;
|
||||
tag?: {
|
||||
text?: string;
|
||||
color?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type MessageListType = MessageItemData[];
|
||||
|
||||
interface MessageListProps {
|
||||
data: MessageItemData[];
|
||||
unReadData: MessageItemData[];
|
||||
onItemClick?: (item: MessageItemData, index: number) => void;
|
||||
onAllBtnClick?: (
|
||||
unReadData: MessageItemData[],
|
||||
data: MessageItemData[]
|
||||
) => void;
|
||||
}
|
||||
|
||||
function MessageList(props: MessageListProps) {
|
||||
const t = useLocale();
|
||||
const { data, unReadData } = props;
|
||||
|
||||
function onItemClick(item: MessageItemData, index: number) {
|
||||
if (item.status) return;
|
||||
props.onItemClick && props.onItemClick(item, index);
|
||||
}
|
||||
|
||||
function onAllBtnClick() {
|
||||
props.onAllBtnClick && props.onAllBtnClick(unReadData, data);
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
noDataElement={<Result status="404" subTitle={t['message.empty.tips']} />}
|
||||
footer={
|
||||
<div className={styles.footer}>
|
||||
<div className={styles['footer-item']}>
|
||||
<Button type="text" size="small" onClick={onAllBtnClick}>
|
||||
{t['message.allRead']}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['footer-item']}>
|
||||
<Button type="text" size="small">
|
||||
{t['message.seeMore']}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{data.map((item, index) => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
actionLayout="vertical"
|
||||
style={{
|
||||
opacity: item.status ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
onItemClick(item, index);
|
||||
}}
|
||||
>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
item.avatar && (
|
||||
<Avatar shape="circle" size={36}>
|
||||
<img src={item.avatar} />
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
title={
|
||||
<div className={styles['message-title']}>
|
||||
<Space size={4}>
|
||||
<span>{item.title}</span>
|
||||
<Typography.Text type="secondary">
|
||||
{item.subTitle}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
{item.tag && item.tag.text ? (
|
||||
<Tag color={item.tag.color}>{item.tag.text}</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div>
|
||||
<Typography.Paragraph style={{ marginBottom: 0 }} ellipsis>
|
||||
{item.content}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{item.time}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageList;
|
||||
@ -0,0 +1,46 @@
|
||||
@import '@arco-themes/react-arco-pro/variables.less';
|
||||
|
||||
.message-box {
|
||||
width: 400px;
|
||||
max-height: 800px;
|
||||
background-color: var(--color-bg-popup);
|
||||
border: 1px solid var(--color-border-2);
|
||||
box-shadow: @shadow2-down;
|
||||
border-radius: @border-radius-medium;
|
||||
|
||||
:global(.arco-tabs-header-nav) {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid var(--color-border-2);
|
||||
}
|
||||
|
||||
:global(.arco-list-item-meta) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
:global(.arco-list-item-meta-content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.arco-tabs-content) {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.message-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.footer-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 50%;
|
||||
|
||||
&:first-child {
|
||||
border-right: 1px solid var(--color-border-2);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { Button } from '@arco-design/web-react';
|
||||
import styles from './style/icon-button.module.less';
|
||||
import cs from 'classnames';
|
||||
|
||||
function IconButton(props, ref) {
|
||||
const { icon, className, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
icon={icon}
|
||||
shape="circle"
|
||||
type="secondary"
|
||||
className={cs(styles['icon-button'], className)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(IconButton);
|
||||
@ -0,0 +1,217 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import {
|
||||
Tooltip,
|
||||
Input,
|
||||
Avatar,
|
||||
Select,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Divider,
|
||||
Message,
|
||||
Button,
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
IconLanguage,
|
||||
IconNotification,
|
||||
IconSunFill,
|
||||
IconMoonFill,
|
||||
IconUser,
|
||||
IconSettings,
|
||||
IconPoweroff,
|
||||
IconExperiment,
|
||||
IconDashboard,
|
||||
IconInteraction,
|
||||
IconTag,
|
||||
IconLoading,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { GlobalState } from '@/store';
|
||||
import { GlobalContext } from '@/context';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import Logo from '@/assets/logo.svg';
|
||||
import MessageBox from '@/components/MessageBox';
|
||||
import IconButton from './IconButton';
|
||||
import Settings from '../Settings';
|
||||
import styles from './style/index.module.less';
|
||||
import defaultLocale from '@/locale';
|
||||
import useStorage from '@/utils/useStorage';
|
||||
import { generatePermission } from '@/routes';
|
||||
|
||||
function Navbar({ show }: { show: boolean }) {
|
||||
const t = useLocale();
|
||||
const { userInfo, userLoading } = useSelector((state: GlobalState) => state);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [_, setUserStatus] = useStorage('userStatus');
|
||||
const [role, setRole] = useStorage('userRole', 'admin');
|
||||
|
||||
const { setLang, lang, theme, setTheme } = useContext(GlobalContext);
|
||||
|
||||
function logout() {
|
||||
setUserStatus('logout');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
|
||||
function onMenuItemClick(key) {
|
||||
if (key === 'logout') {
|
||||
logout();
|
||||
} else {
|
||||
Message.info(`You clicked ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'update-userInfo',
|
||||
payload: {
|
||||
userInfo: {
|
||||
...userInfo,
|
||||
permissions: generatePermission(role),
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [role]);
|
||||
|
||||
if (!show) {
|
||||
return (
|
||||
<div className={styles['fixed-settings']}>
|
||||
<Settings
|
||||
trigger={
|
||||
<Button icon={<IconSettings />} type="primary" size="large" />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleChangeRole = () => {
|
||||
const newRole = role === 'admin' ? 'user' : 'admin';
|
||||
setRole(newRole);
|
||||
};
|
||||
|
||||
const droplist = (
|
||||
<Menu onClickMenuItem={onMenuItemClick}>
|
||||
<Menu.SubMenu
|
||||
key="role"
|
||||
title={
|
||||
<>
|
||||
<IconUser className={styles['dropdown-icon']} />
|
||||
<span className={styles['user-role']}>
|
||||
{role === 'admin'
|
||||
? t['menu.user.role.admin']
|
||||
: t['menu.user.role.user']}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Menu.Item onClick={handleChangeRole} key="switch role">
|
||||
<IconTag className={styles['dropdown-icon']} />
|
||||
{t['menu.user.switchRoles']}
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item key="setting">
|
||||
<IconSettings className={styles['dropdown-icon']} />
|
||||
{t['menu.user.setting']}
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="more"
|
||||
title={
|
||||
<div style={{ width: 80 }}>
|
||||
<IconExperiment className={styles['dropdown-icon']} />
|
||||
{t['message.seeMore']}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Menu.Item key="workplace">
|
||||
<IconDashboard className={styles['dropdown-icon']} />
|
||||
{t['menu.dashboard.workplace']}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="card list">
|
||||
<IconInteraction className={styles['dropdown-icon']} />
|
||||
{t['menu.list.cardList']}
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
|
||||
<Divider style={{ margin: '4px 0' }} />
|
||||
<Menu.Item key="logout">
|
||||
<IconPoweroff className={styles['dropdown-icon']} />
|
||||
{t['navbar.logout']}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.navbar}>
|
||||
<div className={styles.left}>
|
||||
<div className={styles.logo}>
|
||||
<Logo />
|
||||
<div className={styles['logo-name']}>Arco Pro</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul className={styles.right}>
|
||||
<li>
|
||||
<Input.Search
|
||||
className={styles.round}
|
||||
placeholder={t['navbar.search.placeholder']}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<Select
|
||||
triggerElement={<IconButton icon={<IconLanguage />} />}
|
||||
options={[
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
]}
|
||||
value={lang}
|
||||
triggerProps={{
|
||||
autoAlignPopupWidth: false,
|
||||
autoAlignPopupMinWidth: true,
|
||||
position: 'br',
|
||||
}}
|
||||
trigger="hover"
|
||||
onChange={(value) => {
|
||||
setLang(value);
|
||||
const nextLang = defaultLocale[value];
|
||||
Message.info(`${nextLang['message.lang.tips']}${value}`);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<MessageBox>
|
||||
<IconButton icon={<IconNotification />} />
|
||||
</MessageBox>
|
||||
</li>
|
||||
<li>
|
||||
<Tooltip
|
||||
content={
|
||||
theme === 'light'
|
||||
? t['settings.navbar.theme.toDark']
|
||||
: t['settings.navbar.theme.toLight']
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon={theme !== 'dark' ? <IconMoonFill /> : <IconSunFill />}
|
||||
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Settings />
|
||||
{userInfo && (
|
||||
<li>
|
||||
<Dropdown droplist={droplist} position="br" disabled={userLoading}>
|
||||
<Avatar size={32} style={{ cursor: 'pointer' }}>
|
||||
{userLoading ? (
|
||||
<IconLoading />
|
||||
) : (
|
||||
<img alt="avatar" src={userInfo.avatar} />
|
||||
)}
|
||||
</Avatar>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Navbar;
|
||||
@ -0,0 +1,8 @@
|
||||
.icon-button {
|
||||
font-size: 16px;
|
||||
border: 1px solid var(--color-border-2);
|
||||
|
||||
> svg {
|
||||
vertical-align: -3px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-sizing: border-box;
|
||||
background-color: var(--color-bg-2);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.logo-name {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
margin-left: 10px;
|
||||
font-family: 'PingFang SC';
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding-right: 20px;
|
||||
|
||||
li {
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.round {
|
||||
:global(.arco-input-inner-wrapper) {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.fixed-settings {
|
||||
position: fixed;
|
||||
top: 280px;
|
||||
right: 0px;
|
||||
|
||||
svg {
|
||||
font-size: 18px;
|
||||
vertical-align: -4px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import React, { CSSProperties, ReactNode } from 'react';
|
||||
import { Typography } from '@arco-design/web-react';
|
||||
import cs from 'classnames';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
interface PanelProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
title?: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
function Panel(props: PanelProps) {
|
||||
const { className, style, title, children } = props;
|
||||
return (
|
||||
<div className={cs(styles.panel, className)} style={style}>
|
||||
<Typography.Title>{title}</Typography.Title>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Panel;
|
||||
@ -0,0 +1,4 @@
|
||||
.panel {
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { GlobalState } from '@/store';
|
||||
import { useSelector } from 'react-redux';
|
||||
import authentication, { AuthParams } from '@/utils/authentication';
|
||||
|
||||
type PermissionWrapperProps = AuthParams & {
|
||||
backup?: React.ReactNode;
|
||||
};
|
||||
|
||||
const PermissionWrapper = (
|
||||
props: React.PropsWithChildren<PermissionWrapperProps>
|
||||
) => {
|
||||
const { backup, requiredPermissions, oneOfPerm } = props;
|
||||
const userInfo = useSelector((state: GlobalState) => state.userInfo);
|
||||
|
||||
const hasPermission = useMemo(() => {
|
||||
return authentication(
|
||||
{requiredPermissions, oneOfPerm},
|
||||
userInfo.permissions
|
||||
);
|
||||
},[oneOfPerm, requiredPermissions, userInfo.permissions]);
|
||||
|
||||
if (hasPermission) {
|
||||
return <>{convertReactElement(props.children)}</>;
|
||||
}
|
||||
if (backup) {
|
||||
return <>{convertReactElement(backup)}</>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
function convertReactElement(node: React.ReactNode): React.ReactElement {
|
||||
if (!React.isValidElement(node)) {
|
||||
return <>{node}</>;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
export default PermissionWrapper;
|
||||
@ -0,0 +1,77 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Switch, Divider, InputNumber } from '@arco-design/web-react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { GlobalState } from '../../store';
|
||||
import useLocale from '../../utils/useLocale';
|
||||
import styles from './style/block.module.less';
|
||||
|
||||
export interface BlockProps {
|
||||
title?: ReactNode;
|
||||
options?: { name: string; value: string; type?: 'switch' | 'number' }[];
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export default function Block(props: BlockProps) {
|
||||
const { title, options, children } = props;
|
||||
const locale = useLocale();
|
||||
const settings = useSelector((state: GlobalState) => state.settings);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div className={styles.block}>
|
||||
<h5 className={styles.title}>{title}</h5>
|
||||
{options &&
|
||||
options.map((option) => {
|
||||
const type = option.type || 'switch';
|
||||
|
||||
return (
|
||||
<div className={styles['switch-wrapper']} key={option.value}>
|
||||
<span>{locale[option.name]}</span>
|
||||
{type === 'switch' && (
|
||||
<Switch
|
||||
size="small"
|
||||
checked={!!settings[option.value]}
|
||||
onChange={(checked) => {
|
||||
const newSetting = {
|
||||
...settings,
|
||||
[option.value]: checked,
|
||||
};
|
||||
dispatch({
|
||||
type: 'update-settings',
|
||||
payload: { settings: newSetting },
|
||||
});
|
||||
// set color week
|
||||
if (checked && option.value === 'colorWeek') {
|
||||
document.body.style.filter = 'invert(80%)';
|
||||
}
|
||||
if (!checked && option.value === 'colorWeek') {
|
||||
document.body.style.filter = 'none';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'number' && (
|
||||
<InputNumber
|
||||
style={{ width: 80 }}
|
||||
size="small"
|
||||
value={settings.menuWidth}
|
||||
onChange={(value) => {
|
||||
const newSetting = {
|
||||
...settings,
|
||||
[option.value]: value,
|
||||
};
|
||||
dispatch({
|
||||
type: 'update-settings',
|
||||
payload: { settings: newSetting },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{children}
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { Trigger, Typography } from '@arco-design/web-react';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import { generate, getRgbStr } from '@arco-design/color';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { GlobalState } from '../../store';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import styles from './style/color-panel.module.less';
|
||||
|
||||
function ColorPanel() {
|
||||
const theme =
|
||||
document.querySelector('body').getAttribute('arco-theme') || 'light';
|
||||
const settings = useSelector((state: GlobalState) => state.settings);
|
||||
const locale = useLocale();
|
||||
const themeColor = settings.themeColor;
|
||||
const list = generate(themeColor, { list: true });
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Trigger
|
||||
trigger="hover"
|
||||
position="bl"
|
||||
popup={() => (
|
||||
<SketchPicker
|
||||
color={themeColor}
|
||||
onChangeComplete={(color) => {
|
||||
const newColor = color.hex;
|
||||
dispatch({
|
||||
type: 'update-settings',
|
||||
payload: { settings: { ...settings, themeColor: newColor } },
|
||||
});
|
||||
const newList = generate(newColor, {
|
||||
list: true,
|
||||
dark: theme === 'dark',
|
||||
});
|
||||
newList.forEach((l, index) => {
|
||||
const rgbStr = getRgbStr(l);
|
||||
document.body.style.setProperty(
|
||||
`--arcoblue-${index + 1}`,
|
||||
rgbStr
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className={styles.input}>
|
||||
<div
|
||||
className={styles.color}
|
||||
style={{ backgroundColor: themeColor }}
|
||||
/>
|
||||
<span>{themeColor}</span>
|
||||
</div>
|
||||
</Trigger>
|
||||
<ul className={styles.ul}>
|
||||
{list.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={styles.li}
|
||||
style={{ backgroundColor: item }}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Typography.Paragraph style={{ fontSize: 12 }}>
|
||||
{locale['settings.color.tooltip']}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ColorPanel;
|
||||
@ -0,0 +1,72 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Drawer, Alert, Message } from '@arco-design/web-react';
|
||||
import { IconSettings } from '@arco-design/web-react/icon';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { GlobalState } from '../../store';
|
||||
import Block from './block';
|
||||
import ColorPanel from './color';
|
||||
import IconButton from '../NavBar/IconButton';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
|
||||
interface SettingProps {
|
||||
trigger?: React.ReactElement;
|
||||
}
|
||||
|
||||
function Setting(props: SettingProps) {
|
||||
const { trigger } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const locale = useLocale();
|
||||
const settings = useSelector((state: GlobalState) => state.settings);
|
||||
|
||||
function onCopySettings() {
|
||||
copy(JSON.stringify(settings, null, 2));
|
||||
Message.success(locale['settings.copySettings.message']);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{trigger ? (
|
||||
React.cloneElement(trigger as React.ReactElement, {
|
||||
onClick: () => setVisible(true),
|
||||
})
|
||||
) : (
|
||||
<IconButton icon={<IconSettings />} onClick={() => setVisible(true)} />
|
||||
)}
|
||||
<Drawer
|
||||
width={300}
|
||||
title={
|
||||
<>
|
||||
<IconSettings />
|
||||
{locale['settings.title']}
|
||||
</>
|
||||
}
|
||||
visible={visible}
|
||||
okText={locale['settings.copySettings']}
|
||||
cancelText={locale['settings.close']}
|
||||
onOk={onCopySettings}
|
||||
onCancel={() => setVisible(false)}
|
||||
>
|
||||
<Block title={locale['settings.themeColor']}>
|
||||
<ColorPanel />
|
||||
</Block>
|
||||
<Block
|
||||
title={locale['settings.content']}
|
||||
options={[
|
||||
{ name: 'settings.navbar', value: 'navbar' },
|
||||
{ name: 'settings.menu', value: 'menu' },
|
||||
{ name: 'settings.footer', value: 'footer' },
|
||||
{ name: 'settings.menuWidth', value: 'menuWidth', type: 'number' },
|
||||
]}
|
||||
/>
|
||||
<Block
|
||||
title={locale['settings.otherSettings']}
|
||||
options={[{ name: 'settings.colorWeek', value: 'colorWeek' }]}
|
||||
/>
|
||||
<Alert content={locale['settings.alertContent']} />
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Setting;
|
||||
@ -0,0 +1,16 @@
|
||||
.block {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.switch-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
.input {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.color {
|
||||
width: 100px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.li {
|
||||
width: 10%;
|
||||
height: 26px;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const GlobalContext = createContext<{
|
||||
lang?: string;
|
||||
setLang?: (value: string) => void;
|
||||
theme?: string;
|
||||
setTheme?: (value: string) => void;
|
||||
}>({});
|
||||
@ -0,0 +1,27 @@
|
||||
declare module '*.svg' {
|
||||
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.less' {
|
||||
const classes: { [className: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*/settings.json' {
|
||||
const value: {
|
||||
colorWeek: boolean;
|
||||
navbar: boolean;
|
||||
menu: boolean;
|
||||
footer: boolean;
|
||||
themeColor: string;
|
||||
menuWidth: number;
|
||||
};
|
||||
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'menu.welcome': 'Welcome',
|
||||
'menu.dashboard': 'Dashboard',
|
||||
'menu.list': 'List',
|
||||
'menu.result': 'Result',
|
||||
'menu.exception': 'Exception',
|
||||
'menu.form': 'Form',
|
||||
'menu.profile': 'Profile',
|
||||
'menu.visualization': 'Data Visualization',
|
||||
'menu.user': 'User Center',
|
||||
'menu.exception.403': '403',
|
||||
'menu.exception.404': '404',
|
||||
'menu.exception.500': '500',
|
||||
'menu.profile.basic': 'Basic Profile',
|
||||
'menu.list.cardList': 'Card List',
|
||||
'menu.visualization.dataAnalysis': 'Analysis',
|
||||
'menu.result.error': 'Error',
|
||||
'menu.form.group': 'Group Form',
|
||||
'menu.dashboard.monitor': 'Real-time Monitor',
|
||||
'menu.visualization.multiDimensionDataAnalysis': 'Multi-D Analysis',
|
||||
'menu.list.searchTable': 'Search Table',
|
||||
'menu.form.step': 'Step Form',
|
||||
'menu.result.success': 'Success',
|
||||
'menu.user.info': 'User Info',
|
||||
'menu.user.setting': 'User Setting',
|
||||
'menu.user.switchRoles': 'Switch Roles',
|
||||
'menu.user.role.admin': 'Admin',
|
||||
'menu.user.role.user': 'General User',
|
||||
'menu.dashboard.workplace': 'Workplace',
|
||||
'navbar.logout': 'Logout',
|
||||
'settings.title': 'Settings',
|
||||
'settings.themeColor': 'Theme Color',
|
||||
'settings.content': 'Content Setting',
|
||||
'settings.navbar': 'Navbar',
|
||||
'settings.menuWidth': 'Menu Width (px)',
|
||||
'settings.navbar.theme.toLight': 'Click to use light mode',
|
||||
'settings.navbar.theme.toDark': 'Click to use dark mode',
|
||||
'settings.menu': 'Menu',
|
||||
'settings.footer': 'Footer',
|
||||
'settings.otherSettings': 'Other Settings',
|
||||
'settings.colorWeek': 'Color Week',
|
||||
'settings.alertContent':
|
||||
'After the configuration is only temporarily effective, if you want to really affect the project, click the "Copy Settings" button below and replace the configuration in settings.json.',
|
||||
'settings.copySettings': 'Copy Settings',
|
||||
'settings.copySettings.message':
|
||||
'Copy succeeded, please paste to file src/settings.json.',
|
||||
'settings.close': 'Close',
|
||||
'settings.color.tooltip':
|
||||
'10 gradient colors generated according to the theme color',
|
||||
'message.tab.title.message': 'Message',
|
||||
'message.tab.title.notice': 'Notice',
|
||||
'message.tab.title.todo': 'ToDo',
|
||||
'message.allRead': 'All Read',
|
||||
'message.seeMore': 'SeeMore',
|
||||
'message.empty': 'Empty',
|
||||
'message.empty.tips': 'No Content',
|
||||
'message.lang.tips': 'Language switch to ',
|
||||
'navbar.search.placeholder': 'Please search',
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.dashboard': '仪表盘',
|
||||
'menu.list': '列表页',
|
||||
'menu.result': '结果页',
|
||||
'menu.exception': '异常页',
|
||||
'menu.form': '表单页',
|
||||
'menu.profile': '详情页',
|
||||
'menu.visualization': '数据可视化',
|
||||
'menu.user': '个人中心',
|
||||
'menu.exception.403': '403',
|
||||
'menu.exception.404': '404',
|
||||
'menu.exception.500': '500',
|
||||
'menu.profile.basic': '基础详情页',
|
||||
'menu.list.cardList': '卡片列表',
|
||||
'menu.visualization.dataAnalysis': '分析页',
|
||||
'menu.result.error': '失败页',
|
||||
'menu.form.group': '分组表单',
|
||||
'menu.dashboard.monitor': '实时监控',
|
||||
'menu.visualization.multiDimensionDataAnalysis': '多维数据分析',
|
||||
'menu.list.searchTable': '查询表格',
|
||||
'menu.form.step': '分步表单',
|
||||
'menu.result.success': '成功页',
|
||||
'menu.user.info': '用户信息',
|
||||
'menu.user.setting': '用户设置',
|
||||
'menu.user.switchRoles': '切换角色',
|
||||
'menu.user.role.admin': '管理员',
|
||||
'menu.user.role.user': '普通用户',
|
||||
'menu.dashboard.workplace': '工作台',
|
||||
'navbar.logout': '退出登录',
|
||||
'settings.title': '页面配置',
|
||||
'settings.themeColor': '主题色',
|
||||
'settings.content': '内容区域',
|
||||
'settings.navbar': '导航栏',
|
||||
'settings.menuWidth': '菜单宽度 (px)',
|
||||
'settings.navbar.theme.toLight': '点击切换为亮色模式',
|
||||
'settings.navbar.theme.toDark': '点击切换为暗黑模式',
|
||||
'settings.menu': '菜单栏',
|
||||
'settings.footer': '底部',
|
||||
'settings.otherSettings': '其他设置',
|
||||
'settings.colorWeek': '色弱模式',
|
||||
'settings.alertContent':
|
||||
'配置之后仅是临时生效,要想真正作用于项目,点击下方的 "复制配置" 按钮,将配置替换到 settings.json 中即可。',
|
||||
'settings.copySettings': '复制配置',
|
||||
'settings.copySettings.message':
|
||||
'复制成功,请粘贴到 src/settings.json 文件中',
|
||||
'settings.close': '关闭',
|
||||
'settings.color.tooltip':
|
||||
'根据主题颜色生成的 10 个梯度色(将配置复制到项目中,主题色才能对亮色 / 暗黑模式同时生效)',
|
||||
'message.tab.title.message': '消息',
|
||||
'message.tab.title.notice': '通知',
|
||||
'message.tab.title.todo': '待办',
|
||||
'message.allRead': '全部已读',
|
||||
'message.seeMore': '查看更多',
|
||||
'message.empty': '清空',
|
||||
'message.empty.tips': '暂无内容',
|
||||
'message.lang.tips': '语言切换至 ',
|
||||
'navbar.search.placeholder': '输入内容查询',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,11 @@
|
||||
import Mock from 'mockjs';
|
||||
import { isSSR } from '@/utils/is';
|
||||
|
||||
import './user';
|
||||
import './message-box';
|
||||
|
||||
if (!isSSR) {
|
||||
Mock.setup({
|
||||
timeout: '500-1500',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import Mock from 'mockjs';
|
||||
import { isSSR } from '@/utils/is';
|
||||
import setupMock from '@/utils/setupMock';
|
||||
import { generatePermission } from '@/routes';
|
||||
|
||||
if (!isSSR) {
|
||||
Mock.XHR.prototype.withCredentials = true;
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
// 用户信息
|
||||
const userRole = window.localStorage.getItem('userRole') || 'admin';
|
||||
Mock.mock(new RegExp('/api/user/userInfo'), () => {
|
||||
return Mock.mock({
|
||||
name: 'admin',
|
||||
avatar:
|
||||
'https://lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png',
|
||||
email: 'wangliqun@email.com',
|
||||
job: 'frontend',
|
||||
jobName: '前端开发工程师',
|
||||
organization: 'Frontend',
|
||||
organizationName: '前端',
|
||||
location: 'beijing',
|
||||
locationName: '北京',
|
||||
introduction: '王力群并非是一个真实存在的人。',
|
||||
personalWebsite: 'https://www.arco.design',
|
||||
verified: true,
|
||||
phoneNumber: /177[*]{6}[0-9]{2}/,
|
||||
accountId: /[a-z]{4}[-][0-9]{8}/,
|
||||
registrationTime: Mock.Random.datetime('yyyy-MM-dd HH:mm:ss'),
|
||||
permissions: generatePermission(userRole),
|
||||
});
|
||||
});
|
||||
|
||||
// 登录
|
||||
Mock.mock(new RegExp('/api/user/login'), (params) => {
|
||||
const { userName, password } = JSON.parse(params.body);
|
||||
if (!userName) {
|
||||
return {
|
||||
status: 'error',
|
||||
msg: '用户名不能为空',
|
||||
};
|
||||
}
|
||||
if (!password) {
|
||||
return {
|
||||
status: 'error',
|
||||
msg: '密码不能为空',
|
||||
};
|
||||
}
|
||||
if (userName === 'admin' && password === 'admin') {
|
||||
return {
|
||||
status: 'ok',
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 'error',
|
||||
msg: '账号或者密码错误',
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import cookies from 'next-cookies';
|
||||
import Head from 'next/head';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { createStore } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import '../style/global.less';
|
||||
import { ConfigProvider } from '@arco-design/web-react';
|
||||
import zhCN from '@arco-design/web-react/es/locale/zh-CN';
|
||||
import enUS from '@arco-design/web-react/es/locale/en-US';
|
||||
import axios from 'axios';
|
||||
import NProgress from 'nprogress';
|
||||
import rootReducer from '../store';
|
||||
import { GlobalContext } from '../context';
|
||||
import checkLogin from '@/utils/checkLogin';
|
||||
import changeTheme from '@/utils/changeTheme';
|
||||
import useStorage from '@/utils/useStorage';
|
||||
import Layout from './layout';
|
||||
import '../mock';
|
||||
|
||||
const store = createStore(rootReducer);
|
||||
|
||||
interface RenderConfig {
|
||||
arcoLang?: string;
|
||||
arcoTheme?: string;
|
||||
}
|
||||
|
||||
export default function MyApp({
|
||||
pageProps,
|
||||
Component,
|
||||
renderConfig,
|
||||
}: AppProps & { renderConfig: RenderConfig }) {
|
||||
const { arcoLang, arcoTheme } = renderConfig;
|
||||
const [lang, setLang] = useStorage('arco-lang', arcoLang || 'en-US');
|
||||
const [theme, setTheme] = useStorage('arco-theme', arcoTheme || 'light');
|
||||
const router = useRouter();
|
||||
|
||||
const locale = useMemo(() => {
|
||||
switch (lang) {
|
||||
case 'zh-CN':
|
||||
return zhCN;
|
||||
case 'en-US':
|
||||
return enUS;
|
||||
default:
|
||||
return enUS;
|
||||
}
|
||||
}, [lang]);
|
||||
|
||||
function fetchUserInfo() {
|
||||
store.dispatch({
|
||||
type: 'update-userInfo',
|
||||
payload: { userLoading: true },
|
||||
});
|
||||
axios.get('/api/user/userInfo').then((res) => {
|
||||
store.dispatch({
|
||||
type: 'update-userInfo',
|
||||
payload: { userInfo: res.data, userLoading: false },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (checkLogin()) {
|
||||
fetchUserInfo();
|
||||
} else if (window.location.pathname.replace(/\//g, '') !== 'login') {
|
||||
window.location.pathname = '/login';
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStart = () => {
|
||||
NProgress.set(0.4);
|
||||
NProgress.start();
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
NProgress.done();
|
||||
};
|
||||
|
||||
router.events.on('routeChangeStart', handleStart);
|
||||
router.events.on('routeChangeComplete', handleStop);
|
||||
router.events.on('routeChangeError', handleStop);
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', handleStart);
|
||||
router.events.off('routeChangeComplete', handleStop);
|
||||
router.events.off('routeChangeError', handleStop);
|
||||
};
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
document.cookie = `arco-lang=${lang}; path=/`;
|
||||
document.cookie = `arco-theme=${theme}; path=/`;
|
||||
changeTheme(theme);
|
||||
}, [lang, theme]);
|
||||
|
||||
const contextValue = {
|
||||
lang,
|
||||
setLang,
|
||||
theme,
|
||||
setTheme,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>工业软件组件化构建开发与运行支撑环境</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico"
|
||||
/>
|
||||
</Head>
|
||||
<ConfigProvider
|
||||
locale={locale}
|
||||
componentConfig={{
|
||||
Card: {
|
||||
bordered: false,
|
||||
},
|
||||
List: {
|
||||
bordered: false,
|
||||
},
|
||||
Table: {
|
||||
border: false,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Provider store={store}>
|
||||
<GlobalContext.Provider value={contextValue}>
|
||||
{Component.displayName === 'LoginPage' ? (
|
||||
<Component {...pageProps} suppressHydrationWarning />
|
||||
) : (
|
||||
<Layout>
|
||||
<Component {...pageProps} suppressHydrationWarning />
|
||||
</Layout>
|
||||
)}
|
||||
</GlobalContext.Provider>
|
||||
</Provider>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// fix: next build ssr can't attach the localstorage
|
||||
MyApp.getInitialProps = async (appContext) => {
|
||||
const { ctx } = appContext;
|
||||
const serverCookies = cookies(ctx);
|
||||
return {
|
||||
renderConfig: {
|
||||
arcoLang: serverCookies['arco-lang'],
|
||||
arcoTheme: serverCookies['arco-theme'],
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,74 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Space,
|
||||
Select,
|
||||
Input,
|
||||
Button,
|
||||
Typography,
|
||||
Spin,
|
||||
} from '@arco-design/web-react';
|
||||
import { IconDownload, IconFaceSmileFill } from '@arco-design/web-react/icon';
|
||||
import axios from 'axios';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import MessageList from './message-list';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
export default function ChatPanel() {
|
||||
const t = useLocale(locale);
|
||||
const [messageList, setMessageList] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function fetchMessageList() {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get('/api/chatList')
|
||||
.then((res) => {
|
||||
setMessageList(res.data || []);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchMessageList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles['chat-panel']}>
|
||||
<div className={styles['chat-panel-header']}>
|
||||
<Typography.Title
|
||||
style={{ marginTop: 0, marginBottom: 16 }}
|
||||
heading={6}
|
||||
>
|
||||
{t['monitor.title.chatPanel']}
|
||||
</Typography.Title>
|
||||
<Space size={8}>
|
||||
<Select style={{ width: 80 }} defaultValue="all">
|
||||
<Select.Option value="all">
|
||||
{t['monitor.chat.options.all']}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
<Input.Search
|
||||
placeholder={t['monitor.chat.placeholder.searchCategory']}
|
||||
/>
|
||||
<Button type="text" iconOnly>
|
||||
<IconDownload />
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={styles['chat-panel-content']}>
|
||||
<Spin loading={loading} style={{ width: '100%' }}>
|
||||
<MessageList data={messageList} />
|
||||
</Spin>
|
||||
</div>
|
||||
<div className={styles['chat-panel-footer']}>
|
||||
<Space size={8}>
|
||||
<Input suffix={<IconFaceSmileFill />} />
|
||||
<Button type="primary">{t['monitor.chat.update']}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import { Table, Tag, Typography } from '@arco-design/web-react';
|
||||
import React from 'react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
export default function QuickOperation() {
|
||||
const t = useLocale(locale);
|
||||
const columns = [
|
||||
{
|
||||
title: t['monitor.list.title.order'],
|
||||
render: (_col, _record, index) => <span>{index + 1}</span>,
|
||||
},
|
||||
{
|
||||
title: t['monitor.list.title.cover'],
|
||||
dataIndex: 'cover',
|
||||
render: (_col, record) => (
|
||||
<div className={styles['data-statistic-list-cover-wrapper']}>
|
||||
<img src={record.cover} />
|
||||
{record.status === -1 && (
|
||||
<Tag
|
||||
color="red"
|
||||
className={styles['data-statistic-list-cover-tag']}
|
||||
>
|
||||
{t['monitor.list.tag.auditFailed']}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t['monitor.list.title.name'],
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
dataIndex: 'duration',
|
||||
title: t['monitor.list.title.duration'],
|
||||
},
|
||||
{
|
||||
dataIndex: 'id',
|
||||
title: t['monitor.list.title.id'],
|
||||
},
|
||||
];
|
||||
const data = [
|
||||
{
|
||||
cover:
|
||||
'http://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/c788fc704d32cf3b1136c7d45afc2669.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '视频直播',
|
||||
duration: '00:05:19',
|
||||
id: '54e23ade',
|
||||
status: -1,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className={styles['']}>
|
||||
<Table
|
||||
columns={columns}
|
||||
data={data}
|
||||
rowKey="id"
|
||||
rowSelection={{
|
||||
type: 'checkbox',
|
||||
}}
|
||||
border={false}
|
||||
pagination={false}
|
||||
/>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
className={styles['data-statistic-list-tip']}
|
||||
>
|
||||
{t['monitor.list.tip.rotations']}
|
||||
{data.length}
|
||||
{t['monitor.list.tip.rest']}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import { Button, Card, Radio, Tabs } from '@arco-design/web-react';
|
||||
import React from 'react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import DataStatisticList from './data-statistic-list';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
export default function DataStatistic() {
|
||||
const t = useLocale(locale);
|
||||
return (
|
||||
<Card>
|
||||
<Tabs defaultActiveTab="liveMethod">
|
||||
<Tabs.TabPane
|
||||
key="liveMethod"
|
||||
title={t['monitor.tab.title.liveMethod']}
|
||||
/>
|
||||
<Tabs.TabPane
|
||||
key="onlineUsers"
|
||||
title={t['monitor.tab.title.onlineUsers']}
|
||||
/>
|
||||
</Tabs>
|
||||
<div className={styles['data-statistic-content']}>
|
||||
<Radio.Group defaultValue="3" type="button">
|
||||
<Radio value="1">{t['monitor.liveMethod.normal']}</Radio>
|
||||
<Radio value="2">{t['monitor.liveMethod.flowControl']}</Radio>
|
||||
<Radio value="3">{t['monitor.liveMethod.video']}</Radio>
|
||||
<Radio value="4">{t['monitor.liveMethod.web']}</Radio>
|
||||
</Radio.Group>
|
||||
|
||||
<div className={styles['data-statistic-list-wrapper']}>
|
||||
<div className={styles['data-statistic-list-header']}>
|
||||
<Button type="text">{t['monitor.editCarousel']}</Button>
|
||||
<Button disabled>{t['monitor.startCarousel']}</Button>
|
||||
</div>
|
||||
<div className={styles['data-statistic-list-content']}>
|
||||
<DataStatisticList />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { Space } from '@arco-design/web-react';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ChatPanel from './chat-panel';
|
||||
import Studio from './studio';
|
||||
import DataStatistic from './data-statistic';
|
||||
import StudioStatus from './studio-status';
|
||||
import QuickOperation from './quick-operation';
|
||||
import StudioInformation from './studio-information';
|
||||
import styles from './style/index.module.less';
|
||||
import './mock';
|
||||
|
||||
export default function Monitor() {
|
||||
const userInfo = useSelector((state: any) => state.userInfo);
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.layout}>
|
||||
<div className={styles['layout-left-side']}>
|
||||
<ChatPanel />
|
||||
</div>
|
||||
<div className={styles['layout-content']}>
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<Studio userInfo={userInfo} />
|
||||
<DataStatistic />
|
||||
</Space>
|
||||
</div>
|
||||
<div className={styles['layout-right-side']}>
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<StudioStatus />
|
||||
<QuickOperation />
|
||||
<StudioInformation />
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'menu.dashboard': 'Dashboard',
|
||||
'monitor.title.chatPanel': 'Chat Window',
|
||||
'monitor.title.quickOperation': 'Quick Operation',
|
||||
'monitor.title.studioInfo': 'Studio Information',
|
||||
'monitor.title.studioPreview': 'Studio Preview',
|
||||
'monitor.chat.options.all': 'All',
|
||||
'monitor.chat.placeholder.searchCategory': 'Search Category',
|
||||
'monitor.chat.update': 'Update',
|
||||
'monitor.list.title.order': 'Order',
|
||||
'monitor.list.title.cover': 'Cover',
|
||||
'monitor.list.title.name': 'Name',
|
||||
'monitor.list.title.duration': 'Duration',
|
||||
'monitor.list.title.id': 'ID',
|
||||
'monitor.list.tip.rotations': 'Rotations ',
|
||||
'monitor.list.tip.rest': ', The program list is not visible to viewers',
|
||||
'monitor.list.tag.auditFailed': 'Audit Failed',
|
||||
'monitor.tab.title.liveMethod': 'Live Method',
|
||||
'monitor.tab.title.onlineUsers': 'Online Users',
|
||||
'monitor.liveMethod.normal': 'Normal Live',
|
||||
'monitor.liveMethod.flowControl': 'Flow Control Live',
|
||||
'monitor.liveMethod.video': 'Video Live',
|
||||
'monitor.liveMethod.web': 'Web Live',
|
||||
'monitor.editCarousel': 'Edit',
|
||||
'monitor.startCarousel': 'Start',
|
||||
'monitor.quickOperation.changeClarity': 'Change the Clarity',
|
||||
'monitor.quickOperation.switchStream': 'Switch Stream',
|
||||
'monitor.quickOperation.removeClarity': 'Remove the Clarity',
|
||||
'monitor.quickOperation.pushFlowGasket': 'Push Flow Gasket',
|
||||
'monitor.studioInfo.label.studioTitle': 'Studio Title',
|
||||
'monitor.studioInfo.label.onlineNotification': 'Online Notification',
|
||||
'monitor.studioInfo.label.studioCategory': 'Studio Category',
|
||||
'monitor.studioInfo.placeholder.studioTitle': "'s Studio",
|
||||
'monitor.studioStatus.title.studioStatus': 'Studio Status',
|
||||
'monitor.studioStatus.title.pictureInfo': 'Picture Information',
|
||||
'monitor.studioStatus.smooth': 'Smooth',
|
||||
'monitor.studioStatus.frameRate': 'Frame',
|
||||
'monitor.studioStatus.bitRate': 'Bit',
|
||||
'monitor.studioStatus.mainstream': 'Main',
|
||||
'monitor.studioStatus.hotStandby': 'Hot',
|
||||
'monitor.studioStatus.coldStandby': 'Cold',
|
||||
'monitor.studioStatus.line': 'Line',
|
||||
'monitor.studioStatus.play': 'Format',
|
||||
'monitor.studioStatus.pictureQuality': 'Quality',
|
||||
'monitor.studioPreview.studio': 'Studio',
|
||||
'monitor.studioPreview.watching': 'watching',
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.dashboard': '仪表盘',
|
||||
'menu.dashboard.monitor': '实时监控',
|
||||
'monitor.title.chatPanel': '聊天窗口',
|
||||
'monitor.title.quickOperation': '快捷操作',
|
||||
'monitor.title.studioInfo': '直播信息',
|
||||
'monitor.title.studioPreview': '直播预览',
|
||||
'monitor.chat.options.all': '全部',
|
||||
'monitor.chat.placeholder.searchCategory': '搜索类目',
|
||||
'monitor.chat.update': '更新',
|
||||
'monitor.list.title.order': '序号',
|
||||
'monitor.list.title.cover': '封面',
|
||||
'monitor.list.title.name': '名称',
|
||||
'monitor.list.title.duration': '视频时长',
|
||||
'monitor.list.title.id': '视频Id',
|
||||
'monitor.list.tip.rotations': '轮播次数',
|
||||
'monitor.list.tip.rest': ',节目单观众不可见',
|
||||
'monitor.list.tag.auditFailed': '审核未通过',
|
||||
'monitor.tab.title.liveMethod': '直播方式',
|
||||
'monitor.tab.title.onlineUsers': '在线人数',
|
||||
'monitor.liveMethod.normal': '普通直播',
|
||||
'monitor.liveMethod.flowControl': '控流直播',
|
||||
'monitor.liveMethod.video': '视频直播',
|
||||
'monitor.liveMethod.web': '网页开播',
|
||||
'monitor.editCarousel': '编辑轮播',
|
||||
'monitor.startCarousel': '开始轮播',
|
||||
'monitor.quickOperation.changeClarity': '切换清晰度',
|
||||
'monitor.quickOperation.switchStream': '主备流切换',
|
||||
'monitor.quickOperation.removeClarity': '摘除清晰度',
|
||||
'monitor.quickOperation.pushFlowGasket': '推流垫片',
|
||||
'monitor.studioInfo.label.studioTitle': '直播标题',
|
||||
'monitor.studioInfo.label.onlineNotification': '上线通知',
|
||||
'monitor.studioInfo.label.studioCategory': '直播类目',
|
||||
'monitor.studioInfo.placeholder.studioTitle': '的直播间',
|
||||
'monitor.studioStatus.title.studioStatus': '直播状态',
|
||||
'monitor.studioStatus.title.pictureInfo': '画面信息',
|
||||
'monitor.studioStatus.smooth': '流畅',
|
||||
'monitor.studioStatus.frameRate': '帧率',
|
||||
'monitor.studioStatus.bitRate': '码率',
|
||||
'monitor.studioStatus.mainstream': '主流',
|
||||
'monitor.studioStatus.hotStandby': '热备',
|
||||
'monitor.studioStatus.coldStandby': '冷备',
|
||||
'monitor.studioStatus.line': '线路',
|
||||
'monitor.studioStatus.play': '播放格式',
|
||||
'monitor.studioStatus.pictureQuality': '画质',
|
||||
'monitor.studioPreview.studio': '直播间',
|
||||
'monitor.studioPreview.watching': '在看',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Result } from '@arco-design/web-react';
|
||||
import MessageItem, { Message } from './item';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
interface MessageListProps {
|
||||
data: Message[];
|
||||
}
|
||||
|
||||
function MessageList(props: MessageListProps) {
|
||||
const { data = [] } = props;
|
||||
return (
|
||||
<div className={styles['message-list']}>
|
||||
{data.map((item) => (
|
||||
<MessageItem key={item.id} data={item} />
|
||||
))}
|
||||
{!data.length && <Result status="404" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageList;
|
||||
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { Space, Typography } from '@arco-design/web-react';
|
||||
import { IconCommand, IconStar } from '@arco-design/web-react/icon';
|
||||
import cs from 'classnames';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
export interface Message {
|
||||
id?: string;
|
||||
username?: string;
|
||||
content?: string;
|
||||
time?: string;
|
||||
isCollect?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageItemProps {
|
||||
data: Message;
|
||||
}
|
||||
|
||||
function MessageItem(props: MessageItemProps) {
|
||||
const { data = {} } = props;
|
||||
const classNames = cs(styles['message-item'], {
|
||||
[styles['message-item-collected']]: data.isCollect,
|
||||
});
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<Space size={4} direction="vertical" style={{ width: '100%' }}>
|
||||
<Typography.Text type="warning">{data.username}</Typography.Text>
|
||||
<Typography.Text>{data.content}</Typography.Text>
|
||||
<div className={styles['message-item-footer']}>
|
||||
<div className={styles['message-item-time']}>
|
||||
<Typography.Text type="secondary">{data.time}</Typography.Text>
|
||||
</div>
|
||||
<div className={styles['message-item-actions']}>
|
||||
<div className={styles['message-item-actions-item']}>
|
||||
<IconCommand />
|
||||
</div>
|
||||
<div
|
||||
className={cs(
|
||||
styles['message-item-actions-item'],
|
||||
styles['message-item-actions-collect']
|
||||
)}
|
||||
>
|
||||
<IconStar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageItem;
|
||||
@ -0,0 +1,52 @@
|
||||
.message-item {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
|
||||
&-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
|
||||
&-item {
|
||||
margin-right: 4px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-3);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(var(--gray-3));
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-collected {
|
||||
.message-item-actions-collect {
|
||||
color: rgb(var(--gold-6));
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(var(--gray-2));
|
||||
|
||||
.message-item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import Mock from 'mockjs';
|
||||
import setupMock from '@/utils/setupMock';
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
Mock.mock(new RegExp('/api/chatList'), () => {
|
||||
const data = Mock.mock({
|
||||
'data|4-6': [
|
||||
{
|
||||
'id|+1': 1,
|
||||
username: '用户7352772',
|
||||
content: '马上就开始了,好激动!',
|
||||
time: '13:09:12',
|
||||
'isCollect|2': true,
|
||||
},
|
||||
],
|
||||
});
|
||||
return data.data;
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, Typography, Space } from '@arco-design/web-react';
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconStop,
|
||||
IconSwap,
|
||||
IconTags,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
|
||||
export default function QuickOperation() {
|
||||
const t = useLocale(locale);
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title style={{ marginTop: 0, marginBottom: 16 }} heading={6}>
|
||||
{t['monitor.title.quickOperation']}
|
||||
</Typography.Title>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={10}>
|
||||
<Button long icon={<IconTags />}>
|
||||
{t['monitor.quickOperation.changeClarity']}
|
||||
</Button>
|
||||
<Button long icon={<IconSwap />}>
|
||||
{t['monitor.quickOperation.switchStream']}
|
||||
</Button>
|
||||
<Button long icon={<IconStop />}>
|
||||
{t['monitor.quickOperation.removeClarity']}
|
||||
</Button>
|
||||
<Button long icon={<IconArrowRight />}>
|
||||
{t['monitor.quickOperation.pushFlowGasket']}
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { Card, Typography, Form, Input, Button } from '@arco-design/web-react';
|
||||
import React from 'react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
|
||||
export default function StudioInformation() {
|
||||
const t = useLocale(locale);
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title style={{ marginTop: 0, marginBottom: 16 }} heading={6}>
|
||||
{t['monitor.title.studioInfo']}
|
||||
</Typography.Title>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label={t['monitor.studioInfo.label.studioTitle']} required>
|
||||
<Input
|
||||
placeholder={`admin${t['monitor.studioInfo.placeholder.studioTitle']}`}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['monitor.studioInfo.label.onlineNotification']}
|
||||
required
|
||||
>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['monitor.studioInfo.label.studioCategory']}
|
||||
required
|
||||
>
|
||||
<Input.Search />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['monitor.studioInfo.label.studioCategory']}
|
||||
required
|
||||
>
|
||||
<Input.Search />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Button type="primary">更新</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
Typography,
|
||||
Tag,
|
||||
Space,
|
||||
Descriptions,
|
||||
} from '@arco-design/web-react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
|
||||
export default function StudioStatus() {
|
||||
const t = useLocale(locale);
|
||||
const dataStatus = [
|
||||
{
|
||||
label: (
|
||||
<span>
|
||||
<Typography.Text style={{ paddingRight: 8 }}>
|
||||
{t['monitor.studioStatus.mainstream']}
|
||||
</Typography.Text>
|
||||
{t['monitor.studioStatus.bitRate']}
|
||||
</span>
|
||||
),
|
||||
value: '6 Mbps',
|
||||
},
|
||||
{
|
||||
label: t['monitor.studioStatus.frameRate'],
|
||||
value: '60',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<span>
|
||||
<Typography.Text style={{ paddingRight: 8 }}>
|
||||
{t['monitor.studioStatus.hotStandby']}
|
||||
</Typography.Text>
|
||||
{t['monitor.studioStatus.bitRate']}
|
||||
</span>
|
||||
),
|
||||
value: '6 Mbps',
|
||||
},
|
||||
{
|
||||
label: t['monitor.studioStatus.frameRate'],
|
||||
value: '60',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<span>
|
||||
<Typography.Text style={{ paddingRight: 8 }}>
|
||||
{t['monitor.studioStatus.coldStandby']}
|
||||
</Typography.Text>
|
||||
{t['monitor.studioStatus.bitRate']}
|
||||
</span>
|
||||
),
|
||||
value: '6 Mbps',
|
||||
},
|
||||
{
|
||||
label: t['monitor.studioStatus.frameRate'],
|
||||
value: '60',
|
||||
},
|
||||
];
|
||||
const dataPicture = [
|
||||
{
|
||||
label: t['monitor.studioStatus.line'],
|
||||
value: '热备',
|
||||
},
|
||||
{
|
||||
label: 'CDN',
|
||||
value: 'KS',
|
||||
},
|
||||
{
|
||||
label: t['monitor.studioStatus.play'],
|
||||
value: 'FLV',
|
||||
},
|
||||
{
|
||||
label: t['monitor.studioStatus.pictureQuality'],
|
||||
value: '原画',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Space align="start">
|
||||
<Typography.Title
|
||||
style={{ marginTop: 0, marginBottom: 16 }}
|
||||
heading={6}
|
||||
>
|
||||
{t['monitor.studioStatus.title.studioStatus']}
|
||||
</Typography.Title>
|
||||
<Tag color="green">{t['monitor.studioStatus.smooth']}</Tag>
|
||||
</Space>
|
||||
<Descriptions
|
||||
colon=": "
|
||||
layout="horizontal"
|
||||
data={dataStatus}
|
||||
column={2}
|
||||
/>
|
||||
<Typography.Title style={{ marginBottom: 16 }} heading={6}>
|
||||
{t['monitor.studioStatus.title.pictureInfo']}
|
||||
</Typography.Title>
|
||||
<Descriptions
|
||||
colon=": "
|
||||
layout="horizontal"
|
||||
data={dataPicture}
|
||||
column={2}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
import { Card, Typography, Avatar, Space, Grid } from '@arco-design/web-react';
|
||||
import { IconMore } from '@arco-design/web-react/icon';
|
||||
import React from 'react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
interface StudioProps {
|
||||
userInfo: {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Studio(props: StudioProps) {
|
||||
const t = useLocale(locale);
|
||||
const { userInfo } = props;
|
||||
return (
|
||||
<Card>
|
||||
<Grid.Row>
|
||||
<Grid.Col span={16}>
|
||||
<Typography.Title
|
||||
style={{ marginTop: 0, marginBottom: 16 }}
|
||||
heading={6}
|
||||
>
|
||||
{t['monitor.title.studioPreview']}
|
||||
</Typography.Title>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ textAlign: 'right' }}>
|
||||
<IconMore />
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
<div className={styles['studio-wrapper']}>
|
||||
<img
|
||||
src="http://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/c788fc704d32cf3b1136c7d45afc2669.png~tplv-uwbnlip3yd-webp.webp"
|
||||
className={styles['studio-preview']}
|
||||
/>
|
||||
<div className={styles['studio-bar']}>
|
||||
{userInfo && (
|
||||
<div>
|
||||
<Space size={12}>
|
||||
<Avatar size={24}>
|
||||
<img src={userInfo.avatar} />
|
||||
</Avatar>
|
||||
<Typography.Text>
|
||||
{userInfo.name}
|
||||
{t['monitor.studioPreview.studio']}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
<Typography.Text type="secondary">
|
||||
3,6000 {t['monitor.studioPreview.watching']}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
.layout {
|
||||
display: flex;
|
||||
|
||||
&-left-side {
|
||||
flex-basis: 300px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: 1;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
&-right-side {
|
||||
flex-basis: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
height: 100%;
|
||||
background-color: var(--color-bg-2);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
|
||||
&-content {
|
||||
flex: 1;
|
||||
margin: 20px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.data-statistic {
|
||||
&-content {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
&-list {
|
||||
&-header {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&-cover {
|
||||
&-wrapper {
|
||||
height: 68px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-tag {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-tip {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.studio {
|
||||
&-wrapper {
|
||||
:global(.arco-card-body) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-preview {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&-bar {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Handle, Position } from '@xyflow/react';
|
||||
|
||||
export const CustomNode = ({ data }) => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ padding: '10px 20px' }}>
|
||||
{data.label}
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
.text-updater-node {
|
||||
//width: 150px;
|
||||
//height: 80px;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #cccccc;
|
||||
background-color: #fff;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import style from './TextUpdaterNode.module.less';
|
||||
import { Handle, Position } from '@xyflow/react';
|
||||
|
||||
export function TextUpdaterNode(props) {
|
||||
const onChange = useCallback((evt) => {
|
||||
console.log(evt.target.value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={style['text-updater-node']}>
|
||||
<div>
|
||||
<label htmlFor="text">Text:</label>
|
||||
<input id="text" name="text" onChange={onChange} className="nodrag" />
|
||||
</div>
|
||||
<Handle type="target" position={Position.Left} id="a" />
|
||||
<Handle type="target" position={Position.Bottom} id="a1" />
|
||||
<Handle type="target" position={Position.Top} id="a2" />
|
||||
<Handle type="source" position={Position.Right} id="b" />
|
||||
<Handle type="source" position={Position.Right} style={{ top: 10 }} id="b1" />
|
||||
<Handle type="source" position={Position.Right} style={{ top: 20 }} id="b2" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { ReactFlow, applyNodeChanges, applyEdgeChanges, addEdge, Background, Controls } from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { TextUpdaterNode } from './components/textUpdateNode/TextUpdaterNode';
|
||||
|
||||
const nodeTypes = {
|
||||
textUpdater: TextUpdaterNode
|
||||
};
|
||||
|
||||
const initialNodes = [
|
||||
{
|
||||
id: 'n1',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { label: 'Node 1' },
|
||||
type: 'input'
|
||||
},
|
||||
{
|
||||
id: 'node-1',
|
||||
type: 'textUpdater',
|
||||
position: { x: 150, y: 0 },
|
||||
data: { value: 123 }
|
||||
},
|
||||
{
|
||||
id: 'n2',
|
||||
position: { x: 100, y: 100 },
|
||||
data: { label: 'Custom Node' }
|
||||
}
|
||||
];
|
||||
|
||||
const initialEdges = [
|
||||
{
|
||||
id: 'n1-n2',
|
||||
source: 'n1',
|
||||
target: 'n2'
|
||||
},
|
||||
{
|
||||
id: 'n1-node-1',
|
||||
source: 'n1',
|
||||
target: 'node-1',
|
||||
targetHandle: 'a'
|
||||
},
|
||||
{
|
||||
id: 'n2-node-1',
|
||||
source: 'n2',
|
||||
target: 'node-1',
|
||||
targetHandle: 'a1'
|
||||
}
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [nodes, setNodes] = useState(initialNodes);
|
||||
const [edges, setEdges] = useState(initialEdges);
|
||||
|
||||
const onNodesChange = useCallback(
|
||||
(changes) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)),
|
||||
[]
|
||||
);
|
||||
const onEdgesChange = useCallback(
|
||||
(changes) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)),
|
||||
[]
|
||||
);
|
||||
const onConnect = useCallback(
|
||||
(params) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ width: '90vw', height: '91vh' }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Tinyflow, TinyflowHandle } from '@tinyflow-ai/react';
|
||||
import '@tinyflow-ai/react/dist/index.css';
|
||||
|
||||
const App = () => {
|
||||
const tinyflowRef = useRef<TinyflowHandle>(null);
|
||||
|
||||
const handleGetData = () => {
|
||||
if (tinyflowRef.current) {
|
||||
const data = tinyflowRef.current.getData();
|
||||
console.log('Flow Data:', data);
|
||||
}
|
||||
};
|
||||
|
||||
const customNodes = {
|
||||
'custom-node': {
|
||||
title: '自定义节点',
|
||||
description: '这是一个测试的自定义节点',
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 20C20 20.5523 19.5523 21 19 21H5C4.44772 21 4 20.5523 4 20V11H1L11.3273 1.6115C11.7087 1.26475 12.2913 1.26475 12.6727 1.6115L23 11H20V20ZM18 19V9.15745L12 3.7029L6 9.15745V19H18ZM12 17L8.64124 13.6412C7.76256 12.7625 7.76256 11.3379 8.64124 10.4592C9.51992 9.58056 10.9445 9.58056 11.8232 10.4592L12 10.636L12.1768 10.4592C13.0555 9.58056 14.4801 9.58056 15.3588 10.4592C16.2374 11.3379 16.2374 12.7625 15.3588 13.6412L12 17Z"></path></svg>',
|
||||
sortNo: 2,
|
||||
render: (parent, node, flowInstance) => {
|
||||
parent.innerHTML = `<select style="width: 100%">
|
||||
<option>test</option>
|
||||
<option>test1</option>
|
||||
<option>test2</option>
|
||||
</select>`;
|
||||
|
||||
parent.querySelector('select')
|
||||
?.addEventListener('change', (e) => {
|
||||
console.log('select change: ', e);
|
||||
flowInstance.updateNodeData(node.id, {
|
||||
test: e.target.value
|
||||
});
|
||||
})
|
||||
;
|
||||
|
||||
console.log('render: ', node, flowInstance);
|
||||
},
|
||||
onUpdate: (parent, node) => {
|
||||
console.log('onUpdate: ', node);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'test-node': {
|
||||
title: '测试节点',
|
||||
description: '这是一个测试的自定义节点',
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 20C20 20.5523 19.5523 21 19 21H5C4.44772 21 4 20.5523 4 20V11H1L11.3273 1.6115C11.7087 1.26475 12.2913 1.26475 12.6727 1.6115L23 11H20V20ZM18 19V9.15745L12 3.7029L6 9.15745V19H18ZM12 17L8.64124 13.6412C7.76256 12.7625 7.76256 11.3379 8.64124 10.4592C9.51992 9.58056 10.9445 9.58056 11.8232 10.4592L12 10.636L12.1768 10.4592C13.0555 9.58056 14.4801 9.58056 15.3588 10.4592C16.2374 11.3379 16.2374 12.7625 15.3588 13.6412L12 17Z"></path></svg>',
|
||||
sortNo: 310,
|
||||
group: 'tools',
|
||||
forms: [
|
||||
{
|
||||
type: 'heading',
|
||||
label: '测试节点'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'test',
|
||||
label: '测试',
|
||||
placeholder: '请输入测试内容'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'test2',
|
||||
label: '测试2',
|
||||
placeholder: '请选择测试内容',
|
||||
defaultValue: '1',
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: '选项3',
|
||||
value: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Tinyflow React Example</h1>
|
||||
<Tinyflow
|
||||
ref={tinyflowRef}
|
||||
data={{
|
||||
nodes: [
|
||||
{ id: '1', label: '开始', position: { x: 100, y: 100 } },
|
||||
{ id: '2', label: '结束', position: { x: 300, y: 100 } },
|
||||
{ id: '3', label: '结束1', position: { x: 300, y: 150 } }
|
||||
],
|
||||
edges: [
|
||||
{ source: '1', target: '2' }
|
||||
]
|
||||
}}
|
||||
style={{ border: '1px solid #ccc' }}
|
||||
className="custom-class"
|
||||
customNodes={customNodes}
|
||||
/>
|
||||
<button onClick={handleGetData}>获取流程数据</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@ -0,0 +1,67 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Link, Card, Skeleton, Tag, Typography } from '@arco-design/web-react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/announcement.module.less';
|
||||
|
||||
function Announcement() {
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const t = useLocale(locale);
|
||||
|
||||
const fetchData = () => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get('/api/workplace/announcement')
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
function getTagColor(type) {
|
||||
switch (type) {
|
||||
case 'activity':
|
||||
return 'orangered';
|
||||
case 'info':
|
||||
return 'cyan';
|
||||
case 'notice':
|
||||
return 'arcoblue';
|
||||
default:
|
||||
return 'arcoblue';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography.Title heading={6}>
|
||||
{t['workplace.announcement']}
|
||||
</Typography.Title>
|
||||
<Link>{t['workplace.seeMore']}</Link>
|
||||
</div>
|
||||
<Skeleton loading={loading} text={{ rows: 5, width: '100%' }} animation>
|
||||
<div>
|
||||
{data.map((d) => (
|
||||
<div key={d.key} className={styles.item}>
|
||||
<Tag color={getTagColor(d.type)} size="small">
|
||||
{t[`workplace.${d.type}`]}
|
||||
</Tag>
|
||||
<span className={styles.link}>{d.content}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Skeleton>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default Announcement;
|
||||
@ -0,0 +1,86 @@
|
||||
<svg width="55" height="58" viewBox="0 0 55 58" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ii_1053_46645)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2234 16H34.1971H19.6665C17.4612 16 15.6665 17.7937 15.6665 19.9977V23.9953V26.6605V35.9883C15.6665 38.1923 17.4612 39.986 19.6665 39.986H35.6665C37.8718 39.986 39.6665 38.1923 39.6665 35.9883V26.6605V23.9953V21.2979L34.2234 16Z" fill="#7DA2FF"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_di_1053_46645)">
|
||||
<path d="M31.6884 25.1609H20.5815C20.0751 25.1609 19.6646 25.5712 19.6646 26.0773C19.6646 26.5833 20.0751 26.9936 20.5815 26.9936H31.6884C32.1948 26.9936 32.6052 26.5833 32.6052 26.0773C32.6052 25.5712 32.1948 25.1609 31.6884 25.1609Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_di_1053_46645)">
|
||||
<path d="M27.1313 21.5852H20.5226C20.0488 21.5852 19.6646 21.9691 19.6646 22.4427C19.6646 22.9163 20.0488 23.3001 20.5226 23.3001H27.1313C27.6052 23.3001 27.9893 22.9163 27.9893 22.4427C27.9893 21.9691 27.6052 21.5852 27.1313 21.5852Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_di_1053_46645)">
|
||||
<path d="M35.6691 30.6563C35.6691 29.9208 35.1558 29.3238 34.5259 29.3238H20.8078C20.1779 29.3238 19.6646 29.9208 19.6646 30.6563V33.6234C19.6646 34.4513 20.3362 35.1225 21.1646 35.1225H34.1691C34.9975 35.1225 35.6691 34.4513 35.6691 33.6234V30.6563Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_f_1053_46645)">
|
||||
<path d="M28.1665 39.986C34.5178 39.986 39.6665 38.8674 39.6665 37.4874C39.6665 36.1075 34.5178 34.9889 28.1665 34.9889C21.8152 34.9889 16.6665 36.1075 16.6665 37.4874C16.6665 38.8674 21.8152 39.986 28.1665 39.986Z" fill="#7CA0FD"/>
|
||||
</g>
|
||||
<path d="M36.2095 21.2979H39.6669L34.2095 15.986V19.2991C34.2095 20.403 35.1049 21.2979 36.2095 21.2979Z" fill="#B9CDFA"/>
|
||||
<defs>
|
||||
<filter id="filter0_ii_1053_46645" x="15.6665" y="9.88145" width="24" height="30.1045" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-6.11855"/>
|
||||
<feGaussianBlur stdDeviation="3.82409"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0716667 0 0 0 0 0.136167 0 0 0 0 0.716667 0 0 0 0.35 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1053_46645"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-2.29445"/>
|
||||
<feGaussianBlur stdDeviation="1.52964"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="effect1_innerShadow_1053_46645" result="effect2_innerShadow_1053_46645"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_1053_46645" x="5.15213" y="18.5644" width="41.9658" height="30.8575" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="7.91587"/>
|
||||
<feGaussianBlur stdDeviation="7.25621"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0450001 0 0 0 0 0.45 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1053_46645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1053_46645" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.338819 0 0 0 0 0.521617 0 0 0 0 0.991667 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_1053_46645"/>
|
||||
</filter>
|
||||
<filter id="filter2_di_1053_46645" x="5.15213" y="14.9887" width="37.3495" height="30.7397" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="7.91587"/>
|
||||
<feGaussianBlur stdDeviation="7.25621"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0450001 0 0 0 0 0.45 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1053_46645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1053_46645" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.338819 0 0 0 0 0.521617 0 0 0 0 0.991667 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_1053_46645"/>
|
||||
</filter>
|
||||
<filter id="filter3_di_1053_46645" x="5.15213" y="22.7273" width="45.0292" height="34.8236" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="7.91587"/>
|
||||
<feGaussianBlur stdDeviation="7.25621"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0450001 0 0 0 0 0.45 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1053_46645"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1053_46645" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-2"/>
|
||||
<feGaussianBlur stdDeviation="3.29828"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.690196 0 0 0 0 0.776941 0 0 0 0 1 0 0 0 0.6 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_1053_46645"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_1053_46645" x="4.6665" y="22.9889" width="47" height="28.9971" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="6" result="effect1_foregroundBlur_1053_46645"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
@ -0,0 +1,71 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ii_161_33236)">
|
||||
<path d="M20.4884 3.5143C21.5946 4.62323 22.4625 5.9143 23.0679 7.3527C23.692 8.84199 24.008 10.425 24.008 12.0536C24 13.6768 23.6786 15.2491 23.0464 16.7277C22.4384 18.1554 21.5679 19.4358 20.4616 20.5313C19.358 21.6268 18.0696 22.484 16.6366 23.0813C15.1714 23.6893 13.6152 24 12.0107 24H11.9544C10.9717 23.9958 10.0284 23.677 9.11446 23.2543C7.52892 22.5211 5.79636 22.1752 4.07277 22.4593L3.19059 22.6048C2.74817 22.6777 2.26797 22.7683 1.88519 22.5348C1.71473 22.4308 1.57149 22.2866 1.46868 22.1153C1.2409 21.7359 1.32941 21.2627 1.40047 20.8259L1.54851 19.9159C1.82774 18.1994 1.48269 16.4748 0.753006 14.8962C0.330661 13.9826 0.0122489 13.0404 0.00799498 12.0563C-4.07491e-05 10.4357 0.310674 8.85806 0.926747 7.37413C1.51871 5.94109 2.38121 4.65537 3.47407 3.54912C4.56961 2.44287 5.84997 1.57501 7.27765 0.964294C8.75622 0.33215 10.3285 0.0107213 11.9518 0.00268555H12.0053C13.6152 0.00268555 15.1795 0.316079 16.6527 0.93483C18.0911 1.53751 19.3821 2.40805 20.4884 3.5143Z" fill="#FDA979"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_dii_161_33236)">
|
||||
<path d="M18.2016 13.5312C17.3786 13.5312 16.7141 12.8477 16.7141 12C16.7141 11.1523 17.3786 10.4688 18.2016 10.4688C19.0247 10.4688 19.6891 11.1523 19.6891 12C19.6919 12.8477 19.0247 13.5312 18.2016 13.5312ZM12.0028 13.5312C11.1797 13.5312 10.5153 12.8477 10.5153 12C10.5153 11.1523 11.1797 10.4688 12.0028 10.4688C12.8258 10.4688 13.4903 11.1523 13.4903 12C13.4876 12.8477 12.8231 13.5312 12.0028 13.5312Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_dii_161_33236)">
|
||||
<path d="M4.31378 12C4.31378 12.2011 4.35226 12.4002 4.42701 12.586C4.50177 12.7718 4.61133 12.9406 4.74946 13.0828C4.88759 13.2249 5.05157 13.3377 5.23204 13.4147C5.41251 13.4916 5.60594 13.5312 5.80128 13.5312C5.99662 13.5312 6.19005 13.4916 6.37052 13.4147C6.551 13.3377 6.71498 13.2249 6.8531 13.0828C6.99123 12.9406 7.1008 12.7718 7.17555 12.586C7.25031 12.4002 7.28878 12.2011 7.28878 12C7.28878 11.5939 7.13206 11.2044 6.8531 10.9172C6.57414 10.6301 6.19579 10.4688 5.80128 10.4688C5.40677 10.4688 5.02842 10.6301 4.74946 10.9172C4.4705 11.2044 4.31378 11.5939 4.31378 12Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ii_161_33236" x="0.00784302" y="-2.95135" width="24.0002" height="28.0583" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-2.95403"/>
|
||||
<feGaussianBlur stdDeviation="1.84627"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.78 0 0 0 0 0 0 0 0 0.29 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_161_33236"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.10694"/>
|
||||
<feGaussianBlur stdDeviation="1.05495"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="effect1_innerShadow_161_33236" result="effect2_innerShadow_161_33236"/>
|
||||
</filter>
|
||||
<filter id="filter1_dii_161_33236" x="6.74583" y="8.75536" width="16.7127" height="10.6014" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.05607"/>
|
||||
<feGaussianBlur stdDeviation="1.88473"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.8625 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33236"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33236" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.37071"/>
|
||||
<feGaussianBlur stdDeviation="0.856696"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.925 0 0 0 0 0.320667 0 0 0 0 0.0616667 0 0 0 0.12 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33236"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.514017"/>
|
||||
<feGaussianBlur stdDeviation="0.342678"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33236" result="effect3_innerShadow_161_33236"/>
|
||||
</filter>
|
||||
<filter id="filter2_dii_161_33236" x="0.544321" y="8.75536" width="10.5139" height="10.6014" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.05607"/>
|
||||
<feGaussianBlur stdDeviation="1.88473"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.8625 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33236"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33236" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.37071"/>
|
||||
<feGaussianBlur stdDeviation="0.856696"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.925 0 0 0 0 0.320667 0 0 0 0 0.0616667 0 0 0 0.12 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33236"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.514017"/>
|
||||
<feGaussianBlur stdDeviation="0.342678"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33236" result="effect3_innerShadow_161_33236"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@ -0,0 +1,168 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_161_33222)">
|
||||
<rect x="0.0283203" y="3.10522" width="12.9766" height="18.5206" rx="2.03077" fill="url(#paint0_linear_161_33222)"/>
|
||||
<mask id="mask0_161_33222" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="3" y="0" width="21" height="24">
|
||||
<path d="M5.41988 0.119385L15.1885 0.119384C15.8615 0.119384 16.5092 0.376475 16.9991 0.838099L22.4105 5.93776C22.9398 6.43659 23.2399 7.13171 23.2399 7.85905L23.2399 21.8994C23.2399 22.9929 22.3534 23.8794 21.2599 23.8794L5.41988 23.8794C4.32636 23.8794 3.43988 22.9929 3.43988 21.8994L3.43988 2.09938C3.43988 1.00586 4.32636 0.119385 5.41988 0.119385Z" fill="#4D72D3"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_161_33222)">
|
||||
<g filter="url(#filter0_dii_161_33222)">
|
||||
<path d="M5.41988 0.119385L15.1885 0.119384C15.8615 0.119384 16.5092 0.376475 16.9991 0.838099L22.4105 5.93776C22.9398 6.43659 23.2399 7.13171 23.2399 7.85905L23.2399 21.8994C23.2399 22.9929 22.3534 23.8794 21.2599 23.8794L5.41988 23.8794C4.32636 23.8794 3.43988 22.9929 3.43988 21.8994L3.43988 2.09938C3.43988 1.00586 4.32636 0.119385 5.41988 0.119385Z" fill="url(#paint1_linear_161_33222)"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_dii_161_33222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2799 6.05945L23.2399 6.05945L23.2399 0.119451L17.2999 0.119452L17.2999 4.07945C17.2999 5.17297 18.1864 6.05945 19.2799 6.05945Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<g filter="url(#filter2_dii_161_33222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2546 7.37437C11.5777 7.76209 11.5253 8.33833 11.1376 8.66144L8.71308 10.6818C8.51387 10.8479 8.25351 10.9214 7.99689 10.8842C7.74026 10.847 7.51153 10.7025 7.36769 10.4867L6.55952 9.27448C6.27956 8.85454 6.39304 8.28716 6.81298 8.0072C7.23292 7.72724 7.80029 7.84072 8.08025 8.26065L8.32429 8.62671L9.96751 7.25737C10.3552 6.93426 10.9315 6.98665 11.2546 7.37437Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_dii_161_33222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8708 9.57522C12.8708 9.07052 13.28 8.66138 13.7847 8.66138L19.4418 8.66138C19.9465 8.66138 20.3557 9.07052 20.3557 9.57522C20.3557 10.0799 19.9465 10.4891 19.4418 10.4891L13.7847 10.4891C13.28 10.4891 12.8708 10.0799 12.8708 9.57522Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_dii_161_33222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2549 15.2324C11.2549 14.7277 11.664 14.3186 12.1687 14.3186L19.4422 14.3186C19.9469 14.3186 20.356 14.7277 20.356 15.2324C20.356 15.7372 19.9469 16.1463 19.4422 16.1463L12.1687 16.1463C11.664 16.1463 11.2549 15.7372 11.2549 15.2324Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter5_dii_161_33222)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.21375 15.2324C7.21375 14.7277 7.60556 14.3186 8.08889 14.3186L8.16629 14.3186C8.64962 14.3186 9.04144 14.7277 9.04144 15.2324C9.04144 15.7372 8.64962 16.1463 8.16629 16.1463L8.08889 16.1463C7.60556 16.1463 7.21375 15.7372 7.21375 15.2324Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_dii_161_33222" x="-5.56012" y="-4.88062" width="37.8" height="41.76" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="4.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.697798 0 0 0 0 0.346806 0 0 0 0 0.945833 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.131148"/>
|
||||
<feGaussianBlur stdDeviation="0.0327869"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.968627 0 0 0 0 0.6 0 0 0 0 0.984314 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.131148"/>
|
||||
<feGaussianBlur stdDeviation="0.131148"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<filter id="filter1_dii_161_33222" x="11.2854" y="-2.61439" width="17.9691" height="17.9691" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.28067"/>
|
||||
<feGaussianBlur stdDeviation="3.00728"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0450001 0 0 0 0 0.45 0 0 0 0.12 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-2.18712"/>
|
||||
<feGaussianBlur stdDeviation="1.36695"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.690196 0 0 0 0 0.776941 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.406154"/>
|
||||
<feGaussianBlur stdDeviation="0.546779"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<filter id="filter2_dii_161_33222" x="0.391376" y="4.31164" width="17.0896" height="15.8773" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.28067"/>
|
||||
<feGaussianBlur stdDeviation="3.00728"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.00314236 0 0 0 0 0.0782449 0 0 0 0 0.754167 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.21846"/>
|
||||
<feGaussianBlur stdDeviation="1.36695"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.57 0 0 0 0 0.6308 0 0 0 0 0.95 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.820168"/>
|
||||
<feGaussianBlur stdDeviation="0.546779"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<filter id="filter3_dii_161_33222" x="6.85628" y="5.92748" width="19.514" height="13.8568" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.28067"/>
|
||||
<feGaussianBlur stdDeviation="3.00728"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.00314236 0 0 0 0 0.0782449 0 0 0 0 0.754167 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.21846"/>
|
||||
<feGaussianBlur stdDeviation="1.36695"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.57 0 0 0 0 0.6308 0 0 0 0 0.95 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.820168"/>
|
||||
<feGaussianBlur stdDeviation="0.546779"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<filter id="filter4_dii_161_33222" x="5.24031" y="11.5847" width="21.1303" height="13.8568" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.28067"/>
|
||||
<feGaussianBlur stdDeviation="3.00728"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.00314236 0 0 0 0 0.0782449 0 0 0 0 0.754167 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.21846"/>
|
||||
<feGaussianBlur stdDeviation="1.36695"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.57 0 0 0 0 0.6308 0 0 0 0 0.95 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.820168"/>
|
||||
<feGaussianBlur stdDeviation="0.546779"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<filter id="filter5_dii_161_33222" x="3.15221" y="13.1001" width="9.95078" height="10.3883" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.28067"/>
|
||||
<feGaussianBlur stdDeviation="2.03077"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0450001 0 0 0 0 0.45 0 0 0 0.65 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_161_33222"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_161_33222" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.21846"/>
|
||||
<feGaussianBlur stdDeviation="1.36695"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.57 0 0 0 0 0.6308 0 0 0 0 0.95 0 0 0 0.4 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_161_33222"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.820168"/>
|
||||
<feGaussianBlur stdDeviation="0.546779"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_161_33222" result="effect3_innerShadow_161_33222"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_161_33222" x1="1.97655" y1="3.10522" x2="1.97655" y2="16.0647" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E14BFE"/>
|
||||
<stop offset="1" stop-color="#B84FD1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_161_33222" x1="3.43988" y1="0.119385" x2="3.43988" y2="23.8794" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E982FE"/>
|
||||
<stop offset="1" stop-color="#B353FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_161_33222">
|
||||
<rect width="24" height="24" fill="white" transform="matrix(1 -8.74228e-08 -8.74228e-08 -1 0 24)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,77 @@
|
||||
<svg width="24" height="27" viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_178_29628" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="4" width="21" height="23">
|
||||
<path d="M20.1248 4.00061H3.87483C2.83929 4.00061 1.99983 4.84008 1.99983 5.87561V24.6256C1.99983 25.6611 2.83929 26.5006 3.87483 26.5006H20.1248C21.1604 26.5006 21.9998 25.6611 21.9998 24.6256V5.87561C21.9998 4.84008 21.1604 4.00061 20.1248 4.00061Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_178_29628)">
|
||||
<g filter="url(#filter0_ii_178_29628)">
|
||||
<path d="M20.1248 4.00061H3.87483C2.83929 4.00061 1.99983 4.84008 1.99983 5.87561V23.6256C1.99983 24.6611 2.83929 25.5006 3.87483 25.5006H20.1248C21.1604 25.5006 21.9998 24.6611 21.9998 23.6256V5.87561C21.9998 4.84008 21.1604 4.00061 20.1248 4.00061Z" fill="url(#paint0_linear_178_29628)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g filter="url(#filter1_di_178_29628)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8747 1.87549C14.6534 1.87549 15.3213 2.35019 15.6046 3.02601C15.6294 3.08496 15.6858 3.12552 15.7497 3.12549C16.7853 3.12549 17.6247 3.96495 17.6247 5.00049C17.6247 6.03602 16.7853 6.87549 15.7497 6.87549H8.24974C7.2142 6.87549 6.37474 6.03602 6.37474 5.00049C6.37474 3.96495 7.2142 3.12549 8.24974 3.12549C8.31366 3.12552 8.37011 3.08496 8.39483 3.02601C8.67819 2.35019 9.34603 1.87549 10.1247 1.87549H13.8747Z" fill="#FFFEFE"/>
|
||||
</g>
|
||||
<path d="M17.9719 9H6.02754V20.9444H17.9719V9Z" fill="white" fill-opacity="0.01"/>
|
||||
<g filter="url(#filter2_dii_178_29628)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5335 11.6822C12.5335 11.1511 12.9641 10.7206 13.4951 10.7206H17.0843C17.6154 10.7206 18.0459 11.1511 18.0459 11.6822V15.2715C18.0459 15.8025 17.6154 16.233 17.0843 16.233C16.5533 16.233 16.1227 15.8025 16.1227 15.2715V12.6438H13.4951C12.9641 12.6438 12.5335 12.2133 12.5335 11.6822Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.7488 10.9869C18.1327 11.3539 18.1464 11.9626 17.7795 12.3465L13.3481 16.9826C13.036 17.3092 12.5385 17.3744 12.1526 17.1393L9.93329 16.233L7.35673 18.8752C7.01826 19.2844 6.41212 19.3418 6.00289 19.0033C5.59365 18.6648 5.53629 18.0587 5.87476 17.6495L8.98186 14.3659C9.28641 13.9977 9.81516 13.9089 10.2232 14.1576L12.4926 15.0943L16.3892 11.0176C16.7562 10.6337 17.3649 10.62 17.7488 10.9869Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ii_178_29628" x="1.99983" y="1.85775" width="20" height="24.7143" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-2.14286"/>
|
||||
<feGaussianBlur stdDeviation="1.07143"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0788195 0 0 0 0 0.633708 0 0 0 0 0.945833 0 0 0 0.7 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_178_29628"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.07143"/>
|
||||
<feGaussianBlur stdDeviation="1.07143"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.7 0"/>
|
||||
<feBlend mode="normal" in2="effect1_innerShadow_178_29628" result="effect2_innerShadow_178_29628"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_178_29628" x="4.23188" y="0.452631" width="15.5357" height="9.28571" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.72"/>
|
||||
<feGaussianBlur stdDeviation="1.07143"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.373715 0 0 0 0 0.67555 0 0 0 0 0.954167 0 0 0 0.6 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_178_29628"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_178_29628" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.72"/>
|
||||
<feGaussianBlur stdDeviation="1.07143"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0178125 0 0 0 0 0.37905 0 0 0 0 0.7125 0 0 0 0.4 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_178_29628"/>
|
||||
</filter>
|
||||
<filter id="filter2_dii_178_29628" x="1.32699" y="9.27808" width="21.0461" height="17.1577" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.88476"/>
|
||||
<feGaussianBlur stdDeviation="2.16357"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.355333 0 0 0 0 0.683333 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_178_29628"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_178_29628" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-1.1539"/>
|
||||
<feGaussianBlur stdDeviation="0.72119"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.58 0 0 0 0 1 0 0 0 0.4 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_178_29628"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="-0.582535"/>
|
||||
<feGaussianBlur stdDeviation="0.388357"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.879167 0 0 0 0 1 0 0 0 0 1 0 0 0 0.75 0"/>
|
||||
<feBlend mode="normal" in2="effect2_innerShadow_178_29628" result="effect3_innerShadow_178_29628"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_178_29628" x1="31.2857" y1="-4.2138" x2="0.7475" y2="26.2897" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#24B5E3"/>
|
||||
<stop offset="0.53305" stop-color="#56CCFF"/>
|
||||
<stop offset="1" stop-color="#0BA7FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Carousel } from '@arco-design/web-react';
|
||||
|
||||
const imageSrc = [
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/f7e8fc1e09c42e30682526252365be1c.jpg~tplv-uwbnlip3yd-webp.webp',
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp',
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/ec447228c59ae1ebe185bab6cd776ca4.jpg~tplv-uwbnlip3yd-webp.webp',
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/1d1580d2a5a1e27415ff594c756eabd8.jpg~tplv-uwbnlip3yd-webp.webp',
|
||||
];
|
||||
function C() {
|
||||
return (
|
||||
<Carousel
|
||||
indicatorType="slider"
|
||||
showArrow="never"
|
||||
autoPlay
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 160,
|
||||
}}
|
||||
>
|
||||
{imageSrc.map((src, index) => (
|
||||
<div key={index}>
|
||||
<img
|
||||
src={src}
|
||||
style={{
|
||||
width: 280,
|
||||
transform: 'translateY(-30px)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
);
|
||||
}
|
||||
|
||||
export default C;
|
||||
@ -0,0 +1,88 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Spin, Typography } from '@arco-design/web-react';
|
||||
import { DonutChart } from 'bizcharts';
|
||||
import axios from 'axios';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
|
||||
function PopularContent() {
|
||||
const t = useLocale(locale);
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchData = () => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get('/api/workplace/content-percentage')
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title heading={6}>
|
||||
{t['workplace.contentPercentage']}
|
||||
</Typography.Title>
|
||||
<Spin loading={loading} style={{ display: 'block' }}>
|
||||
<DonutChart
|
||||
autoFit
|
||||
height={340}
|
||||
data={data}
|
||||
radius={0.7}
|
||||
innerRadius={0.65}
|
||||
angleField="count"
|
||||
colorField="type"
|
||||
color={['#21CCFF', '#313CA9', '#249EFF']}
|
||||
interactions={[
|
||||
{
|
||||
type: 'element-single-selected',
|
||||
},
|
||||
]}
|
||||
tooltip={{ showMarkers: false }}
|
||||
label={{
|
||||
visible: true,
|
||||
type: 'spider',
|
||||
formatter: (v) => `${(v.percent * 100).toFixed(0)}%`,
|
||||
style: {
|
||||
fill: '#86909C',
|
||||
fontSize: 14,
|
||||
},
|
||||
}}
|
||||
legend={{
|
||||
position: 'bottom',
|
||||
}}
|
||||
statistic={{
|
||||
title: {
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
lineHeight: 2,
|
||||
color: 'rgb(--var(color-text-1))',
|
||||
},
|
||||
formatter: () => '内容量',
|
||||
},
|
||||
content: {
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
color: 'rgb(--var(color-text-1))',
|
||||
},
|
||||
formatter: (_, data) => {
|
||||
const sum = data.reduce((a, b) => a + b.count, 0);
|
||||
return Number(sum).toLocaleString();
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Spin>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopularContent;
|
||||
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Link, Card, Typography } from '@arco-design/web-react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/docs.module.less';
|
||||
|
||||
const links = {
|
||||
react: 'https://arco.design/react/docs/start',
|
||||
vue: 'https://arco.design/vue/docs/start',
|
||||
designLab: 'https://arco.design/themes',
|
||||
materialMarket: 'https://arco.design/material/',
|
||||
};
|
||||
function QuickOperation() {
|
||||
const t = useLocale(locale);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography.Title heading={6}>{t['workplace.docs']}</Typography.Title>
|
||||
<Link>{t['workplace.seeMore']}</Link>
|
||||
</div>
|
||||
<div className={styles.docs}>
|
||||
{Object.entries(links).map(([key, value]) => (
|
||||
<Link className={styles.link} key={key} href={value} target="_blank">
|
||||
{t[`workplace.${key}`]}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuickOperation;
|
||||
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Grid, Space } from '@arco-design/web-react';
|
||||
import Overview from './overview';
|
||||
import PopularContents from './popular-contents';
|
||||
import ContentPercentage from './content-percentage';
|
||||
import Shortcuts from './shortcuts';
|
||||
import Announcement from './announcement';
|
||||
import Carousel from './carousel';
|
||||
import Docs from './docs';
|
||||
import styles from './style/index.module.less';
|
||||
import './mock';
|
||||
|
||||
const { Row, Col } = Grid;
|
||||
|
||||
const gutter = 16;
|
||||
|
||||
function Workplace() {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Space size={16} direction="vertical" className={styles.left}>
|
||||
<Overview />
|
||||
<Row gutter={gutter}>
|
||||
<Col span={12}>
|
||||
<PopularContents />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<ContentPercentage />
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
<Space className={styles.right} size={16} direction="vertical">
|
||||
<Shortcuts />
|
||||
<Carousel />
|
||||
<Announcement />
|
||||
<Docs />
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Workplace;
|
||||
@ -0,0 +1,78 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'workplace.welcomeBack': 'Welcome Back,',
|
||||
'workplace.totalOnlyData': 'Total online data',
|
||||
'workplace.contentInMarket': 'Content in market',
|
||||
'workplace.comments': 'Comments',
|
||||
'workplace.growth': 'Growth',
|
||||
'workplace.contentData': 'Content Data',
|
||||
'workplace.1year': 'Nearly 1 Year',
|
||||
'workplace.seeMore': 'See More',
|
||||
'workplace.popularContents': 'Popular Contents',
|
||||
'workplace.text': 'Text',
|
||||
'workplace.image': 'Image',
|
||||
'workplace.video': 'Video',
|
||||
'workplace.column.rank': 'Rank',
|
||||
'workplace.column.title': 'Title',
|
||||
'workplace.column.pv': 'PV',
|
||||
'workplace.column.increase': 'Daily Increase',
|
||||
'workplace.contentPercentage': 'Percentage of content categories',
|
||||
'workplace.shortcuts': 'Shortcuts',
|
||||
'workplace.manage': 'Manage',
|
||||
'workplace.contentMgmt': 'Management',
|
||||
'workplace.contentStatistic': 'Statistic',
|
||||
'workplace.advancedMgmt': 'Advance',
|
||||
'workplace.onlinePromotion': 'Promotion',
|
||||
'workplace.marketing': 'Marketing',
|
||||
'workplace.recent': 'Recent',
|
||||
'workplace.announcement': 'Announcement',
|
||||
'workplace.activity': 'Activity',
|
||||
'workplace.info': 'Info',
|
||||
'workplace.notice': 'Notice',
|
||||
'workplace.docs': 'Document',
|
||||
'workplace.pecs': 'pecs',
|
||||
'workplace.designLab': 'DesignLab',
|
||||
'workplace.materialMarket': 'MaterialMarket',
|
||||
'workplace.react': 'React Quick Start',
|
||||
'workplace.vue': 'Vue Quick Start',
|
||||
},
|
||||
'zh-CN': {
|
||||
'workplace.welcomeBack': '欢迎回来,',
|
||||
'workplace.totalOnlyData': '线上总数据',
|
||||
'workplace.contentInMarket': '投放中的内容',
|
||||
'workplace.comments': '日新增评论',
|
||||
'workplace.growth': '较昨日新增',
|
||||
'workplace.contentData': '内容数据',
|
||||
'workplace.1year': '近1年',
|
||||
'workplace.seeMore': '查看更多',
|
||||
'workplace.popularContents': '线上热门内容',
|
||||
'workplace.text': '文本',
|
||||
'workplace.image': '图文',
|
||||
'workplace.video': '视频',
|
||||
'workplace.column.rank': '排名',
|
||||
'workplace.column.title': '内容标题',
|
||||
'workplace.column.pv': '点击量',
|
||||
'workplace.column.increase': '日涨幅',
|
||||
'workplace.contentPercentage': '内容类别占比',
|
||||
'workplace.shortcuts': '快捷入口',
|
||||
'workplace.manage': '管理',
|
||||
'workplace.contentMgmt': '内容管理',
|
||||
'workplace.contentStatistic': '内容数据',
|
||||
'workplace.advancedMgmt': '高级管理',
|
||||
'workplace.onlinePromotion': '线上推广',
|
||||
'workplace.marketing': '内容投放',
|
||||
'workplace.recent': '最近访问',
|
||||
'workplace.announcement': '公告',
|
||||
'workplace.activity': '活动',
|
||||
'workplace.info': '消息',
|
||||
'workplace.notice': '通知',
|
||||
'workplace.docs': '文档中心',
|
||||
'workplace.pecs': '个',
|
||||
'workplace.designLab': '风格配置平台',
|
||||
'workplace.materialMarket': '物料市场',
|
||||
'workplace.react': 'React 组件库',
|
||||
'workplace.vue': 'Vue 组件库',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,117 @@
|
||||
import Mock from 'mockjs';
|
||||
import qs from 'query-string';
|
||||
import setupMock from '@/utils/setupMock';
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
Mock.mock(new RegExp('/api/workplace/overview-content'), () => {
|
||||
const year = new Date().getFullYear();
|
||||
const getLineData = () => {
|
||||
return new Array(12).fill(0).map((_item, index) => ({
|
||||
date: `${year}-${index + 1}`,
|
||||
count: Mock.Random.natural(20000, 75000),
|
||||
}));
|
||||
};
|
||||
return {
|
||||
allContents: '373.5w+',
|
||||
liveContents: '368',
|
||||
increaseComments: '8874',
|
||||
growthRate: '2.8%',
|
||||
chartData: getLineData(),
|
||||
};
|
||||
});
|
||||
|
||||
const getList = () => {
|
||||
const { list } = Mock.mock({
|
||||
'list|100': [
|
||||
{
|
||||
'rank|+1': 1,
|
||||
title: () =>
|
||||
Mock.Random.pick([
|
||||
'经济日报:财政政策要精准提升效能',
|
||||
'“双12”遇冷消费者厌倦了电商平台的促销“套路”',
|
||||
'致敬坚守战“疫”一线的社区工作者',
|
||||
'普高还是职高?家长们陷入选校难题',
|
||||
]),
|
||||
pv: function () {
|
||||
return 500000 - 3200 * this.rank;
|
||||
},
|
||||
increase: '@float(-1, 1)',
|
||||
},
|
||||
],
|
||||
});
|
||||
return list;
|
||||
};
|
||||
const listText = getList();
|
||||
const listPic = getList();
|
||||
const listVideo = getList();
|
||||
|
||||
Mock.mock(new RegExp('/api/workplace/popular-contents'), (params) => {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 5,
|
||||
category = 0,
|
||||
} = qs.parseUrl(params.url).query as unknown as {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
category?: number;
|
||||
};
|
||||
|
||||
const list = [listText, listPic, listVideo][Number(category)];
|
||||
return {
|
||||
list: list.slice((page - 1) * pageSize, page * pageSize),
|
||||
total: 100,
|
||||
};
|
||||
});
|
||||
|
||||
Mock.mock(new RegExp('/api/workplace/content-percentage'), () => {
|
||||
return [
|
||||
{
|
||||
type: '纯文本',
|
||||
count: 148564,
|
||||
percent: 0.16,
|
||||
},
|
||||
{
|
||||
type: '图文类',
|
||||
count: 334271,
|
||||
percent: 0.36,
|
||||
},
|
||||
{
|
||||
type: '视频类',
|
||||
count: 445695,
|
||||
percent: 0.48,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
Mock.mock(new RegExp('/api/workplace/announcement'), () => {
|
||||
return [
|
||||
{
|
||||
type: 'activity',
|
||||
key: '1',
|
||||
content: '内容最新优惠活动',
|
||||
},
|
||||
{
|
||||
type: 'info',
|
||||
key: '2',
|
||||
content: '新增内容尚未通过审核,详情请点击查看。',
|
||||
},
|
||||
{
|
||||
type: 'notice',
|
||||
key: '3',
|
||||
content: '当前产品试用期即将结束,如需续费请点击查看。',
|
||||
},
|
||||
{
|
||||
type: 'notice',
|
||||
key: '4',
|
||||
content: '1 月新系统升级计划通知',
|
||||
},
|
||||
{
|
||||
type: 'info',
|
||||
key: '5',
|
||||
content: '新增内容已经通过审核,详情请点击查看。',
|
||||
},
|
||||
];
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,156 @@
|
||||
import React, { useState, useEffect, ReactNode } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Card,
|
||||
Typography,
|
||||
Divider,
|
||||
Skeleton,
|
||||
Link,
|
||||
} from '@arco-design/web-react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { IconCaretUp } from '@arco-design/web-react/icon';
|
||||
import OverviewAreaLine from '@/components/Chart/overview-area-line';
|
||||
import axios from 'axios';
|
||||
import locale from './locale';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import styles from './style/overview.module.less';
|
||||
import IconCalendar from './assets/calendar.svg';
|
||||
import IconComments from './assets/comments.svg';
|
||||
import IconContent from './assets/content.svg';
|
||||
import IconIncrease from './assets/increase.svg';
|
||||
|
||||
const { Row, Col } = Grid;
|
||||
|
||||
type StatisticItemType = {
|
||||
icon?: ReactNode;
|
||||
title?: ReactNode;
|
||||
count?: ReactNode;
|
||||
loading?: boolean;
|
||||
unit?: ReactNode;
|
||||
};
|
||||
|
||||
function StatisticItem(props: StatisticItemType) {
|
||||
const { icon, title, count, loading, unit } = props;
|
||||
return (
|
||||
<div className={styles.item}>
|
||||
<div className={styles.icon}>{icon}</div>
|
||||
<div>
|
||||
<Skeleton loading={loading} text={{ rows: 2, width: 60 }} animation>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.count}>
|
||||
{count}
|
||||
<span className={styles.unit}>{unit}</span>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type DataType = {
|
||||
allContents?: string;
|
||||
liveContents?: string;
|
||||
increaseComments?: string;
|
||||
growthRate?: string;
|
||||
chartData?: { count?: number; date?: string }[];
|
||||
down?: boolean;
|
||||
};
|
||||
|
||||
function Overview() {
|
||||
const [data, setData] = useState<DataType>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const t = useLocale(locale);
|
||||
|
||||
const userInfo = useSelector((state: any) => state.userInfo || {});
|
||||
|
||||
const fetchData = () => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get('/api/workplace/overview-content')
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title heading={5}>
|
||||
{t['workplace.welcomeBack']}
|
||||
{userInfo.name}
|
||||
</Typography.Title>
|
||||
<Divider />
|
||||
<Row>
|
||||
<Col flex={1}>
|
||||
<StatisticItem
|
||||
icon={<IconCalendar />}
|
||||
title={t['workplace.totalOnlyData']}
|
||||
count={data.allContents}
|
||||
loading={loading}
|
||||
unit={t['workplace.pecs']}
|
||||
/>
|
||||
</Col>
|
||||
<Divider type="vertical" className={styles.divider} />
|
||||
<Col flex={1}>
|
||||
<StatisticItem
|
||||
icon={<IconContent />}
|
||||
title={t['workplace.contentInMarket']}
|
||||
count={data.liveContents}
|
||||
loading={loading}
|
||||
unit={t['workplace.pecs']}
|
||||
/>
|
||||
</Col>
|
||||
<Divider type="vertical" className={styles.divider} />
|
||||
<Col flex={1}>
|
||||
<StatisticItem
|
||||
icon={<IconComments />}
|
||||
title={t['workplace.comments']}
|
||||
count={data.increaseComments}
|
||||
loading={loading}
|
||||
unit={t['workplace.pecs']}
|
||||
/>
|
||||
</Col>
|
||||
<Divider type="vertical" className={styles.divider} />
|
||||
<Col flex={1}>
|
||||
<StatisticItem
|
||||
icon={<IconIncrease />}
|
||||
title={t['workplace.growth']}
|
||||
count={
|
||||
<span>
|
||||
{data.growthRate}{' '}
|
||||
<IconCaretUp
|
||||
style={{ fontSize: 18, color: 'rgb(var(--green-6))' }}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
loading={loading}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<div>
|
||||
<div className={styles.ctw}>
|
||||
<Typography.Paragraph
|
||||
className={styles['chart-title']}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
{t['workplace.contentData']}
|
||||
<span className={styles['chart-sub-title']}>
|
||||
({t['workplace.1year']})
|
||||
</span>
|
||||
</Typography.Paragraph>
|
||||
<Link>{t['workplace.seeMore']}</Link>
|
||||
</div>
|
||||
<OverviewAreaLine data={data.chartData} loading={loading} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default Overview;
|
||||
@ -0,0 +1,115 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Link, Card, Radio, Table, Typography } from '@arco-design/web-react';
|
||||
import { IconCaretDown, IconCaretUp } from '@arco-design/web-react/icon';
|
||||
import axios from 'axios';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/popular-contents.module.less';
|
||||
|
||||
function PopularContent() {
|
||||
const t = useLocale(locale);
|
||||
const [type, setType] = useState(0);
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const fetchData = useCallback(() => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.get(
|
||||
`/api/workplace/popular-contents?page=${page}&pageSize=5&category=${type}`
|
||||
)
|
||||
.then((res) => {
|
||||
setData(res.data.list);
|
||||
setTotal(res.data.total);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [page, type]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, fetchData]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t['workplace.column.rank'],
|
||||
dataIndex: 'rank',
|
||||
width: 65,
|
||||
},
|
||||
{
|
||||
title: t['workplace.column.title'],
|
||||
dataIndex: 'title',
|
||||
render: (x) => (
|
||||
<Typography.Paragraph style={{ margin: 0 }} ellipsis>
|
||||
{x}
|
||||
</Typography.Paragraph>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t['workplace.column.pv'],
|
||||
dataIndex: 'pv',
|
||||
width: 100,
|
||||
render: (text) => {
|
||||
return `${text / 1000}k`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t['workplace.column.increase'],
|
||||
dataIndex: 'increase',
|
||||
sorter: (a, b) => a.increase - b.increase,
|
||||
width: 110,
|
||||
render: (text) => {
|
||||
return (
|
||||
<span>
|
||||
{`${(text * 100).toFixed(2)}%`}
|
||||
<span className={styles['symbol']}>
|
||||
{text < 0 ? (
|
||||
<IconCaretUp style={{ color: 'rgb(var(--green-6))' }} />
|
||||
) : (
|
||||
<IconCaretDown style={{ color: 'rgb(var(--red-6))' }} />
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography.Title heading={6}>
|
||||
{t['workplace.popularContents']}
|
||||
</Typography.Title>
|
||||
<Link>{t['workplace.seeMore']}</Link>
|
||||
</div>
|
||||
<Radio.Group
|
||||
type="button"
|
||||
value={type}
|
||||
onChange={setType}
|
||||
options={[
|
||||
{ label: t['workplace.text'], value: 0 },
|
||||
{ label: t['workplace.image'], value: 1 },
|
||||
{ label: t['workplace.video'], value: 2 },
|
||||
]}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Table
|
||||
rowKey="rank"
|
||||
columns={columns}
|
||||
data={data}
|
||||
loading={loading}
|
||||
tableLayoutFixed
|
||||
onChange={(pagination) => {
|
||||
setPage(pagination.current);
|
||||
}}
|
||||
pagination={{ total, current: page, pageSize: 5, simple: true }}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopularContent;
|
||||
@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Link,
|
||||
Card,
|
||||
Divider,
|
||||
Message,
|
||||
Typography,
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
IconFile,
|
||||
IconStorage,
|
||||
IconSettings,
|
||||
IconMobile,
|
||||
IconFire,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/shortcuts.module.less';
|
||||
|
||||
function Shortcuts() {
|
||||
const t = useLocale(locale);
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
title: t['workplace.contentMgmt'],
|
||||
key: 'Content Management',
|
||||
icon: <IconFile />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.contentStatistic'],
|
||||
key: 'Content Statistic',
|
||||
icon: <IconStorage />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.advancedMgmt'],
|
||||
key: 'Advanced Management',
|
||||
icon: <IconSettings />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.onlinePromotion'],
|
||||
key: 'Online Promotion',
|
||||
icon: <IconMobile />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.marketing'],
|
||||
key: 'Marketing',
|
||||
icon: <IconFire />,
|
||||
},
|
||||
];
|
||||
|
||||
const recentShortcuts = [
|
||||
{
|
||||
title: t['workplace.contentStatistic'],
|
||||
key: 'Content Statistic',
|
||||
icon: <IconStorage />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.contentMgmt'],
|
||||
key: 'Content Management',
|
||||
icon: <IconFile />,
|
||||
},
|
||||
{
|
||||
title: t['workplace.advancedMgmt'],
|
||||
key: 'Advanced Management',
|
||||
icon: <IconSettings />,
|
||||
},
|
||||
];
|
||||
|
||||
function onClickShortcut(key) {
|
||||
Message.info({
|
||||
content: (
|
||||
<span>
|
||||
You clicked <b>{key}</b>
|
||||
</span>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography.Title heading={6}>
|
||||
{t['workplace.shortcuts']}
|
||||
</Typography.Title>
|
||||
<Link>{t['workplace.seeMore']}</Link>
|
||||
</div>
|
||||
<div className={styles.shortcuts}>
|
||||
{shortcuts.map((shortcut) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
key={shortcut.key}
|
||||
onClick={() => onClickShortcut(shortcut.key)}
|
||||
>
|
||||
<div className={styles.icon}>{shortcut.icon}</div>
|
||||
<div className={styles.title}>{shortcut.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={styles.recent}>{t['workplace.recent']}</div>
|
||||
<div className={styles.shortcuts}>
|
||||
{recentShortcuts.map((shortcut) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
key={shortcut.key}
|
||||
onClick={() => onClickShortcut(shortcut.key)}
|
||||
>
|
||||
<div className={styles.icon}>{shortcut.icon}</div>
|
||||
<div className={styles.title}>{shortcut.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default Shortcuts;
|
||||
@ -0,0 +1,19 @@
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.link {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-2);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
.docs {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-text-2);
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:hover {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
.banner {
|
||||
background-color: var(--color-bg-2);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: calc(100% - 296px);
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
.container {
|
||||
padding: 20px;
|
||||
|
||||
:global(.arco-divider-horizontal) {
|
||||
border-bottom: 1px solid var(--color-border-1);
|
||||
}
|
||||
|
||||
:global(.arco-divider-vertical) {
|
||||
border-left: 1px solid var(--color-border-1);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
background-color: var(--color-fill-2);
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
|
||||
.unit {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-2);
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.ctw {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chart-sub-title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
.symbol {
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
|
||||
> svg {
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
.shortcuts {
|
||||
display: grid;
|
||||
grid-template-columns: 33.33% 33.33% 33.33%;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.icon {
|
||||
background-color: var(--color-primary-light-1);
|
||||
|
||||
svg {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-fill-2);
|
||||
margin-bottom: 4px;
|
||||
|
||||
svg {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.recent {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: var(--color-text-1);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Result, Button } from '@arco-design/web-react';
|
||||
import locale from './locale';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
function Exception403() {
|
||||
const t = useLocale(locale);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.wrapper}>
|
||||
<Result
|
||||
className={styles.result}
|
||||
status="403"
|
||||
subTitle={t['exception.result.403.description']}
|
||||
extra={
|
||||
<Button key="back" type="primary">
|
||||
{t['exception.result.403.back']}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Exception403;
|
||||
@ -0,0 +1,17 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'menu.exception': 'Exception page',
|
||||
'menu.exception.403': '403',
|
||||
'exception.result.403.description':
|
||||
'Access to this resource on the server is denied.',
|
||||
'exception.result.403.back': 'Back',
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.exception': '异常页',
|
||||
'menu.exception.403': '403',
|
||||
'exception.result.403.description': '对不起,您没有访问该资源的权限',
|
||||
'exception.result.403.back': '返回',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,11 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-1);
|
||||
height: calc(100vh - 168px);
|
||||
}
|
||||
|
||||
.result {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Result, Button } from '@arco-design/web-react';
|
||||
import locale from './locale';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
function Exception404() {
|
||||
const t = useLocale(locale);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Result
|
||||
className={styles.result}
|
||||
status="404"
|
||||
subTitle={t['exception.result.404.description']}
|
||||
extra={[
|
||||
<Button key="again" style={{ marginRight: 16 }}>
|
||||
{t['exception.result.404.retry']}
|
||||
</Button>,
|
||||
<Button key="back" type="primary">
|
||||
{t['exception.result.404.back']}
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Exception404;
|
||||
@ -0,0 +1,18 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'menu.exception': 'Exception page',
|
||||
'menu.exception.404': '404',
|
||||
'exception.result.404.description': 'Whoops, this page is gone.',
|
||||
'exception.result.404.retry': 'Retry',
|
||||
'exception.result.404.back': 'Back',
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.exception': '异常页',
|
||||
'menu.exception.404': '404',
|
||||
'exception.result.404.description': '抱歉,页面不见了~',
|
||||
'exception.result.404.retry': '重试',
|
||||
'exception.result.404.back': '返回',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,11 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-1);
|
||||
height: calc(100vh - 168px);
|
||||
}
|
||||
|
||||
.result {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Result, Button } from '@arco-design/web-react';
|
||||
import locale from './locale';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
function Exception500() {
|
||||
const t = useLocale(locale);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Result
|
||||
className={styles.result}
|
||||
status="500"
|
||||
subTitle={t['exception.result.500.description']}
|
||||
extra={
|
||||
<Button key="back" type="primary">
|
||||
{t['exception.result.500.back']}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Exception500;
|
||||
@ -0,0 +1,16 @@
|
||||
const i18n = {
|
||||
'en-US': {
|
||||
'menu.exception': 'Exception page',
|
||||
'menu.exception.500': '500',
|
||||
'exception.result.500.description': 'Internal server error',
|
||||
'exception.result.500.back': 'Back',
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.exception': '异常页',
|
||||
'menu.exception.500': '500',
|
||||
'exception.result.500.description': '抱歉,服务器出了点问题~',
|
||||
'exception.result.500.back': '返回',
|
||||
},
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@ -0,0 +1,11 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-1);
|
||||
height: calc(100vh - 168px);
|
||||
}
|
||||
|
||||
.result {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
@ -0,0 +1,281 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import {
|
||||
Typography,
|
||||
Card,
|
||||
Form,
|
||||
Select,
|
||||
Input,
|
||||
Grid,
|
||||
Space,
|
||||
Button,
|
||||
Message,
|
||||
} from '@arco-design/web-react';
|
||||
import { FormInstance } from '@arco-design/web-react/es/Form';
|
||||
import axios from 'axios';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/index.module.less';
|
||||
import './mock';
|
||||
|
||||
function GroupForm() {
|
||||
const t = useLocale(locale);
|
||||
const formRef = useRef<FormInstance>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function submit(data) {
|
||||
setLoading(true);
|
||||
axios
|
||||
.post('/api/groupForm', {
|
||||
data,
|
||||
})
|
||||
.then(() => {
|
||||
Message.success(t['groupForm.submitSuccess']);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.current.validate().then((values) => {
|
||||
submit(values);
|
||||
});
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
formRef.current.resetFields();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Form layout="vertical" ref={formRef} className={styles['form-group']}>
|
||||
<Card>
|
||||
<Typography.Title heading={6}>
|
||||
{t['groupForm.title.video']}
|
||||
</Typography.Title>
|
||||
<Grid.Row gutter={80}>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.mode']}
|
||||
field="video.mode"
|
||||
initialValue={'custom'}
|
||||
>
|
||||
<Select placeholder={t['groupForm.placeholder.video.mode']}>
|
||||
<Select.Option value="custom">自定义</Select.Option>
|
||||
<Select.Option value="mode1">模式1</Select.Option>
|
||||
<Select.Option value="mode2">模式2</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.acquisition.resolution']}
|
||||
field="video.acquisition.resolution"
|
||||
>
|
||||
<Select
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.acquisition.resolution']
|
||||
}
|
||||
>
|
||||
<Select.Option value="resolution1">分辨率1</Select.Option>
|
||||
<Select.Option value="resolution2">分辨率2</Select.Option>
|
||||
<Select.Option value="resolution3">分辨率3</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.acquisition.frameRate']}
|
||||
field="video.acquisition.frameRate"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.acquisition.frameRate']
|
||||
}
|
||||
addAfter="fps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
<Grid.Row gutter={80}>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.resolution']}
|
||||
field="video.encoding.resolution"
|
||||
>
|
||||
<Select
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.resolution']
|
||||
}
|
||||
>
|
||||
<Select.Option value="resolution1">分辨率1</Select.Option>
|
||||
<Select.Option value="resolution2">分辨率2</Select.Option>
|
||||
<Select.Option value="resolution3">分辨率3</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.rate.min']}
|
||||
field="video.encoding.rate.min"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.rate.min']
|
||||
}
|
||||
addAfter="bps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.rate.max']}
|
||||
field="video.encoding.rate.max"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.rate.max']
|
||||
}
|
||||
addAfter="bps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
<Grid.Row gutter={80}>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.rate.default']}
|
||||
field="video.encoding.rate.default"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.rate.default']
|
||||
}
|
||||
addAfter="bps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.frameRate']}
|
||||
field="video.encoding.frameRate"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.frameRate']
|
||||
}
|
||||
addAfter="fps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.video.encoding.profile']}
|
||||
field="video.encoding.profile"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.video.encoding.profile']
|
||||
}
|
||||
addAfter="bps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
</Card>
|
||||
<Card>
|
||||
<Typography.Title heading={6}>
|
||||
{t['groupForm.title.audio']}
|
||||
</Typography.Title>
|
||||
<Grid.Row gutter={80}>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.audio.mode']}
|
||||
initialValue={'custom'}
|
||||
field="audio.mode"
|
||||
>
|
||||
<Select placeholder={t['groupForm.placeholder.audio.mode']}>
|
||||
<Select.Option value="custom">自定义</Select.Option>
|
||||
<Select.Option value="mode1">模式1</Select.Option>
|
||||
<Select.Option value="mode2">模式2</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.audio.acquisition.channels']}
|
||||
field="audio.acquisition.channels"
|
||||
>
|
||||
<Select
|
||||
placeholder={
|
||||
t['groupForm.placeholder.audio.acquisition.channels']
|
||||
}
|
||||
>
|
||||
<Select.Option value="1">1</Select.Option>
|
||||
<Select.Option value="2">2</Select.Option>
|
||||
<Select.Option value="3">3</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.audio.encoding.rate']}
|
||||
field="audio.encoding.rate"
|
||||
>
|
||||
<Input
|
||||
placeholder={t['groupForm.placeholder.audio.encoding.rate']}
|
||||
addAfter="bps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
<Grid.Row gutter={80}>
|
||||
<Grid.Col span={8}>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.audio.encoding.profile']}
|
||||
field="audio.encoding.profile"
|
||||
>
|
||||
<Input
|
||||
placeholder={
|
||||
t['groupForm.placeholder.audio.encoding.profile']
|
||||
}
|
||||
addAfter="fps"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
</Card>
|
||||
<Card style={{ marginBottom: '40px' }}>
|
||||
<Typography.Title heading={6}>
|
||||
{t['groupForm.title.explanation']}
|
||||
</Typography.Title>
|
||||
<Form.Item
|
||||
label={t['groupForm.form.label.explanation']}
|
||||
field="audio.explanation"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder={t['groupForm.placeholder.explanation']}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
</Form>
|
||||
<div className={styles.actions}>
|
||||
<Space>
|
||||
<Button onClick={handleReset} size="large">
|
||||
{t['groupForm.reset']}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSubmit}
|
||||
loading={loading}
|
||||
size="large"
|
||||
>
|
||||
{t['groupForm.submit']}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GroupForm;
|
||||
@ -0,0 +1,11 @@
|
||||
import Mock from 'mockjs';
|
||||
import setupMock from '@/utils/setupMock';
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
// 保存表单数据
|
||||
Mock.mock(new RegExp('/api/groupForm'), () => {
|
||||
return true;
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
.container {
|
||||
overflow: hidden;
|
||||
|
||||
:global(.arco-card-body) {
|
||||
padding: 20px 20px 10px;
|
||||
}
|
||||
|
||||
:global(.arco-card) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 12px 40px;
|
||||
background-color: var(--color-bg-2);
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -3px 12px rgb(0 0 0 / 10%);
|
||||
}
|
||||
@ -0,0 +1,257 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Steps,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
DatePicker,
|
||||
InputTag,
|
||||
Button,
|
||||
Typography,
|
||||
Space,
|
||||
Card,
|
||||
Switch,
|
||||
Result,
|
||||
} from '@arco-design/web-react';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import locale from './locale';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
function StepForm() {
|
||||
const t = useLocale(locale);
|
||||
const [current, setCurrent] = useState(1);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const viewForm = () => {
|
||||
const values = form.getFields();
|
||||
form.setFields(values);
|
||||
setCurrent(1);
|
||||
};
|
||||
|
||||
const reCreateForm = () => {
|
||||
form.resetFields();
|
||||
setCurrent(1);
|
||||
};
|
||||
|
||||
const toNext = async () => {
|
||||
try {
|
||||
await form.validate();
|
||||
setCurrent(current + 1);
|
||||
} catch (_) {}
|
||||
};
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Card>
|
||||
<Title heading={5}>{t['stepForm.desc.basicInfo']}</Title>
|
||||
<div className={styles.wrapper}>
|
||||
<Steps current={current} lineless>
|
||||
<Steps.Step
|
||||
title={t['stepForm.title.basicInfo']}
|
||||
description={t['stepForm.desc.basicInfo']}
|
||||
/>
|
||||
<Steps.Step
|
||||
title={t['stepForm.title.channel']}
|
||||
description={t['stepForm.desc.channel']}
|
||||
/>
|
||||
<Steps.Step
|
||||
title={t['stepForm.title.created']}
|
||||
description={t['stepForm.desc.created']}
|
||||
/>
|
||||
</Steps>
|
||||
<Form form={form} className={styles.form}>
|
||||
{current === 1 && (
|
||||
<Form.Item noStyle>
|
||||
<Form.Item
|
||||
label={t['stepForm.basicInfo.name']}
|
||||
required
|
||||
field="basic.name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.basicInfo.name.required'],
|
||||
},
|
||||
{
|
||||
validator: (value: string, callback) => {
|
||||
if (!/^[\u4e00-\u9fa5a-zA-Z0-9]{1,20}$/g.test(value)) {
|
||||
callback(t['stepForm.basicInfo.name.placeholder']);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t['stepForm.basicInfo.name.placeholder']}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.basicInfo.channelType']}
|
||||
required
|
||||
initialValue="app"
|
||||
field="basic.channelType"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.basicInfo.channelType.required'],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="app">APP通用渠道</Select.Option>
|
||||
<Select.Option value="site">网页通用渠道</Select.Option>
|
||||
<Select.Option value="game">游戏通用渠道</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.basicInfo.time']}
|
||||
required
|
||||
field="basic.time"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.basicInfo.time.required'],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<DatePicker.RangePicker style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.basicInfo.link']}
|
||||
required
|
||||
extra={t['stepForm.basicInfo.link.tips']}
|
||||
field="basic.link"
|
||||
initialValue={'https://arco.design'}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t['stepForm.basicInfo.link.placeholder']}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
)}
|
||||
{current === 2 && (
|
||||
<Form.Item noStyle>
|
||||
<Form.Item
|
||||
label={t['stepForm.channel.source']}
|
||||
required
|
||||
field="channel.source"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.channel.source.required'],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t['stepForm.channel.source.placeholder']}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.channel.media']}
|
||||
required
|
||||
field="channel.media"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.channel.media.required'],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={t['stepForm.channel.media.placeholder']}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.channel.keywords']}
|
||||
required
|
||||
field="channel.keywords"
|
||||
initialValue={['今日头条', '火山']}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<InputTag />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t['stepForm.channel.remind']}
|
||||
required
|
||||
initialValue={true}
|
||||
field="channel.remind"
|
||||
triggerPropName="checked"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t['stepForm.channel.content']}
|
||||
required
|
||||
field="channel.content"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t['stepForm.channel.content.required'],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder={t['stepForm.channel.content.placeholder']}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
)}
|
||||
{current !== 3 ? (
|
||||
<Form.Item label=" ">
|
||||
<Space>
|
||||
{current === 2 && (
|
||||
<Button
|
||||
size="large"
|
||||
onClick={() => setCurrent(current - 1)}
|
||||
>
|
||||
{t['stepForm.prev']}
|
||||
</Button>
|
||||
)}
|
||||
{current !== 3 && (
|
||||
<Button type="primary" size="large" onClick={toNext}>
|
||||
{t['stepForm.next']}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item noStyle>
|
||||
<Result
|
||||
status="success"
|
||||
title={t['stepForm.created.success.title']}
|
||||
subTitle={t['stepForm.created.success.desc']}
|
||||
extra={[
|
||||
<Button
|
||||
key="reset"
|
||||
style={{ marginRight: 16 }}
|
||||
onClick={viewForm}
|
||||
>
|
||||
{t['stepForm.created.success.view']}
|
||||
</Button>,
|
||||
<Button key="again" type="primary" onClick={reCreateForm}>
|
||||
{t['stepForm.created.success.again']}
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
{current === 3 && (
|
||||
<div className={styles['form-extra']}>
|
||||
<Title heading={6}>{t['stepForm.created.extra.title']}</Title>
|
||||
<Paragraph type="secondary">
|
||||
{t['stepForm.created.extra.desc']}
|
||||
<Button type="text">{t['stepForm.created.extra.detail']}</Button>
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StepForm;
|
||||
@ -0,0 +1,11 @@
|
||||
import Mock from 'mockjs';
|
||||
import setupMock from '@/utils/setupMock';
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
// 保存表单数据
|
||||
Mock.mock(new RegExp('/api/groupForm'), () => {
|
||||
return true;
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
.container {
|
||||
:global(.arco-card-body) {
|
||||
> h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 624px;
|
||||
margin: 0 auto;
|
||||
padding-top: 56px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-top: 76px;
|
||||
padding-right: 76px;
|
||||
}
|
||||
|
||||
.form-extra {
|
||||
width: 895px;
|
||||
margin: 54px auto;
|
||||
background-color: var(--color-fill-1);
|
||||
padding: 20px;
|
||||
margin-bottom: 120px;
|
||||
|
||||
> h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue