├── src
├── pages
│ ├── clinic
│ │ ├── index.less
│ │ └── index.tsx
│ ├── ts-study
│ │ ├── index.scss
│ │ ├── constants.tsx
│ │ ├── mock.tsx
│ │ ├── types.tsx
│ │ ├── index.tsx
│ │ ├── code-block.tsx
│ │ └── tstype.md
│ ├── demo-hooks
│ │ ├── index.less
│ │ ├── withLogger.tsx
│ │ ├── useHooks.ts
│ │ ├── child-class.tsx
│ │ ├── useReducer.tsx
│ │ ├── useRouter.tsx
│ │ ├── useContext.tsx
│ │ ├── useRef.tsx
│ │ ├── useCallback.tsx
│ │ └── index.tsx
│ ├── demo-components
│ │ ├── style.module.css
│ │ ├── index.less
│ │ └── index.tsx
│ ├── user
│ │ ├── user.scss
│ │ ├── model.ts
│ │ └── User.tsx
│ ├── login
│ │ ├── images
│ │ │ └── bg.jpg
│ │ ├── login.less
│ │ └── Login.tsx
│ ├── demo
│ │ ├── index.less
│ │ ├── test.tsx
│ │ ├── withLoading.tsx
│ │ ├── test-loading.tsx
│ │ ├── index.tsx
│ │ └── lifeCycle.tsx
│ ├── not-found
│ │ ├── images
│ │ │ └── 404.png
│ │ ├── not-found.scss
│ │ └── not-found.tsx
│ ├── home
│ │ ├── index.less
│ │ ├── types.tsx
│ │ ├── constants.tsx
│ │ ├── mock.tsx
│ │ └── index.tsx
│ ├── todo
│ │ ├── types.tsx
│ │ ├── index.scss
│ │ ├── constants.tsx
│ │ ├── mock.tsx
│ │ ├── task-card.tsx
│ │ ├── dialog.tsx
│ │ └── index.tsx
│ ├── register
│ │ ├── record.tsx
│ │ └── add-register.tsx
│ ├── charge
│ │ ├── charge-manage.tsx
│ │ ├── settle-record.tsx
│ │ └── prescription-detail.tsx
│ ├── category
│ │ ├── ModalStatusCode.ts
│ │ ├── Model.ts
│ │ ├── UpdateFrom.tsx
│ │ ├── AddForm.tsx
│ │ └── Category.tsx
│ ├── product
│ │ ├── product.scss
│ │ ├── Product.tsx
│ │ ├── Model.ts
│ │ ├── media.tsx
│ │ ├── rich-text-editor.tsx
│ │ ├── detail.tsx
│ │ ├── pictures-wall.tsx
│ │ └── home.tsx
│ ├── role
│ │ └── Model.ts
│ ├── zent
│ │ ├── index.scss
│ │ ├── data.tsx
│ │ ├── feedback.tsx
│ │ ├── show.tsx
│ │ ├── base.tsx
│ │ └── nav.tsx
│ ├── chars
│ │ ├── Bar.tsx
│ │ ├── Line.tsx
│ │ └── Pie.tsx
│ └── admin
│ │ └── admin.tsx
├── components
│ ├── app-card
│ │ ├── style
│ │ │ ├── index.ts
│ │ │ └── index.scss
│ │ └── index.tsx
│ ├── help-icon
│ │ ├── style
│ │ │ ├── index.ts
│ │ │ └── index.scss
│ │ └── index.tsx
│ ├── affix-tabs-nav
│ │ ├── style
│ │ │ ├── index.ts
│ │ │ └── index.scss
│ │ └── index.tsx
│ ├── content-title
│ │ ├── style
│ │ │ ├── index.ts
│ │ │ └── index.less
│ │ └── index.tsx
│ ├── vip-icon
│ │ ├── index.less
│ │ └── index.tsx
│ ├── link-button
│ │ ├── index.less
│ │ └── index.tsx
│ ├── zent
│ │ ├── loading.tsx
│ │ ├── IME.tsx
│ │ ├── affix.tsx
│ │ ├── Infinite.tsx
│ │ ├── collapse.tsx
│ │ ├── way-point.tsx
│ │ ├── drawer.tsx
│ │ ├── portal.tsx
│ │ ├── tree.tsx
│ │ ├── dialog.tsx
│ │ ├── swiper.tsx
│ │ ├── table.tsx
│ │ ├── table-header-group.tsx
│ │ ├── tree-plain.tsx
│ │ └── form.tsx
│ ├── left-nav
│ │ ├── index.less
│ │ └── index.tsx
│ └── header
│ │ ├── index.less
│ │ └── index.tsx
├── react-app-env.d.ts
├── assets
│ └── images
│ │ ├── logo.png
│ │ └── .DS_Store
├── redux
│ ├── model.ts
│ ├── action-types.ts
│ ├── store.ts
│ ├── types.d.ts
│ ├── reducer.ts
│ └── actions.ts
├── setupTests.ts
├── api
│ ├── ReqMethodEnum.ts
│ ├── Model.ts
│ ├── ajax.ts
│ └── useFetchData.ts
├── utils
│ ├── Constants.ts
│ ├── MemoryUtils.ts
│ ├── StorageUtils.ts
│ └── DateUtils.tsx
├── setupProxy.js
├── index.css
├── App.tsx
├── index.tsx
├── test
│ └── api
│ │ ├── Api.test.tsx
│ │ └── test.http
├── logo.svg
├── config
│ └── menuConfig.ts
└── serviceWorker.ts
├── .gitignore
├── .npmrc
├── .babelrc
├── .DS_Store
├── base-tsconfig.json
├── public
├── favicon.ico
├── css
│ └── reset.css
└── index.html
├── path.json
├── craco.config.js
├── tsconfig.json
├── README.md
└── package.json
/src/pages/clinic/index.less:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/ts-study/index.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # registry=https://npm.taobao.org
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["zent"]
4 | ]
5 | }
--------------------------------------------------------------------------------
/src/components/app-card/style/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
--------------------------------------------------------------------------------
/src/components/help-icon/style/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/index.less:
--------------------------------------------------------------------------------
1 | .mt10{
2 | margin: 10px 0;
3 | }
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/components/affix-tabs-nav/style/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
--------------------------------------------------------------------------------
/src/components/content-title/style/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/.DS_Store
--------------------------------------------------------------------------------
/src/pages/demo-components/style.module.css:
--------------------------------------------------------------------------------
1 | .cssmodule{
2 | color: red;
3 | }
--------------------------------------------------------------------------------
/base-tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "isolatedModules": false
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/pages/user/user.scss:
--------------------------------------------------------------------------------
1 | label {
2 | display: inline-block;
3 | width: 100px;
4 | text-align: right;
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/src/assets/images/.DS_Store
--------------------------------------------------------------------------------
/src/pages/login/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/src/pages/login/images/bg.jpg
--------------------------------------------------------------------------------
/src/pages/demo/index.less:
--------------------------------------------------------------------------------
1 | .fatherContainer{
2 | // text-align:center;
3 | margin: 20px;
4 | // height:300px
5 | }
--------------------------------------------------------------------------------
/src/pages/not-found/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexwjj/React-TypeScript/HEAD/src/pages/not-found/images/404.png
--------------------------------------------------------------------------------
/src/pages/home/index.less:
--------------------------------------------------------------------------------
1 | .J-Model{
2 |
3 | .ant-modal-body{
4 | background: url(../../assets/images/logo.png) no-repeat;
5 | }
6 | }
--------------------------------------------------------------------------------
/path.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/pages/home/types.tsx:
--------------------------------------------------------------------------------
1 | export interface ITask {
2 | id: number;
3 | status: number;
4 | title: string,
5 | description?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/todo/types.tsx:
--------------------------------------------------------------------------------
1 | export interface ITask {
2 | id: number;
3 | status: number;
4 | title: string,
5 | description?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/vip-icon/index.less:
--------------------------------------------------------------------------------
1 |
2 | .vip {
3 | width: 26px;
4 | min-width: 26px;
5 | height: 16px;
6 | margin-left: 4px;
7 | object-fit: cover;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/link-button/index.less:
--------------------------------------------------------------------------------
1 | .link-button{
2 | background-color: transparent;
3 | border: none;
4 | outline: none;
5 | color: green;
6 | cursor: pointer;
7 | }
--------------------------------------------------------------------------------
/src/pages/register/record.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const register = () => {
4 | return (
5 |
挂号登记
6 | );
7 | };
8 |
9 | export default register;
10 |
--------------------------------------------------------------------------------
/src/pages/charge/charge-manage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const register = () => {
4 | return (
5 | 收费管理
6 | );
7 | };
8 |
9 | export default register;
10 |
--------------------------------------------------------------------------------
/src/pages/charge/settle-record.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const register = () => {
4 | return (
5 | 新建挂号
6 | );
7 | };
8 |
9 | export default register;
10 |
--------------------------------------------------------------------------------
/src/pages/register/add-register.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const register = () => {
4 | return (
5 | 新建挂号
6 | );
7 | };
8 |
9 | export default register;
10 |
--------------------------------------------------------------------------------
/src/pages/charge/prescription-detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const register = () => {
4 | return (
5 | 处方详情
6 | );
7 | };
8 |
9 | export default register;
10 |
--------------------------------------------------------------------------------
/src/pages/clinic/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less';
3 |
4 | const register = () => {
5 | return (
6 | 门诊医生工作站
7 | );
8 | };
9 |
10 | export default register;
11 |
--------------------------------------------------------------------------------
/src/components/link-button/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.less'
3 |
4 | export default function LinkButton(props: any) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/home/constants.tsx:
--------------------------------------------------------------------------------
1 | export const TASK_STATUS = {
2 | TODO: 0,
3 | DOING: 1,
4 | DONE: 2
5 | }
6 |
7 | export const TASK_BTN_TEXT = {
8 | 0: '点击开始',
9 | 1: '点击完成',
10 | 2: '查看详情'
11 | }
--------------------------------------------------------------------------------
/src/pages/todo/index.scss:
--------------------------------------------------------------------------------
1 | .mtb10{
2 | margin: 10px 10;
3 | }
4 |
5 | .task{
6 | height: 100%;
7 | min-height: 500px;
8 | overflow: auto;
9 | margin-top: 10px;
10 | // border: 1px solid #666;
11 | }
--------------------------------------------------------------------------------
/src/pages/ts-study/constants.tsx:
--------------------------------------------------------------------------------
1 | export const TASK_STATUS = {
2 | TODO: 0,
3 | DOING: 1,
4 | DONE: 2
5 | }
6 |
7 | export const TASK_BTN_TEXT = {
8 | 0: '点击开始',
9 | 1: '点击完成',
10 | 2: '查看详情'
11 | }
--------------------------------------------------------------------------------
/src/pages/demo-components/index.less:
--------------------------------------------------------------------------------
1 | .scrm-components {
2 | .icon-youzan {
3 | color: red;
4 | }
5 |
6 | .zent-block-header {
7 | margin-bottom: 0 !important;
8 | }
9 | }
--------------------------------------------------------------------------------
/src/redux/model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-29 00:18:11
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-29 00:19:31
8 | */
9 | export type HeadTitle={
10 | title: string
11 | }
--------------------------------------------------------------------------------
/src/components/help-icon/style/index.scss:
--------------------------------------------------------------------------------
1 | @import 'vars/colors';
2 |
3 | .help-icon {
4 | &__icon {
5 | @include theme-color(color, stroke, 3);
6 |
7 | font-size: 16px;
8 | margin-left: 4px;
9 | }
10 |
11 | &__pop {
12 | max-width: 400px;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/demo/test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const Test: React.FC = (props) => {
4 | return (
5 | <>
6 | 测试props.children
7 | {props.children}
8 | >
9 | )
10 | }
11 |
12 | export default Test
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/components/content-title/style/index.less:
--------------------------------------------------------------------------------
1 |
2 | .content-title {
3 | height: 50px;
4 | font-size: 24px;
5 | color: #3e7fee;
6 | line-height: 50px;
7 | padding-left: 10px;
8 | cursor: pointer;
9 | &__icon {
10 | font-size: 18px;
11 | margin-left: 10px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/category/ModalStatusCode.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-16 16:52:25
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-10-16 16:54:22
8 | */
9 | export enum ModalStatusCode{
10 | Invisble=0,
11 | Add=1,
12 | Update=2
13 | }
--------------------------------------------------------------------------------
/src/pages/todo/constants.tsx:
--------------------------------------------------------------------------------
1 | export const TASK_STATUS = {
2 | TODO: 0,
3 | DOING: 1,
4 | DONE: 2
5 | }
6 |
7 | export const TASK_BTN_TEXT = {
8 | 0: '点击开始',
9 | 1: '点击完成',
10 | 2: '查看详情'
11 | }
12 |
13 | export const TASK_BTN_STYLE = {
14 | 0: 'warning',
15 | 1: 'success',
16 | 2: 'primary'
17 | }
--------------------------------------------------------------------------------
/src/api/ReqMethodEnum.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-11-02 14:18:37
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-11-02 14:28:09
8 | */
9 | export enum ReqMethodEnum {
10 | GET = 'GET',
11 | POST = 'POST',
12 | PUT = 'PUT',
13 | DELETE = 'DELETE',
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/withLogger.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const withLogger = (prefix = "") => (WrappedComponent) => {
4 | const WithLogger = (props) => {
5 | console.log(`${prefix}[Props]:`, props);
6 | return ;
7 | };
8 | return WithLogger;
9 | };
10 |
11 | export default withLogger;
12 |
--------------------------------------------------------------------------------
/src/components/vip-icon/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './index.less';
4 |
5 | const vipSprites = 'https://b.yzcdn.cn/yzscrm/customer/vip-sprites.png';
6 |
7 | const VipRender = ({ vip = 0 }) => {
8 | return
;
9 | };
10 | export default VipRender;
11 |
--------------------------------------------------------------------------------
/src/utils/Constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion: 常用的一些常量
3 | * @version: 1.0
4 | * @Author: alexwjj
5 | * @Date: 2020-11-29 16:16:27
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-12-27 17:37:37
8 | */
9 |
10 | // 每页显示的记录数
11 | export const PAGE_SIZE = 3;
12 |
13 | export const BASE_URL = 'http://localhost:5000'
14 |
15 | export const BASE_IMAGES_URL = BASE_URL + '/files/'
--------------------------------------------------------------------------------
/src/pages/product/product.scss:
--------------------------------------------------------------------------------
1 | .product-detail {
2 | .list {
3 | .left {
4 | font-size: 20px;
5 | font-weight: bold;
6 | margin-right: 15px;
7 | }
8 | .product-img {
9 | margin-left: 10px;
10 | zoom: 0.2;
11 | }
12 | .item {
13 | display: flex;
14 | display: -webkit-flex;
15 | justify-content: flex-start;
16 | }
17 | }
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/redux/action-types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-27 23:40:08
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-10 01:10:21
8 | */
9 | export const SET_HEAD_TITLE = 'set_head_title';
10 | export const RECEIVE_USER = 'receive_user';
11 | export const SHOW_ERROR_MSG = 'show_error_msg';
12 | export const RESET_USER = 'reset_user';
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useHooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 |
3 | /**
4 | * 获取当前时间的 hooks
5 | */
6 | export default function useTime(initialTime: Date = new Date()): [Date, () => void] {
7 | const [time, setDate] = useState(initialTime);
8 | const refresh = useCallback(() => {
9 | setDate(new Date());
10 | }, [setDate]);
11 |
12 | return [time, refresh];
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/demo/withLoading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const withLoading = (Loading) => (WapperedComponent) => {
4 | const WithLoading = (props) => {
5 | console.log(props);
6 | return props.loading ? (
7 |
8 | ) : (
9 |
10 | );
11 | };
12 | return WithLoading;
13 | };
14 |
15 | export default withLoading;
16 |
--------------------------------------------------------------------------------
/src/pages/not-found/not-found.scss:
--------------------------------------------------------------------------------
1 | .not-found {
2 | background-color: #f0f2f5;
3 | height: 100%;
4 |
5 | .left {
6 | height: 100%;
7 | background: url(./images/404.png) no-repeat center;
8 | }
9 | .right {
10 | padding-left: 50px;
11 | margin-top: 400px;
12 | h1 {
13 | font-size: 35px;
14 | }
15 | h2 {
16 | margin-bottom: 20px;
17 | font-size: 20px;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/pages/role/Model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-04 22:54:12
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-15 21:45:43
8 | */
9 | export interface RoleModel {
10 | id?: number;
11 | name: string;
12 | createTime: String;
13 | menus?: string;
14 | v?: number;
15 | authTime?: String|null;
16 | authName?: string;
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/home/mock.tsx:
--------------------------------------------------------------------------------
1 | export const allList = [
2 | {
3 | id: 1,
4 | title: "第一天的任务",
5 | status: 2,
6 | description: "熟悉zent组件",
7 | },
8 | {
9 | id: 2,
10 | title: "第二天的任务",
11 | status: 1,
12 | description: "开发一个TODO LIST",
13 | },
14 | {
15 | id: 3,
16 | title: "第三天的任务",
17 | status: 0,
18 | description: "熟悉Node 和 Dubbo的调用",
19 | },
20 | ];
21 |
--------------------------------------------------------------------------------
/src/pages/todo/mock.tsx:
--------------------------------------------------------------------------------
1 | export const allList = [
2 | {
3 | id: 1,
4 | title: "第一天的任务",
5 | status: 2,
6 | description: "熟悉zent组件",
7 | },
8 | {
9 | id: 2,
10 | title: "第二天的任务",
11 | status: 1,
12 | description: "开发一个TODO LIST",
13 | },
14 | {
15 | id: 3,
16 | title: "第三天的任务",
17 | status: 0,
18 | description: "熟悉Node 和 Dubbo的调用",
19 | },
20 | ];
21 |
--------------------------------------------------------------------------------
/src/pages/ts-study/mock.tsx:
--------------------------------------------------------------------------------
1 | export const allList = [
2 | {
3 | id: 1,
4 | title: "第一天的任务",
5 | status: 2,
6 | description: "熟悉zent组件",
7 | },
8 | {
9 | id: 2,
10 | title: "第二天的任务",
11 | status: 1,
12 | description: "开发一个TODO LIST",
13 | },
14 | {
15 | id: 3,
16 | title: "第三天的任务",
17 | status: 0,
18 | description: "熟悉Node 和 Dubbo的调用",
19 | },
20 | ];
21 |
--------------------------------------------------------------------------------
/src/pages/user/model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-21 23:37:24
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-23 17:23:58
8 | */
9 | export interface UserModel {
10 | id?: number;
11 | password?: string;
12 | name?: string;
13 | phone?: string;
14 | email?: string;
15 | roleId?: string;
16 | createTime?: string;
17 | __v?: number;
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/child-class.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Notify, Button } from "zent";
3 | interface IProps {
4 | value: string
5 | }
6 | class Child extends React.Component{
7 | handleLog = () => {
8 | Notify.info("点击了子组件");
9 | };
10 | render() {
11 | return ;
12 | }
13 | }
14 | export default Child;
15 |
--------------------------------------------------------------------------------
/src/utils/MemoryUtils.ts:
--------------------------------------------------------------------------------
1 | import { ProductsModel } from '../pages/product/Model';
2 | /*
3 | * @Descripttion:
4 | * @version:
5 | * @Author: alexwjj
6 | * @Date: 2021-02-11 13:56:15
7 | * @LastEditors: alexwjj
8 | * @LastEditTime: 2021-02-11 14:28:45
9 | */
10 |
11 | interface MemoryUtilsModel {
12 | product?: ProductsModel;
13 | }
14 |
15 | const MemoryUtils: MemoryUtilsModel = {
16 |
17 | };
18 |
19 | export default MemoryUtils;
20 |
--------------------------------------------------------------------------------
/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const { createProxyMiddleware } = require('http-proxy-middleware');
2 |
3 | module.exports = function (app) {
4 | app.use(createProxyMiddleware('/api', { target: 'http://123.57.208.169:8888/', changeOrigin: true }));
5 | app.use(
6 | createProxyMiddleware('/fileApi', {
7 | target: 'http://123.57.208.169:8888/',
8 | changeOrigin: true,
9 | pathRewrite: {
10 | '^/fileApi': '',
11 | },
12 | })
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/zent/loading.tsx:
--------------------------------------------------------------------------------
1 | import { InlineLoading } from "zent";
2 | import * as React from "react";
3 | export const JLoading: React.FC = () => {
4 | return (
5 |
6 |
7 |
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
15 | h3{
16 | margin-bottom: 0!important;
17 | }
18 |
--------------------------------------------------------------------------------
/src/redux/store.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-27 23:36:53
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-30 21:37:58
8 | */
9 | import { createStore, applyMiddleware, compose } from 'redux';
10 |
11 | import thunk from 'redux-thunk';
12 | import reducer from './reducer';
13 |
14 | const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
15 |
16 | export default createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
17 |
--------------------------------------------------------------------------------
/src/pages/category/Model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-21 17:11:52
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-11-29 15:48:03
8 | */
9 | export interface ICategory {
10 | parentId: string;
11 | _id: string;
12 | name: string;
13 | __v: number;
14 | categoryName: string;
15 | parentName: string;
16 | }
17 |
18 | export interface CategoryModel {
19 | parentId: string;
20 | id: number;
21 | name: string;
22 | categoryName: string;
23 | parentName: string;
24 | }
--------------------------------------------------------------------------------
/src/pages/demo/test-loading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import withLoading from './withLoading';
3 |
4 | const TestLoading: React.FC = (props) => {
5 | let [loadingOut, setLoadingOut] = React.useState(false);
6 | React.useEffect(() => {
7 | setTimeout(() => {
8 | setLoadingOut(true);
9 | }, 3000);
10 | }, []);
11 | return <>{loadingOut ? loading结束
: loading中
}>;
12 | };
13 |
14 | const Loading = () => loading
;
15 |
16 | export default withLoading(Loading)(TestLoading)
17 |
--------------------------------------------------------------------------------
/src/components/left-nav/index.less:
--------------------------------------------------------------------------------
1 | .left-nav{
2 | height: 100vh;
3 | color: white;
4 | position: fixed;
5 |
6 | .left-nav-header{
7 | height: 60px;
8 | width:200px;
9 | display: flex;
10 | display: -webkit-flex;
11 | align-items: center;
12 | justify-content: center;
13 | background-color: #002140;
14 | img{
15 | height: 40px;
16 | width: 40px;
17 | margin-right: 15px;
18 | }
19 | h1{
20 | color: white;
21 | font-size: 20px;
22 | margin-bottom: 0px;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/redux/types.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-30 21:39:42
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-02 21:41:44
8 | */
9 |
10 | import { StateType } from 'typesafe-actions';
11 |
12 | declare module 'typesafe-actions' {
13 | export type Store = StateType
14 |
15 | export type RootState = StateType;
16 |
17 | export type RootAction = ActionType
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/pages/ts-study/types.tsx:
--------------------------------------------------------------------------------
1 | export interface UserModel {
2 | name: string;
3 | age?: number;
4 | sex: number;
5 | address?: string;
6 | tel?: string;
7 | favorite?: string;
8 | }
9 |
10 | // type TodoProperty = 'title' | 'description';
11 |
12 | // type Todo = Record;
13 |
14 | interface Todo {
15 | title: string;
16 | description: string;
17 | done: boolean;
18 | }
19 |
20 | type TodoBase = Omit;
21 |
22 | // type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
23 |
24 | type T0 = Parameters<() => string>; // []
25 |
26 | type T1 = Parameters<(s: string) => void>; // [string]
--------------------------------------------------------------------------------
/src/pages/zent/index.scss:
--------------------------------------------------------------------------------
1 | .mt10 {
2 | margin-top: 10px;
3 | }
4 | .ft30{
5 | font-size: 30px;
6 | }
7 | .zent-popover{
8 | background-color: green;
9 | }
10 |
11 | .waypoint-demo-basic {
12 | width: 400px;
13 | height: 100px;
14 | overflow: auto;
15 | background-color: grey;
16 | text-align: center;
17 | &__waypoint-line{
18 | height:2px;
19 | border: 1px solid red;
20 | }
21 | }
22 | .infinite-scroller-demo{
23 | height:300px;
24 | overflow: auto;
25 | }
26 | .swiper-demo-container{
27 | height:200px;
28 | background-color: grey;
29 | text-align: center;
30 | }
--------------------------------------------------------------------------------
/src/pages/product/Product.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect, Route, Switch } from 'react-router';
3 | import ProductHome from './home';
4 | import ProductAddUpdate from './add-update';
5 | import ProductDetail from './detail';
6 | import './product.scss';
7 |
8 | export default class Product extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/product/Model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-11-03 22:59:39
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-12-29 23:42:19
8 | */
9 | export interface ProductsModel {
10 | id?: number;
11 | images: string;
12 | status: number;
13 | idStr?: string;
14 | name: string;
15 | desc: string;
16 | detail: string;
17 | categoryId: string;
18 | pcategoryId: string;
19 | price: string;
20 | v: number;
21 | }
22 |
23 | export interface FileUploadResponseModel {
24 | url: string;
25 | name: string;
26 | }
27 |
28 | export interface StringValidator {
29 | isAcceptable(s: string): boolean;
30 | }
31 |
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | const CracoLessPlugin = require('craco-less');
2 | const path = require('path')
3 |
4 | const pathResolve = pathUrl => path.join(__dirname, pathUrl)
5 |
6 | module.exports = {
7 | webpack: {
8 | alias: {
9 | '@': pathResolve('src'),
10 | '@assets': pathResolve('src/assets'),
11 | '@components': pathResolve('src/components'),
12 | },
13 | },
14 | resolve:{
15 | extensions:['.js','.jsx','.json']
16 | },
17 | module: {
18 | rules: []
19 | },
20 | plugins: [{
21 | plugin: CracoLessPlugin,
22 | options: {
23 | lessLoaderOptions: {
24 | lessOptions: {
25 | modifyVars: {
26 | '@primary-color': '#3e7fee'
27 | },
28 | javascriptEnabled: true,
29 | },
30 | },
31 | },
32 | }, ],
33 | };
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-09-28 21:45:10
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-11 13:43:50
8 | */
9 | import React, { Component } from 'react';
10 | import 'antd/dist/antd.less';
11 | import 'zent/css/index.css';
12 | import Login from './pages/login/Login';
13 | import admin from './pages/admin/admin';
14 | import { HashRouter, Route, Switch } from 'react-router-dom';
15 | class App extends Component {
16 | render() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/src/components/help-icon/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames';
2 | import React from 'react';
3 | import { Icon, IIconProps, IPopCommonProps, Pop } from 'zent';
4 |
5 | interface IProps extends IPopCommonProps {
6 | iconType?: IIconProps['type'];
7 | iconClassName?: string;
8 | iconStyle?: IIconProps['style'];
9 | spin?: boolean;
10 | }
11 |
12 | export const HelpIcon: React.FC = (props) => {
13 | const { iconType = 'help-circle-o', iconClassName, iconStyle, className,spin, ...popProps } = props;
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default HelpIcon;
23 |
--------------------------------------------------------------------------------
/src/components/zent/IME.tsx:
--------------------------------------------------------------------------------
1 | import { IMEComposition, Input, Button } from "zent";
2 | import * as React from "react";
3 |
4 | function JJIME() {
5 | const [enable, setEnable] = React.useState(true);
6 | const [text, setText] = React.useState("");
7 |
8 | const onInputChange = (e: any) => {
9 | setText(e.target.value);
10 | };
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 | );
23 | }
24 |
25 | export default JJIME
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./path.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "noUnusedParameters": false,
20 | "suppressImplicitAnyIndexErrors": true,
21 | "noEmit": true,
22 | "noImplicitAny": false,
23 | "noUnusedLocals": false,
24 | "jsx": "react",
25 | "isolatedModules": true
26 | },
27 | "include": [
28 | "src",
29 | "craco.config.js"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/ts-study/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Alert } from "zent";
3 | import ReactMarkdown from "react-markdown";
4 | // @ts-ignore
5 | import apiMd from "./tstype.md";
6 | import CodeBlock from "./code-block";
7 |
8 | export const TS: React.FC = () => {
9 | const [text, setText] = React.useState("## text");
10 | React.useEffect(() => {
11 | fetch(apiMd)
12 | .then((res) => res.text())
13 | .then((text) => setText(text));
14 | }, []);
15 | return (
16 | <>
17 |
18 |
25 | >
26 | );
27 | };
28 |
29 | export default TS;
30 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-14 21:16:42
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-28 00:02:53
8 | */
9 | import React from 'react';
10 | import ReactDOM from 'react-dom';
11 | import { Provider } from 'react-redux';
12 | import App from './App';
13 | import './index.css';
14 | import store from './redux/store';
15 | import * as serviceWorker from './serviceWorker';
16 |
17 | ReactDOM.render(
18 |
19 |
20 | ,
21 | document.getElementById('root')
22 | );
23 |
24 | // If you want your app to work offline and load faster, you can change
25 | // unregister() to register() below. Note this comes with some pitfalls.
26 | // Learn more about service workers: https://bit.ly/CRA-PWA
27 | serviceWorker.unregister();
28 |
--------------------------------------------------------------------------------
/src/utils/StorageUtils.ts:
--------------------------------------------------------------------------------
1 | import { UserModel } from './../pages/user/model';
2 | /*
3 | * @Descripttion:
4 | * @version:
5 | * @Author: alexwjj
6 | * @Date: 2020-10-14 21:16:42
7 | * @LastEditors: alexwjj
8 | * @LastEditTime: 2021-02-02 21:23:04
9 | */
10 |
11 | import store from 'store';
12 |
13 | const USER_KEY = 'user_key';
14 |
15 | export type LoginUser = Partial;
16 |
17 | export default {
18 | saveUser(user: LoginUser) {
19 | // localStorage.setItem(USER_KEY, JSON.stringify(user));
20 | store.set(USER_KEY, user);
21 | },
22 |
23 | getUser(): LoginUser {
24 | // return JSON.parse(localStorage.getItem(USER_KEY) ?? '{}');
25 | return store.get(USER_KEY) || {};
26 | },
27 |
28 | removeUser() {
29 | // localStorage.removeItem(USER_KEY);
30 | store.remove(USER_KEY);
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/src/api/Model.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-11-28 16:40:32
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2020-12-15 23:15:09
8 | */
9 | //自定义返回类型
10 | export interface ResponseValue {
11 | flag?: boolean;
12 | status?: number;
13 | message?: string;
14 | data?: T;
15 | }
16 |
17 | //分页数据模型
18 | export interface PageSplitModel {
19 | endRow: number;
20 | hasNextPage: boolean;
21 | hasPreviousPage: boolean;
22 | isFirstPage: boolean;
23 | isLastPage: boolean;
24 | list: T[] | undefined;
25 | navigateFirstPage: number;
26 | navigateLastPage: number;
27 | navigatePages: number;
28 | navigatepageNums: number[];
29 | nextPage: number;
30 | pageNum: number;
31 | pageSize: number;
32 | pages: number;
33 | prePage: number;
34 | size: number;
35 | startRow: number;
36 | total: number;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/content-title/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from "classnames";
2 | import React from "react";
3 | import "./style/index.less";
4 | import { Icon,IIconProps } from "zent";
5 |
6 | interface IProps {
7 | title: string;
8 | iconType?: IIconProps['type'];
9 | isShowIcon?: boolean;
10 | iconClassName?: string;
11 | titleClassName?: string;
12 | }
13 |
14 |
15 | export const ContentTitle: React.FC = (props) => {
16 | const { title, iconType = 'youzan', isShowIcon = false , iconClassName, titleClassName, ...popProps } = props;
17 |
18 | return (
19 |
20 | {title}
21 | {isShowIcon && }
26 |
27 | );
28 | };
29 |
30 | export default ContentTitle;
31 |
--------------------------------------------------------------------------------
/src/components/affix-tabs-nav/style/index.scss:
--------------------------------------------------------------------------------
1 | @import "vars/colors";
2 |
3 | .zent-affix.rc__affix-tabs-nav__wrap {
4 | @include theme-color(background-color, stroke, 9);
5 |
6 | .zent-tabs-nav {
7 | height: 48px;
8 | line-height: 48px;
9 | border: none;
10 | }
11 |
12 | .zent-tabs-nav-content {
13 | .zent-tabs-tab {
14 | @include theme-border-right(1px, solid, stroke, 6);
15 |
16 | &:last-child {
17 | border-right: none;
18 | }
19 | }
20 | }
21 |
22 | .zent-tabs-tab,
23 | .zent-tabs-tab-inner {
24 | border: none;
25 | }
26 |
27 | .zent-tabs-tab-inner {
28 | height: 48px;
29 | line-height: 48px;
30 | font-weight: 500;
31 | }
32 |
33 | .zent-tabs-tab__actived {
34 | @include theme-color(color, primary, 4);
35 |
36 | .zent-tabs-tab-inner {
37 | border: none;
38 | }
39 | }
40 |
41 | &--pined {
42 | @include theme-border-bottom(1px, solid, stroke, 6);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useReducer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from "react";
2 | import { Button } from "zent";
3 |
4 | const myReducer = (state, action) => {
5 | switch(action.type) {
6 | case('countUp'):
7 | return {
8 | ...state,
9 | count: state.count + 1
10 | }
11 | case('countDown'):
12 | return {
13 | ...state,
14 | count: state.count - 1
15 | }
16 | default:
17 | return state
18 | }
19 | }
20 |
21 | function ReducerDemo() {
22 | const [state, dispatch] = useReducer(myReducer, { count: 0 })
23 |
24 | return (
25 |
26 |
29 |
32 |
Count: {state.count}
33 |
34 | );
35 | }
36 |
37 |
38 | export default ReducerDemo;
39 |
--------------------------------------------------------------------------------
/src/components/zent/affix.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Affix, Alert } from "zent";
3 |
4 | interface IProps {
5 | offsetTop?: number;
6 | title?: string;
7 | }
8 |
9 | class JJAffix extends React.Component {
10 | static defaultProps = {
11 | offsetTop: 0,
12 | title: "默认标题",
13 | };
14 |
15 | constructor(props: IProps) {
16 | super(props);
17 | this.state = {
18 | text: this.props.title,
19 | }
20 | }
21 |
22 | // 页面滚动 固定时的操作
23 | onPin = () => {
24 | this.setState({
25 | text: '固定在位置上啦'
26 | })
27 | };
28 | // 页面还原到 改图钉原有位置
29 | onUnpin = () => {
30 | this.setState({
31 | text: '回到原来的位置啦'
32 | })
33 | };
34 |
35 | render() {
36 | return (
37 |
38 | {this.props.title}
39 |
40 | );
41 | }
42 | }
43 |
44 | export default JJAffix;
45 |
--------------------------------------------------------------------------------
/src/pages/product/media.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | interface Props {
4 | contentState: any;
5 | block: any;
6 | blockProps: any;
7 | }
8 |
9 | interface Sate {}
10 |
11 | export const myBlockRenderer = (contentBlock: any): any => {
12 | const type = contentBlock.getType();
13 |
14 | // 图片类型转换为mediaComponent
15 | if (type === 'atomic') {
16 | return {
17 | component: Media,
18 | editable: false,
19 | props: {
20 | foo: 'bar',
21 | },
22 | };
23 | }
24 | };
25 |
26 | class Media extends Component {
27 | render() {
28 | const { block, contentState } = this.props;
29 | const data = contentState.getEntity(block.getEntityAt(0)).getData();
30 | const emptyHtml = ' ';
31 | return (
32 |
33 | {emptyHtml}
34 |

39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/zent/Infinite.tsx:
--------------------------------------------------------------------------------
1 | import { InfiniteScroller, Card } from 'zent';
2 | import * as React from 'react';
3 | export default class JInfinite extends React.Component {
4 | state = {
5 | list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
6 | };
7 |
8 | loadMore(closeLoading) {
9 | const { list } = this.state;
10 | const latestList = list.slice(list.length - 10);
11 | const newList = latestList.map(item => item + 10);
12 |
13 | setTimeout(() => {
14 | this.setState({
15 | list: [...list, ...newList],
16 | });
17 | closeLoading && closeLoading();
18 | }, 500);
19 | }
20 |
21 | render() {
22 | const { list } = this.state;
23 | return (
24 |
29 | {list.map(item => (
30 | {item}
31 | ))}
32 |
33 | );
34 | }
35 | }
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useRouter.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./index.less";
3 | import {
4 | useHistory,
5 | useLocation,
6 | useParams,
7 | useRouteMatch,
8 | } from "react-router-dom";
9 | import { Button, Icon } from "zent";
10 |
11 | interface IProps {
12 | title?: string;
13 | }
14 |
15 | export const UseRouter: React.FC = () => {
16 | const history = useHistory();
17 | console.log(useHistory(), "useHistory");
18 | console.log(useLocation(), "useLocation");
19 | //useParams存储动态路由参数的地方,此时没有使用动态路由,也没有路由传参,无任何输出
20 | console.log(useParams(), "useParams");
21 | console.log(useRouteMatch(), "useRouteMatch");
22 | const handleGo = (path: string) => {
23 | history.push(path);
24 | };
25 | return (
26 | <>
27 |
28 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export default UseRouter;
38 |
--------------------------------------------------------------------------------
/src/utils/DateUtils.tsx:
--------------------------------------------------------------------------------
1 | export function formatDate(timeBySeconds: number) {
2 | let time = new Date(timeBySeconds);
3 | return (
4 | time.getFullYear() +
5 | '-' +
6 | (time.getMonth() + 1) +
7 | '-' +
8 | time.getDate() +
9 | ' ' +
10 | time.getHours() +
11 | ':' +
12 | time.getMinutes() +
13 | ':' +
14 | time.getSeconds()
15 | );
16 | }
17 |
18 | export function formatDateByString(date: Date, fmt: string) {
19 | let o = {
20 | 'M+': date.getMonth() + 1, //月份
21 | 'd+': date.getDate(), //日
22 | 'h+': date.getHours(), //小时
23 | 'm+': date.getMinutes(), //分
24 | 's+': date.getSeconds(), //秒
25 | 'q+': Math.floor((date.getMonth() + 3) / 3), //季度
26 | 'S': date.getMilliseconds(), //毫秒
27 | };
28 | if (/(y+)/.test(fmt)) {
29 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
30 | }
31 | for (var k in o) {
32 | if (new RegExp('(' + k + ')').test(fmt)) {
33 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length));
34 | }
35 | }
36 | return fmt;
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/zent/data.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | LayoutRow as Row,
5 | LayoutCol as Col,
6 | LayoutGrid as Grid,
7 | LayoutConfigProvider as ConfigProvider,
8 | } from "zent";
9 | import JJForm from "../../components/zent/form";
10 |
11 | import { Alert } from "zent";
12 |
13 | class Data extends React.Component {
14 | state = {
15 | current: 1,
16 | };
17 | render() {
18 | return (
19 | <>
20 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | >
43 | );
44 | }
45 | }
46 |
47 | export default Data;
48 |
--------------------------------------------------------------------------------
/src/pages/not-found/not-found.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-02-11 01:17:41
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-11 01:56:53
8 | */
9 |
10 | import { Button, Col, Row } from 'antd';
11 | import React, { useEffect } from 'react';
12 | import { useDispatch } from 'react-redux';
13 | import { useHistory } from 'react-router';
14 | import { setHeadTitle } from '../../redux/actions';
15 | import './not-found.scss';
16 |
17 | const NotFound = () => {
18 | const history = useHistory();
19 | const dispatch = useDispatch();
20 | const goHome = () => {
21 | dispatch(setHeadTitle('首页'));
22 | history.push('/home');
23 | };
24 |
25 | useEffect(()=>{
26 | dispatch(setHeadTitle('404'))
27 | })
28 | return (
29 |
30 |
31 |
32 | 404
33 | 抱歉,您访问的页面不存在
34 |
35 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default NotFound;
45 |
--------------------------------------------------------------------------------
/src/components/zent/collapse.tsx:
--------------------------------------------------------------------------------
1 | import { Collapse } from "zent";
2 | import * as React from "react";
3 | export default class JCollapse extends React.Component {
4 | state = {
5 | activeKey: "1",
6 | };
7 |
8 | handleChange(activeKey) {
9 | this.setState({
10 | activeKey,
11 | });
12 | }
13 |
14 | render() {
15 | const { activeKey } = this.state;
16 | return (
17 |
22 |
23 | 紫陌寻春去,红尘拂面来。无人不道看花回。惟见石榴新蕊、一枝开。
24 | 冰簟堆云髻,金尊滟玉醅。绿阴青子相催。留取红巾千点、照池台。
25 |
26 |
27 | 孤馆灯青,野店鸡号,旅枕梦残。渐月华收练,晨霜耿耿,云山摛锦,朝露漙漙。世路无穷,劳生有限,似此区区长鲜欢。微吟罢,凭征鞍无语,往事千端。
28 | 当时共客长安。似二陆初来俱少年。有笔头千字,胸中万卷,致君尧舜,此事何难。用舍由时,行藏在我,袖手何妨闲处看。身长健,但优游卒岁,且斗尊前。
29 |
30 |
31 | 缥缈红妆照浅溪。薄云疏雨不成泥。送君何处古台西。
32 | 废沼夜来秋水满,茂林深处晚莺啼。行人肠断草凄迷。
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/zent/way-point.tsx:
--------------------------------------------------------------------------------
1 | import { Waypoint, Icon } from "zent";
2 | import * as React from "react";
3 |
4 | function JJWayPoint() {
5 | const [msg, setMsg] = React.useState("");
6 | const setEnterMsg = React.useCallback(() => setMsg("Waypoint 进入屏幕"), []);
7 | const setLeaveMsg = React.useCallback(() => setMsg("Waypoint 离开屏幕"), []);
8 |
9 | return (
10 | <>
11 | {msg}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | >
31 | );
32 | }
33 |
34 | function Spacer() {
35 | return (
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | export default JJWayPoint
43 |
--------------------------------------------------------------------------------
/src/components/header/index.less:
--------------------------------------------------------------------------------
1 | .header {
2 | // height: 80px;
3 | background-color: white;
4 | .header-top {
5 | height: 60px;
6 | text-align: right;
7 | padding-right: 30px;
8 | line-height: 60px;
9 | box-shadow:0px 0px 3px #000;
10 | span {
11 | margin-right: 10px;
12 | }
13 | }
14 |
15 | .header-bottom {
16 | height: 40px;
17 | display: flex;
18 | align-items: center;
19 |
20 | .header-bottom-left {
21 | position: relative;
22 | width: 25%;
23 | text-align: center;
24 |
25 | &::after {
26 | content: '';
27 | position: absolute;
28 | right: 50%;
29 | transform: translateX(50%);
30 | top: 100%;
31 | border-top: 20px solid white;
32 | border-right: 20px solid transparent;
33 | border-left: 20px solid transparent;
34 | border-bottom: 20px solid transparent;
35 |
36 | }
37 | }
38 |
39 | .header-bottom-right {
40 | width: 75%;
41 | text-align: right;
42 | padding-right: 30px;
43 |
44 | img {
45 | margin: 0 15px;
46 | width: 30px;
47 | height: 20px;
48 | }
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/pages/ts-study/code-block.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import PropTypes from "prop-types";
3 | import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
4 | // 设置高亮样式
5 | import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
6 | // 设置高亮的语言
7 | import {
8 | jsx,
9 | javascript,
10 | } from "react-syntax-highlighter/dist/esm/languages/prism";
11 |
12 | class CodeBlock extends PureComponent {
13 | static propTypes = {
14 | value: PropTypes.string.isRequired,
15 | language: PropTypes.string,
16 | };
17 |
18 | static defaultProps = {
19 | language: null,
20 | };
21 |
22 | componentWillMount() {
23 | // 注册要高亮的语法,
24 | // 注意:如果不设置打包后供第三方使用是不起作用的
25 | SyntaxHighlighter.registerLanguage("jsx", jsx);
26 | SyntaxHighlighter.registerLanguage("javascript", javascript);
27 | }
28 |
29 | render() {
30 | // @ts-ignore
31 | const { language, value } = this.props;
32 | return (
33 |
34 |
35 | {value}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default CodeBlock;
43 |
--------------------------------------------------------------------------------
/public/css/reset.css:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
2 | html,
3 | body,
4 | p,
5 | ol,
6 | ul,
7 | li,
8 | dl,
9 | dt,
10 | dd,
11 | blockquote,
12 | figure,
13 | fieldset,
14 | legend,
15 | textarea,
16 | pre,
17 | iframe,
18 | hr,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | h5,
24 | h6 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 | h1,
30 | h2,
31 | h3,
32 | h4,
33 | h5,
34 | h6 {
35 | font-size: 100%;
36 | font-weight: normal;
37 | }
38 |
39 | ul {
40 | list-style: none;
41 | }
42 |
43 | button,
44 | input,
45 | select,
46 | textarea {
47 | margin: 0;
48 | }
49 |
50 | html {
51 | box-sizing: border-box;
52 | }
53 |
54 | *,
55 | *::before,
56 | *::after {
57 | box-sizing: inherit;
58 | }
59 |
60 | img,
61 | video {
62 | height: auto;
63 | max-width: 100%;
64 | }
65 |
66 | iframe {
67 | border: 0;
68 | }
69 |
70 | table {
71 | border-collapse: collapse;
72 | border-spacing: 0;
73 | }
74 |
75 | td,
76 | th {
77 | padding: 0;
78 | }
79 |
80 | td:not([align]),
81 | th:not([align]) {
82 | text-align: left;
83 | }
84 |
85 | html,
86 | body {
87 | height: 100%;
88 | width: 100%;
89 | }
90 |
91 | #root {
92 | height: 100%;
93 | width: 100%;
94 | }
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { Button } from "zent";
3 | interface IUser {
4 | username: string;
5 | }
6 |
7 | const AppContext = React.createContext({ username: "" });
8 |
9 | const Navbar = () => {
10 | const { username } = useContext(AppContext);
11 |
12 | return (
13 |
14 |
子组件 username 001:{username}
15 |
16 | );
17 | };
18 |
19 | const Messages = () => {
20 | const { username } = useContext(AppContext);
21 |
22 | return (
23 |
24 |
子组件 username 002:{username}
25 |
26 | );
27 | };
28 |
29 | function ContextDemo() {
30 | const [user, setUser] = useState({ username: "lxy" });
31 | return (
32 |
33 |
34 | 父组件传递了一个默认username:lxy
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default ContextDemo;
48 |
--------------------------------------------------------------------------------
/src/components/zent/drawer.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Drawer, Radio } from "zent";
2 | import * as React from "react";
3 |
4 | const RadioGroup = Radio.Group;
5 |
6 | function JDrawer() {
7 | const [visible, setVisible] = React.useState(false);
8 | const [placement, setPlacement] = React.useState("top");
9 |
10 | return (
11 | <>
12 | setPlacement(e.target.value || "top")}
14 | value={placement}
15 | >
16 | top
17 | right
18 | bottom
19 | left
20 |
21 |
28 | setVisible(false)}
31 | // @ts-ignore
32 | placement={placement}
33 | maskClosable
34 | >
35 | Drawer Content ...
36 | Drawer Content ...
37 | Drawer Content ...
38 |
39 | >
40 | );
41 | }
42 |
43 | export default JDrawer;
44 |
--------------------------------------------------------------------------------
/src/redux/reducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import StorageUtils, { LoginUser } from '../utils/StorageUtils';
3 | import { SET_HEAD_TITLE, RECEIVE_USER, SHOW_ERROR_MSG, RESET_USER } from './action-types';
4 | /*
5 | * @Descripttion:
6 | * @version:
7 | * @Author: alexwjj
8 | * @Date: 2021-01-27 23:37:19
9 | * @LastEditors: alexwjj
10 | * @LastEditTime: 2021-02-02 23:46:10
11 | */
12 |
13 | //管理头部标题
14 | const initHeadTitle = '首页';
15 |
16 | function headTitle(state: string = initHeadTitle, action: { type: any; payload: string }) {
17 | switch (action.type) {
18 | case SET_HEAD_TITLE:
19 | return action.payload;
20 | default:
21 | return state;
22 | }
23 | }
24 |
25 | //管理登录用户
26 | const initUser = StorageUtils.getUser();
27 |
28 | function user(state: LoginUser = initUser, action: { type: any; payload: LoginUser }) {
29 | switch (action.type) {
30 | case RECEIVE_USER:
31 | return action.payload;
32 | case SHOW_ERROR_MSG:
33 | const errorMsg = action.payload.errorMsg;
34 | return { ...state, errorMsg };
35 | case RESET_USER:
36 | StorageUtils.removeUser();
37 | return {};
38 | default:
39 | return state;
40 | }
41 | }
42 |
43 | const rootReducer = combineReducers({
44 | headTitle,
45 | user,
46 | });
47 |
48 | export default rootReducer;
49 |
--------------------------------------------------------------------------------
/src/components/zent/portal.tsx:
--------------------------------------------------------------------------------
1 | import { Portal, Button } from "zent";
2 | import * as React from "react";
3 |
4 | function JJPortal() {
5 | const [bodyPortalVisible, setBodyPortalVisible] = React.useState(false);
6 | const showBodyPortal = () => setBodyPortalVisible(true);
7 | const hideBodyPortal = () => setBodyPortalVisible(false);
8 | return (
9 |
10 |
11 |
21 |
34 | 默认插入到body最后,并设置为拥有遮罩用于关闭
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | export default JJPortal;
42 |
--------------------------------------------------------------------------------
/src/pages/chars/Bar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Card, Button } from 'antd';
3 | import ReactEcharts from 'echarts-for-react';
4 |
5 | /*
6 | 后台管理的柱状图路由组件
7 | */
8 | const Bar = () => {
9 | const [sales, setSales] = useState([5, 20, 36, 10, 10, 20]);
10 | const [stores, setStore] = useState([6, 10, 25, 20, 15, 10]);
11 |
12 | const update = () => {
13 | setSales(sales.map((e) => e + 1));
14 | setStore(stores.map((e) => e - 1));
15 | };
16 |
17 | /*
18 | 返回柱状图的配置对象
19 | */
20 | const getOption = (sales: any, stores: any) => {
21 | return {
22 | title: {
23 | text: 'ECharts 入门示例',
24 | },
25 | tooltip: {},
26 | legend: {
27 | data: ['销量', '库存'],
28 | },
29 | xAxis: {
30 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
31 | },
32 | yAxis: {},
33 | series: [
34 | {
35 | name: '销量',
36 | type: 'bar',
37 | data: sales,
38 | },
39 | {
40 | name: '库存',
41 | type: 'bar',
42 | data: stores,
43 | },
44 | ],
45 | };
46 | };
47 |
48 | return (
49 |
50 |
51 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | };
62 | export default Bar;
--------------------------------------------------------------------------------
/src/api/ajax.ts:
--------------------------------------------------------------------------------
1 | import { ReqMethodEnum } from './ReqMethodEnum';
2 | /*
3 | * @Descripttion:
4 | * @version:
5 | * @Author: alexwjj
6 | * @Date: 2020-09-28 21:45:10
7 | * @LastEditors: alexwjj
8 | * @LastEditTime: 2021-01-23 21:57:33
9 | */
10 | import { message } from 'antd';
11 | import Axios from 'axios';
12 |
13 | export default function ajax(url: string, data: {} = {}, method: ReqMethodEnum = ReqMethodEnum.GET): Promise {
14 | return new Promise((resolve, rejects) => {
15 | const headers = {
16 | 'content-Type': 'application/json;charset=UTF-8',
17 | };
18 | let promise: Promise;
19 | switch (method) {
20 | case ReqMethodEnum.GET:
21 | promise = Axios.get(url, { params: data, headers: headers });
22 | break;
23 | case ReqMethodEnum.POST:
24 | promise = Axios.post(url, data, { headers });
25 | break;
26 | case ReqMethodEnum.PUT:
27 | promise = Axios.put(url, data, { headers });
28 | break;
29 | case ReqMethodEnum.DELETE:
30 | promise = Axios.delete(url, { data, headers });
31 | break;
32 | default:
33 | promise = Axios.get(url, { params: data, headers });
34 | break;
35 | }
36 | promise
37 | .then((response: any) => {
38 | resolve(response.data);
39 | })
40 | .catch((error) => {
41 | message.error('请求出错:' + error.message);
42 | });
43 |
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/src/pages/todo/task-card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Card, Button } from "zent";
3 | // import { ITask } from "./types";
4 | import { TASK_BTN_TEXT, TASK_BTN_STYLE } from "./constants";
5 |
6 | // interface IProps extends ITask {
7 | // date?: string;
8 | // }
9 |
10 | function TaskCardItem(props) {
11 | const onStatusChange = React.useCallback(() => {
12 | props.onStatusChange(props.task);
13 | }, [props]);
14 | return (
15 | <>
16 |
27 | {TASK_BTN_TEXT[props.task.status]}
28 |
29 | }
30 | >
31 | {props.task.description}
32 |
33 | >
34 | );
35 | }
36 |
37 | function TaskCard(props) {
38 | return props.taskList?.length ? (
39 | props.taskList.map((task, index) => {
40 | return (
41 |
46 | );
47 | })
48 | ) : (
49 | 暂无任务
50 | );
51 | }
52 |
53 | export default TaskCard;
54 |
--------------------------------------------------------------------------------
/src/components/app-card/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { ClampLines } from 'zent';
3 | import './style/index.scss'
4 |
5 | interface IAppCard {
6 | href: string;
7 | icon?: string;
8 | title: string;
9 | desc: string;
10 | className?: string;
11 | imgColorClassName?: string;
12 | fontIcon?: string;
13 | onClick: (appName: string) => void;
14 | }
15 |
16 | // TODO: 修改 title 为 POP
17 | const AppCard: React.FC = (props) => {
18 | const { href, icon, title, desc, className = '', imgColorClassName = '', fontIcon, onClick } = props;
19 |
20 | const imgClassName = `app-card-wrapper-icon ${imgColorClassName}`;
21 |
22 | const iconImg = fontIcon ? (
23 | {fontIcon}
24 | ) : (
25 |
26 | );
27 |
28 | const onCardClick = useCallback(() => onClick(title), [onClick, title]);
29 |
30 | return (
31 |
32 | {iconImg}
33 |
37 |
38 | );
39 | };
40 |
41 | export default AppCard;
42 |
--------------------------------------------------------------------------------
/src/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Modal } from "antd";
2 | import React from "react";
3 | import { Alert } from "zent";
4 | import "./index.less";
5 |
6 | export const Home: React.FC = () => {
7 | const [isModalVisible, setIsModalVisible] = React.useState(false);
8 | const showModal = () => {
9 | setIsModalVisible(true);
10 | };
11 |
12 | const handleOk = () => {
13 | setIsModalVisible(false);
14 | };
15 |
16 | const handleCancel = () => {
17 | setIsModalVisible(false);
18 | };
19 |
20 | return (
21 | <>
22 |
23 |
26 |
35 | Some contents...
36 | Some contents...
37 | Some contents...
38 | Some contents...
39 | Some contents...
40 | Some contents...
41 | Some contents...
42 | Some contents...
43 | Some contents...
44 | Some contents...
45 | Some contents...
46 | Some contents...
47 |
48 | >
49 | );
50 | };
51 |
52 | export default Home;
53 |
--------------------------------------------------------------------------------
/src/components/zent/tree.tsx:
--------------------------------------------------------------------------------
1 | import { Tree } from 'zent';
2 | import * as React from 'react';
3 |
4 | const treeData = [{
5 | id: 1,
6 | title: '杭州有赞科技有限公司',
7 | children: [{
8 | id: 2,
9 | title: '技术',
10 | children: [{
11 | id: 3,
12 | title: '后端',
13 | children: [{
14 | id: 7,
15 | title: 'JAVA'
16 | }, {
17 | id: 8,
18 | title: 'PHP'
19 | }, {
20 | id: 9,
21 | title: 'GO'
22 | }, {
23 | id: 10,
24 | title: '.NET'
25 | }]
26 | }, {
27 | id: 4,
28 | title: '运维'
29 | }, {
30 | id: 5,
31 | title: '前端'
32 | }]
33 | }, {
34 | id: 6,
35 | title: '产品'
36 | }]
37 | }];
38 |
39 | export default class JJTree extends React.Component {
40 | state = {
41 | checkedKeys: [3, 5, 22],
42 | disabledCheckedKeys: [4, 7, 9, 22]
43 | }
44 |
45 | onCheck = (checked: boolean, helpInfo: object) => {
46 | this.setState({
47 | checkedKeys: checked
48 | });
49 | }
50 |
51 | render() {
52 | const { checkedKeys, disabledCheckedKeys } = this.state;
53 |
54 | return (
55 |
56 |
65 |
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useRef.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useState, useEffect, useRef } from "react";
3 | import { Button, BlockHeader } from "zent";
4 | import Child from "./child-class";
5 | interface IPropsText {
6 | value: string;
7 | }
8 | type IProps = Partial;
9 |
10 | // function Child(props: IProps) {
11 | // const handleLog = () => {
12 | // Notify.info("点击了子组件");
13 | // };
14 | // return (
15 | //
18 | // );
19 | // }
20 | function UseRef(props: IProps) {
21 | const ref1 = useRef();
22 | const ref2 = useRef();
23 | const [value, setValue] = useState("default");
24 |
25 | useEffect(() => {
26 | console.log(ref2, 'ref');
27 | }, [ref2]);
28 |
29 | const onChange = (e) => {
30 | setValue(e.target.value);
31 | };
32 | const onNotice = () => {
33 | // @ts-ignore
34 | ref2.current.handleLog();
35 | };
36 |
37 | return (
38 |
39 |
40 | {/* @ts-ignore */}
41 |
42 |
43 | {/* @ts-ignore */}
44 |
45 | {/* @ts-ignore */}
46 |
49 |
50 | );
51 | }
52 |
53 | export default UseRef;
54 |
--------------------------------------------------------------------------------
/src/pages/chars/Line.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-01 19:15:01
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-07 13:42:36
8 | */
9 | import React, { useState } from 'react';
10 | import { Card, Button } from 'antd';
11 | import ReactEcharts from 'echarts-for-react';
12 |
13 | /*
14 | 后台管理的柱状图路由组件
15 | */
16 | const Line = () => {
17 | const [sales, setSales] = useState([5, 20, 36, 10, 10, 20]);
18 | const [stores, setStore] = useState([6, 10, 25, 20, 15, 10]);
19 |
20 | const update = () => {
21 | setSales(sales.map((e) => e + 1));
22 | setStore(stores.map((e) => e - 1));
23 | };
24 |
25 | /*
26 | 返回柱状图的配置对象
27 | */
28 | const getOption = (sales: any, stores: any) => {
29 | return {
30 | title: {
31 | text: 'ECharts 入门示例',
32 | },
33 | tooltip: {},
34 | legend: {
35 | data: ['销量', '库存'],
36 | },
37 | xAxis: {
38 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
39 | },
40 | yAxis: {},
41 | series: [
42 | {
43 | name: '销量',
44 | type: 'line',
45 | data: sales,
46 | },
47 | {
48 | name: '库存',
49 | type: 'line',
50 | data: stores,
51 | },
52 | ],
53 | };
54 | };
55 |
56 | return (
57 |
58 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | };
70 | export default Line;
--------------------------------------------------------------------------------
/src/pages/login/login.less:
--------------------------------------------------------------------------------
1 | .login {
2 | width: 100vw;
3 | height: 100vh;
4 | background-size: cover;
5 | position: relative;
6 | display: flex;
7 | justify-content: center;
8 | overflow: hidden;
9 | background-image: linear-gradient(125deg,#F44336,#E91E63,#9C27B0,#3F51B5,#2196F3);
10 | background-size: 400%;
11 | font-family: "montserrat";
12 | animation: bganimation 15s infinite;
13 | @keyframes bganimation{
14 | 0%{
15 | background-position: 0% 50%;
16 | }
17 | 50%{
18 | background-position: 100% 50%;
19 | }
20 | 100%{
21 | background-position: 0% 50%;
22 | }
23 | }
24 | .login-logo{
25 | position: absolute;
26 | top: 20px;
27 | left: 20px;
28 | width: 100px;
29 | height: 100px;
30 | background-image: url(../../assets/images/logo.png);
31 | background-size: cover;
32 |
33 | }
34 | .login-content{
35 | width: 400px;
36 | height: 320px;
37 | border: 1px solid white;
38 | box-shadow:2px 4px 6px #000;
39 | margin-top: 40vh;
40 | display: flex;
41 | justify-content: center;
42 | padding-top: 30px;
43 | background-color: white;
44 | // align-items: center;
45 | }
46 | .login-form{
47 | width:100%;
48 | &-title{
49 | font-size: 20px;
50 | font-weight: 700;
51 | text-align: center;
52 | margin-bottom: 12px;
53 | }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/redux/actions.ts:
--------------------------------------------------------------------------------
1 | import { reqRoleById } from './../api/index';
2 | import { reqLogin } from '../api';
3 | import { RECEIVE_USER, RESET_USER, SET_HEAD_TITLE, SHOW_ERROR_MSG } from './action-types';
4 | import StorageUtils, { LoginUser } from '../utils/StorageUtils';
5 | import { createAction } from 'typesafe-actions';
6 | /*
7 | * @Descripttion: 包含多个action creator的函数模块
8 | * @version:
9 | * @Author: alexwjj
10 | * @Date: 2021-01-27 23:38:20
11 | * @LastEditors: alexwjj
12 | * @LastEditTime: 2021-02-02 23:48:19
13 | */
14 | export const setHeadTitle = createAction(SET_HEAD_TITLE)();
15 |
16 | export const receiveUser = createAction(RECEIVE_USER)();
17 |
18 | export const showErrorMsg = createAction(SHOW_ERROR_MSG)();
19 |
20 | export const logout = createAction(RESET_USER)();
21 |
22 | export const login = (username: string, password: string) => async (dispatch: any) => {
23 | const result = await reqLogin(username, password);
24 | if (result.status === 0) {
25 | const user = result.data;
26 | if (user) {
27 | const role = await reqRoleById(user.roleId ?? '');
28 | StorageUtils.saveUser({
29 | id: user.id ?? -1,
30 | name: user.name ?? '',
31 | menus: role.menus?.split(',') ?? [],
32 | roleId: role.id?.toString() ?? '',
33 | });
34 | dispatch(receiveUser({ id: user.id, name: user.name, roleId: user.roleId, menus: role.menus?.split(',') }));
35 | } else {
36 | dispatch(showErrorMsg({errorMsg:'用户名或密码错误'}));
37 | }
38 | } else {
39 | dispatch(showErrorMsg({errorMsg:'用户名或密码错误'}));
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/src/components/zent/dialog.tsx:
--------------------------------------------------------------------------------
1 | import { Dialog, Button } from "zent";
2 | import * as React from "react";
3 | const { openDialog, closeDialog } = Dialog;
4 | const id = "my_dialog";
5 |
6 | export default class JDialog extends React.Component {
7 | state = { visible: false };
8 |
9 | setVisible = (visible) => {
10 | this.setState({ visible });
11 | };
12 |
13 | open = () => {
14 | openDialog({
15 | dialogId: id, // id is used to close the dialog
16 | title: "使用 openDialog 直接打开对话框",
17 | children: Hello World
,
18 | footer: ,
19 | onClose() {
20 | console.log("outer dialog closed");
21 | },
22 | });
23 | };
24 |
25 | componentDidMount() {
26 | console.log('componentDidMount');
27 | }
28 |
29 | shouldComponentUpdate(prevProps: any, nextState: any) {
30 | console.log(prevProps, nextState, "shouldComponentUpdate方法执行");
31 | return true;
32 | }
33 |
34 | render() {
35 | return (
36 |
37 |
40 |
43 |
51 |
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 俊劫学习系统
10 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/zent/swiper.tsx:
--------------------------------------------------------------------------------
1 | import { Swiper, Button } from 'zent';
2 | import * as React from 'react';
3 |
4 | const pages = [1, 2, 3, 4, 5];
5 |
6 | export default class JSwiper extends React.Component {
7 | [x: string]: any;
8 |
9 | go = (index) => {
10 | this.swiper?.swipeTo(index);
11 | }
12 |
13 | prev = () => {
14 | this.swiper?.prev();
15 | }
16 |
17 | next = () => {
18 | this.swiper?.next();
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
this.swiper = swiper}
26 | className="swiper-demo-simple"
27 | >
28 | {
29 | pages.map((item, index) => {
30 | return {item}
;
31 | })
32 | }
33 |
34 |
35 | {
36 | pages.map((item, index) => {
37 | return (
38 |
45 | );
46 | })
47 | }
48 |
54 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
--------------------------------------------------------------------------------
/src/components/app-card/style/index.scss:
--------------------------------------------------------------------------------
1 |
2 | @media screen and (max-width: 1199px) {
3 | .app-card-wrapper {
4 | width: calc(((100% - 32px) / 3));
5 |
6 | &:nth-child(3n) {
7 | margin-right: 0;
8 | }
9 | }
10 | }
11 |
12 | @media screen and (min-width: 1200px) and (max-width: 1599px) {
13 | .app-card-wrapper {
14 | width: calc((100% - 48px) / 4);
15 |
16 | &:nth-child(4n) {
17 | margin-right: 0;
18 | }
19 | }
20 | }
21 |
22 | @media screen and (min-width: 1600px) {
23 | .app-card-wrapper {
24 | width: calc((100% - 64px) / 5);
25 |
26 | &:nth-child(5n) {
27 | margin-right: 0;
28 | }
29 | }
30 | }
31 |
32 | .app-card-wrapper {
33 | display: flex;
34 | align-items: center;
35 | border-radius: 2px;
36 | box-sizing: border-box;
37 | padding: 16px;
38 | overflow: hidden;
39 | text-decoration: none;
40 | height: 73px;
41 | cursor: pointer;
42 |
43 |
44 | &:hover {
45 | }
46 |
47 | &-icon {
48 | width: 40px;
49 | height: 40px;
50 | border-radius: 4px;
51 |
52 | }
53 |
54 | &-icon__blue {
55 | background-color: #1989fa;
56 | }
57 |
58 | &-icon__green {
59 | background-color: #00bf56;
60 | }
61 |
62 | &-font-icon {
63 | display: flex;
64 | justify-content: center;
65 | align-items: center;
66 | font-size: 20px;
67 |
68 | }
69 |
70 | &-content {
71 | margin-left: 10px;
72 | display: flex;
73 | width: calc(100% - 50px);
74 | flex-direction: column;
75 | justify-content: center;
76 |
77 | &-title {
78 | font-size: 14px;
79 | line-height: 20px;
80 |
81 | }
82 |
83 | &-desc {
84 | font-size: 12px;
85 | line-height: 18px;
86 |
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/test/api/Api.test.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-11-30 22:46:31
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-01-22 21:38:27
8 | */
9 | import ajax from '../../api/ajax';
10 | import { PageSplitModel, ResponseValue } from '../../api/Model';
11 | import { ReqMethodEnum } from '../../api/ReqMethodEnum';
12 | import { CategoryModel } from '../../pages/category/Model';
13 | import { ProductsModel } from '../../pages/product/Model';
14 | import { BASE_URL } from '../../utils/Constants';
15 |
16 | describe('test api', () => {
17 |
18 | test('test reqByProductsByName', async () => {
19 | const result = await ajax>('http://localhost:5000/api/products/searchByName', {
20 | name: '8',
21 | pageNum: 1,
22 | pageSize: 3,
23 | });
24 | if (result.total === 0) {
25 | expect(result.list?.length).toEqual(0);
26 | } else {
27 | expect(result.list?.length).toBeGreaterThan(0);
28 | }
29 | });
30 |
31 | test('test reqCategoryById ', async () => {
32 | const result = await ajax('http://localhost:5000/api/category/findCategoryById/5');
33 | expect(result.name).toEqual('手机');
34 | });
35 |
36 | test('update product images', async () => {
37 | const data = await ajax(BASE_URL + '/api/products/findById/1', ReqMethodEnum.GET);
38 | const result = await ajax>(BASE_URL + '/api/products/updateImages/1',{
39 | images: data.images.split(",")
40 | },ReqMethodEnum.PUT);
41 | expect(result.data).toEqual(1);
42 | });
43 |
44 | test('test delete images',async ()=>{
45 | const result = await ajax>(BASE_URL+"/deleteFile/imag.jpg",ReqMethodEnum.DELETE);
46 | console.log(result);
47 | })
48 | });
49 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/useCallback.tsx:
--------------------------------------------------------------------------------
1 | import "./index.less";
2 | import React, { useState, useEffect, useCallback } from "react";
3 | import { Button, Notify, Tag, Radio } from "zent";
4 |
5 | function Child({ onClick }) {
6 | const [list, setList] = useState([]);
7 | useEffect(() => {
8 | setList(onClick());
9 | Notify.info("子组件渲染");
10 | }, [onClick]);
11 |
12 | return (
13 |
14 | {list.map((item, index) => {
15 | return (
16 |
17 | 列表:{item};
18 |
19 | );
20 | })}
21 |
22 | );
23 | }
24 |
25 | function CallbackDemoAdd() {
26 | const [count, setCount] = useState(0);
27 | const [value, setValue] = useState(0);
28 | const [isUseCallback, setIsUse] = useState(false);
29 | const show = () => {
30 | return [count, count + 1, count + 2];
31 | };
32 |
33 | const showUse = useCallback(() => {
34 | return [count, count + 1, count + 2];
35 | }, [count]);
36 |
37 | const onChange = (e) => {
38 | setIsUse(e.target.value);
39 | };
40 |
41 | return (
42 |
43 |
44 | 点击两个按钮,都会触发子组件的重新渲染,但是child组件只依赖于count,也就是只有count变化的时候才去重新渲染。
45 |
46 |
47 |
48 | 不使用
49 | 使用useCallback
50 |
51 |
52 |
53 |
61 |
69 |
70 |
71 | );
72 | }
73 |
74 | export default CallbackDemoAdd;
75 |
--------------------------------------------------------------------------------
/src/pages/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import LifeCycle from "./lifeCycle";
3 | import { Button, Alert, BlockLoading } from "zent";
4 | import "./index.less";
5 | // import TestLoading from "./test-loading";
6 | // import Test from './test';
7 |
8 | // 定义 LifeCycle 组件的父组件
9 |
10 | export default class LifeCycleContainer extends React.Component {
11 | // state 也可以像这样用属性声明的形式初始化
12 | state = {
13 | text: "父组件的文本",
14 | display: "none",
15 | hideChild: true,
16 | loading: true,
17 | lifeCycle: "https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/",
18 | };
19 |
20 | // 点击按钮,修改父组件文本的方法
21 |
22 | changeText = () => {
23 | this.setState({
24 | text: "修改后的父组件文本",
25 | });
26 | };
27 |
28 | // 点击按钮,隐藏(卸载)LifeCycle 组件的方法
29 |
30 | hideChild = () => {
31 | this.setState({
32 | hideChild: !this.state.hideChild,
33 | });
34 | };
35 | onLoading = () => {
36 | this.setState({
37 | loading: false,
38 | });
39 | };
40 |
41 | render() {
42 | return (
43 | <>
44 | {/*
45 | */}
46 |
47 |
48 |
51 |
54 | {this.state.hideChild ? null : (
55 |
56 | )}
57 |
58 |
59 |
60 |
68 |
69 | >
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 
18 |
19 | # 个人学习项目
20 |
21 | - 尝试各种技术
22 | - 等项目搞复杂了,再搞搞优化
23 | - 基础代码源于[MFinnnne/react-admin](https://github.com/MFinnnne/react-admin-server)
24 | - 交流学习:[俊劫的个人博客](https://alexwjj.github.io/) 欢迎扫码加 V
25 |
26 | ## 技术栈
27 |
28 | - React
29 | - TypeScript
30 | - Zent
31 | - Antd
32 | - 微服务 - qiankun (TODO)
33 |
34 | ## 启动
35 |
36 | ```js
37 | yarn //安装依赖
38 |
39 | yarn dev //启动
40 |
41 | yarn build //打包
42 | ```
43 |
44 | ## 输出文章
45 |
46 | ### 1、[vue 转 react 不完全指北](https://juejin.cn/post/6953482028188860424) 已完成 2021-4-21
47 |
48 | 
49 |
50 | ### 2、[一名 vueCoder 总结的 React 基础](https://juejin.cn/post/6960556335092269063) 已完成 2021-5-10
51 | 
52 |
53 | ### 3、[一篇够用的TypeScript总结](https://juejin.cn/post/6981728323051192357) 已完成 2021-7-6
54 | 
55 |
56 | - react hooks Doing
57 | - node bff 开发实战
58 |
59 | ## QA
60 |
61 | ### 1、项目无法启动,提示 craco 版本过低
62 |
63 | 删除本地 node_modules,重新安装
64 |
65 | ### 2、craco配置别名不生效
66 |
67 | 需要在tsconfig中也配置下,因为别名会经过ts编译
68 |
69 |
70 | ### 2022-06-15更新
71 | - 启动craco会报错,原因使用了cnpm i ,使用yarn安装即可解决,记得先删除包
--------------------------------------------------------------------------------
/src/api/useFetchData.ts:
--------------------------------------------------------------------------------
1 | import { message } from 'antd';
2 | import Axios from 'axios';
3 | import { useState, useEffect } from 'react';
4 | import { ReqMethodEnum } from './ReqMethodEnum';
5 |
6 | /*
7 | * @Descripttion:
8 | * @version:
9 | * @Author: alexwjj
10 | * @Date: 2021-01-23 21:42:50
11 | * @LastEditors: alexwjj
12 | * @LastEditTime: 2021-01-30 22:16:52
13 | */
14 |
15 | interface FetChDataModel {
16 | url: string;
17 | requestType: ReqMethodEnum;
18 | params: {};
19 | }
20 |
21 | const headers = {
22 | 'content-Type': 'application/json;charset=UTF-8',
23 | };
24 |
25 | export default function useHackerNewsApi() {
26 | const [data, setData] = useState({ hits: [] });
27 | const [reqData, setUrl] = useState(null);
28 | const [isLoading, setIsLoading] = useState(false);
29 | const [isError, setIsError] = useState(false);
30 |
31 | useEffect(() => {
32 | if (reqData !== null) {
33 | const fetchData = async () => {
34 | setIsError(false);
35 | setIsLoading(true);
36 | const url = reqData?.url;
37 | let promise: Promise;
38 | switch (reqData?.requestType) {
39 | case ReqMethodEnum.GET:
40 | promise = Axios.get(url, { params: data, headers: headers });
41 | break;
42 | case ReqMethodEnum.POST:
43 | promise = Axios.post(url, data, { headers });
44 | break;
45 | case ReqMethodEnum.PUT:
46 | promise = Axios.put(url, data, { headers });
47 | break;
48 | case ReqMethodEnum.DELETE:
49 | promise = Axios.delete(url, { data, headers });
50 | break;
51 | default:
52 | promise = Axios.get(url, { params: data, headers });
53 | break;
54 | }
55 | promise
56 | .then((response: any) => {
57 | setData(response.data);
58 | setIsLoading(false);
59 | })
60 | .catch((error) => {
61 | message.error('请求出错:' + error.message);
62 | setIsError(true);
63 | });
64 | };
65 | fetchData();
66 | }
67 | }, [reqData?.url]);
68 |
69 | const doFetch = (data: FetChDataModel) => {
70 | setUrl(data);
71 | };
72 |
73 | return { data, isLoading, isError, doFetch };
74 | }
75 |
--------------------------------------------------------------------------------
/src/pages/demo/lifeCycle.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button } from "zent";
3 | interface IProps {
4 | text: string;
5 | count: number;
6 | }
7 | // 定义子组件
8 | export default class LifeCycle extends React.Component {
9 | constructor(props: any) {
10 | super(props);
11 | console.log("进入constructor");
12 | // state 可以在 constructor 里初始化
13 | this.state = { text: "子组件的文本" };
14 | }
15 | // 初始化/更新时调用
16 | static getDerivedStateFromProps(props: any, state: any) {
17 | console.log(props, state, "getDerivedStateFromProps方法执行");
18 |
19 | return {
20 | fatherText: props.text,
21 | };
22 | }
23 |
24 | // 初始化渲染时调用
25 |
26 | componentDidMount() {
27 | console.log("componentDidMount方法执行");
28 | }
29 |
30 | // 组件更新时调用
31 |
32 | shouldComponentUpdate(prevProps: any, nextState: any) {
33 | console.log(prevProps, nextState, "shouldComponentUpdate方法执行");
34 |
35 | return true;
36 | }
37 |
38 | // 组件更新时调用
39 |
40 | getSnapshotBeforeUpdate(prevProps: any, prevState: any) {
41 | console.log("getSnapshotBeforeUpdate方法执行");
42 |
43 | return "componentDidUpdated的第三个参数";
44 | }
45 |
46 | // 组件更新后调用
47 |
48 | componentDidUpdate(preProps: any, preState: any, valueFromSnapshot: any) {
49 | console.log("componentDidUpdate方法执行");
50 |
51 | console.log("从 getSnapshotBeforeUpdate 获取到的值是", valueFromSnapshot);
52 | }
53 |
54 | // 组件卸载时调用
55 |
56 | componentWillUnmount() {
57 | console.log("子组件的componentWillUnmount方法执行");
58 | }
59 |
60 | // 点击按钮,修改子组件文本内容的方法
61 |
62 | changeText = () => {
63 | this.setState({
64 | text: "修改后的子组件文本",
65 | });
66 | };
67 |
68 | render() {
69 | console.log("render方法执行");
70 |
71 | return (
72 |
73 |
76 |
77 |
{this.props.count}
78 |
79 |
{this.state.text}
80 |
81 |
{this.props.text}
82 |
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/test/api/test.http:
--------------------------------------------------------------------------------
1 | ###
2 | POST http://localhost:5000/api/login
3 | Accept: */*
4 | Cache-Control: no-cache
5 | Content-Type: application/json
6 |
7 | {
8 | "name": "admin",
9 | "password": "admin"
10 | }
11 | ###
12 | GET http://localhost:5000/hello
13 | ###
14 | GET http://api.map.baidu.com/telematics/v3/weather?location=%E5%B8%B8%E5%B7%9E&output=json&ak=3p49MVra6urFRGOT9s8UBWr2
15 |
16 | ###
17 | GET http://localhost:5000/api/category/list/0
18 |
19 | ###
20 |
21 | POST http://localhost:5000/api/category/add
22 | Accept: */*
23 | Cache-Control: no-cache
24 | Content-Type: application/json
25 |
26 | {
27 | "parentId": "0",
28 | "categoryName":"一级分类",
29 | "name": "乐器1"
30 | }
31 |
32 |
33 | ###
34 |
35 | POST http://localhost:5000/api/category/add
36 |
37 | Content-Type: application/json
38 | {
39 | "categoryName":"一级分类100",
40 | "parentId":"0"
41 | }
42 |
43 |
44 | ###
45 |
46 | PUT http://localhost:5000/api/category/update
47 | Accept: */*
48 | Cache-Control: no-cache
49 | Content-Type: application/json
50 |
51 | {
52 | "parentId": "0",
53 | "categoryName":"一级分类",
54 | "name": "乐器1"
55 | }
56 |
57 | ###
58 |
59 | POST http://localhost:5000/api/products/list
60 | Accept: */*
61 | Cache-Control: no-cache
62 | Content-Type: application/json
63 |
64 | {
65 | "pageNum":1,
66 | "pageSize":3
67 | }
68 |
69 | ###
70 | GET http://localhost:5000/api/products/searchByDesc/一部手机而已/1/2
71 |
72 |
73 | ###
74 |
75 | GET http://localhost:5000/api/products/searchByName/小米8/1/2
76 |
77 |
78 | ###
79 | GET http://localhost:5000/api/products/searchByName?name=8&pageNum=1&pageSize=3
80 |
81 | ###
82 | GET http://wthrcdn.etouch.cn/weather_mini?citykey=101010100
83 |
84 | ###
85 | GET http://localhost:5000/files/1.jpV
86 |
87 | ###
88 | GET http://localhost:5000/api/products/findById/1
89 |
90 | ###
91 | DELETE http://localhost:5000/deleteFile/1.jpg
92 |
93 | ###
94 | POST http://localhost:5000/api/role/createRole HTTP/1.1
95 | Accept: */*
96 | Cache-Control: no-cache
97 | Content-Type: application/json
98 |
99 | {
100 | "authName": "admin",
101 | "menus": "" ,
102 | "name": "asdasda",
103 | "v": 0,
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/pages/category/UpdateFrom.tsx:
--------------------------------------------------------------------------------
1 | import { Form, Input, message, Modal } from 'antd';
2 | import React, { Component } from 'react';
3 | import { reqUpdateCategory } from '../../api';
4 | import { ResponseValue } from '../../api/Model';
5 | import { ICategory } from './Model';
6 | import { ModalStatusCode } from './ModalStatusCode';
7 |
8 | interface IUpdateFormProps {
9 | category: ICategory;
10 | showStatus: ModalStatusCode;
11 | onCancel: () => void;
12 | updateCategory: (category:ICategory) => void;
13 | }
14 |
15 | interface IUpdateFormState {
16 | name: string | null;
17 | status: ModalStatusCode;
18 | }
19 |
20 | interface Values {
21 | title?: string;
22 | value?: string;
23 | }
24 |
25 | export default class UpdateFrom extends Component {
26 | private onCancel = (): void => {
27 | this.props.onCancel();
28 | };
29 |
30 | private CreateModalFrom = (): any => {
31 | const [form] = Form.useForm();
32 | const { category, updateCategory } = this.props;
33 | return (
34 | {
42 | form
43 | .validateFields()
44 | .then(async (values: Values) => {
45 | const result: ResponseValue = await reqUpdateCategory(category._id, category.parentId, values.title ?? ' ', category.categoryName);
46 | if (result.status === 0) {
47 | message.info('品类更新成功');
48 | //更新列表
49 | updateCategory(category);
50 | } else {
51 | message.error('品类更新失败');
52 | }
53 | this.onCancel();
54 | })
55 | .catch((info) => {
56 | console.log('Validate Failed:', info);
57 | });
58 | }}
59 | >
60 |
62 | {}
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | render() {
70 | return (
71 |
72 |
73 |
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/pages/todo/dialog.tsx:
--------------------------------------------------------------------------------
1 | import { Dialog, Button } from "zent";
2 | import React from "react";
3 | import { FormInputField, Form, FormStrategy, Validators } from "zent";
4 | interface IFormValue {
5 | title: string;
6 | description: string;
7 | }
8 | interface IProps {
9 | visible: boolean;
10 | type: string;
11 | taskInfo: any;
12 | onConfirmDialog: (value: IFormValue) => void;
13 | onCloseDialog(): void;
14 | }
15 |
16 | function TaskDialog(props: IProps) {
17 | const form = Form.useForm(FormStrategy.View);
18 |
19 | setTimeout(() => {
20 | if (props.type === "detail") {
21 | form.initialize(props.taskInfo);
22 | } else {
23 | form.clear();
24 | }
25 | }, 0);
26 |
27 | const resetForm = React.useCallback(() => {
28 | form.clear();
29 | form.model.clearError()
30 | props.onCloseDialog();
31 | }, [form, props]);
32 | const onSubmit = React.useCallback(
33 | (form) => {
34 | const value = form.getValue();
35 | props.onConfirmDialog(value);
36 | form.resetValue();
37 | },
38 | [props]
39 | );
40 |
41 | return (
42 |
43 |
84 |
85 | );
86 | }
87 |
88 | export default TaskDialog;
89 |
--------------------------------------------------------------------------------
/src/components/zent/table.tsx:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | import { Grid, Notify } from "zent";
3 | import React from 'react';
4 |
5 | interface IData {
6 | id: string,
7 | name: string,
8 | uv: number,
9 | stock: number,
10 | }
11 | const columns = [
12 | {
13 | title: "grid组件实现的table",
14 | name: "name",
15 | },
16 | {
17 | title: "访问量",
18 | name: "uv",
19 | },
20 | {
21 | title: "库存",
22 | name: "stock",
23 | },
24 | ];
25 |
26 | const pageSize = 5;
27 | const totalItem = 10;
28 |
29 | const datasets: IData[] = [];
30 | const datasets2: IData[] = [];
31 |
32 | for (let i = 0; i < 5; i++) {
33 | datasets.push({
34 | id: `f-${i}`,
35 | name: `母婴商品 ${i}`,
36 | uv: 20,
37 | stock: 5,
38 | });
39 | datasets2.push({
40 | id: `s-${i}`,
41 | name: `宠物商品 ${i}`,
42 | uv: 20,
43 | stock: 5,
44 | });
45 | }
46 | interface IState {
47 | selectedRowKeys: any,
48 | datasets: IData[],
49 | current: number
50 | }
51 |
52 | export default class Table extends React.Component {
53 | state = {
54 | selectedRowKeys: ["f-0"],
55 | datasets,
56 | current: 1,
57 | };
58 | // @ts-ignore
59 | onChange = ({ current }) => {
60 | this.setState({
61 | current,
62 | datasets: current === 1 ? datasets : datasets2,
63 | });
64 | };
65 |
66 | render() {
67 | return (
68 | {
80 | if (selectedRowKeys.length > 2) {
81 | Notify.error("你最多选择两个");
82 | this.setState({
83 | // @ts-ignore
84 | selectedRowKeys: [].concat(this.state.selectedRowKeys),
85 | });
86 | } else {
87 | this.setState({
88 | selectedRowKeys,
89 | });
90 | }
91 | },
92 | getSelectionProps: (data) => ({
93 | disabled: data.name === "母婴商品 1",
94 | reason: "禁用原因",
95 | }),
96 | }}
97 | rowKey="id"
98 | // @ts-ignore
99 | onChange={this.onChange}
100 | />
101 | );
102 | }
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/pages/demo-components/index.tsx:
--------------------------------------------------------------------------------
1 | import "./index.less";
2 | // @ts-ignore
3 | // import s from "./style.module.css";
4 |
5 | import React from "react";
6 | import { BlockHeader, Alert, Notify } from "zent";
7 | import AffixTabsNav from "@/components/affix-tabs-nav";
8 | import ContentTitle from "@/components/content-title";
9 | import VipIcon from "@/components/vip-icon";
10 | import AppCard from "@/components/app-card";
11 |
12 |
13 | function ScrmComponent() {
14 | const tabsProps = {
15 | stretch: true,
16 | tabs: [
17 | {
18 | key: "cards",
19 | title: "运营计划",
20 | target: "#plan-list-cards",
21 | },
22 | {
23 | key: "table",
24 | title: "计划列表",
25 | target: "#plan-list-table",
26 | },
27 | ],
28 | };
29 | const onAppClick = () => {
30 | Notify.info("appcard被点击");
31 | };
32 | const list = [
33 | {
34 | title: "app卡片标题1",
35 | desc: "app卡片描述信息1111",
36 | icon:
37 | "https://img01.yzcdn.cn/upload_files/2021/04/14/Fjumo6k-YcHhLVs_-XHHuyZn2sjH.png",
38 | href: "www.youzan.com",
39 | onClick: onAppClick,
40 | },
41 | {
42 | title: "app卡片标题2",
43 | desc: "app卡片描述信息2222",
44 | icon:
45 | "https://img01.yzcdn.cn/upload_files/2021/04/14/FijAV5lSau2S97W7Bj8wCNl0YCfs.png",
46 | href: "www.youzan.com",
47 | onClick: onAppClick,
48 | },
49 | ];
50 | return (
51 |
52 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
74 |
75 | {list.map((item, index) => {
76 | return (
77 |
85 | );
86 | })}
87 |
88 | );
89 | }
90 |
91 | export default ScrmComponent;
92 |
--------------------------------------------------------------------------------
/src/components/zent/table-header-group.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from "zent";
2 | import * as React from "react";
3 | interface IData {
4 | id: string;
5 | name: string;
6 | stock: number;
7 | type: string;
8 | company: string;
9 | phone: string;
10 | createdTime: string;
11 | sortBy?: string
12 | sortType?: number;
13 | }
14 |
15 | const datasets: IData[] = [];
16 |
17 | for (let i = 0; i < 3; i++) {
18 | datasets.push({
19 | id: `id-${i}`,
20 | name: `商品 ${i}`,
21 | type: `type-${i}`,
22 | company: `company-${i}`,
23 | phone: `123342345${i}`,
24 | stock: 5,
25 | createdTime: "2018-12-11",
26 | });
27 | }
28 |
29 | export default class TableHeaderGroup extends React.Component {
30 | state = {
31 | datasets,
32 | sortBy: '',
33 | sortType: 0,
34 | };
35 |
36 | getColumns = () => {
37 | return [
38 | {
39 | title: "商品名",
40 | name: "name",
41 | className: "name",
42 | width: 100,
43 | fixed: true,
44 | },
45 | {
46 | title: "商品信息",
47 | name: "productInfo",
48 | children: [
49 | {
50 | title: "类型",
51 | name: "type",
52 | width: 200,
53 | },
54 | {
55 | title: "供货商",
56 | name: "supplier",
57 | children: [
58 | {
59 | title: "公司",
60 | name: "company",
61 | width: 300,
62 | },
63 | {
64 | title: "电话",
65 | name: "phone",
66 | width: 300,
67 | },
68 | ],
69 | },
70 | ],
71 | },
72 | {
73 | title: "库存",
74 | name: "stock",
75 | defaultText: 0,
76 | },
77 | {
78 | title: "创建时间",
79 | name: "createdTime",
80 | width: 100,
81 | needSort: true,
82 | fixed: "right",
83 | },
84 | ];
85 | };
86 |
87 | onChange = (conf: any) => {
88 | this.setState({
89 | ...conf,
90 | });
91 | };
92 |
93 | render() {
94 | const { sortBy, sortType } = this.state;
95 | return (
96 | `${data.id}-${index}`}
101 | bordered
102 | scroll={{ x: 1400, y: 400 }}
103 | sortBy={sortBy}
104 | // @ts-ignore
105 | sortType={sortType}
106 | rowKey="id"
107 | onChange={this.onChange}
108 | />
109 | );
110 | }
111 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-admin-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "./",
6 | "dependencies": {
7 | "@ant-design/pro-form": "^1.13.6",
8 | "@antv/data-set": "^0.11.8",
9 | "@craco/craco": "^5.9.0",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.5.0",
12 | "@testing-library/user-event": "^7.2.1",
13 | "@types/echarts": "^4.9.3",
14 | "@types/html-to-draftjs": "^1.4.0",
15 | "@types/jest": "^24.9.1",
16 | "@types/jsonp": "^0.2.0",
17 | "@types/node": "^12.12.62",
18 | "@types/react": "^16.9.49",
19 | "@types/react-dom": "^16.9.8",
20 | "@types/react-redux": "^7.1.16",
21 | "@types/react-router-dom": "^5.1.5",
22 | "@types/store": "^2.0.2",
23 | "antd": "^4.6.6",
24 | "axios": "^0.20.0",
25 | "bizcharts": "^4.1.7",
26 | "classnames": "^2.2.6",
27 | "craco-less": "^1.17.0",
28 | "draft-js": "^0.11.7",
29 | "draftjs-to-html": "^0.9.1",
30 | "echarts": "^4.2.1",
31 | "echarts-for-react": "^2.0.15-beta.0",
32 | "html-to-draftjs": "^1.5.0",
33 | "http-proxy-middleware": "^1.0.5",
34 | "jsonp": "^0.2.1",
35 | "nanoid": "^3.1.20",
36 | "node-sass": "4.14.1",
37 | "raw-loader": "^4.0.2",
38 | "react": "^16.13.1",
39 | "react-dom": "^16.13.1",
40 | "react-draft-wysiwyg": "^1.14.5",
41 | "react-markdown": "^5.0.3",
42 | "react-redux": "^7.2.2",
43 | "react-redux-typescript-scripts": "^1.6.2",
44 | "react-router": "^5.2.0",
45 | "react-router-dom": "^5.2.0",
46 | "react-scripts": "3.4.3",
47 | "react-syntax-highlighter": "^15.4.3",
48 | "redux": "^4.0.5",
49 | "redux-devtools-extension": "^2.13.8",
50 | "redux-react-hook": "^4.0.3",
51 | "redux-thunk": "^2.3.0",
52 | "store": "^2.0.12",
53 | "typesafe-actions": "^5.1.0",
54 | "typescript": "^4.1.2",
55 | "zent": "^9.4.0"
56 | },
57 | "scripts": {
58 | "dev": "craco start",
59 | "build": "craco build",
60 | "test": "craco test",
61 | "eject": "craco eject"
62 | },
63 | "eslintConfig": {
64 | "extends": "react-app"
65 | },
66 | "resolutions": {
67 | "**/@typescript-eslint/eslint-plugin": "^4.1.1",
68 | "**/@typescript-eslint/parser": "^4.1.1"
69 | },
70 | "browserslist": {
71 | "production": [
72 | ">0.2%",
73 | "not dead",
74 | "not op_mini all"
75 | ],
76 | "development": [
77 | "last 1 chrome version",
78 | "last 1 firefox version",
79 | "last 1 safari version"
80 | ]
81 | },
82 | "devDependencies": {
83 | "@types/classnames": "^2.3.1",
84 | "@types/draft-js": "^0.10.44",
85 | "@types/draftjs-to-html": "^0.8.0",
86 | "@types/react-draft-wysiwyg": "^1.13.0"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/pages/demo-hooks/index.tsx:
--------------------------------------------------------------------------------
1 | import "./index.less";
2 | import React, { useState, useEffect } from "react";
3 | import { Alert, Button } from "zent";
4 | import CallbackDemo from "./useCallback";
5 | import ContextDemo from "./useContext";
6 | import ReducerDemo from "./useReducer";
7 | import UseRouter from "./useRouter";
8 | import UseRef from "./useRef";
9 | import withLogger from "./withLogger";
10 | // import useTime from "./useHooks.ts";
11 | // Hooks.defaultProps = {
12 | // title: 'wjj'
13 | // }
14 |
15 | function Hooks(props) {
16 | const [count, setCount] = useState(0);
17 | useEffect(() => {
18 | const titleCache = document.title;
19 | document.title = `You clicked ${count} times`;
20 |
21 | return () => {
22 | document.title = titleCache;
23 | };
24 | }, [count]);
25 | // const [time, setTime] = useTime();
26 |
27 | return (
28 |
29 | {/* {props.title} */}
30 |
35 |
36 |
41 |
42 |
43 |
50 |
You clicked {count} times
51 |
54 |
55 |
60 |
61 |
62 |
67 |
68 |
69 |
74 |
75 |
80 | {/*
83 |
{time}
*/}
84 |
85 | );
86 | }
87 |
88 | export default withLogger("DIY:")(Hooks);
89 |
--------------------------------------------------------------------------------
/src/components/zent/tree-plain.tsx:
--------------------------------------------------------------------------------
1 | import { Tree, Icon, Radio } from "zent";
2 | import * as React from "react";
3 |
4 | const RadioGroup = Radio.Group;
5 | const originData = [
6 | {
7 | id: 1,
8 | title: "杭州有赞科技有限公司",
9 | },
10 | {
11 | id: 2,
12 | title: "技术",
13 | parentId: 1,
14 | },
15 | {
16 | id: 3,
17 | title: "后端",
18 | parentId: 2,
19 | },
20 | {
21 | id: 4,
22 | title: "运维",
23 | parentId: 2,
24 | },
25 | {
26 | id: 5,
27 | title: "前端",
28 | parentId: 2,
29 | },
30 | {
31 | id: 6,
32 | title: "产品",
33 | parentId: 1,
34 | },
35 | ];
36 | interface INode {
37 | id: string;
38 | parentId: number;
39 | title: string;
40 | }
41 | const deepClone = (
42 | node: any,
43 | parentId: number = 0,
44 | nodeArray: INode[] = []
45 | ) => {
46 | const copyNode: INode = {
47 | id: String(Math.random()).replace("0.", ""),
48 | parentId,
49 | title: node.title,
50 | };
51 | nodeArray.push(copyNode);
52 |
53 | for (
54 | let i = 0, l = (node.children && node.children.length) || 0;
55 | i < l;
56 | i++
57 | ) {
58 | // @ts-ignore
59 | deepClone(node.children[i], copyNode.id, nodeArray);
60 | }
61 | return nodeArray;
62 | };
63 |
64 | export default class JJTreePlain extends React.Component {
65 | state = {
66 | treeData: originData,
67 | copyType: "shallow",
68 | };
69 |
70 | onDelete = (data: any) => {
71 | this.setState({
72 | treeData: this.state.treeData.filter((item) => item.id !== data.id),
73 | });
74 | };
75 |
76 | onClone = (data: any) => {
77 | const { copyType } = this.state;
78 |
79 | if (copyType === "shallow") {
80 | const node = Object.assign({}, data, { id: Date.now() });
81 | this.setState({
82 | treeData: [...this.state.treeData, node],
83 | });
84 | } else if (copyType === "deep") {
85 | const nodeArray = deepClone(data, data.parentId);
86 | this.setState({
87 | treeData: [...this.state.treeData, ...nodeArray],
88 | });
89 | }
90 | };
91 |
92 | onCopyTypeChange = (e: any) => this.setState({ copyType: e.target.value });
93 |
94 | render() {
95 | const { copyType, treeData } = this.state;
96 | const operations = [
97 | {
98 | name: "Delete",
99 | icon: ,
100 | action: this.onDelete,
101 | },
102 | {
103 | name: "Clone",
104 | icon: ,
105 | action: this.onClone,
106 | },
107 | ];
108 |
109 | return (
110 |
111 |
112 | 浅拷贝
113 | 深拷贝
114 |
115 |
116 |
117 |
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/pages/category/AddForm.tsx:
--------------------------------------------------------------------------------
1 | import { Select, Form, Input, Modal, message } from 'antd';
2 | import React, { Component } from 'react';
3 | import { reqAddCategory } from '../../api';
4 | import { ICategory } from './Model';
5 | import { ModalStatusCode } from './ModalStatusCode';
6 |
7 | const Option = Select.Option;
8 | const Item = Form.Item;
9 |
10 | interface Values {
11 | title?: string;
12 | value?: string;
13 | }
14 |
15 | interface IAddFormProps {
16 | category: ICategory;
17 | onCancel: () => void;
18 | showStatus: ModalStatusCode;
19 | categorys: ICategory[];
20 | updateCategory: (category:ICategory) => void;
21 | }
22 | export default class AddForm extends Component {
23 | private onCancel = (): void => {
24 | this.props.onCancel();
25 | };
26 |
27 | private CreateModalFrom = (): any => {
28 | const [form] = Form.useForm();
29 | const { categorys, updateCategory,category } = this.props;
30 | return (
31 | {
39 | form
40 | .validateFields()
41 | .then(async (values: Values) => {
42 | console.log(values);
43 | if (values.value === undefined || values.title === undefined) {
44 | message.error('参数错误');
45 | return;
46 | }
47 | const result = await reqAddCategory(values.value, values.value === '0' ? '一级分类' : categorys[Number.parseInt(values.value) - 1].categoryName, values.title);
48 | if (result.status === 0) {
49 | message.info('添加成功');
50 | updateCategory(category);
51 | } else {
52 | message.error('添加失败');
53 | }
54 | this.onCancel();
55 | })
56 | .catch((info) => {
57 | console.log('Validate Failed:', info);
58 | });
59 | }}
60 | >
61 |
64 |
65 | );
66 | };
67 |
68 | private addFromElement(): React.ReactNode {
69 | const { categorys,category} = this.props;
70 | return (
71 |
72 | -
73 |
83 |
84 | -
85 |
86 |
87 |
88 | );
89 | }
90 |
91 | render() {
92 | return (
93 |
94 |
95 |
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/pages/product/rich-text-editor.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { EditorState, convertToRaw, ContentState } from 'draft-js';
3 | import { Editor } from 'react-draft-wysiwyg';
4 | import draftToHtml from 'draftjs-to-html';
5 | import htmlToDraft from 'html-to-draftjs';
6 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
7 | import { BASE_URL } from '../../utils/Constants';
8 | import { ResponseValue } from '../../api/Model';
9 | import { FileUploadResponseModel } from './Model';
10 | import { myBlockRenderer } from './media';
11 |
12 | interface State {
13 | editorState: EditorState;
14 | }
15 |
16 | interface Props {
17 | detail: string;
18 | }
19 |
20 | export default class RichTextEditor extends Component {
21 | constructor(props: Props) {
22 | super(props);
23 | this.state = {
24 | editorState: this.initContent(),
25 | };
26 | }
27 |
28 |
29 |
30 | private initContent = (): any => {
31 | if (this.props.detail !== '') {
32 | const contentBlock = htmlToDraft(this.props.detail);
33 | const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
34 | const editorState = EditorState.createWithContent(contentState);
35 | return editorState;
36 | }
37 | return EditorState.createEmpty();
38 | };
39 |
40 | private onEditorStateChange = (editorState: EditorState): void => {
41 | this.setState({
42 | editorState,
43 | });
44 | };
45 |
46 | private uploadImageCallBack = (file: any): Promise => {
47 | return new Promise((resolve, reject) => {
48 | const xhr = new XMLHttpRequest();
49 | xhr.open('POST', BASE_URL + '/uploadFile');
50 | const data = new FormData();
51 | data.append('image', file);
52 | xhr.send(data);
53 | xhr.addEventListener('load', () => {
54 | const response: ResponseValue = JSON.parse(
55 | xhr.responseText
56 | ) as ResponseValue;
57 | const url = response.data?.url ?? '';
58 | const name = response.data?.name ?? '';
59 | resolve({ data: { link: url + '/files/' + name } });
60 | });
61 | xhr.addEventListener('error', () => {
62 | const error = JSON.parse(xhr.responseText);
63 | reject(error);
64 | });
65 | });
66 | };
67 |
68 | public getDetail = (): string => {
69 | const { editorState } = this.state;
70 | return draftToHtml(convertToRaw(editorState.getCurrentContent()));
71 | };
72 |
73 | render() {
74 | const { editorState } = this.state;
75 |
76 | return (
77 |
78 |
92 |
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/pages/ts-study/tstype.md:
--------------------------------------------------------------------------------
1 | ## Partial
2 |
3 | ### 定义
4 |
5 | 将T中所有属性转换为可选属性。返回的类型可以是T的任意子集
6 |
7 | ```js
8 | export interface UserModel {
9 | name: string;
10 | age?: number;
11 | sex: number;
12 | address?: string;
13 | tel?: string;
14 | favorite?: string;
15 | }
16 |
17 | type JUserModel = Partial
18 | // =
19 | type JUserModel = {
20 | name?: string | undefined;
21 | age?: number | undefined;
22 | sex?: number | undefined;
23 | address?: string | undefined;
24 | tel?: string | undefined;
25 | favorite?: string | undefined;
26 | }
27 | ```
28 |
29 | ### 源码解析
30 | ```js
31 | type Partial = { [P in keyof T]?: T[P]; };
32 | ```
33 |
34 | ## Required
35 |
36 | ### 定义
37 |
38 | 通过将T的所有属性设置为必选属性来构造一个新的类型。与Partial相对
39 |
40 | ```js
41 | type JUserModel2 = Required
42 | // =
43 | type JUserModel2 = {
44 | name: string;
45 | age: number;
46 | sex: number;
47 | address: string;
48 | tel: string;
49 | favorite: string;
50 | }
51 | ```
52 |
53 | ## Readonly
54 | 将T中所有属性设置为只读
55 |
56 | ```js
57 | type JUserModel3 = Readonly
58 |
59 | // =
60 | type JUserModel3 = {
61 | readonly name: string;
62 | readonly age?: number | undefined;
63 | readonly sex: number;
64 | readonly address?: string | undefined;
65 | readonly tel?: string | undefined;
66 | readonly favorite?: string | undefined;
67 | }
68 | ```
69 |
70 | ## Record
71 | 构造一个类型,该类型具有一组属性K,每个属性的类型为T。可用于将一个类型的属性映射为另一个类型。
72 |
73 | Record 后面的泛型就是对象键和值的类型。
74 |
75 | ```js
76 | type TodoProperty = 'title' | 'description';
77 |
78 | type Todo = Record;
79 | // =
80 | type Todo = {
81 | title: string;
82 | description: string;
83 | }
84 | ```
85 |
86 | ## Pick
87 | 通过在T中抽取一组属性K构建一个新类型
88 |
89 | ```js
90 | interface Todo {
91 | title: string;
92 | description: string;
93 | done: boolean;
94 | }
95 |
96 | type TodoBase = Pick;
97 |
98 | // =
99 | type TodoBase = {
100 | title: string;
101 | done: boolean;
102 | }
103 | ```
104 |
105 | ## Omit
106 | 从T中取出除去K的其他所有属性。与Pick相对。
107 |
108 |
109 | ## Exclude
110 | 从T中排除可分配给U的属性,剩余的属性构成新的类型
111 |
112 | ```js
113 | type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
114 |
115 | // =
116 |
117 | type T0 = "b" | "c"
118 | ```
119 | ## Extract
120 |
121 | 从T中抽出可分配给U的属性构成新的类型。与Exclude相反
122 |
123 | ```js
124 | type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
125 |
126 | // =
127 |
128 | type T0 = 'a'
129 | ```
130 |
131 | ## NonNullable
132 |
133 | 去除T中的 null 和 undefined 类型
134 |
135 | ## Parameters
136 | 返回类型为T的函数的参数类型所组成的数组
137 |
138 | ```js
139 |
140 | type T0 = Parameters<() => string>; // []
141 |
142 | type T1 = Parameters<(s: string) => void>; // [string]
143 | ```
144 |
145 | ## ReturnType
146 | function T的返回类型
147 | ```js
148 | type T0 = ReturnType<() => string>; // string
149 |
150 | type T1 = ReturnType<(s: string) => void>; // void
151 |
152 | ```
153 | ## InstanceType
154 | 返回构造函数类型T的实例类型; 相当于js中的,不过返回的是对应的实例
155 |
156 | ```js
157 | class C {
158 | x = 0;
159 | y = 0;
160 | }
161 |
162 | type T0 = InstanceType; // C
163 | ```
--------------------------------------------------------------------------------
/src/pages/zent/feedback.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import JDialog from "../../components/zent/dialog";
3 | import JDrawer from "../../components/zent/drawer";
4 | import { JLoading } from "../../components/zent/loading";
5 |
6 | import {
7 | LayoutRow as Row,
8 | LayoutCol as Col,
9 | LayoutGrid as Grid,
10 | LayoutConfigProvider as ConfigProvider,
11 | } from "zent";
12 |
13 | import { Alert, AnimateHeight, Button, Placeholder, Sweetalert } from "zent";
14 |
15 | const Feedback: React.FC = () => {
16 | const [height, setHeight] = useState(200);
17 | const onAdd = () => {
18 | setHeight(height + 100);
19 | };
20 | const onDec = () => {
21 | setHeight(height - 100);
22 | };
23 | const showAlertInfo = () => {
24 | Sweetalert.alert({
25 | content: "这个是具体内容",
26 | parentComponent: this,
27 | });
28 | };
29 |
30 | return (
31 | <>
32 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | {/* @ts-ignore */}
82 |
83 | {/* @ts-ignore */}
84 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | >
101 | );
102 | };
103 |
104 | export default Feedback;
105 |
--------------------------------------------------------------------------------
/src/pages/login/Login.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHistory } from "react-router-dom";
3 | import "./login.less";
4 | import {
5 | FormInputField,
6 | Form,
7 | FormStrategy,
8 | Validators,
9 | Pop,
10 | Icon,
11 | Button,
12 | Notify
13 | } from "zent";
14 | // import imgURL from "../../assets/images/logo.png";
15 |
16 | function Login() {
17 | const form = Form.useForm(FormStrategy.View);
18 | const [initializing, setInitializing] = React.useState(false);
19 | const initialize = React.useCallback(() => {
20 | setInitializing(true);
21 | setTimeout(() => {
22 | form.initialize({
23 | name: "alexwjj",
24 | password: "88888888",
25 | });
26 | setInitializing(false);
27 | }, 1000);
28 | }, [form]);
29 | let history = useHistory();
30 |
31 | const handleClick = () => {
32 | console.log(form.getValue(),"form");
33 | const user = form.getValue()
34 | if(user.name === "alexwjj") {
35 | sessionStorage.setItem('user', JSON.stringify(user));
36 | history.push("/home");
37 | } else {
38 | Notify.warn('账号错误,请点击账号填充!')
39 | }
40 |
41 | };
42 | return (
43 |
44 |
45 |
46 | {/*
})
*/}
47 |
98 |
99 |
100 | );
101 | }
102 | export default Login;
103 |
--------------------------------------------------------------------------------
/src/pages/product/detail.tsx:
--------------------------------------------------------------------------------
1 | import { Card, List } from 'antd';
2 | import React, { Component } from 'react';
3 | import { ArrowLeftOutlined } from '@ant-design/icons';
4 | import { RouteComponentProps, withRouter } from 'react-router';
5 | import LinkButton from '../../components/link-button';
6 | import { reqCategoryById } from '../../api';
7 | import MemoryUtils from '../../utils/MemoryUtils';
8 |
9 | interface ProductDetailState {
10 | cName: string;
11 | pName: string;
12 | }
13 |
14 | interface ProductDetailProps {}
15 |
16 | type ProductDetailRoutePros = ProductDetailProps & RouteComponentProps;
17 |
18 | class ProductDetail extends Component {
19 | constructor(props: ProductDetailRoutePros) {
20 | super(props);
21 |
22 | this.state = {
23 | cName: '',
24 | pName: '',
25 | };
26 | }
27 |
28 | componentDidMount() {
29 | this.getCategoryName();
30 | }
31 |
32 | private async getCategoryName() {
33 | const { categoryId, pcategoryId } = MemoryUtils.product??{};
34 | if (pcategoryId === '0') {
35 | const cResult = await reqCategoryById(categoryId??'');
36 | this.setState({
37 | cName: cResult.name,
38 | });
39 | } else {
40 | Promise.all([reqCategoryById(categoryId??''), reqCategoryById(pcategoryId??'')]).then((value) => {
41 | this.setState({
42 | cName: value[0].name,
43 | pName: value[1].name,
44 | });
45 | });
46 | }
47 | }
48 |
49 | render() {
50 |
51 | const { desc, detail, images, price, name } = MemoryUtils.product??{};
52 | const { cName, pName } = this.state;
53 | const imageList: string[] = images?.split(',') ?? [];
54 | const title = (
55 |
56 | {
58 | this.props.history.goBack();
59 | }}
60 | >
61 |
62 |
63 | 商品详情
64 |
65 | );
66 |
67 | return (
68 |
69 |
70 |
71 |
72 | 商品名称:
73 | {name}
74 |
75 |
76 | 商品描述:
77 | {desc}
78 |
79 |
80 | 商品价格:
81 | {price}
82 |
83 |
84 | 所属分类:
85 |
86 | {pName === '' ? '' : `${pName}-->`}
87 | {cName}
88 |
89 |
90 |
91 | 商品图片:
92 |
93 | {imageList.map((img) => {
94 | return (
95 |
96 | );
97 | })}
98 |
99 |
100 |
101 | 商品详情:
102 |
103 |
104 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | export default withRouter(ProductDetail);
112 |
--------------------------------------------------------------------------------
/src/components/header/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2021-01-31 19:59:55
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-02 23:43:29
8 | */
9 | import { Modal } from 'antd';
10 | import React, { Component } from 'react';
11 | import { RouteComponentProps, withRouter } from 'react-router';
12 | // import { reqWheater } from '../../api';
13 | import { formatDate } from '../../utils/DateUtils';
14 | import { ExclamationCircleOutlined } from '@ant-design/icons';
15 | import './index.less';
16 | import LinkButton from '../link-button';
17 | import { connect } from 'react-redux';
18 | import { RootState } from 'typesafe-actions';
19 | import { logout } from '../../redux/actions';
20 | import { Affix } from "zent";
21 |
22 |
23 | const mapStateToProps = (state: RootState) => ({
24 | headTitle: state.headTitle,
25 | user: state.user,
26 | });
27 |
28 | const mapDispatchToProps = {
29 | logout
30 | };
31 | // interface userSession {
32 | // name?: string;
33 | // password?: string;
34 | // }
35 |
36 | const userSession: any = JSON.parse(sessionStorage.getItem('user') || '{}');
37 | interface HeaderState {
38 | currentTime: string;
39 | dayPictureUrl: string;
40 | weather: string;
41 | }
42 |
43 | type HeaderProps = RouteComponentProps & ReturnType & typeof mapDispatchToProps;
44 |
45 | class Header extends Component {
46 | timerId: NodeJS.Timeout | null = null;
47 | user = this.props.user;
48 | constructor(props: HeaderProps) {
49 | super(props);
50 | this.state = {
51 | currentTime: formatDate(Date.now()),
52 | dayPictureUrl: '',
53 | weather: '',
54 | };
55 | }
56 |
57 | componentDidMount() {
58 | this.getNowTime();
59 | // this.getWeather();
60 | }
61 |
62 | // private async getWeather() {
63 | // const { dayPictureUrl, weather } = await reqWheater('常州');
64 | // this.setState({
65 | // dayPictureUrl,
66 | // weather,
67 | // });
68 | // }
69 |
70 | private getNowTime() {
71 | this.timerId = setInterval(() => {
72 | this.setState({
73 | currentTime: formatDate(Date.now()),
74 | });
75 | }, 1000);
76 | }
77 |
78 | componentWillUnmount() {
79 | if (this.timerId !== null) {
80 | clearInterval(this.timerId);
81 | }
82 | }
83 | private logout(): any {
84 | Modal.confirm({
85 | content: '是否退出',
86 | icon: ,
87 | okText: '确认',
88 | cancelText: '取消',
89 | onOk: () => {
90 | // this.props.logout();
91 | sessionStorage.removeItem('user')
92 | this.props.history.push('/')
93 | },
94 | onCancel: () => {
95 | console.log('cancel');
96 | },
97 | });
98 | }
99 |
100 | render() {
101 |
102 | return (
103 |
104 |
105 |
106 | 欢迎,{userSession ? userSession.name : ''}
107 | 退出
108 |
109 | {/*
110 |
{this.props.headTitle}
111 |
112 |
{this.state.currentTime}
113 |

114 |
{this.state.weather}
115 |
116 |
*/}
117 |
118 |
119 | );
120 | }
121 | }
122 |
123 | export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Header));
124 |
--------------------------------------------------------------------------------
/src/pages/chars/Pie.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-01 19:15:13
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-07 14:00:57
8 | */
9 | import { Card } from 'antd';
10 | import { EChartOption } from 'echarts';
11 | import ReactEcharts from 'echarts-for-react';
12 | import React from 'react';
13 |
14 | const Pie = () => {
15 | const getOptions = (): EChartOption => {
16 | return {
17 | backgroundColor: '#2c343c',
18 |
19 | title: {
20 | text: 'Customized Pie',
21 | left: 'center',
22 | top: 20,
23 | textStyle: {
24 | color: '#ccc',
25 | },
26 | },
27 |
28 | tooltip: {
29 | trigger: 'item',
30 | },
31 |
32 | visualMap: [
33 | {
34 | show: false,
35 | min: 80,
36 | max: 600,
37 | inRange: {
38 | colorLightness: [0, 1],
39 | },
40 | },
41 | ],
42 | series: [
43 | {
44 | name: '访问来源',
45 | type: 'pie',
46 | radius: '55%',
47 | center: ['50%', '50%'],
48 | data: [
49 | { value: 335, name: '直接访问' },
50 | { value: 310, name: '邮件营销' },
51 | { value: 274, name: '联盟广告' },
52 | { value: 235, name: '视频广告' },
53 | { value: 400, name: '搜索引擎' },
54 | ].sort(function (a, b) {
55 | return a.value - b.value;
56 | }),
57 | roseType: 'radius',
58 | label: {
59 | color: 'rgba(255, 255, 255, 0.3)',
60 | },
61 | labelLine: {
62 | lineStyle: {
63 | color: 'rgba(255, 255, 255, 0.3)',
64 | },
65 | smooth: 0.2,
66 | length: 10,
67 | length2: 20,
68 | },
69 | itemStyle: {
70 | color: '#c23531',
71 | shadowBlur: 200,
72 | shadowColor: 'rgba(0, 0, 0, 0.5)',
73 | },
74 |
75 | animationType: 'scale',
76 | animationEasing: 'elasticOut',
77 | animationDelay: function () {
78 | return Math.random() * 200;
79 | },
80 | },
81 | ],
82 | };
83 | };
84 |
85 |
86 | const getOptions2 = (): EChartOption => {
87 | return {
88 | title: {
89 | text: '某站点用户访问来源',
90 | subtext: '纯属虚构',
91 | left: 'center'
92 | },
93 | tooltip: {
94 | trigger: 'item',
95 | formatter: '{a}
{b} : {c} ({d}%)'
96 | },
97 | legend: {
98 | orient: 'vertical',
99 | left: 'left',
100 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
101 | },
102 | series: [
103 | {
104 | name: '访问来源',
105 | type: 'pie',
106 | radius: '55%',
107 | center: ['50%', '60%'],
108 | data: [
109 | {value: 335, name: '直接访问'},
110 | {value: 310, name: '邮件营销'},
111 | {value: 234, name: '联盟广告'},
112 | {value: 135, name: '视频广告'},
113 | {value: 1548, name: '搜索引擎'}
114 | ],
115 | emphasis: {
116 | itemStyle: {
117 | shadowBlur: 10,
118 | shadowOffsetX: 0,
119 | shadowColor: 'rgba(0, 0, 0, 0.5)'
120 | }
121 | }
122 | }
123 | ]
124 | };
125 |
126 | };
127 |
128 | return (
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | );
138 | };
139 |
140 | export default Pie;
141 |
--------------------------------------------------------------------------------
/src/pages/zent/show.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | LayoutRow as Row,
5 | LayoutCol as Col,
6 | LayoutGrid as Grid,
7 | LayoutConfigProvider as ConfigProvider,
8 | } from "zent";
9 |
10 | import {
11 | Alert,
12 | Avatar,
13 | Badge,
14 | Icon,
15 | BlockHeader,
16 | Progress,
17 | TextMark,
18 | } from "zent";
19 | import JCollection from "../../components/zent/collapse";
20 | import JInfinite from "../../components/zent/Infinite";
21 | import JSwiper from "../../components/zent/swiper";
22 |
23 | class Show extends React.Component {
24 | state = {
25 | current: 1,
26 | };
27 | render() {
28 | return (
29 | <>
30 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
52 |
60 | IT
61 |
62 |
63 |
67 |
68 |
69 |
70 |
71 | test}
74 | position="top-center"
75 | />
76 | test}
80 | position="top-center"
81 | />
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
110 |
111 |
112 |
120 | ,
121 |
122 |
123 |
124 |
125 |
126 | >
127 | );
128 | }
129 | }
130 |
131 | export default Show;
132 |
--------------------------------------------------------------------------------
/src/config/menuConfig.ts:
--------------------------------------------------------------------------------
1 | export interface MenuConfig {
2 | title: string;
3 | key: string;
4 | icon: string;
5 | isPublic?: boolean;
6 | children?: MenuConfig[];
7 | }
8 |
9 | export const menuList: MenuConfig[] = [
10 | {
11 | title: "Home",
12 | key: "/home",
13 | icon: "HomeOutlined",
14 | isPublic: true,
15 | },
16 | {
17 | title: "Todo",
18 | key: "/todo",
19 | icon: "LikeOutlined",
20 | isPublic: true,
21 | },
22 | // {
23 | // title: '门诊工作站',
24 | // key: '/clinic',
25 | // icon: 'HomeOutlined',
26 | // isPublic: true,
27 | // },
28 | // {
29 | // title: '挂号登记',
30 | // key: '/register',
31 | // icon: 'AppstoreOutlined',
32 | // children: [
33 | // {
34 | // title: '挂号记录',
35 | // key: '/record',
36 | // icon: 'ToolOutlined',
37 | // },
38 | // {
39 | // title: '新建挂号',
40 | // key: '/add-register',
41 | // icon: 'ToolOutlined',
42 | // },
43 | // ],
44 | // },
45 | // {
46 | // title: '收费管理',
47 | // key: '/charge',
48 | // icon: 'AppstoreOutlined',
49 | // children: [
50 | // {
51 | // title: '收费管理',
52 | // key: '/charge-manage',
53 | // icon: 'ToolOutlined',
54 | // },
55 | // {
56 | // title: '结算记录',
57 | // key: '/settle-record',
58 | // icon: 'ToolOutlined',
59 | // },
60 | // {
61 | // title: '处方详情',
62 | // key: '/prescription-detail',
63 | // icon: 'ToolOutlined',
64 | // },
65 | // ],
66 | // },
67 | {
68 | title: "Zent",
69 | key: "/zent",
70 | icon: "AppstoreOutlined",
71 | children: [
72 | {
73 | title: "zent-base",
74 | key: "/zent-base",
75 | icon: "AimOutlined",
76 | isPublic: true,
77 | },
78 | {
79 | title: "zent-nav",
80 | key: "/zent-nav",
81 | icon: "RiseOutlined",
82 | isPublic: true,
83 | },
84 | {
85 | title: "zent-data",
86 | key: "/zent-data",
87 | icon: "DatabaseOutlined",
88 | isPublic: true,
89 | },
90 | {
91 | title: "zent-show",
92 | key: "/zent-show",
93 | icon: "ThunderboltOutlined",
94 | isPublic: true,
95 | },
96 | {
97 | title: "zent-feedback",
98 | key: "/zent-feedback",
99 | icon: "WhatsAppOutlined",
100 | isPublic: true,
101 | },
102 | ],
103 | },
104 |
105 | {
106 | title: "LifeCycle",
107 | key: "/demo",
108 | icon: "NodeIndexOutlined",
109 | isPublic: true,
110 | },
111 | {
112 | title: "Demo-Hooks",
113 | key: "/demo-hooks",
114 | icon: "RobotOutlined",
115 | isPublic: true,
116 | },
117 | {
118 | title: "Demo-Components",
119 | key: "/demo-components",
120 | icon: "LayoutOutlined",
121 | isPublic: true,
122 | },
123 | {
124 | title: "TypeScript",
125 | key: "/ts-study",
126 | icon: "FundProjectionScreenOutlined",
127 | isPublic: true,
128 | },
129 | // {
130 | // title: '商品',
131 | // key: '/products',
132 | // icon: 'AppstoreOutlined',
133 | // children: [
134 | // {
135 | // title: '品类管理',
136 | // key: '/category',
137 | // icon: 'ToolOutlined',
138 | // },
139 | // {
140 | // title: '商品管理',
141 | // key: '/product',
142 | // icon: 'ToolOutlined',
143 | // },
144 | // ],
145 | // },
146 | // {
147 | // title: '用户管理',
148 | // key: '/user',
149 | // icon: 'UserOutlined',
150 | // },
151 | // {
152 | // title: '角色管理',
153 | // key: '/role',
154 | // icon: 'SafetyOutlined',
155 | // },
156 | // {
157 | // title: '图形图表',
158 | // key: '/charts',
159 | // icon: 'AreaChartOutlined',
160 | // children: [
161 | // {
162 | // title: '柱形图',
163 | // key: '/bar',
164 | // icon: 'BarsOutlined',
165 | // },
166 | // {
167 | // title: '折线图',
168 | // key: '/line',
169 | // icon: 'LineChartOutlined',
170 | // },
171 | // {
172 | // title: '饼图',
173 | // key: '/pie',
174 | // icon: 'PieChartOutlined',
175 | // },
176 | // ],
177 | // },
178 | ];
179 |
--------------------------------------------------------------------------------
/src/pages/zent/base.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./index.scss";
3 | import JJIME from "../../components/zent/IME";
4 | import JJPortal from "../../components/zent/portal";
5 | import JJWayPoint from "../../components/zent/way-point";
6 |
7 | import {
8 | LayoutRow as Row,
9 | LayoutCol as Col,
10 | LayoutGrid as Grid,
11 | LayoutConfigProvider as ConfigProvider,
12 | } from "zent";
13 | import { Disabled, Button, Input, Popover } from "zent";
14 |
15 | import { Alert, Icon } from "zent";
16 |
17 | class Base extends React.Component {
18 | state = {
19 | current: 1,
20 | };
21 | render() {
22 | return (
23 | <>
24 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
77 |
78 |
84 |
85 |
86 |
87 |
88 | 内容区,可以写任何html,慢慢感觉到react组件确实比vue更加自由
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | >
112 | );
113 | }
114 | }
115 |
116 | export default Base;
117 |
--------------------------------------------------------------------------------
/src/components/left-nav/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-14 21:16:42
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-02 23:56:52
8 | */
9 | import React, { Component } from 'react';
10 | import './index.less';
11 | // import logo from '../../assets/images/logo.png';
12 | import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
13 | import { Menu } from 'antd';
14 | import { menuList, MenuConfig } from '../../config/menuConfig';
15 | import * as Icon from '@ant-design/icons';
16 | // import { LoginUser } from '../../utils/StorageUtils';
17 | import { connect } from 'react-redux';
18 | import { RootState } from 'typesafe-actions';
19 | import { setHeadTitle } from '../../redux/actions';
20 |
21 | const { SubMenu } = Menu;
22 |
23 | type LeftNavProps = RouteComponentProps & typeof dispatchProps & ReturnType;
24 |
25 | class LeftNav extends Component {
26 | menuNodes: JSX.Element[] = [];
27 | openKey: string = '';
28 | constructor(props: LeftNavProps) {
29 | super(props);
30 | this.menuNodes = this.getMenuNodes2(menuList);
31 | this.getOpenKey(menuList);
32 | }
33 |
34 | getOpenKey = (menuList: MenuConfig[]) => {
35 | // const path: string = this.props.location.pathname;
36 | return menuList.forEach((item) => {
37 | if (item.children) {
38 | // const cItem = item.children.find((cItem) => path.indexOf(cItem.key) === 0);
39 | // if (cItem) {
40 | // this.openKey = item.key;
41 | // }
42 | }
43 | });
44 | };
45 |
46 | // private hasAuth = (node: MenuConfig): boolean => {
47 | // // const user: LoginUser = this.props.user;
48 | // // if (user.name === 'admin' || node.isPublic || (user.menus??[]).indexOf(node.key) !== -1) {
49 | // // return true;
50 | // // } else if (node.children) {
51 | // // return !!node.children.find((child) => {
52 | // // return (user.menus??[]).indexOf(child.key) !== -1;
53 | // // });
54 | // // }
55 | // // 权限全部放开
56 | // return true;
57 | // };
58 |
59 | getMenuNodes = (menuList: MenuConfig[]): JSX.Element[] | null => {
60 | return menuList.map((item) => {
61 | if (item.children) {
62 | return (
63 |
64 | {this.getMenuNodes(item.children)}
65 |
66 | );
67 | } else {
68 | return (
69 |
70 | {item.title}
71 |
72 | );
73 | }
74 | });
75 | };
76 |
77 | getMenuNodes2 = (menuList: MenuConfig[]): JSX.Element[] => {
78 | const path = this.props.location.pathname;
79 | return menuList.reduce((pre: JSX.Element[], item: MenuConfig): JSX.Element[] => {
80 | // if (this.hasAuth(item)) {
81 | if (!item.children) {
82 | if (item.key === path || path.indexOf(item.key) === 0) {
83 | this.props.setHeadTitle(item.title);
84 | }
85 | pre.push(
86 |
87 | this.props.setHeadTitle(item.title)}>
88 | {item.title}
89 |
90 |
91 | );
92 | } else {
93 | pre.push(
94 |
95 | {this.getMenuNodes2(item.children)}
96 |
97 | );
98 | }
99 | // }
100 | return pre;
101 | }, []);
102 | };
103 |
104 | render() {
105 | let path = this.props.location.pathname;
106 | if (path.indexOf('/product') === 0) {
107 | path = '/product';
108 | }
109 | return (
110 |
111 |
112 | {/*

*/}
113 |
俊劫学习系统
114 |
115 |
116 |
119 |
120 |
121 | );
122 | }
123 | }
124 |
125 | const dispatchProps = { setHeadTitle };
126 | const mapStateToProps = (state: RootState) => ({user:state.user})
127 |
128 | export default connect(mapStateToProps, dispatchProps)(withRouter(LeftNav));
129 |
--------------------------------------------------------------------------------
/src/pages/product/pictures-wall.tsx:
--------------------------------------------------------------------------------
1 | import { Upload, Modal, message } from 'antd';
2 | import { PlusOutlined } from '@ant-design/icons';
3 | import React, { Component } from 'react';
4 | import { UploadChangeParam } from 'antd/lib/upload';
5 | import { UploadFile } from 'antd/lib/upload/interface';
6 | import { BASE_IMAGES_URL, BASE_URL } from '../../utils/Constants';
7 | import { ResponseValue } from '../../api/Model';
8 | import { nanoid } from 'nanoid';
9 | import { reqDeleteProductsImages } from '../../api';
10 | import { FileUploadResponseModel } from './Model';
11 |
12 | function getBase64(file: any): Promise {
13 | return new Promise((resolve, reject) => {
14 | const reader = new FileReader();
15 | reader.readAsDataURL(file);
16 | reader.onload = () => resolve(reader.result);
17 | reader.onerror = (error) => reject(error);
18 | });
19 | }
20 |
21 |
22 | interface PicturesWallState {
23 | previewVisible: boolean;
24 | previewImage?: string;
25 | previewTitle?: string;
26 | fileList: UploadFile[];
27 | }
28 |
29 | interface PicturesWallProps {
30 | images: string;
31 | }
32 |
33 | export default class PicturesWall extends Component {
34 | constructor(props: PicturesWallProps) {
35 | super(props);
36 | this.state = {
37 | previewVisible: false,
38 | previewImage: '',
39 | previewTitle: '',
40 | fileList: [],
41 | };
42 | }
43 |
44 | private initPreviewImages = (): void => {
45 | const fileList: UploadFile[] = [];
46 | if (this.props.images === '') {
47 | return;
48 | }
49 | this.props.images.split(',').forEach((item: string) => {
50 | fileList.push({
51 | uid: nanoid(),
52 | url: BASE_IMAGES_URL + item,
53 | name: item,
54 | size: 0,
55 | type: 'image/webp',
56 | status: 'done',
57 | });
58 | });
59 |
60 | this.setState({
61 | fileList,
62 | });
63 | };
64 |
65 | private handleCancel = () => this.setState({ previewVisible: false });
66 |
67 | private handlePreview = async (file: UploadFile) => {
68 | if (!file.url && !file.preview) {
69 | file.preview = await getBase64(file.originFileObj);
70 | }
71 |
72 | this.setState({
73 | previewImage: file.url || file.preview,
74 | previewVisible: true,
75 | previewTitle: file.name || file.url?.substring(file.url.lastIndexOf('/') + 1),
76 | });
77 | };
78 |
79 |
80 | private handleChange = async ({ file, fileList, event }: UploadChangeParam) => {
81 | if (file.status === 'done') {
82 | const result: ResponseValue = file.response as ResponseValue;
83 | if (result.status === 0 && result.data) {
84 | message.success('上传图片成功');
85 | let currentIndex = fileList.length - 1;
86 | fileList[currentIndex].name = result.data?.name;
87 | fileList[currentIndex].url = BASE_IMAGES_URL + result.data?.name;
88 | } else {
89 | message.error('上传失败');
90 | }
91 | } else if (file.status === 'removed') {
92 | const result: ResponseValue = await reqDeleteProductsImages(file.name ?? '');
93 | if (result.status === 0) {
94 | message.success(file.fileName + '已经成功删除');
95 | } else {
96 | message.error('删除图片失败');
97 | }
98 | }
99 | this.setState({
100 | fileList,
101 | });
102 | };
103 |
104 | public getImages = (): string[] => {
105 | return this.state.fileList.map((file) => file.name ?? '');
106 | };
107 |
108 | componentDidMount() {
109 | this.initPreviewImages();
110 | }
111 |
112 | render() {
113 | const { previewVisible, previewImage, fileList, previewTitle } = this.state;
114 | const uploadButton = (
115 |
119 | );
120 | return (
121 |
122 |
132 | {fileList.length >= 3 ? null : uploadButton}
133 |
134 |
135 |
136 |
137 |
138 | );
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/zent/form.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Form,
3 | FormStrategy,
4 | Checkbox,
5 | Validators,
6 | FormNumberInputField,
7 | FormCheckboxGroupField,
8 | FormSingleUploadField,
9 | FormInputField,
10 | FormUploadField,
11 | FormImageUploadField,
12 | Button
13 | } from "zent";
14 | import React, { useCallback } from 'react';
15 |
16 | /**
17 | * 自定义表单校验,内置组件接受 renderError 参数,若不传默认会显示 message
18 | */
19 | function equalsPassword(value:string, ctx: any):any {
20 | if (value !== ctx.getSectionValue("password").password) {
21 | return {
22 | name: "passwordEqual",
23 | message: "两次填写的密码不一致",
24 | };
25 | }
26 | return null;
27 | }
28 |
29 | function idLength(value:any) {
30 | if (value.length !== 10 && value.length !== 15) {
31 | return {
32 | name: "idLength",
33 | message: "证件号码是10位或者15位数字",
34 | };
35 | }
36 | }
37 |
38 | function JJForm() {
39 | const form = Form.useForm(FormStrategy.View);
40 | const resetForm = useCallback(() => {
41 | form.resetValue();
42 | }, [form]);
43 | const onSubmit = useCallback((form) => {
44 | const value = form.getValue();
45 | console.log(value);
46 | }, []);
47 | return (
48 |
151 | );
152 | }
153 |
154 | export default JJForm
--------------------------------------------------------------------------------
/src/pages/admin/admin.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-14 21:16:42
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-11 01:32:47
8 | */
9 | import React, { Component } from "react";
10 | import { Layout } from "antd";
11 | import LeftNav from "../../components/left-nav";
12 | import Header from "../../components/header";
13 | import { Redirect, Route, Switch } from "react-router-dom";
14 | import Category from "../category/Category";
15 | import Product from "../product/Product";
16 | import Role from "../role/Role";
17 | import Bar from "../chars/Bar";
18 | import Line from "../chars/Line";
19 | import Pie from "../chars/Pie";
20 | import Home from "../home";
21 | import Todo from "../todo";
22 | import TsStudy from "../ts-study";
23 |
24 | import Clinic from "../clinic/index";
25 | import User from "../user/User";
26 | import Record from "../register/record";
27 | import AddRegister from "../register/add-register";
28 | import ChargeManage from "../charge/charge-manage";
29 | import SettleRecord from "../charge/settle-record";
30 | import PrescriptionDetail from "../charge/prescription-detail";
31 | import Demo from "../demo";
32 | import DemoHooks from "../demo-hooks";
33 | import DemoComponents from "../demo-components";
34 |
35 | import ZentBase from "../zent/base";
36 | import ZentNav from "../zent/nav";
37 | import ZentData from "../zent/data";
38 | import ZentShow from "../zent/show";
39 | import ZentFeedback from "../zent/feedback";
40 |
41 | import { connect } from "react-redux";
42 | import { RootState } from "typesafe-actions";
43 | import NotFound from "../not-found/not-found";
44 | const { Footer, Sider, Content } = Layout;
45 |
46 | type IProps = ReturnType;
47 |
48 | class admin extends Component {
49 | render() {
50 | const user = sessionStorage.getItem("user");
51 | if (!user) {
52 | return ;
53 | }
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
86 |
87 |
88 |
89 | {/* 废弃 */}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
103 |
104 |
105 | );
106 | }
107 | }
108 |
109 | const mapStateToProps = (state: RootState) => ({
110 | user: state.user,
111 | });
112 |
113 | export default connect(mapStateToProps, {})(admin);
114 |
--------------------------------------------------------------------------------
/src/components/affix-tabs-nav/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames';
2 | import { find, get, some, throttle } from 'lodash';
3 | import React from 'react';
4 | import { Affix, IAffixProps, ITab, ITabsProps, Tabs } from 'zent';
5 |
6 | interface IAffixTab extends ITab {
7 | target: string;
8 | hidden?: boolean;
9 | }
10 |
11 | interface IAffixTabsProps extends ITabsProps {
12 | defaultActiveId: T;
13 | tabs: Array>;
14 | }
15 |
16 | interface IProps {
17 | offset?: number;
18 | pinClassName?: string;
19 | affixProps?: IAffixProps;
20 | tabsProps: Partial>;
21 | onActiveIdChange?: (key: string) => void;
22 | }
23 |
24 | /**
25 | * 给定 目标元素 或 目标选择器 滚动到对应位置
26 | * @param target string | HTMLElement
27 | * @param offset number
28 | */
29 | const scrollTo = (target: string | HTMLElement, offset = 0) => {
30 | if (!target) return;
31 |
32 | let $target: HTMLElement | null;
33 | if (typeof target === 'string') {
34 | $target = document.querySelector(target);
35 | } else {
36 | $target = target;
37 | }
38 |
39 | if ($target) {
40 | const targetTop = $target.getBoundingClientRect().top + (document.documentElement || document.body).scrollTop;
41 | const offsetTop = targetTop - offset;
42 | window.scrollTo({ top: offsetTop!, behavior: 'smooth' });
43 | }
44 | };
45 |
46 | /**
47 | * Affix + Tabs 组成的固定导航
48 | * tabsProps 属性会覆盖内部的非受控逻辑 activeId + onChange
49 | * @param props
50 | */
51 | function AffixTabsNav(props: IProps): React.ReactElement {
52 | const { affixProps, tabsProps, offset, pinClassName = 'rc__affix-tabs-nav__wrap--pined', onActiveIdChange } = props;
53 |
54 | // 计算默认 选中
55 | const defaultActiveId = React.useMemo(() => {
56 | return tabsProps.defaultActiveId || get(tabsProps, 'tabs[0]key');
57 | }, [tabsProps]);
58 |
59 | // 处理非受控逻辑
60 | const [activeId, setActiveId] = React.useState(defaultActiveId);
61 | const handleChange = React.useCallback(
62 | (activeId) => {
63 | const tab = find(tabsProps.tabs, (tab) => tab.key === activeId);
64 | if (tab?.key) {
65 | scrollTo(tab.target, offset);
66 | }
67 |
68 | setActiveId(activeId);
69 | onActiveIdChange?.(activeId);
70 | },
71 | [tabsProps, offset, onActiveIdChange]
72 | );
73 |
74 | // 处理滚动逻辑
75 | React.useEffect(() => {
76 | const timeout = 100;
77 | // 滚动完成后的处理
78 | const scrollEndFix = () => {
79 | let minDistance = 9999;
80 | let targetId: T | null = null;
81 | some(tabsProps.tabs, (tab: any) => {
82 | const dis = Math.abs(document.querySelector(tab.target)?.getBoundingClientRect()?.top ?? 9999);
83 | if (dis > minDistance) {
84 | return true;
85 | }
86 |
87 | if (dis <= minDistance) {
88 | targetId = tab.key;
89 | minDistance = dis;
90 | return false;
91 | }
92 | });
93 | if (targetId !== null) {
94 | setActiveId(targetId);
95 | }
96 | };
97 | scrollEndFix.timer = (0 as unknown) as NodeJS.Timeout;
98 |
99 | // 滚动处理
100 | const scrollHandler = () => {
101 | some(tabsProps.tabs, (tab: any) => {
102 | const $target = document.querySelector(tab.target);
103 | if (!$target) return;
104 |
105 | const rect = $target.getBoundingClientRect();
106 | const distance = Math.abs(rect.top);
107 | const distanceOffset = Math.max(offset || 0, 48);
108 |
109 | if (distance <= distanceOffset) {
110 | setActiveId(tab.key);
111 | return true;
112 | }
113 | });
114 |
115 | clearTimeout(scrollEndFix.timer);
116 | scrollEndFix.timer = setTimeout(() => {
117 | scrollEndFix();
118 | }, timeout * 2);
119 | };
120 | const throttledHandler = throttle(scrollHandler, timeout);
121 | window.addEventListener('scroll', throttledHandler);
122 |
123 | return () => {
124 | window.removeEventListener('scroll', throttledHandler);
125 | };
126 | // eslint-disable-next-line react-hooks/exhaustive-deps
127 | }, [setActiveId, tabsProps, offset]);
128 |
129 | // props 处理
130 | const { activeId: propsActiveId, onChange: propsOnChange, tabs, ...rest } = tabsProps;
131 | const passTabsProps = {
132 | activeId: propsActiveId ?? activeId,
133 | onChange: propsOnChange ?? handleChange,
134 | tabs: tabs?.filter((tab) => !tab.hidden),
135 | ...rest,
136 | };
137 |
138 | // affix props 处理
139 | const { onPin, onUnpin, ...passAffixProps } = affixProps || {};
140 | const [pined, setPined] = React.useState(false);
141 | const handlePin = React.useCallback(() => {
142 | setPined(true);
143 | onPin && onPin();
144 | }, [onPin]);
145 | const handleUnPin = React.useCallback(() => {
146 | setPined(false);
147 | onUnpin && onUnpin();
148 | }, [onUnpin]);
149 | const pinCls = React.useMemo(() => {
150 | return pined ? pinClassName || '' : '';
151 | }, [pined, pinClassName]);
152 |
153 | // 渲染
154 | return (
155 |
162 |
163 |
164 | );
165 | }
166 |
167 | AffixTabsNav.defaultProps = {
168 | affixProps: {},
169 | };
170 |
171 | export default AffixTabsNav;
172 |
--------------------------------------------------------------------------------
/src/pages/todo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./index.scss";
3 | import { allList } from "./mock";
4 | import TaskCard from "./task-card";
5 | import { TASK_STATUS } from "./constants";
6 | import { ITask } from "./types";
7 | import TaskDialog from "./dialog";
8 | import {
9 | LayoutRow as Row,
10 | LayoutCol as Col,
11 | LayoutGrid as Grid,
12 | LayoutConfigProvider as ConfigProvider,
13 | ScrollAlert,
14 | AlertItem,
15 | Notify,
16 | Button,
17 | Card,
18 | } from "zent";
19 |
20 | function Todo() {
21 | const localTask = localStorage.getItem("taskList");
22 | const initialValue = localTask ? JSON.parse(localTask) : allList;
23 | const [taskList, setTaskList] = React.useState(initialValue);
24 | const [dialogVisible, setDialogVisible] = React.useState(false);
25 | const [dialogType, setDialogType] = React.useState("");
26 | const [taskInfo, setTaskInfo] = React.useState();
27 |
28 | const onAddTask = () => {
29 | // Notify.info("功能开发中");
30 | setDialogType("add");
31 | setDialogVisible(true);
32 | };
33 | const onCloseDialog = () => {
34 | setDialogVisible(false);
35 | };
36 | // useEffect(() => {
37 | // localStorage.setItem('taskList', JSON.stringify(taskList))
38 | // // Notify.warn('任务发生了改变')
39 | // }, [taskList])
40 | const onConfirmDialog = (form) => {
41 | const task = {
42 | id: Math.random(),
43 | status: TASK_STATUS.TODO,
44 | ...form,
45 | };
46 | setTaskList([...taskList, task]);
47 | Notify.success("保存成功");
48 | setDialogVisible(false);
49 | };
50 | const todoList: ITask[] = taskList.filter(
51 | (v) => v.status === TASK_STATUS.TODO
52 | );
53 | const doingList: ITask[] = taskList.filter(
54 | (v) => v.status === TASK_STATUS.DOING
55 | );
56 | const doneList: ITask[] = taskList.filter(
57 | (v) => v.status === TASK_STATUS.DONE
58 | );
59 |
60 | const onStatusChange = (task) => {
61 | console.log(task);
62 | if (task?.status === TASK_STATUS.DONE) {
63 | setDialogType("detail");
64 | setTaskInfo(task);
65 | setDialogVisible(true);
66 | return;
67 | }
68 | if (
69 | task?.status === TASK_STATUS.TODO ||
70 | task?.status === TASK_STATUS.DOING
71 | ) {
72 | let newTask = taskList.filter((v) => v.id !== task.id);
73 | task.status += 1;
74 | newTask.push(task);
75 | setTaskList(newTask);
76 | }
77 | };
78 | return (
79 |
80 |
86 |
87 |
88 |
89 | {taskList.map((task) => {
90 | return (
91 | {
99 | onStatusChange(task);
100 | }}
101 | >
102 | 查看详情
103 |
104 | }
105 | />
106 | );
107 | })}
108 |
109 |
110 |
111 |
112 |
113 |
120 | {
126 | onAddTask();
127 | }}
128 | >
129 | 新建任务
130 |
131 | }
132 | >
133 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | );
175 | }
176 |
177 | export default Todo;
178 |
--------------------------------------------------------------------------------
/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/pages/product/home.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Card, Input, message, Select, Table } from 'antd';
2 | import React, { Component } from 'react';
3 | import { PlusOutlined } from '@ant-design/icons';
4 | import LinkButton from '../../components/link-button';
5 | import { reqProducts, reqProductsByDesc, reqProductsByName, reqUpdateStatus } from '../../api';
6 | import { ProductsModel } from './Model';
7 | import { PageSplitModel } from '../../api/Model';
8 | import { PAGE_SIZE } from '../../utils/Constants';
9 | import { RouteComponentProps, withRouter } from 'react-router';
10 | import MemoryUtils from '../../utils/MemoryUtils';
11 | /**
12 | * product的默认子路由组件
13 | */
14 |
15 | interface ProductHomeState {
16 | products: ProductsModel[] | undefined;
17 | total: number;
18 | loading: boolean;
19 | searchInfo: string;
20 | searchType: string;
21 | pageNum: number;
22 | }
23 |
24 | interface ProductHomeProps {}
25 |
26 | type ProductHomeRouteProps = ProductHomeProps & RouteComponentProps;
27 | class ProductHome extends Component {
28 | private columns: any[] = [];
29 | private pageNum: number = 1;
30 | constructor(props: ProductHomeRouteProps) {
31 | super(props);
32 | this.state = {
33 | products: [],
34 | total: 0,
35 | loading: false,
36 | searchInfo: '',
37 | searchType: 'searchByName',
38 | pageNum: 1,
39 | };
40 |
41 | this.initColumns();
42 | }
43 |
44 | private showDetails = (product: ProductsModel): void => {
45 | MemoryUtils.product = product;
46 | this.props.history.push('/product/detail');
47 | };
48 |
49 | private showUpdate = (product: ProductsModel) => {
50 | MemoryUtils.product = product;
51 | this.props.history.push('/product/add');
52 | };
53 | /**
54 | * @name: 初始化表头
55 | * @test: test font
56 | * @msg:
57 | * @param {*}
58 | * @return {*}
59 | */
60 | private initColumns = (): void => {
61 | this.columns = [
62 | {
63 | title: '商品名称',
64 | dataIndex: 'name',
65 | },
66 | {
67 | title: '商品描述',
68 | dataIndex: 'desc',
69 | },
70 | {
71 | title: '价格',
72 | dataIndex: 'price',
73 | render: (price: any) => {
74 | return '¥' + price;
75 | },
76 | },
77 | {
78 | title: '状态',
79 | render: (product: ProductsModel) => {
80 | const { status, id } = product;
81 | return (
82 |
83 |
95 | {status === 1 ? '在售' : '已下架'}
96 |
97 | );
98 | },
99 | },
100 | {
101 | title: '操作',
102 | render: (product: ProductsModel) => {
103 | return (
104 |
105 | {
107 | this.showDetails(product);
108 | }}
109 | >
110 | 详情
111 |
112 | {
114 | this.showUpdate(product);
115 | }}
116 | >
117 | 修改
118 |
119 |
120 | );
121 | },
122 | },
123 | ];
124 | };
125 |
126 | private updateStatus = async (id: number, status: number) => {
127 | const result = await reqUpdateStatus(id, status);
128 | if (result === 1) {
129 | this.getDataSources(this.pageNum);
130 | }
131 | };
132 |
133 | componentDidMount() {
134 | this.getDataSources(1);
135 | }
136 |
137 | private getDataSources = async (pageNum: number) => {
138 | this.pageNum = pageNum;
139 | this.setState({ loading: true });
140 | let result: PageSplitModel;
141 | if (this.state.searchInfo !== '') {
142 | result = await this.searchByNameOrDesc(pageNum);
143 | } else {
144 | result = await reqProducts(pageNum, PAGE_SIZE);
145 | }
146 | this.setState(() => {
147 | return {
148 | products: result.list,
149 | total: result.total,
150 | loading: false,
151 | pageNum: pageNum,
152 | };
153 | });
154 | };
155 |
156 | private searchByNameOrDesc = (pageNum: number): Promise> => {
157 | const { searchType, searchInfo } = this.state;
158 | if (searchType === 'searchByName') {
159 | return reqProductsByName(searchInfo, pageNum, PAGE_SIZE);
160 | } else {
161 | return reqProductsByDesc(searchInfo, pageNum, PAGE_SIZE);
162 | }
163 | };
164 |
165 | render() {
166 | const { products, total, loading, searchType, searchInfo, pageNum } = this.state;
167 | const title = (
168 |
169 |
173 | this.setState({ searchInfo: e.target.value })}
178 | >
179 |
187 |
188 | );
189 | const extra = (
190 | }
193 | onClick={() => {
194 | this.props.history.push('/product/add');
195 | }}
196 | >
197 | 添加商品
198 |
199 | );
200 | return (
201 |
202 |
216 |
217 | );
218 | }
219 | }
220 |
221 | export default withRouter(ProductHome);
222 |
--------------------------------------------------------------------------------
/src/pages/zent/nav.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import JJAffix from "../../components/zent/affix";
3 | import JJTable from "../../components/zent/table";
4 | import TableHeaderGroup from "../../components/zent/table-header-group";
5 | import JJTree from "../../components/zent/tree";
6 | import JJTreePlain from "../../components/zent/tree-plain";
7 | import { Breadcrumb } from "zent";
8 |
9 | import {
10 | LayoutRow as Row,
11 | LayoutCol as Col,
12 | LayoutGrid as Grid,
13 | LayoutConfigProvider as ConfigProvider,
14 | } from "zent";
15 |
16 | import {
17 | Dropdown,
18 | Menu,
19 | DropdownButton,
20 | DropdownPosition,
21 | DropdownClickTrigger,
22 | DropdownContent,
23 | } from "zent";
24 | import { Alert, Pagination, Notify, Steps, Tabs } from "zent";
25 | const { MenuItem } = Menu;
26 |
27 | class Nav extends React.Component {
28 | state = {
29 | current: 1,
30 | dataList: [
31 | { name: "zent", href: "/" },
32 | { name: "Demo", href: "/demo" },
33 | { name: "Demo-Hooks", href: "/demo-hooks" },
34 | { name: "Demo-Components" },
35 | ],
36 | };
37 | render() {
38 | return (
39 | <>
40 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
77 | 菜单 Dropdown(项目中没有用到)
78 |
79 |
80 |
81 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | {
106 | Notify.info("分页组件切换");
107 | }}
108 | />
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
121 |
122 |
123 |
124 |
125 | {
129 | this.setState({ current: this.state.current + 1 });
130 | }}
131 | >
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | {
145 | this.setState({ current: this.state.current + 1 });
146 | }}
147 | type="button"
148 | >
149 | 选项一} id={1}>
150 | 选项一的内容
151 |
152 |
153 | 选项二的内容
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
174 |
175 |
176 | >
177 | );
178 | }
179 | }
180 |
181 | export default Nav;
182 |
--------------------------------------------------------------------------------
/src/pages/user/User.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version:
4 | * @Author: alexwjj
5 | * @Date: 2020-10-14 21:16:42
6 | * @LastEditors: alexwjj
7 | * @LastEditTime: 2021-02-04 16:20:43
8 | */
9 | import ProForm, { ModalForm, ProFormSelect, ProFormText } from '@ant-design/pro-form';
10 | import { Button, Card, message, Space, Table } from 'antd';
11 | import React, { useEffect, useState } from 'react';
12 | import { reqAddUser, reqDeleteUser, reqRoles, reqUpdateUser, reqUsers } from '../../api';
13 | import { UserModel } from './model';
14 | import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons';
15 | import WrappedProFormText from '@ant-design/pro-form/lib/components/Text';
16 | import { RoleModel } from '../role/Model';
17 | import {logout} from '../../redux/actions';
18 | import { useDispatch, useSelector } from 'react-redux';
19 | import { RootState } from 'typesafe-actions';
20 |
21 |
22 | const User = () => {
23 | const [users, setUsers] = useState([]);
24 | const [roles, setRoles] = useState([]);
25 | const [user, setUser] = useState();
26 | const [isUpdate, setIsUpdate] = useState(false);
27 | const dispath =useDispatch();
28 | const loginUser = useSelector((state:RootState)=>state.user);
29 | const columns = [
30 | {
31 | title: '用户名',
32 | dataIndex: 'name',
33 | },
34 | {
35 | title: '邮箱',
36 | dataIndex: 'email',
37 | },
38 | {
39 | title: '电话',
40 | dataIndex: 'phone',
41 | },
42 | {
43 | title: '注册时间',
44 | dataIndex: 'createTime',
45 | },
46 | {
47 | title: '所属角色',
48 | dataIndex: 'roleId',
49 | render: (roleId: string) => roles.find((r) => r.id?.toString() === roleId)?.name,
50 | },
51 | {
52 | title: '操作',
53 | dataIndex: 'action',
54 | render: (text: any, record: UserModel) => (
55 |
56 | 修改}
62 | modalProps={{
63 | onCancel: () => console.log(text, record),
64 | afterClose: () => {
65 | if (user?.name === loginUser.name) {
66 | dispath(logout());
67 | }
68 | },
69 | }}
70 | onFinish={async (values: Record): Promise => {
71 | Object.assign(record, values);
72 | const result: string = await reqUpdateUser(record);
73 | if (result === 'success') {
74 | message.success('修改成功');
75 | setUser(record);
76 | } else {
77 | message.error('修改失败');
78 | }
79 | return true;
80 | }}
81 | >
82 | {proForm(record)}
83 |
84 | 删除}
90 | modalProps={
91 | {
92 | // onCancel: () => console.log(text, record),
93 | }
94 | }
95 | onFinish={async (values: Record): Promise => {
96 | if (record.id) {
97 | const result: string = await reqDeleteUser(record.id);
98 | if (result === 'success') {
99 | setUser(record);
100 | message.success('删除成功');
101 | } else {
102 | message.error('删除失败');
103 | }
104 | }
105 | return true;
106 | }}
107 | >
108 | 确定要删除此用户吗
109 |
110 |
111 | ),
112 | },
113 | ];
114 |
115 | useEffect(() => {
116 | let ignore: boolean = false;
117 | const fetchData = async () => {
118 | const users = await reqUsers();
119 | const roles = (await reqRoles()).data;
120 | if (!ignore) {
121 | setRoles(roles ?? []);
122 | setUsers(users);
123 | }
124 | };
125 | fetchData();
126 | return () => {
127 | ignore = true;
128 | setIsUpdate(false);
129 | };
130 | }, [isUpdate, user]);
131 |
132 | const proForm = (user?: UserModel): React.ReactElement => {
133 | return (
134 |
135 |
143 | {user === undefined ? (
144 | (visible ? : ),
153 | }}
154 | >
155 | ) : (
156 | (visible ? : ),
166 | }}
167 | >
168 | )}
169 |
177 |
185 |
186 | {
189 | return roles.reduce((acc: { label?: string; value?: string }[], curValue: RoleModel): {
190 | label?: string;
191 | value?: string;
192 | }[] => {
193 | acc.push({ label: curValue.name.toString(), value: curValue.id?.toString() });
194 | return acc;
195 | }, []);
196 | }}
197 | initialValue={user === undefined ? null : roles.find((r) => r.id?.toString() === user.roleId)?.name}
198 | label="角色"
199 | name="roleId"
200 | />
201 |
202 |
203 | );
204 | };
205 |
206 | const title = (
207 |
208 | 创建角色}
214 | modalProps={{
215 | onCancel: () => console.log('run1'),
216 | }}
217 | onFinish={async (values: Record): Promise => {
218 | const result = await reqAddUser(values);
219 | if (result === 'success') {
220 | message.success('添加用户成功');
221 | users.push(values);
222 | setIsUpdate(true);
223 | } else {
224 | message.error('添加用户失败');
225 | }
226 | return true;
227 | }}
228 | >
229 | {proForm()}
230 |
231 |
232 | );
233 |
234 | return (
235 |
240 | );
241 | };
242 |
243 | export default User;
244 |
--------------------------------------------------------------------------------
/src/pages/category/Category.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * _oo0oo_
3 | * o8888888o
4 | * 88" . "88
5 | * (| -_- |)
6 | * 0\ = /0
7 | * ___/`---'\___
8 | * .' \\| |// '.
9 | * / \\||| : |||// \
10 | * / _||||| -:- |||||- \
11 | * | | \\\ - /// | |
12 | * | \_| ''\---/'' |_/ |
13 | * \ .-\__ '-' ___/-. /
14 | * ___'. .' /--.--\ `. .'___
15 | * ."" '< `.___\_<|>_/___.' >' "".
16 | * | | : `- \`.;`\ _ /`;.`/ - ` : | |
17 | * \ \ `_. \_ __\ /__ _/ .-` / /
18 | * =====`-.____`.___ \_____/___.-`___.-'=====
19 | * `=---='
20 | *
21 | *
22 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 | *
24 | * 佛祖保佑 永不宕机 永无BUG
25 | *
26 | * 佛曰:
27 | * 写字楼里写字间,写字间里程序员;
28 | * 程序人员写程序,又拿程序换酒钱。
29 | * 酒醒只在网上坐,酒醉还来网下眠;
30 | * 酒醉酒醒日复日,网上网下年复年。
31 | * 但愿老死电脑间,不愿鞠躬老板前;
32 | * 奔驰宝马贵者趣,公交自行程序员。
33 | * 别人笑我忒疯癫,我笑自己命太贱;
34 | * 不见满街漂亮妹,哪个归得程序员?
35 | */
36 |
37 | /*
38 | * @Descripttion:
39 | * @version:
40 | * @Author: alexwjj
41 | * @Date: 2020-10-14 21:16:42
42 | * @LastEditors: alexwjj
43 | * @LastEditTime: 2020-11-02 17:38:13
44 | */
45 |
46 | import { Button, Card, message, Table } from 'antd';
47 | import React, { Component } from 'react';
48 | import { PlusOutlined, ArrowRightOutlined } from '@ant-design/icons';
49 | import LinkButton from '../../components/link-button';
50 | import { reqCategorys } from '../../api';
51 | import { ModalStatusCode } from './ModalStatusCode';
52 | import { CategoryModel, ICategory } from './Model';
53 | import UpdateFrom from './UpdateFrom';
54 | import AddForm from './AddForm';
55 |
56 | interface ICategoryProps {}
57 |
58 | interface ICategoryState {
59 | categorys: ICategory[];
60 | loading: boolean;
61 | parentId: string;
62 | parentName: string;
63 | showStatus: number;
64 | }
65 |
66 |
67 | export default class Category extends Component {
68 | private columns: any[] = [];
69 | private defaultCategory: ICategory = {
70 | parentId: '',
71 | _id: '',
72 | name: '',
73 | __v: 0,
74 | categoryName: '',
75 | parentName: '',
76 | };
77 | constructor(props: ICategoryProps) {
78 | super(props);
79 | this.initColumns();
80 | this.state = {
81 | loading: false,
82 | categorys: [],
83 | parentId: '0',
84 | parentName: '',
85 | showStatus: 0,
86 | };
87 | }
88 |
89 | componentDidMount() {
90 | const { parentId } = this.state;
91 | this.getCategory(parentId);
92 | }
93 |
94 | private getCategory = async (parentId: string) => {
95 | this.setLoading(true);
96 | const result: any = await reqCategorys(parentId);
97 | this.setLoading(false);
98 | if (result.status !== 0) {
99 | message.error('获取分类列表失败');
100 | return;
101 | } else {
102 | this.setCategorys(result);
103 | }
104 | };
105 |
106 | private setLoading = (isLoading: boolean): void => {
107 | this.setState(() => {
108 | return {
109 | loading: isLoading,
110 | };
111 | });
112 | };
113 |
114 | private setCategorys = (result: any): void => {
115 | let categorys: ICategory[] = result.data.map((item: CategoryModel) => {
116 | let tmp: ICategory = { parentId: item.parentId, _id: String(item.id), __v: 0, name: item.name, categoryName: item.categoryName, parentName: item.parentName };
117 | return tmp;
118 | });
119 | this.setState(() => {
120 | return {
121 | categorys: categorys,
122 | };
123 | });
124 | };
125 |
126 | /**
127 | * @name: 显示二级列表
128 | * @test:
129 | * @msg:
130 | * @param {category: ICategory)}
131 | * @return {void}
132 | */
133 | private showSubCategorys = (category: ICategory): void => {
134 | this.defaultCategory = category;
135 | this.setState(
136 | () => {
137 | return {
138 | parentId: category._id,
139 | parentName: category.name,
140 | };
141 | },
142 | () => {
143 | this.getCategory(this.state.parentId);
144 | }
145 | );
146 | };
147 |
148 | /**
149 | * @name: 显示一级列表
150 | * @test: test font
151 | * @msg:
152 | * @return {type}
153 | */
154 | private showCategorys = (category:ICategory=this.defaultCategory): void => {
155 | if (category._id==='') {
156 | this.setState(
157 | () => {
158 | return { parentId: '0', parentName: '' };
159 | },
160 | () => {
161 | this.getCategory(this.state.parentId);
162 | }
163 | );
164 | }else{
165 | this.showSubCategorys(category);
166 | }
167 | };
168 |
169 | /**
170 | * @name: 点击退出模态框
171 | * @test: test font
172 | * @msg:
173 | * @param {type}
174 | * @return {type}
175 | */
176 | private handleCancel = (): void => {
177 | this.showModalWithMutiForm(ModalStatusCode.Invisble);
178 | };
179 |
180 | /**
181 | * @name: 显示模态框
182 | * @test: test font
183 | * @msg:
184 | * @param {type}
185 | * @return {type}
186 | */
187 | private showModalWithMutiForm = (status: number = ModalStatusCode.Add): void => {
188 | this.setState({
189 | showStatus: status,
190 | });
191 | };
192 |
193 | private showUpdateCategory = (category: ICategory): void => {
194 | this.defaultCategory = category;
195 | this.showModalWithMutiForm(ModalStatusCode.Update);
196 | };
197 |
198 | /**
199 | * @name: 初始化表头
200 | * @test: test font
201 | * @msg:
202 | * @return void
203 | */
204 | private initColumns = (): void => {
205 | this.columns = [
206 | {
207 | title: '分类名称',
208 | dataIndex: 'name',
209 | },
210 | {
211 | title: '操作',
212 | width: 300,
213 | render: (category: ICategory) => (
214 |
215 | this.showUpdateCategory(category)}>修改分类
216 | {this.state.parentId === '0' ? (
217 | {
219 | this.showCategorys(category);
220 | }}
221 | >
222 | 查看子分类
223 |
224 | ) : null}
225 |
226 | ),
227 | },
228 | ];
229 | };
230 |
231 | render() {
232 | const { categorys, loading, parentName, parentId } = this.state;
233 | const title: any =
234 | parentId === '0' ? (
235 | '一级分类列表'
236 | ) : (
237 |
238 | 一级分类列表
239 |
240 | {parentName}
241 |
242 | );
243 | const extra: React.ReactNode = (
244 | }
247 | onClick={() => {
248 | this.showModalWithMutiForm(ModalStatusCode.Add);
249 | }}
250 | >
251 | 添加
252 |
253 | );
254 | return (
255 |
256 |
257 |
258 |
259 |
260 | );
261 | }
262 | }
263 |
--------------------------------------------------------------------------------