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 { @@ -2,6 +2,12 @@ interface IGlobalParams {
gUrl: string
}
// 获取 token
const getToken = (): string => {
const app = getApp<IAppOption>()
return app.globalData.initLoginInfo?.token || ''
}
export const request = function (
{ gUrl }: IGlobalParams,
{
@ -13,6 +19,7 @@ export const request = function ( @@ -13,6 +19,7 @@ export const request = function (
loading = false,
loadingText = '加载中...',
isJSON = false,
needToken = true, // 默认需要 token
...options
}: IAgaxParams,
): Promise<any> {
@ -23,46 +30,77 @@ export const request = function ( @@ -23,46 +30,77 @@ export const request = function (
mask: true,
})
}
wx.request({
header: {
loginState: getApp().globalData.loginState,
// 构建请求头
const requestHeader: any = {
'content-type': isJSON ? 'application/json' : 'application/x-www-form-urlencoded',
...header,
},
}
// 添加 token(如果需要且存在)
if (needToken) {
const token = getToken()
if (token) {
requestHeader.Authorization = `${token}`
}
}
wx.request({
header: requestHeader,
url: gUrl + url,
method,
data: {
loginState: getApp().globalData.loginState,
...(data as object),
},
...options,
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) {
resolve(data)
} else if (code === 0) {
resolve(data)
} else if (showMsg) {
const msg = errPicker(res.data)
const message = msg || errPicker(res.data)
if (loading) {
setTimeout(() => {
wx.showToast({
title: msg,
title: message,
icon: 'none',
})
}, 30)
} else {
wx.showToast({
title: msg,
title: message,
icon: 'none',
})
reject(res)
}
reject(res.data)
} else {
reject(res)
reject(res.data)
}
},
fail(err) {
if (showMsg) {
wx.showToast({
title: '网络请求失败',
icon: 'none',
})
}
reject(err)
},
complete() {
@ -74,9 +112,9 @@ export const request = function ( @@ -74,9 +112,9 @@ export const request = function (
})
}
function errPicker(err) {
function errPicker(err: any): string {
if (typeof err === 'string') {
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 @@ @@ -1,6 +1,6 @@
{
"$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": [
{
"root": "ground",

20
src/app.ts

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

51
src/components/pickerArea/index.ts

@ -230,15 +230,53 @@ Component({ @@ -230,15 +230,53 @@ Component({
getArea() {
wx.ajax({
method: 'GET',
url: '/js/area.json',
isJSON: true,
}).then((res) => {
url: '/app/common/common/area-list',
}).then((res: any[]) => {
// 将扁平数据转换为树形结构
const areaTree = this.buildAreaTree(res)
this.setData({
area: res,
area: areaTree,
})
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) {
const { code, name } = e.currentTarget.dataset
this.setData({
@ -281,9 +319,10 @@ Component({ @@ -281,9 +319,10 @@ Component({
getRangeList() {
const { area, ProvinceId } = this.data
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({
range,
range: province.children,
active: 1,
scrollIntoView0: '',
scrollIntoView1: `id${this.data.CityId}`,

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

@ -1,13 +1,106 @@ @@ -1,13 +1,106 @@
const app = getApp<IAppOption>()
Page({
data: {},
onLoad() {
data: {
// 文章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(() => {
// 页面加载完成
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() {
wx.navigateBack()
},

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

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

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

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
"usingComponents": {
"popup": "/components/popup/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>() @@ -2,37 +2,169 @@ const app = getApp<IAppOption>()
Page({
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() {
// 药店端文章列表页面,仅允许药店人员访问
// 药店端教育页面,仅允许药店人员访问
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({
url: '/doctor/pages/article/index',
url: `/doctor/pages/article/index?id=${id}`,
})
},
})

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

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

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

@ -1,13 +1,62 @@ @@ -1,13 +1,62 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
// 新姓名
name: '',
},
onLoad() {
// 药店端修改昵称页面,仅允许药店人员访问
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() {
wx.navigateBack()
},

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

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

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

@ -5,18 +5,140 @@ Page({ @@ -5,18 +5,140 @@ Page({
mobile: '',
code: '',
codeText: '发送验证码',
counting: false,
countdown: 60,
},
timer: null as any,
onLoad() {
// 药店端修改手机号页面,仅允许药店人员访问
app.waitLogin({ types: [4] }).then(() => {
// 页面加载完成
})
},
onUnload() {
// 清除定时器
if (this.timer) {
clearInterval(this.timer)
}
},
// 获取验证码
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() {
// 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() {
wx.navigateBack()

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

@ -18,6 +18,15 @@ Page({ @@ -18,6 +18,15 @@ Page({
jumpPatientCount: 0,
enrollPatientCount: 0,
// 适应症统计
indicationStats: [] as Array<{
indicationId: number
indicationName: string
invitePatientCount: number
jumpPatientCount: number
enrollPatientCount: number
}>,
// 图表数据
chartData: [] as Array<{ date: string, count: number }>,
@ -62,13 +71,20 @@ Page({ @@ -62,13 +71,20 @@ Page({
getUserInfo() {
wx.ajax({
method: 'GET',
url: '/app/pharmacist/pharmacist/info',
url: '/app/pharmacist/pharmacist/profile',
}).then((res: any) => {
this.setData({
pharmacistName: res.name,
pharmacistAvatar: res.avatar,
pharmacyName: res.pharmacyName,
})
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
pharmacistName: '李药师',
pharmacistAvatar: '',
pharmacyName: '康泰大药房(人民路店)',
})
})
},
@ -81,6 +97,11 @@ Page({ @@ -81,6 +97,11 @@ Page({
this.setData({
pendingCount: res.count || 0,
})
}).catch(() => {
// 接口失败时使用模拟数据
this.setData({
pendingCount: 12,
})
})
},
@ -91,9 +112,22 @@ Page({ @@ -91,9 +112,22 @@ Page({
url: '/app/pharmacist/pharmacist/statistics',
}).then((res: any) => {
this.setData({
invitePatientCount: res.invitePatientCount,
jumpPatientCount: res.jumpPatientCount,
enrollPatientCount: res.enrollPatientCount,
invitePatientCount: res.invitePatientCount || 0,
jumpPatientCount: res.jumpPatientCount || 0,
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({ @@ -109,12 +143,42 @@ Page({
endDate: this.data.endDate,
},
}).then((res: any) => {
const list = res.list || []
this.setData({
chartData: list,
})
this.initChartBar(list)
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
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) {
@ -128,8 +192,26 @@ Page({ @@ -128,8 +192,26 @@ Page({
// 日期选择变化
onDateChange(e: WechatMiniprogram.CustomEvent) {
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({
[field]: e.detail.value,
[field]: value,
})
// 重新加载图表数据
this.getPatientChart()

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

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

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

@ -1,11 +1,107 @@ @@ -1,11 +1,107 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
// 药店人员信息
pharmacistName: '',
pharmacistAvatar: '',
pharmacistId: '',
// 小程序码
qrCodeUrl: '',
// 项目信息
projectId: '',
projectName: '',
},
onLoad() {
// 药店端邀约页面,仅允许药店人员访问
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() {

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

@ -9,26 +9,37 @@ @@ -9,26 +9,37 @@
class="page"
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">
<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="nickname">刘平安</view>
<view class="nickname">{{pharmacistName || '药师'}}</view>
<view class="label">药师</view>
</view>
</view>
<view class="title">邀请您加入健康管理项目</view>
<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>
<view class="code-wrap">
<image class="code" src="{{imageUrl}}cache/c1.png?t={{Timestamp}}" show-menu-by-longpress></image>
<view class="code-wrap" bind:tap="saveQrCode">
<image class="code" src="{{qrCodeUrl}}" show-menu-by-longpress></image>
</view>
<view class="tip">
<view class="dot"></view>
扫码立即加入
<view class="dot"></view>
</view>
<view class="save-tip" wx:if="{{qrCodeUrl}}">点击二维码保存到相册</view>
</view>
</view>

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

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

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

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
"van-icon": "@vant/weapp/icon/index",
"navbar": "/components/navbar/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 { @@ -141,6 +141,7 @@ page {
max-height: 50vh;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
.l-item {
padding: 32rpx 0;
.name {

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

@ -1,174 +1,256 @@ @@ -1,174 +1,256 @@
const app = getApp<IAppOption>()
let timer = null as null | number
Page({
data: {
mobile: '',
code: '',
codeText: '发送验证码',
back: false,
showForm: false,
check: false,
// 扫码参数
promoterId: '',
projectId: '',
// 表单数据
name: '',
pharmacyId: '',
pharmacyName: '',
// 药店选择弹窗
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() {
if (timer) return
const mobile = this.data.mobile
if (!mobile) {
onLoad(option: { promoterId?: string; projectId?: string; scene?: string }) {
// 解析扫码参数
let promoterId = option.promoterId || ''
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({
title: '手机号不能为空',
title: '缺少必要参数',
icon: 'none',
})
return
}
// 验证手机号
if (!/^1[3-9]\d{9}$/.test(mobile)) {
wx.showToast({
title: '手机号格式不正确',
icon: 'none',
this.setData({
promoterId,
projectId,
})
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({
method: 'POST',
url: '/app/common/common/send-code',
method: 'GET',
url: '/app/common/common/pharmacy-list',
data: {
phone: mobile,
type: 1,
keyword: this.data.keyword,
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
timer = setInterval(() => {
time--
}).catch(() => {
this.setData({ loading: false })
})
},
// 省市区选择变化
handleChange(e: WechatMiniprogram.CustomEvent) {
const [province, city, district] = e.detail
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) {
clearInterval(timer as number)
timer = null
this.getPharmacyList()
},
// 搜索药店
handleSearch(e: WechatMiniprogram.CustomEvent) {
this.setData({
codeText: '发送验证码',
keyword: e.detail.value,
page: 1,
pharmacyList: [],
hasMore: true,
pagination: {
count: 0,
page: 0,
pages: 0,
},
})
}
}, 1000)
this.getPharmacyList()
},
// 加载更多
loadMore() {
this.getPharmacyList()
},
// 选择药店
selectPharmacy(e: WechatMiniprogram.CustomEvent) {
const { id, name } = e.currentTarget.dataset
this.setData({
pharmacyId: id,
pharmacyName: name,
show: false,
})
},
// 提交绑定
handleSubmit() {
if (!this.data.mobile) {
wx.showToast({
icon: 'none',
title: '请输入手机号',
})
return
}
if (!this.data.code) {
const { promoterId, projectId, name, pharmacyId } = this.data
if (!name.trim()) {
wx.showToast({
title: '请输入姓名',
icon: 'none',
title: '请输入验证码',
})
return
}
if (!this.data.check) {
if (!pharmacyId) {
wx.showToast({
title: '请选择所属药店',
icon: 'none',
title: '请同意用户协议',
})
return
}
wx.showLoading({ title: '提交中...' })
wx.ajax({
method: 'POST',
url: '/app/pharmacist/pharmacist/sms-login',
url: '/app/pharmacist/pharmacist/bind-promoter',
data: {
phone: this.data.mobile,
code: this.data.code,
promoterId,
projectId,
name: name.trim(),
pharmacyId,
},
}).then((res: any) => {
app.globalData.loginState = res.token
wx.hideLoading()
// 保存登录信息
app.globalData.initLoginInfo = {
loginType: 4,
isLogin: 1,
isReg: 1,
...res,
}
this.submitCallback()
})
},
handleWxSubmit(e: WechatMiniprogram.CustomEvent) {
wx.redirectTo({
url: '/doctor/pages/home/index',
})
return
if (!this.data.check) {
wx.showToast({
icon: 'none',
title: '请同意用户协议',
title: '绑定成功',
icon: 'success',
})
return
}
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() {
// 跳转到药店端首页
setTimeout(() => {
wx.reLaunch({
url: '/doctor/pages/home/index',
})
},
handlePatient() {
wx.redirectTo({
url: '/pages/index/index',
})
},
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,
}, 1500)
}).catch(() => {
wx.hideLoading()
wx.showToast({
title: '绑定失败',
icon: 'none',
})
},
onClose() {
this.setData({
show: false,
})
},
})
export {}

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

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

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

@ -1,11 +1,143 @@ @@ -1,11 +1,143 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
// 药店人员信息
pharmacistInfo: {
id: 0,
name: '',
phone: '',
avatar: '',
pharmacyName: '',
},
// 邀约人信息
inviterInfo: {
promoterName: '',
promoterPhone: '',
},
},
onLoad() {
// 药店端我的页面,仅允许药店人员访问
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() {
@ -24,9 +156,20 @@ Page({ @@ -24,9 +156,20 @@ Page({
})
},
handleExit() {
wx.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除登录信息
app.globalData.initLoginInfo = null
app.globalData.loginState = ''
wx.redirectTo({
url: '/pages/work/index',
})
}
},
})
},
})

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

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

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

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
"usingComponents": {
"navbar": "../../../components/navbar/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>() @@ -2,37 +2,287 @@ const app = getApp<IAppOption>()
Page({
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() {
// 药店端患者列表页面,仅允许药店人员访问
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({
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 @@ @@ -12,7 +12,7 @@
<image class="icon" src="{{imageUrl}}icon1.png?t={{Timestamp}}"></image>
<input
type="text"
placeholder="搜索药店名/药师姓名"
placeholder="搜索患者姓名"
class="input"
placeholder-class="place-input"
confirm-type="search"
@ -20,188 +20,126 @@ @@ -20,188 +20,126 @@
/>
</view>
<view class="row">
<picker mode="region">
<picker bindchange="handleJumpStatusChange" value="{{jumpStatus}}" range="{{['全部', '已跳转']}}">
<view class="col">
<view class="label">跳转:</view>
<view class="content">全部</view>
<view class="content">{{jumpStatus === 0 ? '全部' : '已跳转'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view>
</picker>
<picker mode="region">
<picker bindchange="handleEnrollStatusChange" value="{{enrollStatus}}" range="{{['全部', '已入组']}}">
<view class="col">
<view class="label">入组:</view>
<view class="content">全部</view>
<view class="content">{{enrollStatus === 0 ? '全部' : '已入组'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view>
</picker>
</view>
<view class="range">
<view class="label">时间筛选:</view>
<picker class="picker" mode="date">
<view class="date">开始时间</view>
<picker
class="picker"
mode="date"
bindchange="{{timeType === 0 ? 'handleJumpStartTimeChange' : 'handleEnrollStartTimeChange'}}"
value="{{timeType === 0 ? jumpStartTime : enrollStartTime}}"
>
<view class="date">{{(timeType === 0 ? jumpStartTime : enrollStartTime) || '开始时间'}}</view>
</picker>
<view class="line"></view>
<picker class="picker" mode="date">
<view class="date">开始时间</view>
<picker
class="picker"
mode="date"
bindchange="{{timeType === 0 ? 'handleJumpEndTimeChange' : 'handleEnrollEndTimeChange'}}"
value="{{timeType === 0 ? jumpEndTime : enrollEndTime}}"
>
<view class="date">{{(timeType === 0 ? jumpEndTime : enrollEndTime) || '结束时间'}}</view>
</picker>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view>
<view class="row">
<picker mode="region">
<picker bindchange="handleTimeTypeChange" value="{{timeType}}" range="{{['跳转时间', '入组时间']}}">
<view class="col">
<view class="label">时间类型:</view>
<view class="content">全部</view>
<view class="content">{{timeType === 0 ? '跳转时间' : '入组时间'}}</view>
<image class="icon" src="{{imageUrl}}icon2.png?t={{Timestamp}}"></image>
</view>
</picker>
<view class="total">
已邀约总人数:
<text class="num">28人</text>
<text class="num">{{totalCount}}人</text>
</view>
</view>
</view>
<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">
<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="info">
<text class="name">张患者</text>
<text class="tel">13800138000</text>
<text class="name">{{item.patientName}}</text>
<text class="tel">{{item.phone}}</text>
</view>
<view class="site">康泰大药房(人民路店)</view>
<view class="date">绑定时间:2026/01/10 16:27:35</view>
<view class="site">{{item.pharmacyName}}</view>
<view class="date">绑定时间:{{item.bindTimeFormatted}}</view>
</view>
</view>
<view class="container">
<view class="c-item">
<!-- 跳转状态 -->
<view class="c-item {{item.jumpStatus === 1 ? 'active' : ''}}">
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">未跳转</view>
<view class="name">{{item.jumpStatus === 1 ? '已跳转' : '未跳转'}}</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 class="date" wx:if="{{item.jumpTime}}">{{item.jumpTimeFormatted}}</view>
<view class="status" wx:if="{{item.jumpStatus === 1}}">
<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
class="btn{{item.materialStatus === 3 ? '2' : '1'}}"
catch:tap="{{item.materialStatus === 3 ? 'handleUpload' : 'handleInfo'}}"
data-id="{{item.id}}"
>
{{item.materialStatus === 3 ? '重新提交' : '查看提交材料'}}
</view>
</view>
<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">
<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 class="remark" wx:if="{{item.materialStatus === 3}}">材料信息不全</view>
</view>
</view>
<view class="c-item active">
<!-- 入组状态 -->
<view class="c-item {{item.enrollStatus === 1 ? 'active' : ''}}">
<view class="aside">
<view class="line-top"></view>
<view class="step">
<view class="name">已入组</view>
<view class="name">{{item.enrollStatus === 1 ? '已入组' : '未入组'}}</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="date" wx:if="{{item.enrollTime}}">{{item.enrollTimeFormatted}}</view>
<view class="status" wx:if="{{item.enrollStatus === 1}}">
<view class="s1">审核通过</view>
<view class="btn1">查看提交材料</view>
<view class="btn1" catch:tap="handleInfo" data-id="{{item.id}}">查看提交材料</view>
</view>
</view>
</view>
</view>
</view>
<!-- 分页组件 -->
<pagination pagination="{{pagination}}" wx:if="{{!loading || patientList.length > 0}}"></pagination>
</view>
</view>

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

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

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

@ -1,11 +1,70 @@ @@ -1,11 +1,70 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
name: '',
loading: false,
},
onLoad() {
// 地推端修改昵称页面,仅允许地推人员访问
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() {

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

@ -6,6 +6,15 @@ @@ -6,6 +6,15 @@
</navbar>
<view class="page" style="padding-top: {{pageTop+28}}px;">
<input class="input" placeholder-class="place-input" type="text" placeholder="请输入新昵称" />
<view class="btn">确认</view>
<input
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>

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

@ -1,10 +1,13 @@ @@ -1,10 +1,13 @@
const app = getApp<IAppOption>()
let timer = null as null | number
Page({
data: {
mobile: '',
code: '',
codeText: '发送验证码',
loading: false,
},
onLoad() {
// 地推端修改手机号页面,仅允许地推人员访问
@ -12,11 +15,107 @@ Page({ @@ -12,11 +15,107 @@ Page({
// 页面加载完成
})
},
onUnload() {
// 页面卸载时清除定时器
if (timer) {
clearInterval(timer)
timer = null
}
},
// 获取验证码
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() {
// 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() {
wx.navigateBack()

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

@ -25,9 +25,11 @@ @@ -25,9 +25,11 @@
placeholder-class="place-input"
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 class="submit" bind:tap="handleSubmit">修改手机号</view>
<view class="submit" bind:tap="handleSubmit" style="opacity: {{loading ? 0.6 : 1}};">
{{loading ? '修改中...' : '修改手机号'}}
</view>
</view>
</view>

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

@ -87,13 +87,27 @@ Page({ @@ -87,13 +87,27 @@ Page({
url: '/app/promoter/promoter/statistics',
}).then((res: any) => {
this.setData({
invitePharmacyCount: res.invitePharmacyCount,
invitePharmacistCount: res.invitePharmacistCount,
invitePatientCount: res.invitePatientCount,
jumpPatientCount: res.jumpPatientCount,
enrollPatientCount: res.enrollPatientCount,
invitePharmacyCount: res.invitePharmacyCount || 0,
invitePharmacistCount: res.invitePharmacistCount || 0,
invitePatientCount: res.invitePatientCount || 0,
jumpPatientCount: res.jumpPatientCount || 0,
enrollPatientCount: res.enrollPatientCount || 0,
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({ @@ -108,10 +122,18 @@ Page({
endDate: this.data.endDate,
},
}).then((res: any) => {
const list = res.list || []
this.setData({
chartData: list,
})
this.initChartBar(list)
}).catch(() => {
// 接口失败时使用模拟数据
const mockData = this.generateMockChartData()
this.setData({
chartData: res.list || [],
chartData: mockData,
})
this.initChartBar(res.list || [])
this.initChartBar(mockData)
})
},
@ -127,6 +149,10 @@ Page({ @@ -127,6 +149,10 @@ Page({
},
}).then((res: any) => {
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({ @@ -142,7 +168,33 @@ Page({
},
}).then((res: any) => {
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({ @@ -159,8 +211,26 @@ Page({
// 日期选择变化
onDateChange(e: WechatMiniprogram.CustomEvent) {
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({
[field]: e.detail.value,
[field]: value,
})
// 重新加载图表数据
this.getPatientChart()

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

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

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

@ -1,11 +1,108 @@ @@ -1,11 +1,108 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
// 地推人员信息
promoterName: '',
promoterAvatar: '',
promoterId: '',
// 小程序码
qrCodeUrl: '',
// 项目信息
projectId: '',
projectName: '',
},
onLoad() {
// 地推端邀约页面,仅允许地推人员访问
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() {

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

@ -9,26 +9,37 @@ @@ -9,26 +9,37 @@
class="page"
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">
<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="nickname">刘平安</view>
<view class="nickname">{{promoterName || '邀约专员'}}</view>
<view class="label">邀约专员</view>
</view>
</view>
<view class="title">邀请您加入健康管理项目</view>
<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>
<view class="code-wrap">
<image class="code" src="{{imageUrl}}cache/c1.png?t={{Timestamp}}" show-menu-by-longpress></image>
<view class="code-wrap" bind:tap="saveQrCode">
<image class="code" src="{{qrCodeUrl}}" show-menu-by-longpress></image>
</view>
<view class="tip">
<view class="dot"></view>
扫码立即加入
<view class="dot"></view>
</view>
<view class="save-tip" wx:if="{{qrCodeUrl}}">点击二维码保存到相册</view>
</view>
</view>

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

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

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

@ -1,11 +1,108 @@ @@ -1,11 +1,108 @@
const app = getApp<IAppOption>()
Page({
data: {},
data: {
// 用户信息
promoterName: '',
promoterAvatar: '',
promoterPhone: '',
},
onLoad() {
// 地推端我的页面,仅允许地推人员访问
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() {
@ -24,9 +121,17 @@ Page({ @@ -24,9 +121,17 @@ Page({
})
},
hadleExit() {
wx.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
wx.reLaunch({
url: '/pages/work/index',
})
}
},
})
},
})

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

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

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

@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
"navigationBarTitleText": "地推端",
"navigationStyle": "custom",
"usingComponents": {
"navbar": "../../../components/navbar/index",
"ground-tab-bar": "/ground/components/ground-tab-bar/index"
"navbar": "/components/navbar/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>() @@ -2,37 +2,152 @@ const app = getApp<IAppOption>()
Page({
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() {
// 地推端药师页面,仅允许地推人员访问
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({
url: '/ground/pages/stat/index',
url: `/ground/pages/stat/index?id=${id}`,
})
},
})

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

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

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

@ -1,12 +1,150 @@ @@ -1,12 +1,150 @@
const app = getApp<IAppOption>()
Page({
data: {},
onLoad() {
data: {
// 药师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(() => {
// 页面加载完成
// 获取药师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() {
wx.navigateBack()

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

@ -8,16 +8,16 @@ @@ -8,16 +8,16 @@
<view class="page" style="padding-top: {{pageTop}}px;">
<view class="page-header" style="top:{{pageTop}}px">
<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="content">2025/02/26</view>
<view class="content">{{startDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view>
</picker>
<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="content">2025/02/26</view>
<view class="content">{{endDate}}</view>
<image class="icon" src="{{imageUrl}}icon8.png?t={{Timestamp}}"></image>
</view>
</picker>
@ -26,174 +26,73 @@ @@ -26,174 +26,73 @@
<view class="stat-card">
<view class="col">
<view class="num">
1893
{{invitePatientCount}}
<text class="sub">人</text>
</view>
<view class="title">邀约患者总数</view>
</view>
<view class="col">
<view class="num">
1893
{{jumpPatientCount}}
<text class="sub">人</text>
</view>
<view class="title">跳转患者数</view>
</view>
<view class="col">
<view class="num">
1893
{{enrollPatientCount}}
<text class="sub">人</text>
</view>
<view class="title">跳转患者数</view>
<view class="title">入组患者数</view>
</view>
</view>
<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="line-top"></view>
<view class="dot"></view>
<view class="line-bottom"></view>
</view>
<view class="m-container">
<view class="date">2025/02/26</view>
<view class="date">{{item.date}}</view>
<view class="card">
<view class="row2">
<view class="col">
<view class="name">邀约患者数</view>
<view class="num">750</view>
<view class="num">{{item.inviteCount || 0}}</view>
</view>
<view class="line"></view>
<view class="col">
<view class="name">跳转患者数</view>
<view class="num">750</view>
<view class="num">{{item.jumpCount || 0}}</view>
</view>
<view class="line"></view>
<view class="col">
<view class="name">入组患者数</view>
<view class="num">750</view>
<view class="num">{{item.enrollCount || 0}}</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="name">入组患者数</view>
<view class="num">300</view>
<view class="name">{{indItem.indicationName}}</view>
<view class="num">{{indItem.inviteCount || 0}}</view>
<view class="line"></view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="name">{{indItem.indicationName}}</view>
<view class="num">{{indItem.jumpCount || 0}}</view>
</view>
<view class="col">
<view class="name">斑块状银屑病</view>
<view class="num">280</view>
<view class="name">{{indItem.indicationName}}</view>
<view class="num">{{indItem.enrollCount || 0}}</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 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 class="empty" wx:if="{{!loading && chartList.length === 0}}">暂无数据</view>
</view>
</view>

113
src/pages/index/index.ts

@ -11,17 +11,35 @@ Page({ @@ -11,17 +11,35 @@ Page({
options: [] as Array<{ indicationId: number; indicationName: string; active: boolean }>,
agreementChecked: true,
projectId: 0,
projectId: '',
projectName: '',
// 扫码获取的药师ID
pharmacistId: '',
isLogin: 0,
isPatient: 0,
// 是否已有项目
hasProject: false,
// 选中的适应症名称
selectedIndicationName: '',
bannerList: [] as any[],
},
onLoad() {
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(() => {
this.getBanner()
this.checkStatus()
@ -43,28 +61,34 @@ Page({ @@ -43,28 +61,34 @@ Page({
checkStatus() {
wx.ajax({
method: 'GET',
url: '/app/patient/patient/check-status',
}).then((res: any) => {
url: '/app/patient/patient/recent-project',
})
.then((res: any) => {
if (res && res.projectId) {
// 已有项目,显示已参加项目状态
this.setData({
isLogin: res.isLogin,
isPatient: res.isPatient,
isLogin: 1,
isPatient: 1,
projectId: res.projectId,
projectName: res.projectName,
hasProject: true,
selectedIndicationName: res.indicationName,
})
if (res.isLogin === 1 && res.isPatient === 1 && res.projectId) {
} else {
// 没有项目,获取项目列表供选择
this.setData({
options: [
{
indicationId: res.indicationId,
indicationName: res.indicationName,
active: true,
},
],
hasProject: false,
})
} else {
this.getProjectInfo()
}
})
.catch(() => {
// 接口失败,获取项目列表
this.setData({
hasProject: false,
})
this.getProjectInfo()
})
},
getProjectInfo() {
@ -135,20 +159,65 @@ Page({ @@ -135,20 +159,65 @@ Page({
data: {
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',
url: '/app/patient/patient/select-indication',
data: {
projectId: this.data.projectId,
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({
appId: 'wx05551c5ee1fd1c12',
path: res.jumpUrl,
path: jumpUrl,
})
})
} else {
// 没有药师ID,直接跳转
wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12',
path: jumpUrl,
})
}
})
},
})
@ -167,6 +236,14 @@ Page({ @@ -167,6 +236,14 @@ Page({
})
},
handleJump() {
// 跳转到腾讯药箱小程序
wx.navigateToMiniProgram({
appId: 'wx05551c5ee1fd1c12',
path: '',
})
},
onShareAppMessage() {
return {
title: '华观健康',

14
src/pages/index/index.wxml

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

29
src/pages/start/index.scss

@ -1,29 +1,4 @@ @@ -1,29 +1,4 @@
page {
background-color: #f8fafa;
}
.page-title {
font-weight: 900;
font-size: 44rpx;
line-height: 52rpx;
color: #00b2a9;
}
.page {
.page-banner {
.page{
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;
}
}
height: 100vh;
}

68
src/pages/start/index.ts

@ -4,20 +4,66 @@ const app = getApp<IAppOption>() @@ -4,20 +4,66 @@ const app = getApp<IAppOption>()
Page({
data: {},
onLoad(options) {
// 解析扫码参数
let pharmacistId = ''
let projectId = ''
if (options.scene) {
const { doctorId } = parseScene(options.scene) as { doctorId: string }
app.globalData.waitBindDoctorId = doctorId
const sceneData = parseScene(options.scene) as { pharmacistId?: string; projectId?: string }
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 || {}
// wx.reLaunch({
// url: '/pages/index/index',
// })
// })
// wx.reLaunch({
// url: '/pages/index/index',
// })
// 已注册,根据身份跳转到对应首页
const homePageUrl = {
2: '/pages/index/index',
3: '/ground/pages/home/index',
4: '/doctor/pages/home/index',
}[loginIdentity as 2 | 3 | 4]
wx.reLaunch({
url: homePageUrl || '/pages/index/index',
})
})
},
})

12
src/pages/start/index.wxml

@ -1,11 +1 @@ @@ -1,11 +1 @@
<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>
<image src="{{imageUrl}}start1.png?t={{Timestamp}}" mode="aspectFill" class="page"></image>

7
src/pages/tourists/index.json

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

Loading…
Cancel
Save