一、项目介绍:
这是一个医疗类的小程序,主要用于新冠疫苗预约,HPV疫苗预约,核酸预约,和咨询等,主要作用就是方便快捷,可以在手机上进行预约挂号,和一些健康自测的功能,还有一些医生专家讲座,分享一些疾病宣传及预防
二、项目使用技术栈:
✨脚手架工具:uniapp 和 高效、快速的 Vite
🔥前端框架:眼下最时髦的 Vue3
🍍状态管理器:vue3
新秀 Pinia,犹如 react zustand
般的体验,友好的api和异步处理
🏆开发语言 TypeScript
三、项目模块:
项目目录:
com-components:
文件名 | 页面属性 |
point.vue | 页面没有数据,为空的时候显示该页面 |
st-member.vue | 模态框,点击显示模态框,选择就诊人,提价数据 |
pages:
文件夹名 | 页面属性 |
login-page | 登录授权 |
index | 首页 |
registered | 挂号 |
video | 医师课堂 |
mine | 我的 |
xinguan-vaccine | 新冠接种疫苗接种预约 |
hpv-vaccine | hpv页面和详情页 |
hesuan-vaccine | 核酸检测预约 |
graphics | 图文咨询 |
phy-exam | 体检套餐列表页面和体检详情页 |
my-service | 存放的是每个页面的二级三级....页面 |
my-service:
文件夹名 | 页面属性 |
hesuan | 核酸预约列表 |
hpv-view | hpv 预约列表 |
my-patient | 我的就诊人和添加就诊人 |
xinguan | 新冠疫苗预约订单 |
phy-view | 体检预约订单 |
public:
文件名 | 页面属性 |
request.ts | 接口封装 |
decl-type.d.ts | 类型声明文件 .d.ts文件 不能执行逻辑操作 只能用来声明 不会转化成js文件 |
misc.ts | 上传图片的方法 |
四、 项目开发前期准备工作:
1、打开uniapp官网下载项目框架
这次做的是uniapp,vue3,ts的项目,以下图片可根据需求下载,点击gitee到git码云仓库下载
2、获取Appid
重定项目Appid
打开src/manifest.json文件,微信小程序配置里,更改为你自己的小程序APPID
3,下载依赖
npm install
4、运行
在命令窗口输入: npm run dev:mp-weixin ,用于解析代码转换小程序原生代码
运行完之后会生成一个dist文件夹,
5、生成dist文件包,因为本项目用到vue3+ts语法,所以选用vs code编译器,但是不能主动调去微信开发者工具。所以手动拖动项目到微信开发者平台
五、封装接口
1、在src下创建public文件夹,在这个文件夹加下面创建request.ts文件
2、在这个页面下定义一个对象,用于接口管理,然后用export导出
3、登录授权,下载js-base64,封装一个方法用于获取本地存储的token,在请求接口request()里的header使用这个方法
//公用请求
const baseUrl = 'https://meituan.thexxdd.cn/api'
//获取token npm install -- save js-base64
import { Base64 } from 'js-base64'
import {
Rescovidapi,
Wxloginapi,
Covidcancelapi,
Hpvorder,
Resnuataapi,
AddPatient,
Graphics,
FilterData,
PhyRes
} from "@/public/decl-type"
function getToken(): string {
const token = uni.getStorageSync('wxuser').user_Token || ''
//后端和前端的约定
const base64_token = Base64.encode(token + ':')
return 'Basic ' + base64_token
}
//请求
function request(url: string, method: 'GET' | 'POST', data: string | object | ArrayBuffer) {
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + url,
method,
data,
header: {
Authorization: getToken()
},
success: (res: any) => {
if (res.statusCode == 200) {
resolve(res)
} else if (res.statusCode == 401) {
//权限过期
// 没有权限访问接口:跳转到登陆界面
uni.navigateTo({ url: '/pages/login-page/index' });
uni.showToast({
title: "信息过期",
icon: 'none',
duration: 1000
})
reject(res)
} else if (res.statusCode == 400) {
uni.showToast({
title: '开发者某个字段或参数填写不对',
icon: 'none',
duration: 1000,
});
reject(res);
} else if (res.statusCode == 500) {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(res)
} else if (res.statusCode == 202) {
uni.showToast({
title: res.data.msg,
icon: 'none',
duration: 1000
})
reject(res)
} else {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(res)
}
},
fail: (err: any) => {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(err)
}
})
})
}
//接口管理
const RequestApi = {
//首页数据
Frontpage: () => request('/frontpage', 'GET', {}),
//新冠疫苗预约时段
NewappTime: () => request('/newapptime', 'GET', {}),
}
//上传图片
let IMAGEURL = baseUrl + "/upload_picture"
//上传图片,身份证信息
let AICARD = baseUrl + "/ai_card";
export { RequestApi, IMAGEURL, AICARD }
登录逻辑:
1、下载js-base64 下载方法:npm install -- save js-base64
2、在封住的request里面写请求头
3、请求小程序登录接口,我这个使用ts写的蓝色的单词是ts的规范接口类型,用js的把这个删了就行了
4、在登录页面登录按钮添加点击事件、当点击事件触发的时候进行的操作
在登录页面调用封装好的登录接口
5、 登录写一个方法用于调用api接口登录
6、点击登录
7、在访问一些需要登录的页面,没有登录或者登录过期的时候后台会返回一个失败信息,这个失败信息的statusCode是401,所以需要在请求接口的时候判断,如果返回的是401,需要跳转到登录页面,重新登陆。
代码:
const baseUrl = 'https://meituan.thexxdd.cn/api'
//获取token npm install -- save js-base64
import { Base64 } from 'js-base64'
function getToken(): string {
const token = uni.getStorageSync('wxuser').user_Token || ''
//后端和前端的约定
const base64_token = Base64.encode(token + ':')
return 'Basic ' + base64_token
}
//请求
function request(url: string, method: 'GET' | 'POST', data: string | object | ArrayBuffer) {
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + url,
method,
data,
header: {
Authorization: getToken()
},
success: (res: any) => {
if (res.statusCode == 200) {
resolve(res)
} else if (res.statusCode == 401) {
//权限过期
// 没有权限访问接口:跳转到登陆界面
uni.navigateTo({ url: '/pages/login-page/index' });
uni.showToast({
title: "信息过期",
icon: 'none',
duration: 1000
})
reject(res)
} else if (res.statusCode == 400) {
uni.showToast({
title: '开发者某个字段或参数填写不对',
icon: 'none',
duration: 1000,
});
reject(res);
} else if (res.statusCode == 500) {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(res)
} else if (res.statusCode == 202) {
uni.showToast({
title: res.data.msg,
icon: 'none',
duration: 1000
})
reject(res)
} else {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(res)
}
},
fail: (err: any) => {
uni.showToast({
title: "服务器错误",
icon: 'none',
duration: 1000
})
reject(err)
}
})
})
}
//接口管理
const RequestApi = {
//首页数据
Frontpage: () => request('/frontpage', 'GET', {}),
//新冠疫苗预约时段
NewappTime: () => request('/newapptime', 'GET', {}),
// 新冠疫苗提交预约
RescoVid: (data: Rescovidapi) => request('/rescovid', 'POST', data),
// 小程序登陆
WxLogin: (data: Wxloginapi) => request('/wx_login', 'POST', data),
}
export { RequestApi}
首页代码:
<template>
<view class="Login-page">
<image
mode="aspectFill"
src="https://diancan-1252107261.cos.accelerate.myqcloud.com/yiliao/denglu-yemian.jpg"
></image>
<button @click="Login">授权登陆</button>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, toRefs } from "vue";
import { RequestApi } from "@/public/request";
// 授权登陆
function Login() {
uni.getUserInfo({
// desc: "获取个人信息",
withCredentials: false,
lang: "zh_CN",
timeout: 10000,
success: (res) => {
let { avatarUrl, nickName } = res.userInfo;
// 获取code
uni.login({
success: (code) => {
console.log(code);
uni.showLoading({ title: "登陆中", mask: true });
ApiLogin(avatarUrl, nickName, code.code);
},
fail: (err) => {
uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
},
});
},
fail: (err) => {
uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
},
});
}
// 调用api接口登陆
async function ApiLogin(avatarUrl: string, nickName: string, code: string) {
try {
let obj = {
appid: "",//id
secret: "",//秘钥
avatarUrl,
nickName,
code,
};
let res: any = await RequestApi.WxLogin(obj);
// 存储用户信息到本地缓存,然后返回上一页
uni.setStorageSync("wxuser", res.data.data);
setTimeout(() => {
uni.navigateBack({ delta: 1 });
uni.hideLoading();
}, 600);
} catch (error) {
uni.showToast({ title: "登录失败", icon: "none", duration: 1000 });
}
}
</script>
六、首页
效果图:
因为我们每个人的手机型号不一样,尺寸不一样,所以要做到没个手机型号和尺寸的自适应一样。
逻辑:
在app.vue页面一开始加载的时候需要获取按钮高度,这里用到onLaunch()生命周期
在首页页面挂载钱获取按钮高度
在style里面用v-bind()绑定获取到的高度
剩下的就是调取接口,获取数据,渲染数据,样式布局,以及点击响应的区域跳转到响应的模块
七、新冠疫苗(亮点:选择时段)
效果图:
这个页面的输入框是小程序自带的,他的需要就是绑定所有数据,调取提交接口
比较难的业务逻辑就是上午时段和下午时段只能选择一个
然后用这些下标用三元表达式绑定动态class,这样能实现效果图的效果了
点击提交预约,发送后台需要的字段,调取接口
字段:
提交成功后,跳转到订单页面
渲染数据,点击取消预约调用接口,传递ID,就成功取消预约
八、HPV疫苗(亮点:Top切换)
效果图:
列表页:
1、点击获取下标,用三元表达式的方法绑定动态class
2、每一个分类他的商品都有相同的id,用id进行筛选显示相应的数据
3、将获取到的数据设置两个变量。一个用于筛选,一个用于展示全部数据
点击去预约需要把id,套餐名,价格,描述用字符串的形式传给页面
预约页:
跟新冠疫苗页面相似,也渲染数据,调取接口,跳转页面,提交。
HPV订单页面:
提交成功后跳转到HPV订单页面
效果图:
九、核酸检测(亮点:电话)
核酸检测预约效果图:
亮点逻辑:
写完之后,需要去微信公众平台绑定接口路径
下滑找到一下区域 这个接口不需要在后面拼接/api
逻辑写完后跳转到订单页面
核酸检测订单:
十、首页——图文咨询(重点:亮点是上传图片):
以下是图文咨询的页面效果,每个红色的盒子是一个模块,方便后期遍历数据
当前页面的重点逻辑:
1、上传图片:在这个项目中我们有很多地方会用到长传图片的这个功能,所以把这个方法封装了一下,方便后期复用。
操作如下:
在public问价夹创建misc.ts文件,把上传图片的方法放在这个文件夹里。
在request.ts写自己的接口地址
代码:
function uploadImage(url: string, su_title: string, err_title: string) {
return new Promise((reactive, reject) => {
uni.chooseMedia({
count: 1, //每次可选择几张图片
mediaType: ["image"], //文件类型,图片、视频
sizeType: ["compressed"], //压缩图片
success(res) {
console.log(res);
uni.showLoading({
title: su_title,
mask: true
})
uni.uploadFile({
url, //自己的接口地址
filePath: res.tempFiles[0].tempFilePath, //请求成功返回的临时地址
name: "file",
success(res: any) {
reactive(res)
uni.hideLoading()
},
fail(err: any) {
uni.showToast({
title: err_title,
icon: "error",
duration: 1000
})
reject(err)
}
});
},
});
});
}
export { uploadImage };
在图文咨询页面引入这个方法
控制台输出:
代码:
const unload = async () => {
const res: any = await uploadImage(IMAGEURL, "上传中", "上传失败");
const res_data = JSON.parse(res.data);
submitData.ins_report.push(res_data);
};
添加就诊人需要跳转到就诊人页面,如果需要添加就诊人 要调到添加就诊人页面
这个两个页面也是比较简单,主要考验css功底,和渲染数据,比较难的就是身份证识别,但这个功能是上传图片后台识别后返回身份证信息的数据,然后绑定数据就行了,方法跟上面一致。
这有一个bug,看一下效果图
想要实现我们需要的效果把那段注释的代码解开
代码:
const AIcardupload = async () => {
const res: any = (await uploadImage(AICARD, "识别中", "识别失败")) as {
statusCode: number;
};
const res_data = JSON.parse(res.data).data;
if (res.statusCode == 200) {
submitData.name = res_data.name;
submitData.sex = res_data.sex;
submitData.born = res_data.born;
submitData.id_card = res_data.id_card;
console.log(submitData.born);
submitData.born = submitData.born.slice(0, 4)+"-" +submitData.born.slice(4, 6)+"-"+submitData.born.slice(6, 8)
}
console.log(submitData, "submitData");
};
存储到pinia仓库
在选择就诊人页面选择就诊人保存到pinia,因为在上一个页面(图文咨询)需要用到name和ID
npm install pinia --save 下载
在src下面添加store文件夹,再添加一个index.ts文件(可以去参考pinia官网)
pinia仓库代码:
import { defineStore } from "pinia";
interface patient {
name:string;
id:string
}
export const myData = defineStore('my_data', {
state: () => {
return {
patient: {
name: "",
id: ""
} as patient
}
},
actions:{
addPatient(value :patient){
this.patient = value
}
}
})
就诊人页面代码:
//引入pinia仓库
import { myData } from "@/store/index";
let store = myData();
//点击事件触发向pinia发送数据
const checkedData = (name: string, id: string) => {
store.addPatient({ name, id });
};
图文咨询页面:
提交数据:
添加就诊人提交接口提交参数
图文咨询提交接口提交参数
十一、健康体检(亮点:点击全部点击相应的套餐显示响应的数据、销量排序、价格排序,uniapp模态框二次封装,方便后期复用)
体检预约和体检详情页面效果:
页面逻辑:
全部、销量、价格 这个盒子是定位在头部的,数据上划的时候需要显示在头部
它们都需要绑定一个点击事件,用来触发后续的筛选,和调用接口
html和逻辑代码:
1,样式结构
控制台输出
2、用于存放数据
3、请求接口方法
4、点击触发 全部、销量、价格的筛选方法
5、点击全部打开模态框,点击相应的筛选数据进行筛选
6、套餐数据请求渲染完之后,点击需要的套菜,携带id到体检详情页面
体检详情:
效果图:
这个页面的时间选择 使用uniapp的组件进行实现的,调一下css的样式就行了
二次封装模态框:
1、在子组件文件夹在新建一个vue文件,用来当做子组件
2、在体检详情页面注册这个子组件,并在子组件标签上绑定ref获取子组件实例
3、点击通过子组件标签上的ref获取到的实例,调取子组件的方法传递数据
父组件:
子组件:
接收到的数据也是用来提交预约触发了调取接口发送后台的
点击选择成员跳转到就诊人页面,这个页面之前就已经写好了,只需要跳转过去就行
点击提交成功后跳转到体检预约订单
效果图:
十二、健康自测
1、首页点击测评,判断点击的是哪一个测评模块
2、跳转到测评页面后,做完页面的样式进行的业务逻辑
2.1 在进入页面的时候接受上个页面传递过来的数据,进行下一步的请求接口的判断
2.2 在页面也开始加载的时候处理接口请求,看首页点击的是哪个自测进行判断,进行相应的接口请求
2.3 再点击选项的时候触发相应的业务逻辑
2.4 页面优化 跳转页面