尚硅谷vue项目实战《尚医通》,Vue3项目+TypeScript前端项目 笔记

Vite

https://cn.vitejs.dev/guide/

创建项目

$ npm create vite@latest

选择VUE,选择TypeScript

指定默认路径

在文件vite.config.ts中配置

//引入node提供内置模块path:可以获取绝对路径
import path from "path";

export default defineConfig({
  plugins: [vue()],
  resolve:{
    alias:{
      "@":path.resolve(__dirname,'src')
    }
  }
})

在tsconfig.app.json文件中配置

"compilerOptions": {
	"baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
}

清除默认样式

https://www.npmjs.com/package/reset-css?activeTab=code在该地址中找到reset.css

并将该内容复制到项目中(style.reset.scss)

/* http://meyerweb.com/eric/tools/css/reset/
   v5.0.1 | 20191019
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
main, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, main, menu, nav, section {
	display: block;
}
/* HTML5 hidden-attribute fix for newer browsers */
*[hidden] {
    display: none;
}
body {
	line-height: 1;
}
menu, ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

将复制后的内容引入的main.ts中

import '@/style/reset.scss'

创建组件

在main.ts中创建组件

import { createApp } from 'vue'
import App from '@/App.vue';
import '@/style/reset.scss'
const app = createApp(App);
//挂载
app.mount("#app");

拆分组件

删除components下的默认组件

并在该文件夹下创建组件HospitalTop、HospitalBottom,并将其引入到APP中

import HospitalTop from '@/components/hospital_top/index.vue';
// @ts-ignore
import HospitalBottom from '@/components/hospital_bottom/index.vue';
const app = createApp(App);
app.component('HospitalTop',HospitalTop)
app.component('HospitalBottom',HospitalBottom)
//挂载
app.mount("#app");

注意在挂载前组装组件

定义HospitalTop组件
<script setup lang="ts">

</script>

<template>
<div class="top">
  <div class="content">
<!--   左侧 -->
   <div class="left">
     <img src="../../assets/images/logo.png" alt="">
     <p>尚医通 预约挂号统一平台</p>
   </div>
<!--   右侧 -->
   <div class="right">
     <p class="help">帮助中心</p>
     <p class="login">登录/注册</p>
   </div>
  </div>
</div>
</template>

<style scoped>
  .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>

了解新样式

 	position: fixed;
    z-index: 999;
    display: flex;
    justify-content: center;

弹性布局(Flex布局)是一种现代的CSS布局方式,通过使用display: flex属性来创建一个弹性容器,并在其中使用灵活的盒子模型来进行元素的排列和定位。

弹性布局具有以下特点:

  • 主轴与交叉轴:弹性容器具有主轴(main axis)和交叉轴(cross axis)。默认情况下,主轴是水平方向,交叉轴是垂直方向。
  • 弹性容器:通过将父元素的display属性设置为flex或inline-flex来创建弹性容器。
  • 子元素的弹性项目:弹性容器中的每个子元素都成为弹性项目。子元素可以指定各自在主轴和交叉轴上的大小、顺序以及对齐方式等。
  • 主轴对齐:弹性项目可以在主轴上按照一定比例分配空间,也可以使用justify-content属性定义主轴的对齐方式。
  • 交叉轴对齐:弹性项目可以在交叉轴上进行对齐,包括顶部对齐、底部对齐、居中对齐等,使用align-items属性定义交叉轴对齐方式。
  • 换行与自动调整:可控制弹性项目是否换行,并且具备自动调整元素大小的能力。

弹性布局简化了网页布局的开发过程,提供了更灵活、响应式的布局方式。它适用于各种屏幕尺寸和设备类型,并能够快速适应不同的布局需求。

容器的属性

justify-content

justify-content属性定义了项目在主轴上的对齐方式。

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

flex-start(默认值):左对齐
flex-end:右对齐
center: 居中
space-between:两端对齐,项目之间的间隔都相等。
space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

align-items

align-items属性定义项目在交叉轴上如何对齐。

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

flex-start:交叉轴的起点对齐。
flex-end:交叉轴的终点对齐。
center:交叉轴的中点对齐。
baseline: 项目的第一行文字的基线对齐。
stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

flex-direction

决定主轴的方向,水平或者垂直

  • row(默认值):主轴为水平方向,起点在左端。

  • row-reverse:主轴为水平方向,起点在右端。

  • column:主轴为垂直方向,起点在上沿。

  • column-reverse:主轴为垂直方向,起点在下沿。

    flex-wrap

    换行不换行以及换行的方向

    • nowrap(默认):不换行

    • wrap:换行,第一行在上方。

    • wrap-reverse:换行,第一行在下方。

      flex-flow

      以上两种的简写方式

      .box {
      flex-flow: || ;
      }

      align-content

      align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

      flex-start:与交叉轴的起点对齐。
      flex-end:与交叉轴的终点对齐。
      center:与交叉轴的中点对齐。
      space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
      space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
      stretch(默认值):轴线占满整个交叉轴。order属性

      order属性

      定义项目的排列顺序。数值越小,排列越靠前,默认为0。

      flex-grow属性

      flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

      如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

      flex-shrink属性

      flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

      如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

      flex-basis属性

      flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

      它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

      flex属性

      flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。

      该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

      建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

      align-self属性

      align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

      该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

position一般分为三种,一种是相对定位relative,一种是绝对定位absolute,一种是固定定位fixed

a】position 定位

​ b】z-index 定位层级

​ c】opacity 透明度

定义HospitalBottom组件
<script setup lang="ts">

</script>

<template>
  <div class="bottom">
    <div class="content">
      <div class="left">京ICP备 13018369号 电话挂号010-56253825</div>
      <div class="right">
        <span>联系我们</span>
        <span>合作伙伴</span>
        <span>用户协议</span>
        <span>隐私协议</span>
      </div>
    </div>
  </div>

</template>

<style scoped lang="scss">
.bottom{
  width: 100%;
  height: 50px;
  background: #f0f2f5;
  display: flex;
  justify-content: center;
  .content{
    width: 1200px;
    height: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 14px;
    color: #aaa;
    .right{
      span{
        margin: 0px 5px;
      }
    }
  }
}

</style>

创建路由

安装路由 npm i vue-router

在src下创建router文件夹并创建index.ts

import {createRouter,createWebHistory} from "vue-router";
//createRouter方法,用于创建路由实例,可以管理多个路由

export default createRouter({
    // 路由模式设置
    history:createWebHistory(),
    routes:[
        {
            path:'/home',
            component:()=> import('@/pages/home/index.vue')
        },
        {
            path:'/hospital',
            component:()=>import('@/pages/hospital/index.vue')
        },
        {
            path:'/',
            redirect:'/home'
        }
    ],
    //滚动行为:控制滚动条的位置
    scrollBehavior(){
        return{
            left:0,
            top:0
        }
    }
})

并在main.ts中引入并使用

import router from "@/router";
app.use(router);

创建首页及医院详情页

在src下创建pages文件夹,并创建home及hospital文件夹,然后分别创建index.vue

syt/src/pages/home/index.vue

<script setup lang="ts">

</script>

<template>
<div>我是首页</div>
</template>

<style scoped lang="scss">

</style>

syt/src/pages/hospital/index.vue

<script setup lang="ts">

</script>

<template>
<div>我是医院详情</div>
</template>

<style scoped lang="scss">

</style>

首页设计

使用Element Plus 架构

https://element-plus.org/zh-CN/guide/quickstart.html

安装Element Plus
npm install element-plus --save
引入element-plus插件

在main.ts 中引入

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import App from '@/App.vue';
import '@/style/reset.scss'
//引入element-plus插件
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import HospitalTop from '@/components/hospital_top/index.vue';
// @ts-ignore
import HospitalBottom from '@/components/hospital_bottom/index.vue';
//引入vue-router核心插件并安装
import router from "@/router";
const app = createApp(App);
app.component('HospitalTop',HospitalTop)
app.component('HospitalBottom',HospitalBottom)
//安装vue-router
app.use(router);
//安装element-plus插件
app.use(ElementPlus)
//挂载
app.mount("#app");

首页拆分为三部分

轮播图、搜索框、医院信息

创建轮播图

syt/src/pages/home/carousel/index.vue

<script setup lang="ts">

</script>

<template>
<div>
  <el-carousel height="350px">
    <el-carousel-item v-for="item in 4" :key="item">
      <img src="../../../assets/images/web-banner1.png">
    </el-carousel-item>
  </el-carousel>
</div>
</template>

<style scoped lang="scss">
  img{
    width: 100%;
    height: 350px;
  }
</style>

首页添加轮播图

syt/src/pages/home/index.vue

<script setup lang="ts">
import Carousel from './carousel/index.vue'
</script>

<template>
<div>
    <!-- 首页轮播图结构 -->
  <Carousel/>
    
</div>
</template>

<style scoped lang="scss">

</style>
创建搜索框

syt/src/pages/home/search/index.vue

<script setup lang="ts">
import {Search} from "@element-plus/icons-vue";
</script>

<template>
<div class="search">
  <el-autocomplete
      clearable
      class="inline-input w-50"
      placeholder="请输入医院名称"
  />
  <el-button type="primary" :icon="Search">搜索</el-button>
</div>
</template>

<style scoped lang="scss">
.search{
  width: 100%;
  height: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 10px 0px;
  //深度选择器
  ::v-deep(.el-input__wrapper){
    margin-right: 10px;
  }
}
</style>

首页添加搜索框

syt/src/pages/home/index.vue

<script setup lang="ts">
import Carousel from './carousel/index.vue'
</script>

<template>
<div>
    <!-- 首页轮播图结构 -->
  <Carousel/>
    <!-- 首页搜索医院的表单区域 -->
  <Search/>
</div>
</template>

<style scoped lang="scss">

</style>
设计医院结构

使用Element Plusd Layout布局

syt/src/pages/home/index.vue

  <!-- 首页轮播图结构 -->
  <Carousel/>
    <!-- 首页搜索医院的表单区域 -->
  <Search/>
<!-- 底部展示医院的结构 -->
  <el-row gutter="20">
    <el-col :span="20">
        
    </el-col>
    <el-col :span="4">456</el-col>
  </el-row>

医院结构拆分为等级、地区、医院卡片

创建医院等级

syt/src/pages/home/level/index.vue

<script setup lang="ts">

</script>

<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>
      </ul>
    </div>
  </div>
</template>

<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>

创建地区

syt/src/pages/home/region/index.vue

<script setup lang="ts">

</script>

<template>
  <div class="region">
    <div class="content">
      <div class="left">地区:</div>
      <ul>
        <li class="active">全部</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li>朝阳区</li>
        <li v-for="item in 10" :key="item">朝阳区</li>

      </ul>
    </div>
  </div>
</template>

<style scoped lang="scss">
  .region{
    color:#7f7f7f;
    margin-top: 10px;
    .content{
      display: flex;
      .left{
        margin-right: 10px;
        width: 53px;
      }
      ul{
        display: flex;
        flex-wrap: wrap;
        li{
          margin-right: 10px;
          margin-bottom: 10px;
          &.active{
            color:#55a6fe;
          }
        }
        li:hover{
          color:#55a6fe;
          cursor:pointer;
        }
      }
    }
  }

</style>

创建医院卡片

syt/src/pages/home/card/index.vue

<script setup lang="ts">
</script>

<template>
  <div>
  <el-card class="box-card" shadow="hover">
   <div class="content">
     <div class="left">
       <div class="hospital_name">医院名称</div>
       <div class="tip">
         <div class="level">
           <svg t="1720662383862" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3224" width="16" height="16"><path d="M857.28 344.992h-264.832c12.576-44.256 18.944-83.584 18.944-118.208 0-78.56-71.808-153.792-140.544-143.808-60.608 8.8-89.536 59.904-89.536 125.536v59.296c0 76.064-58.208 140.928-132.224 148.064l-117.728-0.192A67.36 67.36 0 0 0 64 483.04V872c0 37.216 30.144 67.36 67.36 67.36h652.192a102.72 102.72 0 0 0 100.928-83.584l73.728-388.96a102.72 102.72 0 0 0-100.928-121.824zM128 872V483.04c0-1.856 1.504-3.36 3.36-3.36H208v395.68H131.36A3.36 3.36 0 0 1 128 872z m767.328-417.088l-73.728 388.96a38.72 38.72 0 0 1-38.048 31.488H272V476.864a213.312 213.312 0 0 0 173.312-209.088V208.512c0-37.568 12.064-58.912 34.72-62.176 27.04-3.936 67.36 38.336 67.36 80.48 0 37.312-9.504 84-28.864 139.712a32 32 0 0 0 30.24 42.496h308.512a38.72 38.72 0 0 1 38.048 45.888z" p-id="3225"></path></svg>
           <span>三级甲等</span>
         </div>
         <div class="timer">
           <svg t="1720662686849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4370" width="16" height="16"><path d="M511.913993 63.989249c-247.012263 0-447.924744 200.912481-447.924744 447.924744s200.912481 447.924744 447.924744 447.924744 447.924744-200.912481 447.924744-447.924744S758.926256 63.989249 511.913993 63.989249zM511.913993 895.677474c-211.577356 0-383.763481-172.186125-383.763481-383.763481 0-211.577356 172.014111-383.763481 383.763481-383.763481s383.763481 172.014111 383.763481 383.763481S723.491349 895.677474 511.913993 895.677474z" fill="#575B66" p-id="4371"></path><path d="M672.05913 511.913993l-159.973123 0L512.086007 288.123635c0-17.717453-14.277171-32.166639-31.994625-32.166639-17.717453 0-31.994625 14.449185-31.994625 32.166639l0 255.956996c0 17.717453 14.277171 31.994625 31.994625 31.994625l191.967747 0c17.717453 0 32.166639-14.277171 32.166639-31.994625C704.053754 526.191164 689.604569 511.913993 672.05913 511.913993z" fill="#575B66" p-id="4372"></path></svg>
           <span>每天8:00放号</span>
         </div>
       </div>
     </div>
     <div class="right">
 		<img src="../../../assets/images/logo.png">
     </div>
   </div>
  </el-card>
  </div>
</template>

<style scoped lang="scss">
.content{
  display: flex;
  justify-content: space-between;
  .right{
    img{
      width: 50px;
      height: 50px;
    }
  }
  .left{
    width: 60%;
    .tip{
      color: #7f7f7f;
      margin-top: 20px;
      display: flex;
      justify-content: space-between;
    }
    .level{
      display: flex;
      align-items: center;
      span{
        margin-left: 5px;
      }
    }
    .timer{
      display: flex;
      align-items: center;
      span{
        margin-left: 5px;
      }
    }
  }
}

</style>

图标网址

https://www.iconfont.cn/collections/index?spm=a313x.home_index.i3.3.58a33a819QJkxd

医院结构组装
<script setup lang="ts">
import Search from './search/index.vue'
import Level from './level/index.vue'
import Region from './region/index.vue'
import Card from './card/index.vue'
    </script>
<template>
<div>
  <!-- 首页轮播图结构 -->
  <Carousel/>
    <!-- 首页搜索医院的表单区域 -->
  <Search/>
<!-- 底部展示医院的结构 -->
  <el-row gutter="20">
    <el-col :span="20">
         <level/>
      <Region/>
      <div class="hospital">
        <Card class="item" v-for="(item,index) in 10" :key="index" />
      </div>
    </el-col>
    <el-col :span="4">456</el-col>
  </el-row>
    </div>
</template>

<style scoped lang="scss">
.hospital{
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  .item{
    width: 48%;
    margin: 10px 0px;
  }
}
</style>

分页器

https://element-plus.org/zh-CN/component/pagination.html

<script setup lang="ts">
import { ref } from 'vue'
let currentPage = ref<number>(1)
let pageSize = ref<number>(10) 
</script>
<el-pagination
            v-model:current-page="currentPage"
            v-model:page-size="pageSize"
            :page-sizes="[10, 20, 30, 40]"
            :background="true"
            layout="prev, pager, next, jumper,->, sizes,total"
            :total="13"
  />

Axios的二次封装与代理跨域配置 P10

创建syt/src/utils/request.ts


//对axios函数库进行二次封装
//目的1:利用axios请求、响应拦截器功能
//目的2:请求拦截器,一般可以在请求头总携带公共的参数:token
//目的3:响应拦截器,可以简化服务器返回的数据,处理http网络错误
import axios from "axios";
//利用axios.create方法创建一个axios实例:可以设置基础路径、超时的时间设置
const  request = axios.create({
    baseURL:'/api',
    timeout:5000
});

//请求拦截器
request.interceptors.request.use((config)=>{
    return config;
});

//响应拦截器
request.interceptors.response.use((response)=>{
    return response.data;
},(error)=>{
    return Promise.reject( new Error(error.message))
});

//对外暴露axios
export  default  request;

跨域配置

在syt/vite.config.ts文件中增加

server: {
    proxy: {
      '/api': {
        // target: 'http://localhost',
        target:'http://syt.atguigu.cn',
        changeOrigin: true,
      },
    },
  }

尚硅谷服务地址

服务器地址:http://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.htm1
订单接口: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

创建syt/src/api/home/index.ts

import request from "@/utils/request.ts";
enum API {
    // HOSPITAL_URL = 'hosp/hospital/'     //尚硅谷地址
    HOSPITAL_URL = 'hosp/hospital/findHospitalList/'  //本地地址
}

export const reqHospital = (page:number,limit:number)=>request.get(API.HOSPITAL_URL+`${page}/${limit}`)

展示已有医院数据并分页展示 P11

修改首页
<script setup lang="ts">
import {reqHospital} from "@/api/home";
import {onMounted} from "vue";
let hasHospitalArr = ref([]);
let total = ref(0);
onMounted(()=>{
 getHospitalInfo();
})
const getHospitalInfo = async ()=>{
  let result: any = await reqHospital(currentPage.value,pageSize.value);
  hasHospitalArr.value = result.data.data.content;
  total = result.data.data.totalElements
}
//分页器页码发生变化时回调
const  currentPageChange = ()=>{
  getHospitalInfo();
}
//分页器下拉菜单发生变化时回调
const  pageSizeChange = ()=>{
  getHospitalInfo();
}
</script>

<template>
<div>
<!-- 首页轮播图结构 -->
  <Carousel/>
<!-- 首页搜索医院的表单区域 -->
  <Search/>
<!-- 底部展示医院的结构 -->
  <el-row gutter="20">
    <el-col :span="20">
      <level/>
      <Region/>
      <div class="hospital">
        <Card class="item" v-for="(item,index) in hasHospitalArr" :key="item.id" :hospitalInfo="item"/>
      </div>
              </div>
<!--  分页器    -->
      <div>
        <el-pagination
            v-model:current-page="currentPage"
            v-model:page-size="pageSize"
            :page-sizes="[10, 20, 30, 40]"
            :background="true"
            layout="prev, pager, next, jumper,->, sizes,total"
            :total="total"
            @current-change="currentPageChange"
            @size-change="pageSizeChange"
        />
      </div>

    </el-col>
    <el-col :span="4">456</el-col>
  </el-row>
</div>
</template>
修改Card
<script setup lang="ts">
defineProps(['hospitalInfo'])
</script>

<template>
  <div>
  <el-card class="box-card" shadow="hover">
   <div class="content">
     <div class="left">
       <div class="hospital_name">{{hospitalInfo.hosname}}</div>
       <div class="tip">
         <div class="level">
           <svg t="1720662383862" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3224" width="16" height="16"><path d="M857.28 344.992h-264.832c12.576-44.256 18.944-83.584 18.944-118.208 0-78.56-71.808-153.792-140.544-143.808-60.608 8.8-89.536 59.904-89.536 125.536v59.296c0 76.064-58.208 140.928-132.224 148.064l-117.728-0.192A67.36 67.36 0 0 0 64 483.04V872c0 37.216 30.144 67.36 67.36 67.36h652.192a102.72 102.72 0 0 0 100.928-83.584l73.728-388.96a102.72 102.72 0 0 0-100.928-121.824zM128 872V483.04c0-1.856 1.504-3.36 3.36-3.36H208v395.68H131.36A3.36 3.36 0 0 1 128 872z m767.328-417.088l-73.728 388.96a38.72 38.72 0 0 1-38.048 31.488H272V476.864a213.312 213.312 0 0 0 173.312-209.088V208.512c0-37.568 12.064-58.912 34.72-62.176 27.04-3.936 67.36 38.336 67.36 80.48 0 37.312-9.504 84-28.864 139.712a32 32 0 0 0 30.24 42.496h308.512a38.72 38.72 0 0 1 38.048 45.888z" p-id="3225"></path></svg>
           <span>{{hospitalInfo.param.hostypeString}}</span>
         </div>
         <div class="timer">
           <svg t="1720662686849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4370" width="16" height="16"><path d="M511.913993 63.989249c-247.012263 0-447.924744 200.912481-447.924744 447.924744s200.912481 447.924744 447.924744 447.924744 447.924744-200.912481 447.924744-447.924744S758.926256 63.989249 511.913993 63.989249zM511.913993 895.677474c-211.577356 0-383.763481-172.186125-383.763481-383.763481 0-211.577356 172.014111-383.763481 383.763481-383.763481s383.763481 172.014111 383.763481 383.763481S723.491349 895.677474 511.913993 895.677474z" fill="#575B66" p-id="4371"></path><path d="M672.05913 511.913993l-159.973123 0L512.086007 288.123635c0-17.717453-14.277171-32.166639-31.994625-32.166639-17.717453 0-31.994625 14.449185-31.994625 32.166639l0 255.956996c0 17.717453 14.277171 31.994625 31.994625 31.994625l191.967747 0c17.717453 0 32.166639-14.277171 32.166639-31.994625C704.053754 526.191164 689.604569 511.913993 672.05913 511.913993z" fill="#575B66" p-id="4372"></path></svg>
           <span>每天{{hospitalInfo.bookingRule.releaseTime}}放号</span>
         </div>
       </div>
     </div>
     <div class="right">
       <img :src="`data:image/jpeg;base64,${hospitalInfo.logoData}`"
            :alt="`${hospitalInfo.hosname}`"
            class="hospital-img">
<!--       <img src="../../../assets/images/logo.png">-->
     </div>
   </div>
  </el-card>
  </div>
</template>

<style scoped lang="scss">
.content{
  display: flex;
  justify-content: space-between;
  .right{
    img{
      width: 50px;
      height: 50px;
    }
  }
  .left{
    width: 60%;
    .tip{
      color: #7f7f7f;
      margin-top: 20px;
      display: flex;
      justify-content: space-between;
    }
    .level{
      display: flex;
      align-items: center;
      span{
        margin-left: 5px;
      }
    }
    .timer{
      display: flex;
      align-items: center;
      span{
        margin-left: 5px;
      }
    }
  }
}

</style>

首页已有医院数据的TS类型定义 P12

创建syt/src/api/home/type.ts
//定义首页模块ts数据类型
export interface ResponseData{
    code:number,
    message:string,
    ok:boolean
}

//代表已有的医院数据的ts类型
export interface Hospital{
    "id": string,
    "createTime": string,
    "updateTime": string,
    "isDeleted": number,
    "param": {
        "hostypeString": string,
        "fullAddress": string
    },
    "hoscode": string,
    "hosname": string,
    "hostype": string,
    "provinceCode": string,
    "cityCode": string,
    "districtCode": string,
    "address": string,
    "logoData": string,
    "intro": string,
    "route": string,
    "status": number,
    "bookingRule": {
        "cycle": number,
        "releaseTime": string,
        "stopTime": string,
        "quitDay": number,
        "quitTime": string,
        "rule": string[]
    }
}

//存储全部已有医院的数组类型
export type Content = Hospital[]

//获取已有医院接口返回的数据ts类型
export interface HospitalResponseData extends ResponseData{
    data:{
        "content":Content,
        "pageable": {
            "sort": {
                "unsorted": boolean,
                "sorted": boolean,
                "empty": boolean
            },
            "offset": number,
            "pageSize": number,
            "pageNumber": number,
            "unpaged": boolean,
            "paged": boolean
        },
        "last": boolean,
        "totalPages": number,
        "totalElements": number,
        "number": number,
        "size": number,
        "sort": {
            "unsorted": boolean,
            "sorted": boolean,
            "empty": boolean
        },
        "numberOfElements": number,
        "first": boolean,
        "empty": boolean
    }
}

修改syt/src/api/home/index.ts
import {HospitalResponseData} from "@/api/home/type.ts";
export const reqHospital = (page:number,limit:number)=>request.get<any,HospitalResponseData>(API.HOSPITAL_URL+`${page}/${limit}`)

修改syt/src/pages/home/index.vue
import type {Content,HospitalResponseData} from "@/api/home/type.ts";
let hasHospitalArr = ref<Content>([]);
let total= ref<number>(0);
const getHospitalInfo = async ()=>{
  let result: HospitalResponseData = await reqHospital(currentPage.value,pageSize.value);
  if(result.code==200){
    hasHospitalArr.value = result.data.content;
    total = result.data.totalElements
  }
}

首页医院等级与地区数据展示 P13

修改syt/src/api/home/type.ts

增加

export interface HospitalLevelAndRegion{
    "id": number,
    "createTime": string,
    "updateTime": string,
    "isDeleted": number,
    "param": {},
    "parentId": number,
    "name": string,
    "value": string,
    "dictCode": string,
    "hasChildren": boolean
}

export type HospitalLevelAndRegionArr = HospitalLevelAndRegion[];
//获取等级或医院地区接口返回数据类型
export interface HospitalLevelAndRegionResponseData extends ResponseData{
    data:HospitalLevelAndRegionArr
}
修改syt/src/api/home/index.ts
import {HospitalLevelAndRegionResponseData, HospitalResponseData} from "@/api/home/type.ts";
enum API {
    // HOSPITAL_URL = 'hosp/hospital/'     //尚硅谷地址
    HOSPITAL_URL = 'hosp/hospital/findHospitalList/',  //本地地址
    HOSPITALLEAVELANDREGION_URL='/cmn/dict/findByDictCode/'
}
export const reqHospitalLevelAndRegion = (dictCode:string)=>request.get<any,HospitalLevelAndRegionResponseData>(API.HOSPITALLEAVELANDREGION_URL+dictCode);

修改syt/src/pages/home/level/index.vue
<script setup lang="ts">
import {onMounted,ref} from "vue";
import {reqHospitalLevelAndRegion} from "@/api/home";
import {HospitalLevelAndRegionArr, HospitalLevelAndRegionResponseData} from "@/api/home/type.ts";
let levelArr = ref<HospitalLevelAndRegionArr>([]);
let activeFlag = ref<string>('');
onMounted(()=>{
  getLevel();
})
const getLevel = async ()=>{
  let result: HospitalLevelAndRegionResponseData = await reqHospitalLevelAndRegion('Hostype');
  if(result.code==200){
    levelArr.value = result.data;
  }
}
const changeLevel = (level:string)=>{
activeFlag.value = level;
console.log(activeFlag)
}

</script>
<script lang="ts">
export default {
  name:"Level"
}

template中修改

 <ul class="hospital">
        <li :class="{active:activeFlag==''}" @click="changeLevel('')">全部</li>
        <li v-for="level in levelArr" :key="level.value" @click="changeLevel(level.value)" :class="{active:activeFlag==level.value}">{{level.name}}</li>
      </ul>
修改syt/src/pages/home/region/index.vue
<script setup lang="ts">

import {HospitalLevelAndRegionArr, HospitalLevelAndRegionResponseData} from "@/api/home/type.ts";
import {reqHospitalLevelAndRegion} from "@/api/home";
import {onMounted, ref} from "vue";
let regionArr = ref<HospitalLevelAndRegionArr>([]);
let activeFlag = ref<string>('');
onMounted(()=>{
  getRegion();
})
const getRegion = async ()=>{
  let result: HospitalLevelAndRegionResponseData = await reqHospitalLevelAndRegion('Beijing');
  if(result.code==200){
    regionArr.value = result.data;
  }
}
const changeRegion=(region:string)=>{
  activeFlag.value = region;
}
</script>
<script lang="ts">
export default {
  name:"Region"
}
</script>

template中修改

 <ul>
        <li :class="{active:activeFlag==''}" @click="changeRegion('')">全部</li>
        <li v-for="item in regionArr" :key="item.value" @click="changeRegion(item.value)" :class="{active:activeFlag==item.value}">{{item.name}}</li>
</ul>

首页根据等级与地区筛选医院展示 P14

修改syt/src/api/home/index.ts
export const reqHospital = (page:number,limit:number,hostype='',districtCode='')=>request.get<any,HospitalResponseData>(API.HOSPITAL_URL+`${page}/${limit}?hostype=${hostype}&districtCode=${districtCode}`);

修改syt/src/pages/home/index.vue
let hostype= ref<string>('');
let districtCode= ref<string>('');
//子组件自定义事件:获取儿子给父组件传递过来的等级参数
const getLevel=(level:string)=>{
  hostype.value = level;
  getHospitalInfo();
}
//子组件自定义事件:获取儿子给父组件传递过来的地区参数
const getRegion=(region:string)=>{
  districtCode.value = region;
  getHospitalInfo();
}


<level @getLevel="getLevel"/>
<Region @getRegion="getRegion"/>

<div class="hospital" v-if="hashasHospitalArr.length>0">
        <Card class="item" v-for="(item,index) in hashasHospitalArr" :key="item.id" :hospitalInfo="item"/>
      </div>
 <el-empty v-else description="暂无数据" />

空状态组件

https://element-plus.org/zh-CN/component/empty.html

修改syt/src/pages/home/level/index.vue
const changeLevel = (level:string)=>{
activeFlag.value = level;
//触发自定义事件:将医院等级参数回传给父组件
  $emit('getLevel',level);
}
let $emit = defineEmits(['getLevel'])
修改syt/src/pages/home/region/index.vue
const changeRegion=(region:string)=>{
  activeFlag.value = region;
  //给父组件传递区域的参数
  $emit('getRegion',region);
}
let $emit = defineEmits(['getRegion'])

首页根据关键字搜索医院P15

修改syt/src/api/home/index.ts
import {HospitalInfo, HospitalLevelAndRegionResponseData, HospitalResponseData} from "@/api/home/type";
    HOSPITALINFO_URL='/hosp/hospital/findByHosname/'
//根据关键字获取医院的数据进行展示
export const reqHospitalInfo = (hosname:string)=>request.get<any,HospitalInfo>(API.HOSPITALINFO_URL+hosname);
修改syt/src/api/home/type.ts

export interface HospitalInfo extends ResponseData{
    data:Content
}
修改syt/src/pages/home/search/index.vue
<script setup lang="ts">
import {Search} from "@element-plus/icons-vue";
import {ref} from "vue";
import {reqHospitalInfo} from "@/api/home";
import type {HospitalInfo,Content} from "@/api/home/type";
import {useRouter} from "vue-router";
//收集搜索的关键字(医院的名字)
let hosname=ref<string>('');
//创建路由对象
let $router = useRouter();
//顶部组件的回调
const fetchData= async (keyword:string,cb:any)=>{
  let result: HospitalInfo = await reqHospitalInfo(keyword);
  let showData = result.data.map(item=>{
    return {
      value:item.hosname,
      hoscode:item.hoscode
    }
  })
  cb(showData);
}
//点击某一个推荐项
const goDetail =(item:any)=>{
  console.log(item);
  //点击推荐项目进行人民医院详情页,将来需要携带query参数
  $router.push({path:'/hospital'})
}

</script>
<script lang="ts">
export default {
  name:"Search"
}
    <template>
<div class="search">
  <el-autocomplete
      :trigger-on-focus="false"
      clearable
      class="inline-input w-50"
      placeholder="请输入医院名称"
      :v-model="hosname"
      :fetchSuggestions="fetchData"
      @select="goDetail"
  />
  <el-button type="primary" :icon="Search">搜索</el-button>
</div>
</template>
修改syt/src/pages/home/card/index.vue
<script setup lang="ts">
let propos = defineProps(['hospitalInfo'])
import {useRouter} from "vue-router";
//创建路由对象
let $router = useRouter();
const goDetail = ()=>{
  console.log(propos.hospitalInfo);
  $router.push({path:'/hospital'})
}
</script>
<el-card class="box-card" shadow="hover" @click="goDetail">
修改syt/src/components/hospital_top/index.vue
<script setup lang="ts">
import {useRouter} from "vue-router";
let $router = useRouter();
const goHome = ()=>{
  //编程式导航跳转到首页
  $router.push({path:'/home'})
}
</script>

<template>
<div class="top">
  <div class="content">
<!--   左侧 -->
   <div class="left" @click="goHome">
     <img src="../../assets/images/logo.png" alt="">
     <p>尚医通 预约挂号统一平台</p>
   </div>
<!--   右侧 -->
   <div class="right">
     <p class="help">帮助中心</p>
     <p class="login">登录/注册</p>
   </div>
  </div>
</div>
</template>

首页常见科室静态搭建P16

创建syt/src/pages/home/search/index.vue
<script setup lang="ts">

</script>

<template>
<div class="tip">
<!-- 常见科室的结构 -->
  <div class="department">
    <div class="header">
      <div class="left">
        <svg t="1721686788883" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3781" width="16" height="16"><path d="M400.696008 129.093147c-17.248849 0-31.233352 13.984503-31.233352 31.233352l0 30.470989c0 17.248849 13.984503 31.233352 31.233352 31.233352s31.233352-13.984503 31.233352-31.233352l0-30.470989C431.92936 143.078673 417.944857 129.093147 400.696008 129.093147z" fill="#d81e06" p-id="3782"></path><path d="M623.647823 129.093147c-17.248849 0-31.233352 13.984503-31.233352 31.233352l0 30.470989c0 17.248849 13.985526 31.233352 31.233352 31.233352 17.248849 0 31.233352-13.984503 31.233352-31.233352l0-30.470989C654.881175 143.078673 640.896672 129.093147 623.647823 129.093147z" fill="#d81e06" p-id="3783"></path><path d="M425.695379 312.937269c11.209296 18.047028 41.974997 48.588625 86.152149 48.588625 43.958164 0 75.100442-30.308283 86.573751-48.223305 9.302877-14.528901 5.068436-33.846876-9.455349-43.149752-14.539134-9.307993-33.851992-5.063319-43.149752 9.455349-0.121773 0.198521-13.379729 19.449981-33.968649 19.449981-19.993357 0-32.428573-18.107403-33.271778-19.373233-9.17087-14.417361-28.28009-18.799158-42.829458-9.760295C421.089477 279.028994 416.591023 298.28557 425.695379 312.937269z" fill="#d81e06" p-id="3784"></path><path d="M564.242851 625.945145l-20.278859 0L543.963992 462.486306c0-17.253966-13.985526-31.233352-31.233352-31.233352-17.248849 0-31.233352 13.979386-31.233352 31.233352l0 163.457816-20.283975 0c-45.924959 0-83.289961 37.363979-83.289961 83.289961l0 103.024421c0 45.924959 37.363979 83.289961 83.289961 83.289961l103.029538 0c45.924959 0 83.289961-37.363979 83.289961-83.289961L647.532813 709.234083C647.532813 663.309124 610.168834 625.945145 564.242851 625.945145zM585.066109 812.258505c0 11.286044-9.537214 20.822235-20.822235 20.822235L461.214337 833.080739c-11.286044 0-20.822235-9.537214-20.822235-20.822235L440.392102 709.234083c0-11.286044 9.537214-20.822235 20.822235-20.822235l103.029538 0c11.286044 0 20.822235 9.537214 20.822235 20.822235L585.066109 812.258505z" fill="#d81e06" p-id="3785"></path><path d="M250.808256 625.945145l-17.482163 0 0-266.970354c0-35.483142 28.864398-64.35368 64.343447-64.35368 17.248849 0 31.233352-13.984503 31.233352-31.233352s-13.985526-31.233352-31.233352-31.233352c-69.924559 0-126.810151 56.890708-126.810151 126.820384l0 266.970354-23.079648 0c-45.924959 0-83.289961 37.363979-83.289961 83.289961l0 103.024421c0 45.924959 37.363979 83.289961 83.289961 83.289961l103.029538 0c45.924959 0 83.289961-37.363979 83.289961-83.289961L334.099241 709.234083C334.098217 663.309124 296.734238 625.945145 250.808256 625.945145zM271.630491 812.258505c0 11.286044-9.537214 20.822235-20.822235 20.822235L147.778718 833.080739c-11.286044 0-20.822235-9.537214-20.822235-20.822235L126.956484 709.234083c0-11.286044 9.537214-20.822235 20.822235-20.822235l103.029538 0c11.286044 0 20.822235 9.537214 20.822235 20.822235L271.630491 812.258505z" fill="#d81e06" p-id="3786"></path><path d="M876.565113 625.945145l-21.961174 0 0-266.970354c0-69.929676-56.890708-126.820384-126.815267-126.820384-17.248849 0-31.233352 13.985526-31.233352 31.233352s13.984503 31.233352 31.233352 31.233352c35.483142 0 64.348564 28.869514 64.348564 64.35368l0 266.970354-18.605753 0c-45.924959 0-83.289961 37.363979-83.289961 83.289961l0 103.024421c0 45.924959 37.363979 83.289961 83.289961 83.289961l103.034655 0c45.924959 0 83.289961-37.363979 83.289961-83.289961L959.856098 709.234083C959.854051 663.309124 922.490072 625.945145 876.565113 625.945145zM897.387347 812.258505c0 11.286044-9.537214 20.822235-20.822235 20.822235L773.530458 833.080739c-11.286044 0-20.822235-9.537214-20.822235-20.822235L752.708224 709.234083c0-11.286044 9.537214-20.822235 20.822235-20.822235l103.034655 0c11.286044 0 20.822235 9.537214 20.822235 20.822235L897.387347 812.258505z" fill="#d81e06" p-id="3787"></path></svg>
        <span>常见科室</span>
      </div>
      <div class="right">
        <span>全部</span>
        <svg t="1721686869730" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4858" width="16" height="16"><path d="M357.957 167.176l-49.463 48.973 294.308 296.327-296.782 293.831 49.044 49.381 346.239-342.809z" fill="#848484" p-id="4859"></path></svg>
      </div>
    </div>
    <div class="content">
      <ul>
        <li>神经内科</li>
        <li>消化内科</li>
        <li>呼吸内科</li>
        <li>内科</li>
        <li>神经外科</li>
        <li>妇科</li>
        <li>产科</li>
        <li>儿科</li>
      </ul>
    </div>
  </div>
<!-- 平台公告 -->
  <div class="notice">
    <div class="header">
      <div class="left">
        <svg t="1721687792291" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5901" width="16" height="16"><path d="M836.625965 926.809825a61.44 61.44 0 0 1-61.44-61.44 53.894737 53.894737 0 0 1 0.898246-10.778948l-83.357193-76.889824V682.666667h-68.266667v86.950175a61.44 61.44 0 1 1-66.290526 0V682.666667h-64.314386v35.929824a61.619649 61.619649 0 1 1-65.931228-1.077895V682.666667h-66.290527v94.854737l-45.451228 42.037894a58.026667 58.026667 0 0 1 1.077895 10.958597 61.619649 61.619649 0 1 1-61.619649-61.44 59.823158 59.823158 0 0 1 15.809123 2.335438l23.893333-22.276491v-69.70386a188.990877 188.990877 0 0 1-10.419649-369.178947 255.461053 255.461053 0 0 1 477.14807 0 189.170526 189.170526 0 0 1-4.670877 368.280702v70.602105l62.158596 56.948772a60.541754 60.541754 0 0 1 16.168421-2.155789 61.44 61.44 0 1 1 0 122.88z m-125.754386-157.013334l85.333333 78.865965-1.437193 5.389474a48.864561 48.864561 0 0 0-1.437193 11.317895 43.475088 43.475088 0 0 0 86.950176 0 44.014035 44.014035 0 0 0-44.373334-43.475088 40.780351 40.780351 0 0 0-14.910877 2.694737l-5.389473 1.796491-76.171229-69.70386v-93.417544l7.545264-1.257543a171.205614 171.205614 0 0 0 6.467368-335.764211l-4.850526-1.077895-1.616842-4.670877a237.49614 237.49614 0 0 0-446.787369 0l-1.616842 4.670877-4.850526 1.077895A171.205614 171.205614 0 0 0 305.403509 663.084912l7.545263 1.077895v92.878597l-37.367018 34.67228-5.030175-1.796491a49.762807 49.762807 0 0 0-15.270175-2.874386 43.834386 43.834386 0 1 0 41.858245 31.438597l-1.257544-5.209825 47.427369-43.475088V664.701754h102.220351v62.877193l-5.030176 2.515088a42.756491 42.756491 0 0 0-24.432281 38.98386 43.475088 43.475088 0 1 0 63.955088-38.624562l-4.491228-2.515087V664.701754h100.244211v115.514386l-4.670878 2.515088a43.475088 43.475088 0 1 0 39.882106 0l-4.850527-2.515088V664.701754h104.196492z m5.928421-134.916491H330.015439a141.204211 141.204211 0 0 1-17.964913-281.150877l9.521404-1.077895 2.515088-9.341754a207.674386 207.674386 0 0 1 398.821052 0l2.694737 9.341754 9.341754 1.077895a141.204211 141.204211 0 0 1-17.964912 281.150877zM523.497544 211.806316A190.607719 190.607719 0 0 0 341.333333 348.339649l-5.748772 20.659649-21.198596 2.515088a123.239298 123.239298 0 0 0 15.629474 245.400702h386.604912a123.239298 123.239298 0 0 0 16.168421-245.400702l-21.198597-2.515088-5.928421-20.659649a190.607719 190.607719 0 0 0-182.16421-136.533333z" fill="#1296db" p-id="5902"></path><path d="M646.736842 557.990175H387.862456a9.162105 9.162105 0 0 1-8.982456-8.982456 8.982456 8.982456 0 0 1 8.982456-8.982456H646.736842a8.982456 8.982456 0 0 1 8.982456 8.982456 8.982456 8.982456 0 0 1-8.982456 8.982456zM646.736842 490.621754h-129.706667a8.982456 8.982456 0 0 1-8.982456-8.982456 9.162105 9.162105 0 0 1 8.982456-8.982456H646.736842a8.982456 8.982456 0 0 1 8.982456 8.982456 8.982456 8.982456 0 0 1-8.982456 8.982456zM646.736842 423.432982h-129.706667a9.162105 9.162105 0 0 1-8.982456-8.982456 8.982456 8.982456 0 0 1 8.982456-8.982456H646.736842a8.982456 8.982456 0 0 1 8.982456 8.982456 8.982456 8.982456 0 0 1-8.982456 8.982456z" fill="#1296db" p-id="5903"></path><path d="M387.862456 410.857544m26.408421 0l32.87579 0q26.408421 0 26.408421 26.408421l0 32.875789q0 26.408421-26.408421 26.408421l-32.87579 0q-26.408421 0-26.408421-26.408421l0-32.875789q0-26.408421 26.408421-26.408421Z" fill="#1296db" p-id="5904"></path></svg>
        <span>平台公告</span>
      </div>
      <div class="right">
          <span>全部</span>
          <svg t="1721686869730" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4858" width="16" height="16"><path d="M357.957 167.176l-49.463 48.973 294.308 296.327-296.782 293.831 49.044 49.381 346.239-342.809z" fill="#848484" p-id="4859"></path></svg>
      </div>
    </div>
    <div class="content">
      <ul>
        <li>关于延长北京大学国际医院放假的通知</li>
        <li>北京中医药大学东方医院部分科室医生门诊医</li>
        <li>武警总医院号源暂停更新通知</li>
      </ul>
    </div>
  </div>
<!-- 停诊公告 -->
  <div class="notice">
    <div class="header">
      <div class="left">
        <svg t="1721688425278" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6974" width="16" height="16"><path d="M736 768h-448a32 32 0 0 1 0-64h448a32 32 0 0 1 0 64z m-44.8-415.552l-147.669333-147.669333a61.994667 61.994667 0 0 1-62.848 0l-147.669334 147.669333a32.554667 32.554667 0 0 1-46.037333-46.037333l161.578667-161.578667a63.68 63.68 0 0 1 127.104 0l161.578666 161.578667a32.554667 32.554667 0 0 1-46.037333 46.037333zM565.333333 512a32 32 0 0 1 0 64h-277.333333a32 32 0 0 1 0-64h277.333333z" fill="#1296db" p-id="6975"></path><path d="M853.333333 981.333333H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V384a85.333333 85.333333 0 0 1 85.333334-85.333333h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333333v512a85.333333 85.333333 0 0 1-85.333334 85.333333z m21.333334-576a42.666667 42.666667 0 0 0-42.666667-42.666666H192a42.666667 42.666667 0 0 0-42.666667 42.666666v469.333334a42.666667 42.666667 0 0 0 42.666667 42.666666h640a42.666667 42.666667 0 0 0 42.666667-42.666666V405.333333z" fill="#1296db" p-id="6976"></path></svg>
        <span>停诊公告</span>
      </div>
      <div class="right">
        <span>全部</span>
        <svg t="1721686869730" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4858" width="16" height="16"><path d="M357.957 167.176l-49.463 48.973 294.308 296.327-296.782 293.831 49.044 49.381 346.239-342.809z" fill="#848484" p-id="4859"></path></svg>
      </div>
    </div>
    <div class="content">
      <ul>
        <li>中国人民解放军总医院第六医学中心(原海军总医院)呼吸内科门诊停诊公告</li>
        <li>首都医科大学附属北京潞河医院老年医学科门诊停诊公告</li>
        <li>中日友好医院中西医结合心内科门诊停诊公告</li>
      </ul>
    </div>
  </div>
</div>
</template>

<style scoped lang="scss">
.tip{
  color: #7f7f7f;
  .department{
    .header{
      display: flex;
      justify-content: space-between;
      .left{
        display:flex;
        span{
          margin-left:5px;
        }
      }
    }
    .content{
      ul{
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        li{
          width: 40%;
          margin-top: 20px;
        }
      }
    }
  }
  .notice{
    margin-top: 20px;
    .header{
      display: flex;
      justify-content: space-between;
      .left{
        display:flex;
        span{
          margin-left:5px;
        }
      }
    }
    .content{
      ul{
        li{
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
          margin-top:20px
        }
      }
    }
  }
}
</style>

修改syt/src/pages/home/index.vue
import Tip from './tip/index.vue'
<el-col :span="4"><Tip/></el-col>

医院详情菜单与子路由P17

创建syt/src/pages/hospital/close/index.vue
<script setup lang="ts">

</script>

<template>
<div>停诊信息</div>
</template>

<style scoped lang="scss">

</style>
创建syt/src/pages/hospital/detail/index.vue
<script setup lang="ts">

</script>

<template>
<div>
  医院详情
</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/hospital/notice/index.vue
<script setup lang="ts">

</script>

<template>
<div>
  预约须知
</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/hospital/register/index.vue
<script setup lang="ts">

</script>

<template>
<div>
  预约挂号
</div>
</template>

<style scoped lang="scss">

</style>
创建syt/src/pages/hospital/search/index.vue
<script setup lang="ts">

</script>

<template>
<div>查询与取消</div>
</template>

<style scoped lang="scss">

</style>

修改syt/src/pages/hospital/index.vue
<script setup lang="ts">
import {
  Document,
  Menu as IconMenu,
  InfoFilled,
    Search,
    HomeFilled,
  Setting,
} from '@element-plus/icons-vue'
import {useRouter,useRoute} from "vue-router";
let $router = useRouter();
let $route = useRoute();
const changeActive = (path:string)=>{
  $router.push({path});
}
</script>

<template>
<div class="hospital" >
<!-- 左侧导航区域 -->
  <div class="menu">
    <div class="top">
    <el-icon><HomeFilled/></el-icon>
      <span> / 医院信息</span>
    </div>
    <el-menu
        class="el-menu-vertical-demo"
        :default-active="$route.path"
    >
      <el-menu-item index="/hospital/register" @click="changeActive('/hospital/register')">
        <el-icon><icon-menu /></el-icon>
        <span>预约挂号</span>
      </el-menu-item>
      <el-menu-item index="/hospital/detail"  @click="changeActive('/hospital/detail')">
        <el-icon><document /></el-icon>
        <span>医院详情</span>
      </el-menu-item>
      <el-menu-item index="/hospital/notice"  @click="changeActive('/hospital/notice')">
        <el-icon><setting /></el-icon>
        <span>预约须知</span>
      </el-menu-item>
      <el-menu-item index="/hospital/close" @click="changeActive('/hospital/close')">
        <el-icon><InfoFilled /></el-icon>
        <span>停诊信息</span>
      </el-menu-item>
      <el-menu-item index="/hospital/search" @click="changeActive('/hospital/search')">
        <el-icon><Search /></el-icon>
        <span>查询与取消</span>
      </el-menu-item>
    </el-menu>
  </div>
<!-- 右侧内容展示区域:路由组件展示位置 -->
  <div class="content">
    <router-view></router-view>
  </div>
</div>
</template>

<style scoped lang="scss">
.hospital{
  display: flex;
  .menu{
    flex: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    .top{
      color: #7f7f7f;
    }
  }
  .content{
    flex: 8;
  }
}
</style>

修改syt/src/router/index.ts
 {
            path:'/hospital',
            component:()=>import((`@/pages/hospital/index.vue`)),
            children:[
                {
                    path:'register',
                    component:()=>import(('@/pages/hospital/register/index.vue'))
                },
                {
                    path:'detail',
                    component:()=>import(('@/pages/hospital/detail/index.vue'))
                },
                {
                    path:'notice',
                    component:()=>import(('@/pages/hospital/notice/index.vue'))
                },
                {
                    path:'close',
                    component:()=>import(('@/pages/hospital/close/index.vue'))
                },
                {
                    path:'search',
                    component:()=>import(('@/pages/hospital/search/index.vue'))
                }
            ]
        },
修改syt/src/pages/home/search/index.vue
//点击某一个推荐项
const goDetail =(item:any)=>{
  console.log(item);
  //点击推荐项目进行人民医院详情页,将来需要携带query参数
  $router.push({path:'/hospital/register'})
}
修改syt/src/pages/home/card/index.vue
const goDetail = ()=>{
  console.log(propos.hospitalInfo);
  $router.push({path:'/hospital/register'})
}

Pinia仓库存储医院详情数据P18

创建syt/src/api/hospital/type.ts
//定义详情模块数据ts类型
export interface ResponseData{
    code:number,
    message:string,
    ok:boolean
}

//代表医院详情的数据
export interface HospitalDetail{
    "bookingRule": {
        "cycle": number,
        "releaseTime": string,
        "stopTime": string,
        "quitDay": number,
        "quitTime": string,
        "rule": string[]
    },
    "hospital": {
        "id": string,
        "createTime": string,
        "updateTime": string,
        "isDeleted": 0,
        "param": {
            "hostypeString": string,
            "fullAddress": string
        },
        "hoscode": string,
        "hosname": string,
        "hostype": string,
        "provinceCode": string,
        "cityCode": string,
        "districtCode": string,
        "address": string,
        "logoData": string,
        "intro": string,
        "route": string,
        "status": number,
        "bookingRule": null
    }
}
//医院详情返回数据ts
export interface HospitalDetail extends ResponseData{
    data:HospitalDetail
}


创建syt/src/api/hospital/index.ts
import request from "@/utils/request.ts";
import {HospitalDetail} from "@/api/hospital/type.ts";


enum API{
    HOSPITALDETAIL_URl='/hosp/hosptal'
}
export const reqHospitalDetail = (hoscode:string)=>request.get<any,HospitalDetail>(API.HOSPITALDETAIL_URl+hoscode)

安装pinia

npm i pinia
修改syt/src/main.ts
//引入pinia仓库
import pinia from '@/store'
//安装pinia仓库
app.use(pinia);
创建syt/src/store/index.ts
import {createPinia} from "pinia";
export default createPinia();
创建syt/src/store/modules/hospitalDetail.ts
import {defineStore} from "pinia";
//pinia仓库写法:组合式API、选择式API写法

import {reqHospitalDetail} from "@/api/hospital/index.ts"
import type {HospitalDetail} from "@/api/hospital/type.ts";

const useDetailStore = defineStore('Detail',{
    state:()=>{
        return {
            //医院详情数据
            hospitalInfo:{},
        }
    },
    actions:{
    //获取医院详情的方法
        async getHospital(hoscode:string){
           let result:HospitalDetail =  await reqHospitalDetail(hoscode);
           if(result.code==200){
               this.hospitalInfo = result.data
           }
        }
    },
    getters:{

    }
})

export default useDetailStore

完成预约挂号业务P19

修改syt/src/store/modules/hospitalDetail.ts
import {DetailState} from "@/store/modules/interface";
 state:():DetailState=>{
        return {
            //医院详情数据
            hospitalInfo:({} as HospitalDetail),
        }
    },
创建syt/src/store/modules/interface/index.ts

import type {HospitalDetail} from "@/api/hospital/type.ts";

//定义仓库内部存储数据state的ts类型
export interface DetailState{
    hospitalInfo:HospitalDetail
}

修改syt/src/pages/hospital/register/index.vue
<script setup lang="ts">
import useDetailStore from '@/store/modules/hospitalDetail.ts'
let hospitalStore = useDetailStore();
</script>

<template>
<div class="register">
  <div class="top">
    <div class="title">{{hospitalStore.hospitalInfo.hospital?.hosname}}</div>
    <div class="level">
      <svg t="1721959943048" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8112" width="16" height="16"><path d="M621.674667 408.021333c16.618667-74.24 28.224-127.936 34.837333-161.194666C673.152 163.093333 629.941333 85.333333 544.298667 85.333333c-77.226667 0-116.010667 38.378667-138.88 115.093334l-0.586667 2.24c-13.728 62.058667-34.72 110.165333-62.506667 144.586666a158.261333 158.261333 0 0 1-119.733333 58.965334l-21.909333 0.469333C148.437333 407.808 106.666667 450.816 106.666667 503.498667V821.333333c0 64.8 52.106667 117.333333 116.394666 117.333334h412.522667c84.736 0 160.373333-53.568 189.12-133.92l85.696-239.584c21.802667-60.96-9.536-128.202667-70.005333-150.186667a115.552 115.552 0 0 0-39.488-6.954667H621.674667zM544.256 149.333333c39.253333 0 59.498667 36.48 49.888 84.928-7.573333 38.144-21.984 104.426667-43.221333 198.666667-4.512 20.021333 10.56 39.093333 30.912 39.093333h218.666666c6.101333 0 12.16 1.066667 17.909334 3.168 27.445333 9.984 41.674667 40.554667 31.776 68.266667l-85.568 239.573333C744.981333 838.026667 693.301333 874.666667 635.402667 874.666667H223.498667C194.314667 874.666667 170.666667 850.784 170.666667 821.333333V503.498667c0-17.866667 14.144-32.448 31.829333-32.821334l21.866667-0.469333a221.12 221.12 0 0 0 167.381333-82.56c34.346667-42.602667 59.146667-99.306667 74.869333-169.877333C482.101333 166.336 499.552 149.333333 544.266667 149.333333z" fill="#d81e06" p-id="8113"></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="`${hospitalStore.hospitalInfo.hospital?.hosname}`"
           class="hospital-img">
    </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>
</template>

<style scoped lang="scss">

.register{
  .top{
    display: flex;
    .title{
      font-size: 30px;
    }
    .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;
        border-radius: 50%;
      }
    }
    .right{
      flex: 1;
      margin-left: 20px;
      .time,.address,.route,.releasetime,.ruleInfo{
        margin-top: 10px;
        color: #7f7f7f;
      }
      .rule{
        margin:10px 0px;
      }
    }
  }
}

</style>

完成医院详情业务模块P20

修改syt/src/pages/hospital/detail/index.vue
<script setup lang="ts">
import useDetailStore from '@/store/modules/hospitalDetail.ts'
let hospitalStore = useDetailStore();
</script>

<template>
<div class="detail">
<!-- 医院名字与医院等级的结构 -->
  <div class="top">
    <div class="name">{{hospitalStore.hospitalInfo.hospital?.hosname}}</div>
    <div class="level">
      <svg t="1721959943048" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8112" width="16" height="16"><path d="M621.674667 408.021333c16.618667-74.24 28.224-127.936 34.837333-161.194666C673.152 163.093333 629.941333 85.333333 544.298667 85.333333c-77.226667 0-116.010667 38.378667-138.88 115.093334l-0.586667 2.24c-13.728 62.058667-34.72 110.165333-62.506667 144.586666a158.261333 158.261333 0 0 1-119.733333 58.965334l-21.909333 0.469333C148.437333 407.808 106.666667 450.816 106.666667 503.498667V821.333333c0 64.8 52.106667 117.333333 116.394666 117.333334h412.522667c84.736 0 160.373333-53.568 189.12-133.92l85.696-239.584c21.802667-60.96-9.536-128.202667-70.005333-150.186667a115.552 115.552 0 0 0-39.488-6.954667H621.674667zM544.256 149.333333c39.253333 0 59.498667 36.48 49.888 84.928-7.573333 38.144-21.984 104.426667-43.221333 198.666667-4.512 20.021333 10.56 39.093333 30.912 39.093333h218.666666c6.101333 0 12.16 1.066667 17.909334 3.168 27.445333 9.984 41.674667 40.554667 31.776 68.266667l-85.568 239.573333C744.981333 838.026667 693.301333 874.666667 635.402667 874.666667H223.498667C194.314667 874.666667 170.666667 850.784 170.666667 821.333333V503.498667c0-17.866667 14.144-32.448 31.829333-32.821334l21.866667-0.469333a221.12 221.12 0 0 0 167.381333-82.56c34.346667-42.602667 59.146667-99.306667 74.869333-169.877333C482.101333 166.336 499.552 149.333333 544.266667 149.333333z" fill="#d81e06" p-id="8113"></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="`${hospitalStore.hospitalInfo.hospital?.hosname}`"
           class="hospital-img">
    </div>
    <div class="right">
      <div class="address">
      <svg t="1722032845308" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9200" width="16" height="16"><path d="M504.448 519.552m-151.189333 0a151.189333 151.189333 0 1 0 302.378666 0 151.189333 151.189333 0 1 0-302.378666 0Z" fill="#111111" p-id="9201"></path><path d="M504.448 124.714667c-218.069333 0-394.858667 176.768-394.858667 394.837333s176.789333 394.858667 394.858667 394.858667 394.837333-176.789333 394.837333-394.858667S722.517333 124.714667 504.448 124.714667z m0 64c182.72 0 330.837333 148.117333 330.837333 330.837333s-148.117333 330.858667-330.837333 330.858667S173.589333 702.272 173.589333 519.552c0-182.72 148.138667-330.837333 330.858667-330.837333z" fill="#111111" p-id="9202"></path><path d="M519.552 10.666667a32 32 0 0 1 32 32v98.922666a32 32 0 1 1-64 0V42.666667a32 32 0 0 1 32-32zM519.552 850.410667a32 32 0 0 1 32 32V981.333333a32 32 0 0 1-64 0v-98.922666a32 32 0 0 1 32-32z" fill="#111111" p-id="9203"></path><path d="M10.666667 519.552a32 32 0 0 1 32-32h98.922666a32 32 0 1 1 0 64H42.666667a32 32 0 0 1-32-32zM850.410667 519.552a32 32 0 0 1 32-32H981.333333a32 32 0 0 1 0 64h-98.922666a32 32 0 0 1-32-32z" fill="#111111" p-id="9204"></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>

<style scoped lang="scss">
.detail {
  .top {
    display: flex;

    .name {
      font-size: 30px;
      font-weight: 900;
    }

    .level {
      color: #7f7f7f;
      margin-left: 10px;
      height: 40px;
      text-align: center;
      line-height: 40px;

      span {
        margin-left: 5px;
      }
    }
  }
  .logo {
    display: flex;
    margin:10px 0px;
    .left {
      width: 80px;
      img {
        width: 80px;
        height: 80px;
        border-radius: 50%;
      }
    }
    .right{
      flex: 1;
      margin-left: 10px;
      color: #7f7f7f;
      div{
        margin :10px 0px;
      }
      .rule{

      }
    }
  }
  .footer{
    color: #7f7f7f;
    p{
      margin-top: 10px;
      line-height: 30px;
    }
  }
}
</style>

修改syt/src/pages/hospital/index.vue
const changeActive = (path:string)=>{
  $router.push({path,query:{hoscode:$route.query.hoscode}});
}

完成预约通知业务P21

修改syt/src/pages/hospital/notice/index.vue
<script setup lang="ts">
import useDetailStore from '@/store/modules/hospitalDetail.ts'
let hospitalStore = useDetailStore();
</script>

<template>
<div class="notice">
  <h1 class="hosname">{{hospitalStore.hospitalInfo.hospital?.hosname}}预约挂号须知</h1>
  <div class="top">为方便您早日就医康复。请您认真阅读预约挂号须知:</div>
  <div class="info">
    <div class="tip">一、预约实名制:</div>
    <div class="text">
      统一平台电话预约和网上预约挂号均采取实名制注册预约,请您如实提供就诊人员的真实姓名、有效证件号(身份证、护照)、性别、手机号码、社保卡号等基本信息。
    </div>
  </div>
  <div class="info">
    <div class="tip">二、预约挂号</div>
    <div class="text">
      按照北京市卫健委统一平台要求,预约挂号规则如下:
      <ul>
        <li>在同一自然日,同一医院,同一科室,同一就诊单元,同一就诊人,可以预约最多1个号源;</li>
        <li>在同一自然周,同一就诊人,可以预约最多8个号源;</li>
        <li>在同一自然月,同一就诊人,可以预约最多12个号源;</li>
        <li>在同一自然季度,同一就诊人,可以预约最多24个号源。</li>
      </ul>
    </div>
  </div>
  <div class="info">
    <div class="tip">三、取消预约:</div>
    <div class="text">已完成预约的号源,如需办理退号,至少在就诊前一工作日14:00前通过网站、微信公众号、114电话等平台预约渠道进行取消预约。
      </div>
  </div>
    <div class="info">
      <div class="tip">
            四、爽约处理:
      </div>
      <div class="text">如预约成功后患者未能按时就诊且不办理取消预约号视为爽约,同一患者在自然年内爽约规则如下:
            <ul>
              <li>累计爽约3次,自3次爽约日起,90天内不允许通过114平台进行预约挂号;</li>
              <li>累计爽约6次,自6次爽约日起,180天内不允许通过114平台进行预约挂号。</li>
            </ul>
      </div>
    </div>
</div>
</template>

<style scoped lang="scss">
.notice{
  .hosname{
    text-align: center;
    font-size: 30px;
  }
  .top{
    color: #7f7f7f;
    margin: 10px 0px;
  }
  .info{
    .tip{
      color: red;
      font-weight: 900;
    }
    .text{
      color: #7f7f7f;
      margin: 10px 0px;
      line-height: 30px;
    }
  }

}
</style>

修改syt/src/pages/hospital/close/index.vue
<script setup lang="ts">
import useDetailStore from '@/store/modules/hospitalDetail.ts'
let hospitalStore = useDetailStore();
</script>

<template>
<div>
  <div class="info">
    <h1>{{hospitalStore.hospitalInfo.hospital?.hosname}}停诊信息</h1>
    <el-empty description="暂无信息"></el-empty>
  </div>
</div>
</template>

<style scoped lang="scss">
.info{
  h1{
    text-align: center;
  }
}
</style>

修改syt/src/pages/hospital/search/index.vue
<script setup lang="ts">
import useDetailStore from '@/store/modules/hospitalDetail.ts'
let hospitalStore = useDetailStore();
</script>

<template>
  <div>
    <div class="info">
      <h1>{{hospitalStore.hospitalInfo.hospital?.hosname}}查询信息</h1>
      <el-empty description="暂无信息"></el-empty>
    </div>
  </div>
</template>

<style scoped lang="scss">
.info{
  h1{
    text-align: center;
  }
}
</style>

完成医院科室业务P22

修改syt/src/api/hospital/index.ts
import {DepartmentResponseData, HospitalDetail} from "@/api/hospital/type.ts";
enum API{
    HOSPITALDETAIL_URl='/hosp/hospital/',
    //获取某一个医院的科室数据
    HOSPITALDEPARTMENT_URL='hosp/hospital/department/'
}
//获取医院科室的接口
export const reqHospitalDepartment = (hoscode:string)=>request.get<any,DepartmentResponseData>(API.HOSPITALDEPARTMENT_URL+hoscode);
修改syt/src/api/hospital/type.ts
//代表医院科室的数据
export interface Department{
    "depcode": string,
    "depname": string,
    "children": Department[]
}

//代表存储科室数组类型
export type DepartmentArr = Department[]

//获取科室接口返回的数据类型
export interface DepartmentResponseData extends ResponseData{
    data:DepartmentArr
}
修改syt/src/pages/hospital/index.vue
//获取某一个医院科室的数据
  detailStore.getDepartment($route.query.hoscode as string);
修改syt/src/pages/hospital/register/index.vue

template

<!-- 放置每一个医院的科室的数据 -->
  <div class="department">
    <div class="leftNav">
      <ul>
        <li @click="changeIndex(index)" :class="{active:index===currentIndex}" v-for="(department,index) in hospitalStore.departmentArr" :key="department.depcode">
          {{department.depname}}
        </li>
      </ul>
    </div>
    <div class="departmentInfo">
<!--   用一个div代表:大科室与小科室   -->
      <div class="showDepartment" v-for="(department,index) in hospitalStore.departmentArr" :key="department.depcode">
        <h1 class="cur">{{department.depname}}</h1>
<!--    每个大科室下的小科室    -->
        <ul>
          <li v-for="item in department.children" :key="item.depcode">{{item.depname}}</li>
        </ul>
      </div>
    </div>
  </div>

style

.department{
    width: 100%;
    height: 500px;
    display: flex;
    margin-top: 20px;
    .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: 40px;
        &.active{
          border-left: 1px solid red;
          color: red;
          background: white;
        }
      }
      }
    }
    .departmentInfo{
      flex: 1;
      margin-left: 20px;
      height: 100%;
      overflow: auto;
      &::-webkit-scrollbar{
        display: none;
      }
      .showDepartment{
        h1{
          background: rgb(248,248,248);
          color: #7f7f7f;
          margin-top: 20px;
        }
        ul{
          display: flex;
          flex-wrap: wrap;
          li{
            color: #7f7f7f;
            width: 33%;
            margin-top: 20px;
          }
        }
      }
    }
  }

script

//控制科室高亮的响应式数据
let currentIndex = ref<number>(0);
const changeIndex = (index:number)=>{
  currentIndex.value = index;
  //点击导航获取右侧科室(大科室的h1标题)
  let allH1 = document.querySelectorAll('.cur');
  //滚动到对应科室的位置
  allH1[currentIndex.value].scrollIntoView({
    behavior:'smooth',//过渡动画效果
    block:'start'//滚动到位置,默认起始位置
  })

完成login对话框的显示与隐藏P23

创建syt/src/components/login/index.vue
<script setup lang="ts">
import useUserStore from "@/store/modules/user.ts";
let userStore = useUserStore();
</script>
<script lang="ts">
export default {
  name:'Login'
}
</script>
<template>
<div class="login_container">
  <el-dialog title="用户登录" v-model="userStore.visible"></el-dialog>
</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/store/modules/user.ts
//定义用户相关的仓库
import {defineStore} from "pinia";

const useUserStore = defineStore('User',{
    state:()=>{
        return{
            visible:false//用于控制登录组件的dialog显示与隐藏
        }
    },
    actions:{

    },
    getters:{

    }
})

export default useUserStore;

修改syt/src/components/hospital_top/index.vue
import useUserStore from "@/store/modules/user.ts";
let useStore = useUserStore();
const login = ()=>{
  useStore.visible=true;
}
 <p class="login" @click="login">登录/注册</p>
修改syt/src/pages/hospital/register/index.vue
import useUserStore from "@/store/modules/user.ts";
let useStore = useUserStore();
const showLogin=()=>{
  useStore.visible=true;
}
<!--    每个大科室下的小科室    -->
<ul>
  <li v-for="item in department.children" :key="item.depcode" @click="showLogin">{{item.depname}}</li>
</ul>

登录组件静态搭建与场景的切换P24

修改syt/src/components/login/index.vue
<script setup lang="ts">
import useUserStore from "@/store/modules/user.ts";
import {User,Lock} from "@element-plus/icons-vue";
import {ref} from "vue";
let scene = ref<number>(0);//0代表手机号码登录 1代表微信扫码登录

//点击微信扫码登录|微信小图标切换为微信扫码登录
const changeScene = ()=>{
  scene.value=1
}
let userStore = useUserStore();
</script>
<script lang="ts">
export default {
  name:'Login'
}
</script>
<template>
<div class="login_container">
  <el-dialog title="用户登录" v-model="userStore.visible">
    <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 style="width: 100%" type="primary" size="default" >用户登录</el-button>
             <div class="bottom" @click="changeScene">
                 <p>微信扫码登录</p>
                 <svg t="1722215745147" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10455" width="32" height="32"><path d="M337.387283 341.82659c-17.757225 0-35.514451 11.83815-35.514451 29.595375s17.757225 29.595376 35.514451 29.595376 29.595376-11.83815 29.595376-29.595376c0-18.49711-11.83815-29.595376-29.595376-29.595375zM577.849711 513.479769c-11.83815 0-22.936416 12.578035-22.936416 23.6763 0 12.578035 11.83815 23.676301 22.936416 23.676301 17.757225 0 29.595376-11.83815 29.595376-23.676301s-11.83815-23.676301-29.595376-23.6763zM501.641618 401.017341c17.757225 0 29.595376-12.578035 29.595376-29.595376 0-17.757225-11.83815-29.595376-29.595376-29.595375s-35.514451 11.83815-35.51445 29.595375 17.757225 29.595376 35.51445 29.595376zM706.589595 513.479769c-11.83815 0-22.936416 12.578035-22.936416 23.6763 0 12.578035 11.83815 23.676301 22.936416 23.676301 17.757225 0 29.595376-11.83815 29.595376-23.676301s-11.83815-23.676301-29.595376-23.6763z" fill="#28C445" p-id="10456"></path><path d="M510.520231 2.959538C228.624277 2.959538 0 231.583815 0 513.479769s228.624277 510.520231 510.520231 510.520231 510.520231-228.624277 510.520231-510.520231-228.624277-510.520231-510.520231-510.520231zM413.595376 644.439306c-29.595376 0-53.271676-5.919075-81.387284-12.578034l-81.387283 41.433526 22.936416-71.768786c-58.450867-41.433526-93.965318-95.445087-93.965317-159.815029 0-113.202312 105.803468-201.988439 233.803468-201.98844 114.682081 0 216.046243 71.028902 236.023121 166.473989-7.398844-0.739884-14.797688-1.479769-22.196532-1.479769-110.982659 1.479769-198.289017 85.086705-198.289017 188.67052 0 17.017341 2.959538 33.294798 7.398844 49.572255-7.398844 0.739884-15.537572 1.479769-22.936416 1.479768z m346.265896 82.867052l17.757225 59.190752-63.630058-35.514451c-22.936416 5.919075-46.612717 11.83815-70.289017 11.83815-111.722543 0-199.768786-76.947977-199.768786-172.393063-0.739884-94.705202 87.306358-171.653179 198.289017-171.65318 105.803468 0 199.028902 77.687861 199.028902 172.393064 0 53.271676-34.774566 100.624277-81.387283 136.138728z" fill="#28C445" p-id="10457"></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="1722216391237" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10638" width="16" height="16"><path d="M0 0v1024h1024V0z m1004.307692 1004.307692H19.692308V19.692308h984.615384z" fill="#8a8a8a" p-id="10639"></path><path d="M415.113846 324.923077c-135.876923 24.024615-181.169231 177.624615-73.255384 248.123077 5.907692 3.544615 5.907692 3.150769-2.756924 29.538461l-7.876923 22.843077 27.569231-14.572307 27.569231-14.572308 14.178461 3.544615a244.184615 244.184615 0 0 0 48.049231 6.301539h7.483077l-2.756923-10.24c-21.267692-78.769231 52.381538-157.538462 148.48-157.538462h12.996923l-2.756923-9.058461a171.716923 171.716923 0 0 0-196.923077-105.55077z m-11.815384 68.923077a24.024615 24.024615 0 1 1-32.295385 6.695384 25.206154 25.206154 0 0 1 32.295385-6.695384z m115.790769 0a23.630769 23.630769 0 1 1-24.418462 0 26.781538 26.781538 0 0 1 24.418462 0z m52.381538 58.289231a124.849231 124.849231 0 0 0-111.458461 122.092307c3.544615 78.769231 95.704615 132.726154 184.32 108.307693l10.24-2.756923 22.055384 11.815384a175.261538 175.261538 0 0 0 22.449231 11.027692 96.886154 96.886154 0 0 0-5.12-18.904615c-7.089231-21.661538-7.089231-20.48 2.363077-27.175385 106.338462-76.406154 12.996923-229.612308-124.849231-204.406153z m-2.756923 63.40923a18.904615 18.904615 0 0 1 6.301539 20.873847 18.510769 18.510769 0 1 1-6.301539-20.873847z m94.129231 0a18.116923 18.116923 0 0 1-3.150769 29.538462 18.510769 18.510769 0 1 1 3.150769-29.538462z" fill="#8a8a8a" p-id="10640"></path></svg>
              <p>微信扫一扫关注</p>
              <p>快速预约挂号</p>
              </div>
              <div class="item">
                <img src="../../assets/images/code_login_wechat.png" alt=""/>
                <svg t="1722216639576" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11768" width="16" height="16"><path d="M736.653061 929.959184H287.346939c-45.97551 0-83.591837-37.616327-83.591837-83.591837V177.632653c0-45.97551 37.616327-83.591837 83.591837-83.591837h449.306122c45.97551 0 83.591837 37.616327 83.591837 83.591837v668.734694c0 45.97551-37.616327 83.591837-83.591837 83.591837zM287.346939 135.836735c-22.987755 0-41.795918 18.808163-41.795919 41.795918v668.734694c0 22.987755 18.808163 41.795918 41.795919 41.795918h449.306122c22.987755 0 41.795918-18.808163 41.795919-41.795918V177.632653c0-22.987755-18.808163-41.795918-41.795919-41.795918H287.346939z" fill="#333333" p-id="11769"></path><path d="M616.489796 815.020408H407.510204c-11.493878 0-20.897959-9.404082-20.897959-20.897959s9.404082-20.897959 20.897959-20.897959h208.979592c11.493878 0 20.897959 9.404082 20.897959 20.897959s-9.404082 20.897959-20.897959 20.897959z" fill="#333333" p-id="11770"></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>

<style scoped lang="scss">
.login_container{
  ::v-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>

登录组件获取验证码P25

修改syt/src/api/hospital/index.ts
enum API{
    HOSPITALDETAIL_URl='/hosp/hospital/',
    //获取某一个医院的科室数据
    HOSPITALDEPARTMENT_URL='/hosp/hospital/department/',
    //获取验证码接口
    GETUSERCODE_URL = '/sms/send/'
}
    
    //获取验证码接口
export const reqCode=(phone:string)=>request.get(API.GETUSERCODE_URL+phone);
修改syt/src/store/modules/user.ts

将验证码存储仓库中

const useUserStore = defineStore('User',{
    state:()=>{
        return{
            visible:false,//用于控制登录组件的dialog显示与隐藏
            code:'',//存储用户验证码
        }
    },
    actions:{
       async getCode(phone:string){
          let result:any = await reqCode(phone);
          if(result.code==200){
              this.code=result.data;
              return 'ok';
          }else{
              return Promise.reject(new Error(result.message));
          }
        }
    },
    getters:{

    }
})
修改syt/src/components/login/index.vue

template

   <el-form-item>
                 <el-input placeholder="请输入手机号码" :prefix-icon="User" v-model="loginParam.phone"></el-input>
               </el-form-item>
               <el-form-item>
                 <el-input placeholder="请输入手机验证码" :prefix-icon="Lock" v-model="loginParam.code"></el-input>
               </el-form-item>
               <el-form-item>
                 <el-button :disabled="!isPhone?true:false" @click="getCode">获取验证码</el-button>
               </el-form-item>

script

import {computed, reactive, ref} from "vue";
//收集表单数据
let loginParam = reactive({
  phone:'',//收集手机号码
  code:'' //收集验证码
});
//计算出当前表单元素收集的内容是否手机号码格式
let isPhone = computed(()=>{
  const reg = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/;
  return reg.test(loginParam.phone);
});
const getCode= async()=>{
  try {
    await userStore.getCode(loginParam.phone);
    loginParam.code = userStore.code;
  }catch (error){

  }

}

登录组件获取验证码倒计时业务P26

创建syt/src/components/countdaown/index.vue
<script setup lang="ts">
import {ref, watch} from "vue";
let time = ref<number>(5);
//接受父组件传递过来的props->flag:用于控制计数器组件显示与隐藏的
let props = defineProps(['flag']);
let $emit = defineEmits(['getFlag'])
watch(()=>props.flag,()=>{
  let timer = setInterval(()=>{
    time.value--;
    if(time.value==0){
      $emit('getFlag',false);
      clearInterval(timer);
    }
  },1000)
},{
  immediate:true,
})
</script>

<template>
<div>
  <span>获取验证码({{time}}s)</span>
</div>
</template>

<style scoped lang="scss">

</style>

修改syt/src/components/login/index.vue

修改获取验证码按钮的显示:与倒计时组件切换显示

<el-form-item>
                 <el-button :disabled="!isPhone||flag?true:false">
                   <CountDown v-if="flag" :flag="flag" @getFlag="getFlag"/>
                   <span v-else  @click="getCode">获取验证码</span>
                 </el-button>
               </el-form-item>

增加计数器组件自定义事件

//计数器组件自定义事件
//当倒计时为零的时候,通知父组件倒计时组件隐藏
const getFlag = (val:boolean)=>{
  //倒计时模式结束
    flag.value = val;
}

完成登录业务P27

修改syt/src/components/login/index.vue

标签增加点击事件并加禁用条件

<el-button style="width: 100%" type="primary" size="default" @click="login" :disabled="!isPhone||loginParam.code.length<0?true:false">用户登录</el-button>
             

定义登录点击事件

import {ElMessage} from "element-plus";
const getCode= async()=>{
  //解决element-plus按钮禁用还能点击的问题
  if(!isPhone.value||flag.value){
    return;
  }
  //开启倒计时模式,倒计时组件显示出来
  flag.value=true;
  try {
    await userStore.getCode(loginParam.phone);
    // loginParam.code = userStore.code;
  }catch (error){
    ElMessage({
      type: 'error',
      message: (error as Error).message
    });
  }
}
const login= async ()=>{
  try{
    await userStore.userLogin(loginParam);
    userStore.visible=false
  }catch (error) {
    ElMessage({
      type:'error',
      message:(error as Error).message
    })
  }
}
修改syt/src/api/hospital/index.ts

增加登录路由

enum API{
    HOSPITALDETAIL_URl='/hosp/hospital/',
    //获取某一个医院的科室数据
    HOSPITALDEPARTMENT_URL='/hosp/hospital/department/',
    //获取验证码接口
    // GETUSERCODE_URL = '/sms/send/',
    GETUSERCODE_URL = '/msm/send/',//本地
    //用户登录接口
    UERLOGIN_URL = '/user/login'
}

//用户登录接口
export const reqUserLogin = (data:LoginData)=>request.post<any,UserLoginResponseData>(API.UERLOGIN_URL,data);
修改syt/src/api/hospital/type.ts
//用户登录接口需要携带参数类型
export interface LoginData{
    phone:string,
    code:string
}
//登录接口返回用户信息数据
export interface UserInfo{
    name:string,
    token:string
}
//登录接口返回的数据类型
export interface UserLoginResponseData extends ResponseData{
    data:UserInfo
}
修改syt/src/store/modules/interface/index.ts
//用户仓库相关state数据ts类型定义
export interface UserState{
    visible:boolean,    //用于控制登录组件的dialog显示与隐藏
    code:string,    //存储用户的验证码
    userInfo:UserInfo
}
创建syt/src/utils/user.ts

将用户信息存储本地

//本地存储操作用户信息
export const SET_TOKEN =(userInfo:string)=>{
    localStorage.setItem('USERINFO',userInfo);
}
export const GET_TOKEN=()=>{
    return localStorage.getItem('USERINFO')
}

修改syt/src/store/modules/user.ts
import {GET_TOKEN, SET_TOKEN} from "@/utils/user.ts";

state中增加userInfo

// userInfo:({} as UserInfo)
            userInfo:JSON.parse(GET_TOKEN() as string)||{}

actions中增加登录方法

 //用户手机号码登录方法
        async userLogin(loginData:LoginData){
          let result:UserLoginResponseData = await reqUserLogin(loginData);
          if(result.code==200){
                this.userInfo = result.data;
                //本地存储持久化存储用户信息
              SET_TOKEN(JSON.stringify(this.userInfo))
                return 'ok';
          }else {
              return Promise.reject(new Error(result.message));

          }

        }
修改syt/src/components/hospital_top/index.vue

标签增加点击事件,登录成功显示用户名,并增加下拉菜单

 <p class="login" @click="login" v-if="!useStore.userInfo?.name">登录/注册</p>
     <el-dropdown v-else>
    <span class="el-dropdown-link">
      {{useStore.userInfo.name}}
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
       <template #dropdown>
         <el-dropdown-menu>
           <el-dropdown-item>实名认证</el-dropdown-item>
           <el-dropdown-item>挂号订单</el-dropdown-item>
           <el-dropdown-item>就诊人管理</el-dropdown-item>
           <el-dropdown-item>退出登录</el-dropdown-item>
         </el-dropdown-menu>
       </template>
     </el-dropdown>

点击登录显示登录弹出

const login = ()=>{
  useStore.visible=true;
}

登录模块表单校验P28

修改syt/src/components/login/index.vue

Form增加model及rules属性

  <el-form :model="loginParam" :rules="rules">
  

FormItem 增加prop属性

 <el-form-item prop="phone">
                    <el-input placeholder="请输入手机号码" :prefix-icon="User" v-model="loginParam.phone"></el-input>
                  </el-form-item>
                  <el-form-item prop="code">
                    <el-input placeholder="请输入手机验证码" :prefix-icon="Lock" v-model="loginParam.code"></el-input>
                  </el-form-item>

定义rules

const rules = {
  //手机号码校验规则
//required:代表当前字段务必进行校验
//message:代表的校验错误的提示信息
//trigger:代表表单校验触发的时机 change:文本发生变化的时候进行校验   blur:失却焦点的时候触发校验
//min:代表的是最小位数
//max:代表的是最大的位置
  phone: [{required: true, message: "手机号码务必11位", trigger: "change", min: 11}],
  code: [{required: true, message: "验证码务必6位", trigger: "blur", min: 6}]
  }

登录模块表单自定义校验规则P29

修改syt/src/components/login/index.vue

rules修改为自定义校验规则


const rules = {
  phone: [{ validator: validatorPhone, trigger: 'change' }],
  code: [{ validator: validatorCode, trigger: 'blur' }],
}

自定义校验规则

//自定义校验规则:手机号码自定义校验规则
const validatorPhone = (rule: any, value: any, callback: any) => {
  //rule:表单校验规则对象
  //value:当前文本内容
  //callback:回调函数
  const reg = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/;
  if(reg.test(value)){
    callback();
  }else{
    callback(new Error('请输入正确的手机号码格式'))
  }

}
const validatorCode = (rule: any, value: any, callback: any) => {
    if(/^\d{6}$/.test(value)){
      callback();
    }else{
      callback(new Error('请数据正确的验证码格式'))
    }
}

表单校验不通过验证

//获取表单组件
let form = ref<any>();
<el-form :model="loginParam" :rules="rules" ref="form">

登录前进行全表单验证

const login = async () => {
  await form.value.validate();

  try {
    await userStore.userLogin(loginParam);
    userStore.visible = false
  } catch (error) {
    ElMessage({
      type: 'error',
      message: (error as Error).message
    })
  }
}

登录模块清空数据与表单校验结果P30

修改syt/src/components/login/index.vue

清空数据

方法1:

在dialog标签增加close属性

<el-dialog title="用户登录" v-model="userStore.visible" @close="close">

定义close方法,在close中将数据清空

const close=()=>{
  //清空收集的数据
  Object.assign(loginParam,{phone:'',code:''});
  //清除上一次校验的结果
  form.value.resetFields();
}

弹窗关闭按钮实现

<el-button type="primary" size="default" @click="closeDialog">关闭窗口</el-button>
const closeDialog =()=>{
  close();
  userStore.visible = false
}

方法2:销毁登录组件

修改syt/src/App.vue
<script setup lang="ts">
import useUserStore from "@/store/modules/user.ts";

let userStore = useUserStore();
</script>
<!-- 登录组件   -->
    <Login v-if="userStore.visible"/>

退出登录业务P31

修改syt/src/components/hospital_top/index.vue

退出登录标签增加点击事件

  <template #dropdown>
         <el-dropdown-menu>
           <el-dropdown-item>实名认证</el-dropdown-item>
           <el-dropdown-item>挂号订单</el-dropdown-item>
           <el-dropdown-item>就诊人管理</el-dropdown-item>
           <el-dropdown-item @click="logout">退出登录</el-dropdown-item>
         </el-dropdown-menu>
       </template>
     </el-dropdown>

定义退出登录方法

const logout =()=>{
  useStore.userLogout();
  $router.push({path:'/home'})
}
修改syt/src/store/modules/user.ts

定义退出登录,将仓库及本地用户信息清除

 //用户退出登录
        async userLogout(){
         this.userInfo = {name:'',token:''};
         REMOVE_TOKEN();
        }
修改syt/src/utils/user.ts

定义清除本地用户信息

export const REMOVE_TOKEN=()=>{
    return localStorage.removeItem('USERINFO')
}

微信扫码登录P34

修改syt/index.html
<!--  引入微信扫码登录需要核心插件wxlogin.js    -->
      <script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
修改syt/src/api/hospital/type.ts

//定义微信扫码登录返回的数据的ts类型
export interface WXLogin {
    "redirectUri": string,
    "appid": string,
    "scope": string,
    "state": string
}
export interface WXLoginResponseData extends ResponseData{
    data:WXLogin
}
修改syt/src/api/hospital/index.ts
enum API{
    HOSPITALDETAIL_URl='/hosp/hospital/',
    //获取某一个医院的科室数据
    HOSPITALDEPARTMENT_URL='/hosp/hospital/department/',
    //获取验证码接口
    // GETUSERCODE_URL = '/sms/send/',
    GETUSERCODE_URL = '/msm/send/',//本地
    //用户登录接口
    UERLOGIN_URL = '/user/login',
    //获取微信扫码登录需要参数
    // WXLOGIN_URL='/user/weixin/getLoginParam'
     WXLOGIN_URL='/ucenter/wx/getLoginParam'    //本地
}
//获取微信扫码登录生成二维码需要参数接口
export const reqWxLogin = (wxRedirectUri:string)=>request.get<any,WXLoginResponseData>(API.WXLOGIN_URL+`?wxRedirectUri`+wxRedirectUri);

修改syt/src/components/login/index.vue

div

  <div class="webchat" v-show="scene==1">
                <div id="login_container"></div>
                <div class="phone" @click="handler">
                  <p>手机短信验证码登录</p>
                  <svg t="1722590738044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2633" width="16" height="16"><path d="M820.409449 797.228346q0 25.19685-10.07874 46.866142t-27.716535 38.299213-41.322835 26.204724-50.897638 9.574803l-357.795276 0q-27.212598 0-50.897638-9.574803t-41.322835-26.204724-27.716535-38.299213-10.07874-46.866142l0-675.275591q0-25.19685 10.07874-47.370079t27.716535-38.80315 41.322835-26.204724 50.897638-9.574803l357.795276 0q27.212598 0 50.897638 9.574803t41.322835 26.204724 27.716535 38.80315 10.07874 47.370079l0 675.275591zM738.771654 170.330709l-455.559055 0 0 577.511811 455.559055 0 0-577.511811zM510.992126 776.062992q-21.165354 0-36.787402 15.11811t-15.622047 37.291339q0 21.165354 15.622047 36.787402t36.787402 15.622047q22.173228 0 37.291339-15.622047t15.11811-36.787402q0-22.173228-15.11811-37.291339t-37.291339-15.11811zM591.622047 84.661417q0-8.062992-5.03937-12.598425t-11.086614-4.535433l-128 0q-5.03937 0-10.582677 4.535433t-5.543307 12.598425 5.03937 12.598425 11.086614 4.535433l128 0q6.047244 0 11.086614-4.535433t5.03937-12.598425z" p-id="2634"></path></svg>
                </div>
              </div>
            </div>

ts

//点击微信扫码登录|微信小图标切换为微信扫码登录
const changeScene = async () => {
  scene.value = 1
  let redirect_URL = encodeURIComponent(window.location.origin+'/wxlogin')
  let result:WXLoginResponseData = await reqWxLogin(redirect_URL);
  //生产微信扫码二维码管理页面
  let obj =  new WxLogin({
    self_redirect:true,
    id: 'login_container', // 需要显示的容器id
    appid: result.data.appid, // 公众号appid wx*******
    scope: result.data.scope, // 网页默认即可
    redirect_uri: result.data.redirectUri, // 授权成功后回调的url
    state: result.data.state, // 可设置为简单的随机数加session用来校验
    style: 'black', // 提供"black"、"white"可选。二维码的样式
    href: '' // 外部css文件url,需要https
  })
}

scss

 .login {
    padding: 20px;
    border: 1px solid #ccc;
    .webchat{
      display: flex;
      flex-direction: column;
      align-items: center;
      .phone{
        display: flex;
        flex-direction: column;
        align-items: center;
        cursor: pointer;
        p{
          margin: 10px 0px;
        }
      }
    }
  }

完成微信扫码登录业务P35

创建syt/src/pages/wxlogin/index.vue
<script setup lang="ts">
import {useRoute} from "vue-router";
import {SET_TOKEN} from "@/utils/user.ts";
let $route = useRoute();
//持久化存储用户信息
SET_TOKEN(JSON.stringify($route.query));
let html:any = document.querySelector('html');
html.style.display='none';
</script>

<template>
<div></div>
</template>

<style scoped lang="scss">

</style>

修改syt/src/router/index.ts

增加微信登录成功重定向路由

{
            path:'/wxlogin',
            component:()=>import(('@/pages/wxlogin/index.vue'))
        },
修改syt/src/components/login/index.vue

增加监听

import {computed, reactive, ref, watch} from "vue";

//监听场景的数据
watch(()=>scene.value,(val:number)=>{
  if(val===1){
    userStore.queryState();
  }
})
修改syt/src/store/modules/user.ts
   //查询微信扫码的接口(看本地存储是否拥有用户信息)
        queryState(){
           let timer = setInterval(()=>{
              if( GET_TOKEN()){
                  this.visible=false;
                  this.userInfo=JSON.parse(GET_TOKEN() as string);
                  clearInterval(timer);
              }
           },1000)

        }

预约挂号的路由与静态的搭建P36

创建syt/src/pages/hospital/register/register_step1.vue
<script setup lang="ts">

</script>

<template>
<div class="wrap">
  <div class="top">
    <div class="hosname">北京XX医院</div>
    <div class="line"></div>
    <div>专科</div>
    <div class="dot">.</div>
    <div class="hosdepatment">多发性硬化专科门诊</div>
  </div>
  <dv class="center">
    <h1 class="time">2023年6月3日</h1>
    <div class="container">
      <div class="item" v-for="item in 6" :key="item">
        <div class="item_top">123</div>
        <div class="item_bottom">456</div>
      </div>
    </div>
    <el-pagination layout="prev,pager,next" :total="50"/>
  </dv>
</div>
</template>

<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;
    .item{
      font-weight: 900;
    }
    .container{
      display: flex;
      width: 100%;
      margin: 30px 0px;
      .item{
        flex: 1;
        border:1px solid skyblue;
        margin: 0px 5px;
        display: flex;
        flex-direction: column;
        align-items: center;
        .item_top{
          background: #e8f2ff;
          height: 30px;
          width: 100%;
          text-align: center;
          line-height: 30px;
        }
        .item_bottom{
          width: 100%;
          height: 60px;
          text-align: center;
          line-height: 60px;
        }
      }
    }
  }
}
</style>

修改syt/src/router/index.ts
  {
                    path:'register_step1',
                    component:()=>import(('@/pages/hospital/register/register_step1.vue'))
                }
修改syt/src/pages/hospital/register/index.vue
<!--    每个大科室下的小科室    -->
        <ul>
          <li v-for="item in department.children" :key="item.depcode" @click="showLogin(item)">{{item.depname}}</li>
        </ul>
        
        const showLogin=(item:any)=>{
  // useStore.visible=true;
  $router.push({path:'/hospital/register_step1',query:{hoscode:$route.query.hoscode,depcode:item.depcode}})
}

展示预约挂号数据P37

修改syt/src/api/hospital/index.ts
enum API{
    HOSPITALDETAIL_URl='/hosp/hospital/',
    //获取某一个医院的科室数据
    HOSPITALDEPARTMENT_URL='/hosp/hospital/department/',
    //获取验证码接口
    // GETUSERCODE_URL = '/sms/send/',
    GETUSERCODE_URL = '/msm/send/',//本地
    //用户登录接口
    UERLOGIN_URL = '/user/login',
    //获取微信扫码登录需要参数
    // WXLOGIN_URL='/user/weixin/getLoginParam'
     WXLOGIN_URL='/ucenter/wx/getLoginParam',   //本地
    HOSPITALWORK_URL='/hosp/hospital/auth/getBookingScheduleRule/',
}
//获取预约挂号的数据
export const reqHosptalWork=(page:number,limit:number,hoscode:string,depcode:string)=>request.get<any,HospitalWorkData>(API.HOSPITALWORK_URL+`${page}/${limit}/${hoscode}/${depcode}`)

修改syt/src/api/hospital/type.ts
export interface BaseMap{
    "workDateString": string,
    "releaseTime": string,
    "bigname": string,
    "stopTime": string,
    "depname": string,
    "hosname": string
}
export interface WorkData{
    "workDate": string,
    "workDateMd": string,
    "dayOfWeek": string,
    "docCount": number,
    "reservedNumber": null,
    "availableNumber": number,
    "status": number
}
export type BookingScheduleList  = WorkData[]
export interface HospitalWorkData extends ResponseData{
    data:{
        total:number,
        bookingScheduleList:BookingScheduleList,
        baseMap:BaseMap
    }
}
修改syt/src/pages/hospital/register/register_step1.vue
<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="hosdepatment">{{workData.baseMap?.depname}}</div>
  </div>
  <dv 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="item_top">{{ item.workDate }}-{{item.dayOfWeek}}</div>
        <div class="item_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"/>
  </dv>
</div>
</template>
<script setup lang="ts">
import {reqHosptalWork} from "@/api/hospital";
import {onMounted, ref} from "vue";
import {useRoute} from "vue-router";
import {HospitalWorkData} from "@/api/hospital/type.ts";
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 reqHosptalWork(pageNo.value,limit.value,$route.query.hoscode as string,$route.query.depcode as string);
 if(result.code===200){
   workData.value = result.data;
 }
}
</script>


scss

 .item{
        flex: 1;
        border:1px solid skyblue;
        margin: 0px 5px;
        display: flex;
        flex-direction: column;
        align-items: center;
        &.active{
          border: 1px solid #ccc;
          color: #7f7f7f;
          .item_top{
            background: #ccc;
          }
        }

预约挂号底部上下午医生结构的搭建P38

修改syt/src/pages/hospital/register/register_step1.vue

templa

<div class="bottom">
<!--   展示即将放号的时间 -->
    <div class="will">
      <span class="time">2023年6月3日08:30</span>
      <span class="willText">放号</span>
    </div>
<!-- 展示医生的结构   -->
    <div class="doctor">
      <div class="morning">
<!--    顶部文字提示    -->
        <div class="tip">
          <svg t="1722869133535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3749" width="32" height="16"><path d="M904.101037 676.946871H118.670712c-15.461511 0-28.105271-12.535384-28.105271-28.033021 0-15.461511 12.643759-28.105271 28.105271-28.10527h785.430325c15.461511 0 28.105271 12.643759 28.105271 28.10527s-12.607634 28.033021-28.105271 28.033021z m-56.029916-324.980738c-11.018133 10.945883-28.719396 10.945883-39.665279 0-11.018133-10.945883-11.018133-28.755521 0-39.665279l39.665279-39.665279a27.97522 27.97522 0 0 1 39.593029 0 27.93187 27.93187 0 0 1 0 39.665279l-39.593029 39.665279z m-62.279545 212.776406c-26.082269-128.027094-138.683977-224.444789-274.441826-224.44479-135.649474 0-248.287307 96.417696-274.369576 224.44479H177.915755c21.602766-158.263741 162.454244-280.583081 333.433995-280.583081 171.088125 0 311.903478 122.31934 333.506244 280.583081h-59.064418z m-274.441826-336.612997c-15.461511 0-28.033021-12.535384-28.033021-28.10527V143.95823c0-15.461511 12.535384-28.105271 28.033021-28.10527 15.569886 0 28.105271 12.643759 28.10527 28.10527v56.029916c0 15.606011-12.535384 28.141396-28.10527 28.141396zM174.700628 351.966133L135.035349 312.264729c-10.945883-10.945883-10.945883-28.755521 0-39.665279 11.018133-10.945883 28.719396-10.945883 39.665279 0l39.665279 39.665279a27.93187 27.93187 0 0 1 0 39.665279c-10.909758 10.982008-28.647146 10.982008-39.665279 0.036125z m-28.033021 381.119029h729.40041c15.497636 0 28.033021 12.535384 28.03302 28.03302a28.033021 28.033021 0 0 1-28.03302 28.033021H146.667607c-15.497636 0-28.033021-12.535384-28.03302-28.033021 0.036125-15.497636 12.571509-28.033021 28.03302-28.03302z m84.171312 112.168207h561.093911c15.497636 0 28.033021 12.535384 28.033021 28.033021 0 15.569886-12.535384 28.105271-28.033021 28.10527H230.838919c-15.497636 0-28.033021-12.535384-28.03302-28.10527 0-15.497636 12.535384-28.033021 28.03302-28.033021z" fill="#1296db" p-id="3750"></path></svg>
          <span class="text">上午号源</span>
        </div>
<!--   每一个医生的信息     -->
        <div class="doc_info">
<!--      展示医生的名字|技能    -->
          <div class="left">
            <div class="info">
              <span>副主任医生</span>
              <span>|</span>
              <span>贾成豪</span>
            </div>
           <div class="skill">骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病</div>
          </div>
<!--       右侧区域展示挂号的费用   -->
          <div class="right">
            <div class="money">¥100</div>
            <el-button type="primary" size="default">100</el-button>
          </div>
        </div>
        <div class="doc_info">
          <!--      展示医生的名字|技能    -->
          <div class="left">
            <div class="info">
              <span>副主任医生</span>
              <span>|</span>
              <span>贾成豪</span>
            </div>
            <div class="skill">骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病</div>
          </div>
          <!--       右侧区域展示挂号的费用   -->
          <div class="right">
            <div class="money">¥100</div>
            <el-button type="primary" size="default">100</el-button>
          </div>
        </div>
      </div>
      <div class="morning">
        <!--    顶部文字提示    -->
        <div class="tip">
          <svg t="1722870959935" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5200" width="32" height="32"><path d="M356.9664 562.7392H762.88a61.44 61.44 0 0 1 61.44 61.44v153.6H295.7824v-153.6a61.44 61.44 0 0 1 61.184-61.44z" fill="#FFAECF" p-id="5201"></path><path d="M431.872 383.6416h256a61.44 61.44 0 0 1 61.44 61.44v117.76H370.6368v-117.76a61.44 61.44 0 0 1 61.2352-61.44z" fill="#FFAECF" p-id="5202"></path><path d="M945.152 831.5392H78.848a15.36 15.36 0 0 1 0-30.72h866.304a15.36 15.36 0 0 1 0 30.72z" fill="#8E5178" p-id="5203"></path><path d="M661.1456 998.4H362.8544a15.36 15.36 0 0 1-15.36-15.36v-35.0208a32.9728 32.9728 0 0 1 32.9216-32.9216 82.3808 82.3808 0 0 0 82.2784-82.2784v-16.384a15.36 15.36 0 0 1 15.36-15.36h67.8912a15.36 15.36 0 0 1 15.36 15.36v16.384a82.3808 82.3808 0 0 0 82.2784 82.2784 32.9728 32.9728 0 0 1 32.9216 32.9216V983.04a15.36 15.36 0 0 1-15.36 15.36z m-282.9312-30.72h267.5712v-19.6608a2.2016 2.2016 0 0 0-2.2016-2.2016 113.1008 113.1008 0 0 1-112.9984-112.9984v-1.024h-37.1712v1.024a113.1008 113.1008 0 0 1-112.9984 112.7424 2.2016 2.2016 0 0 0-2.2016 2.2016z" fill="#8E5178" p-id="5204"></path><path d="M776.0384 831.5392H247.9616a15.36 15.36 0 0 1-15.36-15.36v-153.6a76.8 76.8 0 0 1 76.8-76.8h405.6064a76.8 76.8 0 0 1 76.8 76.8v153.6a15.36 15.36 0 0 1-15.7696 15.36z m-512.7168-30.72h497.3568v-138.24a46.08 46.08 0 0 0-46.08-46.08H309.1968a46.08 46.08 0 0 0-46.08 46.08z" fill="#8E5178" p-id="5205"></path><path d="M701.1328 616.5504H322.8672a15.36 15.36 0 0 1-15.36-15.36v-117.76a76.8 76.8 0 0 1 76.8-76.8h256a76.8 76.8 0 0 1 76.8 76.8v117.76a15.36 15.36 0 0 1-15.9744 15.36z m-362.9056-30.72h347.5456v-102.4a46.08 46.08 0 0 0-46.08-46.08H384a46.08 46.08 0 0 0-46.08 46.08z" fill="#8E5178" p-id="5206"></path><path d="M641.3312 437.4528H382.6688a15.36 15.36 0 0 1-15.36-15.36V336.2304a76.8 76.8 0 0 1 76.8-76.8h136.192a76.8 76.8 0 0 1 76.8 76.8v85.8624a15.36 15.36 0 0 1-15.7696 15.36z m-243.3024-30.72h227.9424V336.2304a46.08 46.08 0 0 0-46.08-46.08H443.904a46.08 46.08 0 0 0-46.08 46.08z" fill="#8E5178" p-id="5207"></path><path d="M845.5168 831.5392H178.4832a15.36 15.36 0 0 1-15.36-15.36V374.7328a348.8768 348.8768 0 0 1 697.7536 0v441.4464a15.36 15.36 0 0 1-15.36 15.36z m-651.6736-30.72h636.3136V374.7328a318.1568 318.1568 0 0 0-636.3136 0z" fill="#8E5178" p-id="5208"></path><path d="M512 290.3552a15.36 15.36 0 0 1-10.24-3.6352c-2.1504-1.8944-53.504-46.6432-74.6496-77.5168a69.5808 69.5808 0 0 1-5.9392-71.68 58.368 58.368 0 0 1 52.5824-31.1296 51.2 51.2 0 0 1 38.0416 15.36 51.2 51.2 0 0 1 38.0416-15.36 58.2144 58.2144 0 0 1 52.5312 31.1296 69.4272 69.4272 0 0 1-5.888 71.68c-21.1456 30.72-72.4992 75.6224-74.6496 77.5168a15.36 15.36 0 0 1-9.8304 3.6352z m-38.0416-153.2416a27.392 27.392 0 0 0-25.6 14.7456 38.9632 38.9632 0 0 0 4.096 39.8848c13.6704 19.968 43.6736 48.3328 59.392 62.6688 15.7184-14.336 45.7216-42.7008 59.392-62.6688a38.9632 38.9632 0 0 0 4.096-39.8848 27.392 27.392 0 0 0-25.6-14.7456 22.9376 22.9376 0 0 0-22.9888 18.8416 15.36 15.36 0 0 1-15.36 12.2368 15.36 15.36 0 0 1-14.9504-12.4416 22.8864 22.8864 0 0 0-22.4768-18.6368zM600.576 537.1392a44.9536 44.9536 0 0 1-32.5632-14.1312c-5.12-4.6592-6.9632-6.144-11.8784-6.144s-6.7584 1.4848-11.8272 6.144a44.5952 44.5952 0 0 1-65.1264 0c-5.12-4.6592-6.9632-6.144-11.8784-6.144s-6.7584 1.4848-11.8272 6.144a44.5952 44.5952 0 0 1-65.1264 0c-5.12-4.6592-6.9632-6.144-11.8784-6.144a15.36 15.36 0 0 1 0-30.72 44.9536 44.9536 0 0 1 32.5632 14.1312c5.12 4.6592 6.9632 6.144 11.8784 6.144s6.7584-1.4848 11.8272-6.144a44.5952 44.5952 0 0 1 65.1264 0c5.12 4.6592 6.9632 6.144 11.8784 6.144s6.7584-1.4848 11.8272-6.144a44.5952 44.5952 0 0 1 65.1264 0c5.12 4.6592 6.9632 6.144 11.8784 6.144s6.7584-1.4848 11.8784-6.144A44.8512 44.8512 0 0 1 645.12 486.4a15.36 15.36 0 0 1 0 30.72c-5.12 0-6.7584 1.4848-11.8784 6.144a44.8512 44.8512 0 0 1-32.6656 13.8752z" fill="#8E5178" p-id="5209"></path></svg>
          <span class="text">下午号源</span>
        </div>
        <!--   每一个医生的信息     -->
        <div class="doc_info">
          <!--      展示医生的名字|技能    -->
          <div class="left">
            <div class="info">
              <span>副主任医生</span>
              <span>|</span>
              <span>贾成豪</span>
            </div>
            <div class="skill">骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病</div>
          </div>
          <!--       右侧区域展示挂号的费用   -->
          <div class="right">
            <div class="money">¥100</div>
            <el-button type="primary" size="default">100</el-button>
          </div>
        </div>
        <div class="doc_info">
          <!--      展示医生的名字|技能    -->
          <div class="left">
            <div class="info">
              <span>副主任医生</span>
              <span>|</span>
              <span>贾成豪</span>
            </div>
            <div class="skill">骨质疏松和骨代谢疾病、糖尿病、甲状腺疾病</div>
          </div>
          <!--       右侧区域展示挂号的费用   -->
          <div class="right">
            <div class="money">¥100</div>
            <el-button type="primary" size="default">100</el-button>
          </div>
        </div>
      </div>

    </div>
  </div>

scss

 .bottom{
    .will{
      text-align: center;
      font-size: 30px;
      font-weight: 900;
      .time{
        color: red;
      }
      .willText{
        color: skyblue;
      }
    }
    .doctor{
      .morning{
        .tip{
          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{
            width: 150px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            .money{
              color: #7f7f7f;
              font-weigt: 900;
            }
          }
        }
      }
    }
  }

预约挂号底部医生排班业务P39

修改syt/src/api/hospital/index.ts
  //获取医院某一个科室某一天相应医生排班的数据
    HOSPITALDOCTOR_URL = '/hosp/hospital/auth/findScheduleList/'
    
    //获取医生排班的数据
export const reqHospitalDoctor = (hoscode:string,depcode:string,workDate:string)=>request.get<any,DoctorResponseData>(API.HOSPITALDOCTOR_URL+`${hoscode}/${depcode}/${workDate}`)

修改syt/src/api/hospital/type.ts
//代表的是一个医生的数据
export interface Doctor{
    "id": string,
    "createTime": string,
    "updateTime": string,
    "isDeleted": 0,
    "param": {
        "dayOfWeek": string,
        "depname": string,
        "hosname": string
    },
    "hoscode": string,
    "depcode": string,
    "title": string,
    "docname": string,
    "skill": string,
    "workDate": string,
    "workTime": number,
    "reservedNumber": number,
    "availableNumber": number,
    "amount": number,
    "status": number,
    "hosScheduleId": string
}

//数组包含全部医生
export type DocArr = Doctor[];

//获取医生排班接口返回的数据
export interface DoctorResponseData extends ResponseData{
    data:DocArr
}
修改syt/src/pages/hospital/register/register_step1.vue

ts

import {computed, onMounted, ref} from "vue";
import {DocArr, Doctor, DoctorResponseData, HospitalWorkData} from "@/api/hospital/type.ts";
let workData=ref<any>({});
let workTime :any = ref<any>({})
let docArr = ref<DocArr>();

const fetchWorkData = async ()=>{
 let result:HospitalWorkData = await reqHosptalWork(pageNo.value,limit.value,$route.query.hoscode as string,$route.query.depcode as string);
 if(result.code===200){
   workData.value = result.data;
   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);
  if(result.code===200){
    docArr.value = result.data;
  }
}

const changeTime = (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
  })
})

templa

 <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="item_top">{{ item.workDate }}-{{item.dayOfWeek}}</div>
        <div class="item_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 class="will" v-if="workTime.status==1">
      <span class="time">2023年6月3日08:30</span>
      <span class="willText">放号</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" size="default">{{doctor.availableNumber}}</el-button>
          </div>
        </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" size="default">{{doctor.availableNumber}}</el-button>
          </div>
        </div>

scss

&.active{
          border: 1px solid #ccc;
          color: #7f7f7f;
          .item_top{
            background: #ccc;
          }
        }
        &.cur{
          transform: scale(1.1);
        }

预约挂号就诊人静态搭建P40

创建syt/src/pages/hospital/register/register_step2.vue
<script setup lang="ts">
import {User} from '@element-plus/icons-vue'
import Visiter from "@/pages/hospital/register/visiter.vue";
</script>

<template>
  <div class="container">
  <h1 class="tip">确认挂号信息</h1>
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>Card name</span>
          <el-button size="default" type="primary" :icon="User">添加就诊人</el-button>
        </div>
      </template>
      <div class="user">
        <Visiter v-for="item in 4" :key="item" class="item"/>
      </div>
    </el-card>
  </div>
</template>

<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;
        }
      }
    }
  }
</style>

创建syt/src/pages/hospital/register/visiter.vue
<script setup lang="ts">

import {Edit} from "@element-plus/icons-vue";
</script>

<template>
  <div class="visitor">
    <div class="top">
      <div class="left">
        <span class="free">医保</span>
        <span class="username">贾成豪</span>
      </div>
      <div class="right">
        <el-button type="primary" size="default" :icon="Edit"></el-button>
      </div>
    </div>
    <div class="bottom">
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
      <p>证件类型:身份证</p>
    </div>
  </div>
</template>

<style scoped lang="scss">
 .visitor{
   .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: 10px;
         border-radius: 10px;
       }
       .username{
         color: #7f7f7f;
       }
     }
   }
   .bottom{
     padding: 20px;
     p{
       line-height: 40px;
     }
   }
 }
</style>

修改syt/src/router/index.ts
  {
                    path:'register_step2',
                    component:()=>import(('@/pages/hospital/register/register_step2.vue'))
                }
修改syt/src/pages/hospital/register/register_step1.vue
  //上午号源按钮
  <el-button type="primary" size="default" @click="goStep2(doctor)">{{doctor.availableNumber}}</el-button>
  //下午号源按钮
    <el-button type="primary" size="default" @click="goStep2(doctor)">{{doctor.availableNumber}}</el-button>
import {useRoute, useRouter} from "vue-router";
let $router = useRouter();
// 路由跳转进入就诊人页面
const goStep2 =(doctor:Doctor)=>{
  $router.push({path:'/hospital/register_step2',query:{docId:doctor.id}})
}

预约挂号就诊人静态搭建下P41

修改syt/src/pages/hospital/register/register_step2.vue
<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>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              就诊医院:
            </div>
          </template>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              就诊科室:
            </div>
          </template>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生姓名:
            </div>
          </template>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生职称:
            </div>
          </template>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生专长:
            </div>
          </template>
          xxxxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生服务费:
            </div>
          </template>
          <span style="color: red">xxxxxx</span>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
    <div class="btn">
      <el-button type="primary" size="default">确认挂号</el-button>
    </div>

 .btn{
      display: flex;
      justify-content: center;
      margin: 10px 0px;
    }

获取当前账号下就诊人信息P42

修改syt/src/api/hospital/type.ts
export interface Patient{
    address:string,
    birthdate:string,
    cardNo:null,
    certificatesNo:string,
    certificatesType:string,
    cityCode:string,
    contactsCertificatesNo:string,
    contactsCertificatesType:string,
    contactsName:string,
    contactsPhone:string,
    createTime:string,
    districtCode:string,
    id:number,
    isDeleted:number,
    isInsure:number,
    isMarry:number,
    name:string,
    param:{
        certificatesTypeString:string,
        contactsCertificatesTypeString:string,
        cityString:string,
        fullAddress:string,
        districtString:string,
        provinceString:string,
    }
    phone:string,
    provinceCode:string,
    sex:number
    status:string,
    updateTime:string,
    userId:number
}
export type PatientArr = Patient[];
//获取就诊人接口返回的数据
export interface PatientResponseData extends ResponseData{
    data:PatientArr
}
修改syt/src/api/hospital/index.ts
PATIENTS_URL = '/user/patient/auth/findAll'
//获取就诊人数据
export const reqPatient = ()=>request.get<any,PatientResponseData>(API.PATIENTS_URL)
修改syt/src/pages/hospital/register/register_step2.vue
 
 <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>请选择就诊人</span>
          <el-button size="default" type="primary" :icon="User">添加就诊人</el-button>
        </div>
      </template>
      <div class="user">
        <Visiter v-for="patient in patientArr" :key="patient.id" class="item" :patient="patient"/>
      </div>
    </el-card>
    
<script setup lang="ts">
import {User} from '@element-plus/icons-vue'
import Visiter from "@/pages/hospital/register/visiter.vue";
import {onMounted, ref} from "vue";
import {reqPatient} from "@/api/hospital";
import {PatientArr, PatientResponseData} from "@/api/hospital/type.ts";

let patientArr = ref<PatientArr>();
onMounted(()=>{
  fetchPatietnt();
})
const fetchPatietnt= async ()=>{
  let result:PatientResponseData = await reqPatient();
  if(result.code==200){
    patientArr.value=result.data;
  }
}
</script>
修改syt/src/pages/hospital/register/visiter.vue
<template>
  <div class="visitor">
    <div class="top">
      <div class="left">
        <span class="free">{{patient.isInsure==1?'医保':'自费'}}</span>
        <span class="username">{{patient.name}}</span>
      </div>
      <div class="right">
        <el-button type="primary" size="default" :icon="Edit"></el-button>
      </div>
    </div>
    <div class="bottom">
      <p>证件类型:{{patient.param.certificatesTypeString}}</p>
      <p>证件号码:{{patient.certificatesNo}}</p>
      <p>用户性别:{{patient.sex==0?'女生':'男士'}}</p>
      <p>出生日期:{{patient.birthdate}}</p>
      <p>手机号码:{{patient.phone}}</p>
      <p>婚姻状况:{{patient.isMarry==0?'未婚':'已婚'}}</p>
      <p>当前住址:{{patient.param.cityString}}</p>
      <p>详细地址:{{patient.param.fullAddress}}</p>
    </div>
  </div>
</template>

<script setup lang="ts">

import {Edit} from "@element-plus/icons-vue";
//获取父组件信息
defineProps(['patient'])
</script>

获取挂号医生的信息展示P43

修改syt/src/api/hospital/index.ts
//获取医生信息
    SCHEDULE_URL = '/hosp/hospital/getScheduleById/'
    
    export const reqSchedule = (scheduleId:string)=>request.get<any,ScheduleResponseData>(API.SCHEDULE_URL+`${scheduleId}`)
修改syt/src/api/hospital/type.ts
export interface ScheduleResponseData extends ResponseData{
    data:Doctor
}
修改syt/src/pages/hospital/register/register_step2.vue
  <el-descriptions
          class="margin-top"
          :column="2"
          border
      >
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              就诊日期:
            </div>
          </template>
          {{schedule?.workDate}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              就诊医院:
            </div>
          </template>
          {{schedule?.param?.hosname}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              就诊科室:
            </div>
          </template>
          {{schedule?.param?.depname}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生姓名:
            </div>
          </template>
          {{schedule?.docname}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生职称:
            </div>
          </template>
          {{schedule?.title}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生专长:
            </div>
          </template>
          {{schedule?.skill}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
              医生服务费:
            </div>
          </template>
          <span style="color: red">{{schedule?.amount}}</span>
        </el-descriptions-item>
      </el-descriptions>

ts

import {reqPatient, reqSchedule} from "@/api/hospital";
import {Doctor, PatientArr, PatientResponseData, ScheduleResponseData} from "@/api/hospital/type.ts";

let schedule = ref<Doctor>();
onMounted(()=>{
  fetchPatietnt();
  fetchSchedule();
})

      const fetchSchedule= async ()=>{
  let $useRoute = useRoute();
  let scheduleId = $useRoute.query.docId;
  let result:ScheduleResponseData = await reqSchedule(scheduleId as string);
  console.log(result)
  if(result.code==200){
    schedule.value=result.data;
  }
}

确定就诊人业务完成P44

添加已选择标识

修改syt/src/pages/hospital/register/visiter.vue

ts

defineProps(['patient','index','currentIndex']) 

template



<div class="bottom">
      <p>证件类型:{{patient.param.certificatesTypeString}}</p>
      <p>证件号码:{{patient.certificatesNo}}</p>
      <p>用户性别:{{patient.sex==0?'女生':'男士'}}</p>
      <p>出生日期:{{patient.birthdate}}</p>
      <p>手机号码:{{patient.phone}}</p>
      <p>婚姻状况:{{patient.isMarry==0?'未婚':'已婚'}}</p>
      <p>当前住址:{{patient.param.cityString}}</p>
      <p>详细地址:{{patient.param.fullAddress}}</p>
      <transition name="confirm">
        <div class="confirm" v-if="index===currentIndex">已选择</div>
      </transition>
    </div>

scss

.bottom{
     padding-left: 50px;
     position: relative;
     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;
      }
     .confirm-enter-to{
       transform: scale(1.2);
     }
   }
修改syt/src/pages/hospital/register/register_step2.vue

父组件增加点击事件,并将当前选中就诊人索引传递给子组件

let currentIndex = ref<number>(-1);

const changeIndex = (index:number)=>{
  currentIndex.value=index;
}


 <div class="user">
        <Visiter @click="changeIndex(index)" v-for="(patient,index) in patientArr" :key="patient.id" class="item" :patient="patient" :index="index" :currentIndex="currentIndex"/>
      </div>
    
    //禁用确认挂号按钮
    <div class="btn">
      <el-button type="primary" size="default" :disabled="currentIndex===-1">确认挂号</el-button>
    </div>

完成会员中心路由P45

创建syt/src/pages/user/index.vue
<script setup lang="ts">

import {HomeFilled,Document,
  Menu as IconMenu,
  Location,
  Setting} from "@element-plus/icons-vue";
import {useRoute, useRouter} from "vue-router";
let $router = useRouter();
let $route = useRoute();
const goUser = (path:string)=>{
  $router.push({path:path});
}
</script>

<template>
<div class="user">
  <div class="menu">
    <div class="top">
      <el-icon><HomeFilled/></el-icon>
      <span> / 会员中心</span>
    </div>
    <el-menu
        :default-active="$route.path"
        class="el-menu-vertical-demo"
    >
      <el-menu-item  index="/user/certification" @click="goUser('/user/certification')">
          <el-icon><location /></el-icon>
          <span>实名认证</span>
      </el-menu-item>
      <el-menu-item index="/user/order" @click="goUser('/user/order')">
        <el-icon><icon-menu /></el-icon>
        <span>挂号订单</span>
      </el-menu-item>
      <el-menu-item index="/user/patient" @click="goUser('/user/patient')">
        <el-icon><document /></el-icon>
        <span>就诊人管理</span>
      </el-menu-item>
      <el-menu-item index="/user/profile" @click="goUser('/user/profile')">
        <el-icon><setting /></el-icon>
        <span>账号信息</span>
      </el-menu-item>
      <el-menu-item index="/user/feedback" @click="goUser('/user/feedback')">
        <el-icon><setting /></el-icon>
        <span>意见反馈</span>
      </el-menu-item>
    </el-menu>
  </div>
  <div class="content">
    <router-view></router-view>
  </div>
</div>
</template>

<style scoped lang="scss">

.user{
  display: flex;
  .menu{
    flex: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .content{
    flex: 8;
  }
}
</style>

修改syt/src/router/index.ts
 {
            path:'/user',
            component:()=>import(('@/pages/user/index.vue')),
            children:[
                {
                    path:'certification',
                    component:()=>import(('@/pages/user/certification/index.vue'))
                },
                {
                    path:'feedback',
                    component:()=>import(('@/pages/user/feedback/index.vue'))
                },
                {
                    path:'order',
                    component:()=>import(('@/pages/user/order/index.vue'))
                },
                {
                    path:'patient',
                    component:()=>import(('@/pages/user/patient/index.vue'))
                },
                {
                    path:'profile',
                    component:()=>import(('@/pages/user/profile/index.vue'))
                },
            ]
创建syt/src/pages/user/certification/index.vue
<script setup lang="ts">

</script>

<template>
<div>实名认证</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/user/feedback/index.vue
<script setup lang="ts">

</script>

<template>
<div>意见反馈</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/user/order/index.vue
<script setup lang="ts">

</script>

<template>
<div> 挂号订单</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/user/patient/index.vue
<script setup lang="ts">

</script>

<template>
<div>就诊人管理</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/user/profile/index.vue
<script setup lang="ts">

</script>

<template>
<div>账号信息</div>
</template>

<style scoped lang="scss">

</style>

完成确定挂号业务P46

创建syt/src/api/user/index.ts
import request from "@/utils/request.ts";
import { SubmitOrderResponseData} from "@/api/user/type.ts";

enum API {
    SUBMITORDER_URL='/order/orderInfo/auth/submitOrder/'
}

// export const reqSubmitOrder = (hoscode :string,scheduleId:string,patientId:string)=>request.post<any,OrderResponseData>(API.SUBMITORDER_URL+`${hoscode}/${scheduleId}/${patientId}`)
export const reqSubmitOrder = (scheduleId:string,patientId:string)=>request.post<any,SubmitOrderResponseData>(API.SUBMITORDER_URL+`${scheduleId}/${patientId}`);//本地接口

创建syt/src/api/user/type.ts
export interface ResponseData{
    code:number,
    message:string,
    ok:boolean
}
export interface SubmitOrderResponseData extends ResponseData{
    data:number
}

修改syt/src/pages/hospital/register/register_step2.vue

template

<div class="btn">
      <el-button type="primary" size="default" :disabled="currentIndex===-1" @click="goUser()">确认挂号</el-button>
    </div>
  
  

ts

import {useRoute, useRouter} from "vue-router";
let $router = useRouter();
    const goUser = async ()=>{
  let patientId = patientArr.value?. [ currentIndex.value].id;
  let scheduleId = schedule.value?.id;
  let hoscode = schedule.value?.hoscode;
  console.log(hoscode);
 // let result:OrderResponseData =  await reqSubmitOrder(hoscode,scheduleId,patientId);
 let result:SubmitOrderResponseData =  await reqSubmitOrder(scheduleId as string,patientId as string);//本地接口
  if(result.code==200){
    $router.push({path:`/user/order?orderId=${result.data}`})
  }else{
    ElMessage({
      type:'error',
      message:result.message,
    })
  }
}
创建syt/src/pages/user/order/allOrder/index.vue
<script setup lang="ts">

</script>

<template>
<div>全部订单</div>
</template>

<style scoped lang="scss">

</style>

创建syt/src/pages/user/order/detail/index.vue
<script setup lang="ts">

</script>

<template>
<div>订单详情</div>
</template>

<style scoped lang="scss">

</style>

修改syt/src/pages/user/order/index.vue
<script setup lang="ts">
import {useRoute} from "vue-router";
import Detail from '@/pages/user/order/detail/index.vue'
import AllOrder from '@/pages/user/order/allOrder/index.vue'
let $route = useRoute();
</script>

<template>
<div>
  <div v-if="$route.query.orderId">
    <Detail/>
  </div>
  <div v-else>
    <AllOrder/>
  </div>
</div>
</template>

<style scoped lang="scss">

</style>

完成订单详情静态的搭建P47

修改syt/src/pages/user/order/detail/index.vue
<script setup lang="ts">

</script>

<template>
<el-card class="box-card">
  <template #header>
    <div class="detail">挂号详情</div>
  </template>
  <div class="top">
    <el-tag class="ml-2" type="success">
      <div class="tag">
        <svg t="1723598883339" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6216" width="16" height="16"><path d="M414.245926 751.028148l437.57037-453.12c7.300741-7.49037 7.111111-19.531852-0.474074-26.832593-7.49037-7.300741-19.531852-7.111111-26.832593 0.474074L398.506667 712.722963 199.111111 513.327407c-7.395556-7.395556-19.437037-7.395556-26.832593 0l0 0c-7.395556 7.395556-7.395556 19.437037 0 26.832593l212.574815 212.574815c6.731852 6.731852 17.161481 7.300741 24.557037 1.896296C411.211852 753.682963 412.823704 752.545185 414.245926 751.028148z" fill="#1afa29" p-id="6217"></path></svg>
        <span>预约成功,待支付</span>
      </div>
    </el-tag>
    <div class="right_info">
      <img src="@/assets/images/code_app.png">
      <div>
        <p>微信 关注“北京114预约挂号”</p>
        <p>快速挂号,轻松就医</p>
      </div>
    </div>
  </div>
  <div class="bottom">
    <div class="left">
      <el-descriptions
          class="margin-top"
          :column="1"
          border
      >
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊人信息:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊日期:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊医院:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊科室:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            医生职称:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            医事服务费:
            </div>
          </template>
          <span style="color: red">100元</span>
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            挂号单号:
            </div>
          </template>
         xxxx
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            挂号时间:
            </div>
          </template>
        xxxx
        </el-descriptions-item>
      </el-descriptions>
      <div class="btn">
        <el-button>取消预约</el-button>
        <el-button type="primary" size="default">支付</el-button>
      </div>
    </div>
    <div class="right">
      <el-card class="box-card">
        <template #header>
          <div class="card-header">
            <span>注意事项</span>
          </div>
        </template>
        <p>1、请确认就诊人信息是否准确,若填写错误将无法取号就诊,损失由本人承担;</p>
          <p style="color:red">2、【取号】就诊当天需在xxxx在医院取号,未取号视为爽约,该号不退不换;</p>
        <p> 3、【退号】在xxx前可在线退号 ,逾期将不可办理退号退费;</p>
        <p> 4、北京114预约挂号支持自费患者使用身份证预约,同时支持北京市医保患者使用北京社保卡在平台预约挂号。请于就诊当日,携带预约挂号所使用的有效身份证件到院取号;</p>
          <p> 5、请注意北京市医保患者在住院期间不能使用社保卡在门诊取号。</p>

      </el-card>
    </div>
  </div>
</el-card>
</template>

<style scoped lang="scss">
.box-card{
  .detail{
    color:#7f7f7f;
    font-weight: 900;
  }
  .top{
    display: flex;
    justify-content: space-between;
    border-bottom: 1px solid #ccc;
    padding: 10px;
    .tag{
      display: flex;
      justify-content: center;
      align-items: center;
      span{
        margin-left: 5px;
      }
    }
    .right_info{
      display: flex;
      justify-content: space-between;
      img{
        width: 40px;
        height: 40px;
      }
      p{
        font-size: 12px;
        line-height: 20px;
      }
    }
  }
  .bottom{
    display: flex;
    justify-content: space-between;
    margin-top: 20px;
  }
  .left{
    flex: 4;
    .btn{
      margin: 10px 0px;
    }
  }
  .right{
    flex: 6;
    margin-left: 20px;
    p{
      line-height: 20px;
    }
  }
}
</style>

获取订单详情的数据P48

修改syt/src/api/user/type.ts
export interface OrderInfo {
    "id": number,
    "createTime": string,
    "updateTime": string,
    "isDeleted": number,
    "param": {
        "orderStatusString": string
    },
    "userId": number,
    "outTradeNo": string,
    "hoscode": string,
    "hosname": string,
    "depcode": string,
    "depname": string,
    "scheduleId": string,
    "title": string,
    "reserveDate":string,
    "reserveTime": number,
    "patientId": number,
    "patientName": string,
    "patientPhone": string,
    "hosRecordId": string,
    "number": number,
    "fetchTime": string,
    "fetchAddress": string,
    "amount": number,
    "quitTime": string,
    "orderStatus": number
}
export interface OrderInfoResponseData extends ResponseData{
    data:OrderInfo
}
修改syt/src/api/user/index.ts
enum API {
    SUBMITORDER_URL='/order/orderInfo/auth/submitOrder/',
    GETORDERINFO_URL = 'order/orderInfo/auth/getOrders/',
}

export const reqOrderInfo = (id:string)=>request.get<any,OrderInfoResponseData>(API.GETORDERINFO_URL+id);
修改syt/src/pages/user/order/detail/index.vue
<script setup lang="ts">

import {onMounted, ref} from "vue";
import {useRoute} from "vue-router";
import {reqOrderInfo} from "@/api/user";
import {OrderInfo, OrderInfoResponseData} from "@/api/user/type.ts";
let $route = useRoute();
let orderInfo = ref<OrderInfo>(<OrderInfo>{});
onMounted(()=>{
getOrderInfo();
})
const getOrderInfo = async ()=>{
  let result:OrderInfoResponseData = await reqOrderInfo($route.query.orderId as string);
  if(result.code==200){
    orderInfo.value = result.data;
  }
}
</script>

<template>
<el-card class="box-card">
  <template #header>
    <div class="detail">挂号详情</div>
  </template>
  <div class="top">
    <el-tag class="ml-2" type="success">
      <div class="tag">
        <svg t="1723598883339" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6216" width="16" height="16"><path d="M414.245926 751.028148l437.57037-453.12c7.300741-7.49037 7.111111-19.531852-0.474074-26.832593-7.49037-7.300741-19.531852-7.111111-26.832593 0.474074L398.506667 712.722963 199.111111 513.327407c-7.395556-7.395556-19.437037-7.395556-26.832593 0l0 0c-7.395556 7.395556-7.395556 19.437037 0 26.832593l212.574815 212.574815c6.731852 6.731852 17.161481 7.300741 24.557037 1.896296C411.211852 753.682963 412.823704 752.545185 414.245926 751.028148z" fill="#1afa29" p-id="6217"></path></svg>
        <span>{{orderInfo.param?.orderStatusString}}</span>
      </div>
    </el-tag>
    <div class="right_info">
      <img src="@/assets/images/code_app.png">
      <div>
        <p>微信 关注“北京114预约挂号”</p>
        <p>快速挂号,轻松就医</p>
      </div>
    </div>
  </div>
  <div class="bottom">
    <div class="left">
      <el-descriptions
          class="margin-top"
          :column="1"
          border
      >
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊人信息:
            </div>
          </template>
         {{orderInfo.patientName}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊日期:
            </div>
          </template>
         {{orderInfo.reserveDate}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊医院:
            </div>
          </template>
         {{orderInfo.hosname}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            就诊科室:
            </div>
          </template>
         {{orderInfo.depname}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            医生职称:
            </div>
          </template>
         {{orderInfo.title}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            医事服务费:
            </div>
          </template>
          <span style="color: red">{{orderInfo.amount}}元</span>
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            挂号单号:
            </div>
          </template>
         {{orderInfo.outTradeNo}}
        </el-descriptions-item>
        <el-descriptions-item>
          <template #label>
            <div class="cell-item">
            挂号时间:
            </div>
          </template>
        {{orderInfo.createTime}}
        </el-descriptions-item>
      </el-descriptions>
      <div class="btn">
        <el-button>取消预约</el-button>
        <el-button type="primary" size="default">支付</el-button>
      </div>
    </div>
    <div class="right">
      <el-card class="box-card">
        <template #header>
          <div class="card-header">
            <span>注意事项</span>
          </div>
        </template>
        <p>1、请确认就诊人信息是否准确,若填写错误将无法取号就诊,损失由本人承担;</p>
          <p style="color:red">2、【取号】就诊当天需在{{orderInfo.fetchTime}}在医院取号,未取号视为爽约,该号不退不换;</p>
        <p> 3、【退号】在{{orderInfo.quitTime}}前可在线退号 ,逾期将不可办理退号退费;</p>
        <p> 4、北京114预约挂号支持自费患者使用身份证预约,同时支持北京市医保患者使用北京社保卡在平台预约挂号。请于就诊当日,携带预约挂号所使用的有效身份证件到院取号;</p>
          <p> 5、请注意北京市医保患者在住院期间不能使用社保卡在门诊取号。</p>

      </el-card>
    </div>
  </div>
</el-card>
</template>

完成取消预约业务P49

修改syt/src/api/user/index.ts

修改syt/src/pages/user/order/detail/index.vue

ts

import {reqCancelOrderInfo, reqOrderInfo} from "@/api/user";
import {ElMessage} from "element-plus";
const cancel = async ()=>{
  try{
    let result =  await reqCancelOrderInfo(orderInfo.value.id as string);
    console.log(result);
    getOrderInfo();
  }catch (error) {
    ElMessage({
      type:'error',
      message:'取消预约失败'
    })
  }
}

template

<div class="btn" v-if="orderInfo.orderStatus==0||orderInfo.orderStatus==1">
        <el-popconfirm title="确认取消预约吗?" @confirm="cancel" >
          <template #reference>
            <el-button>取消预约</el-button>
          </template>
        </el-popconfirm>

        <el-button type="primary" size="default" v-if="orderInfo.orderStatus==0">支付</el-button>
      </div>

完成支付对话框静态搭建P50

修改syt/src/pages/user/order/detail/index.vue

ts

let dialogVisible = ref<boolean>(false)

const openDialog=()=>{
  dialogVisible.value=true;
}

const closeDialog=()=>{
  dialogVisible.value=false;
}

template

  <el-button type="primary" size="default" v-if="orderInfo.orderStatus==0" @click="openDialog">支付</el-button>
   <div class="paymentDialog">
  <el-dialog v-model="dialogVisible" title="微信支付" width="400">
    <div class="qrcode">
      <img src="@/assets/images/code_app.png">
      <p>请使用微信扫一扫</p>
      <p>扫码二维码支付</p>
    </div>

    <template #footer>
      <el-button size="default" @click="closeDialog">关闭窗口</el-button>
    </template>
  </el-dialog>
  </div>

scss

.paymentDialog{
  ::v-deep(.el-dialog__body){
    border-top: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
  }
  .qrcode{
    display: flex;
    flex-direction: column;
    align-items: center;
    p{
      line-height: 30px;
    }
  }
}

利用qrcode插件获取支付二维码P51

安装qrcode

 npm i qrcode
修改syt/src/api/user/index.ts
enum API {
    //提交订单
    SUBMITORDER_URL='/order/orderInfo/auth/submitOrder/',
    //获取订单信息
    GETORDERINFO_URL = 'order/orderInfo/auth/getOrders/',
    //取消订单
    CANCELORDER_URl = '/order/orderInfo/auth/cancelOrder/',

    QRCODE_URl = '/order/weixin/createNative/',
}
export const reqQrCode = (orderId:string) => request.get<any,QrCode>(API.QRCODE_URl+orderId);

修改syt/src/api/user/type.ts
export interface PayInfo{
    "codeUrl": string,
    "orderId": number,
    "totalFee": number,
    "resultCode": string
}
export  interface QrCode extends ResponseData{
    data:PayInfo
}
修改syt/src/pages/user/order/detail/index.vue

引入插件

import QRCode from 'qrcode'

获取支付二维码

let imgUrl = ref<string>('');
const openDialog= async ()=>{
  dialogVisible.value=true;
 let result : QrCode = await reqQrCode($route.query.orderId as string);
 imgUrl.value= await  QRCode.toDataURL(result.data.codeUrl);
}
 <div class="qrcode">
      <img :src="imgUrl">
      <p>请使用微信扫一扫</p>
      <p>扫码二维码支付</p>
    </div>

完成订单微信支付的业务P52

修改syt/src/api/user/index.ts
  //查询微信支付状态
    PAYSTATUS_URl = '/order/weixin/queryPayStatus/',
    export const reqQueryPayStatus = (orderId:string) => request.get<any,PayStatus>(API.PAYSTATUS_URl+orderId);
修改syt/src/api/user/type.ts
export interface PayStatus extends ResponseData{
   data:boolean
}
修改syt/src/pages/user/order/detail/index.vue

ts

let timer = ref();
const openDialog= async ()=>{
  dialogVisible.value=true;
 let result : QrCode = await reqQrCode($route.query.orderId as string);
 imgUrl.value= await  QRCode.toDataURL(result.data.codeUrl);
 timer.value = setInterval(async ()=>{
   let payStatus:PayStatus = await reqQueryPayStatus($route.query.orderId as string);
   console.log(payStatus.data)
   if(payStatus.data){
     clearInterval(timer.value);
     getOrderInfo();
   }
 },2000)

}
const closeDialog=()=>{
  dialogVisible.value=false;
  clearInterval(timer.value)
}
const close =()=>{
  clearInterval(timer.value);
}

template

关闭弹窗回调:清除定时器

<el-dialog v-model="dialogVisible" title="微信支付" width="400" @close="close">

实名认证模块静态搭建P53

修改syt/src/pages/user/certification/index.vue
<script setup lang="ts">

import {InfoFilled,Plus} from "@element-plus/icons-vue";
</script>

<template>
<!-- 实名认证组件的结构 -->
<el-card class="box-card">
<!-- 卡片的头部 -->
  <template #header>
    <div class="card-header">
      <span>实名信息</span>
      <el-button class="button" text></el-button>
    </div>
  </template>
<!-- 实名认证结果的提示部分 -->
  <div class="tip" style="color: #7f7f7f">
    <p><el-icon><InfoFilled></InfoFilled></el-icon>完成实名认证后才能添加就诊人,正常进行挂号,为了不影响后续步骤,建议提前实名认证</p>
  </div>
<!-- 卡片身体的底部:认证成功的结构、认证未成功的结构 -->
  <el-descriptions v-if="false"
      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>
      贾成豪
    </el-descriptions-item >
    <el-descriptions-item label-align="center" :width="20">
      <template #label>
        <div class="cell-item">
          证件类型
        </div>
      </template>
      身份证
    </el-descriptions-item>
    <el-descriptions-item label-align="center" width="20px">
      <template #label>
        <div class="cell-item">
          证件号
        </div>
      </template>
      23018218871011
    </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 style="width: 100%" placeholder="请选择证件类型">
        <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"
      >
        <el-icon><Plus /></el-icon>
      </el-upload>

      <el-dialog>
        <img w-full alt="Preview Image" />
      </el-dialog>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" size="default">提交</el-button>
      <el-button type="primary" size="default">重写</el-button>
    </el-form-item>
  </el-form>
</el-card>
</template>

<style scoped lang="scss">
.box-card{
  .tip{
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>

实名认证模块获取用户信息P54

修改syt/src/api/user/type.ts
export interface UserInfo{
    "id": number,
    "createTime": string,
    "updateTime": string,
    "isDeleted": number,
    "param": {

},
    "openid": string,
    "nickName": string,
    "phone": string,
    "name": string,
    "certificatesType": string,
    "certificatesNo": string,
    "certificatesUrl": string,
    "authStatus": number,
    "status": number
}


export interface UserInfoResponseData extends ResponseData{
    data:UserInfo;
}
修改syt/src/api/user/index.ts
enum API {
    //提交订单
    SUBMITORDER_URL='/order/orderInfo/auth/submitOrder/',
    //获取订单信息
    GETORDERINFO_URL = 'order/orderInfo/auth/getOrders/',
    //取消订单
    CANCELORDER_URl = '/order/orderInfo/auth/cancelOrder/',

    QRCODE_URl = '/order/weixin/createNative/',
    //查询微信支付状态
    PAYSTATUS_URl = '/order/weixin/queryPayStatus/',

    GETUSERINFO_URL='/user/auth/getUserInfo/'
}
export const reqGetUserinfo = ()=>request.get<any,UserInfoResponseData>(API.GETUSERINFO_URL);

修改syt/src/pages/user/certification/index.vue

ts

<script setup lang="ts">

import {InfoFilled,Plus} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {reqGetUserinfo} from "@/api/user";
import {UserInfoResponseData} from "@/api/user/type.ts";
let userInfo:any = ref<any>({});
onMounted(()=>{
  getUserInfo();
})
const getUserInfo= async ()=>{
 let result:UserInfoResponseData = await reqGetUserinfo();
 if(result.code==200){
   userInfo.value = result.data;
   console.log(userInfo.status)
 }
}
</script>

template

 <el-descriptions v-if="userInfo.authStatus!=0"
      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>
      {{userInfo.name}}
    </el-descriptions-item >
    <el-descriptions-item label-align="center" :width="20">
      <template #label>
        <div class="cell-item">
          证件类型
        </div>
      </template>
      {{userInfo.certificatesType}}
    </el-descriptions-item>
    <el-descriptions-item label-align="center" width="20px">
      <template #label>
        <div class="cell-item">
          证件号
        </div>
      </template>
      {{userInfo.certificatesNo}}
    </el-descriptions-item>

  </el-descriptions>
<!-- 用户未认证的结构 -->
  <el-form style="width: 60%;margin: 20px auto" label-width="80" v-if="userInfo.authStatus==0">
   

获取实名认证证件类型数据P55

修改syt/src/api/user/type.ts
export interface UserParams{
    "certificatesNo": string,
    "certificatesType": string,
    "certificatesUrl": string,
    "name": string
}
修改syt/src/api/user/index.ts
 //获取证件类型的接口地址
    CERTOFOCATOONTYPE_URL='/cmn/dict/findByDictCode/',
    //用户认证的结构
    USERAUTH_URL = '/user/auth/userAuth'
    
export const reqCertificatesType = (CertificatesType='CertificatesType')=>request.get<any,CertificationTypeResponseData>(API.CERTOFOCATOONTYPE_URL+CertificatesType);

export const reqUserAuth = (data:UserParams)=>request.post<any,any>(API.USERAUTH_URL,data);

修改syt/src/pages/user/certification/index.vue

ts

let arrType = ref<CertificationArr>([])
onMounted(()=>{
  getUserInfo();
  getType();
})
const getType = async ()=>{
  let result:CertificationTypeResponseData = await reqCertificatesType();
  if(result.code==200){
    arrType.value = result.data;
  }
}

template

 <el-form-item label="证件类型">
      <el-select style="width: 100%" placeholder="请选择证件类型">
        <el-option :label="item.name" :value="item.value" v-for="(item,index) in arrType" :key="index"></el-option>
      </el-select>
    </el-form-item>

收集表单数据完成实名认证业务P56

修改syt/src/pages/user/certification/index.vue

ts

<script setup lang="ts">

import {InfoFilled,Plus} from "@element-plus/icons-vue";
import {onMounted, reactive, ref} from "vue";
import {reqCertificatesType, reqGetUserinfo, reqUserAuth} from "@/api/user";
import {CertificationArr, CertificationTypeResponseData, UserInfoResponseData, UserParams} from "@/api/user/type.ts";
import {ElMessage} from "element-plus";
let upload=ref();
let userInfo:any = ref<any>({});
let arrType = ref<CertificationArr>([])
let params = reactive<UserParams>({
  certificatesNo: '',
  certificatesType: '',
  certificatesUrl: '',
  name: ''
});
let dialogVisible = ref(false);
onMounted(()=>{
  getUserInfo();
  getType();
})
const getUserInfo= async ()=>{
 let result:UserInfoResponseData = await reqGetUserinfo();
 if(result.code==200){
   userInfo.value = result.data;
 }
}
const getType = async ()=>{
  let result:CertificationTypeResponseData = await reqCertificatesType();
  if(result.code==200){
    arrType.value = result.data;
  }
}
const  exceedhandler = ()=>{
  ElMessage({
    type:'error',
    message:'只能上传一张图片'
  })
}
const successhandler =(response:any,uploadFile:any,uploadFiles:any)=>{
    params.certificatesUrl=response.data;
}

const handlerPictureCardPreview = ()=>{
dialogVisible.value=true;
}
const handlerRemove =()=>{
  params.certificatesUrl='';
}
const reset =()=>{
  upload.value.clearFiles();
  Object.assign(params,{
    certificatesNo: '',
    certificatesType: '',
    certificatesUrl: '',
    name: ''
  });
}

const submit =async ()=>{
  try{
    await reqUserAuth(params);
    getUserInfo();
    ElMessage({
      type:'success',
      message:'认证成功'
    })
  }catch (error){
    ElMessage({
      type:'error',
      message:'认证失败'
    })
  }

}
</script>

template

<el-form style="width: 60%;margin: 20px auto" label-width="80" v-if="userInfo.authStatus==0">
    <el-form-item label="用户姓名">
      <el-input placeholder="请输入用户姓名" v-model="params.name"></el-input>
    </el-form-item>
    <el-form-item label="证件类型">
      <el-select style="width: 100%" placeholder="请选择证件类型" v-model="params.certificatesType">
        <el-option :label="item.name" :value="item.value" v-for="(item,index) in arrType" :key="index"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="证件号" >
      <el-input placeholder="请输入证件号" v-model="params.certificatesNo"></el-input>
    </el-form-item>
    <el-form-item label="上传证件">
      <el-upload
          ref="upload"
          list-type="picture-card"
          action="/api/oss/file/fileUpload"
          limit="1"
          :on-exceed="exceedhandler"
          :on-success="successhandler"
          :on-preview="handlerPictureCardPreview"
          :on-remove="handlerRemove"

      >
        <img style="width: 100%;height: 100%" src="//img.114yygh.com/static/web/auth_example.png">
<!--        <el-icon><Plus /></el-icon>-->
      </el-upload>

      <el-dialog v-model="dialogVisible">
        <img w-full v-if="params.certificatesUrl" style="width: 100%;height: 100%" alt="Preview Image" :src="params.certificatesUrl" />
      </el-dialog>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" size="default" @click="submit">提交</el-button>
      <el-button type="primary" size="default" @click="reset">重写</el-button>
    </el-form-item>
  </el-form>

实名认证表单自定义校验P57

修改syt/src/pages/user/certification/index.vue
<script setup lang="ts">

import {InfoFilled,Plus} from "@element-plus/icons-vue";
import {onMounted, reactive, ref} from "vue";
import {reqCertificatesType, reqGetUserinfo, reqUserAuth} from "@/api/user";
import {CertificationArr, CertificationTypeResponseData, UserInfoResponseData, UserParams} from "@/api/user/type.ts";
import {ElMessage} from "element-plus";
let upload=ref();
let form = ref();
let userInfo:any = ref<any>({});
let arrType = ref<CertificationArr>([])
let params = reactive<UserParams>({
  certificatesNo: '',
  certificatesType: '',
  certificatesUrl: '',
  name: ''
});
let dialogVisible = ref(false);
onMounted(()=>{
  getType();
  getUserInfo();
})
const getUserInfo= async ()=>{
 let result:UserInfoResponseData = await reqGetUserinfo();
 if(result.code==200){
   userInfo.value = result.data;
 }
}
const getType = async ()=>{
  let result:CertificationTypeResponseData = await reqCertificatesType();
  if(result.code==200){
    arrType.value = result.data;
  }
}
const  exceedhandler = ()=>{
  ElMessage({
    type:'error',
    message:'只能上传一张图片'
  })
}
const successhandler =(response:any,uploadFile:any,uploadFiles:any)=>{
    params.certificatesUrl=response.data;
    form.value.clearValidate('certificatesUrl')
}

const handlerPictureCardPreview = ()=>{
dialogVisible.value=true;
}
const handlerRemove =()=>{
  params.certificatesUrl='';
}
const reset =()=>{
  upload.value.clearFiles();
  Object.assign(params,{
    certificatesNo: '',
    certificatesType: '',
    certificatesUrl: '',
    name: ''
  });
}

const submit =async ()=>{
  let result = await form.value.validate();
  try{
    await reqUserAuth(params);
    getUserInfo();
    ElMessage({
      type:'success',
      message:'认证成功'
    })
  }catch (error){
    ElMessage({
      type:'error',
      message:'认证失败'
    })
  }

}
//自定义姓名校验规则方法
const validatorName =(rule:any,value:any,callBack:any)=>{
  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)=>{
  if(value=='10'||value=='20'){
    callBack();
  }else{
    callBack(new Error('请选择正确的证件类型'))
  }
}
const validatorUrl =(rule:any,value:any,callBack:any)=>{
  if(value.length){
    callBack();
  }else{
    callBack(new Error('请上传证件照'))
  }
}
const validatorNo =(rule:any,value:any,callBack:any)=>{
  const sfz = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
  const hkb=/(^\d{15}$)/;
  if(sfz.test(value)||hkb.test(value)){
    callBack();
  }else{
    callBack(new Error('请输入正确的身份证或户口本号'))
  }
}
const  rules ={
  name: [{
    required:true,
    validator:validatorName
  }],
  certificatesNo: [{
    required:true,
    validator:validatorNo
  }],
  certificatesType: [{
    required:true,
    validator:validatorType
  }],
  certificatesUrl: [{
    required:true,
    validator:validatorUrl
  }],
}
</script>

<template>
<!-- 实名认证组件的结构 -->
<el-card class="box-card">
<!-- 卡片的头部 -->
  <template #header>
    <div class="card-header">
      <span>实名信息</span>
      <el-button class="button" text></el-button>
    </div>
  </template>
<!-- 实名认证结果的提示部分 -->
  <div class="tip" style="color: #7f7f7f">
    <p><el-icon><InfoFilled></InfoFilled></el-icon>完成实名认证后才能添加就诊人,正常进行挂号,为了不影响后续步骤,建议提前实名认证</p>
  </div>
<!-- 卡片身体的底部:认证成功的结构、认证未成功的结构 -->
  <el-descriptions v-if="userInfo.authStatus==1"
      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>
      {{userInfo.name}}
    </el-descriptions-item >
    <el-descriptions-item label-align="center" :width="20">
      <template #label>
        <div class="cell-item">
          证件类型
        </div>
      </template>
      {{userInfo.certificatesType}}
    </el-descriptions-item>
    <el-descriptions-item label-align="center" width="20px">
      <template #label>
        <div class="cell-item">
          证件号
        </div>
      </template>
      {{userInfo.certificatesNo}}
    </el-descriptions-item>

  </el-descriptions>
<!-- 用户未认证的结构 -->
  <el-form style="width: 60%;margin: 20px auto" label-width="80" v-if="userInfo.authStatus==0" :model="params" :rules="rules" ref="form">
    <el-form-item label="用户姓名" prop="name">
      <el-input placeholder="请输入用户姓名" v-model="params.name"></el-input>
    </el-form-item>
    <el-form-item label="证件类型" prop="certificatesType">
      <el-select style="width: 100%" placeholder="请选择证件类型" v-model="params.certificatesType">
        <el-option :label="item.name" :value="item.value" v-for="(item,index) in arrType" :key="index"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="证件号" prop="certificatesNo">
      <el-input placeholder="请输入证件号" v-model="params.certificatesNo"></el-input>
    </el-form-item>
    <el-form-item label="上传证件" prop="certificatesUrl">
      <el-upload
          ref="upload"
          list-type="picture-card"
          action="/api/oss/file/fileUpload"
          limit="1"
          :on-exceed="exceedhandler"
          :on-success="successhandler"
          :on-preview="handlerPictureCardPreview"
          :on-remove="handlerRemove"

      >
        <img style="width: 100%;height: 100%" src="//img.114yygh.com/static/web/auth_example.png">
<!--        <el-icon><Plus /></el-icon>-->
      </el-upload>

      <el-dialog v-model="dialogVisible">
        <img w-full v-if="params.certificatesUrl" style="width: 100%;height: 100%" alt="Preview Image" :src="params.certificatesUrl" />
      </el-dialog>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" size="default" @click="submit">提交</el-button>
      <el-button type="primary" size="default" @click="reset">重写</el-button>
    </el-form-item>
  </el-form>
</el-card>
</template>

<style scoped lang="scss">
.box-card{
  .tip{
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
</style>

挂号订单模块静态搭建P58

修改syt/src/pages/user/order/allOrder/index.vue
<script setup lang="ts">
import {ref} from "vue";

let pageNo = ref<number>(1);
let pageSize = ref<number>(10);
</script>

<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>挂号订单</span>
      </div>
    </template>
    <el-form :inline="true">
      <el-form-item label="就诊人" style="width: 30%">
        <el-select placeholder="请选择就诊人">
          <el-option label="111"></el-option>
          <el-option label="222"></el-option>
          <el-option label="333"></el-option>
          <el-option label="444"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="订单状态" style="width: 30%">
        <el-select placeholder="请选择订单状态">
          <el-option label="111"></el-option>
          <el-option label="222"></el-option>
          <el-option label="333"></el-option>
          <el-option label="444"></el-option>
        </el-select>
      </el-form-item>
    </el-form>
    <el-table border style="margin: 10px 0px">
      <el-table-column label="就诊时间"></el-table-column>
      <el-table-column label="医院"></el-table-column>
      <el-table-column label="科室"></el-table-column>
      <el-table-column label="医生"></el-table-column>
      <el-table-column label="服务费"></el-table-column>
      <el-table-column label="就诊人"></el-table-column>
      <el-table-column label="订单状态"></el-table-column>
      <el-table-column label="操作"></el-table-column>
    </el-table>
    <!-- 分页 -->
    <el-pagination
        v-model:current-page="pageNo"
        v-model:page-size="pageSize"
        class="pagination"
        layout="prev, pager, next,jumper,->,sizes,total"
        :total="400"
        :page-sizes="[10,20,30]"
        >
    </el-pagination>
  </el-card>
</template>

<style scoped lang="scss">

</style>

挂号订单模块API书写与TS类型定义P59

修改syt/src/api/user/type.ts
export type records = OrderInfo[];

export interface UserOrderInfoResponseData extends ResponseData{
    data:{
        records:records,
        total:number,
        size:number,
        current:number,
        orders:[],
        hitCount:boolean,
        searchCount:boolean,
        page:number
    }
}
修改syt/src/api/user/index.ts
USERORDERINFO_URL='/order/orderInfo/auth/'

export const reqUserOrderInfo = (page:number,limit:number,patientId:string,orderStatus:string)=>request.get<any,UserOrderInfoResponseData>(API.USERORDERINFO_URL+`${page}/${limit}?patientId=${patientId}&orderStatus=${orderStatus}`);

获取挂号订单数据P60

修改syt/src/pages/user/order/allOrder/index.vue
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {reqUserOrderInfo} from "@/api/user";
import type {Records, UserOrderInfoResponseData} from "@/api/user/type.ts";
import {useRouter} from "vue-router";

let pageNo = ref<number>(1);
let pageSize = ref<number>(10);
let orderStatus = ref<String>('');
let patientId = ref<String>('');
let allOrderArr = ref<Records>([]);
let total = ref<number>(0)
let $router = useRouter();
onMounted(()=>{
getOrderInfo();
})
const getOrderInfo = async ()=>{
 let result:UserOrderInfoResponseData = await reqUserOrderInfo(pageNo.value,pageSize.value,patientId.value,orderStatus.value);
 if(result.code==200){
    allOrderArr.value = result.data.records;
    total.value = result.data.total;
 }
}
const goDetail=(row)=>{
  $router.push({path:'/user/order',query:{orderId:row.id}})
}
</script>

<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>挂号订单</span>
      </div>
    </template>
    <el-form :inline="true">
      <el-form-item label="就诊人" style="width: 30%">
        <el-select placeholder="请选择就诊人">
          <el-option label="111"></el-option>
          <el-option label="222"></el-option>
          <el-option label="333"></el-option>
          <el-option label="444"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="订单状态" style="width: 30%">
        <el-select placeholder="请选择订单状态">
          <el-option label="111"></el-option>
          <el-option label="222"></el-option>
          <el-option label="333"></el-option>
          <el-option label="444"></el-option>
        </el-select>
      </el-form-item>
    </el-form>
    <el-table border style="margin: 10px 0px" :data="allOrderArr">
      <el-table-column label="就诊时间" prop="reserveDate"></el-table-column>
      <el-table-column label="医院" prop="hosname"></el-table-column>
      <el-table-column label="科室" prop="depname"></el-table-column>
      <el-table-column label="医生" prop="title"></el-table-column>
      <el-table-column label="服务费" prop="amount"></el-table-column>
      <el-table-column label="就诊人" prop="patientName"></el-table-column>
      <el-table-column label="订单状态" prop="param.orderStatusString"></el-table-column>
      <el-table-column label="操作" >
        <template #="{row}">
          <el-button type="text" @click="goDetail(row)">详情</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <el-pagination
        v-model:current-page="pageNo"
        v-model:page-size="pageSize"
        class="pagination"
        layout="prev, pager, next,jumper,->,sizes,total"
        :total="total"
        :page-sizes="[10,20,30,40]"
        @current-change="getOrderInfo"
        @size-change="getOrderInfo"
        >
    </el-pagination>
  </el-card>
</template>

<style scoped lang="scss">

</style>

获取就诊人与订单状态API完成P61

就诊人信息在syt/src/api/hospital/index.ts中已定义,直接使用

修改syt/src/api/user/type.ts
export interface OrderState{
    "comment":string,
    "status":number
}

export type AllOrderState = OrderState[];

export interface AllOrderStateResponseData extends ResponseData{
    data:AllOrderState
}
修改syt/src/api/user/index.ts
ORDERSTATE_URL = '/order/orderInfo/auth/getStatusList'
export const reqOrderState = ()=>request.get<any,AllOrderStateResponseData>(API.ORDERSTATE_URL);

完成挂号订单模块P62

修改syt/src/pages/user/order/allOrder/index.vue

ts

import type {AllOrderStateResponseData, Records, UserOrderInfoResponseData} from "@/api/user/type.ts";
import type {PatientResponseData} from "@/api/hospital/type.ts";
import type {PatientArr} from "@/api/hospital/type.ts";
import type {AllOrderState} from "@/api/user/type.ts";
let allUsr =  ref<PatientArr>([]);
let allOrderState = ref<AllOrderState>([])
let allUsr =  ref<PatientArr>([]);
let allOrderState = ref<AllOrderState>([])
onMounted(()=>{
getOrderInfo();
getData();
})
const getData = async ()=>{
 const result:PatientResponseData =  await reqPatient();
 const orderStateResult:AllOrderStateResponseData = await reqOrderState();
 allUsr.value = result.data;
 allOrderState.value = orderStateResult.data;
}

template

  <el-form :inline="true">
      <el-form-item label="就诊人" style="width: 30%">
        <el-select placeholder="请选择就诊人" v-model="patientId" @change="changeUser">
          <el-option :label="item.name" v-for="item in allUsr":key="item.id" :value="item.id"></el-option>

        </el-select>
      </el-form-item>
      <el-form-item label="订单状态" style="width: 30%">
        <el-select placeholder="请选择订单状态" v-model="orderStatus" @change="changeOrderState">
          <el-option label="全部订单" value=""></el-option>
          <el-option :label="item.comment" v-for="item in allOrderState":key="item.status" :value="item.status"></el-option>

        </el-select>
      </el-form-item>

就诊人管理模块展示全部就诊人信息P63

将就诊人组件提到组件模块

syt/src/pages/hospital/register/visiter.vue=》syt/src/components/visiter/visiter.vue

修改syt/src/components/visiter/visiter.vue
import {Delete, Edit} from "@element-plus/icons-vue";
import {useRoute} from "vue-router";

let $useRoute = useRoute();
 <div class="right">
        <el-button type="primary" size="default" :icon="Edit"></el-button>
<!--        <el-button v-if="$useRoute.path=='/user/patient'" type="danger" size="default" :icon="Delete"></el-button>-->
        <slot name="otherBtn" > </slot>
      </div>
修改syt/src/main.ts
import Visiter from "@/components/visiter/visiter.vue";
app.component('Visiter',Visiter)
修改syt/src/pages/hospital/register/register_step2.vue

删除该行代码

import Visiter from "@/pages/hospital/register/visiter.vue";
修改syt/src/pages/user/patient/index.vue
<script setup lang="ts">

import {Delete, User} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {PatientArr, PatientResponseData} from "@/api/hospital/type.ts";
import {reqPatient} from "@/api/hospital";
let patientArr = ref<PatientArr>();
onMounted(()=>{
  fetchPatietnt();
})
const fetchPatietnt= async ()=>{
  let result:PatientResponseData = await reqPatient();
  if(result.code==200){
    patientArr.value=result.data;
  }
}
</script>

<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>就诊人管理</span>
        <el-button size="default" type="primary" :icon="User">添加就诊人</el-button>
      </div>
    </template>
    <div class="visiter">
    <Visiter v-for="(patient,index) in patientArr" :key="patient.id" class="item" :patient="patient" :index="index" >
      <template  #otherBtn>
        <el-button type="danger" size="default" :icon="Delete"></el-button>
      </template>
    </Visiter>
    </div>
  </el-card>
</template>

<style scoped lang="scss">
.box-card{
  margin: 20px 0px;
  .card-header{
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .visiter{
    display: flex;
    flex-wrap: wrap;
    .item{
      width: 32%;
      margin: 5px;
      border: solid 1px #ccc;
    }
  }
}
</style>

完成添加与修改就诊人静态搭建P64

修改syt/src/components/visiter/visiter.vue
let $emit = defineEmits(['changeScene']);
<el-button type="primary" size="default" :icon="Edit" @click="$emit('changeScene')"></el-button>
修改syt/src/pages/user/patient/index.vue
<script setup lang="ts">

import {Delete, User} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {PatientArr, PatientResponseData} from "@/api/hospital/type.ts";
import {reqPatient} from "@/api/hospital";
let patientArr = ref<PatientArr>();
let scene = ref<number>(0);
onMounted(()=>{
  fetchPatietnt();
})
const fetchPatietnt= async ()=>{
  let result:PatientResponseData = await reqPatient();
  if(result.code==200){
    patientArr.value=result.data;
  }
}
const addPatient =()=>{
  scene.value=1;
}
const changeScene =()=>{
  scene.value=1;
}
</script>

<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>就诊人管理</span>
        <el-button size="default" type="primary" :icon="User" @click="addPatient">添加就诊人</el-button>
      </div>
    </template>
    <div class="visiter" v-if="scene==0">
    <Visiter @changeScene="changeScene" v-for="(patient,index) in patientArr" :key="patient.id" class="item" :patient="patient" :index="index" >
      <template  #otherBtn>
        <el-button type="danger" size="default" :icon="Delete"></el-button>
      </template>
    </Visiter>
    </div>
    <div class="form" v-if="scene==1">
      <el-divider content-position="left">就诊人信息</el-divider>
      <el-form style="width: 60%;margin: 10px auto">
        <el-form-item label="用户姓名">
          <el-input placeholder="请你输入用户姓名"></el-input>
        </el-form-item>
        <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型">
            <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-radio-group>
           <el-radio :label="1">男</el-radio>
           <el-radio :label="0">女</el-radio>
         </el-radio-group>
        </el-form-item>
        <el-form-item label="出生日期">
          <el-date-picker type="date" placeholder="请你选择日期"/>
        </el-form-item>
        <el-form-item label="手机号码">
          <el-input placeholder="请你输入用户手机号码"></el-input>
        </el-form-item>
      </el-form>
      <el-divider content-position="left">建档信息(完善后部分医院首次就诊不排队建档)</el-divider>
      <el-form style="width: 60%;margin: 10px auto">
        <el-form-item label="婚姻状况">
          <el-radio-group>
            <el-radio :label="1">已婚</el-radio>
            <el-radio :label="0">未婚</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="自费/医保">
          <el-radio-group>
            <el-radio :label="1">自费</el-radio>
            <el-radio :label="0">医保</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="当前住址">
          <el-select style="width: 100%" placeholder="请选择用户地址">
            <el-option v-for="item in 10" :label="item"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <el-divider content-position="left">联系人信息(选填)</el-divider>
      <el-form style="width: 60%;margin: 10px auto" label-width="80px">
        <el-form-item label="用户姓名">
          <el-input placeholder="请你输入用户姓名"></el-input>
        </el-form-item>
        <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型">
            <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-input placeholder="请你输入用户手机号码"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" size="default">提交</el-button>
          <el-button type="primary" size="default">重写</el-button>
        </el-form-item>
      </el-form>
    </div>
  </el-card>
</template>

<style scoped lang="scss">
.box-card{
  margin: 20px 0px;
  .card-header{
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .visiter{
    display: flex;
    flex-wrap: wrap;
    .item{
      width: 32%;
      margin: 5px;
      border: solid 1px #ccc;
    }
  }
}
</style>

获取证件类型及级联选择器展示地址P65

修改syt/src/api/user/index.ts
// CITY_URL='/cmn/dict/findByParentId/',
    CITY_URL='/cmn/dict/findChildData/',//本地
    export const reqCity = (parentId:string)=>request.get<any,any>(API.CITY_URL+parentId)
修改syt/src/pages/user/patient/index.vue
import type {CertificationArr, CertificationTypeResponseData} from "@/api/user/type.ts";
let certificationArr = ref<CertificationArr>([]);
onMounted(()=>{
  fetchPatietnt();
  getCertificationType();
})
const getCertificationType=async ()=>{
  let result :CertificationTypeResponseData = await reqCertificatesType();
  if(result.code==200){
    certificationArr.value = result.data;
  }
}
  <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型">
            <el-option label="身份证" v-for="item in certificationArr" :key="item.id" :label="item.name" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型">
            <el-option label="身份证" v-for="item in certificationArr" :key="item.id" :label="item.name" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
import {CascaderProps} from "element-plus";
const props:CascaderProps ={
  lazy:true,
 async lazyLoad(node:any,resolve:any){
    let result:any = await reqCity(node.data.id||'86');
    let showData = result.data.map((item:any)=>{
      return{
        id:item.id,
        label:item.name,
        value:item.value,
        leaf:!item.hasChildren
      }

    })
    resolve(showData);
  }
}
<el-form-item label="当前住址">
         <el-cascader :props="props"/>
        </el-form-item>
        

添加就诊人与更新就诊人的API书写66

修改syt/src/api/user/index.ts
import request from "@/utils/request.ts";
import {
    AddOrUpdateUser,
    AllOrderStateResponseData,
    CertificationTypeResponseData,
    OrderInfoResponseData,
    PayStatus,
    QrCode,
    SubmitOrderResponseData,
    UserInfoResponseData, UserOrderInfoResponseData, UserParams
} from "@/api/user/type.ts";

ADDUSER_URL = '/user/patient/auth/save/',
    UPDATEUSER_URL = '/user/patient/auth/update/',
export const reqAddOrUpdateUser = (data:AddOrUpdateUser)=>{
    if(!data.id){
        return request.post(API.ADDUSER_URL,data)
    }else{
        return request.post(API.UPDATEUSER_URL,data);
    }
}

修改syt/src/api/user/type.ts
export interface AddOrUpdateUser {
    address:string,
    addressSelected: [],
    birthdate:string,
    certificatesNo:string,
    certificatesType:string,
    cityCode:string,
    contactsCertificatesNo:string,
    contactsCertificatesType:string,
    contactsName:string,
    contactsPhone:string,
    districtCode:string,
    isDeleted:number,
    isInsure:number,
    isMarry:number,
    name:string,
    phone:string,
    provinceCode:string,
    sex:number,
    id:number
}

完成就诊人添加的业务P67

修改syt/src/pages/hospital/register/register_step2.vue
const addPatient = ()=>{
  $router.push({path:'/user/patient',query:{type:'add'}});
}
 <div class="card-header">
          <span>请选择就诊人</span>
          <el-button size="default" type="primary" :icon="User" @click="addPatient">添加就诊人</el-button>
        </div>
修改syt/src/pages/user/patient/index.vue
<script setup lang="ts">

import {Delete, User} from "@element-plus/icons-vue";
import {onMounted, reactive, ref} from "vue";
import {PatientArr, PatientResponseData} from "@/api/hospital/type.ts";
import {reqPatient} from "@/api/hospital";
import {reqAddOrUpdateUser, reqCertificatesType, reqCity} from "@/api/user";
import {CascaderProps, ElMessage} from "element-plus";
import type {AddOrUpdateUser, CertificationArr, CertificationTypeResponseData} from "@/api/user/type.ts";
import {useRoute, useRouter} from "vue-router";
let patientArr = ref<PatientArr>();
let scene = ref<number>(0);
let certificationArr = ref<CertificationArr>([]);
let patientParams = reactive<AddOrUpdateUser>({
  id: 0,
  address:'',
  addressSelected: [],
  birthdate:'',
  certificatesNo:'',
  certificatesType:'',
  cityCode:'',
  contactsCertificatesNo:'',
  contactsCertificatesType:'',
  contactsName:'',
  contactsPhone:'',
  districtCode:'',
  isDeleted:0,
  isInsure:0,
  isMarry:0,
  name:'',
  phone:'',
  provinceCode:'',
  sex:0,
})
let $route = useRoute();
let $router = useRouter();

onMounted(()=>{
  fetchPatient();
  getCertificationType();
     if($route.query.type=='add'){
    scene.value=1;
  }
})
const fetchPatient= async ()=>{
  let result:PatientResponseData = await reqPatient();
  if(result.code==200){
    patientArr.value=result.data;
  }
}
const addPatient =()=>{
  scene.value=1;
  reset();
}
const changeScene =()=>{
  scene.value=1;
}
const getCertificationType=async ()=>{
  let result :CertificationTypeResponseData = await reqCertificatesType();
  if(result.code==200){
    certificationArr.value = result.data;
  }
}
const props:CascaderProps ={
  lazy:true,
 async lazyLoad(node:any,resolve:any){
    let result:any = await reqCity(node.data.id||'86');
    let showData = result.data.map((item:any)=>{
      return{
        id:item.id,
        label:item.name,
        value:item.value,
        leaf:!item.hasChildren
      }

    })
    resolve(showData);
  }
}
const submit = async ()=>{
  try{
    //地址处理
      patientParams.provinceCode = patientParams.addressSelected[0] || ''
      patientParams.cityCode =patientParams.addressSelected[1] || ''
      patientParams.districtCode = patientParams.addressSelected[2] || ''
    let result:any = await reqAddOrUpdateUser(patientParams);
    if(result.code==200){
      ElMessage({
        type:'success',
        message:patientParams.id?'更新成功':'添加成功'
      })
    }else{
      ElMessage({
        type:'error',
        message:result.message
      })
    }
    if($route.query.type){
      $router.back();
    }else{
      scene.value=0;
    }
  }catch (error){
    ElMessage({
      type:'error',
      message:patientParams.id?'更新失败':'添加失败'
    })
  }

}
const reset = ()=>{
Object.assign(patientParams,{
  address:'',
  addressSelected: [],
  birthdate:'',
  certificatesNo:'',
  certificatesType:'',
  cityCode:'',
  contactsCertificatesNo:'',
  contactsCertificatesType:'',
  contactsName:'',
  contactsPhone:'',
  districtCode:'',
  isDeleted:0,
  isInsure:0,
  isMarry:0,
  name:'',
  phone:'',
  provinceCode:'',
  sex:0,
})
}
</script>

<template>
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>就诊人管理</span>
        <el-button size="default" type="primary" :icon="User" @click="addPatient">添加就诊人</el-button>
      </div>
    </template>
    <div class="visiter" v-if="scene==0">
    <Visiter @changeScene="changeScene" v-for="(patient,index) in patientArr" :key="patient.id" class="item" :patient="patient" :index="index" >
      <template  #otherBtn>
        <el-button type="danger" size="default" :icon="Delete"></el-button>
      </template>
    </Visiter>
    </div>
    <div class="form" v-if="scene==1">
      <el-divider content-position="left">就诊人信息</el-divider>
      <el-form style="width: 60%;margin: 10px auto">
        <el-form-item label="用户姓名" >
          <el-input placeholder="请你输入用户姓名" v-model="patientParams.name"></el-input>
        </el-form-item>
        <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型" v-model="patientParams.certificatesType">
            <el-option label="身份证" v-for="item in certificationArr" :key="item.id" :label="item.name" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="证件号码">
          <el-input placeholder="请你输入证件号码"  v-model="patientParams.certificatesNo"></el-input>
        </el-form-item>
        <el-form-item label="用户性别">
         <el-radio-group v-model="patientParams.sex">
           <el-radio :label="1">男</el-radio>
           <el-radio :label="0">女</el-radio>
         </el-radio-group>
        </el-form-item>
        <el-form-item label="出生日期">
          <el-date-picker type="date" placeholder="请你选择日期" value-format="YYYY-MM-DD" v-model="patientParams.birthdate"/>
        </el-form-item>
        <el-form-item label="手机号码">
          <el-input placeholder="请你输入用户手机号码" v-model="patientParams.phone"></el-input>
        </el-form-item>
      </el-form>
      <el-divider content-position="left">建档信息(完善后部分医院首次就诊不排队建档)</el-divider>
      <el-form style="width: 60%;margin: 10px auto">
        <el-form-item label="婚姻状况">
          <el-radio-group v-model="patientParams.isMarry">
            <el-radio :label="1">已婚</el-radio>
            <el-radio :label="0">未婚</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="自费/医保">
          <el-radio-group v-model="patientParams.isInsure">
            <el-radio :label="1">自费</el-radio>
            <el-radio :label="0">医保</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="当前住址">
         <el-cascader :props="props"  v-model="patientParams.addressSelected"/>
        </el-form-item>
        <el-form-item label="详细地址:">
          <el-input v-model="patientParams.address" placeholder="应公安机关要求,请填写现真实住址"/>
        </el-form-item>
      </el-form>
      <el-divider content-position="left">联系人信息(选填)</el-divider>
      <el-form style="width: 60%;margin: 10px auto" label-width="80px">
        <el-form-item label="用户姓名">
          <el-input placeholder="请你输入用户姓名" v-model="patientParams.contactsName"></el-input>
        </el-form-item>
        <el-form-item label="证件类型">
          <el-select style="width: 100%" placeholder="请选择证件类型" v-model="patientParams.contactsCertificatesType">
            <el-option label="身份证" v-for="item in certificationArr" :key="item.id" :label="item.name" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="证件号码">
          <el-input placeholder="请你输入证件号码" v-model="patientParams.contactsCertificatesNo"></el-input>
        </el-form-item>
        <el-form-item label="手机号码">
          <el-input placeholder="请你输入用户手机号码"  v-model="patientParams.contactsPhone"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" size="default" @click="submit">提交</el-button>
          <el-button type="primary" size="default" @click="reset">重写</el-button>
        </el-form-item>
      </el-form>
    </div>
  </el-card>
</template>

完成更新已有就诊人业务P68

修改syt/src/components/visiter/visiter.vue
 <el-button type="primary" size="default" :icon="Edit" @click="handler"></el-button>
 <script setup lang="ts">

import {Delete, Edit} from "@element-plus/icons-vue";
import {useRoute, useRouter} from "vue-router";

let $useRoute = useRoute();
let $useRouter = useRouter();
let props = defineProps(['patient', 'index', 'currentIndex'])
let $emit = defineEmits(['changeScene']);
const handler = ()=>{
  if($useRoute.path=='/user/patient'){
    $emit('changeScene',props.patient)
  }else{
  $useRouter.push({path:'/user/patient',query:{type:'edit',id:props.patient.id}})
  }
}
</script>
修改syt/src/pages/user/patient/index.vue
import {onMounted, reactive, ref, watch} from "vue";
onMounted(()=>{
  fetchPatient();
  getCertificationType();
  if($route.query.type=='add'){
    scene.value=1;
  }
  if($route.query.type=='edit'){
    scene.value=1;
  }
})
const changeScene =(user:AddOrUpdateUser)=>{
  scene.value=1;
  Object.assign(patientParams,user);
}
watch(()=>patientArr.value,()=>{
  let patient = patientArr.value?.find((item:any)=>{
    return item.id == $route.query.id
  })
  Object.assign(patientParams,patient)
})

完成删除就诊人信息业务P69

修改syt/src/api/user/index.ts
   DELETEUSER_URL='/user/patient/auth/remove/'
   export const reqRemoveUser = (id:number)=>request.delete(API.DELETEUSER_URL+id);
修改syt/src/components/visiter/visiter.vue

template

<div class="right">
        <el-button type="primary" size="default" :icon="Edit" @click="handler"></el-button>
         <el-popconfirm @confirm="removeUser" :title="`你确定要删除${patient.name}吗?`" width="200px">
          <template #reference>
            <el-button v-if="$useRoute.path=='/user/patient'" type="danger" size="default" :icon="Delete"></el-button>
          </template>
        </el-popconfirm>
          </div>

ts

import {useRoute, useRouter} from "vue-router";
import {reqRemoveUser} from "@/api/user";
import {ElMessage} from "element-plus";
let $emit = defineEmits(['changeScene','removeUser']);
const removeUser=async ()=>{
  try{
    await reqRemoveUser(props.patient.id);
    $emit('removeUser')
    ElMessage({
      type:'success',
      message:'删除成功'
    })
  }catch (error){
    ElMessage({
      type:'error',
      message:'删除失败'
    })
  }

}
修改syt/src/pages/user/patient/index.vue
 const removeUser =()=>{
   fetchPatient();
 }
 <Visiter @changeScene="changeScene" v-for="(patient,index) in patientArr" :key="patient.id" class="item" :patient="patient" :index="index"
             @removeUser="removeUser"
    >

路由鉴权之进度条与网页标题设置P70

安装进度条

npm i nprogress
创建syt/src/permisstion.ts
import router from "@/router";
//路由鉴权:就是路由能不能被访问到授权设置-》全局守卫完成
import Nprogress from 'nprogress'
import 'nprogress/nprogress.css'
Nprogress.configure({showSpinner:false})
router.beforeEach((to,from,next)=>{
   //访问路由组件之前,进度条开始动
    Nprogress.start();
    document.title = `尚医通-${to.meta.title}`
    next();
});
router.afterEach((to,from)=>{
   //访问路由组件成功,进度条消失
    Nprogress.done();
})

修改syt/src/main.ts
import '@/permisstion'
修改syt/src/router/index.ts
import {createRouter,createWebHistory} from "vue-router";
//createRouter方法,用于创建路由实例,可以管理多个路由


export default createRouter({
    // 路由模式设置
    history:createWebHistory(),
    routes:[
        {
            path:'/home',
            component:()=> import((`@/pages/home/index.vue`)),
            meta:{
                title:'首页'
            }
        },
        {
            path:'/hospital',
            component:()=>import((`@/pages/hospital/index.vue`)),
            meta:{
                title:'医院信息'
            },
            children:[
                {
                    path:'register',
                    component:()=>import(('@/pages/hospital/register/index.vue')),
                    meta:{
                        title:'预约挂号'
                    },
                },
                {
                    path:'detail',
                    component:()=>import(('@/pages/hospital/detail/index.vue')),
                    meta:{
                        title:'医院详情'
                    },
                },
                {
                    path:'notice',
                    component:()=>import(('@/pages/hospital/notice/index.vue')),
                    meta:{
                        title:'预约须知'
                    },
                },
                {
                    path:'close',
                    component:()=>import(('@/pages/hospital/close/index.vue')),
                    meta:{
                        title:'停诊信息'
                    },
                },
                {
                    path:'search',
                    component:()=>import(('@/pages/hospital/search/index.vue')),
                    meta:{
                        title:'查询与取消'
                    },
                },
                {
                    path:'register_step1',
                    component:()=>import(('@/pages/hospital/register/register_step1.vue')),
                    meta:{
                        title:'预约挂号'
                    },
                },
                {
                    path:'register_step2',
                    component:()=>import(('@/pages/hospital/register/register_step2.vue')),
                    meta:{
                        title:'预约挂号'
                    },
                }
            ]
        },
        {
            path:'/wxlogin',
            component:()=>import(('@/pages/wxlogin/index.vue')),
        },
        {
            path:'/user',
            component:()=>import(('@/pages/user/index.vue')),
            meta:{
                title:'会员中心'
            },
            children:[
                {
                    path:'certification',
                    component:()=>import(('@/pages/user/certification/index.vue')),
                    meta:{
                        title:'实名认证'
                    },
                },
                {
                    path:'feedback',
                    component:()=>import(('@/pages/user/feedback/index.vue')),
                    meta:{
                        title:'意见反馈'
                    },
                },
                {
                    path:'order',
                    component:()=>import(('@/pages/user/order/index.vue')),
                    meta:{
                        title:'挂号订单'
                    },
                },
                {
                    path:'patient',
                    component:()=>import(('@/pages/user/patient/index.vue')),
                    meta:{
                        title:'就诊人管理'
                    },
                },
                {
                    path:'profile',
                    component:()=>import(('@/pages/user/profile/index.vue')),
                    meta:{
                        title:'账号信息'
                    },
                },
            ]
        },
        {
            path:'/',
            redirect:'/home'
        }
    ],
    //滚动行为:控制滚动条的位置
    scrollBehavior(){
        return{
            left:0,
            top:0
        }
    }
})

路由鉴权完成P71

修改syt/src/permisstion.ts
import router from "@/router";
//路由鉴权:就是路由能不能被访问到授权设置-》全局守卫完成
import Nprogress from 'nprogress'
import 'nprogress/nprogress.css'
import userUserStore from '@/store/modules/user'
import pinia from '@/store'

Nprogress.configure({showSpinner: false})
let userStore = userUserStore(pinia);
let whiteList = ['/home', 'hospital/register', '/hospital/detail', '/hospital/notice', '/hospital/close', '/hospital/search'];
router.beforeEach((to, from, next) => {
    //访问路由组件之前,进度条开始动
    Nprogress.start();
    document.title = `尚医通-${to.meta.title}`;
    let token = userStore.userInfo.token;
    if (token) {
        next();
    } else {
        if (whiteList.includes(to.path)) {
            next();
        } else {

            if(to.path.indexOf('/wxlogin')==-1){
                userStore.visible = true;
            }else{
                next();
            }
            next({path: '/home', query: {redirect: to.fullPath}});
        }

    }

});
router.afterEach((to, from) => {
    //访问路由组件成功,进度条消失
    Nprogress.done();
})

修改syt/src/components/login/index.vue
let $route = useRoute();
let $router = useRouter();
const login = async () => {
  await form.value.validate();

  try {
    await userStore.userLogin(loginParam);
    userStore.visible = false
    let redirect = $route.query.redirect;
    if(redirect){
      $router.push(redirect as string)
    }else{
      $router.push('/home')
    }
  } catch (error) {
    ElMessage({
      type: 'error',
      message: (error as Error).message
    })
  }
}

项目路径https://gitee.com/lyfengluo/syt

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值