尚医通是一个网上预约挂号系统,包含后台管理系统和前台用户系统,采用前后端分离开发模式。
尚医通是采用Vue3全家桶、TypeScript、Vite、Pinia、Element-plus等技术栈开发的在线医疗服务平台,集成了多家医院的挂号信息,提供全程跟踪服务,用户可以随时了解自己的挂号状态。
前端整体功能,已经学习完了,功能均已实现,在这里记录一下技术重点.....
一、接口
服务器地址:syt.atguigu.cn
医院接口:http://139.198.34.216:8201/swagger-ui.html
公共数据接口: http://139.198.34.216:8202/swagger-ui.html
会员接口:http://139.198.34.216:8203/swagger-ui.html
短信验证码接口:http://139.198.34.216:8204/swagger-ui.html
订单接口:http://139.198.34.216:8206/swagger-ui.html
文件上传接口:http://139.198.34.216:8205/swagger-ui.html
后台用户接口:http://139.198.34.216:8212/swagger-ui.html
二、项目其他配置
2.1 浏览器自动打开 找到 package.json 配置文件!
"scripts": {
"dev": "vite --open",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
2.2src 别名的配置
找到 vite.config.ts 配置文件。 如果红色语法提示请安装@types/node 是 TypeScript 的一个声明文件包,用于描述 Node.js 核心模块和常用的第三方库的类型信息
安装这份
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
}
})
找到 tsconfig.json 配置文件,找到配置项 compilerOptions 添加配置,这一步的作用是让 IDE 可以对路径进行智能提示
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
//微信开放平台官网地址 open.weixin.qq.com
//查看微信扫码登录文档 mp.weixin.qq.com/
项目当中全部路由:、 用户未登录可以访问的路由 /home /hospital/register /hospital/detail /hospital/notice /hospital/close / hospital/search 剩下其余的路由未登录不能访问的,如果用户登录了全部的路由都是可以访问的
引入清除默认样式
三、顶部、底部静态页面搭建
页面展示
App.vue
<template>
<div class="container">
<!-- 顶部全局组件 -->
<HospitalTop />
<!-- 展示路由组件的区域 -->
<div class="content">展示路由组件的区域</div>
<!-- 底部全局组件 -->
<HospitalBottom />
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.container {
display: flex;
/* 切换主轴方向 */
flex-direction: column;
/* justify-content: center; */
align-items: center;
.content {
margin-top: 70px;
width: 1200px;
min-height: 700px;
}
}
</style>
hospital_top.vue
<template>
<div class="top">
<div class="content">
<!-- 左侧 -->
<div class="left">
<img src="../../assets/images/logo.png" alt="" />
<p>尚医通 预约挂号统一平台</p>
</div>
<!-- 右侧 -->
<div class="right">
<div class="help">帮助中心</div>
<div class="login">登录/注册</div>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.top {
/* 固定到顶部 */
position: fixed;
/* 层级 */
z-index: 999;
width: 100%;
height: 70px;
background: #fff;
display: flex;
/* 主轴上居中 */
justify-content: center;
.content {
width: 1200px;
height: 70px;
background: white;
display: flex;
justify-content: space-between;
.left {
display: flex;
/* 主轴侧轴都居中 */
justify-content: center;
align-items: center;
img {
width: 50px;
height: 50px;
margin-right: 10px;
}
p {
font-size: 20px;
color: #55a6fe;
}
}
.right {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #bbb;
.help {
margin-right: 10px;
}
}
}
}
</style>
hospital_bottom.vue
<template>
<div class="bottom">
<div class="contont">
<div class="left">蜀ICP备2021007923号</div>
<div class="right">
<span> 联系我们 </span>
<span>合作伙伴</span>
<span>隐私协议</span>
<span>用户协议</span>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.bottom {
width: 100%;
height: 50px;
background-color: #f0f2f5;
display: flex;
justify-content: center;
.contont {
width: 1200px;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
.right {
span {
margin: 0px 5px;
}
}
}
}
</style>
四、路由配置
1.1目录
1.2路由index文件
import { createRouter, createWebHistory } from "vue-router";
// createApp方法,用于创建路由器实例,可以管理多个路由
export default createRouter({
// 路由模式的设置
history: createWebHistory(),
// 管理路由
routes: [
{
path: "/home",
// 路由懒加载模式
component: () => import("@/pages/home/index.vue"),
},
{
path: "/hospital",
component: () => import("@/pages/hospital/index.vue"),
},
{
path: "/",
// 路由重定向到home
redirect: "/home",
},
],
// 滚动行为:控制滚动条的位置
scrollBehavior() {
return {
left: 0,
top: 0,
};
},
});
1.3 路由出口
五、首页静态搭建banner
element-plus 的使用
搜索组件
效果图
六、首页等级与地区组件拆分与静态搭建
level/index.vue
<template>
<div class="level">
<h1>医院</h1>
<div class="content">
<div class="left">等级:</div>
<ul class="hospital">
<li class="active">全部</li>
<li>三级甲等</li>
<li>三级甲等</li>
<li>三级甲等</li>
<li>三级甲等</li>
<li>三级甲等</li>
<li>三级甲等</li>
<li>三级甲等</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.level {
color: #7f7f7f;
h1 {
font-weight: 900;
margin: 10px 0px;
}
.content {
display: flex;
.left {
margin-right: 10px;
}
.hospital {
display: flex;
li {
margin-right: 10px;
&.active {
color: #55a6fe;
}
}
/* 鼠标进经过时变色 */
li:hover {
color: #55a6fe;
/* 鼠标经过时出现小手 */
cursor: pointer;
}
}
}
}
</style>
region/index.vue
<template>
<div class="region">
<div class="content">
<div class="left">地区:</div>
<ul>
<li class="active">全部</li>
<li>昌平区</li>
<li>昌平区</li>
<li>昌平区</li>
<li v-for="item in 20" :key="item">昌平区</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.region {
color: #7f7f7f;
margin-top: 10px;
.content {
display: flex;
.left {
margin-right: 10px;
width: 50px;
}
ul {
display: flex;
flex-wrap: wrap;
li {
margin-right: 10px;
margin-bottom: 10px;
&.active {
color: #55a6fe;
}
}
li:hover {
color: #55a6fe;
/* 鼠标经过时出现小手 */
cursor: pointer;
}
}
}
}
</style>
七、分页搭建
八、网络axios二次封装
utils/request.ts
// 对于axios 函数库进行二次封装
// 你工作的时候是否axios进行二次封装?二次封装的目的是什么?
// 目的1:利用axios请求、响应拦截器功能
// 目的2:请求拦截器,一般可以在请求头中携带公共的参数:token
// 目的3:响应拦截器,可以简化服务器返回的数据,处理http网络错误
import axios from "axios";
import { ElMessage } from "element-plus";
// 利用axios.create方法创建一个axios实例:可以设置基础路径、超时的时间的设置
const request = axios.create({
baseURL: "/api", //请求的基础路径的设置
timeout: 5000, //超时时间的设置,超出5秒请求就失败
});
// 请求拦截器
request.interceptors.request.use((config) => {
// config :请求拦截器回调注入的对象(配置对象),p配置对象的身上最重要的一件事情headers属性
// k可以通过请求头携带公共参数-token
return config;
});
// 响应拦截器
request.interceptors.response.use(
(response) => {
// 响应拦截器成功地回调,一般会进行简化
return response;
},
(error) => {
// 处理http网络错误
let status = error.response.status;
switch (status) {
case 404:
// 错误提示信息
ElMessage({
type: "error",
// message: error.message,
message: "请求失败路径,出现问题",
});
break;
case 401:
// 错误提示信息
ElMessage({
type: "error",
// message: error.message,
message: "参数有误",
});
break;
case 500 | 501 | 502 | 503 | 504 | 505:
// 错误提示信息
ElMessage({
type: "error",
// message: error.message,
message: "服务器挂了",
});
break;
}
return Promise.reject(new Error(error.message));
}
);
// 务必对外暴露axios
export default request;
九、配置代理跨域
十、分页及获取数据的实现
10.1 获取数据接口配置
// 统一管理首页模块接口
import request from "@/utils/request";
// 通过枚举管理首页模块的接口地址
enum API {
// 获取已有的医院的是数据接口地址
HOSPITAL_URL = "/hosp/hospital/",
}
// 获取医院的数据
export const reqHospital = (page: number, limit: number) =>
request.get(API.HOSPITAL_URL + `${page}/${limit}`);
10.2 获取首页数据
10. 分页功能
十一、等级和区域展示
11.1等级及高亮展示
接口类型
11.2获取等级数据
11.3获取医院地区数据
高亮显示
11.4首页根据等级与地区筛选医院展示
给组件绑定事件
暂无数据
十二、首页根据关键字搜索医院及路由简单跳转
接口ts类型
数据接口
绑定事件
当用户搜索关键字时,会出现推荐数据项
因为官方文档中写的是需要返回value值,所以就弄了一个showData函数,让其返回一个对象(对象的话就会有吧value值)
路由跳转三步走
十三、首页常见科室搭建
十四、医院详情菜单与子路由
建五个文件
路由菜单
点击这些组件就会进行跳转
点击搜索框的推荐数据,也会进行跳转到详情页
十五、Pinia仓库存储医院详情数据
pnpm i pinia@2.1.3(注意不要安装最新版,否则会有冲突报错)
拿数据->取数据->存数据
十六、预约挂号业务1
base64码转图片
data:image/jpeg;base64,
<img :src="`data:image/jpeg;base64,`+hospitalStore.hospitalInfo.hospital?.logoData" alt=""/>
16.2、医院详情业务
<template>
<div class="detail">
<!-- 医院名字与等级 -->
<div class="top">
<div class="name">{{ hospitalStore.hospitalInfo.hospital?.hosname }}</div>
<div class="level">
<svg
t="1688813652495"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9729"
width="16"
height="16"
>
<path
d="M939.358251 423.424662c-23.01825-37.252439-62.924121-60.779272-107.019409-63.209624-2.755764-0.38681-5.510504-0.579191-8.347109-0.579191l-152.202471-0.121773c6.649444-28.975938 10.015098-58.653865 10.015098-88.657202 0-28.223808-3.213181-57.139372-9.556657-85.952604-0.447185-2.043542-1.098008-4.006244-1.932002-5.866614-15.820314-57.302077-67.37755-96.841605-127.282918-96.841605-72.827679 0-132.081201 59.254545-132.081201 132.081201 0 3.334955 0.132006 6.66991 0.406253 10.015098-2.196015 57.211003-32.108279 109.947088-80.269162 141.363611-14.447037 9.42465-18.524912 28.773324-9.099239 43.220361 9.414417 14.437827 28.752858 18.535145 43.220361 9.099239 65.811892-42.925648 106.429984-115.325585 108.656699-193.684234 0.030699-1.332345-0.010233-2.663666-0.14224-3.996011-0.203638-2.012843-0.304945-4.016477-0.304945-6.019087 0-38.381146 31.233352-69.614497 69.614497-69.614497 32.57593 0 60.474326 22.204721 67.824735 53.997821 0.356111 1.534959 0.823761 3.019777 1.402953 4.453429 4.696975 22.814612 7.076162 45.579081 7.076162 67.743894 0 37.485753-6.222725 74.352405-18.494213 109.592001-3.324722 9.546424-1.819438 20.111037 4.02671 28.345582 5.856381 8.245801 15.332197 13.146415 25.448602 13.156648l193.226816 0.101307c1.423419 0.264013 2.857071 0.426719 4.300956 0.477884 24.116257 0.9967 45.935192 13.614066 58.603723 34.090423 7.838525 12.31242 11.438517 26.800389 10.431583 41.939181-0.080841 0.945535-0.121773 1.911536-0.11154 2.877537 0 0.854461 0.040932 1.697665 0.11154 2.53166 0.010233 0.335644-0.030699 0.661056-0.11154 0.976234-0.101307 0.376577-0.193405 0.772596-0.284479 1.159406l-74.972529 330.391802c-0.914836 1.281179-1.738597 2.6432-2.449795 4.046153-5.937223 11.762905-14.660908 21.48329-25.346271 28.172643-10.746762 6.812149-23.059182 10.614755-35.757388 11.06194-0.854461-0.061398-513.766226-0.224104-513.766226-0.224104-0.467651-0.020466-0.935302-0.030699-1.402953-0.030699 0 0-111.01542 0.172939-112.718201 0.457418-1.932002 0-3.446495-1.50426-3.446495-3.415796l0.299829-416.334173c0-1.901303 1.545192-3.446495 3.01466-3.456728l1.245364 0.121773c1.174756 0.132006 2.653433 0.284479 3.52836 0.193405l83.82822-0.222057 0 339.367221c0 17.253966 13.979386 31.233352 31.233352 31.233352s31.233352-13.979386 31.233352-31.233352L281.009092 435.350273c0-1.778506 0-8.631588 0-10.411117 0-17.253966-13.979386-30.928407-31.233352-30.928407-1.50426 0-117.547183 0.304945-119.402437 0.304945-36.34272 0-65.913199 29.566386-65.913199 65.893756l-0.299829 416.334173c0 36.337603 29.571503 65.902966 65.913199 65.902966 2.541893 0 111.406323-0.457418 111.406323-0.457418 0.457418 0.020466 0.925069 0.030699 1.382487 0.030699 0 0 511.458671 0.274246 512.505513 0.274246 25.469068 0 50.296523-7.197936 71.647807-20.73116 19.612687-12.281721 35.777855-29.881564 46.839795-50.967812 3.660366-5.622044 6.435573-11.875468 8.256034-18.615986 0.11154-0.416486 0.213871-0.823761 0.304945-1.240247l74.881454-330.340637c1.596358-6.212492 2.257413-12.586666 2.00261-18.992563C960.892707 473.366098 953.948551 446.331371 939.358251 423.424662z"
fill="#d81e06"
p-id="9730"
></path>
<path
d="M450.027553 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C481.260905 532.629853 467.281519 518.650467 450.027553 518.650467z"
fill="#d81e06"
p-id="9731"
></path>
<path
d="M693.805696 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C725.039048 532.629853 711.058638 518.650467 693.805696 518.650467z"
fill="#d81e06"
p-id="9732"
></path>
<path
d="M648.940882 660.09492c-14.304797-9.393951-33.592073-5.398964-43.159986 8.763594-0.132006 0.193405-13.614066 19.754926-34.171264 19.754926-19.98824 0-32.423457-18.09717-33.266661-19.368116-9.17087-14.427594-28.254507-18.809391-42.834574-9.770528-14.650675 9.109472-19.155269 28.366048-10.055007 43.016723 11.214413 18.047028 41.96988 48.588625 86.156242 48.588625 43.962258 0 75.104535-30.318516 86.572728-48.222281C667.404396 688.461991 663.216004 669.500127 648.940882 660.09492z"
fill="#d81e06"
p-id="9733"
></path>
</svg>
<span>{{
hospitalStore.hospitalInfo.hospital?.param.hostypeString
}}</span>
</div>
</div>
<!-- 医院logo与路线 -->
<div class="logo">
<div class="left">
<img
:src="
`data:image/jpeg;base64,` +
hospitalStore.hospitalInfo.hospital?.logoData
"
alt=""
/>
</div>
<div class="right">
<div class="address">
<svg
t="1688828004474"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="10870"
width="16"
height="16"
>
<path
d="M512 512a136.533333 136.533333 0 1 1 136.533333-136.533333 136.533333 136.533333 0 0 1-136.533333 136.533333z m0-219.272533a81.92 81.92 0 1 0 81.92 81.92 81.92 81.92 0 0 0-81.92-81.92z"
fill="#0073FF"
p-id="10871"
></path>
<path
d="M512 831.214933a27.306667 27.306667 0 0 1-19.2512-8.055466l-214.493867-214.357334a330.5472 330.5472 0 1 1 467.490134 0l-214.357334 214.357334a27.306667 27.306667 0 0 1-19.387733 8.055466z m0-732.091733a275.933867 275.933867 0 0 0-195.106133 471.04L512 765.269333l195.106133-195.106133A275.933867 275.933867 0 0 0 512 99.1232z"
fill="#0073FF"
p-id="10872"
></path>
<path
d="M514.321067 979.490133c-147.456 0-306.107733-37.000533-306.107734-118.3744 0-45.602133 51.746133-81.92 145.681067-102.4a27.306667 27.306667 0 1 1 11.605333 53.384534c-78.370133 17.066667-102.673067 41.915733-102.673066 49.015466 0 18.432 88.064 63.761067 251.4944 63.761067s251.4944-45.192533 251.4944-63.761067c0-7.3728-25.258667-32.768-106.496-49.834666a27.306667 27.306667 0 1 1 11.195733-53.384534c96.6656 20.343467 150.186667 56.9344 150.186667 103.2192-0.273067 80.964267-158.9248 118.3744-306.3808 118.3744z"
fill="#0073FF"
p-id="10873"
></path>
</svg>
<span
>具体位置:{{
hospitalStore.hospitalInfo.hospital?.param?.fullAddress
}}</span
>
</div>
<div class="route">
规划路线:{{ hospitalStore.hospitalInfo.hospital?.route }}
</div>
</div>
</div>
<!-- 医院的介绍 -->
<div class="footer">
<h1>医院介绍</h1>
<p>
{{ hospitalStore.hospitalInfo.hospital?.intro }}
</p>
</div>
</div>
</template>
<script setup lang="ts">
// 获取医院详情仓库数据进行展示
import useDetailStore from "@/store/modules/hospitalDetail";
let hospitalStore = useDetailStore();
</script>
<style scoped lang="scss">
.detail {
.top {
display: flex;
align-items: center;
.name {
font-size: 30px;
font-weight: 900;
}
.level {
color: #7f7f7f;
margin-left: 10px;
span {
margin-left: 5px;
}
}
}
.logo {
display: flex;
margin: 10px 0px;
.left {
img {
width: 80px;
height: 80px;
border-radius: 50px;
}
}
.right {
color: #7f7f7f;
margin-left: 10px;
div {
margin: 10px 0px;
}
}
}
.footer {
color: #7f7f7f;
p {
margin-top: 10px;
line-height: 30px;
}
}
}
</style>
16.3 小bug
第一次进去本来是能够显示的,而且带有编号,但是但切换菜单时,编号就消失了
16.4做科室时遇见的错误
错误原因:在箭头后面加对象符号{},但是却没有return 回去,导致打印出来的result是undefine。
解决办法:1、不加{},因为箭头函数如果只有一行表达式,可以不加括号,箭头函数自带return。 2、 或者在箭头后面加{return xxxxx},不要忘记return,不然会出现错误
16.5、医院科室业务
效果
左侧导航栏及高亮效果
右侧信息及点击哪个导航就定位到对应的科室
完整代码:
<template>
<div class="register">
<div class="top">
<div class="title">
{{ hospitalStore.hospitalInfo.hospital?.hosname }}
</div>
<div class="level">
<svg
t="1688813652495"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="9729"
width="16"
height="16"
>
<path
d="M939.358251 423.424662c-23.01825-37.252439-62.924121-60.779272-107.019409-63.209624-2.755764-0.38681-5.510504-0.579191-8.347109-0.579191l-152.202471-0.121773c6.649444-28.975938 10.015098-58.653865 10.015098-88.657202 0-28.223808-3.213181-57.139372-9.556657-85.952604-0.447185-2.043542-1.098008-4.006244-1.932002-5.866614-15.820314-57.302077-67.37755-96.841605-127.282918-96.841605-72.827679 0-132.081201 59.254545-132.081201 132.081201 0 3.334955 0.132006 6.66991 0.406253 10.015098-2.196015 57.211003-32.108279 109.947088-80.269162 141.363611-14.447037 9.42465-18.524912 28.773324-9.099239 43.220361 9.414417 14.437827 28.752858 18.535145 43.220361 9.099239 65.811892-42.925648 106.429984-115.325585 108.656699-193.684234 0.030699-1.332345-0.010233-2.663666-0.14224-3.996011-0.203638-2.012843-0.304945-4.016477-0.304945-6.019087 0-38.381146 31.233352-69.614497 69.614497-69.614497 32.57593 0 60.474326 22.204721 67.824735 53.997821 0.356111 1.534959 0.823761 3.019777 1.402953 4.453429 4.696975 22.814612 7.076162 45.579081 7.076162 67.743894 0 37.485753-6.222725 74.352405-18.494213 109.592001-3.324722 9.546424-1.819438 20.111037 4.02671 28.345582 5.856381 8.245801 15.332197 13.146415 25.448602 13.156648l193.226816 0.101307c1.423419 0.264013 2.857071 0.426719 4.300956 0.477884 24.116257 0.9967 45.935192 13.614066 58.603723 34.090423 7.838525 12.31242 11.438517 26.800389 10.431583 41.939181-0.080841 0.945535-0.121773 1.911536-0.11154 2.877537 0 0.854461 0.040932 1.697665 0.11154 2.53166 0.010233 0.335644-0.030699 0.661056-0.11154 0.976234-0.101307 0.376577-0.193405 0.772596-0.284479 1.159406l-74.972529 330.391802c-0.914836 1.281179-1.738597 2.6432-2.449795 4.046153-5.937223 11.762905-14.660908 21.48329-25.346271 28.172643-10.746762 6.812149-23.059182 10.614755-35.757388 11.06194-0.854461-0.061398-513.766226-0.224104-513.766226-0.224104-0.467651-0.020466-0.935302-0.030699-1.402953-0.030699 0 0-111.01542 0.172939-112.718201 0.457418-1.932002 0-3.446495-1.50426-3.446495-3.415796l0.299829-416.334173c0-1.901303 1.545192-3.446495 3.01466-3.456728l1.245364 0.121773c1.174756 0.132006 2.653433 0.284479 3.52836 0.193405l83.82822-0.222057 0 339.367221c0 17.253966 13.979386 31.233352 31.233352 31.233352s31.233352-13.979386 31.233352-31.233352L281.009092 435.350273c0-1.778506 0-8.631588 0-10.411117 0-17.253966-13.979386-30.928407-31.233352-30.928407-1.50426 0-117.547183 0.304945-119.402437 0.304945-36.34272 0-65.913199 29.566386-65.913199 65.893756l-0.299829 416.334173c0 36.337603 29.571503 65.902966 65.913199 65.902966 2.541893 0 111.406323-0.457418 111.406323-0.457418 0.457418 0.020466 0.925069 0.030699 1.382487 0.030699 0 0 511.458671 0.274246 512.505513 0.274246 25.469068 0 50.296523-7.197936 71.647807-20.73116 19.612687-12.281721 35.777855-29.881564 46.839795-50.967812 3.660366-5.622044 6.435573-11.875468 8.256034-18.615986 0.11154-0.416486 0.213871-0.823761 0.304945-1.240247l74.881454-330.340637c1.596358-6.212492 2.257413-12.586666 2.00261-18.992563C960.892707 473.366098 953.948551 446.331371 939.358251 423.424662z"
fill="#d81e06"
p-id="9730"
></path>
<path
d="M450.027553 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C481.260905 532.629853 467.281519 518.650467 450.027553 518.650467z"
fill="#d81e06"
p-id="9731"
></path>
<path
d="M693.805696 518.650467c-17.253966 0-31.233352 13.979386-31.233352 31.233352l0 30.470989c0 17.253966 13.979386 31.233352 31.233352 31.233352 17.253966 0 31.233352-13.979386 31.233352-31.233352l0-30.470989C725.039048 532.629853 711.058638 518.650467 693.805696 518.650467z"
fill="#d81e06"
p-id="9732"
></path>
<path
d="M648.940882 660.09492c-14.304797-9.393951-33.592073-5.398964-43.159986 8.763594-0.132006 0.193405-13.614066 19.754926-34.171264 19.754926-19.98824 0-32.423457-18.09717-33.266661-19.368116-9.17087-14.427594-28.254507-18.809391-42.834574-9.770528-14.650675 9.109472-19.155269 28.366048-10.055007 43.016723 11.214413 18.047028 41.96988 48.588625 86.156242 48.588625 43.962258 0 75.104535-30.318516 86.572728-48.222281C667.404396 688.461991 663.216004 669.500127 648.940882 660.09492z"
fill="#d81e06"
p-id="9733"
></path>
</svg>
<span>{{
hospitalStore.hospitalInfo.hospital?.param.hostypeString
}}</span>
</div>
</div>
<!-- 展示内容的区域 -->
<div class="content">
<div class="left">
<img
:src="
`data:image/jpeg;base64,` +
hospitalStore.hospitalInfo.hospital?.logoData
"
alt=""
/>
</div>
<div class="right">
<div>挂号规则</div>
<div class="time">
预约周期:10天 放号时间:{{
hospitalStore.hospitalInfo.bookingRule?.releaseTime
}}
停挂时间:{{ hospitalStore.hospitalInfo.bookingRule?.stopTime }}
</div>
<div class="address">
具体位置:{{ hospitalStore.hospitalInfo.hospital?.param.fullAddress }}
</div>
<div class="route">
规划路线:{{ hospitalStore.hospitalInfo.hospital?.route }}
</div>
<div class="releasetime">
退号时间:就诊前一工作日{{
hospitalStore.hospitalInfo.bookingRule?.quitTime
}}前取消
</div>
<div class="rule">预约挂号规则</div>
<div
class="ruleInfo"
v-for="(item, index) in hospitalStore.hospitalInfo.bookingRule?.rule"
:key="index"
>
{{ item }}
</div>
</div>
</div>
<!-- 放置每一个医院的科室数据 -->
<div class="deparment">
<div class="leftNav">
<ul>
<li
v-for="(deparment, index) in hospitalStore.deparmentArr"
:class="{ active: index == currentIndex }"
@click="changeIndex(index)"
:key="deparment.depcode"
>
{{ deparment.depname }}
</li>
</ul>
</div>
<div class="deparmentInfo">
<!-- 用一个div代表:大科室与小科室 -->
<div
class="showDeparment"
v-for="deparment in hospitalStore.deparmentArr"
:key="deparment.depcode"
>
<h1 class="cur">{{ deparment.depname }}</h1>
<!-- 每一个大的科室下小科室 -->
<ul>
<li v-for="item in deparment.children" :key="item.depcode">
{{ item.depname }}
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
// 引入医院详情仓库的数据
import useDetailStore from "@/store/modules/hospitalDetail";
let hospitalStore = useDetailStore();
// 控制科室高亮的响应式数据
let currentIndex = ref<number>(0);
// 左侧大的科室点击的事件
const changeIndex = (index: number) => {
currentIndex.value = index;
// 点击导航获取右侧科室(大的科室好标题)
let allH1 = document.querySelectorAll(".cur");
// console.log(allH1);
// 滚动到对应科室的位置
allH1[currentIndex.value].scrollIntoView({
behavior: "smooth",//过渡动画效果
block:'start',//滚动到的位置,默认是起始位置
});
};
</script>
<style scoped lang="scss">
.register {
.top {
display: flex;
.title {
font-size: 30px;
font-weight: 900;
}
.level {
color: #7f7f7f;
margin-left: 10px;
/* 文字居中 */
height: 40px;
text-align: center;
line-height: 40px;
span {
margin-left: 5px;
}
}
}
.content {
display: flex;
margin-top: 20px;
.left {
width: 80px;
img {
width: 80px;
height: 80px;
boder-radius: 50%;
}
}
.right {
/* 左边占了80px,剩下的空间给右边 */
flex: 1;
font-size: 14px;
margin-left: 20px;
.time,
.route,
.address,
.releasetime,
.ruleInfo {
margin-top: 10px;
color: #7f7f7f;
}
.rule {
margin: 10px 0;
}
}
}
.deparment {
width: 100%;
height: 500px;
display: flex;
margin-top: 20px;
/* background: pink; */
.leftNav {
width: 80px;
height: 100%;
ul {
width: 100%;
height: 100%;
background: rgb(248, 248, 248);
display: flex;
flex-direction: column;
li {
flex: 1;
text-align: center;
color: #7f7f7f;
font-size: 14px;
line-height: 30px;
/* 高亮效果 */
&.active {
border-left: 1px solid red;
color: red;
background: white;
}
}
}
}
.deparmentInfo {
/* 除了 leftNav占了80px,其他都是deparmentInfo*/
flex: 1;
margin-left: 20px;
height: 100%;
overflow: auto;
/* 清除滚动条 */
&::-webkit-scrollbar {
display: none;
}
.showDeparment {
h1 {
background: rgb(248, 248, 248);
color: #7f7f7f;
}
ul {
display: flex;
flex-wrap: wrap;
li {
width: 33%;
color: #7f7f7f;
line-height: 30px;
}
}
}
}
}
}
</style>
16.6、登录静态搭建
注册为全局组件main.ts
完整代码
<template>
<div class="login-container">
<!-- title:对话框左上角的标题 v-model :控制对话框的显示与隐藏 -->
<el-dialog v-model="userStore.visiable" title="用户登录">
<!-- 对话框身体部分结构 -->
<div class="content">
<el-row>
<!-- 左侧结构:收集号码登录、微信扫一扫登录 -->
<el-col :span="12">
<div class="login">
<div v-show="scene == 0">
<el-form>
<el-form-item>
<el-input
placeholder="请你输入手机号码"
:prefix-icon="User"
></el-input>
</el-form-item>
<el-form-item>
<el-input
placeholder="请你输入手机验证码"
:prefix-icon="Lock"
></el-input>
</el-form-item>
<el-form-item>
<el-button>获取验证码</el-button>
</el-form-item>
</el-form>
<el-button type="primary" style="width: 100%"
>用户登陆</el-button
>
<div class="bottom" @click="changeScene">
<p>微信扫码登录</p>
<svg
t="1688921974790"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="15061"
width="32"
height="32"
>
<path
d="M1023.162182 641.303273c0-143.243636-143.243636-260.910545-306.944-260.910546-168.820364 0-306.944 117.666909-306.944 260.910546s133.003636 260.910545 306.944 260.910545c35.816727 0 71.633455-10.24 107.426909-20.456727l97.210182 56.273454-25.576728-92.090181c71.633455-56.273455 127.883636-127.906909 127.883637-204.637091z m-404.130909-46.056728c-20.456727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727-0.023273 15.36-20.48 35.816727-46.056727 35.816727z m194.397091 0c-15.336727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727 0 15.36-15.36 35.816727-46.056727 35.816727z m0 0"
fill="#04AE0F"
p-id="15062"
></path>
<path
d="M358.097455 104.122182c-199.517091 0-358.120727 133.026909-358.120728 306.967273 0 97.210182 56.273455 179.060364 143.243637 240.453818L107.426909 758.970182l127.906909-61.393455c46.033455 10.24 81.850182 20.456727 127.906909 20.456728h35.816728c-5.12-25.576727-10.216727-51.153455-10.216728-76.730182 0-158.580364 133.003636-286.510545 306.944-286.510546h35.816728C690.641455 211.549091 537.157818 104.122182 358.097455 104.122182z m-112.54691 240.453818c-25.576727 0-56.273455-15.36-56.273454-46.056727 0-25.6 25.6-46.056727 56.273454-46.056728 25.576727 0 46.056727 15.36 46.056728 46.056728-5.12 30.696727-20.456727 46.056727-46.056728 46.056727z m250.693819 0c-25.576727 0-56.273455-15.36-56.273455-46.056727 0-25.6 25.576727-46.056727 56.273455-46.056728 25.576727 0 46.056727 15.36 46.056727 46.056728-0.023273 30.696727-20.48 46.056727-46.056727 46.056727z m0 0"
fill="#04AE0F"
p-id="15063"
></path>
</svg>
</div>
</div>
<div class="webchat" v-show="scene == 1">微信扫码登录</div>
</div>
</el-col>
<el-col :span="12">
<div class="leftContent">
<div class="top">
<div class="item">
<img src="../../assets/images/code_app.png" alt="" />
<svg
t="1688921974790"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="15061"
width="16"
height="16"
>
<path
d="M1023.162182 641.303273c0-143.243636-143.243636-260.910545-306.944-260.910546-168.820364 0-306.944 117.666909-306.944 260.910546s133.003636 260.910545 306.944 260.910545c35.816727 0 71.633455-10.24 107.426909-20.456727l97.210182 56.273454-25.576728-92.090181c71.633455-56.273455 127.883636-127.906909 127.883637-204.637091z m-404.130909-46.056728c-20.456727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727-0.023273 15.36-20.48 35.816727-46.056727 35.816727z m194.397091 0c-15.336727 0-35.816727-15.36-35.816728-35.816727 0-15.336727 20.456727-35.816727 35.816728-35.816727 25.576727 0 46.056727 15.36 46.056727 35.816727 0 15.36-15.36 35.816727-46.056727 35.816727z m0 0"
fill="#515151"
p-id="15062"
></path>
<path
d="M358.097455 104.122182c-199.517091 0-358.120727 133.026909-358.120728 306.967273 0 97.210182 56.273455 179.060364 143.243637 240.453818L107.426909 758.970182l127.906909-61.393455c46.033455 10.24 81.850182 20.456727 127.906909 20.456728h35.816728c-5.12-25.576727-10.216727-51.153455-10.216728-76.730182 0-158.580364 133.003636-286.510545 306.944-286.510546h35.816728C690.641455 211.549091 537.157818 104.122182 358.097455 104.122182z m-112.54691 240.453818c-25.576727 0-56.273455-15.36-56.273454-46.056727 0-25.6 25.6-46.056727 56.273454-46.056728 25.576727 0 46.056727 15.36 46.056728 46.056728-5.12 30.696727-20.456727 46.056727-46.056728 46.056727z m250.693819 0c-25.576727 0-56.273455-15.36-56.273455-46.056727 0-25.6 25.576727-46.056727 56.273455-46.056728 25.576727 0 46.056727 15.36 46.056727 46.056728-0.023273 30.696727-20.48 46.056727-46.056727 46.056727z m0 0"
fill="#515151"
p-id="15063"
></path>
</svg>
<p>微信扫一扫关注</p>
<p>“快速预约挂号”</p>
</div>
<div class="item">
<img src="../../assets/images/code_login_wechat.png" alt="" />
<svg
t="1688923491762"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="16931"
width="16"
height="16"
>
<path
d="M766.003899 974.097466H282.947368a47.906433 47.906433 0 0 1-47.906432-47.906433V83.836257a47.906433 47.906433 0 0 1 47.906432-47.906432h483.056531a47.906433 47.906433 0 0 1 47.906432 47.906432v842.354776a47.906433 47.906433 0 0 1-47.906432 47.906433z m-241.528265-57.88694a61.879142 61.879142 0 1 0-61.879143-61.879142 61.879142 61.879142 0 0 0 61.879143 61.879142z m202.604288-791.454191H321.871345v598.83041h405.208577V124.756335z"
fill="#E61D15"
p-id="16932"
></path>
</svg>
<p>扫一扫下载</p>
<p>“预约挂号”APP</p>
</div>
</div>
</div>
<p class="tip">尚医通官方指定平台</p>
<p class="tip">快速挂号 安全放心</p>
</el-col>
</el-row>
</div>
<template #footer>
<el-button type="primary" size="default">关闭窗口</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { User, Lock } from "@element-plus/icons-vue";
// 获取user仓库的数据visiable,可以控制login组件的对话框显示与隐藏
// import useUserStore from '@/store/mod'
import useUserStore from "@/store/modules/user";
import { ref } from "vue";
let userStore = useUserStore();
let scene = ref<number>(0); //0代表手机号码登录,1代表微信扫码登录
// 点击微信扫码登录|微信小图标切换为微信扫码登录
const changeScene = () => {
scene.value = 1;
};
</script>
<script lang="ts">
// 用vue2写个别名,方便vue插件寻找数据
export default {
name: "Login",
};
</script>
<style scoped lang="scss">
.login-container {
/* 要修改组件内部的样式,就要用深度选择器 */
:deep(.el-dialog__body) {
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.login {
padding: 20px;
border: 1px solid #ccc;
}
.bottom {
display: flex;
flex-direction: column;
align-items: center;
p {
margin: 10px 0px;
}
}
.leftContent {
.top {
display: flex;
justify-content: space-around;
.item {
display: flex;
flex-direction: column;
align-items: center;
img {
width: 130px;
height: 130px;
}
p {
margin: 5px 0px;
}
}
}
}
.tip {
text-align: center;
margin: 20px 0px;
font-size: 20px;
font-weight: 900;
}
}
</style>
16.7、登录组件获取验证码
接口设置
输入框验证规则
获取验证码的方法
放在仓库中
16.8获取验证码倒计时业务
将倒计时组件进行封装
用v-if v-else 判断展示哪个组件,当flag为真时,倒计时组件开启,否则关闭。
还有就是当你输入的手机号码不符合规定时或者当开启倒计时后,获取验证码的按钮将会被禁用
十七、登录业务功能
登录相关的ts类型
登录接口
登录请求
登录逻辑
发送登录请求
登录请求成功:顶部组件需要展示用户名字/对话框关闭
登录失败:弹出对应登录失败的错误信息
17.1、普通表单校验规则(一般不用)
17.2、自定义表单校验
17.3、关闭对话框时表单清空
方法二: 当对话框关闭时,直接销毁Login组件
APP.vuew
17.4、退出登录
清除本地存储和仓库存储的数据
17.5、微信扫码登录之二维码嵌套在login内部
17.6 微信扫码登录
十八、预约挂号
18.1 小错误
错误原因:workData刚开始有可能是空对象,所以{}.hosname 肯定报错undefined
修改:
加个?变成可选参数
18.1 页面展示
点击某个科室进入
接口:
接口类型
渲染页面
切换分页获取数据
完整代码
<template>
<div class="wrap">
<!-- 顶部结构 -->
<div class="top">
<div class="hosname">{{ workData.baseMap?.hosname }}</div>
<div class="line">|</div>
<div>{{ workData.baseMap?.bigname }}</div>
<div class="dot">.</div>
<div class="hosdeparment">{{ workData.baseMap?.depname }}</div>
</div>
<div class="center">
<h1 class="time">{{ workData.baseMap?.workDateString }}</h1>
<div class="container">
<div
class="item"
:class="{ active: item.status == -1 || item.availableNumber == -1 }"
v-for="item in workData.bookingScheduleList"
:key="item"
>
<div class="top2">{{ item.workDate }}-{{ item.dayOfWeek }}</div>
<div class="bottom">
<div v-if="item.status == -1">停止挂号</div>
<div v-if="item.status == 0">
{{
item.availableNumber == -1
? `约满了`
: `有号(${item.availableNumber})`
}}
</div>
<div v-if="item.status == 1">即将放号</div>
</div>
</div>
</div>
<el-pagination
v-model:current-page="pageNo"
layout="prev, pager, next"
:total="workData.total"
@current-change="fetchWorkData"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { reqHospitalWork } from "@/api/hospital";
import type { HospitalWorkData } from "@/api/hospital/type";
import { useRoute } from "vue-router";
// 获取路由对象
let $route = useRoute();
// 分页器当前页码
let pageNo = ref<number>(1);
// 每一页展示几条数据
let limit = ref<number>(6);
// 存储医院科室挂号的数据
let workData = ref<any>({});
// 组件挂载完毕发一次请求
onMounted(() => {
fetchWorkData();
});
// 获取挂号的数据
const fetchWorkData = async () => {
let result: HospitalWorkData = await reqHospitalWork(
pageNo.value,
limit.value,
$route.query.hoscode as string,
$route.query.depcode as string
);
console.log(result);
if (result.code == 200) {
workData.value = result.data;
}
};
</script>
<style scoped lang="scss">
.wrap {
.top {
display: flex;
color: #7f7f7f;
.line {
width: 5px;
height: 20px;
background: skyblue;
margin: 0px 5px;
}
.dot {
margin: 0px 5px;
color: skyblue;
}
}
.center {
margin: 20px 0px;
display: flex;
flex-direction: column;
align-items: center;
.time {
font-weight: 900;
}
.container {
width: 100%;
display: flex;
margin: 30px 0px;
.item {
flex: 1;
width: 100%;
border: 1px solid skyblue;
margin: 0px 5px;
&.active {
border: 1px solid #cccc;
color: #7f7f7f;
.top2 {
background: #ccc;
}
}
.top2 {
background: #e8f2ff;
/* margin: 5px 0px; */
width: 100%;
height: 30px;
text-align: center;
line-height: 30px;
}
.bottom {
width: 100%;
height: 60px;
text-align: center;
line-height: 60px;
}
}
}
}
}
</style>
18.2 预约挂号底部医生排班业务
ts数据类型
获取医生信息的逻辑
展示数据
完整代码:
<template>
<div class="wrap">
<!-- 顶部结构 -->
<div class="top">
<div class="hosname">{{ workData.baseMap?.hosname }}</div>
<div class="line">|</div>
<div>{{ workData.baseMap?.bigname }}</div>
<div class="dot">.</div>
<div class="hosdeparment">{{ workData.baseMap?.depname }}</div>
</div>
<!-- 中间展示日期的结构 -->
<div class="center">
<h1 class="time">{{ workData.baseMap?.workDateString }}</h1>
<div class="container">
<div
class="item"
:class="{
active: item.status == -1 || item.availableNumber == -1,
cur: item.workDate == workTime.workDate,
}"
v-for="item in workData.bookingScheduleList"
:key="item"
@click="changeTime(item)"
>
<div class="top2">{{ item.workDate }}-{{ item.dayOfWeek }}</div>
<div class="bottom">
<div v-if="item.status == -1">停止挂号</div>
<div v-if="item.status == 0">
{{
item.availableNumber == -1
? `约满了`
: `有号(${item.availableNumber})`
}}
</div>
<div v-if="item.status == 1">即将放号</div>
</div>
</div>
</div>
<el-pagination
v-model:current-page="pageNo"
layout="prev, pager, next"
:total="workData.total"
@current-change="fetchWorkData"
/>
</div>
<!-- 底部展示医生的结构 -->
<div class="bottom1">
<!-- 展示即将放号的时间 -->
<div class="will" v-if="workTime.status == 1">
<span class="time">2023年{{ workTime.workDateMd }}</span>
<span class="willtext">放号</span>
</div>
<!-- 展示医生的结构 :上午、下午-->
<div class="doctor" v-else>
<div class="morning">
<!-- 顶部文字提示 -->
<div class="tip1">
<svg
t="1689336807398"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2545"
width="32"
height="32"
>
<path
d="M304.5 691.8c0-145.3 117.8-263 263-263s263 117.8 263 263"
fill="#FDDA09"
p-id="2546"
></path>
<path
d="M567.5 428.8c-16.7 0-32.9 1.7-48.7 4.7 122 22.9 214.3 129.7 214.3 258.3h97.4c0.1-145.2-117.7-263-263-263z"
fill="#FDA906"
p-id="2547"
></path>
<path
d="M772.1 687.3c-8.3 0-15-6.7-15-15 0-66.2-25.8-128.5-72.6-175.4-46.8-46.8-109.1-72.6-175.4-72.6-66.2 0-128.5 25.8-175.4 72.6-46.8 46.8-72.6 109.1-72.6 175.4 0 8.3-6.7 15-15 15s-15-6.7-15-15c0-74.3 28.9-144.1 81.4-196.6 52.5-52.5 122.3-81.4 196.6-81.4s144.1 28.9 196.6 81.4c52.5 52.5 81.4 122.3 81.4 196.6 0 8.3-6.7 15-15 15z"
fill=""
p-id="2548"
></path>
<path
d="M914.1 704.5H120c-8.3 0-15-6.7-15-15s6.7-15 15-15h794.1c8.3 0 15 6.7 15 15s-6.7 15-15 15zM755.2 777.4H278.8c-8.3 0-15-6.7-15-15s6.7-15 15-15h476.5c8.3 0 15 6.7 15 15s-6.8 15-15.1 15zM605.6 858.1H428.5c-8.3 0-15-6.7-15-15s6.7-15 15-15h177.1c8.3 0 15 6.7 15 15s-6.8 15-15 15zM179.4 641h-63.5c-8.3 0-15-6.7-15-15s6.7-15 15-15h63.5c8.3 0 15 6.7 15 15s-6.7 15-15 15zM515.9 323.4c-8.3 0-15-6.7-15-15V181.3c0-8.3 6.7-15 15-15s15 6.7 15 15v127.1c0 8.3-6.7 15-15 15zM271 422.9c-3.8 0-7.7-1.5-10.6-4.4l-56.2-56.2c-5.9-5.9-5.9-15.4 0-21.2 5.9-5.9 15.4-5.9 21.2 0l56.2 56.2c5.9 5.9 5.9 15.4 0 21.2-2.9 3-6.8 4.4-10.6 4.4zM754.8 422.9c-3.8 0-7.7-1.5-10.6-4.4-5.9-5.9-5.9-15.4 0-21.2l56.2-56.2c5.9-5.9 15.4-5.9 21.2 0 5.9 5.9 5.9 15.4 0 21.2l-56.2 56.2c-2.9 3-6.7 4.4-10.6 4.4z"
fill=""
p-id="2549"
></path>
<path
d="M441.2 544.2a18.8 17.8 0 1 0 37.6 0 18.8 17.8 0 1 0-37.6 0Z"
fill="#050400"
p-id="2550"
></path>
<path
d="M553.9 544.2a18.8 17.8 0 1 0 37.6 0 18.8 17.8 0 1 0-37.6 0Z"
fill="#050400"
p-id="2551"
></path>
<path
d="M491.3 577.5c0 13.1 11.2 23.7 25 23.7s25-10.6 25-23.7h-50z"
fill="#050400"
p-id="2552"
></path>
<path
d="M406.9 588.6a21.9 20.7 0 1 0 43.8 0 21.9 20.7 0 1 0-43.8 0Z"
fill="#FDA906"
p-id="2553"
></path>
<path
d="M582.1 588.6a21.9 20.7 0 1 0 43.8 0 21.9 20.7 0 1 0-43.8 0Z"
fill="#FDA906"
p-id="2554"
></path>
</svg>
<span class="text">上午号源</span>
</div>
<!-- 每一个医生的信息 -->
<div class="doc_info" v-for="doctor in morningArr" :key="doctor.id">
<!-- 展示医生的名字|技能 -->
<div class="left">
<div class="info">
<span>{{ doctor.title }}</span>
<span>|</span>
<span>{{ doctor.docname }}</span>
</div>
<div class="skill">{{ doctor.skill }}</div>
</div>
<!-- 右侧展示挂号的钱数 -->
<div class="right">
<div class="money">¥{{ doctor.amount }}元</div>
<el-button type="primary">{{ doctor.availableNumber }}</el-button>
</div>
</div>
</div>
<div class="morning">
<!-- 顶部文字提示 -->
<div class="tip1">
<svg
t="1689338507838"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5411"
width="32"
height="32"
>
<path
d="M369.86 594.04c45.34-78.5 145.73-105.38 224.23-60.04a164.171 164.171 0 0 1 60.04 60.04H369.86z m306.23 82.05z m-492.26 0h656.35c22.66 0 41.02 18.36 41.02 41.02 0 22.66-18.36 41.02-41.02 41.02H183.83c-22.66 0-41.02-18.36-41.02-41.02-0.01-22.66 18.36-41.02 41.02-41.02z"
fill="#1296db"
p-id="5412"
></path>
<path
d="M678.58 383.66l43.51-43.51c24.03-24.03 62.99-24.03 87.02 0 24.03 24.03 24.03 62.99 0 87.02l-43.51 43.51c-24.14 23.92-63.1 23.74-87.02-0.39-23.77-23.99-23.77-62.65 0-86.63zM512 204.34c33.98 0 61.53 27.55 61.53 61.53v61.53c-0.14 33.98-27.8 61.42-61.78 61.28-33.79-0.14-61.15-27.49-61.28-61.28v-61.53c0-33.98 27.55-61.53 61.53-61.53zM217.08 340.15c24.03-24.03 62.99-24.03 87.02 0l43.51 43.51c24.03 24.03 24.03 62.99 0 87.02-24.03 24.03-62.99 24.03-87.02 0l-43.51-43.51c-24.03-24.03-24.03-62.99 0-87.02z"
fill="#1296db"
opacity=".3"
p-id="5413"
></path>
</svg>
<span class="text">下午号源</span>
</div>
<!-- 每一个医生的信息 -->
<div class="doc_info" v-for="doctor in afterArr" :key="doctor.id">
<!-- 展示医生的名字|技能 -->
<div class="left">
<div class="info">
<span>{{ doctor.title }}</span>
<span>|</span>
<span>{{ doctor.docname }}</span>
</div>
<div class="skill">{{ doctor.skill }}</div>
</div>
<!-- 右侧展示挂号的钱数 -->
<div class="right">
<div class="money">¥{{ doctor.amount }}元</div>
<el-button type="primary">{{ doctor.availableNumber }}</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { reqHospitalWork, reqHospitalDoctor } from "@/api/hospital";
import type {
HospitalWorkData,
DoctorResponseData,
DocArr,
Doctor,
} from "@/api/hospital/type";
import { useRoute } from "vue-router";
// 获取路由对象
let $route = useRoute();
// 分页器当前页码
let pageNo = ref<number>(1);
// 每一页展示几条数据
let limit = ref<number>(6);
// 存储医院科室挂号的数据
let workData = ref<any>({});
// 存储当前排班日期:当前数据的第一个
let workTime: any = ref({});
// 存储排班医生的数据
let docArr = ref<DocArr>([]);
// 组件挂载完毕发一次请求
onMounted(() => {
fetchWorkData();
});
// 获取挂号的数据
const fetchWorkData = async () => {
let result: HospitalWorkData = await reqHospitalWork(
pageNo.value,
limit.value,
$route.query.hoscode as string,
$route.query.depcode as string
);
// console.log(result);
if (result.code == 200) {
workData.value = result.data;
// 存储第一天日期的数据
// workTime.value=workData.bookingScheduleList[0]
workTime.value = workData.value.bookingScheduleList[0];
// 获取一次医生的数据
getDoctorWorkData();
}
};
// 获取医生排班信息
const getDoctorWorkData = async () => {
// 医院的编号
let hoscode: string = $route.query.hoscode as string;
// 科室的编号
let depcode: string = $route.query.depcode as string;
// 时间
let workDate: string = workTime.value.workDate;
// 获取排班医生的数据
let result: DoctorResponseData = await reqHospitalDoctor(
hoscode,
depcode,
workDate
);
// console.log(result);
if (result.code == 200) {
docArr.value = result.data;
}
};
// 点击顶部某一天的时候触发回调
const changeTime = (item: any) => {
// console.log(item);
// 存储用户选择那一天的数据
workTime.value = item;
// 再发一次获取医生的排班的数据
getDoctorWorkData();
};
// 计算出上午排班的医生数据
let morningArr = computed(() => {
return docArr.value.filter((doc: Doctor) => {
return doc.workTime == 0;
});
});
// 计算出下午排班的医生数据
let afterArr = computed(() => {
return docArr.value.filter((doc: Doctor) => {
return doc.workTime == 1;
});
});
</script>
<style scoped lang="scss">
.wrap {
.top {
display: flex;
color: #7f7f7f;
.line {
width: 5px;
height: 20px;
background: skyblue;
margin: 0px 5px;
}
.dot {
margin: 0px 5px;
color: skyblue;
}
}
.center {
margin: 20px 0px;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.5s;
.time {
font-weight: 900;
}
.container {
width: 100%;
display: flex;
margin: 30px 0px;
.item {
flex: 1;
width: 100%;
border: 1px solid skyblue;
margin: 0px 5px;
&.active {
border: 1px solid #cccc;
color: #7f7f7f;
.top2 {
background: #ccc;
}
}
&.cur {
transform: scale(1.1);
}
.top2 {
background: #e8f2ff;
/* margin: 5px 0px; */
width: 100%;
height: 30px;
text-align: center;
line-height: 30px;
}
.bottom {
width: 100%;
height: 60px;
text-align: center;
line-height: 60px;
}
}
}
}
.bottom1 {
.will {
text-align: center;
font-size: 30px;
font-weight: 900;
.time {
color: red;
}
.willtext {
color: skyblue;
}
}
.doctor {
.morning {
.tip1 {
display: flex;
align-items: center;
.text {
color: #7f7f7f;
font-weight: 900;
}
}
.doc_info {
display: flex;
justify-content: space-between;
margin: 10px 0px;
border-bottom: 1px solid #ccc;
.left {
.info {
color: skyblue;
margin: 10px 0px;
span {
margin: 0px 5px;
font-size: 18px;
font-weight: 900;
}
}
.skill {
margin: 10px 0px;
color: #7f7f7f;
}
}
.right {
display: flex;
width: 150px;
justify-content: space-between;
align-items: center;
.money {
color: #7f7f7f;
font-weight: 900;
}
}
}
}
}
}
}
</style>
18.3、确认挂号信息
静态页面搭建
引入路由
创建文件
点击跳转到这个页面
使用路由
点击跳转,记得带上医生id
register_step2.vue
<template>
<div class="container">
<h1 class="tip">确认挂号信息</h1>
<!-- 卡片:展示就诊人信息 -->
<el-card class="box-card">
<!-- 卡片的头部 -->
<template #header>
<div class="card-header">
<span>请选择就诊人</span>
<el-button class="button" type="primary" size="default" :icon="User"
>添加就诊人</el-button
>
</div>
</template>
<!-- 卡片的身体部分展示就诊人信息 -->
<div class="user">
<!-- :user="user" :index="index" :currenIndex="index" 父组件传数据给子组件 -->
<Visitor
v-for="(user, index) in userArr"
:key="user.id"
class="item"
:user="user"
@click="changeIndex(index)"
:index="index"
:currentIndex="currentIndex"
/>
</div>
</el-card>
<!-- 底部展示医生的信息 -->
<el-card class="box-card">
<!-- 卡片头部 -->
<template #header>
<div class="card-header">
<span>挂号信息</span>
</div>
</template>
<!-- 卡片的身体部分:展示医生的信息-->
<el-descriptions class="margin-top" :column="2" border>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊日期:</div>
</template>
{{ docInfo.workDate }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊医院:</div>
</template>
{{ docInfo.param?.hosname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊科室:</div>
</template>
{{ docInfo.param?.depname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生姓名:</div>
</template>
{{ docInfo?.docname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生职称:</div>
</template>
{{ docInfo.title }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生专长:</div>
</template>
{{ docInfo.skill }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生服务费:</div>
</template>
<span style="color: red">{{ docInfo.amount }}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 确定挂号按钮 -->
<div class="btn">
<el-button
type="primary"
size="default"
:disabled="currentIndex == -1 ? true : false"
>确定挂号</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { User } from "@element-plus/icons-vue";
// 引入获取就诊人的信息接口
import { reqGetUser, reqDoctorInfo } from "@/api/hospital";
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import type {
UserResponseData,
UserArr,
DoctorInfoData,
} from "@/api/hospital/type";
// 就诊人组件
import Visitor from "./visitor.vue";
// 获取路由对象
let $route = useRoute();
// 存储全部就诊人信息
let userArr = ref<UserArr>([]);
// 存储医生的信息
let docInfo = ref<any>({});
// 存储用户确认就诊人索引值
let currentIndex = ref<number>(-1);
// 组件挂载完毕获取数据
onMounted(() => {
// 获取就诊人信息
fetchUserData();
// 获取医生信息
fetchInfo();
});
// 获取就诊人信息
const fetchUserData = async () => {
let result: UserResponseData = await reqGetUser();
// console.log(result);
if (result.code == 200) {
userArr.value = result.data;
}
};
// 获取医生信息
const fetchInfo = async () => {
let result: DoctorInfoData = await reqDoctorInfo(
$route.query.docId as string
);
if (result.code == 200) {
docInfo.value = result.data;
}
};
// 点击就诊人子组件的回调
const changeIndex = (index: number) => {
// console.log(index);
// 存储当前用户选中就诊人信息索引值
currentIndex.value = index;
};
</script>
<style scoped lang="scss">
.container {
.tip {
font-weight: 900;
color: #7f7f7f;
font-size: 20px;
}
.box-card {
margin: 20px 0px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.user {
display: flex;
flex-wrap: wrap;
.item {
width: 32%;
margin: 5px;
}
}
}
.btn {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
}
</style>
visitor.vue
<template>
<div class="visitor">
<div class="top">
<div class="left">
<span class="free">{{ user.isInsure == 1 ? "医保" : "自费" }}</span>
<span class="username">{{ user.name }}</span>
</div>
<div class="right">
<el-button circle type="primary" :icon="Edit"></el-button>
</div>
</div>
<div class="bottom">
<p>证件类型:{{ user.param.certificatesTypeString }}</p>
<p>证件号码:{{ user.certificatesNo }}</p>
<p>用户性别:{{ user.sex == 0 ? "女生" : "男生" }}</p>
<p>出生日期:{{ user.birthdate }}</p>
<p>手机号码:{{ user.phone }}</p>
<p>婚姻状况:{{ user.isMarry == 1 ? "已婚" : "未婚" }}</p>
<p>当前住址:{{ user.param.cityString }}</p>
<p>详细住址:{{ user.paramfullAddress }}</p>
<!-- 红色已选择的盒子 -->
<transition name="confirm">
<div class="confirm" v-if="index === currentIndex">已选择</div>
</transition>
</div>
</div>
</template>
<script setup lang="ts">
import { Edit } from "@element-plus/icons-vue";
// 接受父组件传递过来的就诊人信息
defineProps(["user", "index", "currentIndex"]);
</script>
<style scoped lang="scss">
.visitor {
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
.top {
height: 60px;
background: #e5e5e5;
display: flex;
justify-content: space-around;
align-items: center;
.left {
.free {
background: white;
padding: 5px;
font-size: 12px;
margin-right: 5px;
border-radius: 10px;
}
.username {
color: #7f7f7f;
}
}
}
.bottom {
position: relative;
padding-left: 50px;
p {
line-height: 40px;
}
.confirm {
position: absolute;
width: 200px;
height: 200px;
color: red;
border-radius: 50%;
border: 1px dashed red;
text-align: center;
line-height: 200px;
left: 20%;
top: 20%;
opacity: 0.5;
transform: rotate(35deg);
font-weight: 900;
}
.confirm-enter-from {
transform: scale(1);
}
.confirm-enter-active {
transition: all 0.3s;
}
.confirm-enter-to {
transform: scale(1.3);
}
}
}
</style>
18.4、医生挂号信息展示
接口
展示医生信息
18.5、已选择图的完成
利用子绝父相的方法
18.6、确定就诊人业务
当选择有东西时,才可以确定挂号
十九、会员中心
19.1小错误
里面应该用单引号,不能用双引号
19.2 会员中心路由跳转
19.3 确认挂号(接口似乎挂了,一直数据异常)
接口
确定挂号
完整代码
<template>
<div class="container">
<h1 class="tip">确认挂号信息</h1>
<!-- 卡片:展示就诊人信息 -->
<el-card class="box-card">
<!-- 卡片的头部 -->
<template #header>
<div class="card-header">
<span>请选择就诊人</span>
<el-button class="button" type="primary" size="default" :icon="User"
>添加就诊人</el-button
>
</div>
</template>
<!-- 卡片的身体部分展示就诊人信息 -->
<div class="user">
<!-- :user="user" :index="index" :currenIndex="index" 父组件传数据给子组件 -->
<Visitor
v-for="(user, index) in userArr"
:key="user.id"
class="item"
:user="user"
@click="changeIndex(index)"
:index="index"
:currentIndex="currentIndex"
/>
</div>
</el-card>
<!-- 底部展示医生的信息 -->
<el-card class="box-card">
<!-- 卡片头部 -->
<template #header>
<div class="card-header">
<span>挂号信息</span>
</div>
</template>
<!-- 卡片的身体部分:展示医生的信息-->
<el-descriptions class="margin-top" :column="2" border>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊日期:</div>
</template>
{{ docInfo.workDate }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊医院:</div>
</template>
{{ docInfo.param?.hosname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">就诊科室:</div>
</template>
{{ docInfo.param?.depname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生姓名:</div>
</template>
{{ docInfo?.docname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生职称:</div>
</template>
{{ docInfo.title }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生专长:</div>
</template>
{{ docInfo.skill }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">医生服务费:</div>
</template>
<span style="color: red">{{ docInfo.amount }}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 确定挂号按钮 -->
<div class="btn">
<el-button
type="primary"
size="default"
:disabled="currentIndex == -1 ? true : false"
@click="submitOrder"
>确定挂号</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { User } from "@element-plus/icons-vue";
// 引入获取就诊人的信息接口
import { reqGetUser, reqDoctorInfo } from "@/api/hospital";
import { reqSubmitOrder } from "@/api/user";
import type { SubmitOrder } from "@/api/user/type";
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import type {
UserResponseData,
UserArr,
DoctorInfoData,
} from "@/api/hospital/type";
import { ElMessage } from "element-plus";
// 就诊人组件
import Visitor from "./visitor.vue";
// 获取路由对象
let $route = useRoute();
// 获取路由器对象
let $router = useRouter();
// 存储全部就诊人信息
let userArr = ref<UserArr>([]);
// 存储医生的信息
let docInfo = ref<any>({});
// 存储用户确认就诊人索引值
let currentIndex = ref<number>(-1);
// 组件挂载完毕获取数据
onMounted(() => {
// 获取就诊人信息
fetchUserData();
// 获取医生信息
fetchInfo();
});
// 获取就诊人信息
const fetchUserData = async () => {
let result: UserResponseData = await reqGetUser();
// console.log(result);
if (result.code == 200) {
userArr.value = result.data;
}
};
// 获取医生信息
const fetchInfo = async () => {
let result: DoctorInfoData = await reqDoctorInfo(
$route.query.docId as string
);
if (result.code == 200) {
docInfo.value = result.data;
}
};
// 点击就诊人子组件的回调
const changeIndex = (index: number) => {
// console.log(index);
// 存储当前用户选中就诊人信息索引值
currentIndex.value = index;
};
//确定挂号按钮的回调
const submitOrder = async () => {
//医院编号
let hoscode = docInfo.value.hoscode;
//医生的ID
let scheduleId = docInfo.value.id;
//就诊人的ID
let patientId = userArr.value[currentIndex.value].id;
//提交订单
let result: SubmitOrder = await reqSubmitOrder(
hoscode,
scheduleId,
patientId
);
// console.log(result);
//提交订单成功
if (result.code == 200) {
$router.push({ path: "/user/order", query: { orderId: result.data } });
} else {
ElMessage({
type: "error",
message: result.message,
});
}
};
</script>
<style scoped lang="scss">
.container {
.tip {
font-weight: 900;
color: #7f7f7f;
font-size: 20px;
}
.box-card {
margin: 20px 0px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.user {
display: flex;
flex-wrap: wrap;
.item {
width: 32%;
margin: 5px;
}
}
}
.btn {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
}
</style>
19.4 订单详情
接口
19.5 取消预约
取消预约
19.6 支付二维码静态搭建
19.7 支付二维码获取
将字符串变为二维码插件---npm官网找qrcode插件
获取二维码
动态绑定图片
19.8 订单支付业务
接口
当二维码出现时,每隔两秒都要向服务器询问是否支付成功,当支付成功后,还要将定时器清除
清除定时器(窗口和右上角的叉子)
二十、实名认证静态搭建
完整代码
<template>
<!-- 实名认证结构 -->
<el-card class="box-card">
<!-- 卡片的头部 -->
<template #header>
<div class="card-header">
<h1>实名信息</h1>
</div>
</template>
<!-- 实名认证结构的提示部分 -->
<div class="tip" style="color: #7f7f7f">
<p>
<el-icon><InfoFilled /></el-icon>
完成实名认证后才能添加就诊人,正常进行挂号,为了不影响后续步骤,建议提前实名认证。
</p>
</div>
<!-- 卡片身体的底部:认证成功的结构、认证未成功的结构 -->
<el-descriptions
v-if="true"
class="margin-top"
:column="1"
border
style="margin: 20px auto"
size="small"
>
<el-descriptions-item label-align="center" :width="20">
<template #label>
<div class="cell-item">用户姓名</div>
</template>
kooriookami
</el-descriptions-item>
<el-descriptions-item label-align="center" :width="20">
<template #label>
<div class="cell-item">证件类型</div>
</template>
kooriookami
</el-descriptions-item>
<el-descriptions-item label-align="center" :width="20">
<template #label>
<div class="cell-item">证件号码</div>
</template>
kooriookami
</el-descriptions-item>
</el-descriptions>
<!-- 用户未认证的结构 -->
<el-form style="width: 60%; margin: 20px auto" label-width="80">
<el-form-item label="用户姓名">
<el-input placeholder="请输入用户的姓名"></el-input>
</el-form-item>
<el-form-item label="证件类型">
<el-select placeholder="请选择证件类型" style="width: 100%">
<el-option label="身份证"></el-option>
<el-option label="户口本"></el-option>
</el-select>
</el-form-item>
<el-form-item label="证件号码">
<el-input placeholder="请输入证件号码"></el-input>
</el-form-item>
<el-form-item label="上传证件">
<el-upload list-type="picture-card">
<img
src="../../../assets/images/auth_example.png"
alt=""
style="width: 100%; height: 80%"
/>
</el-upload>
<el-dialog>
<img w-full alt="Preview Image" />
</el-dialog>
</el-form-item>
<el-form-item>
<el-button type="primary"> 提交</el-button>
<el-button> 重写</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup lang="ts">
import { InfoFilled } from "@element-plus/icons-vue";
</script>
<style scoped lang="scss">
.box-card {
.tip {
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
20.1 获取用户模块
接口
20.2 获取表单数据
上传图片功能
小错误
原因:没有判断是否有图片,也可能上传的是空数组
功能实现:
重写:
提交:
20.3 实名认证表单自定义校验
小bug:上传图片成功了,但是校验规则还在
解决:用了element plus自带的方法
自定义表单校验
校验函数;
// 自定义校验姓名规则
const validatorName = (rule: any, value: any, callBack: any) => {
// rule即为当前校验字段的校验规则对象 value即为当前校验字段的校验数据data
// console.log(rule);
// console.log(value);
const reg =
/^[\u00B7\u3007\u3400-\u4DBF\u4E00-\u9FFF\uE000-\uF8FF\uD840-\uD8C0\uDC00-\uDFFF\uF900-\uFAFF]+$/;
if (reg.test(value)) {
callBack();
} else {
callBack(new Error("请输入正确的中国人的名字"));
}
};
// 证件类型校验方法
const validatorType = (rule: any, value: any, callBack: any) => {
// console.log(value);
if (value == "10" || value == "20") {
callBack();
} else {
callBack(new Error("请选择证件类型"));
}
};
// 身份证号码
const validatorNo = (rule: any, value: any, callBack: any) => {
// console.log(111);
const reg =
/^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
const hkb = /^\d{9}$/;
if (reg.test(value) || hkb.test(value)) {
callBack();
} else {
callBack(new Error("请输入正确的身份证号码或者户口本号码"));
}
};
// 证件照图片
const validatorUrl = (rule: any, value: any, callBack: any) => {
if (value.length) {
callBack();
} else {
callBack(new Error("请上传证件照"));
}
};