浏览代码

编写对接服务

bill 3 年之前
父节点
当前提交
6cbe949a2f

+ 4 - 0
package.json

@@ -15,6 +15,7 @@
     "@types/react-dom": "^18.0.6",
     "antd": "^4.21.7",
     "axios": "^0.27.2",
+    "canvas-nest.js": "^2.0.4",
     "craco-less": "^2.0.0",
     "lodash": "^4.17.21",
     "react": "^18.2.0",
@@ -50,5 +51,8 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "http-proxy-middleware": "^2.0.6"
   }
 }

+ 0 - 9
src/api/axios.ts

@@ -1,9 +0,0 @@
-import { axiosFactory } from './setup'
-
-const { axios } = axiosFactory()
-
-export {
-  axios
-}
-
-export default axios

+ 2 - 1
src/api/index.ts

@@ -2,4 +2,5 @@
 
 export { ResCode } from 'constant'
 export * from './scene'
-export * from './axios'
+export * from './instance'
+export * from './user'

+ 32 - 0
src/api/instance.ts

@@ -0,0 +1,32 @@
+import { axiosFactory } from './setup'
+import { message } from 'antd'
+import { LOGIN } from './url'
+
+const instance = axiosFactory()
+
+export const {
+  axios,
+  addReqErrorHandler,
+  addUnsetTokenURLS,
+  addResErrorHandler,
+  setToken,
+  delToken,
+  setDefaultURI
+} = instance
+
+addReqErrorHandler(err => message.error(err.message))
+
+addResErrorHandler(
+  (response, data) => {
+    if (response.status !== 200) {
+      message.error(response.statusText)
+    } else {
+      message.error(data?.message)
+    }
+  }
+)
+
+addUnsetTokenURLS(LOGIN, '/')
+setDefaultURI('/api')
+
+export default axios

+ 3 - 3
src/api/scene.ts

@@ -1,5 +1,5 @@
-import { SceneStatus } from 'constant'
-import axios from './axios'
+import { SceneStatus, SceneType } from 'constant'
+import axios from './instance'
 
 
 export interface Scene {
@@ -12,7 +12,7 @@ export interface Scene {
 
 export type Scenes = Scene[]
 
-export const getSceneByType = async (keyword?: string) => {
+export const getSceneByType = async (type: SceneType, keyword?: string) => {
   const scenes = await axios.get<Scenes>('/scenes', { params: { keyword } })
   return scenes
 }

+ 44 - 34
src/api/setup.ts

@@ -5,7 +5,7 @@ import type { AxiosResponse, AxiosRequestConfig } from 'axios'
 
 export type ResErrorHandler = <D, T extends ResData<D>>(response: AxiosResponse<T>, data?: T) => void
 export type ReqErrorHandler = <T>(err: Error, response: AxiosRequestConfig<T>) => void
-export type ResData<T> = { code: ResCode, msg: string, data: T }
+export type ResData<T> = { code: ResCode, message: string, data: T }
 
 export const axiosFactory = () => {
   const axiosRaw = Axios.create()
@@ -24,8 +24,8 @@ export const axiosFactory = () => {
     { set: (val: AxiosConfig[K]) => void } 
       & (AxiosConfig[K] extends Array<any> 
         ? { 
-            add: (val: AxiosConfig[K]) => void,
-            del: (val?: AxiosConfig[K]) => void
+            add: (...val: AxiosConfig[K]) => void,
+            del: (...val: AxiosConfig[K]) => void
           }
         : { del: () => void })
 
@@ -38,10 +38,10 @@ export const axiosFactory = () => {
     }
     
     if (Array.isArray(axiosObj)) {
-      apis.add = (val: AxiosConfig[K]) => {
+      apis.add = (...val: any[]) => {
         axiosObj.push(...val as any)
       }
-      apis.del = (val?: AxiosConfig[K]) => {
+      apis.del = (...val: any[]) => {
         if (val) {
           axiosConfig[key] = axiosObj.filter(item => !val?.includes(item)) as any
         } else {
@@ -98,42 +98,52 @@ export const axiosFactory = () => {
   const matchURL = (urls: string[], config: AxiosRequestConfig<any>) => 
     config.url && urls.includes(config.url)
 
-  axiosRaw.interceptors.request.use(config => {
-    if (!matchURL(axiosConfig.unTokenSet, config)) {
-      if (!axiosConfig.token) {
-        if (!matchURL(axiosConfig.unReqErrorSet, config)) {
-          const error = new Error('缺少token')
-          axiosConfig.reqErrorHandler.forEach(handler => handler(error, config))
-          throw error
-        }
-      } else {
-        config.headers = {
-          ...config.headers,
-          token: axiosConfig.token
+  axiosRaw.interceptors.request.use(
+    config => {
+      if (!matchURL(axiosConfig.unTokenSet, config)) {
+        if (!axiosConfig.token) {
+          if (!matchURL(axiosConfig.unReqErrorSet, config)) {
+            const error = new Error('缺少token')
+            axiosConfig.reqErrorHandler.forEach(handler => handler(error, config))
+            throw error
+          }
+        } else {
+          config.headers = {
+            ...config.headers,
+            token: axiosConfig.token
+          }
         }
       }
+      return config
     }
-    return config
-  })
+  )
 
-  axiosRaw.interceptors.response.use((response: AxiosResponse<ResData<any>>) => {
-    if (matchURL(axiosConfig.unResErrorSet, response.config)) {
-      return response
-    }
+  axiosRaw.interceptors.response.use(
+    (response: AxiosResponse<ResData<any>>) => {
+      if (matchURL(axiosConfig.unResErrorSet, response.config)) {
+        return response
+      }
 
-    if (response.status !== 200) {
-      axiosConfig.resErrorHandler.forEach(handler => handler(response))
-      throw new Error(response.statusText)
-    } else if (response.data.code !== ResCode.SUCCESS) {
-      axiosConfig.resErrorHandler.forEach(handler => handler(response, response.data))
-      if (response.data.code === ResCode.TOKEN_INVALID) {
-        delToken()
+      if (response.status !== 200) {
+        axiosConfig.resErrorHandler.forEach(handler => handler(response))
+        throw new Error(response.statusText)
+      } else if (response.data.code !== ResCode.SUCCESS) {
+        axiosConfig.resErrorHandler.forEach(handler => handler(response, response.data))
+        if (response.data.code === ResCode.TOKEN_INVALID) {
+          delToken()
+        }
+        throw new Error(response.data.message)
+      } else {
+        return response.data.data
       }
-      throw new Error(response.data.msg)
-    } else {
-      return response.data.data
+    },
+    (err) => {
+      if (!matchURL(axiosConfig.unResErrorSet, err.config)) {
+        axiosConfig.resErrorHandler.forEach(handler => handler(err.response))
+      }
+      throw new Error(err.response.statusText)
     }
-  })
+  )
 
 
   type AxiosProcess = {

+ 1 - 0
src/api/url.ts

@@ -0,0 +1 @@
+export const LOGIN = `/fusion/fdLogin`

+ 18 - 0
src/api/user.ts

@@ -0,0 +1,18 @@
+import { axios, setToken } from './instance'
+import { LOGIN } from './url'
+
+export type User = {
+  id: string,
+  userName: string,
+  head: string,
+  cameraCount: number
+}
+
+// 登录
+export type LoginParams = { phoneNum: string, password: string}
+export const login = async (data: LoginParams) => {
+  axios.get('/')
+  const { token, user } = await axios.post<{token: string, user: User}>(LOGIN, data)
+  setToken(token)
+  return user
+}

+ 27 - 0
src/components/background/index.tsx

@@ -0,0 +1,27 @@
+import { useEffect, useState } from "react";
+import CanvasNest from 'canvas-nest.js';
+import style from './style.module.scss'
+
+export const Background = ({ children }: { children: any }) => {
+  const [cndom, setCNdom] = useState<HTMLElement | null>()
+  useEffect(() => {
+    if (cndom) {
+      const config = {
+        color: '228 229 231',
+        pointColor: '228 229 231',
+        count: 88,
+      }
+      const cn = new CanvasNest(cndom, config);
+      return () => cn.destroy()
+    }
+  }, [cndom])
+
+  return (
+    <>
+      <div className={style['background']} ref={setCNdom} />
+      { children }
+    </>
+  )
+}
+
+export default Background

+ 8 - 0
src/components/background/style.module.scss

@@ -0,0 +1,8 @@
+
+.background {
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  position: absolute !important;
+}

+ 2 - 1
src/components/index.ts

@@ -1,3 +1,4 @@
 export * from './async-component'
 export * from './route-menu'
-export * from './tabs'
+export * from './tabs'
+export * from './background'

+ 3 - 9
src/constant/index.ts

@@ -1,11 +1,5 @@
-export enum RoutePath {
-  login = '/login/:id',
-
-  home = '/',
-
-  scene = '/scene'
-}
-
+export * from './route'
 export * from './scene'
 export * from './thunk'
-export * from './api'
+export * from './api'
+export * from './sys'

+ 7 - 0
src/constant/route.ts

@@ -0,0 +1,7 @@
+export enum RoutePath {
+  login = '/login',
+
+  home = '/',
+
+  scene = '/scene'
+}

+ 1 - 0
src/constant/sys.ts

@@ -0,0 +1 @@
+export const title = '三维数据融合平台'

+ 2 - 1
src/layout/header/index.tsx

@@ -1,11 +1,12 @@
 import { Avatar } from 'antd'
 import style from './style.module.scss'
+import { title } from 'constant'
 
 
 export const HeaderContent = () => {
   return (
     <>
-      <h2 className={style.title}>三维数据融合平台</h2>
+      <h2 className={style.title}>{title}</h2>
       <div className={style.avatar}>
         <Avatar src="https://joeschmoe.io/api/v1/random" />
         <span className={style.username}>用户</span>

+ 5 - 0
src/react-app-env.d.ts

@@ -21,3 +21,8 @@ type ExtractRouteParamsKey<T extends string> =
 type ExtractRouteParams<T> = {
   [key in ExtractRouteParamsKey<T>]: string
 }
+
+
+declare module 'canvas-nest.js' {
+  export default any
+}

+ 14 - 0
src/setupProxy.js

@@ -0,0 +1,14 @@
+// 配置代理 - 用于联调
+const { createProxyMiddleware } = require('http-proxy-middleware')
+
+module.exports = function (app) {
+  app.use(
+    createProxyMiddleware('/api', {
+      target: 'http://120.25.146.52:3090/mock/193/',
+      changeOrigin: true,
+      pathRewrite: {
+        '^/api': ''
+      }
+    })
+  )
+}

+ 14 - 2
src/store/help.ts

@@ -3,6 +3,7 @@ import { ThunkStatus } from 'constant'
 import { filterItems, findItems } from 'utils'
 
 import type { ActionReducerMapBuilder, AsyncThunk } from '@reduxjs/toolkit'
+import type { AsyncThunkFulfilledActionCreator } from '@reduxjs/toolkit/dist/createAsyncThunk'
 
 type BaseThunkState = {
   status: ThunkStatus
@@ -50,9 +51,15 @@ export function createArrayThunkSelects<State, T extends any[], K extends keyof
   }
 }
 
-export const thunkStatusAutoSet = <K, T extends ThunkState<K>, Thunk extends AsyncThunk<any, any, any>>(
+export const thunkStatusAutoSet = <
+  K, 
+  T extends ThunkState<K>, 
+  Thunk extends AsyncThunk<any, any, any>,
+  Returned = Thunk['fulfilled'] extends AsyncThunkFulfilledActionCreator<infer T, any, any> ? T : never
+>(
   builder: ActionReducerMapBuilder<T>,
-  thunk: Thunk
+  thunk: Thunk,
+  success?: (state: T, data: Returned) => void
 ) => {
   builder
     .addCase(thunk.pending, state => {
@@ -60,9 +67,14 @@ export const thunkStatusAutoSet = <K, T extends ThunkState<K>, Thunk extends Asy
     })
     .addCase(thunk.fulfilled, (state, action) => {
       state.status = ThunkStatus.success
+      success && success(state as any, action.payload)
     })
     .addCase(thunk.rejected, (state, action) => {
+      console.error(action.error)
       state.status = ThunkStatus.err
+      state.error = (action.error as Error).message
     })
   return builder
 }
+
+export { ThunkStatus }

+ 7 - 3
src/store/index.tsx

@@ -1,13 +1,15 @@
 import { configureStore } from '@reduxjs/toolkit'
 import { Provider } from 'react-redux'
 import { useDispatch as useDispatchRaw, useSelector as useSelectorRaw } from 'react-redux'
-import { sceneReducer } from './scene'
+import { sceneReducer, sceneName } from './scene'
+import { userReducers, userName } from './user'
 
 import type { TypedUseSelectorHook } from 'react-redux'
 
 export const store = configureStore({
   reducer: {
-    scene: sceneReducer
+    [sceneName]: sceneReducer,
+    [userName]: userReducers,
   }
 })
 
@@ -24,4 +26,6 @@ export const AppStore = ({ children }: { children: any }) => (
   </Provider>
 )
 
-export default store
+export default store
+export * from './scene'
+export * from './user'

+ 11 - 7
src/store/scene.ts

@@ -5,7 +5,9 @@ import { initialThukState, createArrayThunkSelects, thunkStatusAutoSet } from '.
 import type { ThunkState } from './help'
 import type { Scenes } from 'api'
 import type { StoreState } from './'
+import { SceneType } from 'constant'
 
+export type { Scenes, Scene } from 'api'
 export type SceneState = ThunkState<{value: Scenes}>
 
 const initialState: SceneState = {
@@ -20,15 +22,15 @@ const sceneSlice = createSlice({
 
   },
   extraReducers(builder) {
-    thunkStatusAutoSet(builder, fetchScenes)
-
-    builder
-      .addCase(fetchScenes.fulfilled, (state, action) => {
-        state.value = action.payload
-      })
+    thunkStatusAutoSet(
+      builder, 
+      fetchScenes,
+      (state, scenes) => state.value = scenes
+    )
   }
 })
 
+export const sceneName = sceneSlice.name
 export const sceneReducer = sceneSlice.reducer
 export const {
   listSelector: scenesSelector,
@@ -39,4 +41,6 @@ export const {
   'id'
 )
 
-export const fetchScenes = createAsyncThunk('fetch/scenes', getSceneByType)
+export const fetchScenes = createAsyncThunk('fetch/scenes', async (type: SceneType) => {
+  return await getSceneByType(type)
+})

+ 41 - 0
src/store/user.ts

@@ -0,0 +1,41 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
+import { initialThukState, thunkStatusAutoSet } from './help'
+import { login } from 'api'
+
+import type { User } from 'api'
+import type { ThunkState } from './help'
+import type { StoreState } from 'store'
+
+export type { User } 
+
+const initialState: ThunkState<{ value: User }> = {
+  ...initialThukState,
+  value: {
+    id: '0',
+    userName: '游客',
+    head: '',
+    cameraCount: 0
+  }
+}
+
+const userSlice = createSlice({
+  name: 'user',
+  initialState,
+  reducers: { },
+  extraReducers(builder) {
+    thunkStatusAutoSet(
+      builder, 
+      postLogin,
+      (state, user) => state.value = user
+    )
+  },
+})
+
+export const userName = userSlice.name
+export const userReducers = userSlice.reducer
+
+export const postLogin = createAsyncThunk('post/login', login)
+export type { LoginParams } from 'api'
+
+
+export const userSelector = (state: StoreState) => state.user.value

+ 43 - 6
src/views/login/index.tsx

@@ -1,10 +1,47 @@
-import { useRoute, RoutesConfig } from 'router'
+import { title } from 'constant'
+import { Input, Form, Button } from 'antd'
+import { UserOutlined, LockOutlined } from '@ant-design/icons'
+import { Background } from 'components'
+import { useDispatch, postLogin } from 'store'
+import style from './style.module.scss'
 
-export const Login = ({route}: {route: RoutesConfig}) => {
-  console.log(route)
-  const match = useRoute()
-  console.log(match)
-  return <div>123</div>
+const loginInputs = [
+  {
+    name: 'phoneNum',
+    rules: [{ required: true, message: '请输入账号' }],
+    node: <Input placeholder='请输入账号' size="large" prefix={<UserOutlined />} />
+  },
+  {
+    name: 'password',
+    rules: [{ required: true, message: '请输入密码' }],
+    node: <Input.Password placeholder='请输入密码' size="large" prefix={<LockOutlined />} />
+  },
+  {
+    node: <Button type='primary' htmlType='submit' size="large" children="登录" block />
+  }
+]
+
+export const Login = () => {
+  const dispatch = useDispatch()
+  const renderOptions = loginInputs.map(({node, ...props}, i) => (
+    <Form.Item {...props} key={i} children={node} />
+  ))
+
+  return (
+    <Background>
+      <div className={style['login-layout']}>
+        <div className={style['login']}>
+          <h1>{title}</h1>
+          <Form 
+            name="login"
+            onFinish={data => dispatch(postLogin(data))}
+            autoComplete="off"
+            children={renderOptions}
+          />
+        </div>
+      </div>
+    </Background>
+  )
 }
 
 export default Login

+ 18 - 0
src/views/login/style.module.scss

@@ -0,0 +1,18 @@
+.login-layout {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  z-index: 1;
+}
+
+.login {
+  width: 360px;
+
+  h1 {
+    margin-bottom: 40px;
+    text-align: center;
+  }
+}

+ 16 - 19
src/views/scene/list.tsx

@@ -1,32 +1,29 @@
 import { SceneType } from "constant"
 import { Table } from 'antd'
-import style from './style.module.scss'
 import { getTypeColumns } from './table-cloumns'
-
-
-interface DataType {
-  title: string
-  sncode: string,
-  time: string,
-  status: number,
-}
-
-const datas: DataType[] = [
-  {
-    title: '123123',
-    sncode: '123123',
-    time: '123123',
-    status: 2,
-  }
-]
+import style from './style.module.scss'
+import { useEffect } from "react"
+import { 
+  useDispatch,
+  useSelector, 
+  fetchScenes,
+  scenesSelector
+} from 'store'
 
 
 export type SceneListProps = {type: SceneType}
 
 export const SceneList = ({ type }: SceneListProps) => {
+  const dispatch = useDispatch()
+  const scenes = useSelector(scenesSelector)
+
+  useEffect(() => {
+    dispatch(fetchScenes(type))
+  }, [dispatch, type])
+
   return (
     <div className={style['table-body']}>
-      <Table columns={getTypeColumns(type)} dataSource={datas} rowKey="title" />
+      <Table columns={getTypeColumns(type)} dataSource={scenes} rowKey="title" />
     </div>
   )
 }

+ 23 - 0
yarn.lock

@@ -3046,6 +3046,13 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001366:
   resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001370.tgz#0a30d4f20d38b9e108cc5ae7cc62df9fe66cd5ba"
   integrity sha512-3PDmaP56wz/qz7G508xzjx8C+MC2qEm4SYhSEzC9IBROo+dGXFWRuaXkWti0A9tuI00g+toiriVqxtWMgl350g==
 
+canvas-nest.js@^2.0.4:
+  version "2.0.4"
+  resolved "http://192.168.0.47:4873/canvas-nest.js/-/canvas-nest.js-2.0.4.tgz#e010075d5dbbf3cbfc1ef18942c266b1af873469"
+  integrity sha512-Wgc+39fQBIP8fm9+Rq+Zt6+QLHZRsIKpaOQPL5KcWPlXTAalCKY4KCuklY7qzzZqBRF/t2APIrk+PuRw6ZlOog==
+  dependencies:
+    size-sensor "^0.2.0"
+
 case-sensitive-paths-webpack-plugin@^2.4.0:
   version "2.4.0"
   resolved "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
@@ -4962,6 +4969,17 @@ http-proxy-middleware@^2.0.3:
     is-plain-obj "^3.0.0"
     micromatch "^4.0.2"
 
+http-proxy-middleware@^2.0.6:
+  version "2.0.6"
+  resolved "http://192.168.0.47:4873/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
+  integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
+  dependencies:
+    "@types/http-proxy" "^1.17.8"
+    http-proxy "^1.18.1"
+    is-glob "^4.0.1"
+    is-plain-obj "^3.0.0"
+    micromatch "^4.0.2"
+
 http-proxy@^1.18.1:
   version "1.18.1"
   resolved "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
@@ -8513,6 +8531,11 @@ sisteransi@^1.0.5:
   resolved "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
+size-sensor@^0.2.0:
+  version "0.2.6"
+  resolved "http://192.168.0.47:4873/size-sensor/-/size-sensor-0.2.6.tgz#6fe43afd570786f097171834002a97fe0b19a3da"
+  integrity sha512-0EZ0tXXjyRl9R9+JpoBdH57Xpnbf5NTyDdj7fnFL6KqCsdKyah286ShMflnV2SonnrSFH4OAT/52Jy8qqnoerg==
+
 slash@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"