项目练习
初始化项目
克隆项目
由于此项目是一个二次开发的项目,于是首先克隆项目代码
git clone https://gitee.com/yuonly0528/react-project.git
安装依赖
修改镜像源(可选)
# 腾讯
npm config set registry http://mirrors.cloud.tencent.com/npm
# 淘宝
npm config set registry https://registry.npmmirror.com
# npm 原镜像
npm config set registry https://registry.npmjs.org
# 查看npm配置
npm config list
# 设置npm代理
npm config set proxy http://<username>:<password>@<proxy-server-url>:<port>
npm config set proxy http://127.0.0.1:7890
npm config set https-proxy http://127.0.0.1:7890
# 删除npm代理
npm config delete proxy
npm config delete https-proxy
安装依赖
npm i -f # -f force 强制安装
启动项目
npm start
可能出现的问题
1. 由于网络原因,导致包没下全。
1. 删除node_modules : 切换源 重新 npm i -f安装
2. 叉掉错误:对付用
2. token异常:
1. 删除本地存储的token,刷新页面重新登录
项目接口地址
- 医院接口地址:http://139.198.34.216:8201/swagger-ui.html#/
- 数据字典接口地址:http://139.198.34.216:8202/swagger-ui.html#/259682545423383208563164929702
- 登录接口地址:http://139.198.34.216:8212/swagger-ui.html
- 医院管理接口文档
- 用户登录接口文档
- 数据字典接口文档
- 用户管理接口文档
- 订单管理接口文档
测试接口
测试接口也叫对接口,联调接口。
我们可以使用 swagger
postman
工具来测试接口。使用swagger来搭建接口测试
测试接口目的:要保证接口是按照接口文档完成的,没有问题。这样未来我们开发时就不用考虑接口出错的原因了。
技术选型
- react@18 框架
- 全面使用hook函数组件
- react-router-dom@6 前端路由
- redux 集中状态管理方案
- axios 发送请求函数
- antd UI 库
- typescript
- nprogress 进度条
- create-react-app 脚手架
- craco 修改脚手架配置
- i18next & react-i18next i18n国际化
- echarts 数据可视化
项目开发流程
- 项目立项(管理层)
- 项目需求分析(市场部)
产出
:项目需求文档( 产品经理 )
- 设计产品原型 (关键) (axure)
产出
:项目原型图/草图(产品经理)
- 产品开会
产品经理召集前端、后端、UI、测试
开会。
对于前端来说:
产品经理会给我们介绍项目需求具体情况,我们需要尽可能搞清楚需求,同时需要敲定需求完成时间。
UI 提供项目
UI原型图/标注图
。后端提供
接口文档
。认识对接后端、UI、测试、产品同事。
前端开发
根据后端提供开发的接口文档
测试接口
按照需求文档要求
开发项目功能
测试
上线
Demo演示
前台尚医通挂号平台(前台)在线访问地址:
- http://syt-h5.atguigu.cn/
- http://syt.atguigu.cn/
项目配置
由于react项目为了限制私自更改webpack配置,所以react项目中没有webpack的配置文件webpack.config.js
,于是为了修改webpack的配置文件,就需要用到 craco
,通过craco配置,然后再引入到webpack配置中实现自定义webpack配置
配置craro.config.js
const CracoLessPlugin = require("craco-less");
const CracoAlias = require("craco-alias");
module.exports = {
plugins: [
// 自定义主题
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
},
},
},
// 路径别名
{
plugin: CracoAlias,
options: {
source: "tsconfig",
baseUrl: "./",
tsConfigPath: "./tsconfig.extend.json",
},
},
],
// 开发服务器配置
devServer: {
// 激活代理服务器
proxy: {
// 将来以/dev-api开头的请求,就会被开发服务器转发到目标服务器去。
"/dev-api": {
// 需要转发的请求前缀
target: "http://syt-api.atguigu.cn", // 目标服务器地址
changeOrigin: true, // 允许跨域
pathRewrite: {
// 路径重写
"^/dev-api": "",
},
},
},
},
};
配置package.json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
// 这里的eject作用就是把craco的配置引入到webpack中
"eject": "react-scripts eject"
},
路径别名
开发时当组件层级太深时,我们引入其他目录下文件需要回退很多层目录,很麻烦。
路径别名则提供另外一种写路径的方式,或者说路径简写,让我们可以从根路径出发直接写路径,简单方便。
配置文件:tsconfig.extend.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@api/*": ["src/api/*"],
"@assets/*": ["src/assets/*"],
"@comps/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@pages/*": ["src/pages/*"]
}
}
}
通过插件将上述配置加载到 craco.config.js
, 最终会修改 React
脚手架配置,所以就可以项目中使用上述路径别名。
配置 craco.config.js
// 路径别名
{
plugin: CracoAlias,
options: {
source: "tsconfig",
tsConfigPath: "./tsconfig.extend.json",
},
},
路径提示
通过 extends 将上述配置加载到 tsconfig.json
中,此时在 VSCode
写代码才会有路径提示
配置tsconfig.json
{
"extends": "./tsconfig.extend.json",
}
我们将来如果要添加新的路径别名,只需要修改 tsconfig.extend.json
即可
设置代理服务器
- 代理服务器可以解决开发时的跨域问题。
- React脚手架本身提供的配置方式只支持单个代理
- 如果开发中需要有多个如何处理? ==> 利用craco工具来配置
代理服务器配置
配置craco.config.js
module.exports = {
...
...
// 开发服务器配置
devServer: {
// 激活代理服务器
proxy: {
// 拦截以/dev-api开头的请求,就会被代理服务器拦截并转发到目标服务器去。
"/dev-api": { // 需要转发的请求前缀
target: "http://syt-api.atguigu.cn", // 目标服务器地址
changeOrigin: true, // 为true时代理在转发时, 会将请求头的host改为target的值
pathRewrite: { // 路径重写, 在转发请求时自动去除路径的/dev-api
"^/dev-api": "",
},
// 可以在下面配置多个代理
},
},
},
};
需要注意的是,一旦生产环境打包项目,服务器以及相关配置并不会打包进去,所以如果运行打包后的项目,还会产生跨域问题。
最终还是需要服务端来解决,将来我们会学习 `nginx` 来解决此问题
项目中的baseUrl 需要和nginx中http根目录一致。
设置反向代理
环境变量设置
这里的环境变量是指node在运行时能够使用 process.env
属性获取到的变量,这些变量定义在.env.development
和.env.production
中,具体使用哪一个文件中的变量,需要根据webpack.config.js
中Mode
属性的配置(dev or prod)
.env.development
文件配置
REACT_APP_MOCK_API = '/mock-api'
REACT_APP_API = '/dev-api'
.env.production
文件配置
REACT_APP_API = '/prod-api'
项目目录介绍
├── public # 公共静态资源目录
│ ├── favicon.ico # 网站图标
│ ├── index.html # 主页面
├── src # 主目录
│ ├── api # 接口文件
│ ├── app # redux配置文件
│ ├── components # 公共组件
│ │ ├── Loading # loading组件
│ │ ├── Translation # 国际化组件
│ │ └── withAuthorization # 登陆权限校验组件
│ ├── layouts # 主要布局组件
│ ├── locales # i18n国际化配置
│ ├── pages # 路由组件
│ ├── routes # 路由配置
│ ├── styles # 全局/公共样式
│ ├── utils # 工具函数
│ │ └── http # 封装请求函数
│ ├── App.tsx # App组件
│ ├── index.ts # 主入口
│ ├── react-app-env.d.ts # 类型文件,在编译时会引入额外文件
├── .env.development # 开发环境加载的环境变量配置
├── .env.production # 生产环境加载的环境变量配置
├── .gitignore # git忽略文件
├── craco.config.js # react脚手架配置文件
├── package.json # 包文件
├── README.md # 项目说明文件
├── tsconfig.extend.json # 路径别名配置文件
├── tsconfig.json # ts配置文件
└── yarn.lock # yarn下载包的缓存文件
实现功能步骤
1. 创建路由组件
2. 配置路由
3. 创建组件的静态结构
4. 查看接口
1. 封装 interface
2. 封装 api 函数
5. 调用 api 函数
1. 事件回调
2. 钩子函数
6. 声明状态
医院管理功能实现
创建路由组件
创建路由组件:src/pages/hospital/hospitalSet/hospitalSet.tsx
import React from 'react'
import { Button, Card, Form, Input, Space, Table } from 'antd'
import { ColumnsType } from 'antd/lib/table'
export default function hospitalSet() {
// 定义一个静态数据:列数据
const columns:ColumnsType<any> = [
{
title: '序号'
},
{
title: '医院名称'
},
{
title: '医院编号'
},
{
title: 'api基础路径'
},
{
title: '签名'
},
{
title: '联系人姓名'
},
{
title: '联系人手机'
},
{
title: '操作'
}
]
return (
<Card>
{/* Form、Select不支持className,只能用style写样式 */}
<Form
layout='inline'
>
<Form.Item>
<Input placeholder='医院名称' />
</Form.Item>
<Form.Item>
<Input placeholder='医院编号' />
</Form.Item>
<Form.Item>
<Space>
<Button type='primary'>查询</Button>
<Button>清空</Button>
</Space>
</Form.Item>
</Form>
{/* 这里的className:mt 是在 src/style/index.css引入的全局样式 */}
<Space className='mt'>
<Button type='primary'>添加</Button>
<Button disabled>批量删除</Button>
</Space>
<Table
className='mt'
columns={columns}
/>
</Card>
)
}
路由配置
配置路由:src/routes/index.tsx
// src/routes/index.tsx
import { lazy, Suspense, FC } from "react";
import { useRoutes, Navigate } from "react-router-dom";
import { HomeOutlined, ShopOutlined } from "@ant-design/icons";
import type { XRoutes } from "./types";
// import { Translation } from "react-i18next";
import {
Layout,
EmptyLayout,
// CompLayout
} from "../layouts";
import Loading from "@comps/Loading";
const Login = lazy(() => import("@pages/login"));
const Dashboard = lazy(() => import("@pages/dashboard"));
const NotFound = lazy(() => import("@pages/404"));
const hospitalSet = lazy(() => import("@/pages/hospital/hospitalSet/hospitalSet"));
// 定义load函数:用于懒加载
const load = (Comp: FC) => {
return (
// 因为路由懒加载,组件需要一段网络请求时间才能加载并渲染
// 在组件还未渲染时,fallback就生效,来渲染一个加载进度条效果
// 当组件渲染完成时,fallback就失效了
<Suspense fallback={<Loading />}>
{/* 所有lazy的组件必须包裹Suspense组件,才能实现功能 */}
<Comp />
</Suspense>
);
};
// 路由配置
const routes: XRoutes = [
{
path: "/",
// (带逻辑判断组件:如果登陆过就重定向到当前目录,若未登录重定向到登陆页面)
element: <EmptyLayout />,
children: [
{
path: "login",
element: load(Login),
},
],
},
{
path: "/syt",
// (带逻辑判断组件: 如果登陆过就重定向到dashboard页面, 若未登录重定向到登陆页面)
element: <Layout />,
children: [
{
// 路由路径
path: "/syt/dashboard",
meta: {
icon: <HomeOutlined />,
title: '首页'
},
element: load(Dashboard),
},
{
// 路由路径
path: "/syt/hospital",
meta: {
icon: <ShopOutlined />,
title: '医院管理'
},
children: [
{
path: '/syt/hospital/hospitalSet',
meta: {
title: '医院设置'
},
element: load(hospitalSet)
}
]
}
],
},
{
path: "/404",
element: load(NotFound),
},
{
path: "*", // 任意路径:除了上面路径以外其他路径
element: <Navigate to="/404" />,
},
];
/*
自定义hook: 注册应用的所有路由
*/
export const useAppRoutes = () => {
return useRoutes(routes);
};
// 找到要渲染成左侧菜单的路由
export const findSideBarRoutes = () => {
const currentIndex = routes.findIndex((route) => route.path === "/syt");
return routes[currentIndex].children as XRoutes;
};
export default routes;
定义响应体类型
查看接口:【请求方式、url、参数、返回值】
封装interface: 返回值
封装api函数:
方式1. path: request.请求方式<any,响应值类型>(url/参数1/参数2...)
方式2. query: request.请求方式<any,响应值类型>(url,
{params:参数key:参数值}
)
定义request:项目文件初始化的时候已经创建了(二次开发)
定义响应体类型:src/api/hospital/model/hospitalType.ts
/**
* 医院设置每一项对象类型
*/
export interface IHospitalSetItem {
id: number;
createTime: string;
hosname: string;
hoscode: string;
apiUrl: string;
signKey: string;
contactsName: string;
contactsPhone: string;
status: number
}
/**
* 医院设置列表类型
*/
export type IHospitalSetList = IHospitalSetItem[]
/**
* 医院设置分页 响应数据类型
*/
export interface IHospitalSetListResponse {
records: IHospitalSetList;
total: number;
}
定义请求api:src/api/hospital/hospitalSet.ts
import { request } from '@utils/http'
import { IHospitalSetListResponse } from './model/hospitalSetType'
/**
* 医院设置相关api函数
* @param page 当前页 必填
* @param limit 每页几条 必填
* @param hosname 医院名称
* @param hoscode 医院编号
* @returns
*/
export const getHospitalSetList = (page: number, limit: number, hosname?: string, hoscode?: string) => {
return request.get<any, IHospitalSetListResponse>(`/admin/hosp/hospitalSet/${page}/${limit}`, {
params: {
hosname,
hoscode
}
})
}
调用API渲染数据
通过以下两种方式调用API
1. 事件回调
2. 钩子函数:生命周期 useEffect()
渲染数据:设置列数据的dataIndex属性(antd中语法)
1. 定义数据状态
2. 设置状态(修改数据状态)
src/pages/hospital/hospitalSet/HospitalSet.tsx
import React, { useEffect, useState } from 'react'
import { Button, Card, Form, Input, Space, Table } from 'antd'
import { EditOutlined, DeleteOutlined } from '@ant-design/icons'
import { ColumnsType } from 'antd/lib/table'
import { IHospitalSetList } from '@/api/hospital/model/hospitalSetType'
import { getHospitalSetList } from '@/api/hospital/hospitalSet'
export default function HospitalSet() {
// 定义一个静态数据:列数据
const columns: ColumnsType<any> = [
{
// 手动计算序号
title: '序号',
render(value: any, row: any, index: number) {
return (current - 1) * pageSize + (index + 1)
}
},
{
title: '医院名称',
dataIndex: 'hosname'
},
{
title: '医院编号',
dataIndex: 'hoscode'
},
{
title: 'api基础路径',
dataIndex: 'apiUrl'
},
{
title: '签名',
dataIndex: 'signKey'
},
{
title: '联系人姓名',
dataIndex: 'contactsName'
},
{
title: '联系人手机',
dataIndex: 'contactsPhone'
},
{
title: '操作',
render(row: any) {
return (
<Space>
<Button type='primary' icon={<EditOutlined />}>编辑</Button>
<Button type='primary' icon={<DeleteOutlined />} danger>删除</Button>
</Space>
)
}
}
]
// 数据状态
// 分页相关状态
let [current, setCurrent] = useState<number>(1);
let [pageSize, setPageSize] = useState<number>(3);
let [total, setTotal] = useState<number>(10);
// 医院设置列表类型
let [hospitalSetList, setHospitalSetList] = useState<IHospitalSetList>([]);
let [hosname, setHosname] = useState<string>();
let [hoscode, setHoscode] = useState<string>();
// 定义异步请求
async function _getHospitalSetList() {
let { records, total } = await getHospitalSetList(current, pageSize, hosname, hoscode);
// 设置状态
setHospitalSetList(records);
setTotal(total);
}
// 声明周期
useEffect(() => {
// 调用异步请求
_getHospitalSetList();
}, [current, pageSize])
return (
<Card>
<Form
layout='inline'
>
<Form.Item>
<Input placeholder='医院名称' />
</Form.Item>
<Form.Item>
<Input placeholder='医院编号' />
</Form.Item>
<Form.Item>
<Space>
<Button type='primary'>查询</Button>
<Button>清空</Button>
</Space>
</Form.Item>
</Form>
{/* 这里的className:mt 是在 src/style/index.css引入的全局样式 */}
<Space className='mt'>
<Button type='primary'>添加</Button>
<Button disabled>批量删除</Button>
</Space>
<Table
className='mt'
rowKey={'id'}
columns={columns}
dataSource={hospitalSetList}
pagination={{
current,
pageSize,
total,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: [3, 5, 10, 20],
onChange: (page: number, pageSize: number) => {
setCurrent(page);
setPageSize(pageSize)
}
}}
/>
</Card>
)
}
设置表格loading效果
src/pages/hospital/hospitalSet/HospitalSet.tsx
...
// 设置loading状态
let [loading,setLoading] = useState<boolean>(false);
// 定义异步请求
async function _getHospitalSetList() {
setLoading(true);
let { records, total } = await getHospitalSetList(current, pageSize, hosname, hoscode);
// 设置状态
setHospitalSetList(records);
setTotal(total);
setLoading(false);
}
...
<Table
className='mt'
...
loading={loading}
...
/>
列宽度列固定滚动条
可以查阅 antd官方文档中的Table组件中API查看更多属性
src/pages/hospital/hospitalSet/hospitalSet.tsx
const columns: ColumnsType<any> = [
{
// 手动计算序号
title: '序号',
// 设置宽度
width:60,
align:'center',
render(value: any, row: any, index: number) {
return (current - 1) * pageSize + (index + 1)
}
},
{
title: '操作',
width:120,
// 设置固定定位
fixed:'right',
render(row: any) {
return (
<Space>
<Button type='primary' icon={<EditOutlined />}>编辑</Button>
<Button type='primary' icon={<DeleteOutlined />} danger>删除</Button>
</Space>
)
}
}
]
return (
<Table
className='mt'
rowKey={'id'}
columns={columns}
dataSource={hospitalSetList}
loading={loading}
// 设置宽度
scroll={{x:1300}}
pagination={{
current,
pageSize,
total,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: [3, 5, 10, 20],
onChange: (page: number, pageSize: number) => {
setCurrent(page);
setPageSize(pageSize)
console.log(hospitalSetList)
}
}}
/>
)
点击查询获取分页数据
描述: 做了什么,产生了什么结果?
文本框输入内容,点击查询,重新获取分页数据!
拆分步骤(实现步骤):
1. 获取文本框输入内容
1. 参数:values
2. form对象:
1. const [form] = Form.useForm()
2. 绑定form对象
3. form.getFieldsValue获取
4. Form.Item 要有name属性 hosname hoscode
2. 给查询按钮绑定单击事件
1. button htmlType='submit'
2. Form onFinish ==> search 函数
3. 根据文本框内容发送ajax请求
1. 在useEffect 的监听中 监听 hosname hoscode
2. 在 onFinish的事件回调中,重新设置 hosname 和 hoscode的状态
src/pages/hospital/hospitalSet/hospitalSet.tsx
// 创建form对象
let [form] = Form.useForm();
// 搜索方法
const search = () => {
// 获取form表单中的数据
let { hosname, hoscode } = form.getFieldsValue();
// 设置数据状态
setHosname(hosname);
setHoscode(hoscode);
// 检索从第一页开始查看
current !== 1 && setCurrent(1);
}
// 声明周期
useEffect(() => {
// 调用异步请求
_getHospitalSetList();
}, [current, pageSize, hosname, hoscode])
<Form
layout='inline'
onFinish={search}
form={form}
>
<Form.Item name='hosname'>
<Input placeholder='医院名称' />
</Form.Item>
<Form.Item name='hoscode'>
<Input placeholder='医院编号' />
</Form.Item>
<Form.Item>
<Space>
<Button type='primary' htmlType='submit'>查询</Button>
<Button>清空</Button>
</Space>
</Form.Item>
</Form>
清空搜索条件
src/pages/hospital/hospitalSet/hospitalSet.tsx
// 清空方法
const clear = () => {
// 1. 重置表单 hosname 和 hoscode ==>界面
// 2. 重置 状态 hosname 和 hoscode ===> 重发请求的
form.resetFields();
setHosname(undefined);
setHoscode(undefined);
setCurrent(1);
}
<Form.Item>
<Space>
<Button type='primary' htmlType='submit'>查询</Button>
<Button onClick={clear} disabled={hosname === undefined && hoscode === undefined}>清空</Button>
</Space>
</Form.Item>
添加医院功能实现
封装接口
1. 请求方式、url、参数、响应结果
2. 封装 interface
3. 封装api函数
实现添加
描述:点击保存,将表单中的数据添加到医院列表
步骤:
1. 绑定事件
1. 给Form绑定 onFinish 事件
2. Button组件 添加 htmlType='submit'
2. 获取表单数据
通过form获取
0. Form.Item 组件需要有 name属性,并赋值
1. 创建form: const [form] = Form.useForm()
2. 给Form组件绑定form属性
3. 通过form.getFieldsValue获取表单数据
3. 发送ajax请求
调用api函数,发送请求
src/pages/hospital/hospitalSet/HospitalSet.tsx
{/* 这里的className:mt 是在 src/style/index.css引入的全局样式 */}
<Space className='mt'>
<Button type='primary' onClick={() => navigate('/syt/hospital/hospitalSet/add')}>添加</Button>
<Button disabled>批量删除</Button>
</Space>
src/pages/hospital/hospitalSet/components/AddOrUpdate.tex
import { addHospitalSet } from '@/api/hospital/hospitalSet'
import { Button, Card, Form, Input, message, Space } from 'antd'
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function AddOrUpdate() {
// 定义form对象
const [form] = Form.useForm()
const navigate = useNavigate()
const onFinish = async () => {
let data = form.getFieldsValue()
try{
await addHospitalSet(data)
message.success('添加成功')
navigate('/syt/hospital/hospitalSet')
}catch(e){
message.error('添加失败')
}
}
return (
<Card>
<Form
name="basic"
// 设置标签宽度
labelCol={{ span: 2 }}
// 设置输入框的宽度
wrapperCol={{ span: 22 }}
onFinish={onFinish}
form={form}
>
<Form.Item
label="医院名称"
name="hosname"
rules={[{ required: true, message: '请输入医院名称!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="医院编号"
name="hoscode"
rules={[{ required: true, message: '请输入医院编号!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="api基础路径"
name="apiUrl"
rules={[{ required: true, message: '请输入api基础路径!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="联系人姓名"
name="contactsName"
rules={[{ required: true, message: '请输入联系人姓名!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="联系人手机"
name="contactsPhone"
rules={[{ required: true, message: '请输入联系人手机!' }]}
>
<Input />
</Form.Item>
<Form.Item wrapperCol={{ offset: 2, span: 16 }}>
<Space>
<Button type="primary" htmlType="submit">
保存
</Button>
<Button>返回</Button>
</Space>
</Form.Item>
</Form>
</Card>
)
}
定义路由:src/routes/index.tex
{
// 路由路径
path: "/syt/hospital",
meta: {
icon: <ShopOutlined />,
title: '医院管理'
},
children: [
{
path: '/syt/hospital/hospitalSet',
meta: {
title: '医院设置'
},
element: load(HospitalSet)
},
{
path: '/syt/hospital/hospitalSet/add',
meta: {
title: '添加医院'
},
hidden:true, // 隐藏菜单栏
element: load(AddOrUpdate)
}
]
}
定义请求体类型 src/api/model/hospitalSetType.ts
// 添加医院 请求体类型
export interface IHospitalSetData{
apiUrl: string;
contactsName: string;
contactsPhone: string;
hoscode: string;
hosname: string;
}
封装接口 src/api/hospitalSet.ts
* 添加医院设置
* @param data 请求体数据
* @returns null
*/
export const addHospitalSet = (data:IHospitalSetData)=>{
return request.post<any,null>('/admin/hosp/hospitalSet/save', data)
}
删除医院功能
封装接口
封装接口 src/api/hospitalSet.ts
/**
* 根据id 删除医院设置
* @param id
* @returns null
*/
export const delHospitalSetById = (id:string)=>{
return request.delete<any,null>(`/admin/hosp/hospitalSet/remove/${id}`)
}
在组件上绑定单机事件: src/pages/hospital/hospitalSet/HospitalSet.tsx
{
title: '操作',
width:120,
fixed:'right',
render(row: any) {
return (
<Space>
<Button type='primary' icon={<EditOutlined />}>编辑</Button>
<Button type='primary' icon={<DeleteOutlined />} onClick={() => { deleteHospitalById(row.id) }} danger>删除</Button>
</Space>
)
}
}
// 删除医院方法
const deleteHospitalById = (id:string) => {
// 弹窗
confirm({
title: '确定删除么?',
icon: <ExclamationCircleFilled />,
content: '删除当前记录',
async onOk() {
// 发送删除医院请求
await delHospitalById(id)
message.success('删除成功')
// 刷新页面
_getHospitalSetList()
},
onCancel() {
console.log('Cancel');
},
});
}
批量删除功能
添加复选框
src/pages/hospital/hospitalSet/HospitalSet.tsx
<Table
className='mt'
rowKey={'id'}
columns={columns}
dataSource={hospitalSetList}
loading={loading}
// 设置宽度
scroll={{x:1300}}
// 设置复选框
rowSelection={{
/**
* onChange调用时机,列表复选框发生变化的时候
* @param selectedKeys 选中条id组成的数组
*/
onChange(selectedKeys: React.Key[]) {
console.log('selectedKeys: ', selectedKeys)
setSelectedKeys(selectedKeys);
}
}}
pagination={{
current,
pageSize,
total,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: [3, 5, 10, 20],
onChange: (page: number, pageSize: number) => {
setCurrent(page);
setPageSize(pageSize)
console.log(hospitalSetList)
}
}}
/>
封装批量删除的api接口 src/api/hospitalSet.ts
// 批量删除,请求体的类型是React.Key[]不用再单独封装
/**
*
* @param ids id 的数组
* @returns null
*/
export const removeBatch = (ids: React.Key[]) => {
return request.delete<any, null>('/admin/hosp/hospitalSet/batchRemove', {
data: ids
})
}
实现批量删除
src/pages/hospital/hospitalSet/HospitalSet.tsx
import { delHospitalById, getHospitalSetList, removeBatch } from '@/api/hospital/hospitalSet'
// 批量删除医院的方法
const batchRemove = (ids: React.Key[]) => {
// 弹窗
confirm({
title: '确定删除么?',
icon: <ExclamationCircleFilled />,
content: '删除当前记录',
async onOk() {
// 发送删除医院请求
await removeBatch(ids)
// 将selectedKeys状态清空成空数组
setSelectedKeys([]);
message.success('删除成功')
// 刷新页面
_getHospitalSetList()
},
onCancel() {
console.log('Cancel');
},
});
}
{/* 这里的className:mt 是在 src/style/index.css引入的全局样式 */}
<Space className='mt'>
<Button type='primary' onClick={() => navigate('/syt/hospital/hospitalSet/add')}>添加</Button>
<Button disabled={selectedKeys.length === 0} onClick={() => { batchRemove(selectedKeys) }}>批量删除</Button>
</Space>
编辑医院设置
定义路由:src/routes/index.tex
{
path: '/syt/hospital/hospitalSet/edit/:id',
meta: {
title: '编辑医院'
},
hidden: true, // 隐藏菜单栏
element: load(AddOrUpdate)
}
组件上绑定按钮:src/pages/hospital/hospitalSet/HospitalSet.tsx
<Space>
<Button type='primary' icon={<EditOutlined />} onClick={() => { editHospitalById(row.id) }}>编辑</Button>
<Button type='primary' icon={<DeleteOutlined />} onClick={() => { deleteHospitalById(row.id) }} danger>删除</Button>
</Space>
封装api函数:src/api/hospitalSet.ts
/**
* 根据id获取医院设置数据
* @param id
* @returns Promise<IHospitalSetData>
*/
export const getHospitalSetById = (id:string)=>{
return request.get<any,IHospitalSetData>('/admin/hosp/hospitalSet/get/' + id)
}
/**
* 更新医院设置
* @param data
* @returns null
*/
export const updateHospitalSet = (data:IHospitalSetUpdateData)=>{
return request.put<any,null>('/admin/hosp/hospitalSet/update', data)
}
封装更新医院请求体类型:src/api/model/hospitalSetType.ts
/**
* 更新医院设置请求体参数类型
*/
export interface IHospitalSetUpdateData extends IHospitalSetData{
id:string;
}
医院列表路由组件创建
创建医院列表组件:src/pages/hospital/hospitalList/hospitalList.tsx
import React from 'react'
export default function HospitalList() {
return (
<div>hospitalList</div>
)
}
编辑医院列表路由:src/routes/index.tsx
{
path: '/syt/hospital/hospitalList',
meta: {
title: '医院列表'
},
hidden: false, // 隐藏菜单栏
element: load(HospitalList)
}
医院省市区列表
渲染医院列表组件静态:src/pages/hospital/hospitalList/hospitalList.tsx
import { Button, Card, Form, Input, Select, Space } from 'antd'
import Table, { ColumnsType } from 'antd/lib/table'
import React, { useEffect, useState } from 'react'
import { getDistrictList } from '@/api/hospital/hospitalList'
import { IDistrictList } from '@/api/hospital/model/hospitalListType';
// 解构出选项列表
const { Option } = Select;
export default function HospitalList() {
const [form] = Form.useForm();
const columns: ColumnsType<any> = [
{
title: '序号'
},
{
title: '医院logo'
},
{
title: '医院名称'
},
{
title: '等级'
},
{
title: '详细地址'
},
{
title: '状态'
},
{
title: '创建时间'
},
{
title: '操作'
}
]
// 定义省市区数据状态
let [provinceList, setProvinceList] = useState<IDistrictList>([])
let [cityList, setCityList] = useState<IDistrictList>([])
let [dictList, setDictList] = useState<IDistrictList>([])
// 定义获取省市区的方法
// 获取省列表
const getProvinceList = async () => {
const provinceList = await getDistrictList(86);
// 设置省状态数据
setProvinceList(provinceList);
}
// 根据省id 获取是列表并渲染
const getCityList = async (id: number) => {
// 将市、区的表单项赋值为undefined
form.setFieldsValue({
cityCode:undefined,
districtCode:undefined
})
// 将区的状态数据设置为空数组
setDictList([]);
// onSelect=function(value) value的值为选中的value的值
const cityList = await getDistrictList(id);
setCityList(cityList);
}
// 根据市id 获取区列表并渲染
const getDictList = async (id: number) => {
// 将区的表单值设置为undefined
form.setFieldsValue({
districtCode:undefined
})
const dictList = await getDistrictList(id);
setDictList(dictList);
}
// 定义生命周期函数
useEffect(() => {
getProvinceList();
}, [])
return (
<Card>
<Form
layout='inline'
form={form}
>
<Form.Item name='provinceCode'>
<Select
className='mb'
placeholder='请选择省'
style={{ width: 180 }}
onSelect={(value:any)=>{getCityList(value)}}
>
{provinceList.map(province => (
<Option value={province.value} key={province.id}>{province.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='cityCode'>
<Select
placeholder='请选择市'
style={{ width: 180 }}
onSelect={getDictList}
>
{cityList.map(city => (
<Option key={city.id} value={city.value}>{city.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='districtCode'>
<Select placeholder='请选择区' style={{ width: 180 }}>
{dictList.map(dict => (
<Option key={dict.id} value={dict.value}>{dict.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='hosname'>
<Input placeholder='医院名称'/>
</Form.Item>
<Form.Item name='hoscode'>
<Input placeholder='医院编号'/>
</Form.Item>
<Form.Item name='hostype'>
<Select placeholder='医院类型' style={{ width: 180 }}>
<Option value="beijing">北京</Option>
<Option value="beijing">北京</Option>
<Option value="beijing">北京</Option>
</Select>
</Form.Item>
<Form.Item name='status'>
<Select placeholder='医院状态' style={{ width: 180 }}>
<Option value="beijing">北京</Option>
<Option value="beijing">北京</Option>
<Option value="beijing">北京</Option>
</Select>
</Form.Item>
<Form.Item>
<Space>
<Button type='primary' htmlType='submit'>查询</Button>
<Button disabled>清空</Button>
</Space>
</Form.Item>
</Form>
<Table
className='mt'
columns={columns}
/>
</Card>
)
}
Base图片前缀
<Image width={100} src={'data:image/jpg;base64,' + row.logoData}/>
医院列表分页功能
定义api请求体和响应体类型:src/api/hospital/hostpitalType.ts
// 医院列表每一项 类型
export interface IHospitalItem {
id: string;
createTime: string; // 创建时间
param: {
hostypeString: string; // 医院等级
fullAddress: string; // 医院地址
},
hoscode: string; // 医院编号
hosname: string; // 医院名
hostype: string; // 医院类型
provinceCode: string; // 省
cityCode: string; // 市
districtCode: string; // 区
address: string; // 地址
logoData: string; // 医院logo base64URL
route: string; //乘车路线
status: number; // 医院状态
bookingRule: {
cycle: number; // 预约周期
releaseTime: string; // 放号时间
stopTime: string;// 停止挂号时间
quitDay: number; // 就诊结束日期
quitTime: string; // 结束时间
rule: string[]; //取号规则
}
}
/**
* 医院列表类型
*/
export type IHospitalList = IHospitalItem[]
/**
* 请求医院列表响应对象类型
*/
export interface IHospitalListResponse {
content:IHospitalList;
totalElements:number;
}
/**
* 医院列表参数类型
*/
export interface IHospitalListParams {
page: number;
limit: number;
hoscode?: string;
hosname?: string;
hostype?: string;
provinceCode?: string;
cityCode?: string;
districtCode?: string;
status?: number;
}
医院列表api封装:/src/api/hospital/hospitalList.ts
import { request } from "@utils/http"
import { IDistrictList, IHospitalListParams, IHospitalListResponse } from "./model/hospitalListType"
/**
* 根据id 获取省市区列表
* @param id 86 省
* @returns
*/
export const getDistrictList = (id: number) => {
return request.get<any, IDistrictList>('/admin/cmn/dict/findByParentId/' + id)
}
// 获取医院列表分页数据,同时定义请求体的类型和响应体的类型
export const getHospitalList = ({page,limit,hoscode,hosname,hostype,provinceCode,cityCode,districtCode,status}:IHospitalListParams)=>{
return request.get<any, IHospitalListResponse>(`/admin/hosp/hospital/${page}/${limit}`, {
params:{
hoscode,
hosname,
hostype,
provinceCode,
cityCode,
districtCode,
status
}
})
}
渲染医院列表组件静态:src/pages/hospital/hospitalList/hospitalList.tsx
import { Button, Card, Form, Image, Input, Select, Space } from 'antd'
import Table, { ColumnsType } from 'antd/lib/table'
import React, { useEffect, useState } from 'react'
import { getDistrictList, getHospitalList } from '@/api/hospital/hospitalList'
import { IDistrictList, IHospitalList,IHospitalItem } from '@/api/hospital/model/hospitalListType';
// 解构出选项列表
const { Option } = Select;
export default function HospitalList() {
const [form] = Form.useForm();
const columns: ColumnsType<any> = [
{
title: '序号',
render(value:any, row:any, index:number){
return (current - 1) * pageSize + (index + 1)
}
},
{
title: '医院logo',
render(row:IHospitalItem){
return (
<Image width={100} src={'data:image/jpg;base64,' + row.logoData}/>
)
}
},
{
title: '医院名称',
dataIndex:'hosname'
},
{
title: '等级',
render(row:IHospitalItem){
return row.param.hostypeString
}
},
{
title: '详细地址',
render(row:IHospitalItem){
return row.param.fullAddress
}
},
{
title: '状态',
render(row:IHospitalItem){
return row.status ? '已上线' : '未上线'
}
},
{
title: '创建时间',
dataIndex:'createTime'
},
{
render(row:IHospitalItem){
return (
<Space>
<Button type='primary'>查看</Button>
<Button type='primary'>排班</Button>
<Button type='primary'>{row.status ? '下线' : '上线'}</Button>
</Space>
)
}
}
]
// 定义省市区数据状态
let [hospitalList, setHospitalList] = useState<IHospitalList>([]);
let [total, setTotal] = useState<number>(10);
let [current, setCurrent] = useState<number>(1);
let [pageSize, setPageSize] = useState<number>(3);
let [loading, setLoading] = useState<boolean>(false);
let [provinceList, setProvinceList] = useState<IDistrictList>([])
let [cityList, setCityList] = useState<IDistrictList>([])
let [dictList, setDictList] = useState<IDistrictList>([])
let [typeList,setTypeList] = useState<IDistrictList>([])
// 定义获取省市区的方法
// 获取省列表
const getProvinceList = async () => {
const provinceList = await getDistrictList(86);
// 设置省状态数据
setProvinceList(provinceList);
}
// 根据省id 获取是列表并渲染
const getCityList = async (id: number) => {
// 将市、区的表单项赋值为undefined
form.setFieldsValue({
cityCode:undefined,
districtCode:undefined
})
// 将区的状态数据设置为空数组
setDictList([]);
// onSelect=function(value) value的值为选中的value的值
const cityList = await getDistrictList(id);
setCityList(cityList);
}
// 根据市id 获取区列表并渲染
const getDictList = async (id: number) => {
// 将区的表单值设置为undefined
form.setFieldsValue({
districtCode:undefined
})
const dictList = await getDistrictList(id);
setDictList(dictList);
}
// 获取医院等级
const getDegree = async() => {
let degree = await getDistrictList(10000)
setTypeList(degree)
}
// 获取医院分页列表数据
const _getHospitalList = async()=>{
setLoading(true);
let {content, totalElements} = await getHospitalList({page:current,limit:pageSize});
setHospitalList(content);
setTotal(totalElements);
setLoading(false);
console.log(content)
}
// 定义生命周期函数
useEffect(() => {
// 获取省
getProvinceList();
// 获取等级
getDegree()
}, [])
// 定义生命周期函数2
useEffect(()=>{
_getHospitalList();
},[current, pageSize])
return (
<Card>
<Form
layout='inline'
form={form}
>
<Form.Item name='provinceCode'>
<Select
className='mb'
placeholder='请选择省'
style={{ width: 180 }}
onSelect={(value:any)=>{getCityList(value)}}
>
{provinceList.map(province => (
<Option value={province.value} key={province.id}>{province.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='cityCode'>
<Select
placeholder='请选择市'
style={{ width: 180 }}
onSelect={getDictList}
>
{cityList.map(city => (
<Option key={city.id} value={city.value}>{city.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='districtCode'>
<Select placeholder='请选择区' style={{ width: 180 }}>
{dictList.map(dict => (
<Option key={dict.id} value={dict.value}>{dict.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='hosname'>
<Input placeholder='医院名称'/>
</Form.Item>
<Form.Item name='hoscode'>
<Input placeholder='医院编号'/>
</Form.Item>
<Form.Item name='hostype'>
<Select
placeholder='医院类型'
style={{ width: 180 }}
>
{typeList.map(type=>(
<Option value={type.value} key={type.id}>{type.name}</Option>
))}
</Select>
</Form.Item>
<Form.Item name='status'>
<Select placeholder='医院状态' style={{ width: 180 }}>
<Option value={0}>未上线</Option>
<Option value={1}>已上线</Option>
</Select>
</Form.Item>
<Form.Item>
<Space>
<Button type='primary' htmlType='submit'>查询</Button>
<Button disabled>清空</Button>
</Space>
</Form.Item>
</Form>
<Table
className='mt'
rowKey={'id'}
columns={columns}
dataSource={hospitalList}
loading={loading}
pagination={{
current,
pageSize,
total,
onChange(page:number, pageSize:number){
setCurrent(page);
setPageSize(pageSize);
}
}}
/>
</Card>
)
}
医院列表查询功能
定义参数类型:src/api/hospital/hostpitalType.ts
/**
* 医院列表参数类型
*/
export interface IFormFields{
hoscode?: string;
hosname?: string;
hostype?: string;
provinceCode?: string;
cityCode?: string;
districtCode?: string;
status?: number;
}
export interface IHospitalListParams extends IFormFields{
page: number;
limit: number;
}
渲染医院列表组件静态:src/pages/hospital/hospitalList/hospitalList.tsx
// 定义查询params
let [formFields, setFormFields] = useState<IFormFields>({
hoscode: undefined,
hosname: undefined,
provinceCode: undefined,
cityCode: undefined,
districtCode: undefined,
status: undefined,
hostype: undefined
})
// 获取医院分页列表数据
const _getHospitalList = async()=>{
setLoading(true);
let {content, totalElements} = await getHospitalList({ page: current, limit: pageSize, ...formFields });
setHospitalList(content);
setTotal(totalElements);
setLoading(false);
console.log(content)
}
// 清空功能
const clear = () => {
// 清空form表单的数据
// 清空 formFields 状态的值都为 undefined
// 当前页设置为 1
form.resetFields();
setFormFields({
hoscode: undefined,
hosname: undefined,
provinceCode: undefined,
cityCode: undefined,
districtCode: undefined,
status: undefined,
hostype: undefined
})
setCurrent(1);
}
// 定义生命周期函数2
useEffect(()=>{
_getHospitalList();
},[current, pageSize, formFields.hoscode, formFields.hosname, formFields.cityCode, formFields.provinceCode, formFields.districtCode, formFields.hostype, formFields.status])
<Form
layout='inline'
form={form}
onFinish={search}
>
<Button disabled={Object.values(formFields).every(item => item === undefined)} onClick={clear}>清空</Button>
医院详情页面
配置路由:src/route/index.ts
const HospitalDetail = lazy(() => import("@/pages/hospital/hospitalList/components/HospitalDetail"));
{
path: '/syt/hospital/hospitalList/show/:id',
meta: {
title: '医院详情'
},
hidden: true, // 隐藏菜单栏
element: load(HospitalDetail)
}
渲染医院列表组件传参:src/pages/hospital/hospitalList/hospitalList.tsx
const navigate = useNavigate()
<Space>
<Button type='primary' onClick={()=>{navigate('/syt/hospital/hospitalList/show/' + row.id)}}>查看</Button>
<Button type='primary'>排班</Button>
<Button type='primary'>{row.status ? '下线' : '上线'}</Button>
</Space>
定义医院详情请求响应体类型:src/api/hospital/hostpitalType.ts
// 医院详情类型返回值类型
export interface IHospitalDetail{
bookingRule:IBookingRule
hospital:IHospitalItem
}
// 医院列表每一项类型
export interface IHospitalItem {
id: string;
createTime: string; // 创建时间
param: {
hostypeString: string; // 医院等级
fullAddress: string; // 医院地址
},
hoscode: string; // 医院编号
hosname: string; // 医院名
hostype: string; // 医院类型
provinceCode: string; // 省
cityCode: string; // 市
districtCode: string; // 区
address: string; // 地址
logoData: string; // 医院logo base64URL
route: string; //乘车路线
status: number; // 医院状态
intro: string | null; // 联合类型
bookingRule: IBookingRule|null
}
// 医院预约类型
export interface IBookingRule {
cycle: number; // 预约周期
releaseTime: string; // 放号时间
stopTime: string;// 停止挂号时间
quitDay: number; // 就诊结束日期
quitTime: string; // 结束时间
rule: string[]; //取号规则
}
封装医院详情请求api:/src/api/hospital/hospitalList.ts
// 获取医院详情返回值api
export const getHospitalDetail = (id:string) => {
return request.get<any,IHospitalDetail>(`/admin/hosp/hospital/show/${id}`)
}
渲染页面布局:src/pages/hospitalList/components/HospitalDetail.tsx
import { getHospitalDetail } from '@/api/hospital/hospitalList';
import { IBookingRule, IHospitalItem } from '@/api/hospital/model/hospitalListType';
import { Button, Card, Descriptions, Image } from 'antd'
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom';
export default function HospitalDetail() {
// 获取path参数
let {id} = useParams();
// 定义数据状态
let [hospital, setHospital] = useState<IHospitalItem>();
let [bookingRule, setBookingRule] = useState<IBookingRule>();
// 定义方法
const _getHospitalDetail = async ()=>{
let {bookingRule,hospital} = await getHospitalDetail(id as string);
setHospital(hospital);
setBookingRule(bookingRule);
}
useEffect(()=>{
id && _getHospitalDetail();
}, [])
return (
<Card>
<Descriptions title="基本信息" bordered>
<Descriptions.Item labelStyle={{ width: 200 }} label="医院名称" span={1.5}>{hospital?.hosname}</Descriptions.Item>
<Descriptions.Item label="医院logo" span={1.5}>
{hospital?.logoData && <Image width={100} src={'data:image/jpg;base64,' + hospital?.logoData} />}
</Descriptions.Item>
<Descriptions.Item label="医院编码" span={1.5}>{hospital?.hoscode}</Descriptions.Item>
<Descriptions.Item label="医院地址" span={1.5}>{hospital?.param.fullAddress}</Descriptions.Item>
<Descriptions.Item label="坐车路线" span={3}>
{hospital?.route}
</Descriptions.Item>
<Descriptions.Item label="医院简介" span={3}>
{hospital?.intro}
</Descriptions.Item>
</Descriptions>
<Descriptions title="预约规则" bordered className='mt'>
<Descriptions.Item label="预约周期" span={1.5}>{bookingRule?.cycle}</Descriptions.Item>
<Descriptions.Item label="放号时间" span={1.5}>{bookingRule?.releaseTime}</Descriptions.Item>
<Descriptions.Item label="停挂时间" span={1.5}>{bookingRule?.quitDay}</Descriptions.Item>
<Descriptions.Item label="退号时间" span={1.5}>{bookingRule?.quitTime}</Descriptions.Item>
<Descriptions.Item label="预约规则" span={3}>
{bookingRule?.rule.map((item,index)=>(
<div key={index}>{item}</div>
))}
</Descriptions.Item>
</Descriptions>
<Button className='mt'>返回</Button>
</Card>
)
}
医院上下线管理
src/pages/hospital/hospitalList/hospitalList.tsx
<Space>
<Button type='primary' onClick={()=>{navigate('/syt/hospital/hospitalList/show/' + row.id)}}>查看</Button>
<Button type='primary'>排班</Button>
<Button type='primary' onClick={()=>updateStatus(row.id, row.status ? 0: 1)}>{row.status ? '下线' : '上线'}</Button>
</Space>
// 改变医院状态函数
const updateStatus = async (id:string, status:number)=>{
await changeStatus(id, status);
// 重新获取列表
_getHospitalList();
}
封装医院详情请求api:/src/api/hospital/hospitalList.ts
// 调整医院上下线
export const changeStatus = (id:string, status:number)=>{
return request.get<any,null>(`/admin/hosp/hospital/updateStatus/${id}/${status}`);
}
排班功能
定义路由页面:src/pages/hospital/hospitalList/components/hospitalSchedule.tsx
import { Button, Card, Col, Pagination, Row, Table, Tag, Tree } from 'antd'
import React from 'react'
export default function HospitalSchedule() {
return (
<Card>
123
</Card>
)
}
配置路由:src/route/index.ts
const HospitalSchedule = lazy(() => import("@/pages/hospital/hospitalList/components/HospitalSchedule"));
{
path: '/syt/hospital/hospitalList/schedule/:hoscode',
meta: {
title: '医院排班'
},
hidden: true, // 隐藏菜单栏
element: load(HospitalSchedule)
}
绑定事件跳转:src/pages/hospital/hospitalList/hospitalList.tsx
<Button type='primary' onClick={()=>{navigate('/syt/hospital/hospitalList/schedule/' + row.hoscode)}}>排班</Button>
定义类型:src/api/hospital/model/hospitalListType.ts
export interface IDepartmentItem {
depcode: string;
depname: string;
children: IDepartmentList | null;
disabled?:boolean;
}
//科室列表类型
export type IDepartmentList = IDepartmentItem[];
封装api:src/api/hospital/hospitalList.ts
// 根据医院编号 hoscode 获取医院科室列表
export const getDepartmentList = (hoscode:string)=>{
return request.get<any, IDepartmentList>(`/admin/hosp/department/${hoscode}`)
}
静态页面布局 src/pages/hospital/hospitalList/components/hospitalSchedule.tsx
import { Button, Card, Col, Pagination, Row, Table, Tag, Tree } from 'antd'
import { ColumnsType } from 'antd/lib/table'
import React from 'react'
// 获取视口的高度
let height = document.documentElement.clientHeight - 180
export default function HospitalSchedule() {
// 列数据
const columns: ColumnsType<any> = [
{
title: '序号'
},
{
title: '职称'
},
{
title: '号源时间'
},
{
title: '总预约数'
},
{
title: '剩余预约数'
},
{
title: '挂号费(元)'
},
{
title: '擅长技能'
}
]
return (
<Card>
<div>选择:北京人民医院 / 多发性硬化专科门诊 / 2023-07-28</div>
{/* gutter 栅格间隙 这里组件来自 antd中的栅格组件 */}
<Row className='mt' gutter={30}>
<Col span={5}>
<div style={{ border: '1px solid #ddd', height, overflowY: 'scroll' }}>
<Tree
// // onSelect={onSelect}
// // onCheck={onCheck}
// treeData={departmentList as []}
// fieldNames={{
// title:'depname',
// key:'depcode'
// }}
// expandedKeys={expandedKeys}
// selectedKeys={[depcode as string]}
/>
</div>
</Col>
<Col span={19}>
<Tag color="green">
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Tag>
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Tag>
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Pagination
defaultCurrent={6}
total={500}
className='mt'
/>
<Table
className='mt'
pagination={false}
columns={columns}
/>
<Button className='mt'>返回</Button>
</Col>
</Row>
</Card>
)
}
获取数据渲染页面 src/pages/hospital/hospitalList/components/hospitalSchedule.tsx
import { getDepartmentList } from '@/api/hospital/hospitalList'
import { IDepartmentList } from '@/api/hospital/model/hospitalListType'
import { Button, Card, Col, Pagination, Row, Table, Tag, Tree } from 'antd'
import { ColumnsType } from 'antd/lib/table'
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
// 获取视口的高度
let height = document.documentElement.clientHeight - 180
export default function HospitalSchedule() {
// 列数据
const columns: ColumnsType<any> = [
{
title: '序号'
},
{
title: '职称'
},
{
title: '号源时间'
},
{
title: '总预约数'
},
{
title: '剩余预约数'
},
{
title: '挂号费(元)'
},
{
title: '擅长技能'
}
]
// 获取参数
const {hoscode} = useParams()
let [departmentList, setDepartmentList] = useState<IDepartmentList>([]);
let [expandedKeys, setExpandedKeys] = useState<string[]>([]);// 一级科室depcode组成的数组
let [depname, setDepname] = useState<string>();
let [depcode, setDepcode] = useState<string>();
// 方法
// 获取排班列表数据
const _getDepartmentList = async()=>{
let departmentList = await getDepartmentList(hoscode as string);
// console.log(departmentList)
// 禁用一级科室树节点,就是给所有的一级科室对象,添加一个disabled:true
departmentList = departmentList.map(item=>{
item.disabled = true;
return item;
})
setDepartmentList(departmentList);// 设置科室列表状态数据
// 展开所有的一级科室
// 1. 获取所有一级科室 depcode组成的数组
let expandedKeys = departmentList.map(item=>item.depcode)
setExpandedKeys(expandedKeys);
// 2. 处理默认选中科室
let depname = (departmentList[0].children as IDepartmentList)[0].depname;
let depcode = (departmentList[0].children as IDepartmentList)[0].depcode;
// 设置科室名和科室code状态
setDepname(depname);
setDepcode(depcode);
}
// 生命周期
useEffect(()=>{
hoscode && _getDepartmentList();
}, [])
return (
<Card>
<div>选择:北京人民医院 / 多发性硬化专科门诊 / 2023-07-28</div>
{/* gutter 栅格间隙 这里组件来自 antd中的栅格组件 */}
<Row className='mt' gutter={30}>
<Col span={5}>
<div style={{ border: '1px solid #ddd', height, overflowY: 'scroll' }}>
<Tree
// 设置数据源
treeData={departmentList as []}
// 自定义节点 title、key、children 的字段
fieldNames={{
title:'depname',
key:'depcode'
}}
// (受控)展开指定的树节点
expandedKeys={expandedKeys}
// (受控)设置选中的树节点
selectedKeys={[depcode as string]}
/>
</div>
</Col>
<Col span={19}>
<Tag color="green">
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Tag>
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Tag>
<div>2023-07-28 周五</div>
<div>38 / 100</div>
</Tag>
<Pagination
defaultCurrent={6}
total={500}
className='mt'
/>
<Table
className='mt'
pagination={false}
columns={columns}
/>
<Button className='mt'>返回</Button>
</Col>
</Row>
</Card>
)
}
点击切换科室
在科室的页面绑定事件 src/pages/hospital/hospitalList/components/hospitalSchedule.tsx
<Tree
// 设置数据源
treeData={departmentList as []}
// 自定义节点 title、key、children 的字段
fieldNames={{
title:'depname',
key:'depcode'
}}
// (受控)展开指定的树节点
expandedKeys={expandedKeys}
// (受控)设置选中的树节点
selectedKeys={[depcode as string]}
onSelect={(selectedKeys: any,info: any)=>{ // a 是hoscode b是事件对象event
setDepcode(info.node.depcode);
setDepname(info.node.depname);
}}
/>
排班日期分页
定义数据类型:src/api/hospital/model/hospitalListType.ts
// 请求排班日期分页数据响应结果的类型
export interface IScheduleResponse {
total: number;
bookingScheduleList: IBookingScheduleList;
baseMap: {
hosname: string;
}
}
// 排班日期每一项的类型
export interface IBookingScheduleItem {
workDate: string;// 排班日期
dayOfWeek: string;//星期几
docCount: number;// 已预约人数
reservedNumber: number;//总预约数
availableNumber: number;// 剩余预约数
}
// 排班日期列表类型
export type IBookingScheduleList = IBookingScheduleItem[]
封装请求:src/api/hospital/hospitalList.ts
// 获取医院科室排班日期分页列表数据
export const getScheduleList = (page: number, limit: number, hoscode: string, depcode: string) => {
return request.get<any, IScheduleResponse>(`/admin/hosp/schedule/getScheduleRule/${page}/${limit}/${hoscode}/${depcode}`)
}
src/pages/hospital/hospitalList/components/HospitalSchedule.tsx
let [current, setCurrent] = useState<number>(1);
let [pageSize, setPageSize] = useState<number>(3);
let [total, setTotal] = useState<number>(10);
let [bookingScheduleList, setBookingScheduleList] = useState<IBookingScheduleList>([]);
let [hosname, setHosname] = useState<string>();
let [workDate, setWorkDate] = useState<string>();
//获取医院科室 排班日期数据
const _getScheduleList = async () => {
let { total, bookingScheduleList, baseMap: { hosname } } = await getScheduleList(current, pageSize, hoscode as string, depcode as string);
setTotal(total)
setBookingScheduleList(bookingScheduleList)
setHosname(hosname)
// 设置排班日期状态
setWorkDate(bookingScheduleList[0].workDate);
}
useEffect(() => {
depcode && _getScheduleList();// 获取排班日期分页列表数据
}, [depcode, current, pageSize]);
<Col span={19}>
{bookingScheduleList.map((item,index)=>(
<Tag
color={workDate===item.workDate ? 'green':''}
key={item.workDate}
onClick={()=>{setWorkDate(item.workDate)}}
>
<div>{item.workDate} {item.dayOfWeek}</div>
<div>{item.availableNumber} / {item.reservedNumber}</div>
</Tag>
))}
<Pagination
current={current}
total={total}
className='mt'
pageSize={pageSize}
onChange={(page:number, pageSize)=>{
setCurrent(page);
setPageSize(pageSize);
}}
/>
<Table
className='mt'
pagination={false}
columns={columns}
/>
<Button className='mt'>返回</Button>
</Col>
排班医生
定义数据类型:src/api/hospital/model/hospitalListType.ts
/**
* 排班医生对象类型
*/
export interface IDoctorItem {
id: string;
createTime: string;
param: {
dayOfWeek: string;
depname: string;
hosname:string;
},
hoscode:string;
depcode: string;
title: string;
skill: string;
workDate: string;
reservedNumber: number;
availableNumber: number;
amount: number;
status: number;
}
//排班医生列表类型
export type IDoctorList = IDoctorItem[];
封装请求:src/api/hospital/hospitalList.ts
// 获取排班医生列表
export const getDoctorList = (hoscode: string, depcode: string, workDate: string) => {
return request.get<any,IDoctorList>(`/admin/hosp/schedule/findScheduleList/${hoscode}/${depcode}/${workDate}`);
}
数据渲染:src/pages/hospital/hospitalList/components/HospitalSchedule.tsx
const columns: ColumnsType<any> = [
{
title: '序号',
render(value:any, row:any, index:number){
return (index + 1);
}
},
{
title: '职称',
dataIndex:'title'
},
{
title: '号源时间',
width:120,
dataIndex:'workDate'
},
{
title: '总预约数',
dataIndex:'reservedNumber'
},
{
title: '剩余预约数',
dataIndex:'availableNumber'
},
{
title: '挂号费(元)',
dataIndex:'amount'
},
{
title: '擅长技能',
dataIndex:'skill'
}
]
// 医生列表
let [doctorList, setDoctorList] = useState<IDoctorList>([]);
// 获取排班医生列表数据
const _getDoctorList = async ()=>{
let doctorList = await getDoctorList(hoscode as string, depcode as string, workDate as string);
// console.log('doctorList: ', doctorList);
setDoctorList(doctorList);
}
useEffect(()=>{// 组件挂载完成之后执行 + workDate变化后执行
workDate && _getDoctorList();
}, [workDate]);
<Table
className='mt'
pagination={false}
columns={columns}
dataSource={doctorList}
rowKey={'id'}
/>
数据字典
创建数据字典路由
创建数据字典组件:src/pages/cmn/dict/Dict.tsx
import { Card } from 'antd'
import Table, { ColumnsType } from 'antd/lib/table'
import React from 'react'
export default function Dict() {
const columns:ColumnsType<any> = [
{
title:'名称'
},
{
title:'编码'
},
{
title:'值'
},
{
title:'创建时间'
}
]
return (
<Card>
<Table
columns={columns}
pagination={false}
/>
</Card>
)
}
配置路由:src/route/index.ts
{
path:'/syt/cmn/',
meta:{
"title":"数据管理",
"icon":<UnorderedListOutlined />
},
children:[
{
path:"/syt/cmn/dict",
meta:{
"title":"数据字典"
},
element:load(Dict)
}
]
}
获取数据并渲染
创建数据字典组件:src/pages/cmn/dict/Dict.tsx
import { getDistrictList } from '@/api/hospital/hospitalList';
import { IDistrictList } from '@/api/hospital/model/hospitalListType'
import { RightOutlined, DownOutlined } from '@ant-design/icons'
import { Card } from 'antd'
import Table, { ColumnsType } from 'antd/lib/table'
import React, { useEffect, useState } from 'react'
export default function Dict() {
const columns:ColumnsType<any> = [
{
title:'名称',
dataIndex: 'name'
},
{
title:'编码',
dataIndex: 'dictCode'
},
{
title:'值',
dataIndex: 'value'
},
{
title:'创建时间',
dataIndex: 'createTime'
}
]
// 定义数据类型
let [dictList, setDictList] = useState<IDistrictList>([]);
// 获取省市区方法
const _getDictList = async () => {
let dictList = await getDistrictList(1);
setDictList(dictList);
}
// 生命周期函数
useEffect(() => {
_getDictList();
}, [])
return (
<Card>
<Table
rowKey={'id'}
columns={columns}
pagination={false}
dataSource={dictList}
expandable={{
expandIcon: ({ expanded, onExpand, record }) => {
// console.log('expanded: ', expanded);// 展开标识 boolean
// console.log('onExpand: ', onExpand);// 展开函数
// console.log('record: ', record);// 当前行对象
// 首先判断当前行对象中是否有子节点
if(!record.hasChildren){
// 返回一个空标签
return <div style={{display:'inline-block',width:15}}></div>
}
return expanded ? (
<DownOutlined onClick={e => onExpand(record, e)} />
) : (
<RightOutlined onClick={async e => {
// 获取当前行记录的子节点,加入record,进行展开
// 请求子节点的数据
if (!record.children?.length) {// record没有children属性或者record.children 数组的长度是 0,匹配逻辑判断
let children = await getDistrictList(record.id);
// 把这些子节点的信息添加到record对象children属性,就可以使用onExpand展开
record.children = children;
}
onExpand(record, e)
}} />
)
}
}}
/>
</Card>
)
}
打包上线部署
1. 构建最终目录: npm run build ===> build
2. 部署需要解决的问题:
1. 404 ==》 index.html
2. 配置静态资源目录
3. 设置代理服务器完成跨域
使用express配置http服务
const express = require('express');
const history = require('connect-history-api-fallback');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 0. 所有404的相应,使用 index.html替换
app.use(history());
// 1. 配置当前目录为静态资源目录
app.use(express.static('./'));
// 2. 进行跨域配置
app.use('/prod-api', createProxyMiddleware(
{
target: "http://syt-api.atguigu.cn", // 目标服务器地址
changeOrigin: true, // 允许跨域
pathRewrite: {
// 路径重写
"^/prod-api": "",
},
}
));
app.listen(5000, () => {
console.log('server run at 5000');
})
前端发送请求过程
经验总结:
未来三年
学习:
找工作:跟编码水平无关、跟记忆力、脸皮、韧性、心态关系重大
工作前三个月:这里是带薪实训基地【60窝囊废 + 40 技术】
三个月:
1. 行业经验:
2. 真实的项目经验:
3. 编码能力,同行同事
半年后
1. 学历:本科 买一个 学信网可查的 成人教育 2-3年 2. 3 ==》 1
2. 年终奖: