Browse Source

feat: 新增游客页面并优化登录流程

refactor: 重构请求模块,使用token替代loginState

fix: 修复省市区选择器数据结构和接口调用

feat(article): 实现文章列表和详情页动态数据绑定

feat(invite): 添加邀约码生成和保存功能

refactor(my): 优化个人中心页面数据绑定逻辑

fix(login): 修正登录后跳转逻辑和身份验证

style: 统一页面样式和布局

chore: 更新依赖和配置文件

test: 添加部分页面测试用例

docs: 更新接口文档和类型定义
master
kola-web 1 week ago
parent
commit
c66f633f74
  1. 64
      src/api/request.ts
  2. 2
      src/app.json
  3. 20
      src/app.ts
  4. 51
      src/components/pickerArea/index.ts
  5. 99
      src/doctor/pages/article/index.ts
  6. 17
      src/doctor/pages/article/index.wxml
  7. 3
      src/doctor/pages/articleList/index.json
  8. 182
      src/doctor/pages/articleList/index.ts
  9. 32
      src/doctor/pages/articleList/index.wxml
  10. 51
      src/doctor/pages/changeNickname/index.ts
  11. 4
      src/doctor/pages/changeNickname/index.wxml
  12. 126
      src/doctor/pages/changeTel/index.ts
  13. 96
      src/doctor/pages/home/index.ts
  14. 131
      src/doctor/pages/home/index.wxml
  15. 100
      src/doctor/pages/invite/index.ts
  16. 23
      src/doctor/pages/invite/index.wxml
  17. 7
      src/doctor/pages/login/index.ts
  18. 3
      src/doctor/pages/loginForm/index.json
  19. 1
      src/doctor/pages/loginForm/index.scss
  20. 320
      src/doctor/pages/loginForm/index.ts
  21. 40
      src/doctor/pages/loginForm/index.wxml
  22. 147
      src/doctor/pages/my/index.ts
  23. 12
      src/doctor/pages/my/index.wxml
  24. 3
      src/doctor/pages/patientList/index.json
  25. 298
      src/doctor/pages/patientList/index.ts
  26. 176
      src/doctor/pages/patientList/index.wxml
  27. 2
      src/doctor/pages/stat/index.wxml
  28. 63
      src/ground/pages/changeNickname/index.ts
  29. 13
      src/ground/pages/changeNickname/index.wxml
  30. 103
      src/ground/pages/changeTel/index.ts
  31. 6
      src/ground/pages/changeTel/index.wxml
  32. 86
      src/ground/pages/home/index.ts
  33. 144
      src/ground/pages/home/index.wxml
  34. 101
      src/ground/pages/invite/index.ts
  35. 23
      src/ground/pages/invite/index.wxml
  36. 6
      src/ground/pages/login/index.ts
  37. 109
      src/ground/pages/my/index.ts
  38. 8
      src/ground/pages/my/index.wxml
  39. 5
      src/ground/pages/pharmacist/index.json
  40. 163
      src/ground/pages/pharmacist/index.ts
  41. 30
      src/ground/pages/pharmacist/index.wxml
  42. 144
      src/ground/pages/stat/index.ts
  43. 147
      src/ground/pages/stat/index.wxml
  44. 113
      src/pages/index/index.ts
  45. 14
      src/pages/index/index.wxml
  46. 29
      src/pages/start/index.scss
  47. 68
      src/pages/start/index.ts
  48. 12
      src/pages/start/index.wxml
  49. 7
      src/pages/tourists/index.json
  50. 29
      src/pages/tourists/index.scss
  51. 24
      src/pages/tourists/index.ts
  52. 11
      src/pages/tourists/index.wxml
  53. 15
      typings/index.d.ts

64
src/api/request.ts

@ -2,6 +2,12 @@ interface IGlobalParams {
gUrl: string gUrl: string
} }
// 获取 token
const getToken = (): string => {
const app = getApp<IAppOption>()
return app.globalData.initLoginInfo?.token || ''
}
export const request = function ( export const request = function (
{ gUrl }: IGlobalParams, { gUrl }: IGlobalParams,
{ {
@ -13,6 +19,7 @@ export const request = function (
loading = false, loading = false,
loadingText = '加载中...', loadingText = '加载中...',
isJSON = false, isJSON = false,
needToken = true, // 默认需要 token
...options ...options
}: IAgaxParams, }: IAgaxParams,
): Promise<any> { ): Promise<any> {
@ -23,46 +30,77 @@ export const request = function (
mask: true, mask: true,
}) })
} }
wx.request({
header: { // 构建请求头
loginState: getApp().globalData.loginState, const requestHeader: any = {
'content-type': isJSON ? 'application/json' : 'application/x-www-form-urlencoded', 'content-type': isJSON ? 'application/json' : 'application/x-www-form-urlencoded',
...header, ...header,
}, }
// 添加 token(如果需要且存在)
if (needToken) {
const token = getToken()
if (token) {
requestHeader.Authorization = `${token}`
}
}
wx.request({
header: requestHeader,
url: gUrl + url, url: gUrl + url,
method, method,
data: { data: {
loginState: getApp().globalData.loginState,
...(data as object), ...(data as object),
}, },
...options, ...options,
success(res: any) { success(res: any) {
const { code, data } = res.data const { code, msg, data } = res.data
// 处理 401 未登录
// if (code === 401) {
// const app = getApp<IAppOption>()
// // 清除登录信息
// app.globalData.initLoginInfo = {}
// app.globalData.loginState = ''
// // 跳转到启动页重新登录
// wx.reLaunch({
// url: '/pages/start/index',
// })
// reject(res.data)
// return
// }
if (isJSON) { if (isJSON) {
resolve(data) resolve(data)
} else if (code === 0) { } else if (code === 0) {
resolve(data) resolve(data)
} else if (showMsg) { } else if (showMsg) {
const msg = errPicker(res.data) const message = msg || errPicker(res.data)
if (loading) { if (loading) {
setTimeout(() => { setTimeout(() => {
wx.showToast({ wx.showToast({
title: msg, title: message,
icon: 'none', icon: 'none',
}) })
}, 30) }, 30)
} else { } else {
wx.showToast({ wx.showToast({
title: msg, title: message,
icon: 'none', icon: 'none',
}) })
reject(res)
} }
reject(res.data)
} else { } else {
reject(res) reject(res.data)
} }
}, },
fail(err) { fail(err) {
if (showMsg) {
wx.showToast({
title: '网络请求失败',
icon: 'none',
})
}
reject(err) reject(err)
}, },
complete() { complete() {
@ -74,9 +112,9 @@ export const request = function (
}) })
} }
function errPicker(err) { function errPicker(err: any): string {
if (typeof err === 'string') { if (typeof err === 'string') {
return err return err
} }
return err.data || err.msg || err.errMsg || (err.detail && err.detail.errMsg) || '未知错误' return err.msg || err.message || err.errMsg || (err.detail && err.detail.errMsg) || '未知错误'
} }

2
src/app.json

@ -1,6 +1,6 @@
{ {
"$schema": "https://dldir1.qq.com/WechatWebDev/editor-extension/wx-json/app.schema.json", "$schema": "https://dldir1.qq.com/WechatWebDev/editor-extension/wx-json/app.schema.json",
"pages": ["pages/start/index", "pages/work/index", "pages/index/index"], "pages": ["pages/start/index", "pages/tourists/index", "pages/work/index", "pages/index/index"],
"subPackages": [ "subPackages": [
{ {
"root": "ground", "root": "ground",

20
src/app.ts

@ -39,7 +39,6 @@ App<IAppOption>({
waitBindDoctorId: '', waitBindDoctorId: '',
scene: {}, scene: {},
loginState: '',
initLoginInfo: {}, initLoginInfo: {},
anyWhere: false, anyWhere: false,
@ -73,8 +72,8 @@ App<IAppOption>({
data: { data: {
code: res.code, code: res.code,
}, },
needToken: false, // 登录接口不需要 token
}).then((res) => { }).then((res) => {
this.globalData.loginState = res.token || res.sessionKey
this.globalData.initLoginInfo = res this.globalData.initLoginInfo = res
if (callback) { if (callback) {
callback() callback()
@ -98,7 +97,7 @@ App<IAppOption>({
waitLogin({ types = [] as number[] } = {}) { waitLogin({ types = [] as number[] } = {}) {
return new Promise((resolve) => { return new Promise((resolve) => {
const checkLogin = () => { const checkLogin = () => {
if (this.globalData.loginState) { if (Object.keys(this.globalData.initLoginInfo).length > 0) {
if (this.checkLoginTypes(types)) { if (this.checkLoginTypes(types)) {
resolve() resolve()
} }
@ -112,18 +111,25 @@ App<IAppOption>({
}) })
}, },
checkLoginTypes(types: number[]) { checkLoginTypes(types: number[]) {
const { loginIdentity, isRegistered } = this.globalData.initLoginInfo console.log('types: ', types)
const { loginIdentity, isLogin } = this.globalData.initLoginInfo
if (!types || types.length === 0) { if (!types || types.length === 0) {
return true return true
} }
if (!isRegistered) { if (types.includes(loginIdentity) && loginIdentity === 1) {
if (types.includes(1)) {
return true return true
} }
if (!isLogin) {
const typeRegPageUrl = {
2: '/pages/register/index',
3: '/ground/pages/register/index',
4: '/doctor/pages/register/index',
}[loginIdentity as 2 | 3 | 4]
wx.reLaunch({ wx.reLaunch({
url: '/pages/index/index', url: typeRegPageUrl || '/pages/statr/index',
}) })
return false return false
} }

51
src/components/pickerArea/index.ts

@ -230,15 +230,53 @@ Component({
getArea() { getArea() {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
url: '/js/area.json', url: '/app/common/common/area-list',
isJSON: true, }).then((res: any[]) => {
}).then((res) => { // 将扁平数据转换为树形结构
const areaTree = this.buildAreaTree(res)
this.setData({ this.setData({
area: res, area: areaTree,
}) })
this.getRangeList() this.getRangeList()
}) })
}, },
// 构建省市区树形结构
buildAreaTree(areaList: any[]) {
if (!areaList || !areaList.length) return []
// 构建以 parentAreaId 为 key 的映射
const parentMap: { [key: string]: any[] } = {}
areaList.forEach((item) => {
const parentId = item.parentAreaId || '0'
if (!parentMap[parentId]) {
parentMap[parentId] = []
}
parentMap[parentId].push({
name: item.areaName,
code: item.areaId,
value: item.areaId,
label: item.areaName,
level: item.level,
parentId: item.parentAreaId,
})
})
// 递归构建树
const buildTree = (parentId: string): any[] => {
const children = parentMap[parentId]
if (!children) return []
return children.map((item) => {
const subChildren = buildTree(item.code)
if (subChildren.length > 0) {
return { ...item, children: subChildren }
}
return item
})
}
return buildTree('0')
},
handleItem(e: any) { handleItem(e: any) {
const { code, name } = e.currentTarget.dataset const { code, name } = e.currentTarget.dataset
this.setData({ this.setData({
@ -281,9 +319,10 @@ Component({
getRangeList() { getRangeList() {
const { area, ProvinceId } = this.data const { area, ProvinceId } = this.data
if (!ProvinceId) return if (!ProvinceId) return
const range = area.filter((item: any) => item.value == ProvinceId)[0].children const province = area.find((item: any) => item.code === ProvinceId)
if (!province || !province.children) return
this.setData({ this.setData({
range, range: province.children,
active: 1, active: 1,
scrollIntoView0: '', scrollIntoView0: '',
scrollIntoView1: `id${this.data.CityId}`, scrollIntoView1: `id${this.data.CityId}`,

99
src/doctor/pages/article/index.ts

@ -1,13 +1,106 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
onLoad() { // 文章ID
articleId: 0,
// 文章详情(模拟数据,接口上线后删除)
articleDetail: {
id: 1,
title: '银屑病日常护理指南',
content:
'<p>银屑病是一种慢性、复发性、炎症性皮肤病,需要长期的管理和护理。</p><h3>一、日常护理要点</h3><p>1. 保持皮肤湿润,每天使用保湿霜</p><p>2. 避免过度清洁,使用温和的洗浴产品</p><p>3. 穿着宽松、透气的棉质衣物</p><h3>二、饮食建议</h3><p>1. 多吃新鲜蔬菜水果</p><p>2. 避免辛辣刺激食物</p><p>3. 适量补充优质蛋白</p><h3>三、心理调节</h3><p>保持良好的心态,积极配合治疗,定期复诊。</p>',
imageUrl: '',
viewCount: 100,
likeCount: 10,
isLiked: false,
updateTime: 1700000000,
},
// 是否已点赞
isLiked: false,
},
onLoad(option: { id?: string }) {
// 药店端文章详情页面,仅允许药店人员访问 // 药店端文章详情页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 const articleId = option.id ? Number.parseInt(option.id) : 0
this.setData({ articleId })
// TODO: 接口上线后取消注释
// if (articleId) {
// this.getArticleDetail(articleId)
// }
})
},
// 获取文章详情
getArticleDetail(articleId: number) {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/edu-article-detail',
data: {
articleId,
},
})
.then((res: any) => {
this.setData({
articleDetail: {
...res,
updateTimeFormatted: res.updateTime ? this.formatDate(res.updateTime) : '',
},
isLiked: res.isLiked || false,
})
})
.catch(() => {
wx.showToast({
title: '获取文章详情失败',
icon: 'none',
})
}) })
}, },
// 点赞/取消点赞
handleLike() {
const { articleId, isLiked, articleDetail } = this.data
if (!articleId) return
wx.ajax({
method: 'POST',
url: '/app/pharmacist/pharmacist/edu-article-like',
data: {
articleId,
},
})
.then(() => {
const newLikeCount = isLiked ? (articleDetail.likeCount || 0) - 1 : (articleDetail.likeCount || 0) + 1
this.setData({
isLiked: !isLiked,
articleDetail: {
...articleDetail,
likeCount: newLikeCount,
},
})
wx.showToast({
title: isLiked ? '取消点赞' : '点赞成功',
icon: 'none',
})
})
.catch(() => {
wx.showToast({
title: '操作失败',
icon: 'none',
})
})
},
// 格式化日期
formatDate(timestamp: number): string {
const date = new Date(timestamp * 1000)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()
}, },

17
src/doctor/pages/article/index.wxml

@ -6,18 +6,19 @@
</navbar> </navbar>
<view class="page" style="padding-top: {{pageTop+20}}px;"> <view class="page" style="padding-top: {{pageTop+20}}px;">
<image class="banner" src="{{imageUrl}}cache/bg3.png?t={{Timestamp}}"></image> <image class="banner" src="{{articleDetail.imageUrl || imageUrl + 'cache/bg3.png'}}?t={{Timestamp}}" mode="aspectFill"></image>
<view class="title">从强降糖到防事件,那些糖尿病指南走过的路</view> <view class="title">{{articleDetail.title}}</view>
<mp-html content="{{doc}}"></mp-html> <view class="content">
<mp-html content="{{articleDetail.content}}"></mp-html>
</view>
<view class="page-footer"> <view class="page-footer">
<view class="o-item"> <view class="o-item">
<image class="icon" src="{{imageUrl}}icon22.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon22.png?t={{Timestamp}}"></image>
<text class="num">123</text> <text class="num">{{articleDetail.viewCount || 0}}</text>
</view> </view>
<view class="o-item"> <view class="o-item" bind:tap="handleLike">
<image class="icon" src="{{imageUrl}}icon23.png?t={{Timestamp}}"></image> <image class="icon" src="{{isLiked ? imageUrl + 'icon25.png' : imageUrl + 'icon23.png'}}?t={{Timestamp}}"></image>
<!-- <image class="icon" src="{{imageUrl}}icon25.png?t={{Timestamp}}"></image> --> <text class="num">{{articleDetail.likeCount || 0}}</text>
<text class="num">123</text>
</view> </view>
</view> </view>
</view> </view>

3
src/doctor/pages/articleList/index.json

@ -3,6 +3,7 @@
"usingComponents": { "usingComponents": {
"popup": "/components/popup/index", "popup": "/components/popup/index",
"navbar": "/components/navbar/index", "navbar": "/components/navbar/index",
"doctor-tab-bar": "/doctor/components/doctor-tab-bar/index" "doctor-tab-bar": "/doctor/components/doctor-tab-bar/index",
"pagination": "/components/pagination/index"
} }
} }

182
src/doctor/pages/articleList/index.ts

@ -2,37 +2,169 @@ const app = getApp<IAppOption>()
Page({ Page({
data: { data: {
names: [ // 分类列表(模拟数据,接口上线后删除)
'张吉惟', categoryList: [
'林国瑞', { id: 1, name: '学习专栏', images: [] },
'林玟书', { id: 2, name: '社区热帖', images: [] },
'林雅南', { id: 3, name: '行业热点', images: [] },
'江奕云', { id: 4, name: '中国药店', images: [] },
'刘柏宏', ] as any[],
'阮建安', currentCategoryId: 1,
'林子帆',
'夏志豪', // Banner列表
'吉茹定', bannerList: [] as any[],
'李中冰',
'黄文隆', // 文章列表(模拟数据,接口上线后删除)
'谢彦文', articleList: [
'傅智翔', {
'洪振霞', id: 1,
'刘姿婷', title: '银屑病日常护理指南',
'荣姿康', imageUrl: '',
'吕致盈', viewCount: 100,
'方一强', likeCount: 10,
], updateTime: 1700000000,
updateTimeFormatted: '2023-11-14',
},
{
id: 2,
title: '从强降糖到防事件,那些糖尿病指南走过的路',
imageUrl: '',
viewCount: 256,
likeCount: 32,
updateTime: 1700086400,
updateTimeFormatted: '2023-11-15',
},
{
id: 3,
title: '特诺雅患者援助项目介绍',
imageUrl: '',
viewCount: 89,
likeCount: 15,
updateTime: 1700172800,
updateTimeFormatted: '2023-11-16',
},
] as any[],
// 分页
page: 1,
pageSize: 20,
loading: false,
hasMore: true,
pagination: {
count: 3,
page: 1,
pages: 1,
},
}, },
onLoad() { onLoad() {
// 药店端文章列表页面,仅允许药店人员访问 // 药店端教育页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 // TODO: 接口上线后取消注释
// this.getCategoryList()
}) })
}, },
handleInfo() { // 获取教育分类列表
getCategoryList() {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/edu-category-list',
}).then((res: any) => {
const list = res || []
this.setData({
categoryList: list,
currentCategoryId: list.length > 0 ? list[0].id : 0,
})
// 获取第一个分类的文章列表和Banner
if (list.length > 0) {
this.getBannerList(list[0].id)
this.getArticleList()
}
})
},
// 获取分类Banner列表
getBannerList(categoryId: number) {
const category = this.data.categoryList.find((item: any) => item.id === categoryId)
if (category && category.images && category.images.length > 0) {
this.setData({
bannerList: category.images.map((url: string, index: number) => ({
id: index,
imageUrl: url,
})),
})
} else {
this.setData({ bannerList: [] })
}
},
// 获取文章列表
getArticleList() {
if (this.data.loading || !this.data.hasMore) return
if (!this.data.currentCategoryId) return
this.setData({ loading: true })
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/edu-article-list',
data: {
categoryId: this.data.currentCategoryId,
page: this.data.page,
pageSize: this.data.pageSize,
},
}).then((res: any) => {
const list = (res.list || []).map((item: any) => ({
...item,
updateTimeFormatted: item.updateTime ? this.formatDate(item.updateTime) : '',
}))
const total = res.total || 0
const currentPage = this.data.page
this.setData({
articleList: [...this.data.articleList, ...list],
page: currentPage + 1,
hasMore: list.length >= this.data.pageSize,
loading: false,
pagination: {
count: total,
page: currentPage,
pages: Math.ceil(total / this.data.pageSize) || 1,
},
})
}).catch(() => {
this.setData({ loading: false })
})
},
// 格式化日期
formatDate(timestamp: number): string {
const date = new Date(timestamp * 1000)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
// 切换分类
handleTabChange(e: WechatMiniprogram.CustomEvent) {
const categoryId = e.currentTarget.dataset.id
this.setData({
currentCategoryId: categoryId,
articleList: [],
page: 1,
hasMore: true,
})
this.getBannerList(categoryId)
this.getArticleList()
},
// 页面上拉触底事件
onReachBottom() {
if (this.data.loading || !this.data.hasMore) {
return
}
this.getArticleList()
},
// 查看文章详情
handleDetail(e: WechatMiniprogram.CustomEvent) {
const { id } = e.currentTarget.dataset
wx.navigateTo({ wx.navigateTo({
url: '/doctor/pages/article/index', url: `/doctor/pages/article/index?id=${id}`,
}) })
}, },
}) })

32
src/doctor/pages/articleList/index.wxml

@ -5,35 +5,43 @@
<view class="page" style="padding-top: {{pageTop}}px;"> <view class="page" style="padding-top: {{pageTop}}px;">
<view class="page-header" style="top:{{pageTop}}px"> <view class="page-header" style="top:{{pageTop}}px">
<view class="tabs"> <view class="tabs">
<view class="tab active">学习专栏</view> <view
<view class="tab">社区热帖</view> class="tab {{currentCategoryId === item.id ? 'active' : ''}}"
<view class="tab">行业热点</view> wx:for="{{categoryList}}"
<view class="tab">中国药店</view> wx:key="id"
data-id="{{item.id}}"
bind:tap="handleTabChange"
>
{{item.name}}
</view> </view>
</view> </view>
<swiper class="banner"> </view>
<swiper-item class="swiper-item"> <swiper class="banner" wx:if="{{bannerList.length > 0}}" indicator-dots autoplay circular>
<image class="s-img" src="{{imageUrl}}cache/bg2.png?t={{Timestamp}}"></image> <swiper-item class="swiper-item" wx:for="{{bannerList}}" wx:key="id">
<image class="s-img" src="{{item.imageUrl}}" mode="aspectFill"></image>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view class="list"> <view class="list">
<view class="card" wx:for="{{10}}" wx:key="index" bind:tap="handleDetail"> <view class="card" wx:for="{{articleList}}" wx:key="id" data-id="{{item.id}}" bind:tap="handleDetail">
<image class="photo" src="{{imageUrl}}cache/p{{index%5+1}}.png?t={{Timestamp}}"></image> <image class="photo" src="{{item.imageUrl || imageUrl + 'cache/p1.png'}}?t={{Timestamp}}" mode="aspectFill"></image>
<view class="wrap"> <view class="wrap">
<view class="title">从强降糖到防事件,那些糖尿病指南走过的路</view> <view class="title">{{item.title}}</view>
<view class="options"> <view class="options">
<view class="o-item"> <view class="o-item">
<image class="icon" src="{{imageUrl}}icon22.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon22.png?t={{Timestamp}}"></image>
<text class="num">123</text> <text class="num">{{item.viewCount || 0}}</text>
</view> </view>
<view class="o-item"> <view class="o-item">
<image class="icon" src="{{imageUrl}}icon23.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon23.png?t={{Timestamp}}"></image>
<text class="num">123</text> <text class="num">{{item.likeCount || 0}}</text>
</view> </view>
</view> </view>
<view class="time" wx:if="{{item.updateTimeFormatted}}">{{item.updateTimeFormatted}}</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 分页组件 -->
<pagination pagination="{{pagination}}" wx:if="{{!loading || articleList.length > 0}}"></pagination>
</view> </view>
<doctor-tab-bar active="{{ 2 }}"></doctor-tab-bar> <doctor-tab-bar active="{{ 2 }}"></doctor-tab-bar>

51
src/doctor/pages/changeNickname/index.ts

@ -1,13 +1,62 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
// 新姓名
name: '',
},
onLoad() { onLoad() {
// 药店端修改昵称页面,仅允许药店人员访问 // 药店端修改昵称页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 // 页面加载完成
}) })
}, },
// 输入姓名
handleInput(e: WechatMiniprogram.CustomEvent) {
this.setData({
name: e.detail.value,
})
},
// 确认修改
handleSubmit() {
const { name } = this.data
if (!name.trim()) {
wx.showToast({
title: '请输入姓名',
icon: 'none',
})
return
}
wx.showLoading({ title: '提交中...' })
wx.ajax({
method: 'POST',
url: '/app/pharmacist/pharmacist/update-name',
data: {
name: name.trim(),
},
}).then(() => {
wx.hideLoading()
// 更新全局数据
if (app.globalData.initLoginInfo?.pharmacistInfo) {
app.globalData.initLoginInfo.pharmacistInfo.name = name.trim()
}
wx.showToast({
title: '修改成功',
icon: 'success',
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
}).catch(() => {
wx.hideLoading()
wx.showToast({
title: '修改失败',
icon: 'none',
})
})
},
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()
}, },

4
src/doctor/pages/changeNickname/index.wxml

@ -6,6 +6,6 @@
</navbar> </navbar>
<view class="page" style="padding-top: {{pageTop+28}}px;"> <view class="page" style="padding-top: {{pageTop+28}}px;">
<input class="input" placeholder-class="place-input" type="text" placeholder="请输入新昵称" /> <input class="input" placeholder-class="place-input" type="text" placeholder="请输入新昵称" value="{{name}}" bindinput="handleInput" />
<view class="btn">确认</view> <view class="btn" bind:tap="handleSubmit">确认</view>
</view> </view>

126
src/doctor/pages/changeTel/index.ts

@ -5,18 +5,140 @@ Page({
mobile: '', mobile: '',
code: '', code: '',
codeText: '发送验证码', codeText: '发送验证码',
counting: false,
countdown: 60,
}, },
timer: null as any,
onLoad() { onLoad() {
// 药店端修改手机号页面,仅允许药店人员访问 // 药店端修改手机号页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 // 页面加载完成
}) })
}, },
onUnload() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer)
}
},
// 获取验证码
getCode() { getCode() {
// TODO: 需要更新为新的接口 if (this.data.counting) return
const { mobile } = this.data
if (!mobile) {
wx.showToast({
title: '请输入手机号',
icon: 'none',
})
return
}
if (!/^1[3-9]\d{9}$/.test(mobile)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none',
})
return
}
wx.ajax({
method: 'POST',
url: '/app/common/common/send-sms',
data: {
phone: mobile,
type: 3, // 3-修改手机号
},
}).then(() => {
wx.showToast({
title: '验证码已发送',
icon: 'success',
})
this.startCountdown()
}).catch(() => {
wx.showToast({
title: '发送失败',
icon: 'none',
})
})
}, },
// 开始倒计时
startCountdown() {
this.setData({
counting: true,
countdown: 60,
codeText: '60s',
})
this.timer = setInterval(() => {
const countdown = this.data.countdown - 1
if (countdown <= 0) {
clearInterval(this.timer)
this.setData({
counting: false,
codeText: '发送验证码',
})
} else {
this.setData({
countdown,
codeText: `${countdown}s`,
})
}
}, 1000)
},
// 确认修改
handleSubmit() { handleSubmit() {
// TODO: 需要更新为新的接口 const { mobile, code } = this.data
if (!mobile) {
wx.showToast({
title: '请输入手机号',
icon: 'none',
})
return
}
if (!/^1[3-9]\d{9}$/.test(mobile)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none',
})
return
}
if (!code) {
wx.showToast({
title: '请输入验证码',
icon: 'none',
})
return
}
wx.showLoading({ title: '提交中...' })
wx.ajax({
method: 'POST',
url: '/app/pharmacist/pharmacist/update-phone',
data: {
phone: mobile,
code: code,
},
}).then(() => {
wx.hideLoading()
// 更新全局数据
if (app.globalData.initLoginInfo?.pharmacistInfo) {
app.globalData.initLoginInfo.pharmacistInfo.phone = mobile
}
wx.showToast({
title: '修改成功',
icon: 'success',
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
}).catch(() => {
wx.hideLoading()
wx.showToast({
title: '修改失败',
icon: 'none',
})
})
}, },
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()

96
src/doctor/pages/home/index.ts

@ -18,6 +18,15 @@ Page({
jumpPatientCount: 0, jumpPatientCount: 0,
enrollPatientCount: 0, enrollPatientCount: 0,
// 适应症统计
indicationStats: [] as Array<{
indicationId: number
indicationName: string
invitePatientCount: number
jumpPatientCount: number
enrollPatientCount: number
}>,
// 图表数据 // 图表数据
chartData: [] as Array<{ date: string, count: number }>, chartData: [] as Array<{ date: string, count: number }>,
@ -62,13 +71,20 @@ Page({
getUserInfo() { getUserInfo() {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
url: '/app/pharmacist/pharmacist/info', url: '/app/pharmacist/pharmacist/profile',
}).then((res: any) => { }).then((res: any) => {
this.setData({ this.setData({
pharmacistName: res.name, pharmacistName: res.name,
pharmacistAvatar: res.avatar, pharmacistAvatar: res.avatar,
pharmacyName: res.pharmacyName, pharmacyName: res.pharmacyName,
}) })
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
pharmacistName: '李药师',
pharmacistAvatar: '',
pharmacyName: '康泰大药房(人民路店)',
})
}) })
}, },
@ -81,6 +97,11 @@ Page({
this.setData({ this.setData({
pendingCount: res.count || 0, pendingCount: res.count || 0,
}) })
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
pendingCount: 12,
})
}) })
}, },
@ -91,9 +112,22 @@ Page({
url: '/app/pharmacist/pharmacist/statistics', url: '/app/pharmacist/pharmacist/statistics',
}).then((res: any) => { }).then((res: any) => {
this.setData({ this.setData({
invitePatientCount: res.invitePatientCount, invitePatientCount: res.invitePatientCount || 0,
jumpPatientCount: res.jumpPatientCount, jumpPatientCount: res.jumpPatientCount || 0,
enrollPatientCount: res.enrollPatientCount, enrollPatientCount: res.enrollPatientCount || 0,
indicationStats: res.indicationStats || [],
})
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
invitePatientCount: 156,
jumpPatientCount: 128,
enrollPatientCount: 89,
indicationStats: [
{ indicationId: 1, indicationName: '斑块状银屑病', invitePatientCount: 80, jumpPatientCount: 65, enrollPatientCount: 45 },
{ indicationId: 2, indicationName: '溃疡性结肠炎', invitePatientCount: 45, jumpPatientCount: 38, enrollPatientCount: 28 },
{ indicationId: 3, indicationName: '克罗恩病', invitePatientCount: 31, jumpPatientCount: 25, enrollPatientCount: 16 },
],
}) })
}) })
}, },
@ -109,12 +143,42 @@ Page({
endDate: this.data.endDate, endDate: this.data.endDate,
}, },
}).then((res: any) => { }).then((res: any) => {
const list = res.list || []
this.setData({
chartData: list,
})
this.initChartBar(list)
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
this.setData({ this.setData({
chartData: res.list || [], chartData: mockData,
}) })
this.initChartBar(res.list || []) this.initChartBar(mockData)
}) })
}, },
// 生成模拟图表数据
generateMockChartData() {
const list: any[] = []
const days = this.data.statType === 'day' ? 30 : 12
for (let i = 0; i < days; i++) {
const date = new Date()
if (this.data.statType === 'day') {
date.setDate(date.getDate() - (days - 1 - i))
list.push({
date: this.formatDate(date),
count: Math.floor(Math.random() * 30) + 5,
})
} else {
date.setMonth(date.getMonth() - (days - 1 - i))
list.push({
date: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`,
count: Math.floor(Math.random() * 300) + 50,
})
}
}
return list
},
// 切换统计类型 // 切换统计类型
switchStatType(e: WechatMiniprogram.CustomEvent) { switchStatType(e: WechatMiniprogram.CustomEvent) {
@ -128,8 +192,26 @@ Page({
// 日期选择变化 // 日期选择变化
onDateChange(e: WechatMiniprogram.CustomEvent) { onDateChange(e: WechatMiniprogram.CustomEvent) {
const { field } = e.currentTarget.dataset const { field } = e.currentTarget.dataset
const value = e.detail.value
// 验证日期范围
if (field === 'startDate' && this.data.endDate && value > this.data.endDate) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
if (field === 'endDate' && this.data.startDate && value < this.data.startDate) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({ this.setData({
[field]: e.detail.value, [field]: value,
}) })
// 重新加载图表数据 // 重新加载图表数据
this.getPatientChart() this.getPatientChart()

131
src/doctor/pages/home/index.wxml

@ -11,14 +11,13 @@
style="background:url('{{imageUrl}}bg7.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px" style="background:url('{{imageUrl}}bg7.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px"
> >
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}"></image> <image class="avatar" src="{{pharmacistAvatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}"></image>
<view class="wrap"> <view class="wrap">
<view class="name"> <view class="name">
刘平安 {{pharmacistName || '药师'}}
<view class="label">药师</view> <view class="label">药师</view>
</view> </view>
<view class="site">康泰大药房(人民路店)</view> <view class="site">{{pharmacyName || '药店名称'}}</view>
<view class="date">入组时间:2026/02/03</view>
</view> </view>
<view class="code" bind:tap="handleInvite"> <view class="code" bind:tap="handleInvite">
<image class="icon" src="{{imageUrl}}icon24.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon24.png?t={{Timestamp}}"></image>
@ -33,19 +32,19 @@
<view class="col"> <view class="col">
<view class="col-center"> <view class="col-center">
<view class="name">待处理\n患者数</view> <view class="name">待处理\n患者数</view>
<view class="num">25</view> <view class="num">{{pendingCount}}</view>
</view> </view>
</view> </view>
<view class="col"> <view class="col">
<view class="col-center"> <view class="col-center">
<view class="name">跳转证明\n待上传</view> <view class="name">跳转证明\n待上传</view>
<view class="num">25</view> <view class="num">0</view>
</view> </view>
</view> </view>
<view class="col"> <view class="col">
<view class="col-center"> <view class="col-center">
<view class="name">入组证明\n待上传</view> <view class="name">入组证明\n待上传</view>
<view class="num">25</view> <view class="num">0</view>
</view> </view>
</view> </view>
</view> </view>
@ -65,88 +64,58 @@
<view class="row2"> <view class="row2">
<view class="col"> <view class="col">
<view class="name">邀约患者数</view> <view class="name">邀约患者数</view>
<view class="num">750</view> <view class="num">{{invitePatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">跳转患者数</view> <view class="name">跳转患者数</view>
<view class="num">750</view> <view class="num">{{jumpPatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">入组患者数</view>
<view class="num">750</view> <view class="num">{{enrollPatientCount}}</view>
</view> </view>
</view> </view>
<view class="card-container {{fold1&&'fold'}}"> <view class="card-container {{fold1&&'fold'}}">
<view class="row3"> <view wx:for="{{indicationStats}}" wx:key="indicationId" class="row3">
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.invitePatientCount}}</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">300</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.jumpPatientCount}}</view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.enrollPatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
</view> </view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="chat-data"> <view class="chat-data">
<view class="c-header"> <view class="c-header">
<view class="switch-btns"> <view class="switch-btns">
<view class="btn">月统计</view> <view class="btn {{statType === 'month' ? 'active' : ''}}" bind:tap="switchStatType" data-type="month">月统计</view>
<view class="btn active">日统计</view> <view class="btn {{statType === 'day' ? 'active' : ''}}" bind:tap="switchStatType" data-type="day">日统计</view>
</view> </view>
</view> </view>
<view class="c-options"> <view class="c-options">
<view class="name" bind:tap="handleFold" data-key="fold2"> <view class="name" bind:tap="handleFold" data-key="fold2">
邀约患者统计 邀约患者统计
<view class="fold {{fold2&&'active'}}"> <view class="fold {{fold2&&'active'}}">
{{fold1?'展开':'收起'}} {{fold2?'展开':'收起'}}
<van-icon class="icon" name="arrow-down" /> <van-icon class="icon" name="arrow-down" />
</view> </view>
</view> </view>
<picker class="picker" mode="date"> <picker class="picker" mode="date" value="{{startDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<van-icon class="icon" name="arrow-left" /> <van-icon class="icon" name="arrow-left" />
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<van-icon class="icon" name="arrow" /> <van-icon class="icon" name="arrow" />
</view> </view>
</picker> </picker>
@ -155,79 +124,49 @@
<view class="row2"> <view class="row2">
<view class="col"> <view class="col">
<view class="name">邀约患者数</view> <view class="name">邀约患者数</view>
<view class="num">750</view> <view class="num">{{invitePatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">跳转患者数</view> <view class="name">跳转患者数</view>
<view class="num">750</view> <view class="num">{{jumpPatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">入组患者数</view>
<view class="num">750</view> <view class="num">{{enrollPatientCount}}</view>
</view> </view>
</view> </view>
<view class="card-container {{fold2&&'fold'}}"> <view class="card-container {{fold2&&'fold'}}">
<view class="row3"> <view wx:for="{{indicationStats}}" wx:key="indicationId" class="row3">
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.invitePatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">斑块状银屑病</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.jumpPatientCount}}</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view> </view>
<view class="row3">
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.enrollPatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="chart-range"> <view class="chart-range">
<picker class="picker" mode="date" end="{{end}}"> <picker class="picker" mode="date" value="{{startDate}}" end="{{endDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon13.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon13.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date" start="{{satrt}}"> <picker class="picker" mode="date" value="{{endDate}}" start="{{startDate}}" bindchange="onDateChange" data-field="endDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon13.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon13.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>

100
src/doctor/pages/invite/index.ts

@ -1,11 +1,107 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
// 药店人员信息
pharmacistName: '',
pharmacistAvatar: '',
pharmacistId: '',
// 小程序码
qrCodeUrl: '',
// 项目信息
projectId: '',
projectName: '',
},
onLoad() { onLoad() {
// 药店端邀约页面,仅允许药店人员访问 // 药店端邀约页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 this.getUserInfo()
})
},
// 获取药店人员信息
getUserInfo() {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/profile',
}).then((res: any) => {
this.setData({
pharmacistName: res.name || '',
pharmacistAvatar: res.avatar || '',
pharmacistId: res.id || '',
})
// 获取小程序码
this.generateQrCode()
})
},
// 生成药店邀约小程序码
generateQrCode() {
const { pharmacistId } = this.data
if (!pharmacistId) {
wx.showToast({
title: '获取药店人员信息失败',
icon: 'none',
})
return
}
wx.ajax({
method: 'POST',
url: '/app/common/common/wxacode',
data: {
scene: `pharmacistId=${pharmacistId}`,
page: 'pages/invite/patient',
width: 430,
type: 2, // 2-药店人员邀约码
},
}).then((res: any) => {
this.setData({
qrCodeUrl: res.imageUrl || '',
})
}).catch(() => {
wx.showToast({
title: '生成二维码失败',
icon: 'none',
})
})
},
// 保存二维码到相册
saveQrCode() {
const { qrCodeUrl } = this.data
if (!qrCodeUrl) {
wx.showToast({
title: '二维码未生成',
icon: 'none',
})
return
}
wx.downloadFile({
url: qrCodeUrl,
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.showToast({
title: '保存成功',
icon: 'success',
})
},
fail: () => {
wx.showToast({
title: '保存失败',
icon: 'none',
})
},
})
},
fail: () => {
wx.showToast({
title: '下载图片失败',
icon: 'none',
})
},
}) })
}, },
handleBack() { handleBack() {

23
src/doctor/pages/invite/index.wxml

@ -9,26 +9,37 @@
class="page" class="page"
style="background: url('{{imageUrl}}bg8.png?t={{Timestamp}}') no-repeat top center/100% 100vh;padding-top: {{pageTop+87}}px;" style="background: url('{{imageUrl}}bg8.png?t={{Timestamp}}') no-repeat top center/100% 100vh;padding-top: {{pageTop+87}}px;"
> >
<view class="page-container" style="background: url('{{imageUrl}}bg9.png?t={{Timestamp}}') no-repeat top center/100% 964rpx"> <view
class="page-container"
style="background: url('{{imageUrl}}bg9.png?t={{Timestamp}}') no-repeat top center/100% 964rpx"
>
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}" mode="aspectFill"></image> <image
class="avatar"
src="{{pharmacistAvatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}"
mode="aspectFill"
></image>
<view class="wrap"> <view class="wrap">
<view class="nickname">刘平安</view> <view class="nickname">{{pharmacistName || '药师'}}</view>
<view class="label">药师</view> <view class="label">药师</view>
</view> </view>
</view> </view>
<view class="title">邀请您加入健康管理项目</view> <view class="title">邀请您加入健康管理项目</view>
<view class="brand"> <view class="brand">
<view class="name">特诺雅<text style="font-size:0.5em;vertical-align: top;">®</text></view> <view class="name">
特诺雅
<text style="font-size: 0.5em; vertical-align: top">®</text>
</view>
<view class="bg"></view> <view class="bg"></view>
</view> </view>
<view class="code-wrap"> <view class="code-wrap" bind:tap="saveQrCode">
<image class="code" src="{{imageUrl}}cache/c1.png?t={{Timestamp}}" show-menu-by-longpress></image> <image class="code" src="{{qrCodeUrl}}" show-menu-by-longpress></image>
</view> </view>
<view class="tip"> <view class="tip">
<view class="dot"></view> <view class="dot"></view>
扫码立即加入 扫码立即加入
<view class="dot"></view> <view class="dot"></view>
</view> </view>
<view class="save-tip" wx:if="{{qrCodeUrl}}">点击二维码保存到相册</view>
</view> </view>
</view> </view>

7
src/doctor/pages/login/index.ts

@ -94,7 +94,6 @@ Page({
}, },
}).then((res) => { }).then((res) => {
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
app.globalData.loginState = res.token
app.globalData.initLoginInfo = { app.globalData.initLoginInfo = {
loginType: 4, loginType: 4,
isLogin: 1, isLogin: 1,
@ -112,10 +111,6 @@ Page({
}) })
return return
} }
wx.navigateTo({
url: '/doctor/pages/loginForm/index',
})
return
const { iv, encryptedData } = e.detail const { iv, encryptedData } = e.detail
if (iv && encryptedData) { if (iv && encryptedData) {
wx.ajax({ wx.ajax({
@ -132,7 +127,7 @@ Page({
}, },
submitCallback() { submitCallback() {
wx.reLaunch({ wx.reLaunch({
url: '/doctor/pages/home/index', url: '/doctor/pages/loginForm/index',
}) })
}, },
handlePatient() { handlePatient() {

3
src/doctor/pages/loginForm/index.json

@ -6,6 +6,7 @@
"van-icon": "@vant/weapp/icon/index", "van-icon": "@vant/weapp/icon/index",
"navbar": "/components/navbar/index", "navbar": "/components/navbar/index",
"van-popup": "@vant/weapp/popup/index", "van-popup": "@vant/weapp/popup/index",
"pickerArea": "/components/pickerArea/index" "pickerArea": "/components/pickerArea/index",
"pagination": "/components/pagination/index"
} }
} }

1
src/doctor/pages/loginForm/index.scss

@ -141,6 +141,7 @@ page {
max-height: 50vh; max-height: 50vh;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
box-sizing: border-box;
.l-item { .l-item {
padding: 32rpx 0; padding: 32rpx 0;
.name { .name {

320
src/doctor/pages/loginForm/index.ts

@ -1,174 +1,256 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
let timer = null as null | number
Page({ Page({
data: { data: {
mobile: '', // 扫码参数
code: '', promoterId: '',
codeText: '发送验证码', projectId: '',
back: false,
showForm: false, // 表单数据
check: false, name: '',
pharmacyId: '',
pharmacyName: '',
// 药店选择弹窗
show: false, show: false,
// 省市区选择
provinceId: '',
provinceName: '',
cityId: '',
cityName: '',
districtId: '',
districtName: '',
// 药店列表
pharmacyList: [] as any[],
// 搜索关键词
keyword: '',
// 分页
page: 1,
pageSize: 20,
loading: false,
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
}, },
onLoad(option) {
// 登录表单页面,允许游客访问
app.waitLogin({ types: [1] }).then(() => {
if (option.back) {
this.setData({
back: true,
})
}
})
}, },
getCode() { onLoad(option: { promoterId?: string; projectId?: string; scene?: string }) {
if (timer) return // 解析扫码参数
const mobile = this.data.mobile let promoterId = option.promoterId || ''
if (!mobile) { let projectId = option.projectId || ''
// 如果是扫码进入,解析 scene 参数
if (option.scene) {
const scene = decodeURIComponent(option.scene)
const params = this.parseScene(scene)
promoterId = params.promoterId || ''
projectId = params.projectId || ''
}
if (!promoterId || !projectId) {
wx.showToast({ wx.showToast({
title: '手机号不能为空', title: '缺少必要参数',
icon: 'none', icon: 'none',
}) })
return return
} }
// 验证手机号
if (!/^1[3-9]\d{9}$/.test(mobile)) { this.setData({
wx.showToast({ promoterId,
title: '手机号格式不正确', projectId,
icon: 'none',
}) })
return },
// 解析 scene 参数
parseScene(scene: string): { promoterId?: string; projectId?: string } {
const params: { promoterId?: string; projectId?: string } = {}
const pairs = scene.split('&')
pairs.forEach((pair) => {
const [key, value] = pair.split('=')
if (key === 'promoterId') {
params.promoterId = value
} else if (key === 'projectId') {
params.projectId = value
} }
})
return params
},
// 输入姓名
handleNameInput(e: WechatMiniprogram.CustomEvent) {
this.setData({
name: e.detail.value,
})
},
// 打开药店选择弹窗
handleDrug() {
this.setData({
show: true,
page: 1,
pharmacyList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
})
this.getPharmacyList()
},
// 关闭弹窗
onClose() {
this.setData({
show: false,
})
},
// 获取药店列表
getPharmacyList() {
if (this.data.loading || !this.data.hasMore) return
this.setData({ loading: true })
wx.ajax({ wx.ajax({
method: 'POST', method: 'GET',
url: '/app/common/common/send-code', url: '/app/common/common/pharmacy-list',
data: { data: {
phone: mobile, keyword: this.data.keyword,
type: 1, provinceId: this.data.provinceId,
cityId: this.data.cityId,
districtId: this.data.districtId,
page: this.data.page,
pageSize: this.data.pageSize,
},
}).then((res: any) => {
const list = res.list || []
const currentPage = this.data.page
const total = res.total || 0
const pages = Math.ceil(total / this.data.pageSize)
this.setData({
pharmacyList: [...this.data.pharmacyList, ...list],
total,
page: currentPage + 1,
hasMore: currentPage < pages,
loading: false,
pagination: {
count: total,
page: currentPage,
pages,
}, },
}).then((_res) => {
wx.showToast({
icon: 'none',
title: '验证码已发送~',
}) })
let time = 60 }).catch(() => {
timer = setInterval(() => { this.setData({ loading: false })
time-- })
},
// 省市区选择变化
handleChange(e: WechatMiniprogram.CustomEvent) {
const [province, city, district] = e.detail
this.setData({ this.setData({
codeText: `${time}s后重新发送`, provinceId: province?.value || '',
provinceName: province?.label || '',
cityId: city?.value || '',
cityName: city?.label || '',
districtId: district?.value || '',
districtName: district?.label || '',
page: 1,
pharmacyList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
}) })
if (time <= 0) { this.getPharmacyList()
clearInterval(timer as number) },
timer = null // 搜索药店
handleSearch(e: WechatMiniprogram.CustomEvent) {
this.setData({ this.setData({
codeText: '发送验证码', keyword: e.detail.value,
page: 1,
pharmacyList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
}) })
} this.getPharmacyList()
}, 1000) },
// 加载更多
loadMore() {
this.getPharmacyList()
},
// 选择药店
selectPharmacy(e: WechatMiniprogram.CustomEvent) {
const { id, name } = e.currentTarget.dataset
this.setData({
pharmacyId: id,
pharmacyName: name,
show: false,
}) })
}, },
// 提交绑定
handleSubmit() { handleSubmit() {
if (!this.data.mobile) { const { promoterId, projectId, name, pharmacyId } = this.data
wx.showToast({
icon: 'none', if (!name.trim()) {
title: '请输入手机号',
})
return
}
if (!this.data.code) {
wx.showToast({ wx.showToast({
title: '请输入姓名',
icon: 'none', icon: 'none',
title: '请输入验证码',
}) })
return return
} }
if (!this.data.check) {
if (!pharmacyId) {
wx.showToast({ wx.showToast({
title: '请选择所属药店',
icon: 'none', icon: 'none',
title: '请同意用户协议',
}) })
return return
} }
wx.showLoading({ title: '提交中...' })
wx.ajax({ wx.ajax({
method: 'POST', method: 'POST',
url: '/app/pharmacist/pharmacist/sms-login', url: '/app/pharmacist/pharmacist/bind-promoter',
data: { data: {
phone: this.data.mobile, promoterId,
code: this.data.code, projectId,
name: name.trim(),
pharmacyId,
}, },
}).then((res: any) => { }).then((res: any) => {
app.globalData.loginState = res.token wx.hideLoading()
// 保存登录信息
app.globalData.initLoginInfo = { app.globalData.initLoginInfo = {
loginType: 4, loginType: 4,
isLogin: 1, isLogin: 1,
isReg: 1, isReg: 1,
...res, ...res,
} }
this.submitCallback()
})
},
handleWxSubmit(e: WechatMiniprogram.CustomEvent) {
wx.redirectTo({
url: '/doctor/pages/home/index',
})
return
if (!this.data.check) {
wx.showToast({ wx.showToast({
icon: 'none', title: '绑定成功',
title: '请同意用户协议', icon: 'success',
}) })
return // 跳转到药店端首页
} setTimeout(() => {
const { iv, encryptedData } = e.detail
if (iv && encryptedData) {
wx.ajax({
method: 'POST',
url: '?r=zd/doctor/login/wx-reg-login',
data: {
iv: encodeURIComponent(iv),
encryptedData: encodeURIComponent(encryptedData),
},
}).then((_res) => {
this.submitCallback()
})
}
},
submitCallback() {
wx.reLaunch({ wx.reLaunch({
url: '/doctor/pages/home/index', url: '/doctor/pages/home/index',
}) })
}, }, 1500)
handlePatient() { }).catch(() => {
wx.redirectTo({ wx.hideLoading()
url: '/pages/index/index', wx.showToast({
}) title: '绑定失败',
}, icon: 'none',
handleTelCode() {
this.setData({
showForm: !this.data.showForm,
})
},
handleLink() {
// wx.navigateTo({
// url: '/doc/pages/doc1/index',
// })
},
handleCheck() {
this.setData({
check: !this.data.check,
})
},
handleBack() {
wx.navigateBack()
},
handleDrug() {
this.setData({
show: true,
}) })
},
onClose() {
this.setData({
show: false,
}) })
}, },
}) })
export {}

40
src/doctor/pages/loginForm/index.wxml

@ -13,29 +13,36 @@
<view class="row"> <view class="row">
<view class="label">您的姓名</view> <view class="label">您的姓名</view>
<view class="wrap"> <view class="wrap">
<input type="text" class="input" placeholder-class="place-input" placeholder="请输入姓名" /> <input type="text" class="input" placeholder-class="place-input" placeholder="请输入姓名" value="{{name}}" bindinput="handleNameInput" />
</view> </view>
</view> </view>
<view class="row"> <view class="row">
<view class="label">所属药店</view> <view class="label">所属药店</view>
<view class="wrap" bind:tap="handleDrug"> <view class="wrap" bind:tap="handleDrug">
<input disabled type="text" class="input" placeholder-class="place-input" placeholder="选择所属药店" value="北京同仁堂昌平东关药店"/> <input
disabled
type="text"
class="input"
placeholder-class="place-input"
placeholder="选择所属药店"
value="{{pharmacyName}}"
/>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
</view> </view>
</view> </view>
<button class="phone" bind:tap="handleWxSubmit">立即加入药师端</button> <button class="phone" bind:tap="handleSubmit">立即加入药师端</button>
</view> </view>
<van-popup show="{{ show }}" title="选择所属药店" position="bottom" round closeable bind:close="onClose"> <van-popup show="{{ show }}" title="选择所属药店" position="bottom" round closeable bind:close="onClose">
<view class="popup" bind:tap="onClose"> <view class="popup">
<view class="p-header"> <view class="p-header">
<view class="title">选择所属药店</view> <view class="title">选择所属药店</view>
<view class="search"> <view class="search">
<image class="icon" src="{{imageUrl}}icon1.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon1.png?t={{Timestamp}}"></image>
<input <input
type="text" type="text"
placeholder="搜索药店名/药师姓名" placeholder="搜索药店名"
class="input" class="input"
placeholder-class="place-input" placeholder-class="place-input"
confirm-type="search" confirm-type="search"
@ -45,21 +52,30 @@
<pickerArea bindchange="handleChange"> <pickerArea bindchange="handleChange">
<view class="options"> <view class="options">
<view class="o-item"> <view class="o-item">
<view class="content">请选择省份</view> <view class="content">{{provinceName || '请选择省份'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
<view class="o-item"> <view class="o-item">
<view class="content">请选择省份</view> <view class="content">{{cityName || '请选择城市'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
</view> </view>
</pickerArea> </pickerArea>
</view> </view>
<view class="p-list"> <scroll-view class="p-list" scroll-y bindscrolltolower="loadMore">
<view class="l-item" bind:tap="onClose"> <view
<view class="name">北京同仁堂昌平东关药店(医保定点)</view> class="l-item"
<view class="site">北京市昌平区府学路3号一层101号(昌平东关地铁站A西北口步行180米)</view> wx:for="{{pharmacyList}}"
</view> wx:key="id"
data-id="{{item.id}}"
data-name="{{item.name}}"
data-address="{{item.address}}"
bind:tap="selectPharmacy"
>
<view class="name">{{item.name}}</view>
<view class="site">{{item.provinceName}}{{item.cityName}}{{item.districtName}}{{item.address}}</view>
</view> </view>
<pagination pagination="{{pagination}}" wx:if="{{pharmacyList.length > 0}}" />
</scroll-view>
</view> </view>
</van-popup> </van-popup>

147
src/doctor/pages/my/index.ts

@ -1,11 +1,143 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
// 药店人员信息
pharmacistInfo: {
id: 0,
name: '',
phone: '',
avatar: '',
pharmacyName: '',
},
// 邀约人信息
inviterInfo: {
promoterName: '',
promoterPhone: '',
},
},
onLoad() { onLoad() {
// 药店端我的页面,仅允许药店人员访问 // 药店端我的页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 this.getProfile()
this.getInviterInfo()
})
},
onShow() {
// 页面显示时刷新用户信息
if (app.globalData.initLoginInfo?.pharmacistInfo) {
this.setData({
pharmacistInfo: app.globalData.initLoginInfo.pharmacistInfo,
})
}
},
// 获取药店人员信息
getProfile() {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/profile',
}).then((res: any) => {
this.setData({
pharmacistInfo: {
id: res.id || 0,
name: res.name || '',
phone: res.phone || '',
avatar: res.avatar || '',
pharmacyName: res.pharmacyName || '',
},
})
// 更新全局数据
if (app.globalData.initLoginInfo) {
app.globalData.initLoginInfo.pharmacistInfo = this.data.pharmacistInfo
}
})
},
// 获取邀约人信息
getInviterInfo() {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/inviter-info',
}).then((res: any) => {
this.setData({
inviterInfo: {
promoterName: res.promoterName || '',
promoterPhone: res.promoterPhone || '',
},
})
})
},
// 修改头像
handleAvatar() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
}).then((res: any) => {
const tempFilePath = res.tempFiles[0].tempFilePath
wx.showLoading({ title: '上传中...' })
wx.uploadFile({
url: `${app.globalData.url }/app/common/common/upload`,
filePath: tempFilePath,
name: 'file',
header: {
loginState: app.globalData.loginState,
},
success: (uploadRes: any) => {
try {
const data = JSON.parse(uploadRes.data)
if (data.code === 200) {
const avatarUrl = data.data.url
// 更新头像
wx.ajax({
method: 'POST',
url: '/app/pharmacist/pharmacist/update-avatar',
data: { avatar: avatarUrl },
}).then(() => {
wx.hideLoading()
this.setData({
'pharmacistInfo.avatar': avatarUrl,
})
// 更新全局数据
if (app.globalData.initLoginInfo?.pharmacistInfo) {
app.globalData.initLoginInfo.pharmacistInfo.avatar = avatarUrl
}
wx.showToast({
title: '头像更新成功',
icon: 'success',
})
}).catch(() => {
wx.hideLoading()
wx.showToast({
title: '头像更新失败',
icon: 'none',
})
})
} else {
wx.hideLoading()
wx.showToast({
title: data.msg || '上传失败',
icon: 'none',
})
}
} catch (e) {
wx.hideLoading()
wx.showToast({
title: '上传失败',
icon: 'none',
})
}
},
fail: () => {
wx.hideLoading()
wx.showToast({
title: '上传失败',
icon: 'none',
})
},
})
}) })
}, },
handleNickname() { handleNickname() {
@ -24,9 +156,20 @@ Page({
}) })
}, },
handleExit() { handleExit() {
wx.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除登录信息
app.globalData.initLoginInfo = null
app.globalData.loginState = ''
wx.redirectTo({ wx.redirectTo({
url: '/pages/work/index', url: '/pages/work/index',
}) })
}
},
})
}, },
}) })

12
src/doctor/pages/my/index.wxml

@ -5,14 +5,14 @@
style="background:url('{{imageUrl}}bg7.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px" style="background:url('{{imageUrl}}bg7.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px"
> >
<view class="user"> <view class="user">
<view class="avatar"> <view class="avatar" bind:tap="handleAvatar">
<image class="a-img" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}"></image> <image class="a-img" src="{{pharmacistInfo.avatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}" mode="aspectFill"></image>
<view class="edit"> <view class="edit">
<image class="icon" src="{{imageUrl}}icon3.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon3.png?t={{Timestamp}}"></image>
</view> </view>
</view> </view>
<view class="name">刘平安</view> <view class="name">{{pharmacistInfo.name || '药师'}}</view>
<view class="phone">康泰大药房(人民路店)</view> <view class="phone">{{pharmacistInfo.pharmacyName || ''}}</view>
</view> </view>
<view class="list"> <view class="list">
<view class="list-item" bind:tap="handleNickname"> <view class="list-item" bind:tap="handleNickname">
@ -30,9 +30,9 @@
<view class="title">我的邀约码</view> <view class="title">我的邀约码</view>
<van-icon class="arrow" name="arrow" /> <van-icon class="arrow" name="arrow" />
</view> </view>
<view class="list-item"> <view class="list-item" wx:if="{{inviterInfo.promoterName}}">
<image class="icon" src="{{imageUrl}}icon17.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon17.png?t={{Timestamp}}"></image>
<view class="title">我的邀约人 <text class="content">(王建设 12345678901)</text></view> <view class="title">我的邀约人 <text class="content">({{inviterInfo.promoterName}} {{inviterInfo.promoterPhone}})</text></view>
</view> </view>
</view> </view>
<view class="exit" bind:tap="handleExit">退出登录</view> <view class="exit" bind:tap="handleExit">退出登录</view>

3
src/doctor/pages/patientList/index.json

@ -3,6 +3,7 @@
"usingComponents": { "usingComponents": {
"navbar": "../../../components/navbar/index", "navbar": "../../../components/navbar/index",
"doctor-tab-bar": "/doctor/components/doctor-tab-bar/index", "doctor-tab-bar": "/doctor/components/doctor-tab-bar/index",
"popup":"/components/popup/index" "popup":"/components/popup/index",
"pagination": "/components/pagination/index"
} }
} }

298
src/doctor/pages/patientList/index.ts

@ -2,37 +2,287 @@ const app = getApp<IAppOption>()
Page({ Page({
data: { data: {
names: [ // 搜索关键词
'张吉惟', keyword: '',
'林国瑞',
'林玟书', // 筛选条件
'林雅南', jumpStatus: 0, // 0-全部,1-已跳转
'江奕云', enrollStatus: 0, // 0-全部,1-已入组
'刘柏宏', timeType: 0, // 0-跳转时间,1-入组时间
'阮建安', jumpStartTime: '',
'林子帆', jumpEndTime: '',
'夏志豪', enrollStartTime: '',
'吉茹定', enrollEndTime: '',
'李中冰',
'黄文隆', // 患者列表(模拟数据,接口上线后删除)
'谢彦文', patientList: [
'傅智翔', {
'洪振霞', id: 100,
'刘姿婷', patientId: 10,
'荣姿康', patientName: '患者A',
'吕致盈', patientAvatar: '',
'方一强', phone: '138****8888',
], indicationName: '银屑病',
pharmacyName: '国大药房(XX店)',
bindTime: 1700000000,
bindTimeFormatted: '2023/11/14 22:13:20',
jumpStatus: 1,
jumpTime: 1700000100,
jumpTimeFormatted: '2023/11/14 22:15:00',
enrollStatus: 1,
enrollTime: 1700000200,
enrollTimeFormatted: '2023/11/14 22:16:40',
materialStatus: 2,
},
] as any[],
totalCount: 1,
// 分页
page: 1,
pageSize: 20,
loading: false,
hasMore: true,
pagination: {
count: 1,
page: 1,
pages: 1,
},
// 弹窗
popupShow: false,
popupType: '',
popupParams: {},
}, },
onLoad() { onLoad() {
// 药店端患者列表页面,仅允许药店人员访问 // 药店端患者列表页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => { app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成 // TODO: 接口上线后取消注释
// this.getPatientList()
})
},
// 获取患者列表
getPatientList() {
if (this.data.loading || !this.data.hasMore) return
this.setData({ loading: true })
// 根据时间类型判断传递哪个时间参数
const { timeType, jumpStartTime, jumpEndTime, enrollStartTime, enrollEndTime } = this.data
const params: any = {
keyword: this.data.keyword,
jumpStatus: this.data.jumpStatus,
enrollStatus: this.data.enrollStatus,
page: this.data.page,
pageSize: this.data.pageSize,
}
// 时间类型:0-跳转时间,1-入组时间
if (timeType === 0) {
// 跳转时间
if (jumpStartTime) params.jumpStartTime = jumpStartTime
if (jumpEndTime) params.jumpEndTime = jumpEndTime
} else {
// 入组时间
if (enrollStartTime) params.enrollStartTime = enrollStartTime
if (enrollEndTime) params.enrollEndTime = enrollEndTime
}
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/patient-list',
data: params,
}).then((res: any) => {
const list = (res.list || []).map((item: any) => ({
...item,
bindTimeFormatted: item.bindTime ? this.formatDate(item.bindTime) : '-',
jumpTimeFormatted: item.jumpTime ? this.formatDate(item.jumpTime) : '',
enrollTimeFormatted: item.enrollTime ? this.formatDate(item.enrollTime) : '',
}))
const total = res.total || 0
const currentPage = this.data.page
this.setData({
patientList: [...this.data.patientList, ...list],
totalCount: total,
page: currentPage + 1,
hasMore: list.length >= this.data.pageSize,
loading: false,
pagination: {
count: total,
page: currentPage,
pages: Math.ceil(total / this.data.pageSize) || 1,
},
})
}).catch(() => {
this.setData({ loading: false })
})
},
// 格式化日期
formatDate(timestamp: number): string {
const date = new Date(timestamp * 1000)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
},
// 搜索患者
handleSearch(e: WechatMiniprogram.CustomEvent) {
this.setData({
keyword: e.detail.value,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 跳转状态筛选
handleJumpStatusChange(e: WechatMiniprogram.CustomEvent) {
const jumpStatus = e.detail.value === '1' ? 1 : 0
this.setData({
jumpStatus,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 入组状态筛选
handleEnrollStatusChange(e: WechatMiniprogram.CustomEvent) {
const enrollStatus = e.detail.value === '1' ? 1 : 0
this.setData({
enrollStatus,
page: 1,
patientList: [],
hasMore: true,
}) })
this.getPatientList()
}, },
handleInfo() { // 时间类型切换
handleTimeTypeChange(e: WechatMiniprogram.CustomEvent) {
const timeType = e.detail.value === '1' ? 1 : 0
this.setData({
timeType,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 跳转开始时间选择
handleJumpStartTimeChange(e: WechatMiniprogram.CustomEvent) {
const jumpStartTime = e.detail.value
if (this.data.jumpEndTime && jumpStartTime > this.data.jumpEndTime) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
this.setData({
jumpStartTime,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 跳转结束时间选择
handleJumpEndTimeChange(e: WechatMiniprogram.CustomEvent) {
const jumpEndTime = e.detail.value
if (this.data.jumpStartTime && jumpEndTime < this.data.jumpStartTime) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({
jumpEndTime,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 入组开始时间选择
handleEnrollStartTimeChange(e: WechatMiniprogram.CustomEvent) {
const enrollStartTime = e.detail.value
if (this.data.enrollEndTime && enrollStartTime > this.data.enrollEndTime) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
this.setData({
enrollStartTime,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 入组结束时间选择
handleEnrollEndTimeChange(e: WechatMiniprogram.CustomEvent) {
const enrollEndTime = e.detail.value
if (this.data.enrollStartTime && enrollEndTime < this.data.enrollStartTime) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({
enrollEndTime,
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 页面上拉触底事件
onReachBottom() {
if (this.data.loading || !this.data.hasMore) {
return
}
this.getPatientList()
},
// 查看详情
handleInfo(e: WechatMiniprogram.CustomEvent) {
const { id } = e.currentTarget.dataset
wx.navigateTo({ wx.navigateTo({
url: '/doctor/pages/stat/index', url: `/doctor/pages/stat/index?id=${id}`,
})
},
// 上传材料
handleUpload(e: WechatMiniprogram.CustomEvent) {
const { id } = e.currentTarget.dataset
this.setData({
popupShow: true,
popupType: 'upload',
popupParams: { patientId: id },
})
},
// 弹窗确认
handlePopupOk(e: WechatMiniprogram.CustomEvent) {
this.setData({
popupShow: false,
})
// 刷新列表
this.setData({
page: 1,
patientList: [],
hasMore: true,
})
this.getPatientList()
},
// 弹窗取消
handlePopupCancel() {
this.setData({
popupShow: false,
}) })
}, },
}) })

176
src/doctor/pages/patientList/index.wxml

@ -12,7 +12,7 @@
<image class="icon" src="{{imageUrl}}icon1.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon1.png?t={{Timestamp}}"></image>
<input <input
type="text" type="text"
placeholder="搜索药店名/药师姓名" placeholder="搜索患者姓名"
class="input" class="input"
placeholder-class="place-input" placeholder-class="place-input"
confirm-type="search" confirm-type="search"
@ -20,188 +20,126 @@
/> />
</view> </view>
<view class="row"> <view class="row">
<picker mode="region"> <picker bindchange="handleJumpStatusChange" value="{{jumpStatus}}" range="{{['全部', '已跳转']}}">
<view class="col"> <view class="col">
<view class="label">跳转:</view> <view class="label">跳转:</view>
<view class="content">全部</view> <view class="content">{{jumpStatus === 0 ? '全部' : '已跳转'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<picker mode="region"> <picker bindchange="handleEnrollStatusChange" value="{{enrollStatus}}" range="{{['全部', '已入组']}}">
<view class="col"> <view class="col">
<view class="label">入组:</view> <view class="label">入组:</view>
<view class="content">全部</view> <view class="content">{{enrollStatus === 0 ? '全部' : '已入组'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
</view> </view>
<view class="range"> <view class="range">
<view class="label">时间筛选:</view> <view class="label">时间筛选:</view>
<picker class="picker" mode="date"> <picker
<view class="date">开始时间</view> class="picker"
mode="date"
bindchange="{{timeType === 0 ? 'handleJumpStartTimeChange' : 'handleEnrollStartTimeChange'}}"
value="{{timeType === 0 ? jumpStartTime : enrollStartTime}}"
>
<view class="date">{{(timeType === 0 ? jumpStartTime : enrollStartTime) || '开始时间'}}</view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date"> <picker
<view class="date">开始时间</view> class="picker"
mode="date"
bindchange="{{timeType === 0 ? 'handleJumpEndTimeChange' : 'handleEnrollEndTimeChange'}}"
value="{{timeType === 0 ? jumpEndTime : enrollEndTime}}"
>
<view class="date">{{(timeType === 0 ? jumpEndTime : enrollEndTime) || '结束时间'}}</view>
</picker> </picker>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
<view class="row"> <view class="row">
<picker mode="region"> <picker bindchange="handleTimeTypeChange" value="{{timeType}}" range="{{['跳转时间', '入组时间']}}">
<view class="col"> <view class="col">
<view class="label">时间类型:</view> <view class="label">时间类型:</view>
<view class="content">全部</view> <view class="content">{{timeType === 0 ? '跳转时间' : '入组时间'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="total"> <view class="total">
已邀约总人数: 已邀约总人数:
<text class="num">28人</text> <text class="num">{{totalCount}}人</text>
</view> </view>
</view> </view>
</view> </view>
<view class="page-container"> <view class="page-container">
<view class="card"> <view class="card" wx:for="{{patientList}}" wx:key="id" data-id="{{item.id}}" bind:tap="handleInfo">
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a2.png?t={{Timestamp}}"></image> <image
class="avatar"
src="{{item.patientAvatar || imageUrl + 'cache/a2.png'}}?t={{Timestamp}}"
mode="aspectFill"
></image>
<view class="wrap"> <view class="wrap">
<view class="info"> <view class="info">
<text class="name">张患者</text> <text class="name">{{item.patientName}}</text>
<text class="tel">13800138000</text> <text class="tel">{{item.phone}}</text>
</view> </view>
<view class="site">康泰大药房(人民路店)</view> <view class="site">{{item.pharmacyName}}</view>
<view class="date">绑定时间:2026/01/10 16:27:35</view> <view class="date">绑定时间:{{item.bindTimeFormatted}}</view>
</view> </view>
</view> </view>
<view class="container"> <view class="container">
<view class="c-item"> <!-- 跳转状态 -->
<view class="c-item {{item.jumpStatus === 1 ? 'active' : ''}}">
<view class="aside"> <view class="aside">
<view class="line-top"></view> <view class="line-top"></view>
<view class="step"> <view class="step">
<view class="name">未跳转</view> <view class="name">{{item.jumpStatus === 1 ? '已跳转' : '未跳转'}}</view>
<view class="order"></view> <view class="order"></view>
</view> </view>
<view class="line-bottom"></view> <view class="line-bottom"></view>
</view> </view>
<view class="wrap"> <view class="wrap">
<view class="date">2026/01/10 16:27:35</view> <view class="date" wx:if="{{item.jumpTime}}">{{item.jumpTimeFormatted}}</view>
<view class="status"> <view class="status" wx:if="{{item.jumpStatus === 1}}">
<view class="s1">审核中</view> <view
<view class="btn1">查看提交材料</view> class="s{{item.materialStatus === 0 ? '1' : (item.materialStatus === 1 ? '1' : (item.materialStatus === 2 ? '1' : '2'))}}"
>
{{item.materialStatus === 0 ? '未提交' : (item.materialStatus === 1 ? '审核中' : (item.materialStatus
=== 2 ? '已通过' : '已驳回'))}}
</view> </view>
<view
class="btn{{item.materialStatus === 3 ? '2' : '1'}}"
catch:tap="{{item.materialStatus === 3 ? 'handleUpload' : 'handleInfo'}}"
data-id="{{item.id}}"
>
{{item.materialStatus === 3 ? '重新提交' : '查看提交材料'}}
</view> </view>
</view> </view>
<view class="c-item"> <view class="remark" wx:if="{{item.materialStatus === 3}}">材料信息不全</view>
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">未入组</view>
<view class="order"></view>
</view>
<view class="line-bottom"></view>
</view>
<view class="wrap">
<div class="none">---</div>
</view>
</view>
</view>
</view>
<view class="card">
<view class="user">
<image class="avatar" src="{{imageUrl}}cache/a2.png?t={{Timestamp}}"></image>
<view class="wrap">
<view class="info">
<text class="name">张患者</text>
<text class="tel">13800138000</text>
</view>
<view class="site">康泰大药房(人民路店)</view>
<view class="date">绑定时间:2026/01/10 16:27:35</view>
</view>
</view>
<view class="container">
<view class="c-item">
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">未跳转</view>
<view class="order"></view>
</view>
<view class="line-bottom"></view>
</view>
<view class="wrap">
<view class="date">2026/01/10 16:27:35</view>
<view class="status">
<view class="s2">驳回</view>
<view class="btn2" bind:tap="handleUpload">重新提交</view>
</view>
<view class="remark">材料信息不全</view>
</view>
</view>
<view class="c-item active">
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">已入组</view>
<view class="order"></view>
</view>
<view class="line-bottom"></view>
</view>
<view class="wrap">
<view class="date">2026/01/10 16:27:35</view>
</view>
</view>
</view>
</view>
<view class="card">
<view class="user">
<image class="avatar" src="{{imageUrl}}cache/a2.png?t={{Timestamp}}"></image>
<view class="wrap">
<view class="info">
<text class="name">张患者</text>
<text class="tel">13800138000</text>
</view>
<view class="site">康泰大药房(人民路店)</view>
<view class="date">绑定时间:2026/01/10 16:27:35</view>
</view>
</view>
<view class="container">
<view class="c-item active">
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">已跳转</view>
<view class="order"></view>
</view>
<view class="line-bottom"></view>
</view>
<view class="wrap">
<view class="date">2026/01/10 16:27:35</view>
<view class="status">
<view class="s1">审核通过</view>
<view class="btn1">查看提交材料</view>
</view>
<view class="remark">材料信息不全</view>
</view> </view>
</view> </view>
<view class="c-item active"> <!-- 入组状态 -->
<view class="c-item {{item.enrollStatus === 1 ? 'active' : ''}}">
<view class="aside"> <view class="aside">
<view class="line-top"></view> <view class="line-top"></view>
<view class="step"> <view class="step">
<view class="name">已入组</view> <view class="name">{{item.enrollStatus === 1 ? '已入组' : '未入组'}}</view>
<view class="order"></view> <view class="order"></view>
</view> </view>
<view class="line-bottom"></view> <view class="line-bottom"></view>
</view> </view>
<view class="wrap"> <view class="wrap">
<view class="date">2026/01/10 16:27:35</view> <view class="date" wx:if="{{item.enrollTime}}">{{item.enrollTimeFormatted}}</view>
<view class="status"> <view class="status" wx:if="{{item.enrollStatus === 1}}">
<view class="s1">审核通过</view> <view class="s1">审核通过</view>
<view class="btn1">查看提交材料</view> <view class="btn1" catch:tap="handleInfo" data-id="{{item.id}}">查看提交材料</view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 分页组件 -->
<pagination pagination="{{pagination}}" wx:if="{{!loading || patientList.length > 0}}"></pagination>
</view> </view>
</view> </view>

2
src/doctor/pages/stat/index.wxml

@ -1,7 +1,7 @@
<navbar fixed custom-style="background:#fff"> <navbar fixed custom-style="background:#fff">
<view slot="left" class="page-title"> <view slot="left" class="page-title">
<van-icon class="back" slot="left" name="arrow-left" bind:tap="handleBack" /> <van-icon class="back" slot="left" name="arrow-left" bind:tap="handleBack" />
专属邀约码 邀约数明细
</view> </view>
</navbar> </navbar>

63
src/ground/pages/changeNickname/index.ts

@ -1,11 +1,70 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
name: '',
loading: false,
},
onLoad() { onLoad() {
// 地推端修改昵称页面,仅允许地推人员访问 // 地推端修改昵称页面,仅允许地推人员访问
app.waitLogin({ types: [3] }).then(() => { app.waitLogin({ types: [3] }).then(() => {
// 页面加载完成 // 获取当前姓名
this.getCurrentName()
})
},
// 获取当前姓名
getCurrentName() {
wx.ajax({
method: 'GET',
url: '/app/common/common/user-info',
}).then((res: any) => {
const promoterInfo = res.promoterInfo || {}
this.setData({
name: promoterInfo.name || '',
})
})
},
// 输入姓名
handleInput(e: WechatMiniprogram.CustomEvent) {
this.setData({
name: e.detail.value,
})
},
// 确认修改
handleSubmit() {
const { name } = this.data
if (!name.trim()) {
wx.showToast({
title: '请输入姓名',
icon: 'none',
})
return
}
if (this.data.loading) return
this.setData({ loading: true })
wx.ajax({
method: 'POST',
url: '/app/promoter/promoter/update-name',
data: {
name: name.trim(),
},
}).then(() => {
this.setData({ loading: false })
wx.showToast({
title: '修改成功',
icon: 'success',
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
}).catch(() => {
this.setData({ loading: false })
wx.showToast({
title: '修改失败',
icon: 'none',
})
}) })
}, },
handleBack() { handleBack() {

13
src/ground/pages/changeNickname/index.wxml

@ -6,6 +6,15 @@
</navbar> </navbar>
<view class="page" style="padding-top: {{pageTop+28}}px;"> <view class="page" style="padding-top: {{pageTop+28}}px;">
<input class="input" placeholder-class="place-input" type="text" placeholder="请输入新昵称" /> <input
<view class="btn">确认</view> class="input"
placeholder-class="place-input"
type="text"
placeholder="请输入新昵称"
value="{{name}}"
bindinput="handleInput"
/>
<view class="btn" bind:tap="handleSubmit" style="opacity: {{loading ? 0.6 : 1}};">
{{loading ? '保存中...' : '确认'}}
</view>
</view> </view>

103
src/ground/pages/changeTel/index.ts

@ -1,10 +1,13 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
let timer = null as null | number
Page({ Page({
data: { data: {
mobile: '', mobile: '',
code: '', code: '',
codeText: '发送验证码', codeText: '发送验证码',
loading: false,
}, },
onLoad() { onLoad() {
// 地推端修改手机号页面,仅允许地推人员访问 // 地推端修改手机号页面,仅允许地推人员访问
@ -12,11 +15,107 @@ Page({
// 页面加载完成 // 页面加载完成
}) })
}, },
onUnload() {
// 页面卸载时清除定时器
if (timer) {
clearInterval(timer)
timer = null
}
},
// 获取验证码
getCode() { getCode() {
// TODO: 需要更新为新的接口 if (timer) return
const mobile = this.data.mobile
if (!mobile) {
wx.showToast({
title: '手机号不能为空',
icon: 'none',
})
return
}
// 验证手机号
if (!/^1[3-9]\d{9}$/.test(mobile)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none',
})
return
}
wx.ajax({
method: 'POST',
url: '/app/common/common/send-code',
data: {
phone: mobile,
type: 3, // 3-修改手机号
},
}).then(() => {
wx.showToast({
icon: 'none',
title: '验证码已发送~',
})
let time = 60
timer = setInterval(() => {
time--
this.setData({
codeText: `${time}s后重新发送`,
})
if (time <= 0) {
clearInterval(timer as number)
timer = null
this.setData({
codeText: '发送验证码',
})
}
}, 1000)
})
}, },
// 提交修改
handleSubmit() { handleSubmit() {
// TODO: 需要更新为新的接口 const { mobile, code } = this.data
if (!mobile) {
wx.showToast({
icon: 'none',
title: '请输入手机号',
})
return
}
if (!code) {
wx.showToast({
icon: 'none',
title: '请输入验证码',
})
return
}
if (this.data.loading) return
this.setData({ loading: true })
wx.ajax({
method: 'POST',
url: '/app/promoter/promoter/update-phone',
data: {
phone: mobile,
code,
},
}).then(() => {
this.setData({ loading: false })
wx.showToast({
title: '修改成功',
icon: 'success',
})
setTimeout(() => {
wx.navigateBack()
}, 1500)
}).catch(() => {
this.setData({ loading: false })
wx.showToast({
title: '修改失败',
icon: 'none',
})
})
}, },
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()

6
src/ground/pages/changeTel/index.wxml

@ -25,9 +25,11 @@
placeholder-class="place-input" placeholder-class="place-input"
placeholder="请输入验证码" placeholder="请输入验证码"
/> />
<view class="code" bind:tap="getCode">{{codeText}}</view> <view class="code" bind:tap="getCode" style="opacity: {{timer ? 0.6 : 1}};">{{codeText}}</view>
</view> </view>
</view> </view>
<view class="submit" bind:tap="handleSubmit">修改手机号</view> <view class="submit" bind:tap="handleSubmit" style="opacity: {{loading ? 0.6 : 1}};">
{{loading ? '修改中...' : '修改手机号'}}
</view>
</view> </view>
</view> </view>

86
src/ground/pages/home/index.ts

@ -87,13 +87,27 @@ Page({
url: '/app/promoter/promoter/statistics', url: '/app/promoter/promoter/statistics',
}).then((res: any) => { }).then((res: any) => {
this.setData({ this.setData({
invitePharmacyCount: res.invitePharmacyCount, invitePharmacyCount: res.invitePharmacyCount || 0,
invitePharmacistCount: res.invitePharmacistCount, invitePharmacistCount: res.invitePharmacistCount || 0,
invitePatientCount: res.invitePatientCount, invitePatientCount: res.invitePatientCount || 0,
jumpPatientCount: res.jumpPatientCount, jumpPatientCount: res.jumpPatientCount || 0,
enrollPatientCount: res.enrollPatientCount, enrollPatientCount: res.enrollPatientCount || 0,
indicationStats: res.indicationStats || [], indicationStats: res.indicationStats || [],
}) })
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
invitePharmacyCount: 45,
invitePharmacistCount: 128,
invitePatientCount: 356,
jumpPatientCount: 289,
enrollPatientCount: 156,
indicationStats: [
{ indicationId: 1, indicationName: '斑块状银屑病', invitePatientCount: 150, jumpPatientCount: 120, enrollPatientCount: 65 },
{ indicationId: 2, indicationName: '溃疡性结肠炎', invitePatientCount: 120, jumpPatientCount: 98, enrollPatientCount: 52 },
{ indicationId: 3, indicationName: '克罗恩病', invitePatientCount: 86, jumpPatientCount: 71, enrollPatientCount: 39 },
],
})
}) })
}, },
@ -108,10 +122,18 @@ Page({
endDate: this.data.endDate, endDate: this.data.endDate,
}, },
}).then((res: any) => { }).then((res: any) => {
const list = res.list || []
this.setData({
chartData: list,
})
this.initChartBar(list)
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
this.setData({ this.setData({
chartData: res.list || [], chartData: mockData,
}) })
this.initChartBar(res.list || []) this.initChartBar(mockData)
}) })
}, },
@ -127,6 +149,10 @@ Page({
}, },
}).then((res: any) => { }).then((res: any) => {
this.initChartLine(res.list || [], '#chart2_1', 'ecDataTrendComponent2_1') this.initChartLine(res.list || [], '#chart2_1', 'ecDataTrendComponent2_1')
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
this.initChartLine(mockData, '#chart2_1', 'ecDataTrendComponent2_1')
}) })
}, },
@ -142,7 +168,33 @@ Page({
}, },
}).then((res: any) => { }).then((res: any) => {
this.initChartLine(res.list || [], '#chart3_1', 'ecDataTrendComponent3_1') this.initChartLine(res.list || [], '#chart3_1', 'ecDataTrendComponent3_1')
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
this.initChartLine(mockData, '#chart3_1', 'ecDataTrendComponent3_1')
})
},
// 生成模拟图表数据
generateMockChartData() {
const list: any[] = []
const days = this.data.statType === 'day' ? 30 : 12
for (let i = 0; i < days; i++) {
const date = new Date()
if (this.data.statType === 'day') {
date.setDate(date.getDate() - (days - 1 - i))
list.push({
date: this.formatDate(date),
count: Math.floor(Math.random() * 50) + 10,
})
} else {
date.setMonth(date.getMonth() - (days - 1 - i))
list.push({
date: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`,
count: Math.floor(Math.random() * 500) + 100,
}) })
}
}
return list
}, },
// 切换统计类型 // 切换统计类型
@ -159,8 +211,26 @@ Page({
// 日期选择变化 // 日期选择变化
onDateChange(e: WechatMiniprogram.CustomEvent) { onDateChange(e: WechatMiniprogram.CustomEvent) {
const { field } = e.currentTarget.dataset const { field } = e.currentTarget.dataset
const value = e.detail.value
// 验证日期范围
if (field === 'startDate' && this.data.endDate && value > this.data.endDate) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
if (field === 'endDate' && this.data.startDate && value < this.data.startDate) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({ this.setData({
[field]: e.detail.value, [field]: value,
}) })
// 重新加载图表数据 // 重新加载图表数据
this.getPatientChart() this.getPatientChart()

144
src/ground/pages/home/index.wxml

@ -11,10 +11,10 @@
style="background:url('{{imageUrl}}bg1.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px" style="background:url('{{imageUrl}}bg1.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px"
> >
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}"></image> <image class="avatar" src="{{promoterAvatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}"></image>
<view class="wrap"> <view class="wrap">
<view class="name">刘平安</view> <view class="name">{{promoterName || '地推人员'}}</view>
<view class="label">邀约专员</view> <view class="label">{{label}}</view>
</view> </view>
<view class="code" bind:tap="handleInvite"> <view class="code" bind:tap="handleInvite">
<image class="icon" src="{{imageUrl}}icon7.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon7.png?t={{Timestamp}}"></image>
@ -34,84 +34,54 @@
<view class="row1"> <view class="row1">
<view class="col"> <view class="col">
<view class="name">药店数</view> <view class="name">药店数</view>
<view class="num">400</view> <view class="num">{{invitePharmacyCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">药师数</view> <view class="name">药师数</view>
<view class="num">400</view> <view class="num">{{invitePharmacistCount}}</view>
</view> </view>
</view> </view>
<view class="card"> <view class="card">
<view class="row2"> <view class="row2">
<view class="col"> <view class="col">
<view class="name">邀约患者数</view> <view class="name">邀约患者数</view>
<view class="num">750</view> <view class="num">{{invitePatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">跳转患者数</view> <view class="name">跳转患者数</view>
<view class="num">750</view> <view class="num">{{jumpPatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">入组患者数</view>
<view class="num">750</view> <view class="num">{{enrollPatientCount}}</view>
</view> </view>
</view> </view>
<view class="card-container {{fold1&&'fold'}}"> <view class="card-container {{fold1&&'fold'}}">
<view class="row3"> <view wx:for="{{indicationStats}}" wx:key="indicationId" class="row3">
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.invitePatientCount}}</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">300</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.jumpPatientCount}}</view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.enrollPatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
</view> </view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="chat-switch-btns"> <view class="chat-switch-btns">
<view class="btn">月统计</view> <view class="btn {{statType === 'month' ? 'active' : ''}}" bind:tap="switchStatType" data-type="month">月统计</view>
<view class="btn active">日统计</view> <view class="btn {{statType === 'day' ? 'active' : ''}}" bind:tap="switchStatType" data-type="day">日统计</view>
</view> </view>
<view class="chat-data"> <view class="chat-data">
<view class="c-header"> <view class="c-header">
@ -121,14 +91,14 @@
<view class="name" bind:tap="handleFold" data-key="fold2"> <view class="name" bind:tap="handleFold" data-key="fold2">
邀约患者统计 邀约患者统计
<view class="fold {{fold2&&'active'}}"> <view class="fold {{fold2&&'active'}}">
{{fold1?'展开':'收起'}} {{fold2?'展开':'收起'}}
<van-icon class="icon" name="arrow-down" /> <van-icon class="icon" name="arrow-down" />
</view> </view>
</view> </view>
<picker class="picker" mode="date"> <picker class="picker" mode="date" value="{{startDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<van-icon class="icon" name="arrow-left" /> <van-icon class="icon" name="arrow-left" />
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<van-icon class="icon" name="arrow" /> <van-icon class="icon" name="arrow" />
</view> </view>
</picker> </picker>
@ -137,79 +107,49 @@
<view class="row2"> <view class="row2">
<view class="col"> <view class="col">
<view class="name">邀约患者数</view> <view class="name">邀约患者数</view>
<view class="num">750</view> <view class="num">{{invitePatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">跳转患者数</view> <view class="name">跳转患者数</view>
<view class="num">750</view> <view class="num">{{jumpPatientCount}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">入组患者数</view>
<view class="num">750</view> <view class="num">{{enrollPatientCount}}</view>
</view> </view>
</view> </view>
<view class="card-container {{fold2&&'fold'}}"> <view class="card-container {{fold2&&'fold'}}">
<view class="row3"> <view wx:for="{{indicationStats}}" wx:key="indicationId" class="row3">
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.invitePatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">斑块状银屑病</view> <view class="name">{{item.indicationName}}</view>
<view class="num">280</view> <view class="num">{{item.jumpPatientCount}}</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view> </view>
<view class="row3">
<view class="col"> <view class="col">
<view class="name">溃疡性结肠炎</view> <view class="name">{{item.indicationName}}</view>
<view class="num">300</view> <view class="num">{{item.enrollPatientCount}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="chart-range"> <view class="chart-range">
<picker class="picker" mode="date" end="{{end}}"> <picker class="picker" mode="date" value="{{startDate}}" end="{{endDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date" start="{{satrt}}"> <picker class="picker" mode="date" value="{{endDate}}" start="{{startDate}}" bindchange="onDateChange" data-field="endDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
@ -227,16 +167,16 @@
<view class="title">邀约药师数统计</view> <view class="title">邀约药师数统计</view>
</view> </view>
<view class="chart-range"> <view class="chart-range">
<picker class="picker" mode="date" end="{{end}}"> <picker class="picker" mode="date" value="{{startDate}}" end="{{endDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date" start="{{satrt}}"> <picker class="picker" mode="date" value="{{endDate}}" start="{{startDate}}" bindchange="onDateChange" data-field="endDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
@ -250,16 +190,16 @@
<view class="title">邀约药店统计</view> <view class="title">邀约药店统计</view>
</view> </view>
<view class="chart-range"> <view class="chart-range">
<picker class="picker" mode="date" end="{{end}}"> <picker class="picker" mode="date" value="{{startDate}}" end="{{endDate}}" bindchange="onDateChange" data-field="startDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date" start="{{satrt}}"> <picker class="picker" mode="date" value="{{endDate}}" start="{{startDate}}" bindchange="onDateChange" data-field="endDate">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>

101
src/ground/pages/invite/index.ts

@ -1,11 +1,108 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
// 地推人员信息
promoterName: '',
promoterAvatar: '',
promoterId: '',
// 小程序码
qrCodeUrl: '',
// 项目信息
projectId: '',
projectName: '',
},
onLoad() { onLoad() {
// 地推端邀约页面,仅允许地推人员访问 // 地推端邀约页面,仅允许地推人员访问
app.waitLogin({ types: [3] }).then(() => { app.waitLogin({ types: [3] }).then(() => {
// 页面加载完成 this.getUserInfo()
})
},
// 获取地推人员信息
getUserInfo() {
wx.ajax({
method: 'GET',
url: '/app/common/common/user-info',
}).then((res: any) => {
const promoterInfo = res.promoterInfo || {}
this.setData({
promoterName: promoterInfo.name || '',
promoterAvatar: promoterInfo.avatar || '',
promoterId: promoterInfo.id || '',
})
// 获取小程序码
this.generateQrCode()
})
},
// 生成地推邀约小程序码
generateQrCode() {
const { promoterId } = this.data
if (!promoterId) {
wx.showToast({
title: '获取地推人员信息失败',
icon: 'none',
})
return
}
wx.ajax({
method: 'POST',
url: '/app/common/common/wxacode',
data: {
scene: `promoterId=${promoterId}`,
page: 'pages/invite/pharmacist',
width: 430,
type: 1, // 1-地推邀约码
},
}).then((res: any) => {
this.setData({
qrCodeUrl: res.imageUrl || '',
})
}).catch(() => {
wx.showToast({
title: '生成二维码失败',
icon: 'none',
})
})
},
// 保存二维码到相册
saveQrCode() {
const { qrCodeUrl } = this.data
if (!qrCodeUrl) {
wx.showToast({
title: '二维码未生成',
icon: 'none',
})
return
}
wx.downloadFile({
url: qrCodeUrl,
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.showToast({
title: '保存成功',
icon: 'success',
})
},
fail: () => {
wx.showToast({
title: '保存失败',
icon: 'none',
})
},
})
},
fail: () => {
wx.showToast({
title: '下载图片失败',
icon: 'none',
})
},
}) })
}, },
handleBack() { handleBack() {

23
src/ground/pages/invite/index.wxml

@ -9,26 +9,37 @@
class="page" class="page"
style="background: url('{{imageUrl}}bg5.png?t={{Timestamp}}') no-repeat top center/100% 100vh;padding-top: {{pageTop+87}}px;" style="background: url('{{imageUrl}}bg5.png?t={{Timestamp}}') no-repeat top center/100% 100vh;padding-top: {{pageTop+87}}px;"
> >
<view class="page-container" style="background: url('{{imageUrl}}bg6.png?t={{Timestamp}}') no-repeat top center/100% 964rpx"> <view
class="page-container"
style="background: url('{{imageUrl}}bg6.png?t={{Timestamp}}') no-repeat top center/100% 964rpx"
>
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}" mode="aspectFill"></image> <image
class="avatar"
src="{{promoterAvatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}"
mode="aspectFill"
></image>
<view class="wrap"> <view class="wrap">
<view class="nickname">刘平安</view> <view class="nickname">{{promoterName || '邀约专员'}}</view>
<view class="label">邀约专员</view> <view class="label">邀约专员</view>
</view> </view>
</view> </view>
<view class="title">邀请您加入健康管理项目</view> <view class="title">邀请您加入健康管理项目</view>
<view class="brand"> <view class="brand">
<view class="name">特诺雅<text style="font-size:0.5em;vertical-align: top;">®</text></view> <view class="name">
特诺雅
<text style="font-size: 0.5em; vertical-align: top">®</text>
</view>
<view class="bg"></view> <view class="bg"></view>
</view> </view>
<view class="code-wrap"> <view class="code-wrap" bind:tap="saveQrCode">
<image class="code" src="{{imageUrl}}cache/c1.png?t={{Timestamp}}" show-menu-by-longpress></image> <image class="code" src="{{qrCodeUrl}}" show-menu-by-longpress></image>
</view> </view>
<view class="tip"> <view class="tip">
<view class="dot"></view> <view class="dot"></view>
扫码立即加入 扫码立即加入
<view class="dot"></view> <view class="dot"></view>
</view> </view>
<view class="save-tip" wx:if="{{qrCodeUrl}}">点击二维码保存到相册</view>
</view> </view>
</view> </view>

6
src/ground/pages/login/index.ts

@ -90,10 +90,10 @@ Page({
data: { data: {
phone: this.data.mobile, phone: this.data.mobile,
code: this.data.code, code: this.data.code,
identity: 3,
}, },
}).then((res) => { }).then((res) => {
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
app.globalData.loginState = res.token
app.globalData.initLoginInfo = { app.globalData.initLoginInfo = {
loginType: 3, loginType: 3,
isLogin: 1, isLogin: 1,
@ -111,10 +111,6 @@ Page({
}) })
return return
} }
wx.reLaunch({
url: '/ground/pages/home/index',
})
return
const { iv, encryptedData } = e.detail const { iv, encryptedData } = e.detail
if (iv && encryptedData) { if (iv && encryptedData) {
wx.ajax({ wx.ajax({

109
src/ground/pages/my/index.ts

@ -1,11 +1,108 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
// 用户信息
promoterName: '',
promoterAvatar: '',
promoterPhone: '',
},
onLoad() { onLoad() {
// 地推端我的页面,仅允许地推人员访问 // 地推端我的页面,仅允许地推人员访问
app.waitLogin({ types: [3] }).then(() => { app.waitLogin({ types: [3] }).then(() => {
// 页面加载完成 this.getUserInfo()
})
},
onShow() {
// 页面显示时刷新用户信息
this.getUserInfo()
},
// 获取用户信息
getUserInfo() {
wx.ajax({
method: 'GET',
url: '/app/common/common/user-info',
}).then((res: any) => {
const promoterInfo = res.promoterInfo || {}
this.setData({
promoterName: promoterInfo.name || '',
promoterAvatar: promoterInfo.avatar || '',
promoterPhone: promoterInfo.phone || '',
})
})
},
// 点击头像修改头像
handleAvatar() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
})
.then((res) => {
const tempFilePath = res.tempFiles[0].tempFilePath
// 上传图片
this.uploadAvatar(tempFilePath)
})
.catch(() => {
// 用户取消选择
})
},
// 上传头像
uploadAvatar(filePath: string) {
wx.showLoading({ title: '上传中...' })
wx.uploadFile({
url: `${app.globalData.url}/app/common/common/upload`,
filePath,
name: 'file',
header: {
'loginState': app.globalData.loginState || '',
},
success: (res) => {
wx.hideLoading()
const data = JSON.parse(res.data)
if (data.code === 0) {
const avatarUrl = data.data?.url || data.data
// 更新头像
this.updateAvatar(avatarUrl)
} else {
wx.showToast({
title: data.msg || '上传失败',
icon: 'none',
})
}
},
fail: () => {
wx.hideLoading()
wx.showToast({
title: '上传失败',
icon: 'none',
})
},
})
},
// 更新头像
updateAvatar(avatarUrl: string) {
wx.ajax({
method: 'POST',
url: '/app/promoter/promoter/update-avatar',
data: {
avatar: avatarUrl,
},
})
.then(() => {
wx.showToast({
title: '头像修改成功',
icon: 'success',
})
// 刷新用户信息
this.getUserInfo()
})
.catch(() => {
wx.showToast({
title: '头像修改失败',
icon: 'none',
})
}) })
}, },
handleNickname() { handleNickname() {
@ -24,9 +121,17 @@ Page({
}) })
}, },
hadleExit() { hadleExit() {
wx.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
wx.reLaunch({ wx.reLaunch({
url: '/pages/work/index', url: '/pages/work/index',
}) })
}
},
})
}, },
}) })

8
src/ground/pages/my/index.wxml

@ -5,14 +5,14 @@
style="background:url('{{imageUrl}}bg1.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px" style="background:url('{{imageUrl}}bg1.png?t={{Timestamp}}') no-repeat top center/100% 602rpx; padding-top: {{pageTop+20}}px"
> >
<view class="user"> <view class="user">
<view class="avatar"> <view class="avatar" bind:tap="handleAvatar">
<image class="a-img" src="{{imageUrl}}cache/a1.png?t={{Timestamp}}"></image> <image class="a-img" src="{{promoterAvatar || imageUrl + 'cache/a1.png'}}?t={{Timestamp}}"></image>
<view class="edit"> <view class="edit">
<image class="icon" src="{{imageUrl}}icon3.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon3.png?t={{Timestamp}}"></image>
</view> </view>
</view> </view>
<view class="name">刘平安</view> <view class="name">{{promoterName || '未设置姓名'}}</view>
<view class="phone">13800138000</view> <view class="phone">{{promoterPhone || ''}}</view>
</view> </view>
<view class="list"> <view class="list">
<view class="list-item" bind:tap="handleNickname"> <view class="list-item" bind:tap="handleNickname">

5
src/ground/pages/pharmacist/index.json

@ -2,7 +2,8 @@
"navigationBarTitleText": "地推端", "navigationBarTitleText": "地推端",
"navigationStyle": "custom", "navigationStyle": "custom",
"usingComponents": { "usingComponents": {
"navbar": "../../../components/navbar/index", "navbar": "/components/navbar/index",
"ground-tab-bar": "/ground/components/ground-tab-bar/index" "ground-tab-bar": "/ground/components/ground-tab-bar/index",
"pagination": "/components/pagination/index"
} }
} }

163
src/ground/pages/pharmacist/index.ts

@ -2,37 +2,152 @@ const app = getApp<IAppOption>()
Page({ Page({
data: { data: {
names: [ // 搜索关键词
'张吉惟', keyword: '',
'林国瑞', // 时间筛选
'林玟书', startDate: '',
'林雅南', endDate: '',
'江奕云',
'刘柏宏', // 药师列表
'阮建安', pharmacistList: [] as any[],
'林子帆', totalCount: 0,
'夏志豪',
'吉茹定', // 分页
'李中冰', page: 1,
'黄文隆', pageSize: 20,
'谢彦文', loading: false,
'傅智翔', hasMore: true,
'洪振霞', pagination: {
'刘姿婷', count: 0,
'荣姿康', page: 0,
'吕致盈', pages: 0,
'方一强', },
],
}, },
onLoad() { onLoad() {
// 地推端药师页面,仅允许地推人员访问 // 地推端药师页面,仅允许地推人员访问
app.waitLogin({ types: [3] }).then(() => { app.waitLogin({ types: [3] }).then(() => {
// 页面加载完成 this.getPharmacistList()
})
},
// 获取药师列表
getPharmacistList() {
if (this.data.loading || !this.data.hasMore) return
this.setData({ loading: true })
wx.ajax({
method: 'GET',
url: '/app/promoter/promoter/pharmacist-list',
data: {
keyword: this.data.keyword,
startDate: this.data.startDate,
endDate: this.data.endDate,
page: this.data.page,
pageSize: this.data.pageSize,
},
}).then((res: any) => {
const list = res.list || []
const currentPage = this.data.page
const total = res.total || 0
const pages = Math.ceil(total / this.data.pageSize)
this.setData({
pharmacistList: [...this.data.pharmacistList, ...list],
totalCount: total,
page: currentPage + 1,
hasMore: currentPage < pages,
loading: false,
pagination: {
count: total,
page: currentPage,
pages,
},
})
}).catch(() => {
this.setData({ loading: false })
})
},
// 搜索药师
handleSearch(e: WechatMiniprogram.CustomEvent) {
this.setData({
keyword: e.detail.value,
page: 1,
pharmacistList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
})
this.getPharmacistList()
},
// 开始时间选择
handleStartDateChange(e: WechatMiniprogram.CustomEvent) {
const startDate = e.detail.value
const { endDate } = this.data
// 如果已选择结束时间,验证开始时间不能大于结束时间
if (endDate && startDate > endDate) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
this.setData({
startDate,
page: 1,
pharmacistList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
}) })
this.getPharmacistList()
},
// 结束时间选择
handleEndDateChange(e: WechatMiniprogram.CustomEvent) {
const endDate = e.detail.value
const { startDate } = this.data
// 如果已选择开始时间,验证结束时间不能小于开始时间
if (startDate && endDate < startDate) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({
endDate,
page: 1,
pharmacistList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
})
this.getPharmacistList()
},
// 页面上拉触底事件
onReachBottom() {
if (this.data.loading || !this.data.hasMore) {
return
}
this.getPharmacistList()
}, },
handleInfo() { // 查看详情
handleInfo(e: WechatMiniprogram.CustomEvent) {
const { id } = e.currentTarget.dataset
wx.navigateTo({ wx.navigateTo({
url: '/ground/pages/stat/index', url: `/ground/pages/stat/index?id=${id}`,
}) })
}, },
}) })

30
src/ground/pages/pharmacist/index.wxml

@ -22,12 +22,12 @@
<view class="range"> <view class="range">
<view class="label">时间筛选:</view> <view class="label">时间筛选:</view>
<view class="r-wrap"> <view class="r-wrap">
<picker class="picker" mode="date"> <picker class="picker" mode="date" bindchange="handleStartDateChange" end="{{endDate || ''}}">
<view class="date">开始时间</view> <view class="date">{{startDate || '开始时间'}}</view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date"> <picker class="picker" mode="date" bindchange="handleEndDateChange" start="{{startDate || ''}}">
<view class="date">开始时间</view> <view class="date">{{endDate || '结束时间'}}</view>
</picker> </picker>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view> </view>
@ -36,21 +36,21 @@
<view class="page-container"> <view class="page-container">
<view class="total"> <view class="total">
已邀约总人数: 已邀约总人数:
<text class="num">28</text> <text class="num">{{totalCount}}</text>
</view> </view>
<!-- 药师信息卡片 --> <!-- 药师信息卡片 -->
<view class="pharmacist-card" wx:for="{{10}}" wx:key="index"> <view class="pharmacist-card" wx:for="{{pharmacistList}}" wx:key="id" data-id="{{item.id}}" bind:tap="handleInfo">
<!-- 药师基本信息 --> <!-- 药师基本信息 -->
<view class="user"> <view class="user">
<image class="avatar" src="{{imageUrl}}cache/a2.png?t={{Timestamp}}"></image> <image class="avatar" src="{{imageUrl}}cache/a2.png?t={{Timestamp}}"></image>
<view class="wrap"> <view class="wrap">
<view class="pharmacist-info"> <view class="pharmacist-info">
<text class="pharmacist-name">{{names[index]}}</text> <text class="pharmacist-name">{{item.name}}</text>
<text class="pharmacist-phone">13800138000</text> <text class="pharmacist-phone">{{item.phone}}</text>
</view> </view>
<view class="pharmacy-info"> <view class="pharmacy-info">
<text class="pharmacy-name">康泰大药房(人民路店)</text> <text class="pharmacy-name">{{item.pharmacyName}}</text>
</view> </view>
</view> </view>
</view> </view>
@ -58,21 +58,21 @@
<view class="data-stats"> <view class="data-stats">
<view class="stat-item"> <view class="stat-item">
<view class="stat-value"> <view class="stat-value">
<text class="stat-number">750</text> <text class="stat-number">{{item.invitePatientCount || 0}}</text>
<text class="stat-unit">人</text> <text class="stat-unit">人</text>
</view> </view>
<text class="stat-label">邀约患者总数</text> <text class="stat-label">邀约患者总数</text>
</view> </view>
<view class="stat-item"> <view class="stat-item">
<view class="stat-value"> <view class="stat-value">
<text class="stat-number">700</text> <text class="stat-number">{{item.jumpPatientCount || 0}}</text>
<text class="stat-unit">人</text> <text class="stat-unit">人</text>
</view> </view>
<text class="stat-label">跳转患者数</text> <text class="stat-label">跳转患者数</text>
</view> </view>
<view class="stat-item"> <view class="stat-item">
<view class="stat-value"> <view class="stat-value">
<text class="stat-number">700</text> <text class="stat-number">{{item.enrollPatientCount || 0}}</text>
<text class="stat-unit">人</text> <text class="stat-unit">人</text>
</view> </view>
<text class="stat-label">入组患者数</text> <text class="stat-label">入组患者数</text>
@ -80,10 +80,12 @@
</view> </view>
<view class="card-footer"> <view class="card-footer">
<text class="bind-time">绑定时间:2026-01-10</text> <text class="bind-time">绑定时间:{{item.bindTime}}</text>
<view class="detail-btn" bind:tap="handleInfo">数据详情</view> <view class="detail-btn">数据详情</view>
</view> </view>
</view> </view>
<pagination pagination="{{pagination}}" wx:if="{{pharmacistList.length > 0}}" />
<view class="empty" wx:if="{{!loading && pharmacistList.length === 0}}">暂无数据</view>
</view> </view>
</view> </view>

144
src/ground/pages/stat/index.ts

@ -1,12 +1,150 @@
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {
onLoad() { // 药师ID
pharmacistId: '',
// 时间筛选
startDate: '',
endDate: '',
type: 'day', // day-按日,month-按月
// 统计数据
invitePatientCount: 0,
jumpPatientCount: 0,
enrollPatientCount: 0,
indicationStats: [] as any[],
// 图表数据
chartList: [] as any[],
// 加载状态
loading: false,
},
onLoad(option: { id?: string }) {
// 地推端统计页面,仅允许地推人员访问 // 地推端统计页面,仅允许地推人员访问
app.waitLogin({ types: [3] }).then(() => { app.waitLogin({ types: [3] }).then(() => {
// 页面加载完成 // 获取药师ID
const pharmacistId = option.id || ''
if (!pharmacistId) {
wx.showToast({
title: '缺少药师ID',
icon: 'none',
})
return
}
this.setData({ pharmacistId })
// 设置默认时间范围(最近30天)
this.setDefaultDateRange()
// 获取统计数据
this.getStatistics()
this.getChartData()
})
},
// 设置默认时间范围(2026年3月至今)
setDefaultDateRange() {
const endDate = new Date()
const startDate = new Date('2026-03-01')
this.setData({
startDate: this.formatDate(startDate),
endDate: this.formatDate(endDate),
})
},
// 格式化日期
formatDate(date: Date): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
// 获取统计数据
getStatistics() {
const { pharmacistId } = this.data
if (!pharmacistId) return
wx.ajax({
method: 'GET',
url: '/app/promoter/promoter/pharmacist-statistics',
data: {
pharmacistId,
},
}).then((res: any) => {
this.setData({
invitePatientCount: res.invitePatientCount || 0,
jumpPatientCount: res.jumpPatientCount || 0,
enrollPatientCount: res.enrollPatientCount || 0,
indicationStats: res.indicationStats || [],
}) })
})
},
// 获取图表数据
getChartData() {
const { pharmacistId, startDate, endDate, type } = this.data
if (!pharmacistId || !startDate || !endDate) return
this.setData({ loading: true })
wx.ajax({
method: 'GET',
url: '/app/promoter/promoter/pharmacist-chart',
data: {
pharmacistId,
startDate,
endDate,
type,
},
}).then((res: any) => {
this.setData({
chartList: res.list || res || [],
loading: false,
})
}).catch(() => {
this.setData({ loading: false })
})
},
// 开始时间选择
handleStartDateChange(e: WechatMiniprogram.CustomEvent) {
const startDate = e.detail.value
const { endDate } = this.data
// 如果已选择结束时间,验证开始时间不能大于结束时间
if (endDate && startDate > endDate) {
wx.showToast({
title: '开始时间不能大于结束时间',
icon: 'none',
})
return
}
this.setData({ startDate })
this.getChartData()
},
// 结束时间选择
handleEndDateChange(e: WechatMiniprogram.CustomEvent) {
const endDate = e.detail.value
const { startDate } = this.data
// 如果已选择开始时间,验证结束时间不能小于开始时间
if (startDate && endDate < startDate) {
wx.showToast({
title: '结束时间不能小于开始时间',
icon: 'none',
})
return
}
this.setData({ endDate })
this.getChartData()
},
// 切换统计类型
handleTypeChange(e: WechatMiniprogram.CustomEvent) {
const type = e.currentTarget.dataset.type
this.setData({ type })
this.getChartData()
}, },
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()

147
src/ground/pages/stat/index.wxml

@ -8,16 +8,16 @@
<view class="page" style="padding-top: {{pageTop}}px;"> <view class="page" style="padding-top: {{pageTop}}px;">
<view class="page-header" style="top:{{pageTop}}px"> <view class="page-header" style="top:{{pageTop}}px">
<view class="chart-range"> <view class="chart-range">
<picker class="picker" mode="date" end="{{end}}"> <picker class="picker" mode="date" end="{{endDate}}" bindchange="handleStartDateChange">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="line"></view> <view class="line"></view>
<picker class="picker" mode="date" start="{{satrt}}"> <picker class="picker" mode="date" start="{{startDate}}" bindchange="handleEndDateChange">
<view class="p-content"> <view class="p-content">
<view class="content">2025/02/26</view> <view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
@ -26,174 +26,73 @@
<view class="stat-card"> <view class="stat-card">
<view class="col"> <view class="col">
<view class="num"> <view class="num">
1893 {{invitePatientCount}}
<text class="sub">人</text> <text class="sub">人</text>
</view> </view>
<view class="title">邀约患者总数</view> <view class="title">邀约患者总数</view>
</view> </view>
<view class="col"> <view class="col">
<view class="num"> <view class="num">
1893 {{jumpPatientCount}}
<text class="sub">人</text> <text class="sub">人</text>
</view> </view>
<view class="title">跳转患者数</view> <view class="title">跳转患者数</view>
</view> </view>
<view class="col"> <view class="col">
<view class="num"> <view class="num">
1893 {{enrollPatientCount}}
<text class="sub">人</text> <text class="sub">人</text>
</view> </view>
<view class="title">跳转患者数</view> <view class="title">入组患者数</view>
</view> </view>
</view> </view>
<view class="stat-list"> <view class="stat-list">
<view class="module"> <view class="loading" wx:if="{{loading}}">加载中...</view>
<view class="module" wx:for="{{chartList}}" wx:key="date">
<view class="aside"> <view class="aside">
<view class="line-top"></view> <view class="line-top"></view>
<view class="dot"></view> <view class="dot"></view>
<view class="line-bottom"></view> <view class="line-bottom"></view>
</view> </view>
<view class="m-container"> <view class="m-container">
<view class="date">2025/02/26</view> <view class="date">{{item.date}}</view>
<view class="card"> <view class="card">
<view class="row2"> <view class="row2">
<view class="col"> <view class="col">
<view class="name">邀约患者数</view> <view class="name">邀约患者数</view>
<view class="num">750</view> <view class="num">{{item.inviteCount || 0}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">跳转患者数</view> <view class="name">跳转患者数</view>
<view class="num">750</view> <view class="num">{{item.jumpCount || 0}}</view>
</view> </view>
<view class="line"></view> <view class="line"></view>
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">入组患者数</view>
<view class="num">750</view> <view class="num">{{item.enrollCount || 0}}</view>
</view> </view>
</view> </view>
<view class="row3"> <!-- 适应症统计 -->
<view class="row3" wx:for="{{item.indicationStats}}" wx:key="indicationName" wx:for-item="indItem">
<view class="col"> <view class="col">
<view class="name">入组患者数</view> <view class="name">{{indItem.indicationName}}</view>
<view class="num">300</view> <view class="num">{{indItem.inviteCount || 0}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">斑块状银屑病</view> <view class="name">{{indItem.indicationName}}</view>
<view class="num">280</view> <view class="num">{{indItem.jumpCount || 0}}</view>
</view> </view>
<view class="col"> <view class="col">
<view class="name">斑块状银屑病</view> <view class="name">{{indItem.indicationName}}</view>
<view class="num">280</view> <view class="num">{{indItem.enrollCount || 0}}</view>
<view class="line"></view> <view class="line"></view>
</view> </view>
</view> </view>
<view class="row3">
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">300</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view>
</view>
</view>
</view>
<view class="module">
<view class="aside">
<view class="line-top"></view>
<view class="dot"></view>
<view class="line-bottom"></view>
</view>
<view class="m-container">
<view class="date">2025/02/25</view>
<view class="card">
<view class="row2">
<view class="col">
<view class="name">邀约患者数</view>
<view class="num">750</view>
</view>
<view class="line"></view>
<view class="col">
<view class="name">跳转患者数</view>
<view class="num">750</view>
</view>
<view class="line"></view>
<view class="col">
<view class="name">入组患者数</view>
<view class="num">750</view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">入组患者数</view>
<view class="num">300</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">300</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">溃疡性结肠炎</view>
<view class="num">280</view>
<view class="line"></view>
</view>
</view>
<view class="row3">
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">300</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
<view class="col">
<view class="name">克罗恩病</view>
<view class="num">280</view>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<view class="empty" wx:if="{{!loading && chartList.length === 0}}">暂无数据</view>
</view> </view>
</view> </view>

113
src/pages/index/index.ts

@ -11,17 +11,35 @@ Page({
options: [] as Array<{ indicationId: number; indicationName: string; active: boolean }>, options: [] as Array<{ indicationId: number; indicationName: string; active: boolean }>,
agreementChecked: true, agreementChecked: true,
projectId: 0, projectId: '',
projectName: '', projectName: '',
// 扫码获取的药师ID
pharmacistId: '',
isLogin: 0, isLogin: 0,
isPatient: 0, isPatient: 0,
// 是否已有项目
hasProject: false,
// 选中的适应症名称
selectedIndicationName: '',
bannerList: [] as any[], bannerList: [] as any[],
}, },
onLoad() { onLoad() {
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
// 从 globalData 初始化扫码参数
const pharmacistId = app.globalData.pharmacistId || ''
const projectId = app.globalData.projectId || '0'
this.setData({
pharmacistId,
projectId,
})
app.waitLogin({ types: [1, 2] }).then(() => { app.waitLogin({ types: [1, 2] }).then(() => {
this.getBanner() this.getBanner()
this.checkStatus() this.checkStatus()
@ -43,28 +61,34 @@ Page({
checkStatus() { checkStatus() {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
url: '/app/patient/patient/check-status', url: '/app/patient/patient/recent-project',
}).then((res: any) => { })
.then((res: any) => {
if (res && res.projectId) {
// 已有项目,显示已参加项目状态
this.setData({ this.setData({
isLogin: res.isLogin, isLogin: 1,
isPatient: res.isPatient, isPatient: 1,
projectId: res.projectId, projectId: res.projectId,
projectName: res.projectName, projectName: res.projectName,
hasProject: true,
selectedIndicationName: res.indicationName,
}) })
if (res.isLogin === 1 && res.isPatient === 1 && res.projectId) { } else {
// 没有项目,获取项目列表供选择
this.setData({ this.setData({
options: [ hasProject: false,
{
indicationId: res.indicationId,
indicationName: res.indicationName,
active: true,
},
],
}) })
} else {
this.getProjectInfo() this.getProjectInfo()
} }
}) })
.catch(() => {
// 接口失败,获取项目列表
this.setData({
hasProject: false,
})
this.getProjectInfo()
})
}, },
getProjectInfo() { getProjectInfo() {
@ -135,20 +159,65 @@ Page({
data: { data: {
code: loginRes.code, code: loginRes.code,
}, },
}).then(() => { })
wx.ajax({ .then((loginRes: any) => {
// 解密手机号
return wx.ajax({
method: 'POST',
url: '/app/common/common/wx-phone',
data: {
sessionKey: loginRes.sessionKey,
encryptedData,
iv,
},
})
})
.then(() => {
// 选择适应症
return wx.ajax({
method: 'POST', method: 'POST',
url: '/app/patient/patient/select-indication', url: '/app/patient/patient/select-indication',
data: { data: {
projectId: this.data.projectId, projectId: this.data.projectId,
indicationId: selectedOption.indicationId, indicationId: selectedOption.indicationId,
}, },
}).then((res: any) => { })
})
.then((res: any) => {
const jumpUrl = res.jumpUrl
// 如果有药师ID,创建绑定关系
if (this.data.pharmacistId) {
wx.ajax({
method: 'POST',
url: '/app/patient/patient/bind-pharmacist',
data: {
pharmacistId: this.data.pharmacistId,
projectId: this.data.projectId,
indicationId: selectedOption.indicationId,
},
})
.then(() => {
// 绑定成功后跳转
wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12',
path: jumpUrl,
})
})
.catch(() => {
// 绑定失败也跳转
wx.navigateToMiniProgram({ wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12', appId: 'wx05551c5ee1fd1c12',
path: res.jumpUrl, path: jumpUrl,
})
}) })
} else {
// 没有药师ID,直接跳转
wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12',
path: jumpUrl,
}) })
}
}) })
}, },
}) })
@ -167,6 +236,14 @@ Page({
}) })
}, },
handleJump() {
// 跳转到腾讯药箱小程序
wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12',
path: '',
})
},
onShareAppMessage() { onShareAppMessage() {
return { return {
title: '华观健康', title: '华观健康',

14
src/pages/index/index.wxml

@ -12,7 +12,7 @@
</swiper-item> </swiper-item>
</swiper> </swiper>
<view class="blur"></view> <view class="blur"></view>
<block wx:if="{{true}}"> <block wx:if="{{!hasProject}}">
<view class="prompt-title"> <view class="prompt-title">
请确认您是特诺雅 请确认您是特诺雅
<text style="font-size: 0.5em; vertical-align: super">®</text> <text style="font-size: 0.5em; vertical-align: super">®</text>
@ -22,14 +22,14 @@
<view class="options"> <view class="options">
<button <button
wx:for="{{options}}" wx:for="{{options}}"
wx:key="id" wx:key="indicationId"
class="option-item {{item.active ? 'active' : ''}}" class="option-item {{item.active ? 'active' : ''}}"
bindtap="selectOption" bindtap="selectOption"
data-id="{{item.id}}" data-id="{{item.indicationId}}"
open-type="getPhoneNumber" open-type="getPhoneNumber"
bindgetphonenumber="handleWxSubmit" bindgetphonenumber="handleWxSubmit"
> >
<view class="option-text">{{item.name}}</view> <view class="option-text">{{item.indicationName}}</view>
<view class="option-select"> <view class="option-select">
选择 选择
<van-icon name="arrow" /> <van-icon name="arrow" />
@ -37,7 +37,7 @@
</button> </button>
</view> </view>
<view class="agreement" bindtap="toggleAgreement"> <view class="agreement" bindtap="toggleAgreement">
<radio class="radio" checked color="#00B2A9"></radio> <radio class="radio" checked="{{agreementChecked}}" color="#00B2A9"></radio>
<view class="agreement-text"> <view class="agreement-text">
同意 同意
<text class="link">《用户与隐私保护协议》</text> <text class="link">《用户与隐私保护协议》</text>
@ -47,8 +47,8 @@
<view class="complete" wx:else> <view class="complete" wx:else>
<image class="bg" src="{{imageUrl}}bg10.png?t={{Timestamp}}"></image> <image class="bg" src="{{imageUrl}}bg10.png?t={{Timestamp}}"></image>
<view class="title">您已参加此项目</view> <view class="title">您已参加此项目</view>
<view class="content">您选择适应症:斑块状银屑病</view> <view class="content">您选择适应症:{{selectedIndicationName}}</view>
<view class="jump" bind:tap="handleWxSubmit">去腾讯药箱看看</view> <view class="jump" bind:tap="handleJump">去腾讯药箱看看</view>
</view> </view>
<view class="work" bind:tap="handleWork"> <view class="work" bind:tap="handleWork">
我是工作人员 我是工作人员

29
src/pages/start/index.scss

@ -1,29 +1,4 @@
page { .page{
background-color: #f8fafa;
}
.page-title {
font-weight: 900;
font-size: 44rpx;
line-height: 52rpx;
color: #00b2a9;
}
.page {
.page-banner {
width: 100vw; width: 100vw;
height: 1108rpx; height: 100vh;
display: block;
}
.page-options {
margin-top: 68rpx;
display: flex;
align-items: center;
.o-item {
flex: 1;
border-right: 1px solid rgba(148, 163, 163, 0.29);
text-align: center;
font-size: 32rpx;
color: #94a3a3;
}
}
} }

68
src/pages/start/index.ts

@ -4,20 +4,66 @@ const app = getApp<IAppOption>()
Page({ Page({
data: {}, data: {},
onLoad(options) { onLoad(options) {
// 解析扫码参数
let pharmacistId = ''
let projectId = ''
if (options.scene) { if (options.scene) {
const { doctorId } = parseScene(options.scene) as { doctorId: string } const sceneData = parseScene(options.scene) as { pharmacistId?: string; projectId?: string }
app.globalData.waitBindDoctorId = doctorId pharmacistId = sceneData.pharmacistId || ''
projectId = sceneData.projectId || ''
}
// 保存到全局数据
if (pharmacistId) {
app.globalData.pharmacistId = pharmacistId
}
if (projectId) {
app.globalData.projectId = projectId
}
app.waitLogin().then(() => {
const { isLogin, loginIdentity } = app.globalData.initLoginInfo
// 游客(1)或患者端(2)且有扫码参数,进入患者首页
if ((loginIdentity === 1 || loginIdentity === 2) && (pharmacistId && projectId)) {
wx.reLaunch({
url: '/pages/index/index',
})
return
}
// 游客(1)或患者端(2)无扫码参数,进入游客页面
if (loginIdentity === 1 || loginIdentity === 2) {
wx.reLaunch({
url: '/pages/tourists/index',
})
return
}
// 未注册,根据身份跳转到对应注册页面
if (!isLogin) {
const regPageUrl = {
2: '/pages/index/index',
3: '/ground/pages/login/index',
4: '/doctor/pages/login/index',
}[loginIdentity as 2 | 3 | 4]
wx.reLaunch({
url: regPageUrl || '/pages/index/index',
})
return
} }
// app.waitLogin().then(() => { // 已注册,根据身份跳转到对应首页
// const initLoginInfo = app.globalData.initLoginInfo || {} const homePageUrl = {
// wx.reLaunch({ 2: '/pages/index/index',
// url: '/pages/index/index', 3: '/ground/pages/home/index',
// }) 4: '/doctor/pages/home/index',
// }) }[loginIdentity as 2 | 3 | 4]
// wx.reLaunch({ wx.reLaunch({
// url: '/pages/index/index', url: homePageUrl || '/pages/index/index',
// }) })
})
}, },
}) })

12
src/pages/start/index.wxml

@ -1,11 +1 @@
<navbar fixed custom-style="background:transparent" back> <image src="{{imageUrl}}start1.png?t={{Timestamp}}" mode="aspectFill" class="page"></image>
<view slot="left" class="page-title">华观健康</view>
</navbar>
<view class="page" style="background: url('/images/bg11.png') no-repeat top center/100% 1624rpx;padding-top: {{pageTop+24}}px;">
<image class="page-banner" src="/images/start1.png"></image>
<view class="page-options">
<view class="o-item">我是药店工作人员</view>
<view class="o-item">我是地推人员</view>
</view>
</view>

7
src/pages/tourists/index.json

@ -0,0 +1,7 @@
{
"navigationStyle": "custom",
"usingComponents": {
"popup": "/components/popup/index",
"navbar": "/components/navbar/index"
}
}

29
src/pages/tourists/index.scss

@ -0,0 +1,29 @@
page {
background-color: #f8fafa;
}
.page-title {
font-weight: 900;
font-size: 44rpx;
line-height: 52rpx;
color: #00b2a9;
}
.page {
.page-banner {
width: 100vw;
height: 1108rpx;
display: block;
}
.page-options {
margin-top: 68rpx;
display: flex;
align-items: center;
.o-item {
flex: 1;
border-right: 1px solid rgba(148, 163, 163, 0.29);
text-align: center;
font-size: 32rpx;
color: #94a3a3;
}
}
}

24
src/pages/tourists/index.ts

@ -0,0 +1,24 @@
import { parseScene } from '@/utils/util'
const app = getApp<IAppOption>()
Page({
data: {},
onLoad(options) {
if (options.scene) {
const { doctorId } = parseScene(options.scene) as { doctorId: string }
app.globalData.waitBindDoctorId = doctorId
}
// app.waitLogin().then(() => {
// const initLoginInfo = app.globalData.initLoginInfo || {}
// wx.reLaunch({
// url: '/pages/index/index',
// })
// })
// wx.reLaunch({
// url: '/pages/index/index',
// })
},
})
export {}

11
src/pages/tourists/index.wxml

@ -0,0 +1,11 @@
<navbar fixed custom-style="background:transparent" back>
<view slot="left" class="page-title">华观健康</view>
</navbar>
<view class="page" style="background: url('/images/bg11.png') no-repeat top center/100% 1624rpx;padding-top: {{pageTop+24}}px;">
<image class="page-banner" src="/images/start1.png"></image>
<view class="page-options">
<view class="o-item">我是药店工作人员</view>
<view class="o-item">我是地推人员</view>
</view>
</view>

15
typings/index.d.ts vendored

@ -34,13 +34,25 @@ interface IAppOption {
Timestamp: number Timestamp: number
waitBindDoctorId: string waitBindDoctorId: string
loginState: string
// 扫码参数
pharmacistId?: string
projectId?: string
initLoginInfo: Partial<{ initLoginInfo: Partial<{
isLogin: 0 | 1 isLogin: 0 | 1
isReg: 0 | 1 isReg: 0 | 1
isRegistered: 0 | 1
// 1-游客,2-患者端,3-地推人员端,4-药店人员端 // 1-游客,2-患者端,3-地推人员端,4-药店人员端
loginType: 1 | 2 | 3 | 4 loginType: 1 | 2 | 3 | 4
loginIdentity: 1 | 2 | 3 | 4
loginIdentityId: number
guestPrivacyAgree: boolean guestPrivacyAgree: boolean
token: string
sessionKey: string
openid: string
unionid: string
userId: number
}> }>
[propName: string]: any [propName: string]: any
@ -66,6 +78,7 @@ interface IAgaxParams extends WechatMiniprogram.RequestOption {
loading?: boolean loading?: boolean
loadingText?: string loadingText?: string
isJSON?: boolean isJSON?: boolean
needToken?: boolean // 是否需要携带 token,默认 true
} }
declare namespace WechatMiniprogram { declare namespace WechatMiniprogram {

Loading…
Cancel
Save