├── 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 icon; 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 | {data.alt 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 |
34 |

{title}

35 | 36 |
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 | this.setVisible(false)} 46 | footer={} 47 | title="对话框" 48 | > 49 |

通过visible变量来控制弹窗的显示与隐藏,和element的相似

50 |
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 | react-ts 8 | 9 | 10 | react 11 | 12 | 13 | zent 14 | 15 |

16 | 17 | ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3f46c5cabc554d65b48b3bc4a80ee9c4~tplv-k3u1fbpfcp-watermark.image) 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 | ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/93320594dfea45bd955c28f074fe9733~tplv-k3u1fbpfcp-watermark.image) 49 | 50 | ### 2、[一名 vueCoder 总结的 React 基础](https://juejin.cn/post/6960556335092269063) 已完成 2021-5-10 51 | ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d96f20fe5b94c52ae7224e2df821b2d~tplv-k3u1fbpfcp-watermark.image) 52 | 53 | ### 3、[一篇够用的TypeScript总结](https://juejin.cn/post/6981728323051192357) 已完成 2021-7-6 54 | ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e8c0a52167474efda5eeb47f8cdb1cbb~tplv-k3u1fbpfcp-watermark.image) 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 |
61 | 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 | resetForm()} 46 | title={props.type === "add" ? "新建任务" : "任务详情"} 47 | > 48 |
49 | 59 | 68 | {props.type === "add" ? ( 69 |
70 | 73 | 76 |
77 | ) : ( 78 | "" 79 | )} 80 | 81 | {/* */} 82 | 83 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |
62 | {this.addFromElement()} 63 |
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 | {/* logo */} 47 |
48 |
49 | {/* logo */} 54 | 俊劫学习系统 55 |
56 | 60 | 用户名  61 | 66 | 67 | 68 | : 69 | 70 | } 71 | validators={[ 72 | Validators.minLength(5, "用户名至少 5 个字"), 73 | Validators.maxLength(25, "用户名最多 25 个字"), 74 | ]} 75 | helpDesc="用户名alexwjj" 76 | required="必填" 77 | /> 78 | 88 |
89 | 92 | 95 | 96 |
97 | 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 | img 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 | weather 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 | ( 99 |
100 |
进度
101 |
{percent}%
102 |
103 | )} 104 | /> 105 | 106 | 107 | 108 | 109 |
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 | 117 | {this.menuNodes} 118 | 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 |
116 | 117 |
Upload
118 |
119 | ); 120 | return ( 121 |
122 | 132 | {fileList.length >= 3 ? null : uploadButton} 133 | 134 | 135 | example 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 |
49 | 59 | 69 | 79 | 85 | 92 | 99 | 107 | 电影 108 | 书籍 109 | 旅行 110 | 111 | 120 | 131 | 142 |
143 | 146 | 149 |
150 | 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 |
101 | 十年医院his系统架构师,专业搬砖,值得信赖 102 |
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 | 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 | 82 | subMenu01 83 | subMenu02 84 | 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 |
236 | 237 |
238 |
239 |
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 | 253 | ); 254 | return ( 255 | 256 | 257 | 258 | 259 | 260 | ); 261 | } 262 | } 263 | --------------------------------------------------------------------------------