('/log/geo/day', undefined, Method.GET).then(res => res.data)
9 | }
--------------------------------------------------------------------------------
/src/pages/threejs/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { EarthMap } from './earth'
3 |
4 | export default function ThreeJSPage() {
5 | useEffect(() => {
6 | //
7 | }, [])
8 | return
9 | This is Threejs Demo Page
10 |
11 |
12 | }
--------------------------------------------------------------------------------
/statics/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "React",
3 | "icons": [
4 | {
5 | "src": "logo.svg",
6 | "sizes": "192x192",
7 | "type": "image/svg"
8 | }
9 | ],
10 | "theme_color": "#ffffff",
11 | "background_color": "#ffffff",
12 | "start_url": "/React-PWA/",
13 | "display": "standalone"
14 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React + Vite
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/pages/logs/style.module.scss:
--------------------------------------------------------------------------------
1 |
2 | /* row-flex */
3 |
4 | .large {
5 | top: 80px;
6 |
7 | :global {
8 | .row {
9 | display: flex;
10 | padding-bottom: 12px;
11 |
12 | & > div {
13 | word-break: break-all;
14 |
15 | &:first-child {
16 | flex: 0 0 100px;
17 | }
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/pages/charts/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import DayChart from './day'
3 | import styles from './style.module.scss'
4 |
5 | export default function ChartPage() {
6 |
7 | return (
8 |
9 |
Date Matrix and Line Graphs of Requests from Around the World
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/scss/variables.scss:
--------------------------------------------------------------------------------
1 | $main-color: #fff;
2 |
3 | :root {
4 | --main-bg-color: #fff;
5 | --main-txt-color: #333;
6 | --main-border-color: #eee;
7 | }
8 |
9 | @media (prefers-color-scheme: dark) {
10 | :root {
11 | --main-bg-color: #000;
12 | --main-txt-color: #ddd;
13 | }
14 | }
15 |
16 | body[theme=dark] {
17 | --main-bg-color: #000;
18 | --main-txt-color: #ddd;
19 | }
--------------------------------------------------------------------------------
/src/plugins/redux/model.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface IAction {
3 | type: string
4 | playload?: any
5 | }
6 |
7 | export interface IAnyObject {
8 | [key: string]: any
9 | }
10 |
11 | export interface ICreateStore {
12 | getState: () => any
13 | dispatch: (action: IAction) => void
14 | subscribe: (listener: Function) => () => void
15 | replaceReducer: (nextReducer: Function) => void
16 | }
--------------------------------------------------------------------------------
/statics/logo.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/pages/demos/demo.module.scss:
--------------------------------------------------------------------------------
1 | .block {
2 | margin-bottom: 20px;
3 | padding: 12px;
4 | border-radius: 6px;
5 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 8px, rgba(0, 0, 0, 0.12) 0px 1px 6px;
6 | }
7 |
8 |
9 | .jsonView {
10 | min-height: 200px;
11 |
12 | :global {
13 | .ant-form-item-control-input-content > div {
14 | border-radius: 6px;
15 | min-height: 400px;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/assets/imgs/logo.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/pages/app.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react'
2 | import { AppStore, reducer, initState } from '@/stores'
3 | import { Navigation } from '@/routes/index'
4 |
5 | const App: React.FC = () => {
6 |
7 | const store = useReducer(reducer, initState)
8 |
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default App
--------------------------------------------------------------------------------
/src/scss/theme.scss:
--------------------------------------------------------------------------------
1 |
2 | /* dark mode */
3 | @media (prefers-color-scheme: dark) {
4 | body {
5 | background-color: var(--main-bg-color);
6 | color: var(--main-txt-color);
7 | }
8 |
9 | a {
10 | color: #4e9eff;
11 | }
12 | }
13 |
14 | /* custom dark mode */
15 | body[theme=dark] {
16 | background-color: var(--main-bg-color);
17 | color: var(--main-txt-color);
18 |
19 | a {
20 | color: #4e9eff;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/types/base.ts:
--------------------------------------------------------------------------------
1 |
2 | export class BaseModel {
3 | id: string
4 | createAt: number
5 | createBy: string
6 | updateAt: number
7 | updateBy: string
8 | deleteAt: number
9 | deleteBy: string
10 | }
11 |
12 |
13 | export enum DateFormat {
14 | Date = 'yyyy-MM-dd',
15 | DateDiagonal = 'yyyy/MM/dd',
16 | DateTime = 'yyyy-MM-dd HH:mm:ss',
17 | DateTimeM = 'yyyy-MM-dd HH:mm',
18 | DateTimeMS = 'yyyy-MM-dd HH:mm:SSS'
19 | }
--------------------------------------------------------------------------------
/src/plugins/redux/combineReducers.ts:
--------------------------------------------------------------------------------
1 | import { IAction, IAnyObject } from './model'
2 |
3 | // 组合reducer,计算后存为单个state
4 | export const combineReducers = (reducers: IAnyObject) => {
5 | // 这里重新组合state
6 | return (state: IAnyObject = {}, action: IAction) => {
7 | let newState: IAnyObject = {}
8 | for (let key in reducers) {
9 | newState[key] = reducers[key](state[key], action)
10 | }
11 | return newState
12 | }
13 | }
--------------------------------------------------------------------------------
/src/custom.d.ts:
--------------------------------------------------------------------------------
1 | import { MessageInstance } from 'antd/lib/message/interface'
2 | import { NotificationInstance } from 'antd/lib/notification/interface'
3 | import { AxiosStatic } from 'axios'
4 |
5 | declare global {
6 |
7 | const $http: AxiosStatic
8 |
9 | const $msg: MessageInstance
10 |
11 | const $notice: NotificationInstance
12 |
13 | interface ICommonProps {
14 | [key: string]: P
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/dashboard.ts:
--------------------------------------------------------------------------------
1 |
2 | type Stats = {
3 | count: number
4 | name: string
5 | }
6 |
7 | export class StatsData {
8 | statusCnt: {
9 | apiCnt: Stats[]
10 | errCnt: Stats[]
11 | }
12 | pathCnt: {
13 | apiCnt: Stats[]
14 | errCnt: Stats[]
15 | }
16 | }
17 |
18 | export type GeographicStats = {
19 | ip: string
20 | name_en: string
21 | sub_name_en: string
22 | latitude: string
23 | longitude: string
24 | total: string
25 | }
--------------------------------------------------------------------------------
/src/components/jsonView.tsx:
--------------------------------------------------------------------------------
1 | import React, {} from 'react'
2 | import { JsonView, allExpanded, darkStyles, defaultStyles } from 'react-json-view-lite';
3 | import 'react-json-view-lite/dist/index.css';
4 |
5 | interface IProps {
6 | data: Object | any[]
7 | }
8 |
9 | export const JSONView: React.FC = ({
10 | data
11 | }) => {
12 | return
17 | }
--------------------------------------------------------------------------------
/src/stores/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useAppStore } from './global'
2 | import { Action, setTheme } from './actions'
3 | import { THEME_MODE } from '@/constants'
4 | import { Theme } from '../types/global'
5 | import { storage } from '../utils/tools'
6 |
7 | /**
8 | * set app theme
9 | * @param theme enum Theme
10 | */
11 | export const useTheme = (theme: Theme, dispatch: React.Dispatch) => {
12 | storage.set(THEME_MODE, theme)
13 | dispatch(setTheme(theme))
14 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import App from './pages/app'
4 | import './scss/app.scss'
5 | import 'antd/dist/reset.css'
6 | import 'quill/dist/quill.snow.css'
7 | import serviceWorker from './serviceWorker'
8 | import { setRem } from '@utils/tools'
9 | import '@utils/i18n'
10 |
11 | // setRem();
12 |
13 | const container = document.getElementById('app')!
14 | createRoot(container).render()
15 | serviceWorker()
--------------------------------------------------------------------------------
/src/pages/home/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Layout, Flex, theme } from 'antd'
3 |
4 | const { Footer } = Layout
5 |
6 | export default () => {
7 | const { token: { boxShadow, paddingSM } } = theme.useToken()
8 | return
14 | }
--------------------------------------------------------------------------------
/src/pages/dashboard/style.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .dashboard {
3 | color: var(--main-txt-color);
4 | background-color: var(--main-bg-color);
5 | padding: 20px;
6 |
7 | h2 {
8 | text-align: center;
9 | padding-bottom: 8px;
10 | }
11 |
12 | .chart {
13 | position: relative;
14 | height: 400px;
15 | width: 100%;
16 | margin-bottom: 24px;
17 | border: 1px solid var(--main-border-color);
18 |
19 | :global(.amap-container) {
20 | left: 0;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/models/article.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base'
2 | import { ArticleType } from './articleType'
3 | import { Tag } from './tag'
4 |
5 | export interface Article extends BaseModel {
6 | title: string
7 |
8 | abstract: string
9 |
10 | description: string
11 |
12 | tag: string[]
13 | }
14 |
15 | export type ArticlesData = {
16 | articles?: IPageData
17 | article?: Article
18 | articleTypes: {
19 | list: ArticleType[]
20 | }
21 | tags: {
22 | list: Tag[]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/face/style.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .face {
3 | height: 100%;
4 |
5 | img, canvas {
6 | width: 100%;
7 | display: block;
8 | margin: 0 auto;
9 | }
10 | }
11 |
12 | .wrap {
13 | height: 100%;
14 |
15 | &> div {
16 | width: 44%;
17 | height: 100%;
18 | border: 1px dashed #ccc;
19 | border-radius: 4px;
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 |
24 | &.result {
25 | border-color:red;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/notFound.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import * as React from 'react'
3 | import { Button, Result } from 'antd'
4 | import { useNavigate } from 'react-router-dom'
5 |
6 |
7 | export const NotFound: React.FC = () => {
8 |
9 | const navigate = useNavigate()
10 |
11 | return navigate('/')}>Back Home}
16 | />
17 | }
18 |
--------------------------------------------------------------------------------
/src/types/demo.ts:
--------------------------------------------------------------------------------
1 | import { object2Options } from '@utils/tools'
2 |
3 | export interface APIFormTest {
4 | url: string
5 | method: Method
6 | apiType: APIType
7 | params: string
8 | }
9 |
10 |
11 | export enum Method {
12 | GET = 'GET',
13 | POST = 'POST',
14 | PUT = 'PUT',
15 | DELETE = 'DELETE',
16 | }
17 |
18 | export const methodOpts = object2Options(Method)
19 |
20 | export enum APIType {
21 | 'RESTful' = '/api',
22 | 'Graphql' = '/graphql'
23 | }
24 |
25 | export const apiTypeOpts = object2Options(APIType)
26 |
--------------------------------------------------------------------------------
/src/pages/charts/day/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { useChart } from './useChart'
3 | import { Spin } from 'antd'
4 | import styles from '../style.module.scss'
5 |
6 | export default function Chart() {
7 |
8 | const {
9 | loading,
10 | heatRef,
11 | chartRef,
12 | } = useChart()
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/pages/lottery/detail/style.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .ballWrap {
3 | flex-wrap: wrap;
4 | }
5 | .ball {
6 | width: 32px;
7 | height: 32px;
8 | border-radius: 100%;
9 | cursor: pointer;
10 | border: 1px solid #f54646;
11 | color: #f54646;
12 | transition: all .3s;
13 |
14 | &.active {
15 | background-color: #f54646;
16 | color: #fff;
17 | }
18 |
19 | &.blue {
20 | border-color: #39f;
21 | color: #39f;
22 |
23 | &.active {
24 | background-color: #39f;
25 | color: #fff;
26 | }
27 | }
28 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/utils/i18n/en_US.json:
--------------------------------------------------------------------------------
1 | {
2 | "confirm": "Confirm",
3 | "ok": "OK",
4 | "success": "Success",
5 | "introduction": "Hello, welcome to my personal skills showcase project. I am a web application developer, and my project is developed using React+Typescript. The scaffolding was written by myself from scratch. I am very passionate about programming and enjoy front-end development. Fully understand and be able to develop all front-end and back-end processes and functions, proficient in using technology stacks such as Node.js, Python, Typescript, ES6+, MySQL, etc."
6 | }
--------------------------------------------------------------------------------
/src/stores/global.ts:
--------------------------------------------------------------------------------
1 | import { createContext, Dispatch, useContext, useReducer } from 'react'
2 | import { Store } from '@/types/global'
3 | import { Action, } from './actions'
4 |
5 | export const initState = new Store
6 |
7 | type AppContextProps = [Store, Dispatch] | undefined
8 |
9 | export const AppStore = createContext(undefined)
10 |
11 | export const useAppStore = () => {
12 | const store = useContext(AppStore)
13 | if (!store) {
14 | throw new Error('useAppStore must be used within a AppStore.Provider');
15 | }
16 | return store
17 | }
--------------------------------------------------------------------------------
/src/models/user.ts:
--------------------------------------------------------------------------------
1 | import { object2Options } from '@/utils/tools'
2 | import { BaseModel } from './base'
3 |
4 | export class User extends BaseModel {
5 |
6 | username: string
7 |
8 | nickName: string
9 |
10 | userType: number
11 |
12 | sex: number
13 |
14 | remark: string
15 | }
16 |
17 | export enum UserType {
18 | 超级用户 = 0,
19 | 管理员 = 1,
20 | 普通用户 = 2,
21 | 测试用户 = 9,
22 | }
23 |
24 | export const userTypeOpts = object2Options(UserType)
25 |
26 |
27 | export enum UserSex {
28 | 女 = 0,
29 | 男 = 1,
30 | }
31 |
32 | export const userSexOpts = object2Options(UserSex)
--------------------------------------------------------------------------------
/src/types/api.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface APILog {
3 | host: string
4 |
5 | path: string
6 |
7 | url: string
8 |
9 | params: any
10 |
11 | method: string
12 |
13 | origin: string
14 |
15 | hostname: string
16 |
17 | headers: any
18 |
19 | resHeaders: any
20 |
21 | resData: any
22 |
23 | time: number
24 |
25 | protocol: string
26 |
27 | status: number
28 |
29 | msg: string
30 |
31 | client: string
32 | }
33 |
34 | export type RequestStatus = 'default' | 'success' | 'warning' | 'error'
35 |
36 | export type APIQuery = {
37 | url: string
38 | path: string
39 | createdAt: string
40 | }
--------------------------------------------------------------------------------
/src/models/ball.ts:
--------------------------------------------------------------------------------
1 | import { BaseModel } from './base'
2 |
3 | export interface Ball extends BaseModel {
4 |
5 | issue: number
6 |
7 | red1: number
8 |
9 | red2: number
10 |
11 | red3: number
12 |
13 | red4: number
14 |
15 | red5: number
16 |
17 | red6: number
18 |
19 | reds: number[]
20 |
21 | blue: number | number[]
22 |
23 | pool: number
24 |
25 | drawDate: string
26 | }
27 |
28 | export interface ChartOption {
29 | name: string
30 | value: number
31 | }
32 |
33 | export interface BallChart {
34 | reds: ChartOption[]
35 | blues: ChartOption[]
36 | redDisList: ChartOption[][]
37 | }
--------------------------------------------------------------------------------
/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | // jwt token key
2 | export const JWT_TOKEN = 'JWT_TOKEN'
3 |
4 | // system language key
5 | export const SYS_LANG = 'SYS_LANG'
6 |
7 | // redirect url key
8 | export const REDIRECT_URL = 'REDIRECT_URL'
9 |
10 | // graphql api modal
11 | export const GRAPHQL_API = '/graphql'
12 |
13 | export const COLOR_PRIMARY_KEY = 'COLOR_PRIMARY_KEY'
14 |
15 | export const PRIMARY_COLOR = '#087ea4'
16 |
17 | export const THEME_MODE = 'THEME_MODE'
18 |
19 | export const PREFERS_COLOR_SCHEME_DARK = '(prefers-color-scheme: dark)'
20 |
21 | export const EXCLUDE_KEYS = [
22 | COLOR_PRIMARY_KEY,
23 | THEME_MODE,
24 | ]
25 |
--------------------------------------------------------------------------------
/src/types/stock.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class Stock {
4 | id: number
5 | name: string
6 | code: string
7 | market: Market
8 | block: Board
9 | amount: number // 单手成交数量
10 |
11 | // 最后交易时间
12 | lastestTradeAt: number
13 | }
14 |
15 | export enum Market {
16 | 上证 = 1,
17 | 深证
18 | }
19 |
20 | export type KeyofMarket = keyof typeof Market
21 |
22 | export enum Board {
23 | 主板 = 1,
24 | 创业板,
25 | 科创板
26 | }
27 |
28 | export type KeyofBlock = keyof typeof Board
29 |
30 |
31 | export type StockQuery = Omit
32 |
33 | export type StockStats = {
34 | total: number
35 | block: Board
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/message.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'antd'
2 | import type { MessageInstance } from 'antd/es/message/interface'
3 | import type { ModalStaticFunctions } from 'antd/es/modal/confirm'
4 | import type { NotificationInstance } from 'antd/es/notification/interface'
5 |
6 | let message: MessageInstance
7 | let notification: NotificationInstance
8 | let modal: Omit
9 |
10 | export default function useAntApp() {
11 | const app = App.useApp()
12 | message = app.message
13 | modal = app.modal
14 | notification = app.notification
15 | return { message, notification, modal }
16 | }
17 |
18 | export { message, notification, modal }
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | 'extends': 'stylelint-config-standard',
4 | 'rules': {
5 | 'selector-list-comma-newline-after': 'never-multi-line',
6 | 'value-list-comma-newline-after': 'never-multi-line',
7 | 'selector-pseudo-class-no-unknown': null,
8 | 'declaration-colon-newline-after': null,
9 | 'no-descending-specificity': null,
10 | 'max-empty-lines': 2,
11 | 'at-rule-no-unknown': [true, {
12 | ignoreAtRules: [/for|while|include/]
13 | }]
14 | },
15 | ignoreFiles: [
16 | 'node_modules/**/*',
17 | 'public/**/*',
18 | 'dist/**/*',
19 | '**/*.js',
20 | '**/*.jsx',
21 | '**/*.tsx',
22 | '**/*.ts'
23 | ]
24 | }
--------------------------------------------------------------------------------
/src/components/loading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Spin } from 'antd'
3 |
4 | interface ILoadingProps {
5 | size?: 'small' | 'default' | 'large'
6 | }
7 |
8 | export default class Loading extends React.Component {
9 |
10 | state = {
11 | id: '123'
12 | }
13 |
14 | render () {
15 | const { size = 'default' } = this.props
16 |
17 | return (
18 |
24 |
25 |
26 | )
27 | }
28 | }
29 |
30 | const component = new Loading({size: 'default'})
--------------------------------------------------------------------------------
/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import { pageData2Params } from '@utils/tools'
2 | import { APILog, APIQuery } from 'types/api'
3 | import { ErrorLog, ErrorQuery } from 'types/apiError'
4 | import { useRequest } from './http'
5 | import { StatsData } from '@/types/dashboard'
6 |
7 |
8 | export const getMongoLogsStats = () => {
9 | return useRequest('/log/stats').then(r => r.data)
10 | }
11 |
12 |
13 | export const getApiLogs = (params: APIQuery & IPageParams) => {
14 | return useRequest('/log-api', { params })
15 | }
16 |
17 |
18 | export const getErrorLogs = (params: ErrorQuery & IPageParams) => {
19 | return useRequest('/log-errors', { params })
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/types/stockHistory.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class StockHistory {
4 | id: number
5 | stockId: number
6 | timestamp: number
7 | volume: number
8 | open: number
9 | high: number
10 | low: number
11 | close: number
12 | chg: number
13 | percent: number
14 | turnoverrate: number
15 | amount: number
16 | volume_post: number
17 | amount_post: number
18 | pe: number
19 | pb: number
20 | ps: number
21 | pcf: number
22 | market_capital: number
23 | balance: number
24 | hold_volume_cn: number
25 | hold_ratio_cn: number
26 | net_volume_cn: number
27 | hold_volume_hk: number
28 | hold_ratio_hk: number
29 | net_volume_hk: number
30 |
31 | tradeAt: string
32 | }
--------------------------------------------------------------------------------
/src/pages/home/components/siderbar.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .logo {
3 | height: 64px;
4 | display: flex;
5 | align-items: center;
6 | padding-left: 20px;
7 |
8 | svg {
9 | display: block;
10 | animation: logoRotate 6s linear 0s infinite normal running none;
11 |
12 | &:hover {
13 | animation-play-state: paused;
14 | }
15 | }
16 |
17 | .title {
18 | color: #fff;
19 | margin-left: 20px;
20 | font-weight: bold;
21 | }
22 | }
23 |
24 | :global(.ant-layout-sider-collapsed) .title {
25 | display: none;
26 | }
27 |
28 | @keyframes logoRotate {
29 | from {
30 | transform: rotateZ(0);
31 | }
32 |
33 | to {
34 | transform: rotateZ(360deg);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-typescript
2 |
3 | ### 项目概况
4 | ***
5 | 使用Ant-Design开发的一套后台管理系统,主要用到了React、Typescript、Mobx、PWA等技术,使用webpack5打包构建,包含React18的一些新特性。
6 |
7 | ### 项目主要技术结构
8 |
9 | ***
10 | * react
11 | * typescript
12 | * antd
13 | * hooks
14 | * webpack5
15 | * react-router5
16 | * pwa
17 |
18 | ### 安装
19 | ***
20 | 在终端下操作
21 |
22 | 项目地址: (`git clone`)
23 |
24 | ```
25 | git clone git@github.com:xpioneer/react-typescript.git
26 | ```
27 |
28 | 安装node_modules依赖 `yarn`
29 |
30 | ```
31 | yarn #in your command terminal
32 | ```
33 | ***
34 |
35 |
36 | ### 运行
37 | 启动开发服务: (http://localhost:8060)
38 |
39 | ```
40 | yarn start
41 | ```
42 |
43 | 生产环境打包
44 |
45 | ```
46 | yarn build
47 | ```
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/pages/login/style.module.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | width: 100%;
3 | height: 100vh;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | position: relative;
8 |
9 | .waveW {
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | width: 100%;
14 | height: 100%;
15 | }
16 |
17 | .form {
18 | position: relative;
19 | z-index: 1;
20 |
21 | h2 {
22 | color: var(--main-txt-color);
23 | }
24 |
25 | :global(.ant-checkbox-wrapper) {
26 | color: var(--main-txt-color);
27 | }
28 | }
29 | }
30 |
31 | .intro {
32 | position: absolute;
33 | top: 70%;
34 | left: 0;
35 | width: 100%;
36 | z-index: 1;
37 | padding: 0 240px;
38 | }
--------------------------------------------------------------------------------
/src/types/apiError.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface ErrorLog {
3 | id: string
4 |
5 | host: string
6 |
7 | ip: string
8 |
9 | path: string
10 |
11 | url: string
12 |
13 | params: any
14 |
15 | method: string
16 |
17 | origin: string
18 |
19 | hostname: string
20 |
21 | headers: any
22 |
23 | resHeaders: any
24 |
25 | resData: any
26 |
27 | time: number
28 |
29 | protocol: string
30 |
31 | status: number
32 |
33 | msg: string
34 |
35 | client: string
36 |
37 | errors: string[]
38 |
39 | createdAt: string
40 | }
41 |
42 |
43 | export type ErrorQuery = {
44 | url: string
45 | path: string
46 | msg: string
47 | createdAt: string
48 | _createdAt?: [Date, Date]
49 | }
--------------------------------------------------------------------------------
/src/services/account.ts:
--------------------------------------------------------------------------------
1 | import { LoginForm, RegisterForm } from "types/account"
2 | import $http from '@utils/http'
3 | import { storage } from "@/utils/tools"
4 | import { JWT_TOKEN } from "@/constants"
5 |
6 |
7 | export const onLogin = (data: LoginForm) => {
8 | return $http.post('/api/login', data).then(res => res.data)
9 | }
10 |
11 | export const onLogout = () => {
12 | return $http.post('/api/logout', {}).then((res: any) => {
13 | storage.remove(JWT_TOKEN)
14 | location.replace('/login')
15 | }, err => {
16 | // $msg.error(err.msg)
17 | })
18 | }
19 |
20 |
21 | export const onCreate = (data: LoginForm) => {
22 | return $http.post('/api/register', data).then(res => res.data)
23 | }
24 |
--------------------------------------------------------------------------------
/src/services/stock.ts:
--------------------------------------------------------------------------------
1 | import { Method } from '@/types/demo'
2 | import { pageData2Params } from '@utils/tools'
3 | import { StockStats, Stock } from 'types/stock'
4 | import { useRequest } from './http'
5 |
6 |
7 | export type StockQuery = Omit & { noPage: boolean }
8 |
9 | export const stockPageList = (params: Partial = pageData2Params()) => {
10 | return useRequest('/stocks', params)
11 | }
12 |
13 | export const getStockDetail = (id: number) => {
14 | return useRequest(`/stocks/${id}`).then(res => res.data)
15 | }
16 |
17 |
18 | export const getStockChartCount = () => {
19 | return useRequest('/stock/chartCount').then(res => res.data)
20 | }
--------------------------------------------------------------------------------
/.babelrc.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | presets: [
4 | [
5 | '@babel/preset-env',
6 | {
7 | targets: '> 0.25%, not dead',
8 | ignoreBrowserslistConfig: true,
9 | useBuiltIns: false,
10 | modules: false,
11 | exclude: ['transform-typeof-symbol']
12 | }
13 | ],
14 | '@babel/preset-react',
15 | [
16 | '@babel/preset-typescript',
17 | {
18 | isTSX: true,
19 | allExtensions: true,
20 | allowNamespaces: true,
21 | allowDeclareFields: true
22 | }
23 | ]
24 | ],
25 | plugins: [
26 | [
27 | '@babel/plugin-proposal-decorators',
28 | {legacy: true}
29 | ],
30 | // [
31 | // '@babel/plugin-proposal-class-properties',
32 | // {loose: true}
33 | // ]
34 | ]
35 | }
--------------------------------------------------------------------------------
/src/services/geography.ts:
--------------------------------------------------------------------------------
1 | import { useRequest } from "./http"
2 | import { SystemLog } from "@/types/geolog"
3 | import { Method } from "@/types/demo"
4 | import { GeographicStats } from "@/types/dashboard"
5 |
6 | export const getGeologs = (params: any) => {
7 | return useRequest('/log/geos', params, Method.GET)
8 | }
9 |
10 | export const getGeoGPSStats = () => {
11 | return useRequest('/log/geo/stats').then(r => r.data)
12 | }
13 |
14 | export const getGeoGPSChina = () => {
15 | return useRequest('/log/geo/china').then(r => r.data)
16 | }
17 |
18 | export const getGeoVisit = () => {
19 | return useRequest('/log/geo/visit').then(r => r.data)
20 | }
21 |
22 | export const getGeoMapStats = () => {
23 | return Promise.all([getGeoVisit(), getGeoGPSStats()])
24 | }
--------------------------------------------------------------------------------
/src/utils/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next'
2 | import { initReactI18next } from 'react-i18next'
3 | import en_US from './en_US.json'
4 | import zh_CN from './zh_CN.json'
5 | import zh_TW from './zh_TW.json'
6 | import { getCurrentLang } from '@/types/global'
7 |
8 | const currentLang = getCurrentLang()
9 |
10 | i18n
11 | .use(initReactI18next) // passes i18n down to react-i18next
12 | .init({
13 | resources: {
14 | en_US: {
15 | translation: en_US,
16 | },
17 | zh_CN: {
18 | translation: zh_CN,
19 | },
20 | zh_TW: {
21 | translation: zh_TW,
22 | },
23 | },
24 | lng: currentLang,
25 | fallbackLng: currentLang,
26 | interpolation: {
27 | escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
28 | },
29 | })
--------------------------------------------------------------------------------
/src/pages/demos/components/test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Flex, Space, Button, Badge } from 'antd'
3 |
4 | const useTest = () => {
5 | const [count, setCount] = useState(10)
6 | return {
7 | count,
8 | setCount,
9 | }
10 | }
11 |
12 | export const TestComponent: React.FC = () => {
13 | const { count, setCount } = useTest()
14 | return
15 |
16 | test1
17 |
18 |
19 | }
20 |
21 |
22 | export const TestComponent2: React.FC = () => {
23 | const { count, setCount } = useTest()
24 | return
25 |
26 | test2
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/src/pages/home/home.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Layout } from 'antd'
3 | import { Outlet } from 'react-router-dom'
4 | import { HeaderComponent } from './components/header'
5 | import Footer from './components/footer'
6 | import { SiderBar } from './components/siderbar'
7 | import { useAppStore } from '@/stores/global'
8 | import classNames from 'classnames'
9 | import styles from './style.module.scss'
10 |
11 | const { Content } = Layout
12 |
13 | const HomePage: React.FC = () => {
14 |
15 | const [{
16 | authorized
17 | }] = useAppStore()
18 |
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default HomePage
--------------------------------------------------------------------------------
/src/pages/stocks/kLine/index.tsx:
--------------------------------------------------------------------------------
1 | import { Spin, Form, Row, Col, Select } from 'antd'
2 | import React from 'react'
3 | import { useKLine } from './useKLine'
4 |
5 | const DayKLineChart: React.FC = () => {
6 |
7 | const {
8 | loading,
9 | chartRef,
10 | stockOpts,
11 | onQuery,
12 | onSelectSearch,
13 | } = useKLine()
14 |
15 | return
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 | }
34 |
35 | export default DayKLineChart
36 |
--------------------------------------------------------------------------------
/src/pages/dashboard/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useData } from './useData'
3 | import styles from './style.module.scss'
4 | import { Col, Row } from 'antd'
5 |
6 | export default function Dashboard() {
7 | const {
8 | visitRef,
9 | geoRef,
10 | earthRef,
11 | pathRef,
12 | statusRef,
13 | earthBDRef,
14 | } = useData()
15 |
16 | return
17 |
Request for Information from Around the World
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | }
--------------------------------------------------------------------------------
/src/services/stockHistory.ts:
--------------------------------------------------------------------------------
1 | // import $http from '@utils/http'
2 | import { pageData2Params } from '@utils/tools'
3 | import { StockQuery } from 'types/stock'
4 | import { StockHistory } from 'types/stockHistory'
5 | import { useRequest } from './http'
6 |
7 | export const stockHistoryPageList = (params: Partial = pageData2Params()) => {
8 | return useRequest('/stockhistory', params)
9 | // return $http.get>('/api/stockhistory', { params })
10 | }
11 |
12 | export const stockHistoryTotal = () => {
13 | return useRequest('/stockhistory/total').then(res => res.data)
14 | // return $http.get>('/api/stockhistory/total').then(res => res.data)
15 | }
16 |
17 | export const stockHistoryList = (data = {}) => {
18 | return useRequest('/stockline', data).then(res => res.data)
19 | // return useRequest('/stockline', data).then(res => res.data)
20 | }
--------------------------------------------------------------------------------
/src/plugins/redux/applyMiddlewares.ts:
--------------------------------------------------------------------------------
1 | import { IAction, ICreateStore } from './model'
2 |
3 | // compose
4 | const compose = (fns: Function[]) => fns.reduce((f, g) => (...args: any[]) => f(g(args)))
5 |
6 | // 中间件
7 | const loggerMiddleware = (store: any) => {
8 | return (next: Function) => {
9 | return (action: IAction) => {
10 | next(action)
11 | }
12 | }
13 | }
14 |
15 |
16 | export const applyMiddleware = (...middlewares: Function[]) => {
17 | return (createStore: Function) => {
18 | return (reducer: Function, initState: any): ICreateStore => {
19 | const store: ICreateStore = createStore(reducer, initState)
20 | const chain = middlewares.map(middleware => middleware(store))
21 | let dispatch = store.dispatch
22 | // chain.reverse().forEach(middleware => {
23 | // dispatch = middleware(dispatch)
24 | // })
25 | dispatch = compose(chain)(dispatch)
26 | store.dispatch = dispatch // 重写dispatch
27 | return store
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/utils/params.ts:
--------------------------------------------------------------------------------
1 |
2 | class Params {
3 | constructor () {
4 | //
5 | }
6 |
7 | public fmtPost (data: AnyObject) {
8 | const obj: AnyObject = {}
9 | for (const key in data) {
10 | if (data[key] !== undefined && data[key] !== '' && data[key] !== null && typeof(data[key]) !== 'object') {
11 | obj[key] = data[key]
12 | }
13 | if (typeof(data[key]) === 'object' && Object.keys(data[key]).length > 0) {
14 | obj[key] = data[key]
15 | }
16 | }
17 | return obj
18 | }
19 |
20 | public fmtGet (data: any) {
21 | const arr: any = []
22 | if (data !== null && typeof data === 'object') {
23 | for (const key in data) {
24 | if (data[key] !== undefined && data[key] !== '' && data[key] !== null) {
25 | const str = encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
26 | arr.push(str)
27 | }
28 | }
29 | }
30 | arr.push('_=' + Date.now())
31 | return arr.join('&')
32 | }
33 | }
34 |
35 | export const serialize = new Params
--------------------------------------------------------------------------------
/src/pages/logs/useApi.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { startOfDay, endOfDay } from 'date-fns'
4 | import { APILog, APIQuery } from '@/types/api'
5 | import { data2PageData, pageData2Params, data2AntPageData } from '@/utils/tools'
6 | import { getApiLogs } from '@/services/api'
7 |
8 |
9 | export const useApi = () => {
10 | const [form] = Form.useForm()
11 |
12 | const [loading, setLoading] = useState(false)
13 | const [pageData, setPageData] = useState(data2PageData())
14 |
15 | const onQuery = (page = 1, pageSize = 10) => {
16 | const params = {
17 | page,
18 | pageSize,
19 | }
20 | const vals = form.getFieldsValue()
21 | setLoading(true)
22 | getApiLogs({...params, ...vals}).then(res => {
23 | setPageData(data2PageData(res))
24 | }).finally(() => setLoading(false))
25 | }
26 |
27 | useEffect(() => {
28 | onQuery()
29 | }, [])
30 |
31 | return {
32 | form,
33 | loading,
34 | pageData,
35 | onQuery,
36 | }
37 | }
--------------------------------------------------------------------------------
/src/pages/logs/geography/useList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { startOfDay, endOfDay } from 'date-fns'
4 | import { SystemLog } from '@/types/geolog'
5 | import { data2PageData, pageData2Params, data2AntPageData } from '@/utils/tools'
6 | import { getGeologs } from '@/services/geography'
7 |
8 |
9 | export const useList = () => {
10 | const [form] = Form.useForm()
11 |
12 | const [loading, setLoading] = useState(false)
13 | const [pageData, setPageData] = useState(data2PageData())
14 |
15 | const onQuery = (page = 1, pageSize = 10) => {
16 | const params = {
17 | page,
18 | pageSize,
19 | }
20 | const vals = form.getFieldsValue()
21 | setLoading(true)
22 | getGeologs({...params, ...vals}).then(res => {
23 | setPageData(data2PageData(res))
24 | }).finally(() => setLoading(false))
25 | }
26 |
27 | useEffect(() => {
28 | // onQuery()
29 | }, [])
30 |
31 | return {
32 | form,
33 | loading,
34 | pageData,
35 | onQuery,
36 | }
37 | }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= htmlWebpackPlugin.options.title %>
8 |
9 |
10 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ES6",
5 | "module": "ESNext",
6 | "sourceMap": false,
7 | "esModuleInterop": false,
8 | "moduleResolution": "node",
9 | "resolveJsonModule": true,
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "removeComments": false,
13 | "noImplicitAny": true,
14 | // "suppressImplicitAnyIndexErrors": true,
15 | "allowSyntheticDefaultImports": true,
16 | "strictPropertyInitialization": false,
17 | "jsx": "react",
18 | "lib": [
19 | "dom",
20 | "es2015"
21 | ],
22 | "outDir": "./dist/",
23 | "baseUrl": "./src",
24 | "paths": {
25 | "@/*": ["*"],
26 | "@assets/*": ["assets/*"],
27 | "@components/*": ["components/*"],
28 | "@constants/*": ["constants/*"],
29 | "@models/*": ["models/*"],
30 | "@pages/*": ["pages/*"],
31 | "@plugins": ["plugins/*"],
32 | "@utils/*": ["utils/*"]
33 | },
34 | },
35 | "compileOnSave": false,
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
--------------------------------------------------------------------------------
/src/services/http.ts:
--------------------------------------------------------------------------------
1 | import { Method, APIType } from '@/types/demo'
2 | import { $http } from '@/utils/http'
3 |
4 |
5 | /**
6 | * init GraphQL
7 | * @param query graphql query
8 | * @param data parameters
9 | * @returns Promise | T>
10 | */
11 | export const useGraphQL = (
12 | query: string,
13 | variables: AnyObject = {},
14 | ) => {
15 | return $http.post>(APIType.Graphql, {
16 | query,
17 | variables
18 | }).then(res => {
19 | return res.data
20 | })
21 | }
22 |
23 | /**
24 | * useRequest
25 | * @param url 剔除`/api`的url
26 | * @param data 请求参数,json格式
27 | * @param method http method, 默认为get
28 | * @returns T类型
29 | */
30 | export const useRequest = (
31 | url: string,
32 | data?: AnyObject,
33 | method = Method.GET,
34 | ) => {
35 | const isGetMethod = method === Method.GET
36 | return $http : IResponseData>({
37 | baseURL: APIType.RESTful,
38 | url,
39 | method,
40 | data: isGetMethod ? null : data,
41 | params: isGetMethod ? data : null
42 | })
43 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 | import path from 'path'
4 |
5 | const resolvePath = (dir: string) => path.resolve(process.cwd(), dir)
6 |
7 | export default defineConfig({
8 | plugins: [react()],
9 | resolve: {
10 | alias: {
11 | '@': resolvePath('src'),
12 | '@assets': resolvePath('src/assets'),
13 | '@components': resolvePath('src/components'),
14 | '@constants': resolvePath('src/constants'),
15 | '@models': resolvePath('src/models'),
16 | '@pages': resolvePath('src/pages'),
17 | '@plugins': resolvePath('src/plugins'),
18 | '@utils': resolvePath('src/utils'),
19 | },
20 | },
21 | css: {
22 | preprocessorOptions: {
23 | less: {
24 | javascriptEnabled: true,
25 | },
26 | },
27 | },
28 | server: {
29 | port: 8061,
30 | open: true,
31 | proxy: {
32 | '/api': 'http://127.0.0.1:8020',
33 | '/graphql': 'http://127.0.0.1:8020',
34 | },
35 | },
36 | build: {
37 | outDir: 'dist',
38 | target: 'es2015',
39 | minify: 'terser', // need more time
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/src/pages/logs/useErrors.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { startOfDay, endOfDay } from 'date-fns'
4 | import { ErrorLog, ErrorQuery } from '@/types/apiError'
5 | import { data2PageData, pageData2Params, data2AntPageData } from '@/utils/tools'
6 | import { getErrorLogs } from '@/services/api'
7 |
8 |
9 | export const useErrors = () => {
10 | const [form] = Form.useForm()
11 |
12 | const [data, setData] = useState()
13 | const [loading, setLoading] = useState(false)
14 | const [pageData, setPageData] = useState(data2PageData())
15 |
16 | const onQuery = (page = 1, pageSize = 10) => {
17 | const params = {
18 | page,
19 | pageSize,
20 | }
21 | const vals = form.getFieldsValue()
22 | setLoading(true)
23 | getErrorLogs({...params, ...vals}).then(res => {
24 | setPageData(data2PageData(res))
25 | }).finally(() => setLoading(false))
26 | }
27 |
28 | useEffect(() => {
29 | onQuery()
30 | }, [])
31 |
32 | return {
33 | form,
34 | loading,
35 | pageData,
36 | onQuery,
37 | data,
38 | setData,
39 | }
40 | }
--------------------------------------------------------------------------------
/src/stores/reducer.ts:
--------------------------------------------------------------------------------
1 | import { LangI18n, Store, Theme } from '@/types/global'
2 | import { User } from '@/models/user'
3 | import { ACTION_TYPE, Action, } from './actions'
4 |
5 | export const reducer = (
6 | state: Store,
7 | { type, payload }: Action
8 | ): Store => {
9 | // console.log(type, payload)
10 | switch (type) {
11 | case ACTION_TYPE.SET_THEME:
12 | return {
13 | ...state,
14 | theme: payload as Theme,
15 | }
16 | case ACTION_TYPE.SET_LANG:
17 | return {
18 | ...state,
19 | lang: payload as LangI18n,
20 | }
21 | case ACTION_TYPE.SET_PRIMARY:
22 | return {
23 | ...state,
24 | colorPrimary: payload as string,
25 | }
26 | case ACTION_TYPE.SET_AUTHORIZED:
27 | return {
28 | ...state,
29 | authorized: payload as boolean,
30 | }
31 | case ACTION_TYPE.SET_LOADING:
32 | return {
33 | ...state,
34 | loading: payload as boolean,
35 | }
36 | case ACTION_TYPE.SET_USER_INFO:
37 | return {
38 | ...state,
39 | userInfo: payload as User,
40 | }
41 |
42 | default:
43 | return state
44 | }
45 | }
--------------------------------------------------------------------------------
/src/stores/actions.ts:
--------------------------------------------------------------------------------
1 | import { Store, LangI18n, Theme, getSystemTheme } from '@/types/global'
2 | import { User } from '@/models/user'
3 |
4 | export type ValueOfStore = Store[keyof Store]
5 |
6 | export enum ACTION_TYPE {
7 | SET_THEME,
8 | SET_PRIMARY,
9 | SET_AUTHORIZED,
10 | SET_LANG,
11 | SET_LOADING,
12 | SET_USER_INFO,
13 | }
14 |
15 | export type Action = {
16 | type: ACTION_TYPE
17 | payload: T
18 | }
19 |
20 | // actions
21 | export const setTheme = (payload = getSystemTheme()) => ({
22 | type: ACTION_TYPE.SET_THEME,
23 | payload
24 | })
25 |
26 | export const setPrimary = (payload: string) => ({
27 | type: ACTION_TYPE.SET_PRIMARY,
28 | payload
29 | })
30 |
31 | export const setAuthorized = (payload = false) => ({
32 | type: ACTION_TYPE.SET_AUTHORIZED,
33 | payload
34 | })
35 |
36 | export const setLang = (payload = LangI18n.简体中文) => ({
37 | type: ACTION_TYPE.SET_LANG,
38 | payload,
39 | })
40 |
41 | export const setLoading = (payload = false) => ({
42 | type: ACTION_TYPE.SET_LOADING,
43 | payload
44 | })
45 |
46 | export const setUserInfo = (payload: User) => ({
47 | type: ACTION_TYPE.SET_USER_INFO,
48 | payload
49 | })
50 |
--------------------------------------------------------------------------------
/src/utils/http.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { AxiosResponse, AxiosError } from 'axios'
3 | import { storage } from '@utils/tools'
4 | import { JWT_TOKEN } from '@constants/index'
5 | import helper from './httpHelper'
6 |
7 | export const $http = axios.create({
8 | baseURL: '',
9 | responseType: 'json',
10 | timeout: 60000 * 2
11 | })
12 |
13 | $http.interceptors.request.use(config => {
14 | config.headers.Authorization = 'Bearer ' + storage.get(JWT_TOKEN)
15 | return config
16 | }, error => {
17 | return Promise.reject(error)
18 | })
19 |
20 | $http.interceptors.response.use(response => {
21 | helper.successHelper(response)
22 | // console.log('response>>:', response)
23 | if (response.data.errors) {
24 | return Promise.reject(response.data.data)
25 | } else {
26 | return Promise.resolve(response.data) // status:200, normal
27 | }
28 | }, (error: AxiosError) => {
29 | // console.log(error, 'error')
30 | if (error.response && /^[456]\d{2}$/.test(`${error.response.status}`)) {
31 | helper.errorHelper(error.response)
32 | } else {
33 | $msg.error(error.toString()) // other err: code error
34 | }
35 | return Promise.reject(error.response?.data)
36 | })
37 |
38 | export default $http
39 |
--------------------------------------------------------------------------------
/src/routes/pageRoutes.tsx:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from 'react'
2 | import {
3 | createBrowserRouter,
4 | RouterProvider,
5 | RouteObject
6 | } from 'react-router-dom'
7 | import useAntApp from '@components/message'
8 | import Loading from '@components/loading'
9 | import { routes } from './routeConf'
10 | import { Progress } from '@/components/progress'
11 |
12 | const Home = lazy(() => import(/* webpackChunkName:"home" */ '@pages/home/home'))
13 | const Login = lazy(() => import(/* webpackChunkName:"login" */ '@pages/login'))
14 | const Register = lazy(() => import( /* webpackChunkName:"register" */'@pages/register'))
15 |
16 | const globalRoutes: RouteObject[] = [
17 | {
18 | path: 'login',
19 | element: ,
20 | },
21 | {
22 | path: 'register',
23 | element: ,
24 | },
25 | {
26 | path: '',
27 | element: ,
28 | children: routes.map(item => {
29 | item.element = }>{item.element}
30 | return item
31 | }),
32 | }
33 | ]
34 | export const Routes: React.FC = () => {
35 | const app = useAntApp() // just init
36 | const router = createBrowserRouter(globalRoutes)
37 |
38 | return }>
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/pages/leaveMsg/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { Form } from 'antd'
2 | import { useState, useEffect } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import { LeaveMsg } from '@models/leaveMsg'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query getLeaveMsg($id: String!){
9 | leaveMsg(id: $id){
10 | id
11 | description
12 | ip
13 | createdAt
14 | }
15 | }`
16 |
17 |
18 | export const useDetail = () => {
19 | const {id} = useParams<{id: string}>()
20 |
21 | const [form] = Form.useForm()
22 |
23 | const [loading, setLoading] = useState(false)
24 |
25 |
26 | const isEdit = !!Form.useWatch('id', form)
27 |
28 | const onSave = (vals: LeaveMsg) => {
29 | // setLoading(true)
30 | // useGraphQL<{id: string}>(
31 | // mutation,
32 | // vals
33 | // ).finally(() => setLoading(false))
34 | }
35 |
36 | const onQuery = () => {
37 | setLoading(true)
38 | useGraphQL<{leaveMsg: LeaveMsg}>(
39 | query,
40 | { id }
41 | ).then(res => form.setFieldsValue(res.leaveMsg))
42 | .finally(() => setLoading(false))
43 | }
44 |
45 | useEffect(() => {
46 | if(id) {
47 | onQuery()
48 | }
49 | }, [id])
50 |
51 | return {
52 | form,
53 | loading,
54 | isEdit,
55 | onQuery,
56 | onSave,
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/pages/comment/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { Form } from 'antd'
2 | import { useState, useEffect } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import { Comment } from '@models/comment'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query getComment($id: String!){
9 | comment(id: $id){
10 | id
11 | description
12 | ip
13 | client
14 | createdAt
15 | updatedAt
16 | }
17 | }`
18 |
19 |
20 | export const useDetail = () => {
21 | const {id} = useParams<{id: string}>()
22 |
23 | const [form] = Form.useForm()
24 |
25 | const [loading, setLoading] = useState(false)
26 |
27 |
28 | const isEdit = !!Form.useWatch('id', form)
29 |
30 | const onSave = (vals: Comment) => {
31 | // setLoading(true)
32 | // useGraphQL<{id: string}>(
33 | // mutation,
34 | // vals
35 | // ).finally(() => setLoading(false))
36 | }
37 |
38 | const onQuery = () => {
39 | setLoading(true)
40 | useGraphQL<{comment: Comment}>(
41 | query,
42 | { id }
43 | ).then(res => form.setFieldsValue(res.comment))
44 | .finally(() => setLoading(false))
45 | }
46 |
47 | useEffect(() => {
48 | if(id) {
49 | onQuery()
50 | }
51 | }, [id])
52 |
53 | return {
54 | form,
55 | loading,
56 | isEdit,
57 | onQuery,
58 | onSave,
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/pages/tag/useTag.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { Tag } from '@models/tag'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 |
8 | const query = `
9 | query articleTags($page: Int, $pageSize: Int, $order: pageOrder, $name: String, $createdAt: [String]){
10 | tags(page: $page, pageSize: $pageSize, order: $order, name: $name, createdAt: $createdAt){
11 | list{id,name,remark,createdAt,createdBy,updatedAt}
12 | meta{current,total,pageSize}
13 | }
14 | }`
15 |
16 | export const useTag = () => {
17 | const [form] = Form.useForm()
18 |
19 | const [loading, setLoading] = useState(false)
20 | const [pageData, setPageData] = useState(data2AntPageData())
21 |
22 | const onQuery = (page = 1, pageSize = 10) => {
23 | const params = {
24 | page,
25 | pageSize,
26 | }
27 | const vals = form.getFieldsValue()
28 | setLoading(true)
29 | useGraphQL<{tags: Tag}, true>(
30 | query,
31 | {
32 | ...params,
33 | ...vals
34 | }
35 | ).then(res => {
36 | setPageData(data2AntPageData(res.tags))
37 | }).finally(() => setLoading(false))
38 | }
39 |
40 | useEffect(onQuery, [])
41 |
42 | return {
43 | form,
44 | loading,
45 | pageData,
46 | onQuery,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/debounce.ts:
--------------------------------------------------------------------------------
1 |
2 | type AnyFunc = (...args: any[]) => any
3 |
4 | interface DebouncedFunc {
5 | (...args: Parameters): ReturnType | undefined;
6 | cancel(): void;
7 | }
8 |
9 | export const debounce = (
10 | fn: T,
11 | wait = 500,
12 | immediate = false
13 | ): DebouncedFunc => {
14 | let timer: number
15 | const later = (context: any, ...args: any[]) => {
16 | window.clearTimeout(timer);
17 | timer = window.setTimeout(() => {
18 | fn.apply(context, args);
19 | }, wait);
20 | }
21 | const debounced = function(this: any, ...args: any[]) {
22 | if (immediate && !timer) {
23 | fn.apply(this, args);
24 | }
25 | later(this, ...args)
26 | }
27 | debounced.cancel = () => {
28 | window.clearTimeout(timer)
29 | }
30 | return debounced as DebouncedFunc
31 | }
32 |
33 | export const throttle = (
34 | fn: T,
35 | wait = 500,
36 | immediate = false
37 | ): DebouncedFunc => {
38 | let timer: number
39 | const throttled = function(this: any, ...args: any[]) {
40 | if (!timer) {
41 | fn.apply(this, args);
42 | timer = window.setTimeout(() => {
43 | timer = 0;
44 | }, wait);
45 | }
46 | }
47 | throttled.cancel = () => {
48 | window.clearTimeout(timer)
49 | }
50 | return throttled as DebouncedFunc
51 | }
--------------------------------------------------------------------------------
/src/plugins/react-redux/provider.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { IAnyObject, ICreateStore } from '../redux/model'
3 | const PropTypes = require('prop-types')
4 |
5 | interface IProviderProps{
6 | store: IAnyObject
7 | children: React.ReactNode
8 | }
9 |
10 | export class ReduxProvider extends Component {
11 |
12 | static childContextTypes = {
13 | store: PropTypes.shape({
14 | subscribe: PropTypes.func.isRequired,
15 | dispatch: PropTypes.func.isRequired,
16 | getState: PropTypes.func.isRequired
17 | }).isRequired
18 | }
19 |
20 | constructor (props: IProviderProps) {
21 | super(props)
22 | this.$store = props.store
23 | console.log('ReduxProvider---constructor', 999)
24 | }
25 |
26 | $store: IAnyObject = {}
27 |
28 | getChildContext () {
29 | console.log('获取了---getChildContext', this.$store)
30 | return {
31 | store: this.$store
32 | }
33 | }
34 |
35 | render () {
36 | console.log('$store', this.props.children)
37 | return this.props.children
38 | }
39 | }
40 |
41 |
42 | // createContext
43 |
44 | export const { Provider, Consumer } = React.createContext(null)
45 |
46 | export const ReduxProvider1: React.FC = (props) => {
47 |
48 | return {(props as any).children}
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/articleType/useArticleType.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { ArticleType } from '@models/articleType'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const queryArticleTypes = `
8 | query articleTypePages($page: Int, $pageSize: Int, $order: pageOrder, $name: String, $createdAt: [String]){
9 | articleTypes(page: $page, pageSize: $pageSize, order: $order, name: $name, createdAt: $createdAt){
10 | list{id,name,remark,createdAt,createdBy}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export const useArticleType = () => {
16 | const [form] = Form.useForm()
17 |
18 | const [loading, setLoading] = useState(false)
19 | const [pageData, setPageData] = useState(data2AntPageData())
20 |
21 | const onQuery = (page = 1, pageSize = 10) => {
22 | const vals = form.getFieldsValue()
23 | setLoading(true)
24 | useGraphQL<{articleTypes: ArticleType}, true>(
25 | queryArticleTypes,
26 | {
27 | page,
28 | pageSize,
29 | ...vals
30 | }
31 | ).then(res => {
32 | setPageData(data2AntPageData(res.articleTypes))
33 | }).finally(() => setLoading(false))
34 | }
35 |
36 | useEffect(onQuery, [])
37 |
38 | return {
39 | form,
40 | loading,
41 | pageData,
42 | onQuery,
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/leaveMsg/useList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { LeaveMsg } from '@models/leaveMsg'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query leaveMsgPages($page: Int, $pageSize: Int, $order: pageOrder, $description: String, $createdAt: [String]){
9 | leaveMsgs(page: $page, pageSize: $pageSize, order: $order, description: $description, createdAt: $createdAt){
10 | list{id,description,ip,createdAt}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export const useList = () => {
16 | const [form] = Form.useForm()
17 |
18 | const [loading, setLoading] = useState(false)
19 | const [pageData, setPageData] = useState(data2AntPageData())
20 |
21 | const onQuery = (page = 1, pageSize = 10, order = {createdAt: 'DESC'}) => {
22 | const vals = form.getFieldsValue()
23 | setLoading(true)
24 | useGraphQL<{leaveMsgs: LeaveMsg}, true>(
25 | query,
26 | {
27 | page,
28 | pageSize,
29 | order,
30 | ...vals
31 | }
32 | ).then(res => {
33 | setPageData(data2AntPageData(res.leaveMsgs))
34 | }).finally(() => setLoading(false))
35 | }
36 |
37 | useEffect(() => {
38 | onQuery()
39 | }, [])
40 |
41 | return {
42 | form,
43 | loading,
44 | pageData,
45 | onQuery,
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/pages/comment/useList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { Comment } from '@models/comment'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query commentPages($page: Int, $pageSize: Int, $order: pageOrder, $description: String, $createdAt: [String]){
9 | comments(page: $page, pageSize: $pageSize, order: $order, description: $description, createdAt: $createdAt){
10 | list{id,description,articleId,ip,createdAt,updatedAt}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export const useList = () => {
16 | const [form] = Form.useForm()
17 |
18 | const [loading, setLoading] = useState(false)
19 | const [pageData, setPageData] = useState(data2AntPageData())
20 |
21 | const onQuery = (page = 1, pageSize = 10, order = {createdAt: 'DESC'}) => {
22 | const vals = form.getFieldsValue()
23 | setLoading(true)
24 | useGraphQL<{comments: Comment}, true>(
25 | query,
26 | {
27 | page,
28 | pageSize,
29 | order,
30 | ...vals
31 | }
32 | ).then(res => {
33 | setPageData(data2AntPageData(res.comments))
34 | }).finally(() => setLoading(false))
35 | }
36 |
37 | useEffect(() => {
38 | onQuery()
39 | }, [])
40 |
41 | return {
42 | form,
43 | loading,
44 | pageData,
45 | onQuery,
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/pages/tag/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { Form } from 'antd'
2 | import { useState, useEffect } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import { Tag } from '@models/tag'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query getTag($id: String!){
9 | tag(id: $id){
10 | id
11 | name
12 | remark
13 | createdAt
14 | updatedAt
15 | }
16 | }`
17 |
18 | const mutation = `
19 | mutation updateTag($id: String, $name: String!, $remark: String){
20 | editTag(id: $id, name: $name, remark: $remark){
21 | id
22 | }
23 | }`
24 |
25 |
26 | export const useDetail = () => {
27 | const {id} = useParams<{id: string}>()
28 |
29 | const [form] = Form.useForm()
30 |
31 | const [loading, setLoading] = useState(false)
32 |
33 |
34 | const isEdit = !!Form.useWatch('id', form)
35 |
36 | const onSave = (vals: Tag) => {
37 | setLoading(true)
38 | useGraphQL<{id: string}>(
39 | mutation,
40 | vals
41 | ).finally(() => setLoading(false))
42 | }
43 |
44 | const onQuery = () => {
45 | setLoading(true)
46 | useGraphQL<{tag: Tag}>(
47 | query,
48 | { id }
49 | ).then(res => form.setFieldsValue(res.tag))
50 | .finally(() => setLoading(false))
51 | }
52 |
53 | useEffect(() => {
54 | if(id) {
55 | onQuery()
56 | }
57 | }, [id])
58 |
59 | return {
60 | form,
61 | loading,
62 | isEdit,
63 | onQuery,
64 | onSave,
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/pages/demos/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, IAction } from '../../plugins/redux'
2 |
3 | const initCountState = {
4 | count: 0
5 | }
6 |
7 | const initInfoState = {
8 | name: '这里是Redux',
9 | desc: '我们可以学习到redux的原理...'
10 | }
11 |
12 | const ADD = 'ADD', DECREASE = 'DECREASE'
13 |
14 | export const countActions = {
15 |
16 | [ADD]: {
17 | type: ADD
18 | },
19 |
20 | [DECREASE]: {
21 | type: DECREASE
22 | }
23 | }
24 |
25 | // reducer
26 | const countReducer = (state = initCountState, action: IAction) => {
27 | switch (action.type) {
28 | case ADD :
29 | state.count++
30 | console.log('add', state)
31 | return state
32 | case DECREASE :
33 | state.count--
34 | console.log('dec', state)
35 | return state
36 | default :
37 | return state
38 | }
39 | }
40 |
41 | // reducer
42 | const infoReducer = (state = initInfoState, action: IAction) => {
43 | let random = + Math.random()
44 | switch (action.type) {
45 | case 'SET_NAME' :
46 | state.name = '这里是Redux' + random
47 | return state
48 | case 'SET_DESC' :
49 | state.desc = '我们可以学习到redux的原理...' + random
50 | return state
51 | default :
52 | return state
53 | }
54 | }
55 |
56 |
57 | // 合并reducer
58 | const rootReducer = combineReducers({
59 | counter: countReducer,
60 | info: infoReducer
61 | })
62 |
63 | // store
64 | const store = createStore(rootReducer)
65 |
66 | export {
67 | store
68 | }
--------------------------------------------------------------------------------
/src/pages/user/useList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { User } from '@models/user'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query userPages($page: Int, $pageSize: Int, $order: pageOrder, $nickName: String, $username: String, $userType: Int, $createdAt: [String]){
9 | users(page: $page, pageSize: $pageSize, order: $order, nickName: $nickName, username: $username, userType: $userType, createdAt: $createdAt){
10 | list{id,nickName,username,userType,remark,sex,createdAt,updatedAt}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export const useList = () => {
16 | const [form] = Form.useForm()
17 |
18 | const [loading, setLoading] = useState(false)
19 | const [pageData, setPageData] = useState(data2AntPageData())
20 |
21 | const onQuery = (page = 1, pageSize = 10, order: AnyObject = {createdAt: 'DESC'}) => {
22 | const vals = form.getFieldsValue()
23 | setLoading(true)
24 | useGraphQL<{users: User}, true>(
25 | query,
26 | {
27 | page,
28 | pageSize,
29 | order,
30 | ...vals
31 | }
32 | ).then(res => {
33 | setPageData(data2AntPageData(res.users))
34 | }).finally(() => setLoading(false))
35 | }
36 |
37 | useEffect(() => {
38 | onQuery()
39 | }, [])
40 |
41 | return {
42 | form,
43 | loading,
44 | pageData,
45 | onQuery,
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/pages/lottery/trend/useTrend.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { Ball } from '@models/ball'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query ballPages($page: Int, $pageSize: Int, $order: pageOrder, $issue: String, $drawDate: [String]){
9 | balls(page: $page, pageSize: $pageSize, order: $order, issue: $issue, drawDate: $drawDate){
10 | list{id,issue,red1,red2,red3,red4,red5,red6,reds,blue,drawDate}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export enum LatestRange {
16 | Top40 = 40,
17 | Top60 = 60,
18 | Top100 = 100,
19 | }
20 |
21 | export const useList = () => {
22 | const [form] = Form.useForm()
23 |
24 | const [loading, setLoading] = useState(false)
25 | const [pageData, setPageData] = useState(data2AntPageData())
26 |
27 | const onQuery = (page = 1, pageSize = 40, order = {createdAt: 'DESC'}) => {
28 | const vals = form.getFieldsValue()
29 | setLoading(true)
30 | useGraphQL<{balls: Ball}, true>(
31 | query,
32 | {
33 | ...vals,
34 | page,
35 | pageSize,
36 | order,
37 | }
38 | ).then(res => {
39 | setPageData(data2AntPageData(res.balls))
40 | }).finally(() => setLoading(false))
41 | }
42 |
43 | useEffect(() => {
44 | onQuery()
45 | }, [])
46 |
47 | return {
48 | form,
49 | loading,
50 | pageData,
51 | onQuery,
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/pages/lottery/useBallTrend.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { Ball } from '@models/ball'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query ballPages($page: Int, $pageSize: Int, $order: pageOrder, $issue: String, $drawDate: [String]){
9 | balls(page: $page, pageSize: $pageSize, order: $order, issue: $issue, drawDate: $drawDate){
10 | list{id,issue,red1,red2,red3,red4,red5,red6,reds,blue,drawDate}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | export enum LatestRange {
16 | Top40 = 40,
17 | Top60 = 60,
18 | Top100 = 100,
19 | }
20 |
21 | export const useList = () => {
22 | const [form] = Form.useForm()
23 |
24 | const [loading, setLoading] = useState(false)
25 | const [pageData, setPageData] = useState(data2AntPageData())
26 |
27 | const onQuery = (page = 1, pageSize = 40, order = {createdAt: 'DESC'}) => {
28 | const vals = form.getFieldsValue()
29 | setLoading(true)
30 | useGraphQL<{balls: Ball}, true>(
31 | query,
32 | {
33 | ...vals,
34 | page,
35 | pageSize,
36 | order,
37 | }
38 | ).then(res => {
39 | setPageData(data2AntPageData(res.balls))
40 | }).finally(() => setLoading(false))
41 | }
42 |
43 | useEffect(() => {
44 | onQuery()
45 | }, [])
46 |
47 | return {
48 | form,
49 | loading,
50 | pageData,
51 | onQuery,
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/types/geolog.ts:
--------------------------------------------------------------------------------
1 | export interface SystemLog {
2 |
3 | id: string
4 |
5 | request_ip: string
6 |
7 | request_url: string
8 |
9 | request_method: string
10 |
11 | request_params: string
12 |
13 | request_client: string
14 |
15 | client_type: string
16 |
17 | client_version: string
18 |
19 | host: string
20 |
21 | hostname: string
22 |
23 | origin: string
24 |
25 | path: string
26 |
27 | request_header: string
28 |
29 | protocol: string
30 |
31 | status: number
32 |
33 | time: number
34 |
35 | msg: string
36 |
37 | source: string
38 |
39 | continent_code: string
40 |
41 | continent_name_en: string
42 |
43 | continent_name_zh: string
44 |
45 | continent_geoname_id: number
46 |
47 | country_iso_code: string
48 |
49 | country_geoname_id: number
50 |
51 | country_name_en: string
52 |
53 | country_name_zh: string
54 |
55 | subdivisions_iso_code: string
56 |
57 | subdivisions_geoname_id: number
58 |
59 | subdivisions_name_en: string
60 |
61 | subdivisions_name_zh: string
62 |
63 | city_geoname_id: number
64 |
65 | city_name_en: string
66 |
67 | city_name_zh: string
68 |
69 | accuracy_radius: number
70 |
71 | latitude: string
72 |
73 | longitude: string
74 |
75 | metro_code: number
76 |
77 | time_zone: string
78 |
79 | created_by: string
80 |
81 | created_at: number
82 |
83 | updated_by: string
84 |
85 | updated_at: number
86 |
87 | deleted_by: string
88 |
89 | deleted_at: number
90 |
91 | version: number
92 |
93 | }
--------------------------------------------------------------------------------
/src/pages/lottery/chart/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react'
2 | import { Row, Col, Form, Button, Spin } from 'antd'
3 | import * as Echart from 'echarts/core'
4 | import { LegendComponent, TitleComponent, TooltipComponent, GridComponent } from 'echarts/components'
5 | import { BarChart, LineChart } from 'echarts/charts'
6 | import { CanvasRenderer } from 'echarts/renderers'
7 | import { useChart } from './useChart'
8 | // // 引入柱状图
9 | // require('echarts/lib/chart/bar')
10 | // require('echarts/lib/chart/line')
11 | // // 引入提示框和标题组件
12 | // require('echarts/lib/component/tooltip')
13 | // require('echarts/lib/component/title')
14 | // require('echarts/lib/component/legend')
15 |
16 | Echart.use([
17 | BarChart,
18 | LineChart,
19 | LegendComponent,
20 | TitleComponent,
21 | TooltipComponent,
22 | GridComponent,
23 | CanvasRenderer
24 | ])
25 | const BallChart: React.FC = () => {
26 |
27 | const { loading, redRef, blueRef, chartRef } = useChart()
28 |
29 | return
30 |
31 |
红蓝球历史出现次数
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
红球历史出现分布图
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | }
49 |
50 | export default BallChart
51 |
--------------------------------------------------------------------------------
/src/pages/articleType/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { Form } from 'antd'
2 | import { useState, useEffect } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import { ArticleType } from '@models/articleType'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query getArticleType($id: String!){
9 | articleType(id: $id){
10 | id
11 | name
12 | remark
13 | createdAt
14 | updatedAt
15 | }
16 | }`
17 |
18 | // 这里放开了id的限制,update和insert共用一个mutation,后面再改。
19 | const mutation = `
20 | mutation updateArticleType($id: String, $name: String!, $remark: String){
21 | editArticleType(id: $id, name: $name, remark: $remark){
22 | id
23 | }
24 | }`
25 |
26 |
27 | export const useArticleTypeDetail = () => {
28 | const {id} = useParams<{id: string}>()
29 |
30 | const [form] = Form.useForm()
31 |
32 | const [loading, setLoading] = useState(false)
33 |
34 | const isEdit = !!Form.useWatch('id', form)
35 |
36 | const onSave = (vals: ArticleType) => {
37 | setLoading(true)
38 | useGraphQL<{id: string}>(
39 | mutation,
40 | {
41 | ...vals
42 | }
43 | ).finally(() => setLoading(false))
44 | }
45 |
46 | const onQuery = () => {
47 | setLoading(true)
48 | useGraphQL<{articleType: ArticleType}>(
49 | query,
50 | {
51 | id,
52 | }
53 | ).then(res => form.setFieldsValue(res.articleType))
54 | .finally(() => setLoading(false))
55 | }
56 |
57 | useEffect(() => {
58 | if(id) {
59 | onQuery()
60 | }
61 | }, [id])
62 |
63 | return {
64 | form,
65 | loading,
66 | isEdit,
67 | onQuery,
68 | onSave,
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/tag/tagEdit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('tagEditStore')
8 | @observer
9 | export default class TagEdit extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | componentDidMount () {
16 | const {id}: any = this.props.match.params
17 | this.props.tagEditStore.getDetail(id)
18 | }
19 |
20 | render () {
21 | const { mainData, inputChange, update } = this.props.tagEditStore
22 |
23 | return
24 |
45 |
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/src/types/global.ts:
--------------------------------------------------------------------------------
1 | import { isLogged, object2Options, storage } from '@/utils/tools'
2 | import { User } from '@models/user'
3 | import {
4 | PREFERS_COLOR_SCHEME_DARK,
5 | PRIMARY_COLOR,
6 | COLOR_PRIMARY_KEY,
7 | THEME_MODE,
8 | SYS_LANG,
9 | } from '@/constants'
10 |
11 | export enum LangI18n {
12 | '简体中文' = 'zh_CN',
13 | 'English' = 'en_US',
14 | '繁体中文' = 'zh_TW',
15 | }
16 |
17 | export enum Theme {
18 | Light = 'light',
19 | Dark = 'dark',
20 | }
21 |
22 | export const themeOpts = object2Options(Theme)
23 |
24 | export const getSystemTheme = () => {
25 | const darkMode = window.matchMedia(PREFERS_COLOR_SCHEME_DARK).matches
26 | return darkMode ? Theme.Dark : Theme.Light
27 | }
28 |
29 | const getCurrentTheme = () => {
30 | const theme = storage.get(THEME_MODE) as Theme
31 | if([Theme.Dark, Theme.Light].includes(theme)) {
32 | return theme
33 | }
34 | // if user not set theme, use system theme
35 | return getSystemTheme()
36 | }
37 |
38 | const getCurrentColor = () => {
39 | return storage.get(COLOR_PRIMARY_KEY) || PRIMARY_COLOR
40 | }
41 |
42 |
43 | const getSystemLang = () => {
44 | const language = window.navigator.language
45 | return /^en/g.test(language) ? LangI18n.English : LangI18n.简体中文
46 | }
47 |
48 | export const getCurrentLang = () => {
49 | const lang = storage.get(SYS_LANG) as LangI18n
50 | if (Object.keys(LangI18n).includes(lang)) {
51 | return lang
52 | }
53 | // if user not set language, use system language
54 | return getSystemLang()
55 | }
56 |
57 | export class Store {
58 | theme = getCurrentTheme()
59 | colorPrimary = getCurrentColor()
60 | lang = getCurrentLang()
61 | authorized = isLogged()
62 | loading = false
63 | userInfo = new User()
64 | }
--------------------------------------------------------------------------------
/src/pages/tag/tagCreate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('tagCreateStore')
8 | @observer
9 | export default class TagCreate extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | goDetail = (id: string) => {
16 | this.props.history.push(`/blog/tag/${id}`)
17 | }
18 |
19 | componentDidMount () {
20 | //
21 | }
22 |
23 | render () {
24 | const { mainData, inputChange, save } = this.props.tagCreateStore
25 |
26 | return
27 |
48 |
49 |
50 | }
51 | }
--------------------------------------------------------------------------------
/src/pages/lottery/useBallList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { Ball } from '@models/ball'
4 | import { data2AntPageData } from '@/utils/tools'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query ballPages($page: Int, $pageSize: Int, $order: pageOrder, $issue: String, $drawDate: [String]){
9 | balls(page: $page, pageSize: $pageSize, order: $order, issue: $issue, drawDate: $drawDate){
10 | list{id,issue,red1,red2,red3,red4,red5,red6,blue,pool,prizeOne,prizeOneNum,prizeTwo,prizeTwoNum,bettingNum,drawDate,createdAt}
11 | meta{current,total,pageSize}
12 | }
13 | }`
14 |
15 | const mutation = `
16 | mutation delete($id: String!){
17 | delBall(id: $id)
18 | }`
19 |
20 | export const useList = () => {
21 | const [form] = Form.useForm()
22 |
23 | const [loading, setLoading] = useState(false)
24 | const [pageData, setPageData] = useState(data2AntPageData())
25 |
26 | const onQuery = (page = 1, pageSize = 20, order = {createdAt: 'DESC'}) => {
27 | const vals = form.getFieldsValue()
28 | setLoading(true)
29 | useGraphQL<{balls: Ball}, true>(
30 | query,
31 | {
32 | page,
33 | pageSize,
34 | order,
35 | ...vals
36 | }
37 | ).then(res => {
38 | setPageData(data2AntPageData(res.balls))
39 | }).finally(() => setLoading(false))
40 | }
41 |
42 | const onDelete = (id: string) => {
43 | return useGraphQL(
44 | mutation,
45 | { id }
46 | ).then(() => {
47 | onQuery()
48 | })
49 | }
50 |
51 | useEffect(() => {
52 | onQuery()
53 | }, [])
54 |
55 | return {
56 | form,
57 | loading,
58 | pageData,
59 | onQuery,
60 | onDelete,
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/pages/articleType/articleTypeEdit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('articleTypeEditStore')
8 | @observer
9 | export default class ArticleTypeCreate extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | componentDidMount () {
16 | const {id}: any = this.props.match.params
17 | this.props.articleTypeEditStore.getDetail(id)
18 | }
19 |
20 | render () {
21 | const { mainData, inputChange, update } = this.props.articleTypeEditStore
22 |
23 | return
24 |
45 |
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/src/pages/user/detail/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { Form } from 'antd'
2 | import { useState, useEffect } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import { User } from '@models/user'
5 | import { useGraphQL } from '@/services/http'
6 |
7 | const query = `
8 | query getUser($id: String!){
9 | user(id: $id){
10 | id
11 | username
12 | nickName
13 | userType
14 | sex
15 | createdAt
16 | updatedAt
17 | remark
18 | }
19 | }`
20 |
21 | // 这里放开了id的限制,update和insert共用一个mutation,后面再改。
22 | const mutation = `
23 | mutation saveUser($id: String, $username: String, $nickName: String!, $password: String, $userType: Int!, $sex: Int!, $remark: String){
24 | editUser(id: $id, username: $username, nickName: $nickName, password: $password, userType: $userType, sex: $sex, remark: $remark){id}
25 | }`
26 |
27 |
28 | export const useUserDetail = () => {
29 | const {id} = useParams<{id: string}>()
30 |
31 | const [form] = Form.useForm()
32 |
33 | const [loading, setLoading] = useState(false)
34 |
35 | const isEdit = !!Form.useWatch('id', form)
36 |
37 | const onSave = (vals: User) => {
38 | setLoading(true)
39 | return useGraphQL<{id: string}>(
40 | mutation,
41 | {
42 | ...vals
43 | }
44 | ).finally(() => setLoading(false))
45 | }
46 |
47 | const onQuery = () => {
48 | setLoading(true)
49 | useGraphQL<{user: User}>(
50 | query,
51 | {
52 | id,
53 | }
54 | ).then(res => form.setFieldsValue(res.user))
55 | .finally(() => setLoading(false))
56 | }
57 |
58 | useEffect(() => {
59 | if(id) {
60 | onQuery()
61 | }
62 | }, [id])
63 |
64 | return {
65 | form,
66 | loading,
67 | isEdit,
68 | onQuery,
69 | onSave,
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/pages/articleType/articleTypeCreate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('articleTypeCreateStore')
8 | @observer
9 | export default class ArticleTypeCreate extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | goDetail = (id: string) => {
16 | this.props.history.push(`/blog/type/${id}`)
17 | }
18 |
19 | componentDidMount () {
20 | //
21 | }
22 |
23 | render () {
24 | const { mainData, inputChange, save } = this.props.articleTypeCreateStore
25 |
26 | return
27 |
48 |
49 |
50 | }
51 | }
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { ConfigProvider, theme as antdTheme, App, ThemeConfig } from 'antd'
3 | import zh_CN from 'antd/lib/locale/zh_CN'
4 | import en_US from 'antd/lib/locale/en_US'
5 | import zh_TW from 'antd/lib/locale/zh_TW'
6 | import { useAppStore } from '@/stores'
7 | import { Routes } from './pageRoutes'
8 | import { LangI18n, Theme } from '@/types/global'
9 | import { PREFERS_COLOR_SCHEME_DARK } from '@/constants'
10 | import { useTheme } from '@/stores/hooks'
11 | import { useThemeConfig } from '@/utils/themeConfig'
12 |
13 | const AntdLocale: Record = {
14 | zh_CN,
15 | en_US,
16 | zh_TW,
17 | }
18 |
19 | export const Navigation: React.FC = () => {
20 | const [{ lang, theme, colorPrimary }, dispatch] = useAppStore()
21 | const themeConfig = useThemeConfig()
22 |
23 | useEffect(() => {
24 | const matchMedia = window.matchMedia(PREFERS_COLOR_SCHEME_DARK)
25 | // document.body.setAttribute('theme', theme)
26 |
27 | const onChange = ({ matches }: MediaQueryListEvent) => {
28 | const changedTheme = matches ? Theme.Dark : Theme.Light
29 | useTheme(changedTheme, dispatch)
30 | }
31 | matchMedia.addEventListener('change', onChange)
32 | return () => {
33 | matchMedia.removeEventListener('change', onChange)
34 | }
35 | }, [])
36 |
37 | return (
38 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/plugins/redux/createStore.ts:
--------------------------------------------------------------------------------
1 | import { IAction, ICreateStore } from './model'
2 |
3 | // 创建store
4 | export const createStore = (reducer: Function, initState?: any, enhancer?: Function): ICreateStore => {
5 | let state: any = initState, listeners: Function[] = [], isDispatch = false
6 | if (typeof initState === 'function') {
7 | enhancer = initState
8 | initState = undefined
9 | }
10 |
11 | if (enhancer) {
12 | const newCreateStore = enhancer(createStore)
13 | return newCreateStore(reducer, initState)
14 | } else {
15 | // 获取状态
16 | const getState = () => state
17 |
18 | // 订阅
19 | const subscribe = (listener: Function) => {
20 | listeners.push(listener)
21 | const unsubscribe = () => {
22 | listeners = listeners.filter(ln => listener !== ln) // 保留之前订阅的
23 | }
24 | return unsubscribe
25 | }
26 |
27 | // 更新状态
28 | const dispatch = (action: IAction) => {
29 | if (isDispatch) {
30 | throw new Error('dispatching...')
31 | }
32 | try {
33 | isDispatch = true
34 | console.log('触发方法:', action, '更新之前state:', JSON.stringify(state))
35 | state = reducer(state, action)
36 | } finally {
37 | isDispatch = false
38 | }
39 | listeners.forEach(ln => ln())
40 | console.log('更新state之后:', JSON.stringify(state))
41 | }
42 |
43 | // 更新reducer
44 | const replaceReducer = (nextReducer: Function) => {
45 | reducer = nextReducer
46 | dispatch({
47 | type: `@@REDUX/__REPLACE__${Math.random()}`
48 | })
49 | }
50 |
51 | // 用来获取初始值
52 | dispatch({
53 | type: `@@REDUX/__INIT__${Math.random()}`
54 | })
55 |
56 | return {
57 | getState,
58 | dispatch,
59 | subscribe,
60 | replaceReducer
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/pages/tag/detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import {
4 | Row, Col, Form, Input, Button,
5 | Spin,
6 | FormItemProps,
7 | Space
8 | } from 'antd'
9 | import { useDetail } from './useDetail'
10 |
11 | const formLayout: FormItemProps = {
12 | labelCol: {
13 | span: 4,
14 | },
15 | wrapperCol: {
16 | span: 16
17 | }
18 | }
19 |
20 | const TagDetailPage: React.FC = () => {
21 |
22 | const {
23 | form,
24 | isEdit,
25 | loading,
26 | onSave,
27 | } = useDetail()
28 |
29 | const navigate = useNavigate()
30 |
31 | return
32 | 文章标签详情
33 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {
52 | isEdit && <>
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | >
64 | }
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | }
75 |
76 | export default TagDetailPage
--------------------------------------------------------------------------------
/src/pages/article/useList.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react'
2 | import { Form } from 'antd'
3 | import { ArticleType } from '@models/articleType'
4 | import { Tag } from '@models/tag'
5 | import { Article, ArticlesData } from '@models/article'
6 | import { data2AntPageData } from '@/utils/tools'
7 | import { useGraphQL } from '@/services/http'
8 |
9 | const query = `
10 | query articlePages($page: Int, $pageSize: Int, $order: pageOrder, $title: String, $abstract: String, $tag: String, $typeId: String, $createdAt: [String]){
11 | articles(page: $page, pageSize: $pageSize, order: $order, title: $title, abstract: $abstract, tag: $tag, typeId: $typeId, createdAt: $createdAt){
12 | list{id,title,abstract,createdAt,typeId,tag,createdBy}
13 | meta{current,total,pageSize}
14 | }
15 | articleTypes(page: 1, pageSize: 99){
16 | list{id,name}
17 | }
18 | tags(page: 1, pageSize: 99){
19 | list{id,name}
20 | }
21 | }`
22 |
23 | export const useList = () => {
24 | const [form] = Form.useForm()
25 |
26 | const [loading, setLoading] = useState(false)
27 | const [pageData, setPageData] = useState(data2AntPageData())
28 |
29 | const optsRef = useRef<{
30 | types: ArticleType[]
31 | tags: Tag[]
32 | }>({
33 | types: [],
34 | tags: [],
35 | })
36 |
37 | const onQuery = (page = 1, pageSize = 10, order = {createdAt: 'DESC'}) => {
38 | const vals = form.getFieldsValue()
39 | setLoading(true)
40 | useGraphQL(
41 | query,
42 | {
43 | page,
44 | pageSize,
45 | order,
46 | ...vals
47 | }
48 | ).then(res => {
49 | setPageData(data2AntPageData(res.articles))
50 | // 减少渲染,谨慎使用
51 | optsRef.current = {
52 | types: res.articleTypes.list,
53 | tags: res.tags.list,
54 | }
55 | }).finally(() => setLoading(false))
56 | }
57 |
58 | useEffect(() => {
59 | onQuery()
60 | }, [])
61 |
62 | return {
63 | form,
64 | optsRef,
65 | loading,
66 | pageData,
67 | onQuery,
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/stocks/detail.tsx:
--------------------------------------------------------------------------------
1 | import { dateFormat } from '@utils/tools'
2 | import { Spin, Card, Col, Form, Row, Button, Space } from 'antd'
3 | import React, { useState, useEffect } from 'react'
4 | import { getStockDetail } from '@/services/stock'
5 | import { Stock, Board, Market } from '@/types/stock'
6 | import { useNavigate, useParams } from 'react-router-dom'
7 |
8 | const StockDetailPage: React.FC> = (props) => {
9 | const navigate = useNavigate()
10 | const { id } = useParams();
11 |
12 | const [data, setData] = useState(new Stock)
13 | const [loading, setLoading] = useState(false)
14 |
15 | const onSync = () => {
16 | //
17 | }
18 | const onSyncAll = () => {
19 | //
20 | }
21 |
22 | useEffect(() => {
23 | if (id) {
24 | setLoading(true)
25 | getStockDetail(+id)
26 | .then(setData)
27 | .finally(() => setLoading(false))
28 | }
29 | }, [id])
30 |
31 | console.log(data)
32 |
33 | return (
34 |
35 | navigate(-1)}>Back}>
36 |
37 |
38 | {data.name}
39 |
40 |
41 | {Market[data.market]}
42 |
43 |
44 | {Board[data.block]}
45 |
46 |
47 | {dateFormat(data.lastestTradeAt)}
48 |
49 |
50 |
51 |
54 |
57 |
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
65 | export default StockDetailPage
--------------------------------------------------------------------------------
/src/pages/articleType/detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import {
4 | Row, Col, Form, Input, Button,
5 | Spin,
6 | FormItemProps,
7 | Space
8 | } from 'antd'
9 | import { useArticleTypeDetail } from './useDetail'
10 |
11 | const formLayout: FormItemProps = {
12 | labelCol: {
13 | span: 4,
14 | },
15 | wrapperCol: {
16 | span: 16
17 | }
18 | }
19 |
20 | const ArticleTypeDetailPage: React.FC = () => {
21 |
22 | const {
23 | form,
24 | isEdit,
25 | loading,
26 | onSave,
27 | } = useArticleTypeDetail()
28 |
29 | const navigate = useNavigate()
30 |
31 | return
32 | 文章类型详情
33 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {
52 | isEdit && <>
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | >
64 | }
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | }
75 |
76 | export default ArticleTypeDetailPage
--------------------------------------------------------------------------------
/src/pages/leaveMsg/leaveMsgEdit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('leaveMsgEditStore')
8 | @observer
9 | export default class LeaveMsgEdit extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | componentDidMount () {
16 | const {id}: any = this.props.match.params
17 | this.props.leaveMsgEditStore.getDetail(id)
18 | }
19 |
20 | render () {
21 | const { mainData } = this.props.leaveMsgEditStore
22 |
23 | return
24 |
54 |
55 |
56 | }
57 | }
--------------------------------------------------------------------------------
/src/pages/comment/detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import { Row, Col, Form, Input, Button, FormItemProps } from 'antd'
4 | import { useDetail } from './useDetail'
5 |
6 | const formLayout: FormItemProps = {
7 | labelCol: {
8 | span: 4,
9 | },
10 | wrapperCol: {
11 | span: 16
12 | }
13 | }
14 |
15 | export default function CommentDetail() {
16 |
17 | const navigate = useNavigate()
18 |
19 | const {
20 | form,
21 | isEdit,
22 | loading,
23 | onSave,
24 | } = useDetail()
25 |
26 | return
27 |
72 |
73 | }
--------------------------------------------------------------------------------
/src/pages/leaveMsg/detail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import { Row, Col, Form, Input, Button, FormItemProps } from 'antd'
4 | import { useDetail } from './useDetail'
5 |
6 | const formLayout: FormItemProps = {
7 | labelCol: {
8 | span: 4,
9 | },
10 | wrapperCol: {
11 | span: 16
12 | }
13 | }
14 |
15 | export default function CommentDetail() {
16 |
17 | const navigate = useNavigate()
18 |
19 | const {
20 | form,
21 | isEdit,
22 | loading,
23 | onSave,
24 | } = useDetail()
25 |
26 | return
27 |
72 |
73 | }
--------------------------------------------------------------------------------
/src/components/progress/index.tsx:
--------------------------------------------------------------------------------
1 | import { useAppStore } from '@/stores'
2 | import React, { useEffect, useLayoutEffect, useRef } from 'react'
3 | import { createPortal } from 'react-dom'
4 | import styles from './style.module.scss'
5 |
6 | interface ProgressProps {
7 | size?: 'small' | 'default' | 'large'
8 | }
9 |
10 | export const ProgressComponent: React.FC> = ({
11 | size
12 | }) => {
13 |
14 | const [{
15 | colorPrimary,
16 | }] = useAppStore()
17 |
18 | const el = useRef(null)
19 | const sizes: Record, number> = {
20 | small: 2,
21 | default: 3,
22 | large: 4,
23 | }
24 |
25 | useLayoutEffect(() => {
26 | const TOTAL = 100
27 | let progress = 0, timer = 0
28 | let total = TOTAL // percent 100%
29 | const div = el.current!
30 |
31 | function animate(time: number) {
32 | const used = total * 0.1
33 | total -= used
34 | progress += used
35 |
36 | // update progress
37 | if (progress < TOTAL * 0.9999) {
38 | div.style.width = `${progress}%`
39 | timer = requestAnimationFrame(animate)
40 | } else {
41 | div.style.width = '100%'
42 | cancelAnimationFrame(timer)
43 | }
44 | }
45 |
46 | timer = requestAnimationFrame(animate)
47 | return () => {
48 | div.style.width = '100%'
49 | cancelAnimationFrame(timer)
50 | };
51 | }, []);
52 |
53 | return
62 | }
63 |
64 | export const Progress: React.FC = ({
65 | size = 'default'
66 | }) => {
67 |
68 | const node = useRef(document.createElement('div'))
69 |
70 | useLayoutEffect(() => {
71 | const el = node.current
72 | document.body.append(el)
73 | return () => {
74 | document.body.removeChild(el)
75 | }
76 | }, [])
77 |
78 |
79 |
80 | return createPortal(
81 | ,
82 | node.current
83 | )
84 | }
--------------------------------------------------------------------------------
/src/pages/login/wave.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { Canvas, useFrame } from '@react-three/fiber'
3 | import * as THREE from 'three'
4 | import { useAppStore } from '@/stores'
5 |
6 | const Wave = ({ color }: { color: THREE.Color }) => {
7 | const meshRef = useRef(null)
8 | const geometryRef = useRef(null)
9 |
10 | // Wave parameters
11 | const waveParams = {
12 | amplitude: 2,
13 | wavelength: 10,
14 | frequency: 0.2,
15 | }
16 |
17 | useFrame(({ clock }) => {
18 | const time = clock.getElapsedTime()
19 | const geometry = geometryRef.current!
20 | const positionAttribute = geometry.attributes.position
21 |
22 | for (let i = 0; i < positionAttribute.count; i++) {
23 | const x = positionAttribute.getX(i)
24 | const y = positionAttribute.getY(i)
25 | const z =
26 | Math.sin(x * waveParams.frequency + time) * waveParams.amplitude +
27 | Math.cos(y * waveParams.frequency + time) * waveParams.amplitude
28 | positionAttribute.setZ(i, z)
29 | }
30 |
31 | positionAttribute.needsUpdate = true // Notify Three.js that the vertices have changed
32 | })
33 |
34 | return (
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | export const WaveComponent: React.FC = () => {
43 | const [{ colorPrimary }] = useAppStore()
44 |
45 | const color = new THREE.Color(colorPrimary)
46 | const rgb = [color.r, color.g, color.b].map((i) => Math.round(i * 255))
47 |
48 | return (
49 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/src/pages/register/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Form, Input, Button, Checkbox } from 'antd'
3 | import { UserOutlined, LockOutlined } from '@ant-design/icons'
4 | import { onCreate } from '@/services/account'
5 | import { LoginForm, RegisterForm } from '@/types/account'
6 | import { storage } from "@utils/tools"
7 | import { JWT_TOKEN, REDIRECT_URL } from "@constants/index"
8 |
9 |
10 | const RegisterPage: React.FC = () => {
11 |
12 | const [form] = Form.useForm()
13 |
14 | const [loading, setLoading] = useState(false)
15 |
16 | const onFinish = (values: LoginForm) => {
17 | onCreate(values).then(res => {
18 | window.location.href = '/login'
19 | })
20 | }
21 |
22 |
23 | return
34 | } placeholder="Username"/>
35 |
36 |
37 | } type="password" placeholder="Password"/>
38 |
39 |
40 | name="confirm"
41 | dependencies={['password']}
42 | rules={[
43 | {required: true},
44 | ({ getFieldValue }) => ({
45 | validator(_, value) {
46 | if (!value || getFieldValue('password') === value) {
47 | return Promise.resolve();
48 | }
49 | return Promise.reject(new Error('The new password that you entered do not match!'));
50 | },
51 | }),
52 | ]}
53 | >
54 | } type="password" placeholder="Confirm Password"/>
55 |
56 |
57 |
58 |
59 |
60 | Already have an account?
Login
61 |
62 |
63 | }
64 |
65 | export default RegisterPage
--------------------------------------------------------------------------------
/src/assets/icons/threejs.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | export const ThreejsIcon = () => {
5 | return
12 | }
--------------------------------------------------------------------------------
/src/scss/functions.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局快速简便样式
3 | * 命名使用缩写形式
4 | * [Global quick css]
5 | */
6 |
7 |
8 | /**
9 | * margin, padding
10 | */
11 | $start: 8;
12 | $end: 80;
13 | $step: 8;
14 |
15 | @while $start <= $end {
16 |
17 | $val: $start;
18 |
19 | .mgtrbl#{$val} { margin: $val + px !important; }
20 | .mg#{$val} { margin: $val + px !important; }
21 | .mgtb#{$val} { margin: $val + px 0 !important; }
22 | .mglr#{$val} { margin: 0 $val + px !important; }
23 | .mgt#{$val} { margin-top: $val + px !important; }
24 | .mgr#{$val} { margin-right: $val + px !important; }
25 | .mgb#{$val} { margin-bottom: $val + px !important; }
26 | .mgl#{$val} { margin-left: $val + px !important; }
27 |
28 | .pdtrbl#{$val} { padding: $val + px !important; }
29 | .pd#{$val} { padding: $val + px !important; }
30 | .pdtb#{$val} { padding: $val + px 0 !important; }
31 | .pdlr#{$val} { padding: 0 $val + px !important; }
32 | .pdt#{$val} { padding-top: $val + px !important; }
33 | .pdr#{$val} { padding-right: $val + px !important; }
34 | .pdb#{$val} { padding-bottom: $val + px !important; }
35 | .pdl#{$val} { padding-left: $val + px !important; }
36 |
37 |
38 | .row-col-mgb#{$val} {
39 | .ant-col {
40 | margin-bottom: $val + px;
41 | }
42 | }
43 | .row-col-mgt#{$val} {
44 | .ant-col {
45 | margin-top: $val + px;
46 | }
47 | }
48 | .row-col-mgtb#{$val} {
49 | .ant-col {
50 | margin: $val 0;
51 | }
52 | }
53 |
54 | .row-col-pdb#{$val} {
55 | .ant-col {
56 | padding-bottom: $val + px;
57 | }
58 | }
59 | .row-col-pdt#{$val} {
60 | .ant-col {
61 | padding-top: $val + px;
62 | }
63 | }
64 | .row-col-pdtb#{$val} {
65 | .ant-col {
66 | padding: $val + px 0;
67 | }
68 | }
69 |
70 | $start: $start + $step;
71 | }
72 |
73 | /**
74 | * font-size, font-weigt
75 | */
76 | $fsS: 6;
77 | $fsE: 60;
78 | $fsStep: 1;
79 |
80 | @while $fsS <= $fsE {
81 | .fs#{$fsS} {
82 | font-size: $fsS + px;
83 | }
84 | $fsS: $fsS + $fsStep;
85 | }
86 |
87 | $fwS: 100;
88 | $fwE: 900;
89 | $fwStep: 100;
90 |
91 | @while $fwS <= $fwE {
92 | .fs#{$fwS} {
93 | font-weight: $fwS;
94 | }
95 | $fwS: $fwS + $fwStep;
96 | }
97 |
--------------------------------------------------------------------------------
/src/utils/themeConfig.ts:
--------------------------------------------------------------------------------
1 | import { PRIMARY_COLOR } from '@/constants'
2 | import { useAppStore } from '@/stores'
3 | import { Theme } from '@/types/global'
4 | import { theme as antdTheme, ThemeConfig } from 'antd'
5 | import { AliasToken } from 'antd/lib/theme/interface'
6 | import { debounce, merge } from 'lodash'
7 | import { useEffect, useState } from 'react'
8 |
9 | type PartialAliasToken = Partial
10 |
11 | const breakPoint: PartialAliasToken = {
12 | screenXXL: 3024,
13 | screenXXLMin: 3024,
14 | screenXLMax: 3023,
15 | screenXL: 2560,
16 | screenXLMin: 2560,
17 | screenLGMax: 2559,
18 | screenLG: 1920,
19 | screenLGMin: 1920,
20 | screenMDMax: 1919,
21 | screenMD: 1440,
22 | screenMDMin: 1440,
23 | screenSMMax: 1439,
24 | screenSM: 992,
25 | screenSMMin: 992,
26 | screenXSMax: 991,
27 | screenXS: 768,
28 | screenXSMin: 480,
29 | }
30 |
31 | const getTokenConfig = (): ThemeConfig => {
32 | const width = document.body.clientWidth
33 | const config: ThemeConfig = {
34 | token: {
35 | fontSize: 12,
36 | controlHeight: 24,
37 | },
38 | }
39 | if (width >= breakPoint.screenXXL!) {
40 | config.token = {
41 | fontSize: 26,
42 | controlHeight: 60,
43 | }
44 | } else if (width >= breakPoint.screenXL!) {
45 | config.token = {
46 | fontSize: 22,
47 | controlHeight: 50,
48 | }
49 | } else if (width >= breakPoint.screenLG!) {
50 | config.token = {
51 | fontSize: 16,
52 | controlHeight: 40,
53 | }
54 | } else if (width >= breakPoint.screenMD!) {
55 | config.token = {
56 | fontSize: 14,
57 | controlHeight: 32,
58 | }
59 | } else if (width >= breakPoint.screenSM!) {
60 | config.token = {
61 | fontSize: 13,
62 | controlHeight: 28,
63 | }
64 | }
65 | return merge(config, breakPoint)
66 | }
67 |
68 | export const useThemeConfig = () => {
69 | const [themeConfig, setThemeConfig] = useState(getTokenConfig())
70 |
71 | useEffect(() => {
72 | const onResize = debounce(() => {
73 | setThemeConfig(getTokenConfig())
74 | }, 350)
75 | window.onresize = onResize
76 | return () => {
77 | window.onresize = null
78 | }
79 | }, [])
80 |
81 | return themeConfig
82 | }
83 |
--------------------------------------------------------------------------------
/src/pages/demos/useDemo.ts:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react'
2 | import { Form, message, UploadFile } from 'antd'
3 | import { APIFormTest, APIType, Method } from '@/types/demo'
4 |
5 | const defaultParams: AnyObject = {
6 | query: '{}',
7 | varibles: '{}',
8 | operationName: null
9 | }
10 |
11 |
12 | export const useDemoState = () => {
13 | const [form] = Form.useForm()
14 | const [fileForm] = Form.useForm<{files: UploadFile[]}>()
15 |
16 | const [response, setResponse] = useState({})
17 |
18 | // listener 'apiType'
19 | const apiType = Form.useWatch('apiType', form)
20 |
21 | useEffect(() => {
22 | console.log('watch type', apiType)
23 | if(apiType === APIType.Graphql) {
24 | form.setFieldsValue({
25 | url: apiType,
26 | params: JSON.stringify(defaultParams, null, 4)
27 | })
28 | } else {
29 | form.setFieldsValue({
30 | url: '/api',
31 | params: JSON.stringify({
32 | testField: 'this is testField value',
33 | age: 123,
34 | paid: true
35 | }, null, 4)
36 | })
37 | }
38 | }, [apiType])
39 |
40 | const onRequest = useCallback((data: APIFormTest) => {
41 | const { url, method, params } = data
42 | let jsonData = {}, error = null
43 | try {
44 | if (params) {
45 | jsonData = JSON.parse(params)
46 | }
47 | } catch (e) {
48 | error = e
49 | }
50 | if (error) {
51 | message.error('参数请使用标准json格式')
52 | return
53 | }
54 | $http({
55 | url,
56 | method,
57 | data: method === Method.GET ? null : jsonData,
58 | params: method === Method.GET ? jsonData : null
59 | }).then(res => {
60 | setResponse(res)
61 | }, e => {
62 | setResponse(e)
63 | })
64 | }, [])
65 |
66 | const upload = useCallback((file: File) => {
67 | const fd = new FormData()
68 | fd.append('file', file)
69 |
70 | $http.post('/api/upload', fd).then(res => {
71 | console.log(res)
72 | }, err => {
73 | console.log(err, 'err')
74 | })
75 | }, [])
76 |
77 |
78 | return {
79 | form,
80 | apiType,
81 | fileForm,
82 | response,
83 | onRequest,
84 | upload
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/pages/lottery/detail/useDetail.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Form } from 'antd'
3 | import { useParams, useNavigate } from 'react-router-dom'
4 | import { debounce } from '@utils/debounce'
5 | import { Ball } from '@models/ball'
6 | import { parseISO } from 'date-fns'
7 | import { useGraphQL } from '@/services/http'
8 |
9 | const mutation = `
10 | mutation saveBall($input: ballInput!){
11 | saveBall(input: $input)
12 | }`
13 |
14 | const createBall = `
15 | mutation createBall($input: ballInput!){
16 | ball(input: $input){id}
17 | }`
18 |
19 | const query = `
20 | query ball($id: String!){
21 | ball(id: $id){id,issue,reds,blue,pool,prizeOne,prizeOneNum,prizeTwo,prizeTwoNum,bettingNum,drawDate,createdAt}
22 | }`
23 |
24 | const updateBall = `
25 | mutation updateBall($input: ballInput!){
26 | updated:editBall(input: $input)
27 | }`
28 |
29 | const mapData = (data: Ball, save = false) => {
30 | if(save) {
31 | data.blue = (data.blue as number[])[0];
32 | (data as any).createdAt = undefined
33 | } else {
34 | (data as any).drawDate = parseISO(data.drawDate);
35 | (data as any).createdAt = parseISO(data.createdAt);
36 | data.blue = [data.blue as number]
37 | }
38 |
39 | return data
40 | }
41 |
42 | export const useDetail = () => {
43 | const {id} = useParams<{id: string}>()
44 | const navigate = useNavigate()
45 |
46 | const [form] = Form.useForm()
47 |
48 | const [loading, setLoading] = useState(false)
49 |
50 | const isEdit = !!Form.useWatch('id', form)
51 |
52 | const onSave = debounce((vals: Ball) => {
53 | setLoading(true)
54 | useGraphQL(
55 | mutation,
56 | { input: mapData(vals, true) }
57 | ).then(r => history.go(-1))
58 | .finally(() => setLoading(false))
59 | })
60 |
61 |
62 | const onQuery = () => {
63 | setLoading(true)
64 | useGraphQL<{ball: Ball}>(
65 | query,
66 | {
67 | id,
68 | }
69 | ).then(r => form.setFieldsValue(mapData(r.ball)))
70 | .finally(() => setLoading(false))
71 | }
72 |
73 | useEffect(() => {
74 | if(id) {
75 | onQuery()
76 | }
77 | }, [id])
78 |
79 | return {
80 | form,
81 | isEdit,
82 | loading,
83 | onQuery,
84 | onSave,
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/pages/login/login.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Form, Input, Button, Checkbox } from 'antd'
4 | import { UserOutlined, LockOutlined } from '@ant-design/icons'
5 |
6 | const FormItem = Form.Item
7 |
8 | @inject('loginStore')
9 | @observer
10 | export default class Login extends React.Component {
11 |
12 | // componentWillMount(){
13 | // console.log('componentWillMount')
14 | // }
15 |
16 | // componentWillReceiveProps(){
17 | // console.log('componentWillReceiveProps')
18 | // }
19 |
20 | // shouldComponentUpdate(){
21 | // console.log('shouldComponentUpdate')
22 | // return true
23 | // }
24 |
25 | // componentWillUpdate(){
26 | // console.log('componentWillUpdate')
27 | // }
28 |
29 | // componentDidUpdate(){
30 | // console.log('componentDidUpdate')
31 | // }
32 |
33 | componentDidMount () {
34 | console.log('componentDidMount')
35 | window.addEventListener('keydown', this.listenEnter)
36 | }
37 |
38 | // componentWillUnmount() {
39 | // console.log('componentWillUnmount')
40 | // }
41 |
42 | listenEnter = (e: KeyboardEvent) => {
43 | if (e.keyCode === 13) {
44 | this.props.loginStore.login()
45 | }
46 | }
47 |
48 | handleSubmit = (e: React.FormEvent) => {
49 | e.preventDefault()
50 | }
51 |
52 | render () {
53 | const { inputChange, login, loading, canLogin } = this.props.loginStore
54 |
55 | return
75 | }
76 | }
--------------------------------------------------------------------------------
/src/pages/comment/commentEdit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import {inject, observer} from 'mobx-react'
3 | import { Row, Col, Form, Input, Button, DatePicker, Select, Checkbox, Badge } from 'antd'
4 |
5 | const FormItem = Form.Item
6 |
7 | @inject('commentEditStore')
8 | @observer
9 | export default class CommentEdit extends React.Component {
10 |
11 | back = () => {
12 | this.props.history.go(-1)
13 | }
14 |
15 | componentDidMount () {
16 | const {id}: any = this.props.match.params
17 | this.props.commentEditStore.getDetail(id)
18 | }
19 |
20 | render () {
21 | const { mainData } = this.props.commentEditStore
22 |
23 | return
24 |
59 |
60 |
61 | }
62 | }
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface IResponseData {
4 | data: T
5 | msg: string
6 | status: number
7 | }
8 |
9 | type AnyObject = Record
10 |
11 | type GraphQLResponse = {
12 | data: {
13 | [Key in keyof T]: IsPageData extends true ? IPageData : T[Key]
14 | }
15 | }
16 |
17 | type XExtends = T & { [P in K]: V }
18 |
19 | interface IPager {
20 | count: number
21 | current: number
22 | page: number
23 | pageSize: number
24 | total: number
25 | totalPage: number
26 | showTotal: (total: number) => React.ReactNode
27 | }
28 |
29 | interface IPageData {
30 | list?: T[] // graphql 返回数据
31 | data: T[] // restful 返回数据
32 | meta: Partial
33 | msg?: string
34 | status?: number
35 | }
36 |
37 | type IPageParams = Pick
38 |
39 | interface IOption {
40 | label: L
41 | value: V
42 | }
43 |
44 |
45 | declare const styles: { [className: string]: string }
46 |
47 | declare module '*.scss' {
48 | export default styles;
49 | }
50 | declare module '*.less' {
51 | export default styles;
52 | }
53 |
54 | declare module '*.svg' {
55 |
56 | export const ReactComponent: React.FunctionComponent & { title?: string }>;
59 | const src: string
60 | export default src;
61 | }
62 |
63 |
64 | declare module '*.xml' {
65 | const content: string
66 | export default content;
67 | }
68 |
69 |
70 | declare module '*.avif' {
71 | const src: string
72 | export default src;
73 | }
74 |
75 | declare module '*.bmp' {
76 | const src: string
77 | export default src;
78 | }
79 |
80 | declare module '*.gif' {
81 | const src: string
82 | export default src;
83 | }
84 |
85 | declare module '*.jpg' {
86 | const src: string
87 | export default src;
88 | }
89 |
90 | declare module '*.jpeg' {
91 | const src: string
92 | export default src;
93 | }
94 |
95 | declare module '*.png' {
96 | const src: string
97 | export default src;
98 | }
99 |
100 | declare module '*.webp' {
101 | const src: string
102 | export default src;
103 | }
--------------------------------------------------------------------------------
/src/pages/demos/demoMobx.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { observable, action, autorun, runInAction, computed } from 'mobx'
3 | import {inject, observer} from 'mobx-react'
4 | import { Row, Col, Button, Badge, Form, Input, Select, Space } from 'antd'
5 | import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'
6 |
7 | const FormItem = Form.Item
8 | const TextArea = Input.TextArea
9 | const Option = Select.Option
10 |
11 | const styles = {
12 | block: {
13 | marginBottom: '20px',
14 | boxShadow: 'rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.12) 0px 1px 4px',
15 | padding: '10px'
16 | },
17 | flex: {
18 | display: 'flex',
19 | alignItems: 'center'
20 | },
21 | padding: {
22 | padding: '10px 0'
23 | }
24 | }
25 |
26 | @observer
27 | export default class DemoMobx extends React.Component {
28 |
29 | // state = {
30 | // count: 0,
31 | // }
32 | // add = () => {
33 | // this.setState({
34 | // count: ++this.state.count
35 | // })
36 | // }
37 | // dec = () => {
38 | // this.setState({
39 | // count: --this.state.count
40 | // })
41 | // }
42 |
43 | @observable count = 66
44 |
45 | @action add = () => { this.count++ }
46 | @action dec = () => { this.count-- }
47 |
48 | componentDidMount () {
49 | // console.log($http)
50 | // console.log(this.props)
51 | console.log(this)
52 | }
53 |
54 | render () {
55 | // const {count} = this.state
56 | const {count, add, dec} = this
57 |
58 | return
59 |
60 | mini测试
61 |
62 |
63 |
66 |
67 |
70 |
71 |
72 |
73 |
85 |
86 | }
87 | }
--------------------------------------------------------------------------------
/src/pages/dashboard/useData.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react'
2 | import { getGeoMapStats } from '@/services/geography'
3 | import { getMongoLogsStats } from '@/services/api'
4 | import { useAppStore } from '@/stores'
5 | import { setChartData, setEarthMap } from './echarts'
6 | import { setGeoOptions, setGeoScene, SetChartData } from './antv'
7 |
8 | const data2List = (data: SetChartData[1]) => {
9 | const dest = {
10 | "name_en": "Matawan",
11 | "ip": "45.77.218.105",
12 | "sub_name_en": "New Jersey",
13 | "total": "49",
14 | "latitude": "40.4169",
15 | "longitude": "-74.2579"
16 | }
17 | const list: SetChartData[0] = [];
18 |
19 | data.forEach(i => {
20 | if(i.ip !== dest.ip) {
21 | const total = +i.total || 1
22 | const visit = {
23 | from: i.name_en || 'Unknow',
24 | to: dest.name_en,
25 | value: i.total,
26 | type: 'move_out',
27 | x: i.longitude,
28 | y: i.latitude,
29 | x1: dest.longitude,
30 | y1: dest.latitude,
31 | }
32 | const arr = []
33 | for(let i = 0; i < total; i++) {
34 | arr.push(visit)
35 | }
36 | list.push(...arr)
37 | }
38 | })
39 |
40 | console.log(data.length, list.length)
41 | return list
42 | }
43 |
44 | export const useData = () => {
45 |
46 | const [{ colorPrimary }] = useAppStore()
47 | const [loading, setLoading] = useState(false)
48 |
49 | const visitRef = useRef(null)
50 | const geoRef = useRef(null)
51 | const earthRef = useRef(null)
52 | const pathRef = useRef(null)
53 | const statusRef = useRef(null)
54 | const earthBDRef = useRef(null)
55 |
56 | const dataRef = useRef([[], []])
57 |
58 | useEffect(() => {
59 | getMongoLogsStats().then(r => setChartData(r, [statusRef.current!, pathRef.current!]))
60 | setLoading(true)
61 | getGeoMapStats().then(r => {
62 | dataRef.current = [data2List(r[0]), r[1]]
63 | return setGeoScene([visitRef.current!, geoRef.current!])
64 | }).finally(() => setLoading(false))
65 | }, [])
66 |
67 | useEffect(() => {
68 | if (!loading && dataRef.current.every(i => i.length)) {
69 | setGeoOptions(dataRef.current, colorPrimary)
70 | // setEarthMap(dataRef.current[0], earthBDRef.current!)
71 | }
72 | }, [colorPrimary, loading])
73 |
74 | return {
75 | visitRef,
76 | geoRef,
77 | earthRef,
78 | pathRef,
79 | statusRef,
80 | earthBDRef,
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/scss/app.scss:
--------------------------------------------------------------------------------
1 | // @import './variables.scss';
2 | // @import './theme.scss';
3 | @use './functions.scss' as *;
4 |
5 | #app {
6 | height: 100%;
7 | }
8 |
9 | #not-found {
10 | color: #666;
11 | padding-top: 4rem;
12 | font-size: 2.5rem;
13 | text-align: center;
14 | }
15 |
16 | /* search-form */
17 | .ant-form.form {
18 | padding-bottom: 20px;
19 |
20 | .ant-picker, .ant-input-number {
21 | width: 100%;
22 | }
23 | }
24 |
25 | /* table-list */
26 | // .table-list {
27 | @for $i from 1 to 10 {
28 | .textflow-#{$i} {
29 | cursor: pointer;
30 | overflow: hidden;
31 | text-overflow: ellipsis;
32 | display: -webkit-box;
33 | -webkit-line-clamp: #{$i};
34 | -webkit-box-orient: vertical;
35 | }
36 | }
37 |
38 | .ant-table-thead > tr > th {
39 | padding: 10px;
40 | }
41 |
42 | .ant-table-tbody > tr > td {
43 | padding: 0 6px;
44 | }
45 | // }
46 |
47 |
48 | /* user-menu */
49 | .user-menu {
50 | div {
51 | padding: 4px 0;
52 | cursor: pointer;
53 | border-bottom: 1px solid #eee;
54 |
55 | &:last-child {
56 | border-bottom: none;
57 | }
58 | }
59 | }
60 |
61 | /* editor-wrap */
62 | .editor-wrap {
63 | position: relative;
64 | background-color: #fff;
65 |
66 | .editor-mask {
67 | position: absolute;
68 | top: 0;
69 | left: 0;
70 | width: 100%;
71 | height: 100%;
72 | background: rgba(245, 245, 245, 0.5);
73 | }
74 | }
75 | .ant-form-item-has-error {
76 | .ql-snow {
77 | border-color: #ff4d4f !important;
78 | }
79 | }
80 |
81 | /* trend-table-list */
82 | .trend-table-list {
83 | th,td {
84 | padding: 0 !important;
85 | font-size: 12px;
86 | }
87 | }
88 |
89 | .rules-table {
90 | width: 100%;
91 | border-top: 1px solid #ccc;
92 | border-left: 1px solid #ccc;
93 |
94 | tr:nth-child(-n+2) {
95 | background-color: #eee;
96 | }
97 |
98 | td {
99 | text-align: center;
100 | border-right: 1px solid #ccc;
101 | border-bottom: 1px solid #ccc;
102 | }
103 |
104 | .ball {
105 | width: 14px;
106 | height: 14px;
107 | border-radius: 14px;
108 | margin-right: 4px;
109 | display: inline-block;
110 | }
111 |
112 | .red {
113 | background-color: #f54646;
114 | }
115 |
116 | .blue {
117 | background-color: #39f;
118 | }
119 | }
120 |
121 | /* chart-w */
122 | .chart-w {
123 | height: 400px;
124 | border: 1px solid #eee;
125 | background-color: #fff;
126 | padding: 8px;
127 | }
128 |
--------------------------------------------------------------------------------
/src/pages/home/components/siderbar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Link, useLocation, } from 'react-router-dom'
3 | import { Layout, Menu, theme } from 'antd'
4 | import { MenuItem, LeftMenuConfig } from '@/routes/routeConf'
5 | import styles from './siderbar.module.scss'
6 |
7 | const { Sider } = Layout
8 | const { SubMenu } = Menu
9 |
10 | const DEFAULT_PATH_LEN = 2
11 |
12 |
13 | const setMenuKeys = (paths: string[], end = DEFAULT_PATH_LEN) => paths.slice(0, end)
14 |
15 | export const SiderBar: React.FC = () => {
16 | const { token: { fontSize, fontSizeLG, controlHeight, colorPrimary } } = theme.useToken()
17 | const { pathname } = useLocation()
18 | const [openKeys, setOpenKeys] = useState([])
19 | const [selectedKeys, setSelectedKeys] = useState([])
20 | const [menuList, setMenuList] = useState