(this.el = el)}
144 | style={{
145 | ...styles,
146 | height,
147 | width,
148 | }}
149 | />
150 | );
151 | }
152 | }
153 |
154 | export default connect(state=>state.app)(LineChart);
155 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/PanelGroup/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Row, Col, Icon } from "antd";
3 | import CountUp from "react-countup";
4 | import "./index.less";
5 |
6 | const chartList = [
7 | {
8 | type: "New Visits",
9 | icon: "user",
10 | num: 102400,
11 | color: "#40c9c6",
12 | },
13 | {
14 | type: "Messages",
15 | icon: "message",
16 | num: 81212,
17 | color: "#36a3f7",
18 | },
19 | {
20 | type: "Purchases",
21 | icon: "pay-circle",
22 | num: 9280,
23 | color: "#f4516c",
24 | },
25 | {
26 | type: "Shoppings",
27 | icon: "shopping-cart",
28 | num: 13600,
29 | color: "#f6ab40",
30 | },
31 | ];
32 |
33 | const PanelGroup = (props) => {
34 | const { handleSetLineChartData } = props;
35 | return (
36 |
64 | );
65 | };
66 |
67 | export default PanelGroup;
68 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/PanelGroup/index.less:
--------------------------------------------------------------------------------
1 | .panel-group-container {
2 | .panel-group {
3 | .card-panel-col{
4 | margin-bottom: 20px;
5 | }
6 | .card-panel {
7 | height: 108px;
8 | cursor: pointer;
9 | font-size: 12px;
10 | position: relative;
11 | overflow: hidden;
12 | color: #666;
13 | background: #fff;
14 | box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
15 | border-color: rgba(0, 0, 0, .05);
16 | &:hover {
17 | .card-panel-icon-wrapper {
18 | background: #ccc;
19 | }
20 | }
21 | .card-panel-icon-wrapper {
22 | float: left;
23 | margin: 14px 0 0 14px;
24 | padding: 16px;
25 | transition: all 0.38s ease-out;
26 | border-radius: 6px;
27 | }
28 | .card-panel-icon {
29 | float: left;
30 | font-size: 48px;
31 | }
32 | .card-panel-description {
33 | float: right;
34 | font-weight: bold;
35 | margin: 26px;
36 | margin-left: 0px;
37 | .card-panel-text {
38 | line-height: 18px;
39 | color: rgba(0, 0, 0, 0.45);
40 | font-size: 16px;
41 | margin-bottom: 12px;
42 | }
43 | .card-panel-num {
44 | font-size: 20px;
45 | }
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/views/dashboard/components/PieChart/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { PropTypes } from "prop-types";
3 | import { connect } from "react-redux";
4 | import echarts from "@/lib/echarts";
5 | import { debounce } from "@/utils";
6 |
7 | class PieChart extends Component {
8 | static propTypes = {
9 | width: PropTypes.string,
10 | height: PropTypes.string,
11 | className: PropTypes.string,
12 | styles: PropTypes.object,
13 | };
14 | static defaultProps = {
15 | width: "100%",
16 | height: "300px",
17 | styles: {},
18 | className: "",
19 | };
20 | state = {
21 | chart: null,
22 | };
23 |
24 | componentDidMount() {
25 | debounce(this.initChart.bind(this), 300)();
26 | window.addEventListener("resize", () => this.resize());
27 | }
28 | componentWillReceiveProps(nextProps) {
29 | if (nextProps.sidebarCollapsed !== this.props.sidebarCollapsed) {
30 | this.resize();
31 | }
32 | if (nextProps.chartData !== this.props.chartData) {
33 | debounce(this.initChart.bind(this), 300)();
34 | }
35 | }
36 |
37 | componentWillUnmount() {
38 | this.dispose();
39 | }
40 |
41 | resize() {
42 | const chart = this.state.chart;
43 | if (chart) {
44 | debounce(chart.resize.bind(this), 300)();
45 | }
46 | }
47 |
48 | dispose() {
49 | if (!this.state.chart) {
50 | return;
51 | }
52 | window.removeEventListener("resize", () => this.resize()); // 移除窗口,变化时重置图表
53 | this.setState({ chart: null });
54 | }
55 |
56 | setOptions() {
57 | const animationDuration = 3000;
58 | this.state.chart.setOption({
59 | tooltip: {
60 | trigger: "item",
61 | formatter: "{a}
{b} : {c} ({d}%)",
62 | },
63 | legend: {
64 | left: "center",
65 | bottom: "10",
66 | data: ["Industries", "Technology", "Forex", "Gold", "Forecasts"],
67 | },
68 | calculable: true,
69 | series: [
70 | {
71 | name: "WEEKLY WRITE ARTICLES",
72 | type: "pie",
73 | roseType: "radius",
74 | radius: [15, 95],
75 | center: ["50%", "38%"],
76 | data: [
77 | { value: 320, name: "Industries" },
78 | { value: 240, name: "Technology" },
79 | { value: 149, name: "Forex" },
80 | { value: 100, name: "Gold" },
81 | { value: 59, name: "Forecasts" },
82 | ],
83 | animationEasing: "cubicInOut",
84 | animationDuration
85 | },
86 | ],
87 | });
88 | }
89 |
90 | initChart() {
91 | if (!this.el) return;
92 | this.setState({ chart: echarts.init(this.el, "macarons") }, () => {
93 | this.setOptions(this.props.chartData);
94 | });
95 | }
96 |
97 | render() {
98 | const { className, height, width, styles } = this.props;
99 | return (
100 |
(this.el = el)}
103 | style={{
104 | ...styles,
105 | height,
106 | width,
107 | }}
108 | />
109 | );
110 | }
111 | }
112 |
113 | export default connect((state) => state.app)(PieChart);
114 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/RaddarChart/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { PropTypes } from "prop-types";
3 | import { connect } from "react-redux";
4 | import echarts from "@/lib/echarts";
5 | import { debounce } from "@/utils";
6 |
7 | class RaddarChart extends Component {
8 | static propTypes = {
9 | width: PropTypes.string,
10 | height: PropTypes.string,
11 | className: PropTypes.string,
12 | styles: PropTypes.object,
13 | };
14 | static defaultProps = {
15 | width: "100%",
16 | height: "300px",
17 | styles: {},
18 | className: "",
19 | };
20 | state = {
21 | chart: null,
22 | };
23 |
24 | componentDidMount() {
25 | debounce(this.initChart.bind(this), 300)();
26 | window.addEventListener("resize", () => this.resize());
27 | }
28 | componentWillReceiveProps(nextProps) {
29 | if (nextProps.sidebarCollapsed !== this.props.sidebarCollapsed) {
30 | this.resize();
31 | }
32 | if (nextProps.chartData !== this.props.chartData) {
33 | debounce(this.initChart.bind(this), 300)();
34 | }
35 | }
36 |
37 | componentWillUnmount() {
38 | this.dispose();
39 | }
40 |
41 | resize() {
42 | const chart = this.state.chart;
43 | if (chart) {
44 | debounce(chart.resize.bind(this), 300)();
45 | }
46 | }
47 |
48 | dispose() {
49 | if (!this.state.chart) {
50 | return;
51 | }
52 | window.removeEventListener("resize", () => this.resize()); // 移除窗口,变化时重置图表
53 | this.setState({ chart: null });
54 | }
55 |
56 | setOptions() {
57 | const animationDuration = 3000;
58 | this.state.chart.setOption({
59 | tooltip: {
60 | trigger: "axis",
61 | axisPointer: {
62 | // 坐标轴指示器,坐标轴触发有效
63 | type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
64 | },
65 | },
66 | radar: {
67 | radius: "66%",
68 | center: ["50%", "42%"],
69 | splitNumber: 8,
70 | splitArea: {
71 | areaStyle: {
72 | color: "rgba(127,95,132,.3)",
73 | opacity: 1,
74 | shadowBlur: 45,
75 | shadowColor: "rgba(0,0,0,.5)",
76 | shadowOffsetX: 0,
77 | shadowOffsetY: 15,
78 | },
79 | },
80 | indicator: [
81 | { name: "Sales", max: 10000 },
82 | { name: "Administration", max: 20000 },
83 | { name: "Information Techology", max: 20000 },
84 | { name: "Customer Support", max: 20000 },
85 | { name: "Development", max: 20000 },
86 | { name: "Marketing", max: 20000 },
87 | ],
88 | },
89 | legend: {
90 | left: "center",
91 | bottom: "10",
92 | data: ["Allocated Budget", "Expected Spending", "Actual Spending"],
93 | },
94 | series: [
95 | {
96 | type: "radar",
97 | symbolSize: 0,
98 | areaStyle: {
99 | normal: {
100 | shadowBlur: 13,
101 | shadowColor: "rgba(0,0,0,.2)",
102 | shadowOffsetX: 0,
103 | shadowOffsetY: 10,
104 | opacity: 1,
105 | },
106 | },
107 | data: [
108 | {
109 | value: [5000, 7000, 12000, 11000, 15000, 14000],
110 | name: "Allocated Budget",
111 | },
112 | {
113 | value: [4000, 9000, 15000, 15000, 13000, 11000],
114 | name: "Expected Spending",
115 | },
116 | {
117 | value: [5500, 11000, 12000, 15000, 12000, 12000],
118 | name: "Actual Spending",
119 | },
120 | ],
121 | animationDuration,
122 | },
123 | ],
124 | });
125 | }
126 |
127 | initChart() {
128 | if (!this.el) return;
129 | this.setState({ chart: echarts.init(this.el, "macarons") }, () => {
130 | this.setOptions(this.props.chartData);
131 | });
132 | }
133 |
134 | render() {
135 | const { className, height, width, styles } = this.props;
136 | return (
137 |
(this.el = el)}
140 | style={{
141 | ...styles,
142 | height,
143 | width,
144 | }}
145 | />
146 | );
147 | }
148 | }
149 |
150 | export default connect((state) => state.app)(RaddarChart);
151 |
--------------------------------------------------------------------------------
/src/views/dashboard/components/TransactionTable/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Table, Tag } from "antd";
3 | import { transactionList } from "@/api/remoteSearch";
4 |
5 | const columns = [
6 | {
7 | title: "Order_No",
8 | dataIndex: "order_no",
9 | key: "order_no",
10 | width: 200,
11 | },
12 | {
13 | title: "Price",
14 | dataIndex: "price",
15 | key: "price",
16 | width: 195,
17 | render: text => (`$${text}`),
18 | },
19 | {
20 | title: "Status",
21 | key: "tag",
22 | dataIndex: "tag",
23 | width: 100,
24 | render: (tag) => (
25 |
26 | {tag}
27 |
28 | ),
29 | },
30 | ];
31 |
32 | class TransactionTable extends Component {
33 | _isMounted = false; // 这个变量是用来标志当前组件是否挂载
34 | state = {
35 | list: [],
36 | };
37 | fetchData = () => {
38 | transactionList().then((response) => {
39 | const list = response.data.data.items.slice(0, 13);
40 | if (this._isMounted) {
41 | this.setState({ list });
42 | }
43 | });
44 | };
45 | componentDidMount() {
46 | this._isMounted = true;
47 | this.fetchData();
48 | }
49 | componentWillUnmount() {
50 | this._isMounted = false;
51 | }
52 | render() {
53 | return (
54 |
59 | );
60 | }
61 | }
62 |
63 | export default TransactionTable;
64 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Row, Col } from "antd";
3 | import "./index.less";
4 | import PanelGroup from "./components/PanelGroup";
5 | import LineChart from "./components/LineChart";
6 | import BarChart from "./components/BarChart";
7 | import RaddarChart from "./components/RaddarChart";
8 | import PieChart from "./components/PieChart";
9 | import TransactionTable from "./components/TransactionTable";
10 | import BoxCard from "./components/BoxCard";
11 |
12 | const lineChartDefaultData = {
13 | "New Visits": {
14 | expectedData: [100, 120, 161, 134, 105, 160, 165],
15 | actualData: [120, 82, 91, 154, 162, 140, 145],
16 | },
17 | Messages: {
18 | expectedData: [200, 192, 120, 144, 160, 130, 140],
19 | actualData: [180, 160, 151, 106, 145, 150, 130],
20 | },
21 | Purchases: {
22 | expectedData: [80, 100, 121, 104, 105, 90, 100],
23 | actualData: [120, 90, 100, 138, 142, 130, 130],
24 | },
25 | Shoppings: {
26 | expectedData: [130, 140, 141, 142, 145, 150, 160],
27 | actualData: [120, 82, 91, 154, 162, 140, 130],
28 | },
29 | };
30 |
31 | const Dashboard = () => {
32 | const [lineChartData, setLineChartData] = useState(
33 | lineChartDefaultData["New Visits"]
34 | );
35 |
36 | const handleSetLineChartData = (type) => setLineChartData(lineChartDefaultData[type]);
37 |
38 | return (
39 |
40 |
46 |
47 |
48 |
49 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
85 |
86 |
87 |
95 |
96 |
97 |
98 |
99 | );
100 | };
101 |
102 | export default Dashboard;
103 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.less:
--------------------------------------------------------------------------------
1 | .app-container {
2 | background-color: rgb(240, 242, 245);
3 | position: relative;
4 | .github-corner {
5 | position: absolute;
6 | top: 0px;
7 | right: 0px;
8 | background-image: url('~@/assets/images/githubCorner.png');
9 | background-size: 100% 100%;
10 | width: 120px;
11 | height: 120px;
12 | z-index: 9;
13 | cursor: pointer;
14 | }
15 | .chart-wrapper {
16 | background: #fff;
17 | padding: 16px 16px 0;
18 | margin-bottom: 32px;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/views/doc/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TypingCard from '@/components/TypingCard'
3 | const Doc = () => {
4 | const cardContent = `
5 | 作者博客请戳这里
难凉热血的博客。
6 | 欢迎大家与我交流,如果觉得博客不错,也麻烦给博客赏个 star 哈。
7 | `
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default Doc;
--------------------------------------------------------------------------------
/src/views/error/404/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, Row, Col } from "antd";
3 | import errImg from "@/assets/images/404.png";
4 | import "./index.less";
5 |
6 | const NotFound = (props) => {
7 | const { history } = props;
8 | const goHome = () => history.replace("/");
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | 404
16 | 抱歉,你访问的页面不存在
17 |
18 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default NotFound;
28 |
--------------------------------------------------------------------------------
/src/views/error/404/index.less:
--------------------------------------------------------------------------------
1 | .not-found{
2 | background-color: #f0f2f5;
3 | height: 100%;
4 | .right {
5 | padding-left: 50px;
6 | margin-top: 150px;
7 | h1 {
8 | font-size: 35px;
9 | }
10 | h2 {
11 | margin-bottom: 20px;
12 | font-size: 20px;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/views/excel/exportExcel/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Table,
4 | Tag,
5 | Form,
6 | Icon,
7 | Button,
8 | Input,
9 | Radio,
10 | Select,
11 | message,
12 | Collapse,
13 | } from "antd";
14 |
15 | import { excelList } from "@/api/excel";
16 | const { Panel } = Collapse;
17 | const columns = [
18 | {
19 | title: "Id",
20 | dataIndex: "id",
21 | key: "id",
22 | width: 200,
23 | align: "center",
24 | },
25 | {
26 | title: "Title",
27 | dataIndex: "title",
28 | key: "title",
29 | width: 200,
30 | align: "center",
31 | },
32 | {
33 | title: "Author",
34 | key: "author",
35 | dataIndex: "author",
36 | width: 100,
37 | align: "center",
38 | render: (author) =>
{author},
39 | },
40 | {
41 | title: "Readings",
42 | dataIndex: "readings",
43 | key: "readings",
44 | width: 195,
45 | align: "center",
46 | },
47 | {
48 | title: "Date",
49 | dataIndex: "date",
50 | key: "date",
51 | width: 195,
52 | align: "center",
53 | },
54 | ];
55 | class Excel extends Component {
56 | _isMounted = false; // 这个变量是用来标志当前组件是否挂载
57 | state = {
58 | list: [],
59 | filename: "excel-file",
60 | autoWidth: true,
61 | bookType: "xlsx",
62 | downloadLoading: false,
63 | selectedRows: [],
64 | selectedRowKeys: [],
65 | };
66 | fetchData = () => {
67 | excelList().then((response) => {
68 | const list = response.data.data.items;
69 | if (this._isMounted) {
70 | this.setState({ list });
71 | }
72 | });
73 | };
74 | componentDidMount() {
75 | this._isMounted = true;
76 | this.fetchData();
77 | }
78 | componentWillUnmount() {
79 | this._isMounted = false;
80 | }
81 | onSelectChange = (selectedRowKeys, selectedRows) => {
82 | this.setState({ selectedRows, selectedRowKeys });
83 | };
84 | handleDownload = (type) => {
85 | if (type === "selected" && this.state.selectedRowKeys.length === 0) {
86 | message.error("至少选择一项进行导出");
87 | return;
88 | }
89 | this.setState({
90 | downloadLoading: true,
91 | });
92 | import("@/lib/Export2Excel").then((excel) => {
93 | const tHeader = ["Id", "Title", "Author", "Readings", "Date"];
94 | const filterVal = ["id", "title", "author", "readings", "date"];
95 | const list = type === "all" ? this.state.list : this.state.selectedRows;
96 | const data = this.formatJson(filterVal, list);
97 | excel.export_json_to_excel({
98 | header: tHeader,
99 | data,
100 | filename: this.state.filename,
101 | autoWidth: this.state.autoWidth,
102 | bookType: this.state.bookType,
103 | });
104 | this.setState({
105 | selectedRowKeys: [], // 导出完成后将多选框清空
106 | downloadLoading: false,
107 | });
108 | });
109 | };
110 | formatJson(filterVal, jsonData) {
111 | return jsonData.map(v => filterVal.map(j => v[j]))
112 | }
113 | filenameChange = (e) => {
114 | this.setState({
115 | filename: e.target.value,
116 | });
117 | };
118 | autoWidthChange = (e) => {
119 | this.setState({
120 | autoWidth: e.target.value,
121 | });
122 | };
123 | bookTypeChange = (value) => {
124 | this.setState({
125 | bookType: value,
126 | });
127 | };
128 | render() {
129 | const { selectedRowKeys } = this.state;
130 | const rowSelection = {
131 | selectedRowKeys,
132 | onChange: this.onSelectChange,
133 | };
134 | return (
135 |
136 |
137 |
138 |
140 |
144 | }
145 | placeholder="请输入文件名(默认excel-file)"
146 | onChange={this.filenameChange}
147 | />
148 |
149 |
150 |
154 | 是
155 | 否
156 |
157 |
158 |
159 |
168 |
169 |
170 |
177 |
178 |
179 |
186 |
187 |
188 |
189 |
190 |
191 |
record.id}
195 | dataSource={this.state.list}
196 | pagination={false}
197 | rowSelection={rowSelection}
198 | loading={this.state.downloadLoading}
199 | />
200 |
201 | );
202 | }
203 | }
204 |
205 | export default Excel;
206 |
--------------------------------------------------------------------------------
/src/views/excel/uploadExcel/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Table } from "antd";
3 | import UploadExcelComponent from "@/components/UploadExcel";
4 | class UploadExcel extends Component {
5 | state = {
6 | tableData: [],
7 | tableHeader: [],
8 | };
9 | handleSuccess = ({ results, header }) => {
10 | this.setState({
11 | tableData: results,
12 | tableHeader: header,
13 | });
14 | };
15 | render() {
16 | return (
17 |
18 |
19 |
20 |
({
23 | title: item,
24 | dataIndex: item,
25 | key: item,
26 | width: 195,
27 | align: "center",
28 | }))}
29 | dataSource={this.state.tableData}
30 | />
31 |
32 | );
33 | }
34 | }
35 |
36 | export default UploadExcel;
37 |
--------------------------------------------------------------------------------
/src/views/guide/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Driver from "driver.js"; // import driver.js
3 | import "driver.js/dist/driver.min.css"; // import driver.js css
4 | import { Button } from "antd";
5 | import TypingCard from '@/components/TypingCard'
6 | import steps from "./steps";
7 | const driver = new Driver({
8 | animate: true, // 在更改突出显示的元素时是否设置动画,
9 | // 当header的position为fixed时,会覆盖元素,这是driver.js的bug,
10 | // 详细内容见https://github.com/kamranahmedse/driver.js/issues/97
11 | opacity: 0.75, // 背景不透明度(0表示只有弹出窗口,没有覆盖)
12 | doneBtnText: "完成", // 最后一个按钮上的文本
13 | closeBtnText: "关闭", // 此步骤的“关闭”按钮上的文本
14 | nextBtnText: "下一步", // 此步骤的下一步按钮文本
15 | prevBtnText: "上一步", // 此步骤的上一个按钮文本
16 | });
17 |
18 | const guide = function () {
19 | driver.defineSteps(steps);
20 | driver.start();
21 | };
22 | const Guide = function () {
23 | const cardContent = `引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。
24 | 本Demo是基于driver.js`
25 | return (
26 |
27 |
28 |
31 |
32 | );
33 | };
34 |
35 | export default Guide;
36 |
--------------------------------------------------------------------------------
/src/views/guide/steps.js:
--------------------------------------------------------------------------------
1 | const steps = [
2 | {
3 | element: '.ant-btn-primary',
4 | popover: {
5 | title: '打开引导',
6 | description: '打开页面引导',
7 | position: 'bottom'
8 | }
9 | },
10 | {
11 | element: '.hamburger-container',
12 | popover: {
13 | title: 'Hamburger',
14 | description: '打开/收起左侧导航栏',
15 | position: 'bottom'
16 | }
17 | },
18 | {
19 | element: '.fullScreen-container',
20 | popover: {
21 | title: 'Screenfull',
22 | description: '全屏',
23 | position: 'left'
24 | }
25 | },
26 | {
27 | element: '.settings-container',
28 | popover: {
29 | title: 'Settings',
30 | description: '系统设置',
31 | position: 'left'
32 | }
33 | },
34 |
35 | ]
36 |
37 | export default steps
38 |
--------------------------------------------------------------------------------
/src/views/layout/Content/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Redirect, withRouter, Route, Switch } from "react-router-dom";
3 | import DocumentTitle from "react-document-title";
4 | import { connect } from "react-redux";
5 | import { CSSTransition, TransitionGroup } from "react-transition-group";
6 | import { Layout } from "antd";
7 | import { getMenuItemInMenuListByProperty } from "@/utils";
8 | import routeList from "@/config/routeMap";
9 | import menuList from "@/config/menuConfig";
10 | const { Content } = Layout;
11 |
12 | const getPageTitle = (menuList, pathname) => {
13 | let title = "Ant Design Pro";
14 | let item = getMenuItemInMenuListByProperty(menuList, "path", pathname);
15 | if (item) {
16 | title = `${item.title} - Ant Design Pro`;
17 | }
18 | return title;
19 | };
20 |
21 | const LayoutContent = (props) => {
22 | const { role, location } = props;
23 | const { pathname } = location;
24 | const handleFilter = (route) => {
25 | // 过滤没有权限的页面
26 | return role === "admin" || !route.roles || route.roles.includes(role);
27 | };
28 | return (
29 |
30 |
31 |
32 |
38 |
39 |
40 | {routeList.map((route) => {
41 | return (
42 | handleFilter(route) && (
43 |
48 | )
49 | );
50 | })}
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default connect((state) => state.user)(withRouter(LayoutContent));
61 |
--------------------------------------------------------------------------------
/src/views/layout/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { Icon, Menu, Dropdown, Modal, Layout, Avatar } from "antd";
4 | import { Link } from "react-router-dom";
5 | import { logout, getUserInfo } from "@/store/actions";
6 | import FullScreen from "@/components/FullScreen";
7 | import Settings from "@/components/Settings";
8 | import Hamburger from "@/components/Hamburger";
9 | import BreadCrumb from "@/components/BreadCrumb";
10 | import "./index.less";
11 | const { Header } = Layout;
12 |
13 | const LayoutHeader = (props) => {
14 | const {
15 | token,
16 | avatar,
17 | sidebarCollapsed,
18 | logout,
19 | getUserInfo,
20 | showSettings,
21 | fixedHeader,
22 | } = props;
23 | token && getUserInfo(token);
24 | const handleLogout = (token) => {
25 | Modal.confirm({
26 | title: "注销",
27 | content: "确定要退出系统吗?",
28 | okText: "确定",
29 | cancelText: "取消",
30 | onOk: () => {
31 | logout(token);
32 | },
33 | });
34 | };
35 | const onClick = ({ key }) => {
36 | switch (key) {
37 | case "logout":
38 | handleLogout(token);
39 | break;
40 | default:
41 | break;
42 | }
43 | };
44 | const menu = (
45 |
61 | );
62 | const computedStyle = () => {
63 | let styles;
64 | if (fixedHeader) {
65 | if (sidebarCollapsed) {
66 | styles = {
67 | width: "calc(100% - 80px)",
68 | };
69 | } else {
70 | styles = {
71 | width: "calc(100% - 200px)",
72 | };
73 | }
74 | } else {
75 | styles = {
76 | width: "100%",
77 | };
78 | }
79 | return styles;
80 | };
81 | return (
82 | <>
83 | {/* 这里是仿照antd pro的做法,如果固定header,
84 | 则header的定位变为fixed,此时需要一个定位为relative的header把原来的header位置撑起来 */}
85 | {fixedHeader ? : null}
86 |
90 |
91 |
92 |
93 |
94 | {showSettings ?
: null}
95 |
103 |
104 |
105 | >
106 | );
107 | };
108 |
109 | const mapStateToProps = (state) => {
110 | return {
111 | ...state.app,
112 | ...state.user,
113 | ...state.settings,
114 | };
115 | };
116 | export default connect(mapStateToProps, { logout, getUserInfo })(LayoutHeader);
117 |
--------------------------------------------------------------------------------
/src/views/layout/Header/index.less:
--------------------------------------------------------------------------------
1 | .ant-layout-header {
2 | transition: width 0.2s;
3 | z-index: 9;
4 | position: relative;
5 | height: 64px;
6 | background: #fff !important;
7 | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
8 | .right-menu {
9 | float: right;
10 | .dropdown-wrap {
11 | cursor: pointer;
12 | display: inline-block;
13 | .ant-dropdown-menu-item {
14 | padding: 6px 20px 8px 15px;
15 | i {
16 | padding-right: 15px;
17 | }
18 | }
19 | .anticon-caret-down {
20 | vertical-align: bottom;
21 | padding: 0 0 12px 6px;
22 | }
23 | }
24 | }
25 | }
26 | .fix-header {
27 | position: fixed;
28 | top: 0;
29 | right: 0;
30 | }
31 |
--------------------------------------------------------------------------------
/src/views/layout/RightPanel/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { connect } from "react-redux";
3 | import { Drawer, Switch, Row, Col, Divider, Alert, Icon, Button } from "antd";
4 | import { toggleSettingPanel, changeSetting } from "@/store/actions";
5 | import clip from "@/utils/clipboard";
6 |
7 | const RightPanel = (props) => {
8 | const {
9 | settingPanelVisible,
10 | toggleSettingPanel,
11 | changeSetting,
12 | sidebarLogo: defaultSidebarLogo,
13 | fixedHeader: defaultFixedHeader,
14 | tagsView: defaultTagsView,
15 | } = props;
16 |
17 | const [sidebarLogo, setSidebarLogo] = useState(defaultSidebarLogo);
18 | const [fixedHeader, setFixedHeader] = useState(defaultFixedHeader);
19 | const [tagsView, setTagsView] = useState(defaultTagsView);
20 |
21 | const sidebarLogoChange = (checked) => {
22 | setSidebarLogo(checked);
23 | changeSetting({ key: "sidebarLogo", value: checked });
24 | };
25 |
26 | const fixedHeaderChange = (checked) => {
27 | setFixedHeader(checked);
28 | changeSetting({ key: "fixedHeader", value: checked });
29 | };
30 |
31 | const tagsViewChange = (checked) => {
32 | setTagsView(checked);
33 | changeSetting({ key: "tagsView", value: checked });
34 | };
35 |
36 | const handleCopy = (e) => {
37 | let config = `
38 | export default {
39 | showSettings: true,
40 | sidebarLogo: ${sidebarLogo},
41 | fixedHeader: ${fixedHeader},
42 | tagsView: ${tagsView},
43 | }
44 | `;
45 | clip(config, e);
46 | };
47 |
48 | return (
49 |
50 |
57 |
58 |
59 | 侧边栏 Logo
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 | 固定 Header
74 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 | 开启 Tags-View
88 |
89 |
90 |
96 |
97 |
98 |
99 |
100 |
101 | }
107 | style={{ marginBottom: "16px" }}
108 | />
109 |
112 |
113 |
114 |
115 |
116 | );
117 | };
118 |
119 | const mapStateToProps = (state) => {
120 | return {
121 | ...state.app,
122 | ...state.settings,
123 | };
124 | };
125 |
126 | export default connect(mapStateToProps, { toggleSettingPanel, changeSetting })(
127 | RightPanel
128 | );
129 |
--------------------------------------------------------------------------------
/src/views/layout/Sider/Logo/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import logo from "@/assets/images/logo.svg";
3 | import "./index.less";
4 | const Logo = () => {
5 | return (
6 |
7 |

8 |
难凉热血
9 |
10 | );
11 | };
12 |
13 | export default Logo;
14 |
--------------------------------------------------------------------------------
/src/views/layout/Sider/Logo/index.less:
--------------------------------------------------------------------------------
1 | .sidebar-logo-container {
2 | position: relative;
3 | width: 100%;
4 | height: 64px;
5 | line-height: 64px;
6 | background: #2b2f3a;
7 | text-align: center;
8 | overflow: hidden;
9 |
10 | & .sidebar-logo {
11 | width: 60px;
12 | animation: sidebar-logo-spin infinite 10s linear;
13 | }
14 |
15 | & .sidebar-title {
16 | display: inline-block;
17 | margin: 0;
18 | color: #fff;
19 | font-weight: 600;
20 | line-height: 50px;
21 | font-size: 14px;
22 | font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
23 | vertical-align: middle;
24 | }
25 | }
26 |
27 | @keyframes sidebar-logo-spin {
28 | from {
29 | transform: rotate(0deg);
30 | }
31 | to {
32 | transform: rotate(360deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/views/layout/Sider/Menu/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Menu, Icon } from "antd";
3 | import { Link, withRouter } from "react-router-dom";
4 | import { Scrollbars } from "react-custom-scrollbars";
5 | import { connect } from "react-redux";
6 | import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
7 | import { addTag } from "@/store/actions";
8 | import { getMenuItemInMenuListByProperty } from "@/utils";
9 | import menuList from "@/config/menuConfig";
10 | import "./index.less";
11 | const SubMenu = Menu.SubMenu;
12 | // 重新记录数组顺序
13 | const reorder = (list, startIndex, endIndex) => {
14 | const result = Array.from(list);
15 | const [removed] = result.splice(startIndex, 1);
16 | result.splice(endIndex, 0, removed);
17 | return result;
18 | };
19 |
20 | class Meun extends Component {
21 | state = {
22 | menuTreeNode: null,
23 | openKey: [],
24 | };
25 |
26 | // filterMenuItem用来根据配置信息筛选可以显示的菜单项
27 | filterMenuItem = (item) => {
28 | const { roles } = item;
29 | const { role } = this.props;
30 | if (role === "admin" || !roles || roles.includes(role)) {
31 | return true;
32 | } else if (item.children) {
33 | // 如果当前用户有此item的某个子item的权限
34 | return !!item.children.find((child) => roles.includes(child.role));
35 | }
36 | return false;
37 | };
38 | // 菜单渲染
39 | getMenuNodes = (menuList) => {
40 | // 得到当前请求的路由路径
41 | const path = this.props.location.pathname;
42 | return menuList.reduce((pre, item) => {
43 | if (this.filterMenuItem(item)) {
44 | if (!item.children) {
45 | pre.push(
46 |
47 |
48 | {item.icon ? : null}
49 | {item.title}
50 |
51 |
52 | );
53 | } else {
54 | // 查找一个与当前请求路径匹配的子Item
55 | const cItem = item.children.find(
56 | (cItem) => path.indexOf(cItem.path) === 0
57 | );
58 | // 如果存在, 说明当前item的子列表需要打开
59 | if (cItem) {
60 | this.setState((state) => ({
61 | openKey: [...state.openKey, item.path],
62 | }));
63 | }
64 |
65 | // 向pre添加
66 | pre.push(
67 |
71 | {item.icon ? : null}
72 | {item.title}
73 |
74 | }
75 | >
76 | {this.getMenuNodes(item.children)}
77 |
78 | );
79 | }
80 | }
81 |
82 | return pre;
83 | }, []);
84 | };
85 |
86 | onDragEnd = (result) => {
87 | if (!result.destination) {
88 | return;
89 | }
90 | const _items = reorder(
91 | this.state.menuTreeNode,
92 | result.source.index,
93 | result.destination.index
94 | );
95 | this.setState({
96 | menuTreeNode: _items,
97 | });
98 | };
99 |
100 | handleMenuSelect = ({ key = "/dashboard" }) => {
101 | let menuItem = getMenuItemInMenuListByProperty(menuList, "path", key);
102 | this.props.addTag(menuItem);
103 | };
104 |
105 | componentWillMount() {
106 | const menuTreeNode = this.getMenuNodes(menuList);
107 | this.setState({
108 | menuTreeNode,
109 | });
110 | this.handleMenuSelect(this.state.openKey);
111 | }
112 | render() {
113 | const path = this.props.location.pathname;
114 | const openKey = this.state.openKey;
115 | return (
116 |
117 |
118 |
119 |
120 | {(provided, snapshot) => (
121 |
122 | {this.state.menuTreeNode.map((item, index) => (
123 |
128 | {(provided, snapshot) => (
129 |
134 |
143 |
144 | )}
145 |
146 | ))}
147 |
148 | )}
149 |
150 |
151 |
152 |
153 | );
154 | }
155 | }
156 |
157 | export default connect((state) => state.user, { addTag })(withRouter(Meun));
158 |
--------------------------------------------------------------------------------
/src/views/layout/Sider/Menu/index.less:
--------------------------------------------------------------------------------
1 | .sidebar-menu-container {
2 | height:calc(100% - 64px)
3 | }
--------------------------------------------------------------------------------
/src/views/layout/Sider/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { Layout } from "antd";
4 | import Logo from "./Logo";
5 | import Menu from "./Menu";
6 | const { Sider } = Layout;
7 |
8 | const LayoutSider = (props) => {
9 | const { sidebarCollapsed, sidebarLogo } = props;
10 | return (
11 |
17 | {sidebarLogo ? : null}
18 |
19 |
20 | );
21 | };
22 |
23 | const mapStateToProps = (state) => {
24 | return {
25 | ...state.app,
26 | ...state.settings,
27 | };
28 | };
29 | export default connect(mapStateToProps)(LayoutSider);
30 |
--------------------------------------------------------------------------------
/src/views/layout/TagsView/components/TagList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { withRouter } from "react-router-dom";
4 | import { Scrollbars } from "react-custom-scrollbars";
5 | import { Tag } from "antd";
6 | import { deleteTag, emptyTaglist, closeOtherTags } from "@/store/actions";
7 | class TagList extends Component {
8 | tagListContainer = React.createRef();
9 | contextMenuContainer = React.createRef();
10 | state = {
11 | left: 0,
12 | top: 0,
13 | menuVisible: false,
14 | };
15 | handleClose = (tag) => {
16 | const { history, deleteTag, taglist } = this.props;
17 | const path = tag.path;
18 | const currentPath = history.location.pathname;
19 | const length = taglist.length;
20 | // 如果关闭的是当前页,跳转到最后一个tag
21 | if (path === currentPath) {
22 | history.push(taglist[length - 1].path);
23 | }
24 | // 如果关闭的是最后的tag ,且当前显示的也是最后的tag对应的页面,才做路由跳转
25 | if (
26 | path === taglist[length - 1].path &&
27 | currentPath === taglist[length - 1].path
28 | ) {
29 | // 因为cutTaglist在最后执行,所以跳转到上一个tags的对应的路由,应该-2
30 | if (length - 2 > 0) {
31 | history.push(taglist[length - 2].path);
32 | } else if (length === 2) {
33 | history.push(taglist[0].path);
34 | }
35 | }
36 |
37 | // 先跳转路由,再修改state树的taglist
38 | deleteTag(tag);
39 | };
40 | handleClick = (path) => {
41 | this.props.history.push(path);
42 | };
43 | openContextMenu = (tag, event) => {
44 | event.preventDefault();
45 | const menuMinWidth = 105;
46 | const clickX = event.clientX;
47 | const clickY = event.clientY; //事件发生时鼠标的Y坐标
48 | const clientWidth = this.tagListContainer.current.clientWidth; // container width
49 | const maxLeft = clientWidth - menuMinWidth; // left boundary
50 |
51 | // 当鼠标点击位置大于左侧边界时,说明鼠标点击的位置偏右,将菜单放在左边
52 | if (clickX > maxLeft) {
53 | this.setState({
54 | left: clickX - menuMinWidth + 15,
55 | top: clickY,
56 | menuVisible: true,
57 | currentTag: tag,
58 | });
59 | } else {
60 | // 反之,当鼠标点击的位置偏左,将菜单放在右边
61 | this.setState({
62 | left: clickX,
63 | top: clickY,
64 | menuVisible: true,
65 | currentTag: tag,
66 | });
67 | }
68 | };
69 | handleClickOutside = (event) => {
70 | const { menuVisible } = this.state;
71 | const isOutside = !(
72 | this.contextMenuContainer.current &&
73 | this.contextMenuContainer.current.contains(event.target)
74 | );
75 | if (isOutside && menuVisible) {
76 | this.closeContextMenu();
77 | }
78 | };
79 | closeContextMenu() {
80 | this.setState({
81 | menuVisible: false,
82 | });
83 | }
84 | componentDidMount() {
85 | document.body.addEventListener("click", this.handleClickOutside);
86 | }
87 | componentWillUnmount() {
88 | document.body.removeEventListener("click", this.handleClickOutside);
89 | }
90 | handleCloseAllTags = () => {
91 | this.props.emptyTaglist();
92 | this.props.history.push("/dashboard");
93 | this.closeContextMenu();
94 | };
95 | handleCloseOtherTags = () => {
96 | const currentTag = this.state.currentTag;
97 | const { path } = currentTag;
98 | this.props.closeOtherTags(currentTag)
99 | this.props.history.push(path);
100 | this.closeContextMenu();
101 | };
102 | render() {
103 | const { left, top, menuVisible } = this.state;
104 | const { taglist, history } = this.props;
105 | const currentPath = history.location.pathname;
106 | return (
107 | <>
108 | (
114 |
115 | )}
116 | renderTrackVertical={(props) => (
117 |
118 | )}
119 | >
120 |
121 | {taglist.map((tag) => (
122 | -
123 |
130 | {tag.title}
131 |
132 |
133 | ))}
134 |
135 |
136 | {menuVisible ? (
137 |
142 | - 关闭其他
143 | - 关闭所有
144 |
145 | ) : null}
146 | >
147 | );
148 | }
149 | }
150 | export default withRouter(
151 | connect((state) => state.tagsView, {
152 | deleteTag,
153 | emptyTaglist,
154 | closeOtherTags,
155 | })(TagList)
156 | );
157 |
--------------------------------------------------------------------------------
/src/views/layout/TagsView/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TagList from "./components/TagList";
3 | import "./index.less";
4 |
5 | const TagsView = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default TagsView;
14 |
--------------------------------------------------------------------------------
/src/views/layout/TagsView/index.less:
--------------------------------------------------------------------------------
1 | .tagsView-container {
2 | white-space: nowrap;
3 | width: 100%;
4 | height: 36px;
5 | line-height: 36px;
6 | border-bottom: 1px solid #d8dce5;
7 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
8 | background: #fff;
9 | .tags-wrap {
10 | padding: 0;
11 | & > li {
12 | display: inline-block;
13 | &:first-of-type {
14 | margin-left: 15px;
15 | }
16 | &:last-of-type {
17 | margin-right: 15px;
18 | }
19 | }
20 | }
21 | }
22 | .scrollbar-container {
23 | overflow-y: hidden !important;
24 | }
25 | .scrollbar-track-vertical {
26 | visibility: hidden !important;
27 | }
28 | .contextmenu {
29 | margin: 0;
30 | background: #fff;
31 | z-index: 3000;
32 | position: absolute;
33 | list-style-type: none;
34 | padding: 5px 0;
35 | border-radius: 4px;
36 | font-size: 12px;
37 | font-weight: 400;
38 | color: #333;
39 | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
40 | li {
41 | margin: 0;
42 | padding: 0px 16px;
43 | height: 30px;
44 | cursor: pointer;
45 | &:hover {
46 | background: #eee;
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/views/layout/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import Content from "./Content";
4 | import Header from "./Header";
5 | import RightPanel from "./RightPanel";
6 | import Sider from "./Sider";
7 | import TagsView from "./TagsView";
8 | import { Layout } from "antd";
9 | const Main = (props) => {
10 | const { tagsView } = props;
11 | return (
12 |
13 |
14 |
15 |
16 | {tagsView ? : null}
17 |
18 |
19 |
20 |
21 | );
22 | };
23 | export default connect((state) => state.settings)(Main);
24 |
--------------------------------------------------------------------------------
/src/views/login/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { Form, Icon, Input, Button, message, Spin } from "antd";
4 | import { connect } from "react-redux";
5 | import DocumentTitle from "react-document-title";
6 | import "./index.less";
7 | import { login, getUserInfo } from "@/store/actions";
8 |
9 | const Login = (props) => {
10 | const { form, token, login, getUserInfo } = props;
11 | const { getFieldDecorator } = form;
12 |
13 | const [loading, setLoading] = useState(false);
14 |
15 | const handleLogin = (username, password) => {
16 | // 登录完成后 发送请求 调用接口获取用户信息
17 | setLoading(true);
18 | login(username, password)
19 | .then((data) => {
20 | message.success("登录成功");
21 | handleUserInfo(data.token);
22 | })
23 | .catch((error) => {
24 | setLoading(false);
25 | message.error(error);
26 | });
27 | };
28 |
29 | // 获取用户信息
30 | const handleUserInfo = (token) => {
31 | getUserInfo(token)
32 | .then((data) => {})
33 | .catch((error) => {
34 | message.error(error);
35 | });
36 | };
37 |
38 | const handleSubmit = (event) => {
39 | // 阻止事件的默认行为
40 | event.preventDefault();
41 |
42 | // 对所有表单字段进行检验
43 | form.validateFields((err, values) => {
44 | // 检验成功
45 | if (!err) {
46 | const { username, password } = values;
47 | handleLogin(username, password);
48 | } else {
49 | console.log("检验失败!");
50 | }
51 | });
52 | };
53 |
54 | if (token) {
55 | return ;
56 | }
57 | return (
58 |
59 |
123 |
124 | );
125 | };
126 |
127 | const WrapLogin = Form.create()(Login);
128 |
129 | export default connect((state) => state.user, { login, getUserInfo })(
130 | WrapLogin
131 | );
132 |
--------------------------------------------------------------------------------
/src/views/login/index.less:
--------------------------------------------------------------------------------
1 | .login-container {
2 | height: 100%;
3 | overflow: hidden;
4 | position: relative;
5 | // background: #2d3a4b;
6 | background-image: url('~@/assets/images/bg.jpg');
7 | background-size: 100% 100%;
8 | & > .title {
9 | color: #eee;
10 | font-size: 26px;
11 | font-weight: 400;
12 | margin: 0px auto 30px auto;
13 | text-align: center;
14 | font-weight: bold;
15 | letter-spacing: 1px;
16 | }
17 | .spin-wrap {
18 | text-align: center;
19 | background-color: rgba(255, 255, 255, 0.3);
20 | border-radius: 4px;
21 | position: absolute;
22 | left: 0;
23 | top: 0;
24 | width: 100vw;
25 | height: 100vh;
26 | line-height: 90vh;
27 | z-index: 999;
28 | }
29 | .content {
30 | position: absolute;
31 | background-color: #fff;
32 | left: 50%;
33 | top: 50%;
34 | width: 320px;
35 | padding: 30px 30px 0 30px;
36 | transform: translate(-50%, -60%);
37 | box-shadow: 0 0 10px 2px rgba(40, 138, 204, 0.16);
38 | border-radius: 3px;
39 |
40 | .title {
41 | text-align: center;
42 | margin: 0 0 30px 0;
43 | }
44 | .two-button-wrap {
45 | display: flex;
46 | justify-content: space-between;
47 | }
48 |
49 | .row-container {
50 | display: flex;
51 | justify-content: space-between;
52 | }
53 |
54 | .user-type-button {
55 | width: 45%;
56 | }
57 |
58 | .login-form-button {
59 | width: 100%;
60 | }
61 | .login-form-forgot {
62 | float: right;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-1/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | const Menu1_1 = () => {
3 | return Menu1-1
;
4 | };
5 |
6 | export default Menu1_1;
7 |
--------------------------------------------------------------------------------
/src/views/nested/menu1/menu1-2/menu1-2-1/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | const Menu1_2_1 = () => {
3 | return Menu1-2-1
;
4 | };
5 |
6 | export default Menu1_2_1;
--------------------------------------------------------------------------------
/src/views/permission/adminPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TypingCard from '@/components/TypingCard'
3 | const AdminPage = () => {
4 | const cardContent = `这个页面只有admin角色才可以访问,guest和editor角色看不到`
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default AdminPage;
--------------------------------------------------------------------------------
/src/views/permission/editorPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TypingCard from '@/components/TypingCard'
3 | const GuestPage = () => {
4 | const cardContent = `这个页面只有admin和editor角色才可以访问,guest角色看不到`
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default GuestPage;
--------------------------------------------------------------------------------
/src/views/permission/guestPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TypingCard from '@/components/TypingCard'
3 | const GuestPage = () => {
4 | const cardContent = `这个页面只有admin和guest角色才可以访问,editor角色看不到`
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default GuestPage;
--------------------------------------------------------------------------------
/src/views/permission/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TypingCard from "@/components/TypingCard";
3 | export default () => {
4 | const cardContent = `
5 | 本项目中的菜单权限和路由权限都是基于用户所属角色来分配的,本项目中内置了三种角色,分别是:
6 |
7 |
8 | - 管理员 admin:该角色拥有系统内所有菜单和路由的权限。
9 | - 编辑员 editor:该角色拥有系统内除用户管理页之外的所有菜单和路由的权限。
10 | - 游客 guest:该角色仅拥有Dashboard、作者博客、权限测试和关于作者三个页面的权限。
11 |
12 |
13 | 你可以通过用户管理页面,动态的添加或删除用户,以及编辑某个已经存在的用户,例如修改其权限等操作。
14 | `;
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/views/table/forms/editForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Form, Input, DatePicker, Select, Rate, Modal } from "antd";
3 | import moment from "moment";
4 | import "moment/locale/zh-cn";
5 | moment.locale("zh-cn");
6 | class EditForm extends Component {
7 | render() {
8 | const {
9 | visible,
10 | onCancel,
11 | onOk,
12 | form,
13 | confirmLoading,
14 | currentRowData,
15 | } = this.props;
16 | const { getFieldDecorator } = form;
17 | const { id, author, date, readings, star, status, title } = currentRowData;
18 | const formItemLayout = {
19 | labelCol: {
20 | sm: { span: 4 },
21 | },
22 | wrapperCol: {
23 | sm: { span: 16 },
24 | },
25 | };
26 | return (
27 |
34 |
36 | {getFieldDecorator("id", {
37 | initialValue: id,
38 | })()}
39 |
40 |
41 | {getFieldDecorator("title", {
42 | rules: [{ required: true, message: "请输入标题!" }],
43 | initialValue: title,
44 | })()}
45 |
46 |
47 | {getFieldDecorator("author", {
48 | initialValue: author,
49 | })()}
50 |
51 |
52 | {getFieldDecorator("readings", {
53 | initialValue: readings,
54 | })()}
55 |
56 |
57 | {getFieldDecorator("star", {
58 | initialValue: star.length,
59 | })()}
60 |
61 |
62 | {getFieldDecorator("status", {
63 | initialValue: status,
64 | })(
65 |
69 | )}
70 |
71 |
72 | {getFieldDecorator("date", {
73 | rules: [{ type: 'object', required: true, message: '请选择时间!' }],
74 | initialValue: moment(date || "YYYY-MM-DD HH:mm:ss"),
75 | })()}
76 |
77 |
78 |
79 | );
80 | }
81 | }
82 |
83 | export default Form.create({ name: "EditForm" })(EditForm);
84 |
--------------------------------------------------------------------------------
/src/views/table/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Table,
4 | Tag,
5 | Form,
6 | Button,
7 | Input,
8 | Collapse,
9 | Pagination,
10 | Divider,
11 | message,
12 | Select
13 | } from "antd";
14 | import { tableList, deleteItem,editItem } from "@/api/table";
15 | import EditForm from "./forms/editForm"
16 | const { Column } = Table;
17 | const { Panel } = Collapse;
18 | class TableComponent extends Component {
19 | _isMounted = false; // 这个变量是用来标志当前组件是否挂载
20 | state = {
21 | list: [],
22 | loading: false,
23 | total: 0,
24 | listQuery: {
25 | pageNumber: 1,
26 | pageSize: 10,
27 | title: "",
28 | star: "",
29 | status:""
30 | },
31 | editModalVisible: false,
32 | editModalLoading: false,
33 | currentRowData: {
34 | id: 0,
35 | author: "",
36 | date: "",
37 | readings: 0,
38 | star: "★",
39 | status: "published",
40 | title: ""
41 | }
42 | };
43 | fetchData = () => {
44 | this.setState({ loading: true });
45 | tableList(this.state.listQuery).then((response) => {
46 | this.setState({ loading: false });
47 | const list = response.data.data.items;
48 | const total = response.data.data.total;
49 | if (this._isMounted) {
50 | this.setState({ list, total });
51 | }
52 | });
53 | };
54 | componentDidMount() {
55 | this._isMounted = true;
56 | this.fetchData();
57 | }
58 | componentWillUnmount() {
59 | this._isMounted = false;
60 | }
61 | filterTitleChange = (e) => {
62 | let value = e.target.value
63 | this.setState((state) => ({
64 | listQuery: {
65 | ...state.listQuery,
66 | title:value,
67 | }
68 | }));
69 | };
70 | filterStatusChange = (value) => {
71 | this.setState((state) => ({
72 | listQuery: {
73 | ...state.listQuery,
74 | status:value,
75 | }
76 | }));
77 | };
78 | filterStarChange = (value) => {
79 | this.setState((state) => ({
80 | listQuery: {
81 | ...state.listQuery,
82 | star:value,
83 | }
84 | }));
85 | };
86 | changePage = (pageNumber, pageSize) => {
87 | this.setState(
88 | (state) => ({
89 | listQuery: {
90 | ...state.listQuery,
91 | pageNumber,
92 | },
93 | }),
94 | () => {
95 | this.fetchData();
96 | }
97 | );
98 | };
99 | changePageSize = (current, pageSize) => {
100 | this.setState(
101 | (state) => ({
102 | listQuery: {
103 | ...state.listQuery,
104 | pageNumber: 1,
105 | pageSize,
106 | },
107 | }),
108 | () => {
109 | this.fetchData();
110 | }
111 | );
112 | };
113 | handleDelete = (row) => {
114 | deleteItem({id:row.id}).then(res => {
115 | message.success("删除成功")
116 | this.fetchData();
117 | })
118 | }
119 | handleEdit = (row) => {
120 | this.setState({
121 | currentRowData:Object.assign({}, row),
122 | editModalVisible: true,
123 | });
124 | };
125 |
126 | handleOk = _ => {
127 | const { form } = this.formRef.props;
128 | form.validateFields((err, fieldsValue) => {
129 | if (err) {
130 | return;
131 | }
132 | const values = {
133 | ...fieldsValue,
134 | 'star': "".padStart(fieldsValue['star'], '★'),
135 | 'date': fieldsValue['date'].format('YYYY-MM-DD HH:mm:ss'),
136 | };
137 | this.setState({ editModalLoading: true, });
138 | editItem(values).then((response) => {
139 | form.resetFields();
140 | this.setState({ editModalVisible: false, editModalLoading: false });
141 | message.success("编辑成功!")
142 | this.fetchData()
143 | }).catch(e => {
144 | message.success("编辑失败,请重试!")
145 | })
146 |
147 | });
148 | };
149 |
150 | handleCancel = _ => {
151 | this.setState({
152 | editModalVisible: false,
153 | });
154 | };
155 | render() {
156 | return (
157 |
158 |
159 |
160 |
162 |
163 |
164 |
165 |
171 |
172 |
173 |
180 |
181 |
182 |
185 |
186 |
187 |
188 |
189 |
190 |
record.id}
193 | dataSource={this.state.list}
194 | loading={this.state.loading}
195 | pagination={false}
196 | >
197 | a.id - b.id}/>
198 |
199 |
200 |
201 |
202 | {
203 | let color =
204 | status === "published" ? "green" : status === "deleted" ? "red" : "";
205 | return (
206 |
207 | {status}
208 |
209 | );
210 | }}/>
211 |
212 | (
213 |
214 |
218 | )}/>
219 |
220 |
221 |
`共${total}条数据`}
225 | onChange={this.changePage}
226 | current={this.state.listQuery.pageNumber}
227 | onShowSizeChange={this.changePageSize}
228 | showSizeChanger
229 | showQuickJumper
230 | hideOnSinglePage={true}
231 | />
232 | this.formRef = formRef}
235 | visible={this.state.editModalVisible}
236 | confirmLoading={this.state.editModalLoading}
237 | onCancel={this.handleCancel}
238 | onOk={this.handleOk}
239 | />
240 |
241 | );
242 | }
243 | }
244 |
245 | export default TableComponent;
246 |
--------------------------------------------------------------------------------
/src/views/user/forms/add-user-form.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Form, Input, Select, Modal } from "antd";
3 | import { reqValidatUserID } from "@/api/user";
4 | const { TextArea } = Input;
5 | class AddUserForm extends Component {
6 | validatUserID = async (rule, value, callback) => {
7 | if (value) {
8 | if (!/^[a-zA-Z0-9]{1,6}$/.test(value)) {
9 | callback("用户ID必须为1-6位数字或字母组合");
10 | }
11 | let res = await reqValidatUserID(value);
12 | const { status } = res.data;
13 | if (status) {
14 | callback("该用户ID已存在");
15 | }
16 | } else {
17 | callback("请输入用户ID");
18 | }
19 | callback();
20 | };
21 | render() {
22 | const { visible, onCancel, onOk, form, confirmLoading } = this.props;
23 | const { getFieldDecorator } = form;
24 | const formItemLayout = {
25 | labelCol: {
26 | sm: { span: 4 },
27 | },
28 | wrapperCol: {
29 | sm: { span: 16 },
30 | },
31 | };
32 | return (
33 |
40 |
42 | {getFieldDecorator("id", {
43 | rules: [{ required: true, validator: this.validatUserID }],
44 | })()}
45 |
46 |
47 | {getFieldDecorator("name", {
48 | rules: [{ required: true, message: "请输入用户名称!" }],
49 | })()}
50 |
51 |
52 | {getFieldDecorator("role", {
53 | initialValue: "admin",
54 | })(
55 |
59 | )}
60 |
61 |
62 | {getFieldDecorator("description", {
63 | })()}
64 |
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | export default Form.create({ name: "AddUserForm" })(AddUserForm);
72 |
--------------------------------------------------------------------------------
/src/views/user/forms/edit-user-form.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Form, Input, Select, Modal } from "antd";
3 | const { TextArea } = Input;
4 | class EditUserForm extends Component {
5 | render() {
6 | const {
7 | visible,
8 | onCancel,
9 | onOk,
10 | form,
11 | confirmLoading,
12 | currentRowData,
13 | } = this.props;
14 | const { getFieldDecorator } = form;
15 | const { id, name, role, description } = currentRowData;
16 | const formItemLayout = {
17 | labelCol: {
18 | sm: { span: 4 },
19 | },
20 | wrapperCol: {
21 | sm: { span: 16 },
22 | },
23 | };
24 | return (
25 |
32 |
34 | {getFieldDecorator("id", {
35 | initialValue: id,
36 | })()}
37 |
38 |
39 | {getFieldDecorator("name", {
40 | rules: [{ required: true, message: "请输入用户名称!" }],
41 | initialValue: name,
42 | })()}
43 |
44 |
45 | {getFieldDecorator("role", {
46 | initialValue: role,
47 | })(
48 |
53 | )}
54 |
55 |
56 | {getFieldDecorator("description", {
57 | initialValue: description,
58 | })()}
59 |
60 |
61 |
62 | );
63 | }
64 | }
65 |
66 | export default Form.create({ name: "EditUserForm" })(EditUserForm);
67 |
--------------------------------------------------------------------------------
/src/views/user/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Card, Button, Table, message, Divider } from "antd";
3 | import { getUsers, deleteUser, editUser, addUser } from "@/api/user";
4 | import TypingCard from '@/components/TypingCard'
5 | import EditUserForm from "./forms/edit-user-form"
6 | import AddUserForm from "./forms/add-user-form"
7 | const { Column } = Table;
8 | class User extends Component {
9 | state = {
10 | users: [],
11 | editUserModalVisible: false,
12 | editUserModalLoading: false,
13 | currentRowData: {},
14 | addUserModalVisible: false,
15 | addUserModalLoading: false,
16 | };
17 | getUsers = async () => {
18 | const result = await getUsers()
19 | const { users, status } = result.data
20 | if (status === 0) {
21 | this.setState({
22 | users
23 | })
24 | }
25 | }
26 | handleEditUser = (row) => {
27 | this.setState({
28 | currentRowData:Object.assign({}, row),
29 | editUserModalVisible: true,
30 | });
31 | };
32 |
33 | handleDeleteUser = (row) => {
34 | const { id } = row
35 | if (id === "admin") {
36 | message.error("不能删除管理员用户!")
37 | return
38 | }
39 | deleteUser({id}).then(res => {
40 | message.success("删除成功")
41 | this.getUsers();
42 | })
43 | }
44 |
45 | handleEditUserOk = _ => {
46 | const { form } = this.editUserFormRef.props;
47 | form.validateFields((err, values) => {
48 | if (err) {
49 | return;
50 | }
51 | this.setState({ editModalLoading: true, });
52 | editUser(values).then((response) => {
53 | form.resetFields();
54 | this.setState({ editUserModalVisible: false, editUserModalLoading: false });
55 | message.success("编辑成功!")
56 | this.getUsers()
57 | }).catch(e => {
58 | message.success("编辑失败,请重试!")
59 | })
60 |
61 | });
62 | };
63 |
64 | handleCancel = _ => {
65 | this.setState({
66 | editUserModalVisible: false,
67 | addUserModalVisible: false,
68 | });
69 | };
70 |
71 | handleAddUser = (row) => {
72 | this.setState({
73 | addUserModalVisible: true,
74 | });
75 | };
76 |
77 | handleAddUserOk = _ => {
78 | const { form } = this.addUserFormRef.props;
79 | form.validateFields((err, values) => {
80 | if (err) {
81 | return;
82 | }
83 | this.setState({ addUserModalLoading: true, });
84 | addUser(values).then((response) => {
85 | form.resetFields();
86 | this.setState({ addUserModalVisible: false, addUserModalLoading: false });
87 | message.success("添加成功!")
88 | this.getUsers()
89 | }).catch(e => {
90 | message.success("添加失败,请重试!")
91 | })
92 | });
93 | };
94 | componentDidMount() {
95 | this.getUsers()
96 | }
97 | render() {
98 | const { users } = this.state
99 | const title = (
100 |
101 | 添加用户
102 |
103 | )
104 | const cardContent = `在这里,你可以对系统中的用户进行管理,例如添加一个新用户,或者修改系统中已经存在的用户。`
105 | return (
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | (
116 |
117 |
118 |
119 |
120 |
121 | )}/>
122 |
123 |
124 |
this.editUserFormRef = formRef}
127 | visible={this.state.editUserModalVisible}
128 | confirmLoading={this.state.editUserModalLoading}
129 | onCancel={this.handleCancel}
130 | onOk={this.handleEditUserOk}
131 | />
132 | this.addUserFormRef = formRef}
134 | visible={this.state.addUserModalVisible}
135 | confirmLoading={this.state.addUserModalLoading}
136 | onCancel={this.handleCancel}
137 | onOk={this.handleAddUserOk}
138 | />
139 |
140 | );
141 | }
142 | }
143 |
144 | export default User;
145 |
--------------------------------------------------------------------------------
/src/views/zip/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Table, Tag, Form, Icon, Button, Input, message, Collapse } from "antd";
3 | import { excelList } from "@/api/excel";
4 | const { Panel } = Collapse;
5 | const columns = [
6 | {
7 | title: "Id",
8 | dataIndex: "id",
9 | key: "id",
10 | width: 200,
11 | align: "center",
12 | },
13 | {
14 | title: "Title",
15 | dataIndex: "title",
16 | key: "title",
17 | width: 200,
18 | align: "center",
19 | },
20 | {
21 | title: "Author",
22 | key: "author",
23 | dataIndex: "author",
24 | width: 100,
25 | align: "center",
26 | render: (author) => {author},
27 | },
28 | {
29 | title: "Readings",
30 | dataIndex: "readings",
31 | key: "readings",
32 | width: 195,
33 | align: "center",
34 | },
35 | {
36 | title: "Date",
37 | dataIndex: "date",
38 | key: "date",
39 | width: 195,
40 | align: "center",
41 | },
42 | ];
43 | class Zip extends Component {
44 | _isMounted = false; // 这个变量是用来标志当前组件是否挂载
45 | state = {
46 | list: [],
47 | filename: "file",
48 | downloadLoading: false,
49 | selectedRows: [],
50 | selectedRowKeys: [],
51 | };
52 | fetchData = () => {
53 | excelList().then((response) => {
54 | const list = response.data.data.items;
55 | if (this._isMounted) {
56 | this.setState({ list });
57 | }
58 | });
59 | };
60 | componentDidMount() {
61 | this._isMounted = true;
62 | this.fetchData();
63 | }
64 | componentWillUnmount() {
65 | this._isMounted = false;
66 | }
67 | onSelectChange = (selectedRowKeys, selectedRows) => {
68 | this.setState({ selectedRows, selectedRowKeys });
69 | };
70 | handleDownload = (type) => {
71 | if (type === "selected" && this.state.selectedRowKeys.length === 0) {
72 | message.error("至少选择一项进行导出");
73 | return;
74 | }
75 | this.setState({
76 | downloadLoading: true,
77 | });
78 | import("@/lib/Export2Zip").then((zip) => {
79 | const tHeader = ["Id", "Title", "Author", "Readings", "Date"];
80 | const filterVal = ["id", "title", "author", "readings", "date"];
81 | const list = type === "all" ? this.state.list : this.state.selectedRows;
82 | const data = this.formatJson(filterVal, list);
83 |
84 | zip.export_txt_to_zip(
85 | tHeader,
86 | data,
87 | this.state.filename,
88 | this.state.filename
89 | );
90 | this.setState({
91 | selectedRowKeys: [], // 导出完成后将多选框清空
92 | downloadLoading: false,
93 | });
94 | });
95 | };
96 | formatJson(filterVal, jsonData) {
97 | return jsonData.map((v) => filterVal.map((j) => v[j]));
98 | }
99 | filenameChange = (e) => {
100 | this.setState({
101 | filename: e.target.value,
102 | });
103 | };
104 | render() {
105 | const { selectedRowKeys } = this.state;
106 | const rowSelection = {
107 | selectedRowKeys,
108 | onChange: this.onSelectChange,
109 | };
110 | return (
111 |
112 |
113 |
114 |
116 |
120 | }
121 | placeholder="请输入文件名(默认file)"
122 | onChange={this.filenameChange}
123 | />
124 |
125 |
126 |
131 | 全部导出
132 |
133 |
134 |
135 |
140 | 导出已选择项
141 |
142 |
143 |
144 |
145 |
146 |
147 |
record.id}
151 | dataSource={this.state.list}
152 | pagination={false}
153 | rowSelection={rowSelection}
154 | loading={this.state.downloadLoading}
155 | />
156 |
157 | );
158 | }
159 | }
160 |
161 | export default Zip;
162 |
--------------------------------------------------------------------------------
/wechat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NLRX-WJC/react-antd-admin-template/2bf6517630daa7d68aeb7a71b4c86f99f48160ac/wechat.jpg
--------------------------------------------------------------------------------