mirror of
https://github.com/eyebluecn/tank-front
synced 2024-04-21 12:31:55 +00:00
Finish the first version of tank from bamboo.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
/dist
|
||||
/www
|
||||
/_dist
|
||||
/_package
|
||||
/mock
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
const proxy = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function (app) {
|
||||
|
||||
//为了解决前端开发时跨域问题,配置后台接口代理。
|
||||
app.use(proxy(
|
||||
'/api',
|
||||
{
|
||||
target: 'http://localhost:6020',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/api'
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
};
|
||||
@@ -8,11 +8,13 @@
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/qs": "^6.9.3",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"antd": "^4.3.1",
|
||||
"axios": "^0.19.2",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-plugin-named-asset-import": "^0.3.6",
|
||||
@@ -44,10 +46,13 @@
|
||||
"postcss-normalize": "8.0.1",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react-app-polyfill": "^1.0.6",
|
||||
"react-dev-utils": "^10.2.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"resolve": "1.15.0",
|
||||
"resolve-url-loader": "3.1.1",
|
||||
"sass-loader": "8.0.2",
|
||||
@@ -86,5 +91,8 @@
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-router-dom": "^5.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -1,6 +1,4 @@
|
||||
//import antd style.
|
||||
@import '~antd/dist/antd.css';
|
||||
|
||||
|
||||
.good {
|
||||
color: red;
|
||||
}
|
||||
//import global style.
|
||||
@import './assets/css/index';
|
||||
|
||||
+10
-6
@@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
import './App.less';
|
||||
import {Button} from "antd";
|
||||
import {BrowserRouter as Router} from "react-router-dom";
|
||||
import {ConfigProvider} from 'antd';
|
||||
import Frame from "./pages/Frame";
|
||||
import zhCN from 'antd/lib/locale-provider/zh_CN';
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Router>
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<Frame/>
|
||||
</ConfigProvider>
|
||||
|
||||
<Button>你好</Button>
|
||||
|
||||
<div className="good">good</div>
|
||||
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,868 @@
|
||||
//
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
//== Colors
|
||||
//
|
||||
//## Gray and brand colors for use across Bootstrap.
|
||||
|
||||
@gray-base: #000;
|
||||
@gray-darker: lighten(@gray-base, 13.5%); // #222
|
||||
@gray-dark: lighten(@gray-base, 20%); // #333
|
||||
@gray: lighten(@gray-base, 33.5%); // #555
|
||||
@gray-light: lighten(@gray-base, 46.7%); // #777
|
||||
@gray-lighter: lighten(@gray-base, 93.5%); // #eee
|
||||
|
||||
@brand-primary: darken(#428bca, 6.5%); // #337ab7
|
||||
@brand-success: #5cb85c;
|
||||
@brand-info: #5bc0de;
|
||||
@brand-warning: #f0ad4e;
|
||||
@brand-danger: #d9534f;
|
||||
|
||||
|
||||
//== Scaffolding
|
||||
//
|
||||
//## Settings for some of the most global styles.
|
||||
|
||||
//** Background color for `<body>`.
|
||||
@body-bg: #fff;
|
||||
//** Global text color on `<body>`.
|
||||
@text-color: @gray-dark;
|
||||
|
||||
//** Global textual link color.
|
||||
@link-color: @brand-primary;
|
||||
//** Link hover color set via `darken()` function.
|
||||
@link-hover-color: darken(@link-color, 15%);
|
||||
//** Link hover decoration.
|
||||
@link-hover-decoration: underline;
|
||||
|
||||
|
||||
//== Typography
|
||||
//
|
||||
//## Font, line-height, and color for body text, headings, and more.
|
||||
|
||||
@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
|
||||
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
@font-family-base: @font-family-sans-serif;
|
||||
|
||||
@font-size-base: 14px;
|
||||
@font-size-large: ceil((@font-size-base * 1.25)); // ~18px
|
||||
@font-size-small: ceil((@font-size-base * 0.85)); // ~12px
|
||||
|
||||
@font-size-h1: floor((@font-size-base * 2.6)); // ~36px
|
||||
@font-size-h2: floor((@font-size-base * 2.15)); // ~30px
|
||||
@font-size-h3: ceil((@font-size-base * 1.7)); // ~24px
|
||||
@font-size-h4: ceil((@font-size-base * 1.25)); // ~18px
|
||||
@font-size-h5: @font-size-base;
|
||||
@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px
|
||||
|
||||
//** Unit-less `line-height` for use in components like buttons.
|
||||
@line-height-base: 1.428571429; // 20/14
|
||||
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
|
||||
@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
|
||||
|
||||
//** By default, this inherits from the `<body>`.
|
||||
@headings-font-family: inherit;
|
||||
@headings-font-weight: 500;
|
||||
@headings-line-height: 1.1;
|
||||
@headings-color: inherit;
|
||||
|
||||
|
||||
//== Iconography
|
||||
//
|
||||
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
|
||||
|
||||
//** Load fonts from this directory.
|
||||
@icon-font-path: "../fonts/";
|
||||
//** File name for all font files.
|
||||
@icon-font-name: "glyphicons-halflings-regular";
|
||||
//** Element ID within SVG icon file.
|
||||
@icon-font-svg-id: "glyphicons_halflingsregular";
|
||||
|
||||
|
||||
//== Components
|
||||
//
|
||||
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
|
||||
|
||||
@padding-base-vertical: 6px;
|
||||
@padding-base-horizontal: 12px;
|
||||
|
||||
@padding-large-vertical: 10px;
|
||||
@padding-large-horizontal: 16px;
|
||||
|
||||
@padding-small-vertical: 5px;
|
||||
@padding-small-horizontal: 10px;
|
||||
|
||||
@padding-xs-vertical: 1px;
|
||||
@padding-xs-horizontal: 5px;
|
||||
|
||||
@line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome
|
||||
@line-height-small: 1.5;
|
||||
|
||||
@border-radius-base: 4px;
|
||||
@border-radius-large: 6px;
|
||||
@border-radius-small: 3px;
|
||||
|
||||
//** Global color for active pendants (e.g., navs or dropdowns).
|
||||
@component-active-color: #fff;
|
||||
//** Global background color for active pendants (e.g., navs or dropdowns).
|
||||
@component-active-bg: @brand-primary;
|
||||
|
||||
//** Width of the `border` for generating carets that indicate dropdowns.
|
||||
@caret-width-base: 4px;
|
||||
//** Carets increase slightly in size for larger components.
|
||||
@caret-width-large: 5px;
|
||||
|
||||
|
||||
//== Tables
|
||||
//
|
||||
//## Customizes the `.table` component with basic values, each used across all table variations.
|
||||
|
||||
//** Padding for `<th>`s and `<td>`s.
|
||||
@table-cell-padding: 8px;
|
||||
//** Padding for cells in `.table-condensed`.
|
||||
@table-condensed-cell-padding: 5px;
|
||||
|
||||
//** Default background color used for all tables.
|
||||
@table-bg: transparent;
|
||||
//** Background color used for `.table-striped`.
|
||||
@table-bg-accent: #f9f9f9;
|
||||
//** Background color used for `.table-hover`.
|
||||
@table-bg-hover: #f5f5f5;
|
||||
@table-bg-active: @table-bg-hover;
|
||||
|
||||
//** Border color for table and cell borders.
|
||||
@table-border-color: #ddd;
|
||||
|
||||
|
||||
//== Buttons
|
||||
//
|
||||
//## For each of Bootstrap's buttons, define text, background and border color.
|
||||
|
||||
@btn-font-weight: normal;
|
||||
|
||||
@btn-default-color: #333;
|
||||
@btn-default-bg: #fff;
|
||||
@btn-default-border: #ccc;
|
||||
|
||||
@btn-primary-color: #fff;
|
||||
@btn-primary-bg: @brand-primary;
|
||||
@btn-primary-border: darken(@btn-primary-bg, 5%);
|
||||
|
||||
@btn-success-color: #fff;
|
||||
@btn-success-bg: @brand-success;
|
||||
@btn-success-border: darken(@btn-success-bg, 5%);
|
||||
|
||||
@btn-info-color: #fff;
|
||||
@btn-info-bg: @brand-info;
|
||||
@btn-info-border: darken(@btn-info-bg, 5%);
|
||||
|
||||
@btn-warning-color: #fff;
|
||||
@btn-warning-bg: @brand-warning;
|
||||
@btn-warning-border: darken(@btn-warning-bg, 5%);
|
||||
|
||||
@btn-danger-color: #fff;
|
||||
@btn-danger-bg: @brand-danger;
|
||||
@btn-danger-border: darken(@btn-danger-bg, 5%);
|
||||
|
||||
@btn-link-disabled-color: @gray-light;
|
||||
|
||||
// Allows for customizing button radius independently from global border radius
|
||||
@btn-border-radius-base: @border-radius-base;
|
||||
@btn-border-radius-large: @border-radius-large;
|
||||
@btn-border-radius-small: @border-radius-small;
|
||||
|
||||
|
||||
//== Forms
|
||||
//
|
||||
//##
|
||||
|
||||
//** `<input>` background color
|
||||
@input-bg: #fff;
|
||||
//** `<input disabled>` background color
|
||||
@input-bg-disabled: @gray-lighter;
|
||||
|
||||
//** Text color for `<input>`s
|
||||
@input-color: @gray;
|
||||
//** `<input>` border color
|
||||
@input-border: #ccc;
|
||||
|
||||
//** Default `.form-control` border radius
|
||||
// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
|
||||
@input-border-radius: @border-radius-base;
|
||||
//** Large `.form-control` border radius
|
||||
@input-border-radius-large: @border-radius-large;
|
||||
//** Small `.form-control` border radius
|
||||
@input-border-radius-small: @border-radius-small;
|
||||
|
||||
//** Border color for inputs on focus
|
||||
@input-border-focus: #66afe9;
|
||||
|
||||
//** Placeholder text color
|
||||
@input-color-placeholder: #999;
|
||||
|
||||
//** Default `.form-control` height
|
||||
@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
|
||||
//** Large `.form-control` height
|
||||
@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
|
||||
//** Small `.form-control` height
|
||||
@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
|
||||
|
||||
//** `.form-group` margin
|
||||
@form-group-margin-bottom: 15px;
|
||||
|
||||
@legend-color: @gray-dark;
|
||||
@legend-border-color: #e5e5e5;
|
||||
|
||||
//** Background color for textual input addons
|
||||
@input-group-addon-bg: @gray-lighter;
|
||||
//** Border color for textual input addons
|
||||
@input-group-addon-border-color: @input-border;
|
||||
|
||||
//** Disabled cursor for form controls and buttons.
|
||||
@cursor-disabled: not-allowed;
|
||||
|
||||
|
||||
//== Dropdowns
|
||||
//
|
||||
//## Dropdown menu container and contents.
|
||||
|
||||
//** Background for the dropdown menu.
|
||||
@dropdown-bg: #fff;
|
||||
//** Dropdown menu `border-color`.
|
||||
@dropdown-border: rgba(0,0,0,.15);
|
||||
//** Dropdown menu `border-color` **for IE8**.
|
||||
@dropdown-fallback-border: #ccc;
|
||||
//** Divider color for between dropdown pendants.
|
||||
@dropdown-divider-bg: #e5e5e5;
|
||||
|
||||
//** Dropdown link text color.
|
||||
@dropdown-link-color: @gray-dark;
|
||||
//** Hover color for dropdown links.
|
||||
@dropdown-link-hover-color: darken(@gray-dark, 5%);
|
||||
//** Hover background for dropdown links.
|
||||
@dropdown-link-hover-bg: #f5f5f5;
|
||||
|
||||
//** Active dropdown menu item text color.
|
||||
@dropdown-link-active-color: @component-active-color;
|
||||
//** Active dropdown menu item background color.
|
||||
@dropdown-link-active-bg: @component-active-bg;
|
||||
|
||||
//** Disabled dropdown menu item background color.
|
||||
@dropdown-link-disabled-color: @gray-light;
|
||||
|
||||
//** Text color for headers within dropdown menus.
|
||||
@dropdown-header-color: @gray-light;
|
||||
|
||||
//** Deprecated `@dropdown-caret-color` as of v3.1.0
|
||||
@dropdown-caret-color: #000;
|
||||
|
||||
|
||||
//-- Z-index master list
|
||||
//
|
||||
// Warning: Avoid customizing these values. They're used for a bird's eye view
|
||||
// of components dependent on the z-axis and are designed to all work together.
|
||||
//
|
||||
// Note: These variables are not generated into the Customizer.
|
||||
|
||||
@zindex-navbar: 1000;
|
||||
@zindex-dropdown: 1000;
|
||||
@zindex-popover: 1060;
|
||||
@zindex-tooltip: 1070;
|
||||
@zindex-navbar-fixed: 1030;
|
||||
@zindex-modal-background: 1040;
|
||||
@zindex-modal: 1050;
|
||||
|
||||
|
||||
//== Media queries breakpoints
|
||||
//
|
||||
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
|
||||
|
||||
// Extra small screen / phone
|
||||
//** Deprecated `@screen-xs` as of v3.0.1
|
||||
@screen-xs: 480px;
|
||||
//** Deprecated `@screen-xs-min` as of v3.2.0
|
||||
@screen-xs-min: @screen-xs;
|
||||
//** Deprecated `@screen-phone` as of v3.0.1
|
||||
@screen-phone: @screen-xs-min;
|
||||
|
||||
// Small screen / tablet
|
||||
//** Deprecated `@screen-sm` as of v3.0.1
|
||||
@screen-sm: 768px;
|
||||
@screen-sm-min: @screen-sm;
|
||||
//** Deprecated `@screen-tablet` as of v3.0.1
|
||||
@screen-tablet: @screen-sm-min;
|
||||
|
||||
// Medium screen / desktop
|
||||
//** Deprecated `@screen-md` as of v3.0.1
|
||||
@screen-md: 992px;
|
||||
@screen-md-min: @screen-md;
|
||||
//** Deprecated `@screen-desktop` as of v3.0.1
|
||||
@screen-desktop: @screen-md-min;
|
||||
|
||||
// Large screen / wide desktop
|
||||
//** Deprecated `@screen-lg` as of v3.0.1
|
||||
@screen-lg: 1200px;
|
||||
@screen-lg-min: @screen-lg;
|
||||
//** Deprecated `@screen-lg-desktop` as of v3.0.1
|
||||
@screen-lg-desktop: @screen-lg-min;
|
||||
|
||||
// So media queries don't overlap when required, provide a maximum
|
||||
@screen-xs-max: (@screen-sm-min - 1);
|
||||
@screen-sm-max: (@screen-md-min - 1);
|
||||
@screen-md-max: (@screen-lg-min - 1);
|
||||
|
||||
|
||||
//== Grid system
|
||||
//
|
||||
//## Define your custom responsive grid.
|
||||
|
||||
//** Number of columns in the grid.
|
||||
@grid-columns: 12;
|
||||
//** Padding between columns. Gets divided in half for the left and right.
|
||||
@grid-gutter-width: 30px;
|
||||
// Navbar collapse
|
||||
//** Point at which the navbar becomes uncollapsed.
|
||||
@grid-float-breakpoint: @screen-sm-min;
|
||||
//** Point at which the navbar begins collapsing.
|
||||
@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
|
||||
|
||||
|
||||
//== Container sizes
|
||||
//
|
||||
//## Define the maximum width of `.container` for different screen sizes.
|
||||
|
||||
// Small screen / tablet
|
||||
@container-tablet: (720px + @grid-gutter-width);
|
||||
//** For `@screen-sm-min` and up.
|
||||
@container-sm: @container-tablet;
|
||||
|
||||
// Medium screen / desktop
|
||||
@container-desktop: (940px + @grid-gutter-width);
|
||||
//** For `@screen-md-min` and up.
|
||||
@container-md: @container-desktop;
|
||||
|
||||
// Large screen / wide desktop
|
||||
@container-large-desktop: (1140px + @grid-gutter-width);
|
||||
//** For `@screen-lg-min` and up.
|
||||
@container-lg: @container-large-desktop;
|
||||
|
||||
|
||||
//== Navbar
|
||||
//
|
||||
//##
|
||||
|
||||
// Basics of a navbar
|
||||
@navbar-height: 50px;
|
||||
@navbar-margin-bottom: @line-height-computed;
|
||||
@navbar-border-radius: @border-radius-base;
|
||||
@navbar-padding-horizontal: floor((@grid-gutter-width / 2));
|
||||
@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
|
||||
@navbar-collapse-max-height: 340px;
|
||||
|
||||
@navbar-default-color: #777;
|
||||
@navbar-default-bg: #f8f8f8;
|
||||
@navbar-default-border: darken(@navbar-default-bg, 6.5%);
|
||||
|
||||
// Navbar links
|
||||
@navbar-default-link-color: #777;
|
||||
@navbar-default-link-hover-color: #333;
|
||||
@navbar-default-link-hover-bg: transparent;
|
||||
@navbar-default-link-active-color: #555;
|
||||
@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%);
|
||||
@navbar-default-link-disabled-color: #ccc;
|
||||
@navbar-default-link-disabled-bg: transparent;
|
||||
|
||||
// Navbar brand label
|
||||
@navbar-default-brand-color: @navbar-default-link-color;
|
||||
@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%);
|
||||
@navbar-default-brand-hover-bg: transparent;
|
||||
|
||||
// Navbar toggle
|
||||
@navbar-default-toggle-hover-bg: #ddd;
|
||||
@navbar-default-toggle-icon-bar-bg: #888;
|
||||
@navbar-default-toggle-border-color: #ddd;
|
||||
|
||||
|
||||
//=== Inverted navbar
|
||||
// Reset inverted navbar basics
|
||||
@navbar-inverse-color: lighten(@gray-light, 15%);
|
||||
@navbar-inverse-bg: #222;
|
||||
@navbar-inverse-border: darken(@navbar-inverse-bg, 10%);
|
||||
|
||||
// Inverted navbar links
|
||||
@navbar-inverse-link-color: lighten(@gray-light, 15%);
|
||||
@navbar-inverse-link-hover-color: #fff;
|
||||
@navbar-inverse-link-hover-bg: transparent;
|
||||
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
|
||||
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
|
||||
@navbar-inverse-link-disabled-color: #444;
|
||||
@navbar-inverse-link-disabled-bg: transparent;
|
||||
|
||||
// Inverted navbar brand label
|
||||
@navbar-inverse-brand-color: @navbar-inverse-link-color;
|
||||
@navbar-inverse-brand-hover-color: #fff;
|
||||
@navbar-inverse-brand-hover-bg: transparent;
|
||||
|
||||
// Inverted navbar toggle
|
||||
@navbar-inverse-toggle-hover-bg: #333;
|
||||
@navbar-inverse-toggle-icon-bar-bg: #fff;
|
||||
@navbar-inverse-toggle-border-color: #333;
|
||||
|
||||
|
||||
//== Navs
|
||||
//
|
||||
//##
|
||||
|
||||
//=== Shared nav styles
|
||||
@nav-link-padding: 10px 15px;
|
||||
@nav-link-hover-bg: @gray-lighter;
|
||||
|
||||
@nav-disabled-link-color: @gray-light;
|
||||
@nav-disabled-link-hover-color: @gray-light;
|
||||
|
||||
//== Tabs
|
||||
@nav-tabs-border-color: #ddd;
|
||||
|
||||
@nav-tabs-link-hover-border-color: @gray-lighter;
|
||||
|
||||
@nav-tabs-active-link-hover-bg: @body-bg;
|
||||
@nav-tabs-active-link-hover-color: @gray;
|
||||
@nav-tabs-active-link-hover-border-color: #ddd;
|
||||
|
||||
@nav-tabs-justified-link-border-color: #ddd;
|
||||
@nav-tabs-justified-active-link-border-color: @body-bg;
|
||||
|
||||
//== Pills
|
||||
@nav-pills-border-radius: @border-radius-base;
|
||||
@nav-pills-active-link-hover-bg: @component-active-bg;
|
||||
@nav-pills-active-link-hover-color: @component-active-color;
|
||||
|
||||
|
||||
//== Pagination
|
||||
//
|
||||
//##
|
||||
|
||||
@pagination-color: @link-color;
|
||||
@pagination-bg: #fff;
|
||||
@pagination-border: #ddd;
|
||||
|
||||
@pagination-hover-color: @link-hover-color;
|
||||
@pagination-hover-bg: @gray-lighter;
|
||||
@pagination-hover-border: #ddd;
|
||||
|
||||
@pagination-active-color: #fff;
|
||||
@pagination-active-bg: @brand-primary;
|
||||
@pagination-active-border: @brand-primary;
|
||||
|
||||
@pagination-disabled-color: @gray-light;
|
||||
@pagination-disabled-bg: #fff;
|
||||
@pagination-disabled-border: #ddd;
|
||||
|
||||
|
||||
//== Pager
|
||||
//
|
||||
//##
|
||||
|
||||
@pager-bg: @pagination-bg;
|
||||
@pager-border: @pagination-border;
|
||||
@pager-border-radius: 15px;
|
||||
|
||||
@pager-hover-bg: @pagination-hover-bg;
|
||||
|
||||
@pager-active-bg: @pagination-active-bg;
|
||||
@pager-active-color: @pagination-active-color;
|
||||
|
||||
@pager-disabled-color: @pagination-disabled-color;
|
||||
|
||||
|
||||
//== Jumbotron
|
||||
//
|
||||
//##
|
||||
|
||||
@jumbotron-padding: 30px;
|
||||
@jumbotron-color: inherit;
|
||||
@jumbotron-bg: @gray-lighter;
|
||||
@jumbotron-heading-color: inherit;
|
||||
@jumbotron-font-size: ceil((@font-size-base * 1.5));
|
||||
@jumbotron-heading-font-size: ceil((@font-size-base * 4.5));
|
||||
|
||||
|
||||
//== Form states and alerts
|
||||
//
|
||||
//## Define colors for form feedback states and, by default, alerts.
|
||||
|
||||
@state-success-text: #3c763d;
|
||||
@state-success-bg: #dff0d8;
|
||||
@state-success-border: darken(spin(@state-success-bg, -10), 5%);
|
||||
|
||||
@state-info-text: #31708f;
|
||||
@state-info-bg: #d9edf7;
|
||||
@state-info-border: darken(spin(@state-info-bg, -10), 7%);
|
||||
|
||||
@state-warning-text: #8a6d3b;
|
||||
@state-warning-bg: #fcf8e3;
|
||||
@state-warning-border: darken(spin(@state-warning-bg, -10), 5%);
|
||||
|
||||
@state-danger-text: #a94442;
|
||||
@state-danger-bg: #f2dede;
|
||||
@state-danger-border: darken(spin(@state-danger-bg, -10), 5%);
|
||||
|
||||
|
||||
//== Tooltips
|
||||
//
|
||||
//##
|
||||
|
||||
//** Tooltip max width
|
||||
@tooltip-max-width: 200px;
|
||||
//** Tooltip text color
|
||||
@tooltip-color: #fff;
|
||||
//** Tooltip background color
|
||||
@tooltip-bg: #000;
|
||||
@tooltip-opacity: .9;
|
||||
|
||||
//** Tooltip arrow width
|
||||
@tooltip-arrow-width: 5px;
|
||||
//** Tooltip arrow color
|
||||
@tooltip-arrow-color: @tooltip-bg;
|
||||
|
||||
|
||||
//== Popovers
|
||||
//
|
||||
//##
|
||||
|
||||
//** Popover body background color
|
||||
@popover-bg: #fff;
|
||||
//** Popover maximum width
|
||||
@popover-max-width: 276px;
|
||||
//** Popover border color
|
||||
@popover-border-color: rgba(0,0,0,.2);
|
||||
//** Popover fallback border color
|
||||
@popover-fallback-border-color: #ccc;
|
||||
|
||||
//** Popover title background color
|
||||
@popover-title-bg: darken(@popover-bg, 3%);
|
||||
|
||||
//** Popover arrow width
|
||||
@popover-arrow-width: 10px;
|
||||
//** Popover arrow color
|
||||
@popover-arrow-color: @popover-bg;
|
||||
|
||||
//** Popover outer arrow width
|
||||
@popover-arrow-outer-width: (@popover-arrow-width + 1);
|
||||
//** Popover outer arrow color
|
||||
@popover-arrow-outer-color: fadein(@popover-border-color, 5%);
|
||||
//** Popover outer arrow fallback color
|
||||
@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
|
||||
|
||||
|
||||
//== Labels
|
||||
//
|
||||
//##
|
||||
|
||||
//** Default label background color
|
||||
@label-default-bg: @gray-light;
|
||||
//** Primary label background color
|
||||
@label-primary-bg: @brand-primary;
|
||||
//** Success label background color
|
||||
@label-success-bg: @brand-success;
|
||||
//** Info label background color
|
||||
@label-info-bg: @brand-info;
|
||||
//** Warning label background color
|
||||
@label-warning-bg: @brand-warning;
|
||||
//** Danger label background color
|
||||
@label-danger-bg: @brand-danger;
|
||||
|
||||
//** Default label text color
|
||||
@label-color: #fff;
|
||||
//** Default text color of a linked label
|
||||
@label-link-hover-color: #fff;
|
||||
|
||||
|
||||
//== Modals
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding applied to the modal body
|
||||
@modal-inner-padding: 15px;
|
||||
|
||||
//** Padding applied to the modal title
|
||||
@modal-title-padding: 15px;
|
||||
//** Modal title line-height
|
||||
@modal-title-line-height: @line-height-base;
|
||||
|
||||
//** Background color of modal content area
|
||||
@modal-content-bg: #fff;
|
||||
//** Modal content border color
|
||||
@modal-content-border-color: rgba(0,0,0,.2);
|
||||
//** Modal content border color **for IE8**
|
||||
@modal-content-fallback-border-color: #999;
|
||||
|
||||
//** Modal backdrop background color
|
||||
@modal-backdrop-bg: #000;
|
||||
//** Modal backdrop opacity
|
||||
@modal-backdrop-opacity: .5;
|
||||
//** Modal header border color
|
||||
@modal-header-border-color: #e5e5e5;
|
||||
//** Modal footer border color
|
||||
@modal-footer-border-color: @modal-header-border-color;
|
||||
|
||||
@modal-lg: 900px;
|
||||
@modal-md: 600px;
|
||||
@modal-sm: 300px;
|
||||
|
||||
|
||||
//== Alerts
|
||||
//
|
||||
//## Define alert colors, border radius, and padding.
|
||||
|
||||
@alert-padding: 15px;
|
||||
@alert-border-radius: @border-radius-base;
|
||||
@alert-link-font-weight: bold;
|
||||
|
||||
@alert-success-bg: @state-success-bg;
|
||||
@alert-success-text: @state-success-text;
|
||||
@alert-success-border: @state-success-border;
|
||||
|
||||
@alert-info-bg: @state-info-bg;
|
||||
@alert-info-text: @state-info-text;
|
||||
@alert-info-border: @state-info-border;
|
||||
|
||||
@alert-warning-bg: @state-warning-bg;
|
||||
@alert-warning-text: @state-warning-text;
|
||||
@alert-warning-border: @state-warning-border;
|
||||
|
||||
@alert-danger-bg: @state-danger-bg;
|
||||
@alert-danger-text: @state-danger-text;
|
||||
@alert-danger-border: @state-danger-border;
|
||||
|
||||
|
||||
//== Progress bars
|
||||
//
|
||||
//##
|
||||
|
||||
//** Background color of the whole progress component
|
||||
@progress-bg: #f5f5f5;
|
||||
//** Progress bar text color
|
||||
@progress-bar-color: #fff;
|
||||
//** Variable for setting rounded corners on progress bar.
|
||||
@progress-border-radius: @border-radius-base;
|
||||
|
||||
//** Default progress bar color
|
||||
@progress-bar-bg: @brand-primary;
|
||||
//** Success progress bar color
|
||||
@progress-bar-success-bg: @brand-success;
|
||||
//** Warning progress bar color
|
||||
@progress-bar-warning-bg: @brand-warning;
|
||||
//** Danger progress bar color
|
||||
@progress-bar-danger-bg: @brand-danger;
|
||||
//** Info progress bar color
|
||||
@progress-bar-info-bg: @brand-info;
|
||||
|
||||
|
||||
//== List group
|
||||
//
|
||||
//##
|
||||
|
||||
//** Background color on `.list-group-item`
|
||||
@list-group-bg: #fff;
|
||||
//** `.list-group-item` border color
|
||||
@list-group-border: #ddd;
|
||||
//** List group border radius
|
||||
@list-group-border-radius: @border-radius-base;
|
||||
|
||||
//** Background color of single list pendants on hover
|
||||
@list-group-hover-bg: #f5f5f5;
|
||||
//** Text color of active list pendants
|
||||
@list-group-active-color: @component-active-color;
|
||||
//** Background color of active list pendants
|
||||
@list-group-active-bg: @component-active-bg;
|
||||
//** Border color of active list elements
|
||||
@list-group-active-border: @list-group-active-bg;
|
||||
//** Text color for content within active list pendants
|
||||
@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
|
||||
|
||||
//** Text color of disabled list pendants
|
||||
@list-group-disabled-color: @gray-light;
|
||||
//** Background color of disabled list pendants
|
||||
@list-group-disabled-bg: @gray-lighter;
|
||||
//** Text color for content within disabled list pendants
|
||||
@list-group-disabled-text-color: @list-group-disabled-color;
|
||||
|
||||
@list-group-link-color: #555;
|
||||
@list-group-link-hover-color: @list-group-link-color;
|
||||
@list-group-link-heading-color: #333;
|
||||
|
||||
|
||||
//== Panels
|
||||
//
|
||||
//##
|
||||
|
||||
@panel-bg: #fff;
|
||||
@panel-body-padding: 15px;
|
||||
@panel-heading-padding: 10px 15px;
|
||||
@panel-footer-padding: @panel-heading-padding;
|
||||
@panel-border-radius: @border-radius-base;
|
||||
|
||||
//** Border color for elements within panels
|
||||
@panel-inner-border: #ddd;
|
||||
@panel-footer-bg: #f5f5f5;
|
||||
|
||||
@panel-default-text: @gray-dark;
|
||||
@panel-default-border: #ddd;
|
||||
@panel-default-heading-bg: #f5f5f5;
|
||||
|
||||
@panel-primary-text: #fff;
|
||||
@panel-primary-border: @brand-primary;
|
||||
@panel-primary-heading-bg: @brand-primary;
|
||||
|
||||
@panel-success-text: @state-success-text;
|
||||
@panel-success-border: @state-success-border;
|
||||
@panel-success-heading-bg: @state-success-bg;
|
||||
|
||||
@panel-info-text: @state-info-text;
|
||||
@panel-info-border: @state-info-border;
|
||||
@panel-info-heading-bg: @state-info-bg;
|
||||
|
||||
@panel-warning-text: @state-warning-text;
|
||||
@panel-warning-border: @state-warning-border;
|
||||
@panel-warning-heading-bg: @state-warning-bg;
|
||||
|
||||
@panel-danger-text: @state-danger-text;
|
||||
@panel-danger-border: @state-danger-border;
|
||||
@panel-danger-heading-bg: @state-danger-bg;
|
||||
|
||||
|
||||
//== Thumbnails
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding around the thumbnail image
|
||||
@thumbnail-padding: 4px;
|
||||
//** Thumbnail background color
|
||||
@thumbnail-bg: @body-bg;
|
||||
//** Thumbnail border color
|
||||
@thumbnail-border: #ddd;
|
||||
//** Thumbnail border radius
|
||||
@thumbnail-border-radius: @border-radius-base;
|
||||
|
||||
//** Custom text color for thumbnail captions
|
||||
@thumbnail-caption-color: @text-color;
|
||||
//** Padding around the thumbnail caption
|
||||
@thumbnail-caption-padding: 9px;
|
||||
|
||||
|
||||
//== Wells
|
||||
//
|
||||
//##
|
||||
|
||||
@well-bg: #f5f5f5;
|
||||
@well-border: darken(@well-bg, 7%);
|
||||
|
||||
|
||||
//== Badges
|
||||
//
|
||||
//##
|
||||
|
||||
@badge-color: #fff;
|
||||
//** Linked badge text color on hover
|
||||
@badge-link-hover-color: #fff;
|
||||
@badge-bg: @gray-light;
|
||||
|
||||
//** Badge text color in active nav link
|
||||
@badge-active-color: @link-color;
|
||||
//** Badge background color in active nav link
|
||||
@badge-active-bg: #fff;
|
||||
|
||||
@badge-font-weight: bold;
|
||||
@badge-line-height: 1;
|
||||
@badge-border-radius: 10px;
|
||||
|
||||
|
||||
//== Breadcrumbs
|
||||
//
|
||||
//##
|
||||
|
||||
@breadcrumb-padding-vertical: 8px;
|
||||
@breadcrumb-padding-horizontal: 15px;
|
||||
//** Breadcrumb background color
|
||||
@breadcrumb-bg: #f5f5f5;
|
||||
//** Breadcrumb text color
|
||||
@breadcrumb-color: #ccc;
|
||||
//** Text color of current page in the breadcrumb
|
||||
@breadcrumb-active-color: @gray-light;
|
||||
//** Textual separator for between breadcrumb elements
|
||||
@breadcrumb-separator: "/";
|
||||
|
||||
|
||||
//== Carousel
|
||||
//
|
||||
//##
|
||||
|
||||
@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
||||
|
||||
@carousel-control-color: #fff;
|
||||
@carousel-control-width: 15%;
|
||||
@carousel-control-opacity: .5;
|
||||
@carousel-control-font-size: 20px;
|
||||
|
||||
@carousel-indicator-active-bg: #fff;
|
||||
@carousel-indicator-border-color: #fff;
|
||||
|
||||
@carousel-caption-color: #fff;
|
||||
|
||||
|
||||
//== Close
|
||||
//
|
||||
//##
|
||||
|
||||
@close-font-weight: bold;
|
||||
@close-color: #000;
|
||||
@close-text-shadow: 0 1px 0 #fff;
|
||||
|
||||
|
||||
//== Code
|
||||
//
|
||||
//##
|
||||
|
||||
@code-color: #c7254e;
|
||||
@code-bg: #f9f2f4;
|
||||
|
||||
@kbd-color: #fff;
|
||||
@kbd-bg: #333;
|
||||
|
||||
@pre-bg: #f5f5f5;
|
||||
@pre-color: @gray-dark;
|
||||
@pre-border-color: #ccc;
|
||||
@pre-scrollable-max-height: 340px;
|
||||
|
||||
|
||||
//== Type
|
||||
//
|
||||
//##
|
||||
|
||||
//** Horizontal offset for forms and lists.
|
||||
@component-offset-horizontal: 180px;
|
||||
//** Text muted color
|
||||
@text-muted: @gray-light;
|
||||
//** Abbreviations and acronyms border color
|
||||
@abbr-border-color: @gray-light;
|
||||
//** Headings small color
|
||||
@headings-small-color: @gray-light;
|
||||
//** Blockquote small color
|
||||
@blockquote-small-color: @gray-light;
|
||||
//** Blockquote font size
|
||||
@blockquote-font-size: (@font-size-base * 1.25);
|
||||
//** Blockquote border color
|
||||
@blockquote-border-color: @gray-lighter;
|
||||
//** Page header border color
|
||||
@page-header-border-color: @gray-lighter;
|
||||
//** Width of horizontal description list titles
|
||||
@dl-horizontal-offset: @component-offset-horizontal;
|
||||
//** Point at which .dl-horizontal becomes horizontal
|
||||
@dl-horizontal-breakpoint: @grid-float-breakpoint;
|
||||
//** Horizontal line color.
|
||||
@hr-border: @gray-lighter;
|
||||
@@ -0,0 +1,41 @@
|
||||
@import "variables";
|
||||
|
||||
#radius {
|
||||
.border-radius(@from,@end,@step) {
|
||||
.mX(@f,@e,@s) when (@e >= @f) {
|
||||
.border-radius-@{e} {
|
||||
-webkit-border-radius: @e*1px;
|
||||
-moz-border-radius: @e*1px;
|
||||
border-radius: @e*1px;
|
||||
}
|
||||
.br@{e} {
|
||||
-webkit-border-radius: @e*1px;
|
||||
-moz-border-radius: @e*1px;
|
||||
border-radius: @e*1px;
|
||||
}
|
||||
|
||||
.mX(@f, @e - @s, @s);
|
||||
}
|
||||
.mX(@from, @end, @step);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#radius > .border-radius(1, 10, 1);
|
||||
|
||||
.border-dash {
|
||||
border: 1px dashed #ccc;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
border: 1px solid @brand-danger;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #F9F9F9;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
@import "variables";
|
||||
|
||||
.btn-action {
|
||||
margin: 0 3px;
|
||||
display: inline-block;
|
||||
opacity: 0.85;
|
||||
-webkit-transition: all 0.1s;
|
||||
-o-transition: all 0.1s;
|
||||
transition: all 0.1s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
opacity: 1;
|
||||
-moz-transform: scale(1.2);
|
||||
-webkit-transform: scale(1.2);
|
||||
-o-transform: scale(1.2);
|
||||
-ms-transform: scale(1.2);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
//编辑区域按钮,鼠标一上去按钮变大
|
||||
.action-buttons {
|
||||
a {
|
||||
.btn-action;
|
||||
}
|
||||
}
|
||||
|
||||
.cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
@import "./variables.less";
|
||||
|
||||
.bg-primary{ background-color: @brand-primary; color: white;}
|
||||
.bg-success{ background-color: @brand-success; color: white; }
|
||||
.bg-info{ background-color: @brand-info; color: white; }
|
||||
.bg-warning{ background-color: @brand-warning; color: white; }
|
||||
.bg-danger{ background-color: @brand-danger; color: white; }
|
||||
.bg-gray{ background-color: @brand-gray; color: white; }
|
||||
.bg-laxative{ background-color: @brand-laxative; color: white; }
|
||||
|
||||
|
||||
.text-primary{ color: @brand-primary;}
|
||||
.text-success{ color: @brand-success; }
|
||||
.text-info{ color: @brand-info; }
|
||||
.text-warning{ color: @brand-warning; }
|
||||
.text-danger{ color: @brand-danger; }
|
||||
.text-gray{ color: @brand-gray; }
|
||||
.text-laxative{ color: @brand-laxative; }
|
||||
|
||||
.bg-navy { background-color: #001F3F;}
|
||||
.bg-blue { background-color: #0074D9;}
|
||||
.bg-aqua { background-color: #7FDBFF;}
|
||||
.bg-aliceblue { background-color: aliceblue;}
|
||||
.bg-pink { background-color: pink;}
|
||||
.bg-azure { background-color: azure;}
|
||||
.bg-teal { background-color: #39CCCC;}
|
||||
.bg-olive { background-color: #3D9970;}
|
||||
.bg-green { background-color: #2ECC40;}
|
||||
.bg-lime { background-color: #01FF70;}
|
||||
.bg-yellow { background-color: #FFDC00;}
|
||||
.bg-pink { color: pink; }
|
||||
.bg-orange { background-color: #FF851B;}
|
||||
.bg-red { background-color: #FF4136;}
|
||||
.bg-fuchsia { background-color: #F012BE;}
|
||||
.bg-purple { background-color: #B10DC9;}
|
||||
.bg-maroon { background-color: #85144B;}
|
||||
.bg-white { background-color: #FFFFFF;}
|
||||
.bg-gray { background-color: #AAAAAA;}
|
||||
.bg-silver { background-color: #DDDDDD;}
|
||||
.bg-silver-white {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
.bg-black { background-color: #111111;}
|
||||
|
||||
.bg-111{ background-color:#111 }
|
||||
.bg-222{ background-color:#222 }
|
||||
.bg-333{ background-color:#333 }
|
||||
.bg-444{ background-color:#444 }
|
||||
.bg-555{ background-color:#555 }
|
||||
.bg-666{ background-color:#666 }
|
||||
.bg-777{ background-color:#777 }
|
||||
.bg-888{ background-color:#888 }
|
||||
.bg-999{ background-color:#999 }
|
||||
.bg-aaa{ background-color:#aaa }
|
||||
.bg-bbb{ background-color:#bbb }
|
||||
.bg-ccc{ background-color:#ccc }
|
||||
.bg-ddd{ background-color:#ddd }
|
||||
.bg-eee{ background-color:#eee }
|
||||
|
||||
|
||||
|
||||
/* Colors */
|
||||
.navy { color: #001F3F;}
|
||||
.blue { color: #0074D9;}
|
||||
.aqua { color: #7FDBFF;}
|
||||
.teal { color: #39CCCC;}
|
||||
.olive { color: #3D9970;}
|
||||
.green { color: #2ECC40;}
|
||||
.lime { color: #01FF70;}
|
||||
.yellow { color: #FFDC00;}
|
||||
.pink { color: pink; }
|
||||
.orange { color: #FF851B;}
|
||||
.red { color: #FF4136;}
|
||||
.fuchsia { color: #F012BE;}
|
||||
.purple { color: #B10DC9;}
|
||||
.maroon { color: #85144B;}
|
||||
.white { color: #FFFFFF;}
|
||||
.silver { color: #DDDDDD;}
|
||||
.gray { color: #AAAAAA;}
|
||||
.black { color: #111111;}
|
||||
|
||||
|
||||
.color-111{color:#111}
|
||||
.color-222{color:#222}
|
||||
.color-333{color:#333}
|
||||
.color-444{color:#444}
|
||||
.color-555{color:#555}
|
||||
.color-666{color:#666}
|
||||
.color-777{color:#777}
|
||||
.color-888{color:#888}
|
||||
.color-999{color:#999}
|
||||
.color-aaa{color:#aaa}
|
||||
.color-bbb{color:#bbb}
|
||||
.color-ccc{color:#ccc}
|
||||
.color-ddd{color:#ddd}
|
||||
.color-eee{color:#eee}
|
||||
|
||||
|
||||
.color-text { color: #660E7A;}
|
||||
.color-doc { color: #295496;}
|
||||
.color-xls { color: #1E6C41;}
|
||||
.color-ppt { color: #D04324;}
|
||||
.color-pdf { color: #E40B0B;}
|
||||
.color-audio { color: #5bc0de;}
|
||||
.color-video { color: #5cb85c;}
|
||||
.color-image { color: #0074D9;}
|
||||
.color-archive { color: #4437f2;}
|
||||
|
||||
.color-light-active{ color: #ffc60c;}
|
||||
.color-light-inactive{ color:#ccc;}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#font{
|
||||
.f(@from,@end,@step){
|
||||
.mX(@f,@e,@s) when (@e >= @f){
|
||||
.f@{e}{
|
||||
font-size:@e*1px!important;
|
||||
}
|
||||
.mX(@f,@e - @s,@s);
|
||||
}
|
||||
.mX(@from,@end,@step);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#font > .f(10,80,1);
|
||||
|
||||
#font{
|
||||
.ln(@from,@end,@step){
|
||||
.mX(@f,@e,@s) when (@e >= @f){
|
||||
.ln@{e}{
|
||||
line-height:@e*1px!important;
|
||||
}
|
||||
.mX(@f,@e - @s,@s);
|
||||
}
|
||||
.mX(@from,@end,@step);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#font > .ln(10,100,1);
|
||||
|
||||
|
||||
|
||||
.bold{
|
||||
font-weight:bold;
|
||||
}
|
||||
.italic{
|
||||
font-style:italic;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
全局常用样式定义
|
||||
使用ml、pt等缩写表示常用的布局方式,数字表示像素值
|
||||
比如ml10表示margin-left:10px;
|
||||
*/
|
||||
#layout {
|
||||
.margin(@from,@end,@step) {
|
||||
.mX(@f,@e,@s) when (@e >= @f) {
|
||||
.m@{e} {
|
||||
margin: @e*1px;
|
||||
}
|
||||
.mt@{e} {
|
||||
margin-top: @e*1px;
|
||||
}
|
||||
.mr@{e} {
|
||||
margin-right: @e*1px;
|
||||
}
|
||||
.mb@{e} {
|
||||
margin-bottom: @e*1px;
|
||||
}
|
||||
.ml@{e} {
|
||||
margin-left: @e*1px;
|
||||
}
|
||||
.mv@{e} {
|
||||
margin-top: @e*1px;
|
||||
margin-bottom: @e*1px;
|
||||
}
|
||||
.mh@{e} {
|
||||
margin-left: @e*1px;
|
||||
margin-right: @e*1px*1px;
|
||||
}
|
||||
.mX(@f, @e - @s, @s);
|
||||
}
|
||||
.mX(@from, @end, @step);
|
||||
}
|
||||
.padding(@from,@end,@step) {
|
||||
.pX(@f,@e,@s) when (@e >= @f) {
|
||||
.p@{e} {
|
||||
padding: @e*1px;
|
||||
}
|
||||
.pt@{e} {
|
||||
padding-top: @e*1px;
|
||||
}
|
||||
.pr@{e} {
|
||||
padding-right: @e*1px;
|
||||
}
|
||||
.pb@{e} {
|
||||
padding-bottom: @e*1px;
|
||||
}
|
||||
.pl@{e} {
|
||||
padding-left: @e*1px;
|
||||
}
|
||||
.pv@{e} {
|
||||
padding-top: @e*1px;
|
||||
padding-bottom: @e*1px;
|
||||
}
|
||||
.ph@{e} {
|
||||
padding-left: @e*1px;
|
||||
padding-right: @e*1px;
|
||||
}
|
||||
.pX(@f, @e - @s, @s);
|
||||
}
|
||||
.pX(@from, @end, @step);
|
||||
}
|
||||
.width(@from,@end,@step) {
|
||||
.wX(@f,@e,@s) when (@e >= @f) {
|
||||
.w@{e} {
|
||||
width: @e*1px;
|
||||
}
|
||||
.w@{e}m {
|
||||
max-width: @e*1px;
|
||||
}
|
||||
.w@{e}n {
|
||||
min-width: @e*1px;
|
||||
}
|
||||
.w@{e}i {
|
||||
width: @e*1px !important;
|
||||
}
|
||||
.wX(@f, @e - @s, @s);
|
||||
}
|
||||
.wX(@from, @end, @step);
|
||||
}
|
||||
.height(@from,@end,@step) {
|
||||
.hX(@f,@e,@s) when (@e >= @f) {
|
||||
.h@{e} {
|
||||
height: @e*1px;
|
||||
}
|
||||
.lh@{e} {
|
||||
line-height: @e*1px;
|
||||
}
|
||||
.h@{e}m {
|
||||
max-height: @e*1px;
|
||||
}
|
||||
.h@{e}n {
|
||||
min-height: @e*1px;
|
||||
}
|
||||
.h@{e}i {
|
||||
height: @e*1px !important;
|
||||
}
|
||||
.hX(@f, @e - @s, @s);
|
||||
}
|
||||
.hX(@from, @end, @step);
|
||||
}
|
||||
}
|
||||
|
||||
.wp20 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.wp25 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.wp33 {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.wp100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wp50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.hp100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hp50 {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
#layout > .margin(20, 200, 5);
|
||||
#layout > .margin(0, 19, 1);
|
||||
#layout > .padding(20, 200, 5);
|
||||
#layout > .padding(0, 19, 1);
|
||||
#layout > .width(20, 400, 5);
|
||||
#layout > .width(0, 19, 1);
|
||||
#layout > .height(20, 400, 5);
|
||||
#layout > .height(0, 19, 1);
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
@import "variables";
|
||||
|
||||
.compulsory {
|
||||
&:before {
|
||||
content: "*";
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-hints {
|
||||
font-size: 10px;
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hover-underline {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//超出三行使用点点点
|
||||
.list-text-restriction {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: 17px;
|
||||
max-height: 51px;
|
||||
-webkit-line-clamp: 3;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.one-line {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
//讨厌的outline
|
||||
a {
|
||||
outline: none;
|
||||
|
||||
&:focus, &:hover, &:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
//制造一个手机容器
|
||||
.mobile-container {
|
||||
max-width: 400px;
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
display: block;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
//按照字母来wrap,不然有些老长老长
|
||||
.wrap-word {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.display-none {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//超链接导航样式
|
||||
.link {
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
text-decoration-skip: objects;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//超出三行使用点点点
|
||||
.line-ellipsis(@rows,@line-height) {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: @line-height;
|
||||
max-height: @line-height * @rows;
|
||||
-webkit-line-clamp: @rows;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
@media (min-width: 992px) {
|
||||
.visible-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visible-pc {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.visible-mobile {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.visible-pc {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
//浏览器滚动条样式
|
||||
|
||||
//滚动条样式
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
transition: all .3s ease;
|
||||
border-color: transparent;
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
z-index: 40
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
transition: all .3s ease;
|
||||
background-color: rgba(0, 0, 0, .15)
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: #eaeaeb
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@import "../bootstrap/variables.less";
|
||||
//custom basic color
|
||||
@brand-primary: #006699;
|
||||
@brand-success: #1ab394;
|
||||
@brand-info: #5EC1C5;
|
||||
@brand-warning: #FEC62E;
|
||||
@brand-danger: #FE8768;
|
||||
@brand-gray: #c2c2c2;
|
||||
@brand-laxative: #B3EE3A;
|
||||
|
||||
@theme-color: #1b2a9d;
|
||||
@theme-background: #fafafa;
|
||||
|
||||
//左侧的主题图标导航栏宽度。
|
||||
@subject-icon-bar-width: 50px;
|
||||
@@ -0,0 +1,16 @@
|
||||
//使用自定义的主题
|
||||
@import "global/animation";
|
||||
@import "global/border";
|
||||
@import "global/button";
|
||||
@import "global/color";
|
||||
@import "global/font";
|
||||
@import "global/layout";
|
||||
@import "global/miscellaneous";
|
||||
@import "global/responsive";
|
||||
|
||||
html, body {
|
||||
font-family: "微软雅黑", Microsoft YaHei, "Helvetica Neue", Helvetica, Arial, sans-serif, "open sans";
|
||||
font-size: 13px;
|
||||
color: @text-color;
|
||||
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
/**
|
||||
* 带有挂载状态的自定义组件
|
||||
*/
|
||||
export default class BambooComponent<P, S> extends React.Component <P, S> {
|
||||
|
||||
//是否处于挂载的状态。
|
||||
mounted: boolean = false
|
||||
|
||||
componentWillUnmount() {
|
||||
let that = this
|
||||
that.mounted = false
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let that = this
|
||||
that.mounted = true
|
||||
}
|
||||
|
||||
//更新当前视图。
|
||||
updateUI() {
|
||||
let that = this
|
||||
if (that.mounted) {
|
||||
that.setState({})
|
||||
} else {
|
||||
console.info(this.constructor.name + "已经脱离挂载,不再刷新视图。")
|
||||
}
|
||||
}
|
||||
|
||||
//获取当前组件的唯一标识
|
||||
getIdentifier() {
|
||||
return this.constructor.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ToolItem} from "../types";
|
||||
|
||||
export default class MenuItem implements ToolItem {
|
||||
name: string;
|
||||
url: string;
|
||||
iconType: string;
|
||||
active: boolean = false;
|
||||
|
||||
|
||||
constructor(name: string, url: string, iconType: string) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.iconType = iconType;
|
||||
|
||||
//当前的url完全一致,那么就高亮显示
|
||||
if (url == "/user/login") {
|
||||
this.active = window.location.pathname === url || window.location.pathname === "/user/register"
|
||||
} else {
|
||||
this.active = window.location.pathname === url
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 管理当前所有的菜单
|
||||
*/
|
||||
import MenuItem from './MenuItem';
|
||||
import Moon from '../model/global/Moon';
|
||||
import User from '../model/user/User';
|
||||
import { UserRole } from '../model/user/UserRole';
|
||||
|
||||
export default class MenuManager {
|
||||
|
||||
//单例模式
|
||||
private static singleton: MenuManager;
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
static getSingleton(): MenuManager {
|
||||
if (!MenuManager.singleton) {
|
||||
//初始化一个mainLand.
|
||||
MenuManager.singleton = new MenuManager();
|
||||
|
||||
}
|
||||
return MenuManager.singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到当前高亮的菜单
|
||||
*/
|
||||
getSelectedKeys(): string[] {
|
||||
|
||||
let keys: string[] = this.getMenuItems()
|
||||
.filter((menuItem: MenuItem, index: number) => {
|
||||
return menuItem.active;
|
||||
}).map((menuItem: MenuItem, index: number) => {
|
||||
return menuItem.url;
|
||||
});
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮某个菜单
|
||||
*/
|
||||
selectMenu(url: string) {
|
||||
|
||||
this.getMenuItems().forEach((menuItem: MenuItem, index: number) => {
|
||||
menuItem.active = menuItem.url === url;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
getMenuItems(): MenuItem[] {
|
||||
let user: User = Moon.getSingleton().user;
|
||||
|
||||
let menuItems: MenuItem[] = [];
|
||||
|
||||
if (user.role === UserRole.GUEST) {
|
||||
menuItems = [
|
||||
new MenuItem('登录', '/user/login', 'user'),
|
||||
];
|
||||
} else {
|
||||
menuItems = [
|
||||
new MenuItem('文章', '/article/list', 'bar-chart'),
|
||||
new MenuItem('退出', '/user/logout', 'poweroff'),
|
||||
];
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import BaseEntity from '../base/BaseEntity';
|
||||
import Filter from '../base/filter/Filter';
|
||||
import SortFilter from '../base/filter/SortFilter';
|
||||
import InputFilter from '../base/filter/InputFilter';
|
||||
|
||||
|
||||
export default class Article extends BaseEntity {
|
||||
|
||||
userUuid: string | null = null;
|
||||
title: string | null = null;
|
||||
path: string | null = null;
|
||||
tags: string | null = null;
|
||||
posterTankUuid: string | null = null;
|
||||
posterUrl: string | null = null;
|
||||
author: string | null = null;
|
||||
digest: string | null = null;
|
||||
isMarkdown: boolean = true;
|
||||
html: string | null = null;
|
||||
privacy: boolean = false;
|
||||
top: boolean = false;
|
||||
agree: number = 0;
|
||||
words: number = 0;
|
||||
hit: number = 0;
|
||||
commentNum: number = 0;
|
||||
|
||||
constructor(reactComponent?: React.Component) {
|
||||
|
||||
super(reactComponent);
|
||||
|
||||
}
|
||||
|
||||
assign(obj: any) {
|
||||
super.assign(obj);
|
||||
|
||||
|
||||
}
|
||||
|
||||
getForm(): any {
|
||||
return {
|
||||
title: this.title,
|
||||
path: this.path,
|
||||
author: this.author,
|
||||
html: this.html,
|
||||
uuid: this.uuid ? this.uuid : null,
|
||||
};
|
||||
}
|
||||
|
||||
getFilters(): Filter[] {
|
||||
return [
|
||||
...super.getFilters(),
|
||||
new InputFilter('标题', 'title'),
|
||||
new InputFilter('路径', 'path'),
|
||||
new InputFilter('作者', 'author'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import ObjectUtil from "../../util/ObjectUtil";
|
||||
import JsonUtil from "../../util/JsonUtil";
|
||||
import DateUtil from "../../util/DateUtil";
|
||||
|
||||
/**
|
||||
*
|
||||
* 基类。使我们前端的所有自定义类的基类。
|
||||
* 在这个类中可以统计建立的实体个数。
|
||||
*/
|
||||
export default class Base {
|
||||
|
||||
//id自增长值
|
||||
private static AUTO_INCREMENT_ID = 0
|
||||
|
||||
//当前对象的ID,这个字段让我们可以很轻松的统计出共创建了多少个实体类。
|
||||
autoId: number = 0
|
||||
|
||||
|
||||
//我们认为每个实体都会存放于某个react组件中,当然可以不传入。
|
||||
constructor() {
|
||||
|
||||
this.autoId = Base.generateAutoId()
|
||||
}
|
||||
|
||||
static generateAutoId() {
|
||||
Base.AUTO_INCREMENT_ID++
|
||||
return Base.AUTO_INCREMENT_ID
|
||||
}
|
||||
|
||||
|
||||
//把obj中的属性,赋值到this中来。采用深拷贝。
|
||||
assign(obj: any) {
|
||||
ObjectUtil.extend(this, obj)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param field 字段名
|
||||
* @param Clazz 类型名
|
||||
*/
|
||||
assignList<F extends keyof this>(field: string, Clazz: any) {
|
||||
|
||||
ObjectUtil.assignList(this, field, Clazz)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据一个类型,渲染出对应的数组。
|
||||
* @param json 字符串或者数组对象。
|
||||
* @param Clazz 需要渲染的目标对象
|
||||
* @returns {*}
|
||||
*/
|
||||
static renderList(json: any, Clazz: any) {
|
||||
|
||||
let target: any = []
|
||||
|
||||
|
||||
let arr: any = []
|
||||
|
||||
if (json instanceof String || typeof json === "string") {
|
||||
|
||||
arr = JsonUtil.parseList(json)
|
||||
|
||||
} else if (json instanceof Array) {
|
||||
arr = json
|
||||
} else {
|
||||
|
||||
console.error("源必须为字符或者数组", json, typeof json)
|
||||
return target
|
||||
}
|
||||
|
||||
//如果我们要转换成字符串的数组形式,那么this[field]应该是一个字符串才对。
|
||||
if (Clazz === String) {
|
||||
return arr
|
||||
}
|
||||
|
||||
if (!Clazz || !(Clazz.prototype instanceof Base)) {
|
||||
console.error("指定的类型必须是 Base的子类 ")
|
||||
return target
|
||||
}
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let bean = arr[i]
|
||||
|
||||
let clazz = new Clazz()
|
||||
|
||||
clazz.assign(bean)
|
||||
|
||||
target.push(clazz)
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
//直接render出一个Entity. field字段名,Clazz类名。
|
||||
assignEntity(field: any, Clazz: any) {
|
||||
|
||||
let thisObj: any = this
|
||||
|
||||
let obj: any = thisObj[field]
|
||||
if (!obj) {
|
||||
if (Clazz) {
|
||||
let EntityClazz: any = this.constructor
|
||||
obj = (new EntityClazz())[field]
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (Clazz === Date) {
|
||||
|
||||
thisObj[field] = DateUtil.str2Date(obj)
|
||||
|
||||
} else if (Clazz.prototype instanceof Base) {
|
||||
|
||||
//可能此处的该项属性做了特殊处理的。
|
||||
//1024*1024 以及 "图片尺寸不超过1M"用let bean = new Clazz(); 就无法反映出来。因为父类assign的时候已经将avatar给变成了Object.
|
||||
let bean = (new thisObj.constructor())[field]
|
||||
if (!bean) {
|
||||
bean = new Clazz()
|
||||
}
|
||||
|
||||
if (obj !== null) {
|
||||
bean.assign(obj)
|
||||
thisObj[field] = bean
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error('调用错误!')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import StringUtil from '../../util/StringUtil';
|
||||
import SafeUtil from '../../util/SafeUtil';
|
||||
import SortFilter from './filter/SortFilter';
|
||||
import Filter from './filter/Filter';
|
||||
import HttpBase from './HttpBase';
|
||||
|
||||
|
||||
/**
|
||||
* 实体基类
|
||||
* 继承这个类的表示在数据库中有对应的表。
|
||||
*/
|
||||
export default class BaseEntity extends HttpBase {
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
uuid: string | null = null;
|
||||
|
||||
/**
|
||||
* 排序值
|
||||
*/
|
||||
sort: number = 0;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: Date | null = null;
|
||||
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
updateTime: Date | null = null;
|
||||
|
||||
|
||||
//************ 前端辅助字段 ****************/
|
||||
//加载详情的指示
|
||||
detailLoading: boolean = false;
|
||||
|
||||
|
||||
//我们认为每个实体都会存放于某个react组件中,当然可以不传入。
|
||||
constructor(reactComponent?: React.Component | null) {
|
||||
|
||||
super(reactComponent);
|
||||
|
||||
}
|
||||
|
||||
//把obj中的属性,赋值到this中来。采用深拷贝。
|
||||
assign(obj: any) {
|
||||
super.assign(obj);
|
||||
|
||||
this.assignEntity('createTime', Date);
|
||||
this.assignEntity('updateTime', Date);
|
||||
|
||||
}
|
||||
|
||||
//获取过滤器,必须每次动态生成,否则会造成filter逻辑混乱。
|
||||
getFilters(): Filter[] {
|
||||
return [
|
||||
new SortFilter('修改时间排序', 'orderCreateTime'),
|
||||
new SortFilter('创建时间排序', 'orderUpdateTime'),
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
//提交之前对自己进行验证。返回错误信息,null表示没有错误。
|
||||
validate() {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//提交的表单
|
||||
getForm(): any {
|
||||
console.error('getForm: you should override this base method.');
|
||||
}
|
||||
|
||||
//获取到当前类的单数标签。比如 Project便得到 project
|
||||
getTAG(): string {
|
||||
|
||||
let className = this.constructor.name;
|
||||
|
||||
//IE无法直接通过this.constructor.name获取到相应名称
|
||||
if (!className) {
|
||||
className = StringUtil.functionName(this.constructor);
|
||||
}
|
||||
|
||||
return StringUtil.lowerCamel(className);
|
||||
}
|
||||
|
||||
|
||||
//获取到当前实体的url前缀。比如Notification获取到 /api/fs/notification
|
||||
getUrlPrefix(): string {
|
||||
return '/api' + StringUtil.lowerSlash(this.getTAG());
|
||||
}
|
||||
|
||||
|
||||
getUrlCreate(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/create';
|
||||
}
|
||||
|
||||
getUrlDel(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/delete';
|
||||
|
||||
}
|
||||
|
||||
getUrlEdit(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/edit';
|
||||
}
|
||||
|
||||
getUrlDetail(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/detail';
|
||||
}
|
||||
|
||||
getUrlList(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/list';
|
||||
}
|
||||
|
||||
getUrlSort(): string {
|
||||
let prefix = this.getUrlPrefix();
|
||||
|
||||
return prefix + '/sort';
|
||||
}
|
||||
|
||||
|
||||
//新增或者修改
|
||||
httpSave(successCallback?: any, errorCallback?: any, finallyCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let url = this.getUrlCreate();
|
||||
if (this.uuid) {
|
||||
url = this.getUrlEdit();
|
||||
}
|
||||
|
||||
|
||||
this.errorMessage = this.validate();
|
||||
if (this.errorMessage) {
|
||||
that.defaultErrorHandler(this.errorMessage, errorCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
this.httpPost(url, this.getForm(), function(response: any) {
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finallyCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//common http detail methods.
|
||||
httpDetail(successCallback?: any, errorCallback?: any, finallyCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
if (!this.uuid) {
|
||||
|
||||
this.errorMessage = 'id未指定,无法获取到详情!';
|
||||
|
||||
this.defaultErrorHandler(this.errorMessage, errorCallback);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.getUrlDetail() + '?uuid=' + this.uuid;
|
||||
|
||||
this.detailLoading = true;
|
||||
|
||||
this.httpGet(url, {}, function(response: any) {
|
||||
that.detailLoading = false;
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, function(response: any) {
|
||||
|
||||
that.detailLoading = false;
|
||||
|
||||
if (typeof errorCallback === 'function') {
|
||||
errorCallback(that.getErrorMessage(response), response);
|
||||
} else {
|
||||
//没有传入错误处理的方法就采用默认处理方法:toast弹出该错误信息。
|
||||
that.defaultErrorHandler(response);
|
||||
}
|
||||
}, finallyCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
httpDel(successCallback?: any, errorCallback?: any, finallyCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
if (!this.uuid) {
|
||||
|
||||
this.errorMessage = '没有id,无法删除!';
|
||||
that.defaultErrorHandler(this.errorMessage, errorCallback);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.getUrlDel() + '?uuid=' + this.uuid;
|
||||
|
||||
this.httpPost(url, {}, function(response: any) {
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finallyCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import HttpUtil from "../../util/HttpUtil";
|
||||
import SafeUtil from "../../util/SafeUtil";
|
||||
import qs from "qs"
|
||||
import {message as MessageBox} from 'antd';
|
||||
import React from "react";
|
||||
import ViewBase from "./ViewBase";
|
||||
import Sun from "../global/Sun";
|
||||
import {WebResultCode} from "./WebResultCode";
|
||||
|
||||
/**
|
||||
* 基类。带有网络请求能力的基类
|
||||
* 继承了该类就表示具有了去服务器请求的能力。
|
||||
*/
|
||||
export default class HttpBase extends ViewBase {
|
||||
|
||||
static lastLoginErrorTimestamp = 0
|
||||
|
||||
//是否需要自动刷新State
|
||||
needReactComponentUpdate: boolean = true
|
||||
|
||||
//当前是否正在进行http请求
|
||||
loading: boolean = false
|
||||
|
||||
//请求http的时候是否有错误
|
||||
errorMessage: string | null = null
|
||||
|
||||
//我们认为每个实体都会存放于某个react组件中,当然可以不传入。
|
||||
constructor(reactComponent?: React.Component | null) {
|
||||
|
||||
super()
|
||||
|
||||
if (reactComponent) {
|
||||
this.reactComponent = reactComponent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//更新当前的视图,只在需要更新的情况下才更新。
|
||||
updateUI() {
|
||||
if (this.needReactComponentUpdate && this.reactComponent) {
|
||||
ViewBase.updateComponentUI(this.reactComponent, this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转跳到ACL页面
|
||||
*/
|
||||
jumpLogin() {
|
||||
|
||||
Sun.navigateTo("/user/login")
|
||||
|
||||
console.error("这里是需要跳转的")
|
||||
|
||||
}
|
||||
|
||||
//从一个返回中获取出其错误信息。适配各种错误的类型。
|
||||
getErrorMessage(response: any) {
|
||||
|
||||
console.error("getErrorMessage", response)
|
||||
let msg = '服务器出错,请稍后再试!'
|
||||
|
||||
if (!response) {
|
||||
msg = '出错啦,请稍后重试!'
|
||||
} else if (typeof response === 'string') {
|
||||
msg = response
|
||||
} else if (response['msg']) {
|
||||
msg = response['msg']
|
||||
} else if (response['message']) {
|
||||
msg = response['message']
|
||||
} else {
|
||||
let temp = response['data']
|
||||
if (temp !== null && typeof temp === 'object') {
|
||||
if (temp['message']) {
|
||||
msg = temp['message']
|
||||
} else if (temp['msg']) {
|
||||
msg = temp['msg']
|
||||
} else {
|
||||
if (temp['error'] && temp['error']['message']) {
|
||||
msg = temp['error']['message']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.errorMessage = msg
|
||||
return msg
|
||||
}
|
||||
|
||||
|
||||
//提供全局的默认处理方式,可以自定义错误处理
|
||||
defaultErrorHandler(response: any, errorCallback?: any) {
|
||||
|
||||
let msg = this.getErrorMessage(response)
|
||||
|
||||
console.error("请求出错了", typeof msg, msg)
|
||||
|
||||
if (typeof errorCallback === 'function') {
|
||||
errorCallback(msg, response)
|
||||
} else {
|
||||
MessageBox.error(msg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//专门捕捉没有登录这种错误。return true -> 有错误(已经处理掉了) false -> 没错误 (什么都没干)
|
||||
specialErrorHandler(response: any) {
|
||||
|
||||
if (!response || !response.data) {
|
||||
return false
|
||||
}
|
||||
|
||||
//1.判断是不是登录错误
|
||||
if (response.data["code"] === WebResultCode.LOGIN) {
|
||||
|
||||
//这个问题不能报的太频繁,比如一个页面请求了两个接口,两个接口都报没有登录。
|
||||
if ((new Date().getTime()) - HttpBase.lastLoginErrorTimestamp < 3000) {
|
||||
return true
|
||||
} else {
|
||||
HttpBase.lastLoginErrorTimestamp = (new Date().getTime());
|
||||
}
|
||||
|
||||
MessageBox.error("您尚未登录,请登录后访问!")
|
||||
|
||||
//立即进行登录跳转。
|
||||
this.jumpLogin()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 基类中的http请求会去统一处理错误情况。
|
||||
* 1.登录过期
|
||||
*
|
||||
*/
|
||||
httpGet(url: any, params = {}, successCallback?: any, errorCallback?: any, finallyCallback?: any, opts?: any) {
|
||||
|
||||
let that = this
|
||||
|
||||
if (!opts) {
|
||||
opts = {}
|
||||
}
|
||||
|
||||
|
||||
that.loading = true
|
||||
|
||||
//更新react控件的状态
|
||||
that.updateUI()
|
||||
|
||||
HttpUtil.httpGet(url, params, function (response: any) {
|
||||
//有可能正常接口回来的数据也是错误的。交给错误处理器处理。
|
||||
if (that.specialErrorHandler(response)) {
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(response)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response)
|
||||
|
||||
}, function (err: any) {
|
||||
|
||||
let response = err.response
|
||||
console.error("请求出错啦", response)
|
||||
|
||||
//特殊错误情况的通用处理方式
|
||||
if (that.specialErrorHandler(response)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
that.defaultErrorHandler(response, errorCallback)
|
||||
|
||||
}, function (res: any) {
|
||||
|
||||
that.loading = false
|
||||
|
||||
//更新react控件的状态
|
||||
that.updateUI()
|
||||
|
||||
SafeUtil.safeCallback(finallyCallback)(res)
|
||||
|
||||
}, opts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 基类中的http请求会去统一处理错误情况。
|
||||
* 1.登录过期
|
||||
*
|
||||
*/
|
||||
httpPost(url: any, params = {}, successCallback?: any, errorCallback?: any, finallyCallback?: any, opts?: any) {
|
||||
|
||||
let that = this
|
||||
|
||||
if (!opts) {
|
||||
opts = {}
|
||||
}
|
||||
|
||||
that.loading = true
|
||||
|
||||
//更新react控件的状态
|
||||
that.updateUI()
|
||||
|
||||
|
||||
let formData = qs.stringify(params);
|
||||
|
||||
if (!opts["headers"]) {
|
||||
opts["headers"] = {}
|
||||
}
|
||||
opts["headers"]['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
|
||||
|
||||
HttpUtil.httpPost(url, formData, function (response: any) {
|
||||
|
||||
//有可能正常接口回来的数据也是错误的。交给错误处理器处理。
|
||||
if (that.specialErrorHandler(response)) {
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(response)
|
||||
return
|
||||
}
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response)
|
||||
|
||||
}, function (err: any) {
|
||||
|
||||
let response = err.response
|
||||
|
||||
console.error("请求出错啦", response ? response : err)
|
||||
|
||||
//特殊错误情况的通用处理方式
|
||||
if (that.specialErrorHandler(response)) {
|
||||
return
|
||||
}
|
||||
|
||||
that.defaultErrorHandler(response, errorCallback)
|
||||
|
||||
}, function (res: any) {
|
||||
|
||||
that.loading = false
|
||||
|
||||
//更新react控件的状态
|
||||
that.updateUI()
|
||||
|
||||
SafeUtil.safeCallback(finallyCallback)(res)
|
||||
|
||||
}, opts);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
import BaseEntity from './BaseEntity';
|
||||
import NumberUtil from '../../util/NumberUtil';
|
||||
import SafeUtil from '../../util/SafeUtil';
|
||||
import BrowserUtil from '../../util/BrowserUtil';
|
||||
import StringUtil from '../../util/StringUtil';
|
||||
import ObjectUtil from '../../util/ObjectUtil';
|
||||
import Filter from './filter/Filter';
|
||||
import SortFilter from './filter/SortFilter';
|
||||
import HttpBase from './HttpBase';
|
||||
|
||||
/**
|
||||
* 一个分页帮助器,可以去后台请求数据,也可以渲染需要数据源。
|
||||
* 这个类具有非常神奇的能力,可以说是整个项目含金量最高的一个类!
|
||||
*/
|
||||
export default class Pager<T> extends HttpBase {
|
||||
|
||||
static MAX_PAGE_SIZE = 500;
|
||||
|
||||
/**
|
||||
* 当前分页大小 0基
|
||||
*/
|
||||
page: number = 0;
|
||||
/**
|
||||
* 每一页的大小
|
||||
*/
|
||||
pageSize: number = 10;
|
||||
/**
|
||||
* 总的条目数量
|
||||
*/
|
||||
totalItems: number = 0;
|
||||
/**
|
||||
* 总的页数
|
||||
*/
|
||||
totalPages: number = 0;
|
||||
|
||||
/**
|
||||
* 返回的数据,类型为泛型
|
||||
*/
|
||||
data: T[] = [];
|
||||
|
||||
/**
|
||||
* 类。这个很特殊。
|
||||
*/
|
||||
Clazz: any = null;
|
||||
|
||||
/**
|
||||
* 分页的url链接地址
|
||||
*/
|
||||
urlPage: string | null = null;
|
||||
|
||||
/**
|
||||
* 过滤筛选器
|
||||
*/
|
||||
filters: Filter[] = [];
|
||||
|
||||
/**
|
||||
* 是否要求在浏览器中保存参数
|
||||
*/
|
||||
history: boolean = false;
|
||||
|
||||
|
||||
constructor(reactComponent: React.Component | null, Clazz: any, pageSize = 20) {
|
||||
|
||||
super(reactComponent);
|
||||
|
||||
this.pageSize = pageSize;
|
||||
|
||||
//这里的处理利用了js的原型调用链,比较魔法。
|
||||
if (Clazz && (Clazz.prototype instanceof BaseEntity)) {
|
||||
this.Clazz = Clazz;
|
||||
|
||||
let urlPage = Clazz.prototype.getUrlList();
|
||||
if (urlPage) {
|
||||
this.urlPage = urlPage;
|
||||
} else {
|
||||
console.error(Clazz + '必须定义分页url');
|
||||
}
|
||||
|
||||
if (Clazz.prototype.getFilters) {
|
||||
|
||||
//直接获取该类的过滤器。
|
||||
this.filters = Clazz.prototype.getFilters();
|
||||
|
||||
} else {
|
||||
|
||||
console.error('The Clazz MUST define a prototype method named \'getFilters\'');
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error('You MUST specify a Clazz extended BaseEntity');
|
||||
}
|
||||
}
|
||||
|
||||
//把obj中的属性,赋值到this中来。采用深拷贝。
|
||||
assign(obj: any) {
|
||||
|
||||
super.assign(obj);
|
||||
|
||||
this.assignList('data', this.Clazz);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//该方法是在地址栏添加上query参数,参数就是filters中的key和value.
|
||||
//同时地址栏上有的参数也会自动读取到filters中去
|
||||
//因此,启用该方法后返回时可以停留在之前的页码中。
|
||||
enableHistory() {
|
||||
this.history = true;
|
||||
|
||||
let queryPageNum: string | null = BrowserUtil.getParameterByName('page');
|
||||
let queryPageSize: string | null = BrowserUtil.getParameterByName('pageSize');
|
||||
|
||||
if (queryPageNum !== null && queryPageNum !== '') {
|
||||
this.page = parseInt(queryPageNum);
|
||||
}
|
||||
|
||||
if (queryPageSize !== null && queryPageSize !== '') {
|
||||
this.pageSize = parseInt(queryPageSize);
|
||||
}
|
||||
|
||||
if (!NumberUtil.isInteger(this.page)) {
|
||||
this.page = 0;
|
||||
}
|
||||
if (!NumberUtil.isInteger(this.pageSize)) {
|
||||
this.pageSize = 10;
|
||||
}
|
||||
|
||||
|
||||
//从请求参数中传值。
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
|
||||
let filter: Filter = this.filters[i];
|
||||
|
||||
let queryValue = BrowserUtil.getParameterByName(filter.key);
|
||||
|
||||
if (queryValue !== null && queryValue !== '') {
|
||||
|
||||
filter.putValue(queryValue);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//重置所有过滤器
|
||||
resetFilter() {
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
filter.reset();
|
||||
}
|
||||
};
|
||||
|
||||
//重置排序过滤器
|
||||
resetSortFilters() {
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
if (filter instanceof SortFilter) {
|
||||
filter.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//手动设置过滤器的值
|
||||
setFilterValue(key: string, value: any) {
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
|
||||
let filter = this.filters[i];
|
||||
|
||||
if (filter.key === key) {
|
||||
filter.putValue(value);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//根据key来删除某个Filter
|
||||
removeFilter(key: string) {
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
if (filter.key === key) {
|
||||
this.filters.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//隐藏某个Filter,实际上我们可以根据这个filter来筛选,只不过不出现在NbFilter中而已。
|
||||
showFilter(key: string, visible = true) {
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
if (filter.key === key) {
|
||||
filter.visible = visible;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
showAllFilter(visible = true) {
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
filter.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
//根据一个key来获取某个filter
|
||||
getFilter(key: string): Filter | null {
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
if (filter.key === key) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前进行sort的那个filter
|
||||
* 我们认为一次只有一个排序值
|
||||
*/
|
||||
getCurrentSortFilter(): Filter | null {
|
||||
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
if (filter instanceof SortFilter) {
|
||||
if (!filter.isEmpty()) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//获取所有的filter参数,键值对形式
|
||||
getParams(): { [s: string]: string | number } {
|
||||
|
||||
let params: { [s: string]: string | number } = {
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
};
|
||||
|
||||
if (!this.filters || !this.filters.length) {
|
||||
return params;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
let filter = this.filters[i];
|
||||
|
||||
if (!filter.isEmpty()) {
|
||||
params[filter.key] = filter.getValueString();
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
|
||||
//元素是否为空
|
||||
isEmpty(): boolean {
|
||||
return !this.data || !this.data.length;
|
||||
}
|
||||
|
||||
//去服务器端进行请求
|
||||
httpList(successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let params: { [s: string]: string | number } = this.getParams();
|
||||
|
||||
|
||||
if (this.history) {
|
||||
window.history.replaceState({}, '', window.location.pathname + '?' + ObjectUtil.param(params));
|
||||
}
|
||||
|
||||
|
||||
//准备去请求,所有错误置为空
|
||||
this.errorMessage = null;
|
||||
|
||||
this.httpGet(this.urlPage, params, function(response: any) {
|
||||
// handle success
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, function(errorMessage: any, response: any) {
|
||||
|
||||
//失败了就清空
|
||||
that.data = [];
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(errorMessage, response);
|
||||
|
||||
}, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//从pager中获取当前的分页情况,在table的分页器中显示 PaginationConfig
|
||||
getPagination(): any | false {
|
||||
let that = this;
|
||||
|
||||
if (this.totalPages > 1) {
|
||||
return {
|
||||
current: that.page + 1,
|
||||
pageSize: that.pageSize,
|
||||
total: that.totalItems,
|
||||
showTotal: (totalNum: number) => '共' + totalNum + '条',
|
||||
showSizeChanger: true,
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//获取默认的排序顺序,提供给table使用
|
||||
getDefaultSortOrder(columnKey: string): any | boolean {
|
||||
|
||||
//将变化的这个情况更新。
|
||||
let filterKey = 'order' + StringUtil.capitalize(columnKey);
|
||||
|
||||
//直接放进排序值即可,底层会自动兼容
|
||||
let sortFilter = this.getFilter(filterKey);
|
||||
|
||||
if (sortFilter && (sortFilter instanceof SortFilter)) {
|
||||
|
||||
let antdSortValue: any | null = sortFilter.getAntdValue();
|
||||
if (antdSortValue) {
|
||||
return antdSortValue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//在table的分页器 发生变化调用 TODO: PaginationConfig SorterResult TableCurrentDataSource
|
||||
tableOnChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
let that = this;
|
||||
|
||||
if (pagination.current !== undefined) {
|
||||
that.page = pagination.current - 1;
|
||||
}
|
||||
|
||||
if (pagination.pageSize !== undefined) {
|
||||
|
||||
that.pageSize = pagination.pageSize;
|
||||
}
|
||||
|
||||
|
||||
//重置所有的sort
|
||||
that.resetSortFilters();
|
||||
if (!StringUtil.isEmptyObject(sorter)) {
|
||||
|
||||
//将变化的这个情况更新。
|
||||
let filterKey = 'order' + StringUtil.capitalize(StringUtil.underScoreToCamel(sorter.field));
|
||||
|
||||
|
||||
//直接放进排序值即可,底层会自动兼容
|
||||
that.setFilterValue(filterKey, sorter.order);
|
||||
|
||||
}
|
||||
|
||||
//直接去刷新
|
||||
that.httpList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
enum SortDirection {
|
||||
//这两个是后台通用的标准
|
||||
ASC = 'ASC',
|
||||
DESC = 'DESC',
|
||||
|
||||
//这两项是antd的标准
|
||||
DESCEND = 'descend',
|
||||
ASCEND = 'ascend'
|
||||
}
|
||||
|
||||
|
||||
export default SortDirection
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
*
|
||||
* 基类。
|
||||
*
|
||||
* 这个类附带了一个视图,通过对这个类的操作可以对对应的视图进行更新等操作。
|
||||
* Castle中的类,大多数直接继承这个类。
|
||||
*/
|
||||
import Base from "./Base";
|
||||
import React from "react";
|
||||
|
||||
export default class ViewBase extends Base {
|
||||
|
||||
//该类对应的React视图。
|
||||
reactComponent: React.Component | null = null
|
||||
|
||||
//我们认为每个实体都会存放于某个react组件中,当然可以不传入。
|
||||
constructor() {
|
||||
|
||||
super()
|
||||
|
||||
}
|
||||
|
||||
|
||||
//更新某一个组件对应的UI
|
||||
static updateComponentUI(component: React.Component | null, entity?: any) {
|
||||
if (component) {
|
||||
//_version在这里是充当fake的。
|
||||
component.setState({_version: new Date().getTime()})
|
||||
} else {
|
||||
console.warn(`${entity && entity.constructor ? entity.constructor.name : '未知对象'} 的 reactComponent 不存在,无法更新其对应的UI`)
|
||||
}
|
||||
}
|
||||
|
||||
//获取ReactComponent
|
||||
getReactComponent(): React.ReactElement | null {
|
||||
|
||||
console.error(`你必须在子类 ${this.constructor.name} 中 override getReactComponent 方法`)
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
);
|
||||
}
|
||||
|
||||
//更新当前的视图
|
||||
updateUI() {
|
||||
|
||||
ViewBase.updateComponentUI(this.reactComponent, this)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import SelectionOption from "../base/option/SelectionOption";
|
||||
|
||||
|
||||
enum WebResultCode {
|
||||
SUCCESS = "SUCCESS",
|
||||
BAD_REQUEST = "BAD_REQUEST",
|
||||
LOGIN = "LOGIN",
|
||||
ERROR = "ERROR",
|
||||
}
|
||||
|
||||
let WebResultCodes: WebResultCode[] = Object.keys(WebResultCode).map(k => k as WebResultCode)
|
||||
|
||||
let WebResultCodeMap: { [key in keyof typeof WebResultCode]: SelectionOption } = {
|
||||
SUCCESS: {
|
||||
"name": "成功",
|
||||
"value": "SUCCESS",
|
||||
},
|
||||
BAD_REQUEST: {
|
||||
"name": "请求错误",
|
||||
"value": "BAD_REQUEST",
|
||||
},
|
||||
LOGIN: {
|
||||
"name": "未登录",
|
||||
"value": "LOGIN",
|
||||
},
|
||||
ERROR: {
|
||||
"name": "未知错误",
|
||||
"value": "ERROR",
|
||||
},
|
||||
}
|
||||
|
||||
let WebResultCodeList: SelectionOption[] = []
|
||||
WebResultCodes.forEach((type: WebResultCode, index: number) => {
|
||||
WebResultCodeList.push(WebResultCodeMap[type])
|
||||
})
|
||||
|
||||
|
||||
export {WebResultCode, WebResultCodes, WebResultCodeMap, WebResultCodeList}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import Filter from "./Filter";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Checkbox筛选框过滤器
|
||||
*
|
||||
*/
|
||||
export default class CheckFilter extends Filter {
|
||||
|
||||
//值,最终填写的内容。null表示这个值不设置
|
||||
value: boolean | null = null
|
||||
|
||||
constructor(name: string, code: string, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
}
|
||||
|
||||
//获取值字符串,[null,true,false] => ["","true","false"]
|
||||
getValueString(): string {
|
||||
let stringValue = ""
|
||||
if (this.value === true) {
|
||||
stringValue = "true"
|
||||
} else if (this.value === false) {
|
||||
stringValue = "false"
|
||||
} else {
|
||||
stringValue = ""
|
||||
}
|
||||
return stringValue
|
||||
}
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: any) {
|
||||
|
||||
if (value === "true" || value === true) {
|
||||
this.value = true
|
||||
} else if (value === "false" || value === false) {
|
||||
this.value = false
|
||||
} else {
|
||||
this.value = null
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.value = null
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import Filter from "./Filter";
|
||||
import DateUtil from "../../../util/DateUtil";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 时间筛选器
|
||||
*
|
||||
*/
|
||||
export default class DateFilter extends Filter {
|
||||
|
||||
//时间筛选的格式
|
||||
format: string
|
||||
|
||||
//值,最终填写的内容。null表示这个值不设置
|
||||
value: Date | null = null
|
||||
|
||||
constructor(name: string, code: string, format?: string, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
if (format) {
|
||||
this.format = format
|
||||
} else {
|
||||
this.format = DateUtil.DATE_FORMAT
|
||||
}
|
||||
}
|
||||
|
||||
//获取值字符串
|
||||
getValueString(): string {
|
||||
if (this.value) {
|
||||
return DateUtil.format(this.value, this.format)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: string) {
|
||||
|
||||
this.value = DateUtil.parse(value)
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.value = null
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import Filter from "./Filter";
|
||||
import DateUtil from "../../../util/DateUtil";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 时间筛选器
|
||||
*
|
||||
*/
|
||||
export default class DateTimeFilter extends Filter {
|
||||
|
||||
//时间筛选的格式
|
||||
format: string
|
||||
|
||||
//值,最终填写的内容。null表示这个值不设置
|
||||
value: Date | null = null
|
||||
|
||||
constructor(name: string, code: string, format?: string, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
if (format) {
|
||||
this.format = format
|
||||
} else {
|
||||
this.format = DateUtil.DEFAULT_FORMAT
|
||||
}
|
||||
}
|
||||
|
||||
//获取值字符串
|
||||
getValueString(): string {
|
||||
if (this.value) {
|
||||
return DateUtil.format(this.value, this.format)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: string) {
|
||||
|
||||
this.value = DateUtil.parse(value)
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.value = null
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 这个是列表筛选器的基类。
|
||||
*/
|
||||
export default class Filter {
|
||||
|
||||
//中文名
|
||||
name: string
|
||||
//提交时候的键值,英文
|
||||
key: string
|
||||
//是否可见
|
||||
visible: boolean = true
|
||||
|
||||
constructor(name: string, key: string, visible?: boolean) {
|
||||
this.name = name
|
||||
this.key = key
|
||||
|
||||
//没有传默认为true.
|
||||
if (visible === undefined) {
|
||||
this.visible = true
|
||||
} else {
|
||||
this.visible = visible
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到一个字符串的值
|
||||
*/
|
||||
getValueString(): string {
|
||||
console.error(`${this.constructor.name} 的 getValue 不存在,请开发者及时配置。`)
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一个字符串放置到value中去,因为有可能从请求参数中回填值
|
||||
*/
|
||||
putValue(value: string) {
|
||||
console.error(`${this.constructor.name} 的 putValue 不存在,请开发者及时配置。`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将过滤器的值清空
|
||||
*/
|
||||
reset() {
|
||||
console.error(`${this.constructor.name} 的 reset 不存在,请开发者及时配置。`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前过滤器是否为空
|
||||
*/
|
||||
isEmpty():boolean {
|
||||
console.error(`${this.constructor.name} 的 isEmpty 不存在,请开发者及时配置。`)
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import SelectionFilter from "./SelectionFilter";
|
||||
import SelectionFilterType from "./SelectionFilterType";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 从远程拉取一个筛选项的过滤器
|
||||
*
|
||||
*/
|
||||
export default class HttpSelectionFilter extends SelectionFilter {
|
||||
|
||||
//远程的访问链接。
|
||||
url: string
|
||||
|
||||
constructor(name: string, code: string, url: string, selectionType?: SelectionFilterType, visible?: boolean) {
|
||||
super(name, code, [], selectionType, visible)
|
||||
this.url = url
|
||||
}
|
||||
|
||||
//通过一个字符串来设置值
|
||||
//远程调用的值必须要宽容,因为值可能还没有从远程取回来呢。
|
||||
putValue(value: string) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
//严格的回填,采用父类的策略
|
||||
//这个方法会在http请求完成了之后调用。HttpSelectionFilter中会调用
|
||||
strictPutValue(value: string) {
|
||||
super.putValue(value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import Filter from "./Filter";
|
||||
|
||||
/**
|
||||
* 文本输入框筛选器
|
||||
* 这种一般是模糊搜索。
|
||||
*/
|
||||
export default class InputFilter extends Filter {
|
||||
|
||||
//值,最终填写的内容
|
||||
value: string = ""
|
||||
|
||||
//占位符
|
||||
placeholder: string
|
||||
|
||||
constructor(name: string, code: string, placeholder?: string | null, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
|
||||
if (placeholder) {
|
||||
this.placeholder = placeholder
|
||||
} else {
|
||||
this.placeholder = name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//获取值字符串
|
||||
getValueString(): string {
|
||||
return this.value
|
||||
}
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: string) {
|
||||
|
||||
this.value = value
|
||||
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.value = ""
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import Filter from "./Filter";
|
||||
import SelectionOption from "../option/SelectionOption";
|
||||
import SelectionFilterType from "./SelectionFilterType";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 单项选择过滤器
|
||||
*
|
||||
*/
|
||||
export default class SelectionFilter extends Filter {
|
||||
|
||||
//候选项,要求必须有name和value。
|
||||
options: SelectionOption[]
|
||||
|
||||
//值,最终填写的内容。null表示这个值不设置
|
||||
value: string = ""
|
||||
|
||||
//采用的样式
|
||||
selectionType: SelectionFilterType
|
||||
|
||||
constructor(name: string, code: string, options: SelectionOption[],selectionType?: SelectionFilterType, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
|
||||
this.options = options
|
||||
if (selectionType === undefined) {
|
||||
this.selectionType = SelectionFilterType.COMBOBOX
|
||||
} else {
|
||||
this.selectionType = selectionType
|
||||
}
|
||||
}
|
||||
|
||||
//获取值字符串
|
||||
getValueString(): string {
|
||||
return this.value
|
||||
}
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: string) {
|
||||
|
||||
if (value === "") {
|
||||
this.value = ""
|
||||
} else {
|
||||
|
||||
//必须是options中的值才接纳。
|
||||
for (let j = 0; j < this.options.length; j++) {
|
||||
let opt = this.options[j]
|
||||
if (opt.value === value) {
|
||||
this.value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.value = ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.value = ""
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 过滤器中的选择样式
|
||||
*/
|
||||
enum SelectionFilterType {
|
||||
//下拉框的样式
|
||||
COMBOBOX = "COMBOBOX",
|
||||
//按钮的样式
|
||||
BUTTON = "BUTTON",
|
||||
}
|
||||
|
||||
export default SelectionFilterType
|
||||
@@ -0,0 +1,69 @@
|
||||
import Filter from "./Filter";
|
||||
import SortDirection from "../SortDirection";
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 排序过滤器
|
||||
*
|
||||
*/
|
||||
export default class SortFilter extends Filter {
|
||||
|
||||
//值,最终填写的内容。null表示这个值不设置
|
||||
value: SortDirection | null = null
|
||||
|
||||
constructor(name: string, code: string, visible?: boolean) {
|
||||
super(name, code, visible)
|
||||
//过滤排序器,more是不显示的。
|
||||
//没有传默认为true.
|
||||
if (visible === undefined) {
|
||||
this.visible = false
|
||||
} else {
|
||||
this.visible = visible
|
||||
}
|
||||
}
|
||||
|
||||
//获取值字符串,[null,true,false] => ["","true","false"]
|
||||
getValueString(): string {
|
||||
if (this.value) {
|
||||
return this.value
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
//通过一个字符串来设置值
|
||||
putValue(value: string) {
|
||||
if (value === SortDirection.DESC || value === SortDirection.DESCEND) {
|
||||
this.value = SortDirection.DESC
|
||||
} else if (value === SortDirection.ASC || value === SortDirection.ASCEND) {
|
||||
this.value = SortDirection.ASC
|
||||
} else {
|
||||
this.value = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//获取到antd需要使用的值 TODO: SortOrder
|
||||
getAntdValue(): any | null {
|
||||
if (this.value === SortDirection.DESC || this.value === SortDirection.DESCEND) {
|
||||
return SortDirection.DESCEND
|
||||
} else if (this.value === SortDirection.ASC || this.value === SortDirection.ASCEND) {
|
||||
return SortDirection.ASCEND
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.value = null
|
||||
}
|
||||
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.value === null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 带有Code的一个selection
|
||||
*/
|
||||
import SelectionOption from "./SelectionOption";
|
||||
|
||||
export default interface CodeSelectionOption extends SelectionOption {
|
||||
code: string,
|
||||
type: string,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
enum Color {
|
||||
PRIMARY = "#108EE9",
|
||||
INFO = "#2DB7F5",
|
||||
SUCCESS = "#87D068",
|
||||
WARNING = "#FEC62E",
|
||||
DANGER = "#F50",
|
||||
}
|
||||
|
||||
export default Color
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 下拉筛选框的选项,统一使用该接口,同时这个作为枚举的复杂类型。
|
||||
*/
|
||||
import SelectionOption from "./SelectionOption";
|
||||
|
||||
export default interface ColorSelectionOption extends SelectionOption {
|
||||
index: number,
|
||||
color: string,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 包含图标和 index序列的选项,在ProteinSqlPendant中有用到
|
||||
*/
|
||||
import SelectionOption from "./SelectionOption";
|
||||
|
||||
export default interface IconIndexSelectionOption extends SelectionOption {
|
||||
index: number,
|
||||
icon: string,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 下拉筛选框的选项,统一使用该接口,同时这个作为枚举的复杂类型。
|
||||
*/
|
||||
import SelectionOption from "./SelectionOption";
|
||||
|
||||
export default interface IndexSelectionOption extends SelectionOption {
|
||||
index: number,
|
||||
name: string,
|
||||
value: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 下拉筛选框的选项,统一使用该接口,同时这个作为枚举的复杂类型。
|
||||
*/
|
||||
export default interface SelectionOption {
|
||||
name: string,
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 下拉筛选框的选项,统一使用该接口,同时这个作为枚举的复杂类型。
|
||||
*/
|
||||
import SelectionOption from "./SelectionOption";
|
||||
|
||||
export default interface StyleSelectionOption extends SelectionOption {
|
||||
style: string,
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 定义了一个图标行为的对象,这个可以辅助一些组件。
|
||||
*/
|
||||
export interface IconActionItem {
|
||||
//图片的资源链接(优先级高于iconType)
|
||||
icon?: string
|
||||
//antd的图标类型
|
||||
iconType?: string
|
||||
//名称
|
||||
name?: string
|
||||
//是否可见。默认可见
|
||||
visible?: boolean
|
||||
//点击图标后的行为
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 工具箱的条目,图标采用antd的图标。
|
||||
* 图标不带有高亮样式
|
||||
*/
|
||||
export interface ToolItem {
|
||||
|
||||
name: string
|
||||
|
||||
//当前是否处于展开状态
|
||||
active: boolean
|
||||
|
||||
//antd的图标样式
|
||||
iconType: string
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 该接口可以像文件树一样展示。
|
||||
* 同时图标带有高亮样式
|
||||
*/
|
||||
export interface SkeletonItem {
|
||||
|
||||
name: string
|
||||
|
||||
//当前是否处于展开状态
|
||||
active: boolean
|
||||
|
||||
//高亮的图标 资源链接
|
||||
activeIcon: string
|
||||
|
||||
//一般状态的图标 资源链接
|
||||
icon: string
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求的Map,键值对
|
||||
*/
|
||||
export type RequestParamMap = { [key: string]: string }
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 全局性的对象,主要和实体类相关的
|
||||
* 这个类只包含Base的子类,比如User, Preference.
|
||||
* 和Sun形成姊妹篇
|
||||
*/
|
||||
import User from "../user/User";
|
||||
|
||||
export default class Moon {
|
||||
|
||||
//全局具有唯一的用户,即当前登录的用户.
|
||||
user: User = new User()
|
||||
|
||||
//全局的一个store对象
|
||||
private static singleton: Moon | null = null
|
||||
|
||||
//使用懒加载模式。
|
||||
static getSingleton(): Moon {
|
||||
if (Moon.singleton == null) {
|
||||
Moon.singleton = new Moon();
|
||||
}
|
||||
|
||||
return Moon.singleton
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 全局性的对象,主要和整个框架相关的
|
||||
* 这个类不包含Base的子类,比如User, Preference.
|
||||
* 和Moon形成姊妹篇
|
||||
*/
|
||||
export default class Sun {
|
||||
|
||||
//全局的一个store对象
|
||||
static singleton: Sun | null = null
|
||||
|
||||
//持有全局的react-router对象,方便我们在非jsx环境中控制路由跳转
|
||||
reactRouter: any = null;
|
||||
|
||||
//由于这个类采用单例模式,因此所有属性都是独一份的。
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
//使用懒加载模式。
|
||||
static getSingleton(): Sun {
|
||||
if (Sun.singleton == null) {
|
||||
Sun.singleton = new Sun();
|
||||
}
|
||||
return Sun.singleton
|
||||
}
|
||||
|
||||
|
||||
////////////和路由相关的方法 开始////////////
|
||||
|
||||
/**
|
||||
* 跳转到某个页面去
|
||||
*/
|
||||
static navigateTo(path: any) {
|
||||
if (Sun.getSingleton().reactRouter) {
|
||||
Sun.getSingleton().reactRouter.push(path)
|
||||
} else {
|
||||
console.error("全局的 reactRouter 未定义,请检查代码!")
|
||||
}
|
||||
}
|
||||
|
||||
////////////和路由相关的方法 结束////////////
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import SafeUtil from '../../util/SafeUtil';
|
||||
import BaseEntity from '../base/BaseEntity';
|
||||
import { UserRole } from './UserRole';
|
||||
|
||||
|
||||
export default class User extends BaseEntity {
|
||||
|
||||
//获取当前登录者的信息
|
||||
static URL_INFO = '/api/user/info';
|
||||
|
||||
//用户登录
|
||||
static URL_LOGIN = '/api/user/login';
|
||||
|
||||
//用户注册
|
||||
static URL_REGISTER = '/api/user/register';
|
||||
|
||||
//退出登录
|
||||
static URL_LOGOUT = '/api/user/logout';
|
||||
|
||||
//修改密码
|
||||
static URL_CHANGE_PASSWORD = '/api/user/change/password';
|
||||
|
||||
//用户角色
|
||||
role: UserRole = UserRole.GUEST;
|
||||
//用户名
|
||||
username: string | null = null;
|
||||
//密码
|
||||
password: string | null = null;
|
||||
//头像
|
||||
avatarUrl: string | null = null;
|
||||
//上次登录ip
|
||||
lastIp: string | null = null;
|
||||
//上次登录时间
|
||||
lastTime: string | null = null;
|
||||
//状态
|
||||
status: string | null = null;
|
||||
|
||||
//是否已经登录
|
||||
isLogin: boolean = false;
|
||||
|
||||
constructor(reactComponent?: React.Component) {
|
||||
|
||||
super(reactComponent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
assign(obj: any) {
|
||||
super.assign(obj);
|
||||
|
||||
}
|
||||
|
||||
getForm(): any {
|
||||
return {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
uuid: this.uuid ? this.uuid : null,
|
||||
};
|
||||
}
|
||||
|
||||
//登录
|
||||
httpInfo(successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let form = {};
|
||||
|
||||
this.httpGet(User.URL_INFO, form, function(response: any) {
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//登录
|
||||
httpLogin(username: string, password: string, successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let form = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
|
||||
this.httpGet(User.URL_LOGIN, form, function(response: any) {
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//注册
|
||||
httpRegister(username: string, password: string, successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let form = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
|
||||
this.httpGet(User.URL_REGISTER, form, function(response: any) {
|
||||
|
||||
that.assign(response.data.data);
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//注册
|
||||
httpLogout(successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let form = {};
|
||||
|
||||
this.httpGet(User.URL_LOGOUT, form, function(response: any) {
|
||||
|
||||
console.info('退出成功!');
|
||||
|
||||
that.role = UserRole.GUEST;
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//修改密码
|
||||
httpChangePassword(oldPassword: string, newPassword: string, successCallback?: any, errorCallback?: any, finalCallback?: any) {
|
||||
|
||||
let that = this;
|
||||
|
||||
let form = {
|
||||
oldPassword,
|
||||
newPassword,
|
||||
};
|
||||
|
||||
this.httpPost(User.URL_CHANGE_PASSWORD, form, function(response: any) {
|
||||
|
||||
console.info('修改密码成功!');
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response);
|
||||
|
||||
}, errorCallback, finalCallback);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import SelectionOption from '../base/option/SelectionOption';
|
||||
|
||||
|
||||
enum UserRole {
|
||||
GUEST = 'GUEST',
|
||||
USER = 'USER',
|
||||
ADMINISTRATOR = 'ADMINISTRATOR',
|
||||
}
|
||||
|
||||
let UserRoles: UserRole[] = Object.keys(UserRole).map(k => k as UserRole);
|
||||
|
||||
let UserRoleMap: { [key in keyof typeof UserRole]: SelectionOption } = {
|
||||
GUEST: {
|
||||
'name': '游客',
|
||||
'value': 'GUEST',
|
||||
},
|
||||
USER: {
|
||||
'name': '普通用户',
|
||||
'value': 'USER',
|
||||
},
|
||||
ADMINISTRATOR: {
|
||||
'name': '管理员',
|
||||
'value': 'ADMINISTRATOR',
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
let UserRoleList: SelectionOption[] = [];
|
||||
UserRoles.forEach((type: UserRole, index: number) => {
|
||||
UserRoleList.push(UserRoleMap[type]);
|
||||
});
|
||||
|
||||
|
||||
export { UserRole, UserRoles, UserRoleMap, UserRoleList };
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 定义了一个图标行为的对象,这个可以辅助一些组件。
|
||||
*/
|
||||
export interface IconActionItem {
|
||||
//图片的资源链接(优先级高于iconType)
|
||||
icon?: string
|
||||
//antd的图标类型
|
||||
iconType?: string
|
||||
//名称
|
||||
name?: string
|
||||
//是否可见。默认可见
|
||||
visible?: boolean
|
||||
//点击图标后的行为
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 工具箱的条目,图标采用antd的图标。
|
||||
* 图标不带有高亮样式
|
||||
*/
|
||||
export interface ToolItem {
|
||||
|
||||
name: string
|
||||
|
||||
//当前是否处于展开状态
|
||||
active: boolean
|
||||
|
||||
//antd的图标样式
|
||||
iconType: string
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 该接口可以像文件树一样展示。
|
||||
* 同时图标带有高亮样式
|
||||
*/
|
||||
export interface SkeletonItem {
|
||||
|
||||
name: string
|
||||
|
||||
//当前是否处于展开状态
|
||||
active: boolean
|
||||
|
||||
//高亮的图标 资源链接
|
||||
activeIcon: string
|
||||
|
||||
//一般状态的图标 资源链接
|
||||
icon: string
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求的Map,键值对
|
||||
*/
|
||||
export type RequestParamMap = { [key: string]: string }
|
||||
@@ -0,0 +1,75 @@
|
||||
import SafeUtil from "./SafeUtil";
|
||||
|
||||
export default class AnimateUtil {
|
||||
|
||||
/**
|
||||
* 动画函数
|
||||
* @param startValue 开始的值
|
||||
* @param endValue 结束的值
|
||||
* @param duration 动画持续时间,单位ms 如果开始值和结束值一样,那么会提前结束。
|
||||
* @param processHandler 过程中处理的回调函数. 该函数能保证第一个值是startValue,结束的值是endValue
|
||||
* @param startHandler 开始的回调函数
|
||||
* @param endHandler 结束的回调函数
|
||||
* @param standard 每隔多少毫秒进行一次处理。
|
||||
*
|
||||
*/
|
||||
static animate(
|
||||
startValue: number,
|
||||
endValue: number,
|
||||
duration: number,
|
||||
processHandler: (value: number) => void,
|
||||
startHandler?: (value: number) => void,
|
||||
endHandler?: (value: number) => void,
|
||||
standard?: number
|
||||
): void {
|
||||
|
||||
|
||||
//规则校验。
|
||||
if (duration <= 0) {
|
||||
console.error("duration值只能是正整数")
|
||||
return
|
||||
}
|
||||
|
||||
if (standard == undefined) {
|
||||
standard = 20
|
||||
} else if (standard <= 0) {
|
||||
console.error("standard值只能是正整数")
|
||||
return
|
||||
}
|
||||
|
||||
let totalStep: number = endValue - startValue
|
||||
|
||||
//从startValue到endValue需要触发几次。
|
||||
let num = Math.ceil(duration / standard)
|
||||
|
||||
//每一步的长度
|
||||
let delta: number = totalStep
|
||||
if (num != 0) {
|
||||
delta = Math.ceil(totalStep / num)
|
||||
}
|
||||
|
||||
//定时执行
|
||||
let sum = 0;
|
||||
SafeUtil.safeCallback(startHandler)(startValue)
|
||||
processHandler(startValue)
|
||||
let intervalHandler = setInterval(() => {
|
||||
|
||||
sum = sum + delta
|
||||
if (Math.abs(sum) >= Math.abs(totalStep)) {
|
||||
//该停止了
|
||||
processHandler(endValue)
|
||||
SafeUtil.safeCallback(endHandler)(endValue)
|
||||
|
||||
//关闭定时器。
|
||||
clearInterval(intervalHandler)
|
||||
|
||||
} else {
|
||||
//更新当前进度
|
||||
processHandler(startValue + sum)
|
||||
}
|
||||
|
||||
}, standard)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import DateUtil from "./DateUtil";
|
||||
|
||||
/**
|
||||
* 这个类是专门为antd服务的
|
||||
*/
|
||||
export default class AntdUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 从一个数组中,获取到antd table想要的那种dataSource格式
|
||||
*/
|
||||
static getDataSource(columns :any, data :any) {
|
||||
|
||||
let dataSource :any= []
|
||||
if (!((columns instanceof Array) && (data instanceof Array))) {
|
||||
|
||||
console.error("columns和data必须为数组")
|
||||
return dataSource
|
||||
}
|
||||
|
||||
data.forEach((item, index) => {
|
||||
|
||||
let obj :any = {key: index, id: item.id}
|
||||
|
||||
|
||||
columns.forEach((column, i) => {
|
||||
|
||||
let value = item[column.key]
|
||||
|
||||
//遇到时间自动转成 yyyy-MM-dd HH:mm:ss
|
||||
if (value instanceof Date) {
|
||||
value = DateUtil.simpleDateTime(value)
|
||||
}
|
||||
|
||||
obj[column.key] = value
|
||||
})
|
||||
|
||||
dataSource.push(obj)
|
||||
})
|
||||
|
||||
|
||||
return dataSource
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
export default class ArrayUtil {
|
||||
|
||||
|
||||
//从srcArray中减去childArray的元素
|
||||
static substract(srcArray: [], childArray: any) {
|
||||
if (!srcArray) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!childArray || childArray.length === 0) {
|
||||
return srcArray;
|
||||
}
|
||||
|
||||
let newArray: [] = []
|
||||
|
||||
for (let i = 0; i < srcArray.length; i++) {
|
||||
|
||||
//在childArray的那些元素就不要了。
|
||||
if (childArray.indexOf(srcArray[i]) === -1) {
|
||||
newArray.push(srcArray[i])
|
||||
}
|
||||
|
||||
}
|
||||
return newArray
|
||||
|
||||
}
|
||||
|
||||
//不重复添加
|
||||
static uniqueAdd(srcArray: any, childArray: any) {
|
||||
if (!srcArray) {
|
||||
srcArray = []
|
||||
}
|
||||
|
||||
if (!childArray || childArray.length === 0) {
|
||||
return srcArray;
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < childArray.length; i++) {
|
||||
|
||||
//在childArray的那些元素就不要了。
|
||||
if (srcArray.indexOf(childArray[i]) === -1) {
|
||||
srcArray.push(childArray[i])
|
||||
}
|
||||
|
||||
}
|
||||
return srcArray
|
||||
}
|
||||
|
||||
//不重复添加
|
||||
static uniqueAddOne(srcArray: any[], child: any) {
|
||||
if (!srcArray) {
|
||||
srcArray = []
|
||||
}
|
||||
|
||||
if (child === undefined || child === null) {
|
||||
return srcArray;
|
||||
}
|
||||
|
||||
//在childArray的那些元素就不要了。
|
||||
if (srcArray.indexOf(child) === -1) {
|
||||
srcArray.push(child)
|
||||
}
|
||||
|
||||
return srcArray
|
||||
}
|
||||
|
||||
//数组中查找特定元素并返回所有该元素的索引
|
||||
static findAll(a: any, x: any) {
|
||||
let results: number[] = [],
|
||||
len = a.length,
|
||||
pos: number = 0;
|
||||
while (pos < len) {
|
||||
pos = a.indexOf(x, pos);
|
||||
if (pos === -1) {//未找到就退出循环完成搜索
|
||||
break;
|
||||
}
|
||||
results.push(pos);//找到就存储索引
|
||||
pos += 1;//并从下个位置开始搜索
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//调整元素位置。将dragIndex的元素放到hoverIndex的前面,其余顺移。在拖拽排序中用到。
|
||||
static insertSort(arr: any, dragIndex: any, hoverIndex: any) {
|
||||
|
||||
|
||||
if (!(arr instanceof Array)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dragIndex < 0 || dragIndex >= arr.length) {
|
||||
console.error("dragIndex = " + dragIndex + " index越界 length = " + arr.length)
|
||||
return
|
||||
}
|
||||
|
||||
if (hoverIndex < 0 || hoverIndex >= arr.length) {
|
||||
console.error("hoverIndex = " + hoverIndex + " index越界 length = " + arr.length)
|
||||
return
|
||||
}
|
||||
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dragIndex > hoverIndex) {
|
||||
|
||||
let temp = arr[dragIndex]
|
||||
|
||||
for (let i = dragIndex; i >= hoverIndex + 1; i--) {
|
||||
arr[i] = arr[i - 1]
|
||||
}
|
||||
arr[hoverIndex] = temp
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
let temp = arr[dragIndex]
|
||||
|
||||
for (let i = dragIndex; i <= hoverIndex - 1; i++) {
|
||||
arr[i] = arr[i + 1]
|
||||
}
|
||||
arr[hoverIndex] = temp
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//数组去重
|
||||
static unique(arr: string[]) {
|
||||
let newArr: string[] = [];
|
||||
for (let i = 0, item; item = arr[i++];) {
|
||||
if (newArr.indexOf(item) === -1) {
|
||||
newArr.push(item);
|
||||
}
|
||||
}
|
||||
return newArr;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
export default class BrowserUtil {
|
||||
|
||||
//根据cookie键,读取cookie值
|
||||
static readCookie(name: any) {
|
||||
let nameEQ = name + "=";
|
||||
let ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 获取url地址栏的参数
|
||||
* // query string: ?foo=lorem&bar=&baz
|
||||
* var foo = getParameterByName('foo'); // "lorem"
|
||||
* var bar = getParameterByName('bar'); // "" (present with empty value)
|
||||
* var baz = getParameterByName('baz'); // "" (present with no value)
|
||||
* var qux = getParameterByName('qux'); // null (absent)
|
||||
* @param name
|
||||
* @param url
|
||||
* @returns {*}
|
||||
*/
|
||||
static getParameterByName(name: string, url?: string): string | null {
|
||||
|
||||
if (!url) {
|
||||
url = window.location.href
|
||||
}
|
||||
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
|
||||
let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)")
|
||||
let results = regex.exec(url);
|
||||
|
||||
if (!results) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!results[2]) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 半颜的方法。注释待补充
|
||||
* @param name
|
||||
* @returns {*}
|
||||
*/
|
||||
static getQueryString(name: any) {
|
||||
let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
let r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return unescape(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static isLocalStorageNameSupported() {
|
||||
let testKey = 'test';
|
||||
let storage = window.localStorage;
|
||||
try {
|
||||
storage.setItem(testKey, '1');
|
||||
storage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static readLocalStorage(key: any) {
|
||||
if (BrowserUtil.isLocalStorageNameSupported()) {
|
||||
return window.localStorage[key];
|
||||
} else {
|
||||
console.error("not support localStorage.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static saveToLocalStorage(key: any, content: any) {
|
||||
if (BrowserUtil.isLocalStorageNameSupported()) {
|
||||
window.localStorage[key] = content;
|
||||
} else {
|
||||
console.error("not support localStorage.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static removeLocalStorage(key: any) {
|
||||
if (BrowserUtil.isLocalStorageNameSupported()) {
|
||||
window.localStorage.removeItem(key);
|
||||
} else {
|
||||
console.error("not support localStorage.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取完整的host
|
||||
* eg:
|
||||
* https://bamboo.eyeblue.cn
|
||||
*/
|
||||
static fullHost() {
|
||||
return window.location.protocol + "//" + window.location.host
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import SafeUtil from "./SafeUtil";
|
||||
|
||||
export default class ClipboardUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 复制一段文字到粘贴板。
|
||||
*
|
||||
*/
|
||||
static copy(text: string, successCallback: () => void, errorCallback?: () => void) {
|
||||
|
||||
let className = "clipboard-textarea-util"
|
||||
|
||||
let textAreaElement: HTMLTextAreaElement | null = document.querySelector("." + className);
|
||||
|
||||
if (!textAreaElement) {
|
||||
textAreaElement = document.createElement("textarea")
|
||||
textAreaElement.className = className
|
||||
textAreaElement.style.cssText = "position:fixed;opacity:0;z-index:0;width:5px;height:5px;"
|
||||
document.body.appendChild(textAreaElement)
|
||||
}
|
||||
|
||||
textAreaElement.value = text
|
||||
textAreaElement.select();
|
||||
|
||||
try {
|
||||
let successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
successCallback()
|
||||
} else {
|
||||
SafeUtil.safeCallback(errorCallback)()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('复制失败', err);
|
||||
SafeUtil.safeCallback(errorCallback)()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import moment from "moment"
|
||||
|
||||
export default class DateUtil {
|
||||
|
||||
static DEFAULT_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||
static SLASH_DATE_FORMAT = "YYYY/MM/DD";
|
||||
static TIME_FORMAT = "HH:mm:ss";
|
||||
static DATE_FORMAT = "YYYY-MM-DD";
|
||||
//紧凑型的时间格式
|
||||
static COMPACT_DATE_FORMAT = "YYYYMMDD";
|
||||
|
||||
static simpleDateTime(date: Date | null): string {
|
||||
if (date == null) {
|
||||
return ""
|
||||
} else {
|
||||
return moment(date).format(DateUtil.DEFAULT_FORMAT)
|
||||
}
|
||||
}
|
||||
|
||||
static simpleDate(date: Date | null): string {
|
||||
|
||||
if (date == null) {
|
||||
return ""
|
||||
} else {
|
||||
return moment(date).format(DateUtil.DATE_FORMAT)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 某个日期的前一天
|
||||
*/
|
||||
static lastDay(date: Date | null, format?: string): string {
|
||||
|
||||
if (format === undefined) {
|
||||
format = DateUtil.DEFAULT_FORMAT
|
||||
}
|
||||
|
||||
if (date == null) {
|
||||
return ""
|
||||
} else {
|
||||
return moment(date).add(-1, 'days').format(format)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按照指定格式进行格式化
|
||||
*/
|
||||
static format(date: Date | null, formatString: string): string {
|
||||
if (date == null) {
|
||||
return ""
|
||||
} else {
|
||||
return moment(date).format(formatString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将字符串,按照指定的格式反序列化成为时间对象
|
||||
* @param str
|
||||
*/
|
||||
static parse(str: string): Date | null {
|
||||
|
||||
let valid = moment(str).isValid();
|
||||
if (valid) {
|
||||
return moment(str).toDate()
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//将时间字符串转化成js date
|
||||
//deprecated,使用 parse方法替代。
|
||||
static str2Date(str: any): Date {
|
||||
|
||||
let valid = moment(str).isValid();
|
||||
if (valid) {
|
||||
return moment(str).toDate()
|
||||
} else {
|
||||
//console.warn("不能转换成时间对象:", str)
|
||||
return new Date()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default class DomUtil {
|
||||
|
||||
static addEvent(el: Node, event: string, handler: (evt: Event) => void): void {
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener(event, handler, true);
|
||||
} else {
|
||||
console.error("不支持事件监听!", el)
|
||||
}
|
||||
}
|
||||
|
||||
static removeEvent(el: Node, event: string, handler: (evt: Event) => void): void {
|
||||
|
||||
if (el.removeEventListener) {
|
||||
el.removeEventListener(event, handler, true);
|
||||
} else {
|
||||
console.error("不支持事件监听!", el)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import axios, {AxiosRequestConfig} from "axios";
|
||||
import SafeUtil from "./SafeUtil";
|
||||
|
||||
|
||||
//http请求全部收口在这个工具类中,可以快速切换http框架。
|
||||
export default class HttpUtil {
|
||||
|
||||
|
||||
static httpGet(url: any, params = {}, successCallback?: any, errorCallback?: any, finallyCallback?: any, opts?: any) {
|
||||
|
||||
axios
|
||||
.get(url, {
|
||||
params: params
|
||||
})
|
||||
.then(function (response) {
|
||||
SafeUtil.safeCallback(successCallback)(response)
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(error)
|
||||
|
||||
})
|
||||
.then(function (res) {
|
||||
|
||||
SafeUtil.safeCallback(finallyCallback)(res)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static httpPost(url: any, params = {}, successCallback?: any, errorCallback?: any, finallyCallback?: any, opts?: any) {
|
||||
|
||||
|
||||
axios
|
||||
.post(url, params, opts)
|
||||
.then(function (response) {
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response)
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(error)
|
||||
|
||||
})
|
||||
.then(function (res) {
|
||||
|
||||
SafeUtil.safeCallback(finallyCallback)(res)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件的请求
|
||||
*/
|
||||
static httpPostFile(url: string,
|
||||
formData: FormData,
|
||||
successCallback?: any,
|
||||
errorCallback?: any,
|
||||
finallyCallback?: any,
|
||||
processCallback?: (progressEvent: any) => void,
|
||||
opts?: any) {
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: processCallback
|
||||
}
|
||||
|
||||
axios
|
||||
.post(url, formData, config)
|
||||
.then(function (response) {
|
||||
|
||||
SafeUtil.safeCallback(successCallback)(response)
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
||||
SafeUtil.safeCallback(errorCallback)(error)
|
||||
|
||||
})
|
||||
.then(function (res) {
|
||||
|
||||
SafeUtil.safeCallback(finallyCallback)(res)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
export default class JsonUtil {
|
||||
|
||||
|
||||
//将一个json字符串转换成 json 数组
|
||||
static parseList(str: any) {
|
||||
if (!str) {
|
||||
return []
|
||||
}
|
||||
if (str instanceof Array) {
|
||||
return str;
|
||||
}
|
||||
try {
|
||||
let list = JSON.parse(str);
|
||||
if (list instanceof Array) {
|
||||
return list;
|
||||
} else {
|
||||
console.error("不能将" + str + "转换成数组");
|
||||
return [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("不能将" + str + "转换成JSON");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//将一个字符串转成js对象
|
||||
static toObj(str: any) {
|
||||
|
||||
if (!str) {
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (e) {
|
||||
console.error("不能将json字符串" + str + "转换成对象");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
//将一个对象转成json字符串
|
||||
static toJson(obj: any): string {
|
||||
|
||||
if (!obj) {
|
||||
obj = {}
|
||||
}
|
||||
|
||||
return JSON.stringify(obj)
|
||||
}
|
||||
|
||||
//将JSON进行格式化。
|
||||
static prettyJson(json: any) {
|
||||
|
||||
if (typeof json === 'string') {
|
||||
json = JSON.parse(json);
|
||||
}
|
||||
|
||||
return JSON.stringify(json, null, 2)
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {message} from 'antd';
|
||||
|
||||
/**
|
||||
* 消息弹出框,统一进行收口
|
||||
*/
|
||||
export default class MessageBoxUtil {
|
||||
|
||||
static success(content: string) {
|
||||
message.success(content)
|
||||
}
|
||||
|
||||
static info(content: string) {
|
||||
message.info(content)
|
||||
}
|
||||
|
||||
static error(content: string) {
|
||||
message.error(content)
|
||||
}
|
||||
|
||||
static warn(content: string) {
|
||||
message.warn(content)
|
||||
}
|
||||
|
||||
static warning(content: string) {
|
||||
message.warning(content)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default class NumberUtil {
|
||||
|
||||
|
||||
static isInteger(obj: any) {
|
||||
return typeof obj === 'number' && obj % 1 === 0
|
||||
}
|
||||
|
||||
//转换成整型
|
||||
static parseInt(obj: any): number {
|
||||
|
||||
try {
|
||||
return parseInt(obj)
|
||||
} catch (e) {
|
||||
console.error("无法转换成整数", obj)
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import DateUtil from "./DateUtil";
|
||||
import JsonUtil from "./JsonUtil";
|
||||
|
||||
export default class ObjectUtil {
|
||||
//将 extraObj 中的属性全部赋值给standardObj
|
||||
static extend(standardObj: any, extraObj: any) {
|
||||
for (let key in extraObj) {
|
||||
if (extraObj.hasOwnProperty(key)) {
|
||||
if (standardObj.hasOwnProperty(key)) {
|
||||
standardObj[key] = extraObj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深拷贝,保留standardObj第一层全部属性
|
||||
static deepExtend(standardObj: any, extraObj: any, checkKey: boolean = true) {
|
||||
for (let key in extraObj) {
|
||||
if (checkKey) {
|
||||
if (standardObj.hasOwnProperty(key)) {
|
||||
standardObj[key] =
|
||||
extraObj[key] instanceof Object
|
||||
? ObjectUtil.deepExtend({}, extraObj[key], false)
|
||||
: extraObj[key];
|
||||
}
|
||||
} else {
|
||||
standardObj[key] =
|
||||
extraObj[key] instanceof Object
|
||||
? ObjectUtil.deepExtend({}, extraObj[key], false)
|
||||
: extraObj[key];
|
||||
}
|
||||
}
|
||||
return standardObj;
|
||||
}
|
||||
|
||||
//将standardObj的field属性规整成时间格式。
|
||||
static assignDate(standardObj: any, field: any) {
|
||||
if (standardObj.hasOwnProperty(field)) {
|
||||
standardObj[field] = DateUtil.str2Date(standardObj[field]);
|
||||
}
|
||||
}
|
||||
|
||||
//吧standardObj的field属性归整成Clazz类型
|
||||
static assignList(standardObj: any, field: any, Clazz: any) {
|
||||
if (standardObj.hasOwnProperty(field)) {
|
||||
//如果我们要转换成字符串的数组形式,那么this[field]应该是一个字符串才对。
|
||||
if (Clazz === String) {
|
||||
standardObj[field] = JsonUtil.parseList(standardObj[field]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//下面就是转换实体数组了。
|
||||
let beans = standardObj[field];
|
||||
if (!beans) {
|
||||
//服务器返回这个字段为空 维持构造函数中的默认值(一般而言是一个[])
|
||||
standardObj[field] = new standardObj.constructor()[field];
|
||||
return;
|
||||
}
|
||||
|
||||
standardObj[field] = [];
|
||||
|
||||
if (!Clazz) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < beans.length; i++) {
|
||||
let bean = beans[i];
|
||||
let clazz = new Clazz();
|
||||
|
||||
if (clazz.assign) {
|
||||
clazz.assign(bean);
|
||||
} else {
|
||||
console.error(clazz);
|
||||
console.error("没有定义assign方法");
|
||||
}
|
||||
|
||||
standardObj[field].push(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check whether an obj is empty
|
||||
static isEmptyObject(obj: object) {
|
||||
if (typeof obj !== "object") {
|
||||
console.error("判定的不是obj对象");
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键值对转换成 age=18&name=xxx 的形式
|
||||
*/
|
||||
static param(map: any) {
|
||||
let arr: string[] = [];
|
||||
for (let key in map) {
|
||||
if (map.hasOwnProperty(key)) {
|
||||
let value = map[key];
|
||||
arr[arr.length] =
|
||||
encodeURIComponent(key) +
|
||||
"=" +
|
||||
encodeURIComponent(value == null ? "" : value);
|
||||
}
|
||||
}
|
||||
|
||||
return arr.join("&");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export default class SafeUtil {
|
||||
|
||||
//安全的调用某个函数,函数不存在,创建一个空函数。
|
||||
static safeCallback(callback: any) {
|
||||
if (typeof callback === "function") {
|
||||
return callback
|
||||
} else {
|
||||
return function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//空函数
|
||||
static noop = () => {
|
||||
};
|
||||
|
||||
|
||||
//停止事件冒泡
|
||||
static stopPropagation(e: any) {
|
||||
if (!e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.stopPropagation) {
|
||||
//系统的点击事件
|
||||
e.stopPropagation()
|
||||
} else if (e.domEvent && e.domEvent.stopPropagation) {
|
||||
//antd的事件
|
||||
e.domEvent.stopPropagation()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
export default class StringUtil {
|
||||
|
||||
//大写字母
|
||||
static UPPER_CASES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
//下划线转驼峰
|
||||
static underScoreToCamel(str: string) {
|
||||
|
||||
if (!str) {
|
||||
console.error('不能转换空的驼峰字符串。')
|
||||
return str
|
||||
}
|
||||
|
||||
const regex = /_[a-z]/gm;
|
||||
|
||||
return str.replace(regex, function (letter: any, index: any) {
|
||||
console.log("letter", letter)
|
||||
return letter.substr(1).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
//转换成首字母小写的驼峰法
|
||||
static lowerCamel(str: any) {
|
||||
|
||||
if (!str) {
|
||||
console.error('不能转换空的驼峰字符串。')
|
||||
return str
|
||||
}
|
||||
|
||||
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function (letter: any, index: any) {
|
||||
return index === 0 ? letter.toLowerCase() : letter.toUpperCase()
|
||||
}).replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
|
||||
//转换成全部小写的使用 /分隔的字符串
|
||||
static lowerSlash(str: any) {
|
||||
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function (letter: any, index: any) {
|
||||
return '/' + letter.toLowerCase()
|
||||
}).replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
|
||||
//将首字母大写
|
||||
static capitalize(str: any) {
|
||||
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.replace(/^\w/, (c: any) => c.toUpperCase());
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
//将首字母小写
|
||||
static lower(str: any) {
|
||||
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.replace(/^\w/, (c: any) => c.toLowerCase());
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
//获取一个function的名字
|
||||
static functionName(func: any) {
|
||||
// Match:
|
||||
// - ^ the beginning of the string
|
||||
// - function the word 'function'
|
||||
// - \s+ at least some white space
|
||||
// - ([\w\$]+) capture one or more valid JavaScript identifier characters
|
||||
// - \s* optionally followed by white space (in theory there won't be any here,
|
||||
// so if performance is an issue this can be omitted[1]
|
||||
// - \( followed by an opening brace
|
||||
//
|
||||
let result = /^function\s+([\w\$]+)\s*\(/.exec(func.toString())
|
||||
|
||||
return result ? result[1] : '' // for an anonymous function there won't be a match
|
||||
}
|
||||
|
||||
//check whether an obj is empty
|
||||
static isEmptyObject(obj: any) {
|
||||
|
||||
if (!obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取到上num级路径
|
||||
* 例子: /bamboo/setting/notification -> /bamboo/setting
|
||||
* @param path 原路径
|
||||
* @param num 上几级,默认1级
|
||||
*/
|
||||
static prePath(path: string, num: number = 1): string {
|
||||
if (!path) {
|
||||
return path
|
||||
}
|
||||
|
||||
//去除掉最后的/符号。
|
||||
let parts: string[] = path.split("/");
|
||||
//去除所有的空
|
||||
parts = parts.filter((item: string, index: number) => {
|
||||
return item !== ""
|
||||
})
|
||||
|
||||
//前面几层就是删掉前面几个元素。如果超出了,会全部删掉。
|
||||
parts.splice(parts.length - num)
|
||||
|
||||
return "/" + parts.join("/")
|
||||
}
|
||||
|
||||
|
||||
static startWith(str: any, prefix: any) {
|
||||
if (typeof prefix === 'undefined' || prefix === null || prefix === '' || typeof str === 'undefined' || str === null || str.length === 0 || prefix.length > str.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return str.substr(0, prefix.length) === prefix
|
||||
}
|
||||
|
||||
static endWith(str: any, suffix: any) {
|
||||
if (suffix === null || suffix === '' || str === null || str.length === 0 || suffix.length > str.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return str.substring(str.length - suffix.length) === suffix
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 去除掉开头的前缀
|
||||
* @param str 待处理的字符串
|
||||
* @param prefix 前缀
|
||||
*/
|
||||
static trimPrefix(str: string | null, prefix: string): string {
|
||||
|
||||
if (!str) {
|
||||
return ""
|
||||
} else {
|
||||
if (str.substr(0, prefix.length) === prefix) {
|
||||
return str.substr(prefix.length)
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除掉后缀
|
||||
* @param str 待处理的字符串
|
||||
* @param suffix 前缀
|
||||
*/
|
||||
static trimSuffix(str: string | null, suffix: string): string {
|
||||
|
||||
if (!str) {
|
||||
return ""
|
||||
} else {
|
||||
if (str.substring(str.length - suffix.length) === suffix) {
|
||||
return str.substr(0, str.length - suffix.length)
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//在字符串a后面追加字符串b
|
||||
static append(a: any, b: any, separator = "") {
|
||||
|
||||
if (a === null || a === "" || typeof a !== "string") {
|
||||
return b;
|
||||
} else {
|
||||
return a + separator + b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static isBlank(text: string | null | undefined): boolean {
|
||||
|
||||
if (text === null || text == undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
return text.trim() === "";
|
||||
|
||||
}
|
||||
|
||||
static isNotBlank(text: string | null | undefined): boolean {
|
||||
|
||||
return !StringUtil.isBlank(text)
|
||||
|
||||
}
|
||||
|
||||
|
||||
//将时间戳转换成62进制
|
||||
static generateUniqueCode(num?: number): string {
|
||||
//获取时间戳
|
||||
|
||||
if (num === undefined) {
|
||||
num = new Date().getTime()
|
||||
}
|
||||
|
||||
let standardString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let result;
|
||||
let list: number[] = [];
|
||||
let len = standardString.length;
|
||||
let level;
|
||||
for (level = 0; Math.floor(num / len) > 0; level++) {
|
||||
result = num % len;
|
||||
list.push(result)
|
||||
num = (num - result) / len
|
||||
}
|
||||
list.push(num);
|
||||
let code = '';
|
||||
list.forEach((item) => {
|
||||
code = standardString[item] + code;
|
||||
})
|
||||
return code
|
||||
}
|
||||
|
||||
//字符串转换成十进制
|
||||
static parseUniqueCode(str: string): number {
|
||||
|
||||
let exchangeString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let strNew = str.split("");
|
||||
let list: number[] = [];
|
||||
strNew.map((item: string) => {
|
||||
list.push(exchangeString.indexOf(item));
|
||||
});
|
||||
let num = 0;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
num += list[i] * Math.pow(exchangeString.length, list.length - i - 1);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
.pages-frame {
|
||||
|
||||
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
|
||||
|
||||
.ant-layout {
|
||||
|
||||
@header-height: 50px;
|
||||
height: 100%;
|
||||
|
||||
.ant-layout-sider {
|
||||
.username {
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
|
||||
padding: 0 20px;
|
||||
line-height: @header-height;
|
||||
height: @header-height;
|
||||
background: white;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.logo-title-area {
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.header-logo {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.ant-layout-content {
|
||||
|
||||
padding: 0;
|
||||
|
||||
.pages-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ant-layout-footer {
|
||||
|
||||
background: white;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 10px;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import React from 'react';
|
||||
import { Link, Redirect, Route, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import './Frame.less';
|
||||
import BambooComponent from '../common/component/BambooComponent';
|
||||
import UserLogin from './user/Login';
|
||||
|
||||
import UserProfile from './user/Profile';
|
||||
import ArticleDetail from './article/Detail';
|
||||
import ArticleList from './article/List';
|
||||
import ArticleEdit from './article/Edit';
|
||||
|
||||
import { Layout, Menu } from 'antd';
|
||||
import MenuManager from '../common/menu/MenuManager';
|
||||
import MenuItem from '../common/menu/MenuItem';
|
||||
import { SelectParam } from 'antd/lib/menu';
|
||||
import LogoSvg from '../assets/image/logo.png';
|
||||
import Index from './index/Index';
|
||||
import User from '../common/model/user/User';
|
||||
import Moon from '../common/model/global/Moon';
|
||||
import Sun from '../common/model/global/Sun';
|
||||
import { UserRole } from '../common/model/user/UserRole';
|
||||
|
||||
const { Header, Content, Footer, Sider } = Layout;
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
|
||||
interface IProps extends RouteComponentProps<{}> {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
collapsed: boolean
|
||||
|
||||
}
|
||||
|
||||
|
||||
class RawFrame extends BambooComponent<IProps, IState> {
|
||||
|
||||
user: User = Moon.getSingleton().user;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
|
||||
this.state = {
|
||||
collapsed: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
//装载全局的路由
|
||||
Sun.getSingleton().reactRouter = this.props.history;
|
||||
|
||||
this.fetchInfo();
|
||||
|
||||
}
|
||||
|
||||
//获取当前登录者的信息
|
||||
fetchInfo() {
|
||||
let that = this;
|
||||
|
||||
let whitePaths = ['/user/login', '/user/register'];
|
||||
//如果当前本身是登录界面,那么不去获取。
|
||||
if (whitePaths.indexOf(this.props.location.pathname) == -1) {
|
||||
this.user.httpInfo(function() {
|
||||
that.updateUI();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
onCollapse(collapsed: boolean) {
|
||||
console.log(collapsed);
|
||||
this.setState({ collapsed });
|
||||
};
|
||||
|
||||
onSelect(param: SelectParam) {
|
||||
let that = this;
|
||||
|
||||
let menuManager: MenuManager = MenuManager.getSingleton();
|
||||
menuManager.selectMenu(param.key);
|
||||
|
||||
//打到对应的页面中。
|
||||
if (param.key == '/user/logout') {
|
||||
this.props.history.push('/user/login');
|
||||
} else {
|
||||
this.props.history.push(param.key);
|
||||
}
|
||||
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
goHome() {
|
||||
this.props.history.push('/');
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this;
|
||||
|
||||
let menuManager: MenuManager = MenuManager.getSingleton();
|
||||
|
||||
let menuItems: MenuItem[] = menuManager.getMenuItems();
|
||||
|
||||
let user: User = Moon.getSingleton().user;
|
||||
|
||||
return (
|
||||
|
||||
<div className="pages-frame">
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider>
|
||||
<div className="username">
|
||||
{user.role === UserRole.GUEST ?
|
||||
'未登录' :
|
||||
<Link className="username-text" to="/user/profile">{user.username}</Link>
|
||||
}
|
||||
</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
selectedKeys={menuManager.getSelectedKeys()}
|
||||
onSelect={this.onSelect.bind(this)}
|
||||
mode="inline">
|
||||
{
|
||||
menuItems.map((menuItem: MenuItem, index: number) => {
|
||||
return (
|
||||
<Menu.Item key={menuItem.url}>
|
||||
<span>{menuItem.name}</span>
|
||||
</Menu.Item>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Menu>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header>
|
||||
<div className="logo-title-area" onClick={this.goHome.bind(this)}>
|
||||
<img className="header-logo" src={LogoSvg} alt="logo"/>
|
||||
<span className="header-title">bamboo</span>
|
||||
</div>
|
||||
</Header>
|
||||
<Content>
|
||||
|
||||
<div className="pages-content">
|
||||
<Route exact path="/" render={() =>
|
||||
<Redirect to="/article/list"/>
|
||||
}/>
|
||||
<Route path="/index" component={Index}/>
|
||||
<Route path="/user/login" component={UserLogin}/>
|
||||
<Route path="/user/profile" component={UserProfile}/>
|
||||
<Route path="/article/detail/:uuid" component={ArticleDetail}/>
|
||||
<Route exact path="/article" render={() =>
|
||||
<Redirect to="/article/list"/>
|
||||
}/>
|
||||
<Route path="/article/list" component={ArticleList}/>
|
||||
<Route path="/article/create" component={ArticleEdit}/>
|
||||
<Route path="/article/edit/:uuid" component={ArticleEdit}/>
|
||||
</div>
|
||||
</Content>
|
||||
<Footer style={{ textAlign: 'center' }}>Eyeblue ©2020 Copyright</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const Frame = withRouter<IProps, React.ComponentType<IProps>>(RawFrame);
|
||||
export default Frame;
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
.article-detail {
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
|
||||
|
||||
}
|
||||
.article {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import './Detail.less';
|
||||
import BambooComponent from '../../common/component/BambooComponent';
|
||||
import Article from '../../common/model/article/Article';
|
||||
import { Button, Col, Row, Spin } from 'antd';
|
||||
import InfoCell from '../widget/InfoCell';
|
||||
import StringUtil from '../../common/util/StringUtil';
|
||||
import BambooTitle from '../widget/BambooTitle';
|
||||
|
||||
interface RouteParam {
|
||||
uuid: string
|
||||
}
|
||||
|
||||
interface IProps extends RouteComponentProps<RouteParam> {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
export default class Detail extends BambooComponent<IProps, IState> {
|
||||
|
||||
article: Article = new Article(this);
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
|
||||
this.state = {};
|
||||
|
||||
//article的id设置晚了就来不及了
|
||||
let match = this.props.match;
|
||||
if (match.params.uuid) {
|
||||
this.article.uuid = match.params.uuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
//刷新一下列表
|
||||
let that = this;
|
||||
|
||||
let match = this.props.match;
|
||||
let article = that.article;
|
||||
|
||||
article.httpDetail(function() {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let that = this;
|
||||
let article: Article = that.article;
|
||||
//router中传入的路由相关对象
|
||||
let match = this.props.match;
|
||||
|
||||
|
||||
return (
|
||||
<div className="article-detail">
|
||||
|
||||
<BambooTitle name={'文章详情'}>
|
||||
<Link title="编辑"
|
||||
to={StringUtil.prePath(match.path, 2) + '/edit/' + article.uuid}>
|
||||
<Button className="mh10" type="primary" icon="edit">
|
||||
编辑
|
||||
</Button>
|
||||
</Link>
|
||||
</BambooTitle>
|
||||
|
||||
<Spin tip="加载中" spinning={article.detailLoading}>
|
||||
|
||||
<div className="info">
|
||||
|
||||
{/*<Row>*/}
|
||||
{/* <Col span={12}>*/}
|
||||
{/* <InfoCell name="文章名称">*/}
|
||||
{/* {article.title}*/}
|
||||
{/* </InfoCell>*/}
|
||||
{/* </Col>*/}
|
||||
{/* <Col span={12}>*/}
|
||||
{/* <InfoCell name="作者">*/}
|
||||
{/* {article.author}*/}
|
||||
{/* </InfoCell>*/}
|
||||
{/* </Col>*/}
|
||||
{/* <Col span={12}>*/}
|
||||
{/* <InfoCell name="路径">*/}
|
||||
{/* {article.path}*/}
|
||||
{/* </InfoCell>*/}
|
||||
{/* </Col>*/}
|
||||
|
||||
|
||||
{/*</Row>*/}
|
||||
|
||||
<div className="article" dangerouslySetInnerHTML={{__html: ''+ article.html}} />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</Spin>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
.article-edit {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { Button, Col, DatePicker, Input, message as MessageBox, Row, Spin } from 'antd';
|
||||
import BambooComponent from '../../common/component/BambooComponent';
|
||||
import Article from '../../common/model/article/Article';
|
||||
import StringUtil from '../../common/util/StringUtil';
|
||||
import './Edit.less';
|
||||
import BambooTitle from '../widget/BambooTitle';
|
||||
|
||||
const { MonthPicker, RangePicker } = DatePicker;
|
||||
|
||||
|
||||
interface RouteParam {
|
||||
uuid: string
|
||||
}
|
||||
|
||||
|
||||
interface IProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
class RawEdit extends BambooComponent<IProps, IState> {
|
||||
|
||||
createMode: boolean = true;
|
||||
article: Article = new Article(this);
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
//刷新一下列表
|
||||
|
||||
let article = this.article;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//获取上一级目录
|
||||
getPrePath() {
|
||||
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
//返回到列表页面
|
||||
goToIndex() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this;
|
||||
|
||||
//router中传入的路由相关对象
|
||||
let article = this.article;
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="article-edit">
|
||||
|
||||
<BambooTitle name={this.createMode ? '创建文章' : '编辑文章'}>
|
||||
|
||||
</BambooTitle>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RawEdit;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
.article-list {
|
||||
padding: 5px;
|
||||
|
||||
.ant-table {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import './List.less';
|
||||
import BambooComponent from '../../common/component/BambooComponent';
|
||||
import SortDirection from '../../common/model/base/SortDirection';
|
||||
import Pager from '../../common/model/base/Pager';
|
||||
import Article from '../../common/model/article/Article';
|
||||
import Table, { ColumnProps } from 'antd/lib/table';
|
||||
import StringUtil from '../../common/util/StringUtil';
|
||||
import { Button, message as MessageBox, Popconfirm } from 'antd';
|
||||
import DateUtil from '../../common/util/DateUtil';
|
||||
import FilterPanel from '../widget/filter/FilterPanel';
|
||||
import TableEmpty from '../widget/TableEmpty';
|
||||
import Sun from '../../common/model/global/Sun';
|
||||
import BambooTitle from '../widget/BambooTitle';
|
||||
|
||||
|
||||
interface IProps extends RouteComponentProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default class List extends BambooComponent<IProps, IState> {
|
||||
|
||||
//获取分页的一个帮助器
|
||||
pager: Pager<Article> = new Pager<Article>(this, Article, 10);
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
//刷新一下列表
|
||||
let that = this;
|
||||
|
||||
that.pager.enableHistory();
|
||||
|
||||
that.refresh();
|
||||
}
|
||||
|
||||
search() {
|
||||
let that = this;
|
||||
that.pager.page = 0;
|
||||
that.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
||||
let that = this;
|
||||
|
||||
//如果没有任何的排序,默认使用时间倒序
|
||||
if (!that.pager.getCurrentSortFilter()) {
|
||||
that.pager.setFilterValue('orderCreateTime', SortDirection.DESC);
|
||||
}
|
||||
|
||||
that.pager.httpList();
|
||||
}
|
||||
|
||||
createArticle() {
|
||||
|
||||
let that = this;
|
||||
|
||||
|
||||
//router中传入的路由相关对象
|
||||
let match = this.props.match;
|
||||
|
||||
|
||||
let url: string = StringUtil.prePath(match.path) + `/create`;
|
||||
|
||||
Sun.navigateTo(url);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this;
|
||||
|
||||
//router中传入的路由相关对象
|
||||
let match = this.props.match;
|
||||
|
||||
//pager对象
|
||||
let pager = this.pager;
|
||||
|
||||
const columns: ColumnProps<Article>[] = [{
|
||||
title: '文章名称',
|
||||
dataIndex: 'title',
|
||||
render: (text: any, record: Article, index: number): React.ReactNode => (
|
||||
<Link to={StringUtil.prePath(match.path) + '/detail/' + record.uuid}>{record.title}</Link>
|
||||
),
|
||||
}, {
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
}, {
|
||||
title: '作者',
|
||||
dataIndex: 'author',
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
sortOrder: pager.getDefaultSortOrder('createTime'),
|
||||
sortDirections: [SortDirection.DESCEND, SortDirection.ASCEND],
|
||||
render: (text: any, record: Article, index: number): React.ReactNode => (
|
||||
DateUtil.simpleDateTime(text)
|
||||
),
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
sorter: true,
|
||||
sortOrder: pager.getDefaultSortOrder('updateTime'),
|
||||
sortDirections: [SortDirection.DESCEND, SortDirection.ASCEND],
|
||||
render: (text: any, record: Article, index: number): React.ReactNode => (
|
||||
DateUtil.simpleDateTime(text)
|
||||
),
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
render: (text: any, record: Article) => (
|
||||
<span>
|
||||
|
||||
<Link title="编辑"
|
||||
to={StringUtil.prePath(match.path) + '/edit/' + record.uuid}>
|
||||
编辑
|
||||
</Link>
|
||||
|
||||
<Popconfirm title="确认删除该文章,删除后不可恢复?" onConfirm={(e: any) => {
|
||||
record.httpDel(function() {
|
||||
MessageBox.success('删除成功!');
|
||||
that.refresh();
|
||||
});
|
||||
}} okText="确认" cancelText="取消">
|
||||
<span>编辑</span>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
),
|
||||
}];
|
||||
|
||||
return (
|
||||
<div className="article-list">
|
||||
|
||||
<BambooTitle name={'文章管理'}>
|
||||
<Button type="primary" onClick={this.createArticle.bind(this)}>
|
||||
新建文章
|
||||
</Button>
|
||||
</BambooTitle>
|
||||
|
||||
|
||||
<div>
|
||||
<FilterPanel filters={pager.filters} onChange={this.search.bind(this)}/>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={pager.loading}
|
||||
dataSource={pager.data}
|
||||
columns={columns}
|
||||
pagination={pager.getPagination()}
|
||||
onChange={pager.tableOnChange.bind(pager)}
|
||||
locale={{ emptyText: (<TableEmpty pager={pager} onRefresh={this.refresh.bind(this)}/>) }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
.index-page {
|
||||
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import './Index.less';
|
||||
import BambooComponent from '../../common/component/BambooComponent';
|
||||
import Moon from '../../common/model/global/Moon';
|
||||
import User from '../../common/model/user/User';
|
||||
|
||||
interface IProps extends RouteComponentProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default class Index extends BambooComponent<IProps, IState> {
|
||||
|
||||
user: User = Moon.getSingleton().user;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
let that = this;
|
||||
|
||||
//假装获取一下详情
|
||||
this.user.httpInfo(function() {
|
||||
that.updateUI();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let that = this;
|
||||
|
||||
return (
|
||||
<div className="index-page">
|
||||
|
||||
<h1>欢迎来到蓝眼博客,这里是首页,内容正在开发中。</h1>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
.change-password-modal {
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-area {
|
||||
text-align: center;
|
||||
|
||||
.login-form-button {
|
||||
margin: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import "./ChangePasswordModal.less"
|
||||
import BambooComponent from "../../common/component/BambooComponent";
|
||||
import {Col, Modal, Row} from 'antd';
|
||||
import User from "../../common/model/user/User";
|
||||
import Moon from "../../common/model/global/Moon";
|
||||
|
||||
interface IProps {
|
||||
|
||||
onSuccess: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
export default class ChangePasswordModal extends BambooComponent<IProps, IState> {
|
||||
|
||||
user: User = Moon.getSingleton().user
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
||||
static open(onSuccess: () => void) {
|
||||
|
||||
let modal = Modal.success({
|
||||
okCancel: false,
|
||||
okButtonProps: {
|
||||
className: "display-none"
|
||||
},
|
||||
icon: null,
|
||||
content: <ChangePasswordModal
|
||||
onSuccess={() => {
|
||||
|
||||
onSuccess()
|
||||
modal.destroy()
|
||||
}}
|
||||
onClose={() => {
|
||||
modal.destroy()
|
||||
}}/>,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
handleSubmit(e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
let that = this
|
||||
|
||||
let user = that.user
|
||||
|
||||
|
||||
user.httpChangePassword("", "", function () {
|
||||
|
||||
that.props.onSuccess()
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
return (
|
||||
<div className="change-password-modal">
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
|
||||
<div className="title">
|
||||
修改密码
|
||||
</div>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
.user-login {
|
||||
|
||||
|
||||
padding-top: 150px;
|
||||
|
||||
.welcome {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
|
||||
|
||||
.ant-input-prefix {
|
||||
.anticon {
|
||||
color: rgba(0, 0, 0, .25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-form-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import {RouteComponentProps} from "react-router-dom";
|
||||
import "./Login.less"
|
||||
import BambooComponent from "../../common/component/BambooComponent";
|
||||
import {Col, message as MessageBox, Row} from 'antd';
|
||||
import User from "../../common/model/user/User";
|
||||
import Moon from "../../common/model/global/Moon";
|
||||
|
||||
interface IProps extends RouteComponentProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
class RawLogin extends BambooComponent<IProps, IState> {
|
||||
|
||||
user: User = Moon.getSingleton().user
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
|
||||
//立即退出
|
||||
this.logout()
|
||||
}
|
||||
|
||||
//退出登录
|
||||
logout() {
|
||||
|
||||
this.user.httpLogout()
|
||||
|
||||
}
|
||||
|
||||
|
||||
handleSubmit(e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
let that = this
|
||||
|
||||
let user = that.user
|
||||
|
||||
user.httpLogin("xx", "xx", function () {
|
||||
MessageBox.success("登录成功!")
|
||||
|
||||
that.props.history.push('/')
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
//router中传入的路由相关对象
|
||||
let match = this.props.match;
|
||||
let location = this.props.location;
|
||||
let history = this.props.history;
|
||||
|
||||
return (
|
||||
<div className="user-login">
|
||||
|
||||
<Row>
|
||||
<Col span={8} offset={8}>
|
||||
|
||||
<div className="welcome">
|
||||
欢迎登录
|
||||
</div>
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default RawLogin;
|
||||
@@ -0,0 +1,7 @@
|
||||
.page-profile {
|
||||
|
||||
background-color: white;
|
||||
|
||||
padding: 15px;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import "./Profile.less"
|
||||
import BambooComponent from "../../common/component/BambooComponent";
|
||||
import {Button} from 'antd';
|
||||
import InfoCell from "../widget/InfoCell";
|
||||
import User from "../../common/model/user/User";
|
||||
import Moon from "../../common/model/global/Moon";
|
||||
import DateUtil from "../../common/util/DateUtil";
|
||||
import ChangePasswordModal from "./ChangePasswordModal";
|
||||
import MessageBoxUtil from "../../common/util/MessageBoxUtil";
|
||||
import BambooTitle from '../widget/BambooTitle';
|
||||
|
||||
interface IProps {
|
||||
name: string
|
||||
firstSpan?: number
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
export default class Profile extends BambooComponent <IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
changePassword() {
|
||||
|
||||
ChangePasswordModal.open(() => {
|
||||
MessageBoxUtil.success("修改密码成功!")
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
let name: string = this.props.name
|
||||
let user: User = Moon.getSingleton().user
|
||||
|
||||
return (
|
||||
|
||||
<div className="page-profile">
|
||||
|
||||
<BambooTitle name={'个人资料'}>
|
||||
<Button type="primary" icon="plus" onClick={this.changePassword.bind(this)}>
|
||||
修改密码
|
||||
</Button>
|
||||
</BambooTitle>
|
||||
|
||||
<div>
|
||||
<InfoCell name="用户名">
|
||||
{user.username}
|
||||
</InfoCell>
|
||||
|
||||
<InfoCell name="创建时间">
|
||||
{DateUtil.simpleDateTime(user.createTime)}
|
||||
</InfoCell>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
//页面的导航样式
|
||||
.widget-bamboo-title {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
.item {
|
||||
font-size: 18px;
|
||||
color: #778195;
|
||||
display: inline-block;
|
||||
box-sizing: content-box;
|
||||
line-height: 30px;
|
||||
|
||||
&:hover, &.active {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
}
|
||||
|
||||
.tool {
|
||||
flex: 1;
|
||||
|
||||
padding-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import "./BambooTitle.less"
|
||||
import BambooComponent from '../../common/component/BambooComponent';
|
||||
|
||||
interface IProps {
|
||||
name: React.ReactNode
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题导航快捷插件
|
||||
*/
|
||||
export default class BambooTitle extends BambooComponent<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
//刷新一下列表
|
||||
let that = this
|
||||
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Readonly<IProps>, nextContext: any): void {
|
||||
let that = this
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
return (
|
||||
<div className="widget-bamboo-title">
|
||||
<span className="item active">
|
||||
{that.props.name}
|
||||
</span>
|
||||
<span className="tool">
|
||||
{that.props.children}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.widget-change-num-modal {
|
||||
|
||||
background-color: aliceblue;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import Button from 'antd/lib/button';
|
||||
import {message as MessageBox, Input} from "antd"
|
||||
|
||||
interface IProps {
|
||||
value: number
|
||||
title?: string
|
||||
onSuccess: (val: number) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
innerValue: number
|
||||
}
|
||||
|
||||
export default class ChangeNumModal extends React.Component<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
innerValue: this.props.value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onSubmitClick() {
|
||||
if (this.state.innerValue) {
|
||||
this.props.onSuccess(this.state.innerValue)
|
||||
} else {
|
||||
MessageBox.error("没有填写值,提交失败!")
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
return (
|
||||
<div className="widget-change-num-modal">
|
||||
|
||||
<div className="text-center">
|
||||
<h2>
|
||||
{that.props.title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Input value={this.state.innerValue} onChange={(e) => {
|
||||
that.setState({
|
||||
innerValue: parseInt(e.target.value)
|
||||
})
|
||||
|
||||
}}/>
|
||||
|
||||
<div className="text-center mt20">
|
||||
|
||||
<Button className="ml20" type="default" onClick={() => {
|
||||
this.props.onClose()
|
||||
}}>关闭</Button>
|
||||
|
||||
<Button className="ml20"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
this.onSubmitClick()
|
||||
}}>提交</Button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import "./TableEmpty.less"
|
||||
import BambooComponent from "../../common/component/BambooComponent";
|
||||
import LogoBluePng from "../../assets/image/logo-blue.png";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
/**
|
||||
* 主站加载控件。
|
||||
* 在主站加载过程中或者在工作台加载过程中使用
|
||||
* 该控件采用fixed布局,会占据全屏
|
||||
*/
|
||||
export default class FrameLoading extends BambooComponent<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
return (
|
||||
<div className="app-frame-loading">
|
||||
<div className="loading-box">
|
||||
<div>
|
||||
<img alt="加载按钮" className="loading-logo" src={LogoBluePng}/>
|
||||
</div>
|
||||
<div>
|
||||
加载中...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.castle-widget-info-cell {
|
||||
|
||||
margin-bottom: 10px;
|
||||
.info-cell-name {
|
||||
color: #99a9bf;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-cell-content {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import "./InfoCell.less"
|
||||
import {Col, Row} from "antd"
|
||||
|
||||
interface IProps {
|
||||
name: string
|
||||
firstSpan?: number
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
}
|
||||
|
||||
export default class InfoCell extends React.Component <IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
let name: string = this.props.name
|
||||
|
||||
let firstSpan: number = 8
|
||||
if (this.props.firstSpan !== undefined) {
|
||||
firstSpan = this.props.firstSpan
|
||||
}
|
||||
let secondSpan: number = 24 - firstSpan
|
||||
|
||||
return (
|
||||
<Row className="castle-widget-info-cell">
|
||||
<Col span={firstSpan} className="info-cell-name">{name}</Col>
|
||||
<Col span={secondSpan} className="info-cell-content">
|
||||
{that.props.children}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
.widget-strings-picker {
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import React from 'react';
|
||||
import "./StringsPicker.less"
|
||||
import {Input, Tag, Tooltip} from 'antd';
|
||||
import BambooComponent from "../../common/component/BambooComponent";
|
||||
import SafeUtil from "../../common/util/SafeUtil";
|
||||
|
||||
interface IProps {
|
||||
value?: string | null,
|
||||
onChange?: (value: string | null) => void,
|
||||
editable?: boolean
|
||||
}
|
||||
|
||||
interface IState {
|
||||
tags: string[],
|
||||
inputVisible: boolean,
|
||||
inputValue: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个控件可以填充一组字符串,同时具备回填的能力。
|
||||
* 格式为 json list 的字符串形式
|
||||
*/
|
||||
export default class StringsPicker extends BambooComponent<IProps, IState> {
|
||||
|
||||
innerValue: string | null = null
|
||||
|
||||
inputDom: any = null
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
tags: [],
|
||||
inputVisible: false,
|
||||
inputValue: ""
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
//刷新一下列表
|
||||
let that = this
|
||||
|
||||
this.fillBack()
|
||||
}
|
||||
|
||||
|
||||
componentWillReceiveProps(nextProps: Readonly<IProps>, nextContext: any): void {
|
||||
let that = this
|
||||
|
||||
//每次刷新都尝试去回填。
|
||||
if (nextProps.value != this.innerValue) {
|
||||
|
||||
that.fillBack(nextProps.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//回填
|
||||
fillBack(selectedValue?: string | null) {
|
||||
let that = this
|
||||
|
||||
if (!selectedValue) {
|
||||
selectedValue = this.props.value
|
||||
}
|
||||
|
||||
let arr: string[] = []
|
||||
if (selectedValue) {
|
||||
try {
|
||||
arr = JSON.parse(selectedValue);
|
||||
} catch (e) {
|
||||
arr = []
|
||||
}
|
||||
that.setState({tags: arr})
|
||||
}
|
||||
|
||||
this.setState({
|
||||
tags: arr
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
handleClose(removedTag: string) {
|
||||
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||
console.log(tags);
|
||||
|
||||
this.setState({tags});
|
||||
|
||||
|
||||
this.innerValue = JSON.stringify(tags)
|
||||
//往外通知
|
||||
SafeUtil.safeCallback(this.props.onChange)(this.innerValue)
|
||||
};
|
||||
|
||||
showInput = () => {
|
||||
this.setState({inputVisible: true}, () => this.inputDom.focus());
|
||||
};
|
||||
|
||||
handleInputChange(e: any) {
|
||||
|
||||
this.setState({inputValue: e.target.value});
|
||||
|
||||
};
|
||||
|
||||
handleInputConfirm = () => {
|
||||
const {inputValue} = this.state;
|
||||
let {tags} = this.state;
|
||||
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||
tags = [...tags, inputValue];
|
||||
}
|
||||
console.log(tags);
|
||||
this.setState({
|
||||
tags,
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
});
|
||||
|
||||
this.innerValue = JSON.stringify(tags)
|
||||
|
||||
//往外通知
|
||||
SafeUtil.safeCallback(this.props.onChange)(this.innerValue)
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 由于Select控件具有内部自动搜索的能力,因此value要用nickname,realname,no的拼接
|
||||
*/
|
||||
render() {
|
||||
let tags: string[] = this.state.tags
|
||||
let inputVisible: boolean = this.state.inputVisible
|
||||
let inputValue: string = this.state.inputValue
|
||||
|
||||
let editable: boolean = true
|
||||
if (this.props.editable !== undefined) {
|
||||
editable = this.props.editable
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tags.map((tag, index) => {
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag key={tag} closable={editable} onClose={() => this.handleClose(tag)}>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagElem
|
||||
);
|
||||
})}
|
||||
{inputVisible && editable && (
|
||||
<Input
|
||||
ref={(dom) => {
|
||||
this.inputDom = dom
|
||||
}}
|
||||
type="text"
|
||||
size="small"
|
||||
style={{width: 120}}
|
||||
value={inputValue}
|
||||
onChange={this.handleInputChange.bind(this)}
|
||||
onBlur={this.handleInputConfirm}
|
||||
onPressEnter={this.handleInputConfirm}
|
||||
/>
|
||||
)}
|
||||
{!inputVisible && editable && (
|
||||
<Tag onClick={this.showInput} style={{background: '#fff', borderStyle: 'dashed'}}>
|
||||
添加
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
.widget-table-empty {
|
||||
.empty-content {
|
||||
|
||||
}
|
||||
|
||||
.error-content {
|
||||
img {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import {Empty} from 'antd';
|
||||
import EmptyImage from "../../assets/image/empty.svg"
|
||||
import ErrorImage from "../../assets/image/error.png"
|
||||
import SafeUtil from "../../common/util/SafeUtil";
|
||||
import "./TableEmpty.less"
|
||||
import Pager from "../../common/model/base/Pager";
|
||||
|
||||
interface IProps {
|
||||
pager: Pager<any>
|
||||
onRefresh?: () => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
class TableEmpty extends React.Component<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
let pager = this.props.pager
|
||||
|
||||
|
||||
let emptyContent = (
|
||||
<div className="empty-content">
|
||||
<Empty
|
||||
image={EmptyImage}
|
||||
description="暂无数据"
|
||||
>
|
||||
<span className="link" onClick={event => {
|
||||
SafeUtil.safeCallback(that.props.onRefresh)()
|
||||
}
|
||||
}>点击重试</span>
|
||||
</Empty>
|
||||
</div>
|
||||
)
|
||||
let errorContent = (
|
||||
<div className="error-content">
|
||||
<Empty
|
||||
image={ErrorImage}
|
||||
description={pager.errorMessage}
|
||||
>
|
||||
<span className="link" onClick={event => {
|
||||
SafeUtil.safeCallback(that.props.onRefresh)()
|
||||
}
|
||||
}>点击重试</span>
|
||||
</Empty>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="widget-table-empty">
|
||||
{pager.errorMessage ? errorContent : (pager.data.length ? "" : emptyContent)}
|
||||
</div>
|
||||
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
export default TableEmpty;
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
import {Select} from 'antd';
|
||||
import SafeUtil from "../../../common/util/SafeUtil";
|
||||
import CheckFilter from "../../../common/model/base/filter/CheckFilter";
|
||||
|
||||
const Option = Select.Option;
|
||||
|
||||
interface IProps {
|
||||
checkFilter: CheckFilter
|
||||
onChange?: (value: boolean | null) => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
export default class CheckFilterBox extends React.Component <IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
||||
//改变某个filter的值
|
||||
onValueChange = (value: string) => {
|
||||
|
||||
let that = this
|
||||
|
||||
let checkFilter = this.props.checkFilter
|
||||
|
||||
checkFilter.putValue(value)
|
||||
|
||||
//通知外面变化了。
|
||||
SafeUtil.safeCallback(that.props.onChange)(checkFilter.value)
|
||||
|
||||
//更新UI
|
||||
that.setState({})
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
let checkFilter = this.props.checkFilter
|
||||
|
||||
//用于回填的值
|
||||
let backValue = checkFilter.getValueString()
|
||||
|
||||
return (
|
||||
<span className="filter-block check-filter-box">
|
||||
|
||||
<span className="filter-cell">
|
||||
<span className="filter-name">
|
||||
{checkFilter.name}
|
||||
</span>
|
||||
|
||||
<Select value={backValue} style={{minWidth: 120}}
|
||||
onChange={this.onValueChange}>
|
||||
<Option key={0} value={""}>全部</Option>
|
||||
<Option key={1} value={"true"}>是</Option>
|
||||
<Option key={2} value={"false"}>否</Option>
|
||||
</Select>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
|
||||
import {DatePicker} from 'antd';
|
||||
import moment from "moment";
|
||||
import {default as DateFilter} from "../../../common/model/base/filter/DateFilter";
|
||||
import SafeUtil from "../../../common/util/SafeUtil";
|
||||
|
||||
interface IProps {
|
||||
dateFilter: DateFilter
|
||||
onChange?: (value: Date | null) => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
|
||||
class DateFilterBox extends React.Component<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
//改变某个filter的值
|
||||
onValueChange(momentTime: moment.Moment | null, dateString: string) {
|
||||
|
||||
let that = this
|
||||
|
||||
let filter = this.props.dateFilter
|
||||
|
||||
if (momentTime) {
|
||||
|
||||
filter.value = momentTime.toDate()
|
||||
|
||||
} else {
|
||||
filter.value = null
|
||||
}
|
||||
|
||||
//通知外面变化了。
|
||||
SafeUtil.safeCallback(that.props.onChange)(filter.value)
|
||||
|
||||
//更新UI
|
||||
that.setState({})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
let dateFilter = this.props.dateFilter
|
||||
|
||||
let value: moment.Moment | undefined = undefined
|
||||
if (dateFilter.value) {
|
||||
value = moment(dateFilter.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="filter-block date-time-filter-box">
|
||||
<span className="filter-cell">
|
||||
<span className="filter-name">
|
||||
{dateFilter.name}
|
||||
</span>
|
||||
<span className="filter-body">
|
||||
|
||||
<DatePicker
|
||||
showTime
|
||||
format={dateFilter.format}
|
||||
value={value}
|
||||
placeholder="请选择日期"
|
||||
onChange={(date: moment.Moment | null, dateString: string) => {
|
||||
that.onValueChange(date, dateString)
|
||||
}}/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DateFilterBox;
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
|
||||
import {DatePicker} from 'antd';
|
||||
import moment from "moment";
|
||||
import DateTimeFilter from "../../../common/model/base/filter/DateTimeFilter";
|
||||
import SafeUtil from "../../../common/util/SafeUtil";
|
||||
|
||||
interface IProps {
|
||||
dateTimeFilter: DateTimeFilter
|
||||
onChange?: (value: Date | null) => void
|
||||
}
|
||||
|
||||
interface IState {
|
||||
}
|
||||
|
||||
|
||||
class DateTimeFilterBox extends React.Component<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
//改变某个filter的值
|
||||
onValueChange(momentTime: moment.Moment | null, dateString: string) {
|
||||
|
||||
let that = this
|
||||
|
||||
let filter = this.props.dateTimeFilter
|
||||
|
||||
if (momentTime) {
|
||||
|
||||
filter.value = momentTime.toDate()
|
||||
|
||||
} else {
|
||||
filter.value = null
|
||||
}
|
||||
|
||||
//通知外面变化了。
|
||||
SafeUtil.safeCallback(that.props.onChange)(filter.value)
|
||||
|
||||
//更新UI
|
||||
that.setState({})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let that = this
|
||||
|
||||
let dateTimeFilter = this.props.dateTimeFilter
|
||||
|
||||
let value: moment.Moment | undefined = undefined
|
||||
if (dateTimeFilter.value) {
|
||||
value = moment(dateTimeFilter.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="filter-block date-time-filter-box">
|
||||
<span className="filter-cell">
|
||||
<span className="filter-name">
|
||||
{dateTimeFilter.name}
|
||||
</span>
|
||||
<span className="filter-body">
|
||||
|
||||
<DatePicker
|
||||
showTime
|
||||
format={dateTimeFilter.format}
|
||||
value={value}
|
||||
placeholder="请选择日期时间"
|
||||
onChange={(date: moment.Moment | null, dateString: string) => {
|
||||
that.onValueChange(date, dateString)
|
||||
}}/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DateTimeFilterBox;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user