1
This commit is contained in:
@@ -1 +0,0 @@
|
||||
{"containers":[],"config":{}}
|
||||
11
app.json
11
app.json
@@ -3,16 +3,18 @@
|
||||
"pages/system/welcome/index",
|
||||
"pages/home/index",
|
||||
"pages/test/index",
|
||||
"pages/joinFlow/access/index",
|
||||
"pages/joinFlow/manual/index",
|
||||
"pages/joinFlow/person/index",
|
||||
"pages/expert/index"
|
||||
"pages/expert/index",
|
||||
"pages/chat/index"
|
||||
],
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/home/index"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/chat/index"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/expert/index"
|
||||
@@ -65,6 +67,7 @@
|
||||
"t-cascader": "tdesign-miniprogram/cascader/cascader",
|
||||
"t-form": "tdesign-miniprogram/form/form",
|
||||
"t-form-item": "tdesign-miniprogram/form-item/form-item",
|
||||
"t-divider": "tdesign-miniprogram/divider/divider"
|
||||
"t-divider": "tdesign-miniprogram/divider/divider",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
2
app.scss
2
app.scss
@@ -15,7 +15,9 @@ navigator {
|
||||
|
||||
|
||||
page {
|
||||
--td-brand-color: rgba(0, 106, 106, 1);
|
||||
--background: linear-gradient(135deg, rgba(0, 33, 33, 1), rgba(0, 106, 106, 1));
|
||||
--area-bottom: calc(130rpx + env(safe-area-inset-bottom));
|
||||
.t-navbar__capsule::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ Component({
|
||||
label: '首页',
|
||||
icon: 'home'
|
||||
},
|
||||
{
|
||||
path: "/pages/chat/index",
|
||||
label: 'AI助手',
|
||||
icon: 'chat-bubble'
|
||||
},
|
||||
{
|
||||
path: "/pages/expert/index",
|
||||
label: '专家服务',
|
||||
|
||||
292
pages/chat/index.js
Normal file
292
pages/chat/index.js
Normal file
@@ -0,0 +1,292 @@
|
||||
import request, { streamRequest } from "@/utils/request";
|
||||
import { copyText } from "@/utils/common"
|
||||
let outTimer; //输出定时器
|
||||
let outStrList = [];//输出字符串数组
|
||||
const app = getApp()
|
||||
|
||||
let chat_uuid
|
||||
//工具
|
||||
let toolText = ''
|
||||
let toolInfo = {}
|
||||
Page({
|
||||
data: {
|
||||
chatList: [], //会话记录
|
||||
aiStatus: 3,//1表示接口响应中,2表示接口响应完毕,3表示完全输出完毕
|
||||
keyboardBottom: 0, //底部键盘高度
|
||||
scrollTop: 0, //滚动条位置
|
||||
inputText: '',//输入框内容
|
||||
},
|
||||
onLoad() {
|
||||
let str = `您好!我是术极守护AI助手 🤖\n\n我可以为您解答术后康复相关的问题。请注意,我只能提供基于康复指南的一般性建议,如遇紧急情况请立即就医或联系您的主治医生。`
|
||||
let mdResult = app.towxml(str, 'markdown', {
|
||||
base: "www.xxx.com",
|
||||
});
|
||||
let list = [
|
||||
{
|
||||
loading: false,
|
||||
end: true,
|
||||
chat_type: 2,
|
||||
md_content: mdResult,
|
||||
chat_content: str,
|
||||
quick_btn: [
|
||||
{ text: '🩹伤口有轻微渗液怎么办?' },
|
||||
{ text: '🌡️术后轻微发烧正常吗?' },
|
||||
{ text: '🚿什么时候可以洗澡?' },
|
||||
{ text: '🥗饮食需要注意什么?' }
|
||||
]
|
||||
}
|
||||
]
|
||||
this.setData({
|
||||
chatList: list
|
||||
})
|
||||
},
|
||||
onShow() {
|
||||
this.getTabBar((tabBar) => {
|
||||
tabBar.setData({
|
||||
selected: 'AI助手',
|
||||
})
|
||||
})
|
||||
},
|
||||
//监听键盘弹出
|
||||
bindkeyboardheightchange(event) {
|
||||
let height = event.detail.height
|
||||
let { keyboardBottom } = this.data
|
||||
let scrollTop = 0
|
||||
//获取当前滚动距离
|
||||
const query = wx.createSelectorQuery().in(this)
|
||||
query.select(".silder").scrollOffset()
|
||||
query.exec(function (res) {
|
||||
scrollTop = res[0].scrollTop
|
||||
})
|
||||
this.setData({
|
||||
keyboardBottom: height > 0 ? height - 50 : keyboardBottom,
|
||||
}, () => {
|
||||
if (height > 0) {
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
scrollTop: scrollTop + height
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
},
|
||||
//键盘失去焦点
|
||||
bindkeyboardBlur() {
|
||||
let { scrollTop, keyboardBottom } = this.data
|
||||
let endScrollTop = scrollTop - keyboardBottom
|
||||
this.setData({
|
||||
keyboardBottom: 0
|
||||
}, () => {
|
||||
this.setData({
|
||||
scrollTop: endScrollTop
|
||||
})
|
||||
})
|
||||
},
|
||||
//监听输入框内容
|
||||
changeInput(e) {
|
||||
let value = e.detail.value
|
||||
this.setData({
|
||||
inputText: value
|
||||
})
|
||||
},
|
||||
async handQuick(e) {
|
||||
let { data } = e.currentTarget.dataset
|
||||
this.pushUserTemplate(data.text)
|
||||
this.sendMessage(data.text)
|
||||
|
||||
},
|
||||
|
||||
// 文字提交发送
|
||||
submitInput() {
|
||||
let { inputText, aiStatus } = this.data
|
||||
if (!inputText.trim() == '' && aiStatus) {
|
||||
console.log('111--');
|
||||
this.pushUserTemplate(inputText)
|
||||
this.sendMessage(inputText)
|
||||
}
|
||||
},
|
||||
//发送聊天
|
||||
async sendMessage(text) {
|
||||
let { chatList, aiStatus } = this.data
|
||||
if (aiStatus == 3) {
|
||||
let that = this
|
||||
//初始化变量
|
||||
toolText = ''
|
||||
toolInfo = {}
|
||||
chat_uuid = String(new Date().getTime())
|
||||
this.setData({
|
||||
aiStatus: 1
|
||||
})
|
||||
//输出
|
||||
this.timerOutput()
|
||||
//请求
|
||||
streamRequest("/ai/chat", {
|
||||
message: text,
|
||||
chat_uuid: chat_uuid,
|
||||
}, this.onChunkReceived).then(() => {
|
||||
console.log('-------------成功-----------');
|
||||
if (this.data.aiStatus != 3) {
|
||||
that.setData({
|
||||
aiStatus: 2
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log("------失败1---------");
|
||||
that.data.chatList.at(-1).loading = false
|
||||
that.setData({
|
||||
aiStatus: 3,
|
||||
chatList: chatList
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
//填充用户的聊天数据
|
||||
pushUserTemplate(text) {
|
||||
let { chatList } = this.data
|
||||
let userItem = {
|
||||
loading: false,
|
||||
chat_type: 1,
|
||||
con_type: 1,
|
||||
chat_content: text,
|
||||
md_content: '',
|
||||
}
|
||||
let aiItem = {
|
||||
chat_type: 2,
|
||||
chat_content: '',
|
||||
md_content: '',
|
||||
con_type: 1,
|
||||
loading: true,
|
||||
}
|
||||
chatList.push(userItem)
|
||||
|
||||
chatList.push(aiItem)
|
||||
|
||||
//要修改的数据
|
||||
let setData = {
|
||||
chatList,
|
||||
scrollTop: 80000,
|
||||
inputText: ''
|
||||
}
|
||||
this.setData(setData, () => {
|
||||
this.setData({
|
||||
scrollTop: 80000
|
||||
})
|
||||
})
|
||||
},
|
||||
//只填充ai的默认回复
|
||||
pushAiTemplate(text, options = {}) {
|
||||
let { chatList } = this.data
|
||||
let mdResult = app.towxml(text, 'markdown', {
|
||||
base: "www.xxx.com",
|
||||
});
|
||||
let aiItem = {
|
||||
chat_type: 2,
|
||||
chat_content: text,
|
||||
md_content: mdResult,
|
||||
con_type: 1,
|
||||
quick_btn: options.quick_btn || [],
|
||||
...options
|
||||
}
|
||||
chatList.push(aiItem)
|
||||
//要修改的数据
|
||||
let setData = {
|
||||
chatList,
|
||||
scrollTop: 80000,
|
||||
inputText: ''
|
||||
}
|
||||
this.setData(setData, () => {
|
||||
this.setData({
|
||||
scrollTop: 80000
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
//定时器输出
|
||||
timerOutput() {
|
||||
let { chatList } = this.data
|
||||
let that = this
|
||||
clearInterval(outTimer)
|
||||
outStrList = []
|
||||
let lastChat = this.data.chatList.at(-1)
|
||||
//定时
|
||||
outTimer = setInterval(async () => {
|
||||
if ((outStrList.length == 0) && that.data.aiStatus == 2) {
|
||||
clearInterval(outTimer)
|
||||
lastChat.end = true
|
||||
//如果有工具,设置工具对象
|
||||
let text = lastChat.chat_content
|
||||
// 上传AI响应结果
|
||||
if (text) {
|
||||
await request.post("/ai/history/upload", {
|
||||
chat_content: text,
|
||||
chat_uuid: chat_uuid,
|
||||
})
|
||||
}
|
||||
//调用工具
|
||||
if (toolText) { }
|
||||
that.setData({
|
||||
chatList,
|
||||
aiStatus: 3
|
||||
})
|
||||
|
||||
} else if (outStrList.length > 0) {
|
||||
let firstValue = outStrList.shift();
|
||||
lastChat.chat_content += firstValue
|
||||
//转换md
|
||||
let mdResult = app.towxml(lastChat.chat_content, 'markdown', {
|
||||
base: "www.xxx.com",
|
||||
});
|
||||
lastChat.md_content = mdResult
|
||||
lastChat.loading = false
|
||||
that.setData({
|
||||
chatList: chatList,
|
||||
scrollTop: 80000
|
||||
})
|
||||
}
|
||||
}, 50)
|
||||
},
|
||||
//流回调
|
||||
onChunkReceived(data) {
|
||||
data.forEach((item) => {
|
||||
let value = item?.choices[0].delta.content ?? ''
|
||||
let call = item?.choices[0].delta.tool_calls
|
||||
//储存tool的id和名称
|
||||
if (call) {
|
||||
if (call[0].id) {
|
||||
toolInfo.tool_call_id = call[0].id
|
||||
toolInfo.name = call[0].function.name
|
||||
}
|
||||
let tool = call[0].function.arguments ?? ''
|
||||
toolText += tool
|
||||
}
|
||||
if (value) {
|
||||
outStrList.push(value)
|
||||
}
|
||||
})
|
||||
},
|
||||
//停止
|
||||
async stopMessage() {
|
||||
clearInterval(outTimer)
|
||||
let lastChat = this.data.chatList.at(-1)
|
||||
lastChat.chat_status = 2
|
||||
this.setData({
|
||||
aiStatus: 3,
|
||||
chatList: this.data.chatList
|
||||
})
|
||||
await request.post("/ai/history/upload", {
|
||||
chat_content: lastChat.chat_content || 'nocontent',
|
||||
chat_status: 2,
|
||||
chat_uuid: chat_uuid
|
||||
})
|
||||
},
|
||||
//复制文字
|
||||
copy(e) {
|
||||
let { content, chat_content } = e.currentTarget.dataset.text
|
||||
console.log(e);
|
||||
if (!content) {
|
||||
content = chat_content
|
||||
}
|
||||
copyText(content)
|
||||
},
|
||||
})
|
||||
6
pages/chat/index.json
Normal file
6
pages/chat/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"towxml": "/towxml/towxml"
|
||||
},
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
179
pages/chat/index.scss
Normal file
179
pages/chat/index.scss
Normal file
@@ -0,0 +1,179 @@
|
||||
@import "./makedown.scss";
|
||||
|
||||
.c-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
padding-bottom: var(--area-bottom);
|
||||
|
||||
.t-navbar__content {
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
}
|
||||
|
||||
.silder {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
padding: 30rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.chat-item {
|
||||
padding: 10rpx 30rpx;
|
||||
|
||||
&+.chat-item {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.lawyer-info {
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 0.75rem;
|
||||
|
||||
image {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-image {
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
max-width: calc(100% - 100rpx);
|
||||
min-height: 60rpx;
|
||||
width: fit-content;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
|
||||
.loading {
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 0 var(--td-brand-color);
|
||||
animation: load 2s linear infinite alternate;
|
||||
}
|
||||
|
||||
.stop-tip {
|
||||
margin-top: 20rpx;
|
||||
font-size: 0.64rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.m-footer {
|
||||
margin-top: 10rpx;
|
||||
|
||||
.tip {
|
||||
@extend .stop-tip;
|
||||
margin-top: 0;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.sound-icon {
|
||||
color: var(--text-2);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-warpper {
|
||||
font-size: 0.75rem;
|
||||
gap: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.quick-item {
|
||||
background-color: white;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 50rpx;
|
||||
border: 1px solid #dfdddd;
|
||||
|
||||
&.color {
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-msg {
|
||||
.chat-message {
|
||||
background-color: white;
|
||||
border-radius: 20rpx 30rpx 30rpx 10rpx;
|
||||
box-shadow: 0 0 15rpx #e2e2e2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.user-msg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
.chat-message {
|
||||
background-color: var(--td-brand-color);
|
||||
color: white;
|
||||
border-radius: 30rpx 30rpx 0 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-position {
|
||||
margin: 0 30rpx;
|
||||
position: relative;
|
||||
|
||||
//输入框
|
||||
.input-warpper {
|
||||
box-shadow: 0 0 15rpx #e2e2e2;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: white;
|
||||
border-radius: 50rpx;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.push-btn {
|
||||
border-radius: 50%;
|
||||
width: 65rpx;
|
||||
height: 65rpx;
|
||||
color: var(--text-3);
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&.active-push {
|
||||
background-color: var(--td-brand-color);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.stop-btn {
|
||||
font-size: 65rpx;
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//占位
|
||||
.empty {
|
||||
transition: height 0.3s;
|
||||
}
|
||||
|
||||
//ai响应loading
|
||||
@keyframes load {
|
||||
0% {
|
||||
box-shadow: inset -20rpx 40rpx 0 var(--td-brand-color);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: inset 20rpx -40rpx 0 var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
103
pages/chat/index.wxml
Normal file
103
pages/chat/index.wxml
Normal file
@@ -0,0 +1,103 @@
|
||||
<page-meta root-font-size="system"></page-meta>
|
||||
|
||||
<view class="bg-gradient c-container">
|
||||
<t-navbar class="custom-top-navbar"
|
||||
fixed="{{false}}"
|
||||
title="AI健康管家">
|
||||
</t-navbar>
|
||||
<scroll-view class="silder"
|
||||
scroll-top="{{scrollTop}}"
|
||||
scroll-y>
|
||||
<view class="chat-item {{item.chat_type == 1 ? 'user-msg' : 'ai-msg'}}"
|
||||
id="chat-${{index}}"
|
||||
wx:for="{{chatList}}"
|
||||
wx:key="index">
|
||||
<view class="lawyer-info flex-align"
|
||||
wx:if="{{item.chat_type !=1}}">
|
||||
<text>AI助手</text>
|
||||
</view>
|
||||
<!-- 显示图片格式 -->
|
||||
<view wx:if="{{item.con_type == 2}}">
|
||||
<image src="{{item.chat_content}}"
|
||||
class="chat-image"
|
||||
data-url="{{item.chat_content}}"
|
||||
bind:tap="viewImg"
|
||||
mode="aspectFill" />
|
||||
</view>
|
||||
<view class="chat-message "
|
||||
wx:else>
|
||||
<view class="loading"
|
||||
wx:if="{{item.loading}}"></view>
|
||||
<view wx:elif="{{item.tool}}">
|
||||
{{item.tool}}
|
||||
</view>
|
||||
<view wx:elif="{{item.chat_type == 1}}">
|
||||
{{item.chat_content}}
|
||||
</view>
|
||||
<towxml wx:elif="{{item.chat_type == 2}}"
|
||||
data-text="{{item}}"
|
||||
bind:longpress="copy"
|
||||
nodes="{{item.md_content}}" />
|
||||
<!-- 底部功能 -->
|
||||
<view wx:if="{{item.chat_status == 2}}"
|
||||
class="stop-tip">
|
||||
(已停止)
|
||||
</view>
|
||||
<view class="m-footer flex-between"
|
||||
wx:elif="{{item.end}}">
|
||||
<view class="tip">
|
||||
AI助手仅提供基于康复指南的一般性建议,不构成医疗诊断。如遇紧急情况请立即就医
|
||||
</view>
|
||||
<view class="flex-align">
|
||||
<t-icon name="file-copy"
|
||||
class="sound-icon"
|
||||
data-text="{{item}}"
|
||||
bind:tap="copy" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 快捷按钮 -->
|
||||
<view class="quick-warpper"
|
||||
wx:if="{{item.quick_btn.length > 0 && item.end}}">
|
||||
<view class="quick-item quick-{{quick.type}} {{quick.color ? 'color' : ''}}"
|
||||
wx:for="{{item.quick_btn}}"
|
||||
style="background-color: {{quick.color}};"
|
||||
wx:for-item="quick"
|
||||
wx:key="index"
|
||||
bind:tap="handQuick"
|
||||
data-parent="{{item}}"
|
||||
data-data="{{quick}}">
|
||||
{{quick.text}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-position">
|
||||
<!-- 输入框 -->
|
||||
<view class="input-warpper flex-align">
|
||||
<input placeholder="有什么问题尽管问我"
|
||||
bindblur="bindkeyboardBlur"
|
||||
bindinput="changeInput"
|
||||
bindconfirm="submitInput"
|
||||
value="{{inputText}}"
|
||||
hold-keyboard="{{false}}"
|
||||
adjust-position="{{false}}"
|
||||
bindkeyboardheightchange="bindkeyboardheightchange" />
|
||||
|
||||
<view class="push-btn flex-center {{inputText !='' ? 'active-push' : ''}} "
|
||||
wx:if="{{aiStatus==3}}"
|
||||
bind:tap="submitInput">
|
||||
<t-icon name="send-filled" />
|
||||
</view>
|
||||
<t-icon name="stop-circle-filled"
|
||||
class="stop-btn"
|
||||
bind:tap="stopMessage"
|
||||
wx:else />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 占位撑高 -->
|
||||
<view style="height: {{keyboardBottom}}px;"
|
||||
class="empty"></view>
|
||||
</view>
|
||||
17
pages/chat/makedown.scss
Normal file
17
pages/chat/makedown.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.h2w__main,
|
||||
.h2w__p {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
font-size: 0.9rem;
|
||||
|
||||
}
|
||||
|
||||
.h2w__li {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
|
||||
.h2w__h3 {
|
||||
font-size: 32rpx !important;
|
||||
margin-top: 20rpx !important;
|
||||
}
|
||||
15
pages/chat/read.md
Normal file
15
pages/chat/read.md
Normal file
@@ -0,0 +1,15 @@
|
||||
聊天列表类型
|
||||
``` typescript
|
||||
interface ChatType{
|
||||
loading: boolean, // 是否开始响应
|
||||
end:boolean,//响应输出完成
|
||||
chat_type: 1 | 2, // 1用户,2ai普通响应
|
||||
chat_content: string, //文本内容
|
||||
md_content: string, //md格式
|
||||
con_type:1, //消息类型,1文本,2图片
|
||||
tool:string, //用工具时的默认内容,为空代表不是工具
|
||||
chat_status?:number, //2停止
|
||||
quick_btn:[], //快捷菜单按钮,为空不显示
|
||||
drugList:[],//药品信息,只用来设置提醒
|
||||
}
|
||||
```
|
||||
@@ -1,9 +1,77 @@
|
||||
import request from "@/utils/request"
|
||||
|
||||
const app = getApp()
|
||||
Page({
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
s: "李",
|
||||
name: "李医生",
|
||||
title: "主任医师 · 骨科专家",
|
||||
desc: "20年临床经验"
|
||||
},
|
||||
{
|
||||
s: "王",
|
||||
name: "王医生",
|
||||
title: "副主任医师 · 康复医学",
|
||||
desc: "15年临床经验"
|
||||
},
|
||||
{
|
||||
s: "张",
|
||||
name: "张护士长",
|
||||
title: "主管护师 · 伤口护理",
|
||||
desc: "18年护理经验"
|
||||
}
|
||||
],
|
||||
count: 0
|
||||
},
|
||||
onShow() {
|
||||
let userInfo = app.globalData.userInfo
|
||||
this.setData({
|
||||
count: userInfo.consult_remains
|
||||
})
|
||||
this.getTabBar((tabBar) => {
|
||||
tabBar.setData({
|
||||
selected: '专家服务',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async onPlay() {
|
||||
if (this.data.count <= 0) {
|
||||
wx.showToast({
|
||||
title: '免费次数已用完',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 2. 弹窗确认
|
||||
wx.showModal({
|
||||
title: '拨打专家热线',
|
||||
content: '确认拨打专家咨询电话吗?本次咨询将使用1次免费机会',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
wx.showLoading({ title: '正在呼叫...', mask: true });
|
||||
const info = await request.get("/expert/phone");
|
||||
this.setData({
|
||||
count: this.data.count - 1
|
||||
});
|
||||
wx.hideLoading();
|
||||
|
||||
// 5. 吊起拨号盘
|
||||
if (info && info.expert_phone) {
|
||||
wx.makePhoneCall({
|
||||
phoneNumber: info.expert_phone,
|
||||
});
|
||||
} else {
|
||||
wx.showToast({ title: '暂无专家电话', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
wx.hideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
page {
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
padding-bottom: 200rpx;
|
||||
}
|
||||
|
||||
.head {
|
||||
@@ -47,11 +48,175 @@ page {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
|
||||
.btn {
|
||||
padding: 50rpx 0;
|
||||
background-color: rgba(29, 120, 116, 1);
|
||||
}
|
||||
.c-2{
|
||||
|
||||
|
||||
.service-card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
margin-top: 30rpx;
|
||||
|
||||
.flex-align {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
.icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #f0f5f0; // 图标背景色
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 12px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #18a74f; // 绿色
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #18a74f;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 12px;
|
||||
color: #18a74f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emergency-card {
|
||||
background: rgba(255, 235, 238, 1);
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-top: 30rpx;
|
||||
gap: 30rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
--color: rgba(198, 40, 40, 1);
|
||||
color: var(--color);
|
||||
|
||||
|
||||
.icon {
|
||||
width: 90rpx;
|
||||
aspect-ratio: 1;
|
||||
background-color: rgba(186, 26, 26, 0.15);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.t-1 {
|
||||
font-weight: 700;
|
||||
font-size: 34rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-weight: 700;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size: 28rpx;
|
||||
line-height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
background-color: var(--color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service {
|
||||
background-color: white;
|
||||
--color: black;
|
||||
|
||||
.icon {
|
||||
background-color: rgba(0, 106, 106, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.express {
|
||||
background-color: white;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
|
||||
.item {
|
||||
gap: 30rpx;
|
||||
margin-top: 40rpx;
|
||||
|
||||
.icon {
|
||||
width: 100rpx;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(rgba(29, 120, 116, 1), rgba(46, 139, 139, 1));
|
||||
}
|
||||
|
||||
.t-1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.t-2 {
|
||||
font-size: 28rpx;
|
||||
color: rgba(69, 70, 78, 1);
|
||||
}
|
||||
|
||||
.t-3 {
|
||||
font-size: 24rpx;
|
||||
color: rgba(69, 70, 78, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,91 @@
|
||||
<t-icon name="check-circle" />
|
||||
可咨询次数
|
||||
</view>
|
||||
<view class="num">3</view>
|
||||
<view class="num">{{count}}</view>
|
||||
</view>
|
||||
<view class="tip">本月剩余 3 次免费咨询机会</view>
|
||||
<view class="tip">本月剩余 {{count}} 次免费咨询机会</view>
|
||||
</view>
|
||||
|
||||
<t-button block
|
||||
theme="primary"
|
||||
class="btn"
|
||||
icon="call-1">
|
||||
icon="call-1"
|
||||
bind:tap="onPlay">
|
||||
一键拨打专家电话
|
||||
</t-button>
|
||||
|
||||
<view class="c-2 flex-align">
|
||||
<view class="icon">
|
||||
<t-icon name="time" />
|
||||
<view class="service-card">
|
||||
<view class="header flex-align">
|
||||
<view class="icon">
|
||||
<t-icon name="time" />
|
||||
</view>
|
||||
<view class="title">服务时间</view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="row">
|
||||
<view class="label">工作日</view>
|
||||
<view class="time">09:00 - 21:00</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="label">周末及节假日</view>
|
||||
<view class="time">10:00 - 18:00</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="status">
|
||||
<view class="dot"></view>
|
||||
<view class="text">当前服务中</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="emergency-card">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="error-triangle" />
|
||||
</view>
|
||||
<view style="flex: 1;">
|
||||
<view class="t-1">紧急情况处理</view>
|
||||
<view>如遇以下紧急情况,请立即拨打</view>
|
||||
<view style="margin-bottom: 20rpx;"><text class="num">120</text> 或前往急诊</view>
|
||||
|
||||
<view class="list">
|
||||
<view class="item">大量出血或伤口裂开</view>
|
||||
<view class="item">高热不退(体温≥39°C)</view>
|
||||
<view class="item">剧烈疼痛无法缓解</view>
|
||||
<view class="item">呼吸困难或胸痛</view>
|
||||
<view class="item">意识模糊或昏迷</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="emergency-card service">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="info-circle" />
|
||||
</view>
|
||||
<view style="flex: 1;">
|
||||
<view class="t-1">服务说明</view>
|
||||
|
||||
<view class="list">
|
||||
<view class="item">每次通话时长不超过15分钟</view>
|
||||
<view class="item">专家会根据您的情况提供专业建议</view>
|
||||
<view class="item">复杂问题可能需要您到院面诊</view>
|
||||
<view class="item">建议提前准备好相关检查报告</view>
|
||||
<view class="item">咨询记录会同步到您的康复档案</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="express">
|
||||
<view style="font-weight: 700;">专家团队</view>
|
||||
<view class="item flex-align"
|
||||
wx:for="{{list}}"
|
||||
wx:key="index">
|
||||
<view class="icon flex-center">{{item.s}}</view>
|
||||
<view>
|
||||
<view class="t-1">{{item.name}}</view>
|
||||
<view class="t-2">{{item.title}}</view>
|
||||
<view class="t-3">{{item.desc}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,7 +1,15 @@
|
||||
import request from "@/utils/request"
|
||||
Page({
|
||||
|
||||
data: {
|
||||
detail: {
|
||||
overview: {},
|
||||
tasks: [{}, {}, {}],
|
||||
},
|
||||
loading: true,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.init()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -10,5 +18,31 @@ Page({
|
||||
selected: '首页',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async init() {
|
||||
let res = await request.get("/home")
|
||||
this.setData({
|
||||
detail: res,
|
||||
loading: false,
|
||||
})
|
||||
},
|
||||
//手动标记是否完成
|
||||
handDone(e) {
|
||||
const { data } = e.currentTarget.dataset;
|
||||
const newTasks = this.data.detail.tasks.map(v => {
|
||||
if (v.task_id == data.task_id) {
|
||||
// 如果当前是 1 则变为 0,如果是 0 则变为 1
|
||||
return { ...v, is_completed: v.is_completed == 1 ? 0 : 1 };
|
||||
}
|
||||
return v;
|
||||
});
|
||||
request.post("/home/task-record", {
|
||||
...data,
|
||||
is_completed: data.is_completed == 1 ? 0 : 1
|
||||
})
|
||||
this.setData({
|
||||
'detail.tasks': newTasks
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
page {
|
||||
background-color: rgba(247, 249, 252, 1);
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -78,11 +79,15 @@ page {
|
||||
width: 90rpx;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 20rpx;
|
||||
background-color: var(--color);
|
||||
background-color: var(--back);
|
||||
color: rgba(99, 63, 0, 1);
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
--td-tag-default-light-color: var(--back);
|
||||
--td-tag-default-font-color: var(--color);
|
||||
|
||||
.time {
|
||||
margin-left: 20rpx;
|
||||
color: rgba(69, 70, 78, 1);
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
<wxs src="/utils/wxs/comment.wxs"
|
||||
module="utils"></wxs>
|
||||
|
||||
<view class="header">
|
||||
<t-navbar class="fixed-nav"
|
||||
fixed="{{false}}" />
|
||||
<view class="h-1">
|
||||
<view class="left">
|
||||
<view class="t-1">术后第 2 天</view>
|
||||
<view class="t-2">康复进行中</view>
|
||||
<view class="t-1">术后第 {{detail.overview.postoperative_day || 0}} 天</view>
|
||||
<view class="t-2">{{detail.overview.status_text}}</view>
|
||||
</view>
|
||||
<view class="right flex-column-center ">
|
||||
<view class="d">2</view>
|
||||
<view class="d">{{detail.overview.postoperative_day || 0}} </view>
|
||||
<view class="c">DAYS</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-2">
|
||||
<view class="flex-between title">
|
||||
<view>今日任务完成度</view>
|
||||
<view class="num">0/5</view>
|
||||
<view class="num">{{taskUtils.getDoneCount(detail.tasks)}}/{{detail.overview.total_task_count}}</view>
|
||||
</view>
|
||||
<t-progress percentage="80"
|
||||
<t-progress percentage="{{taskUtils.getDoneCount(detail.tasks) / detail.overview.total_task_count * 100}}"
|
||||
color="{{ ['white', 'white'] }}"
|
||||
track-color="rgba(255, 255, 255, 0.2)"
|
||||
label="" />
|
||||
@@ -25,20 +28,31 @@
|
||||
|
||||
<view class="list">
|
||||
<view class="item flex-align"
|
||||
wx:for="{{5}}"
|
||||
style="--color:{{item.label_color}};--back:{{utils.getLightColor(item.label_color)}}"
|
||||
wx:for="{{detail.tasks}}"
|
||||
wx:key="index">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="apple" />
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="flex-align">
|
||||
<t-tag variant="light"
|
||||
theme="success">用药</t-tag>
|
||||
<view class="time">08:00</view>
|
||||
<t-skeleton loading="{{loading}}"
|
||||
rowCol="{{[{ size: '90rpx' }]}}">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="{{item.pill}}" />
|
||||
</view>
|
||||
<view class="title">服用抗生素</view>
|
||||
<view class="desc">头孢类抗生素,饭后服用</view>
|
||||
<button class="btn">标记完成</button>
|
||||
</t-skeleton>
|
||||
|
||||
<view class="content">
|
||||
<t-skeleton loading="{{loading}}"
|
||||
theme="paragraph">
|
||||
<view class="flex-align">
|
||||
<t-tag variant="light">{{item.category_label}}</t-tag>
|
||||
<view class="time">{{item.time_label}}</view>
|
||||
</view>
|
||||
<view class="title">{{item.title}}</view>
|
||||
<view class="desc">{{item.content}}</view>
|
||||
<button class="btn {{item.is_completed == 1 ? 'active' : ''}}"
|
||||
bind:tap="handDone"
|
||||
data-data="{{item}}">
|
||||
{{item.is_completed == 0 ? '标记完成' : '已完成' }}
|
||||
</button>
|
||||
</t-skeleton>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -47,4 +61,22 @@
|
||||
<view class="title">💡 温馨提示:</view>
|
||||
<view class="text"> 请按时完成康复任务,如有任何不适请及时联系医护人员</view>
|
||||
</view>
|
||||
<view style="height: 30rpx;"></view>
|
||||
<view style="height: 30rpx;"></view>
|
||||
|
||||
|
||||
<wxs module="taskUtils">
|
||||
var getDoneCount = function (tasks) {
|
||||
if (!tasks || !tasks.length) return 0;
|
||||
var count = 0;
|
||||
for (var i = 0; i < tasks.length; i++) {
|
||||
if (tasks[i].is_completed == 1 || tasks[i].is_completed === true) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getDoneCount: getDoneCount
|
||||
};
|
||||
</wxs>
|
||||
@@ -1,31 +0,0 @@
|
||||
// pages/auth/access/index.js
|
||||
Page({
|
||||
|
||||
data: {
|
||||
|
||||
},
|
||||
|
||||
handClick(e) {
|
||||
let { type } = e.currentTarget.dataset
|
||||
//拍照
|
||||
if (type == 1) {
|
||||
wx.scanCode({
|
||||
onlyFromCamera: true,
|
||||
scanType: ['qrCode'],
|
||||
success(res) {
|
||||
console.log(res);
|
||||
},
|
||||
fail(err) {
|
||||
wx.showToast({
|
||||
title: '扫码失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.navigateTo({
|
||||
url: "/pages/joinFlow/manual/index",
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.banner {
|
||||
height: 400rpx;
|
||||
background-image: var(--background);
|
||||
padding: 30rpx;
|
||||
|
||||
.text-1 {
|
||||
color: white;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-2 {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
background-color: white;
|
||||
transform: translateY(-20rpx);
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
padding: 80rpx 30rpx 30rpx;
|
||||
|
||||
.text-1 {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 45rpx;
|
||||
}
|
||||
|
||||
.text-2 {
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
color: rgba(69, 70, 78, 1);
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
box-shadow: 0 4rpx 12rpx rgba(11, 27, 61, 0.14);
|
||||
padding: 30rpx;
|
||||
border-radius: 30rpx;
|
||||
gap: 30rpx;
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.icon {
|
||||
width: 90rpx;
|
||||
aspect-ratio: 1;
|
||||
background-color: rgba(0, 106, 106, 0.1);
|
||||
font-size: 50rpx;
|
||||
border-radius: 20rpx;
|
||||
color: rgba(46, 139, 87, 1);
|
||||
}
|
||||
|
||||
.t-1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.t-2 {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(69, 70, 78, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
background-color: rgba(255, 221, 180, 1);
|
||||
padding: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
color: rgba(99, 63, 0, 1);
|
||||
font-size: 28rpx;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<view class="banner">
|
||||
<t-navbar t-class="fixed-nav"
|
||||
fixed="{{false}}" />
|
||||
<view class="info">
|
||||
<view class="text-1">术极守护</view>
|
||||
<view class="text-2">您的贴身康复管家</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="text-1">开始使用</view>
|
||||
<view class="text-2">请选择康复计划接入方式</view>
|
||||
<view class="item flex-align"
|
||||
bind:tap="handClick"
|
||||
data-type="1">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="qrcode" />
|
||||
</view>
|
||||
<view>
|
||||
<view class="t-1">扫码接入</view>
|
||||
<view class="t-2">扫描医护人员提供的二维码</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item flex-align"
|
||||
bind:tap="handClick"
|
||||
data-type="2">
|
||||
<view class="icon flex-center">
|
||||
<t-icon name="search" />
|
||||
</view>
|
||||
<view>
|
||||
<view class="t-1">手动选择</view>
|
||||
<view class="t-2">搜索医院、科室、手术名称</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tip">
|
||||
<view>💡 温馨提示:</view>
|
||||
<view>首次使用需要录入基本信息,请准备好您的手术相关资料</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,36 +0,0 @@
|
||||
Page({
|
||||
data: {
|
||||
formData: {
|
||||
hospital: "",
|
||||
department: ""
|
||||
}
|
||||
},
|
||||
onInputChange(e) {
|
||||
console.log("dsds");
|
||||
const {field} = e.currentTarget.dataset;
|
||||
this.setData({
|
||||
[`formData.${field}`]: e.detail.value,
|
||||
});
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
let {formData} = this.data
|
||||
let errorText = ""
|
||||
if (!formData.hospital.trim()) {
|
||||
errorText = "请填写医院名称"
|
||||
}
|
||||
if (!formData.department.trim()) {
|
||||
errorText = "请填写科室名称"
|
||||
}
|
||||
if (errorText) {
|
||||
wx.showToast({
|
||||
title: errorText,
|
||||
icon: "none"
|
||||
})
|
||||
return;
|
||||
}
|
||||
wx.navigateTo({
|
||||
url: "/pages/joinFlow/person/index"
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
.content {
|
||||
margin-top: 60rpx;
|
||||
|
||||
.btn {
|
||||
margin-top: 60rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<t-navbar title="选择康复计划"
|
||||
left-arrow
|
||||
fixed="{{false}}" />
|
||||
|
||||
<view class="content">
|
||||
<t-input value="{{formData.hospital}}"
|
||||
borderless
|
||||
label="医院"
|
||||
placeholder="请输入医院名称"
|
||||
bind:change="onInputChange"
|
||||
data-field="hospital" />
|
||||
<t-input value="{{formData.department}}"
|
||||
borderless
|
||||
label="科室"
|
||||
clearable="{{false}}"
|
||||
placeholder="请输入科室名称"
|
||||
bind:change="onInputChange"
|
||||
data-field="department" />
|
||||
|
||||
<view class="btn">
|
||||
<t-button block
|
||||
theme="primary" bind:tap="onSubmit">
|
||||
确认
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,21 +1,100 @@
|
||||
import request from "@/utils/request"
|
||||
import { getToken, setToken } from "@/utils/auth/manageToken"
|
||||
import { formatDate } from "@/utils/common"
|
||||
const app = getApp()
|
||||
Page({
|
||||
data: {
|
||||
loading: true,
|
||||
parmas: {},
|
||||
mood: "manual",
|
||||
showPicker: false,
|
||||
date: new Date().getTime(),
|
||||
|
||||
//手术
|
||||
showSurgery: false,
|
||||
surgeryList: [],
|
||||
selectSurgery: [],
|
||||
//医院信息
|
||||
hospital: "",
|
||||
department: "",
|
||||
//其他信息
|
||||
age: "",
|
||||
allergy: "",
|
||||
comorbidity: "",
|
||||
},
|
||||
onLoad(e) {
|
||||
if (Object.keys(e).length > 0) {
|
||||
this.setData({
|
||||
parmas: e,
|
||||
mood: "scan"
|
||||
})
|
||||
}
|
||||
this.init()
|
||||
},
|
||||
//初始化
|
||||
async init() {
|
||||
let token = getToken()
|
||||
//如果存在
|
||||
if (token) {
|
||||
let info = await request.get("/my-info")
|
||||
this.handUser(info)
|
||||
} else {
|
||||
wx.login({
|
||||
success: async (res) => {
|
||||
let response = await request.post("/login", {
|
||||
"wx_code": res.code
|
||||
})
|
||||
setToken(response.accessToken)
|
||||
this.handUser(response)
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
//处理信息
|
||||
async handUser(data) {
|
||||
app.globalData.userInfo = data.userInfo
|
||||
if (data.needProfile == 0) {
|
||||
wx.switchTab({
|
||||
url: "/pages/home/index",
|
||||
})
|
||||
} else {
|
||||
let res = await request.get("/profile/surgicals")
|
||||
this.setData({
|
||||
surgeryList: res.list,
|
||||
})
|
||||
}
|
||||
this.setData({
|
||||
loading: false,
|
||||
})
|
||||
},
|
||||
|
||||
onInputChange(e) {
|
||||
const { field } = e.currentTarget.dataset;
|
||||
this.setData({
|
||||
[`${field}`]: e.detail.value,
|
||||
});
|
||||
},
|
||||
//选择手术
|
||||
changeSurgeryShow() {
|
||||
this.setData({
|
||||
showSurgery: !this.data.showSurgery
|
||||
})
|
||||
},
|
||||
handSelectSurgery(e) {
|
||||
let { data } = e.currentTarget.dataset
|
||||
let { selectSurgery } = this.data
|
||||
let isHave = selectSurgery.find(item => item.id == data.id)
|
||||
if (isHave) {
|
||||
this.setData({
|
||||
selectSurgery: selectSurgery.filter((item) => item.id != data.id)
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
selectSurgery: [...selectSurgery, data]
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
//选择时间
|
||||
chaneTimeShow() {
|
||||
this.setData({
|
||||
@@ -29,22 +108,26 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
//选择手术
|
||||
changeSurgeryShow() {
|
||||
this.setData({
|
||||
showSurgery: !this.data.showSurgery
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
//提交
|
||||
onSubmit() {
|
||||
let { date, age, allergy, comorbidity } = this.data
|
||||
async onSubmit() {
|
||||
let { mood, hospital, department, date, age, allergy, comorbidity, parmas, selectSurgery } = this.data
|
||||
let errorText = ""
|
||||
if (!date) {
|
||||
if (mood == 'manual' && !hospital.trim()) {
|
||||
errorText = "请输入医院"
|
||||
}
|
||||
else if (mood == 'manual' && !department.trim()) {
|
||||
errorText = "请输入科室"
|
||||
}
|
||||
|
||||
else if (selectSurgery.length == 0) {
|
||||
errorText = "请选择手术"
|
||||
}
|
||||
else if (!date) {
|
||||
errorText = "请选择时间"
|
||||
}
|
||||
if (!age.trim()) {
|
||||
else if (!age.trim()) {
|
||||
errorText = "请填写年龄"
|
||||
}
|
||||
if (errorText) {
|
||||
@@ -54,8 +137,29 @@ Page({
|
||||
})
|
||||
return
|
||||
}
|
||||
wx.switchTab({
|
||||
url: '/pages/home/index',
|
||||
})
|
||||
try {
|
||||
wx.showLoading({
|
||||
title: '提交中',
|
||||
mask: true
|
||||
})
|
||||
await request.post("/profile", {
|
||||
entry_mode: mood,
|
||||
...parmas,
|
||||
hospital_name: hospital,
|
||||
department_name: department,
|
||||
surgical_ids: selectSurgery.map(item => item.id),
|
||||
surgical_date: formatDate(date, 'YYYY-MM-DD'),
|
||||
age: age,
|
||||
allergy_history: allergy,
|
||||
complication: comorbidity
|
||||
})
|
||||
wx.hideLoading()
|
||||
wx.switchTab({
|
||||
url: '/pages/home/index',
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
wx.hideLoading()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
page {
|
||||
.page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -21,6 +21,7 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
@@ -29,6 +30,10 @@ page {
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
padding: 50rpx 0 0;
|
||||
|
||||
.ce {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-1 {
|
||||
font-weight: 700;
|
||||
font-size: 45rpx;
|
||||
|
||||
@@ -1,55 +1,84 @@
|
||||
<wxs src="/utils/wxs/comment.wxs"
|
||||
module="utils"></wxs>
|
||||
<view class="banner">
|
||||
<t-navbar t-class="fixed-nav"
|
||||
fixed="{{false}}" />
|
||||
<view class="info">
|
||||
<view class="text-1">术极守护</view>
|
||||
<view class="text-2">您的贴身康复管家</view>
|
||||
|
||||
<view class="page"
|
||||
wx:if="{{!loading}}">
|
||||
<view class="banner">
|
||||
<t-navbar t-class="fixed-nav"
|
||||
fixed="{{false}}" />
|
||||
<view class="info">
|
||||
<view class="text-1">术极守护</view>
|
||||
<view class="text-2">您的贴身康复管家</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="content"
|
||||
scroll-y>
|
||||
<view class="text-1">建立康复档案</view>
|
||||
<t-cell title="医院"
|
||||
wx:if="{{mood == 'manual'}}">
|
||||
<input slot="note"
|
||||
placeholder="请输入医院"
|
||||
data-field="hospital"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="科室"
|
||||
wx:if="{{mood == 'manual'}}">
|
||||
<input slot="note"
|
||||
placeholder="请输入科室"
|
||||
data-field="department"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="选择手术"
|
||||
arrow
|
||||
t-class-title="ce"
|
||||
bind:tap="changeSurgeryShow">
|
||||
<view slot="note">
|
||||
{{select.getNames(selectSurgery)}}
|
||||
</view>
|
||||
</t-cell>
|
||||
<t-cell title="手术日期"
|
||||
arrow
|
||||
note="{{utils.formatDate(date, 'YYYY/MM/DD') || '请选择'}}"
|
||||
bind:tap="chaneTimeShow">
|
||||
</t-cell>
|
||||
<t-cell title="年龄">
|
||||
<input type="number"
|
||||
slot="note"
|
||||
maxlength="3"
|
||||
placeholder="请输入年龄"
|
||||
data-field="age"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="过敏史">
|
||||
<textarea placeholder="请输入过敏史(如:青霉素过敏)"
|
||||
slot="description"
|
||||
data-field="allergy"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="合并症">
|
||||
<textarea placeholder="请输入合并症(如:高血压、糖尿病)"
|
||||
slot="description"
|
||||
data-field="comorbidity"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
</scroll-view>
|
||||
<view class="btn">
|
||||
<t-button block
|
||||
theme="primary"
|
||||
bind:tap="onSubmit">下一步
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="content"
|
||||
scroll-y>
|
||||
<view class="text-1">建立康复档案</view>
|
||||
<t-cell title="选择手术"
|
||||
arrow
|
||||
note="请选择"
|
||||
bind:tap="changeSurgeryShow">
|
||||
</t-cell>
|
||||
<t-cell title="手术日期"
|
||||
arrow
|
||||
note="{{utils.formatDate(date, 'YYYY/MM/DD') || '请选择'}}"
|
||||
bind:tap="chaneTimeShow">
|
||||
</t-cell>
|
||||
<t-cell title="年龄">
|
||||
<input type="number"
|
||||
slot="note"
|
||||
maxlength="3"
|
||||
placeholder="请输入年龄"
|
||||
data-field="age"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="过敏史">
|
||||
<textarea placeholder="请输入过敏史(如:青霉素过敏)"
|
||||
slot="description"
|
||||
data-field="allergy"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
<t-cell title="合并症">
|
||||
<textarea placeholder="请输入合并症(如:高血压、糖尿病)"
|
||||
slot="description"
|
||||
data-field="comorbidity"
|
||||
bind:change="onInputChange" />
|
||||
</t-cell>
|
||||
</scroll-view>
|
||||
<view class="btn">
|
||||
<t-button block
|
||||
theme="primary"
|
||||
bind:tap="onSubmit">下一步
|
||||
</t-button>
|
||||
<view class="flex-center"
|
||||
style="height: 100vh;"
|
||||
wx:else>
|
||||
<t-loading theme="circular"
|
||||
size="40rpx" />
|
||||
</view>
|
||||
|
||||
|
||||
<t-popup visible="{{showSurgery}}"
|
||||
placement="bottom"
|
||||
bind:visible-change="changeSurgeryShow">
|
||||
@@ -57,9 +86,14 @@
|
||||
<view class="title">手术列表</view>
|
||||
<scroll-view class="surgery-list"
|
||||
scroll-y>
|
||||
<view class="flex-between surgery-item " wx:for="{{6}}" wx:key="index">
|
||||
<view class="name">测试手术名字</view>
|
||||
<t-icon name="check" />
|
||||
<view class="flex-between surgery-item "
|
||||
wx:for="{{surgeryList}}"
|
||||
wx:key="index"
|
||||
data-data="{{item}}"
|
||||
bind:tap="handSelectSurgery">
|
||||
<view class="name">{{item.name}}</view>
|
||||
<t-icon name="check"
|
||||
wx:if="{{select.isSelected(item.id, selectSurgery)}}" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -72,4 +106,28 @@
|
||||
default-value="{{date}}"
|
||||
format="YYYY-MM-DD"
|
||||
bindconfirm="onTimeConfirm"
|
||||
bindclose="chaneTimeShow" />
|
||||
bindclose="chaneTimeShow" />
|
||||
|
||||
|
||||
|
||||
<wxs module="select">
|
||||
module.exports = {
|
||||
isSelected: function (id, list) {
|
||||
if (!list || !list.length) return false;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i].id === id) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getNames: function (list) {
|
||||
if (!list || !list.length) return "请选择";
|
||||
// 拼接名字,逗号分隔
|
||||
var names = "";
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
names += list[i].name;
|
||||
if (i < list.length - 1) names += ", ";
|
||||
}
|
||||
return names;
|
||||
}
|
||||
};
|
||||
</wxs>
|
||||
@@ -12,20 +12,19 @@ Page({
|
||||
let state = wx.getStorageSync(this.data.key);
|
||||
if (state) {
|
||||
wx.redirectTo({
|
||||
url: "/pages/joinFlow/access/index",
|
||||
url: "/pages/joinFlow/person/index",
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
loading: false
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
handNext() {
|
||||
wx.setStorageSync(this.data.key, true)
|
||||
wx.redirectTo({
|
||||
url: "/pages/home/index",
|
||||
url: "/pages/joinFlow/person/index"
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "trial",
|
||||
"libVersion": "3.15.1",
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"projectname": "wx_app_template",
|
||||
"setting": {
|
||||
"compileHotReLoad": true,
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
@@ -20,17 +20,5 @@
|
||||
"bigPackageSizeSupport": false
|
||||
},
|
||||
"libVersion": "3.15.1",
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "pages/home/index",
|
||||
"pathName": "pages/home/index",
|
||||
"query": "",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"condition": {}
|
||||
}
|
||||
@@ -72,4 +72,20 @@ export function uploadQiuFile(path, file, is_public = 1) {
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文字内容
|
||||
* @param {文本} text
|
||||
*/
|
||||
export function copyText(text) {
|
||||
wx.setClipboardData({
|
||||
data: text,
|
||||
success() {
|
||||
wx.showToast({
|
||||
title: '已复制到剪切板',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import httpEnv from "./auth/env"
|
||||
import { getToken, removeToken } from "./auth/manageToken"
|
||||
const baseApi = {
|
||||
//开发版
|
||||
develop: "https://curainwxapp.test.tuzuu.com/api",
|
||||
develop: "https://kairos-wx.test.tuzuu.com/api",
|
||||
// 体验版
|
||||
trial: "https://curainwxapp.test.tuzuu.com/api",
|
||||
trial: "https://kairos-wx.test.tuzuu.com/api",
|
||||
// 正式版
|
||||
release: "https://yidaojia.cells.org.cn/api"
|
||||
release: "https://kairos-wx.test.tuzuu.com/api",
|
||||
}
|
||||
//获取当前环境的接口前缀
|
||||
export const baseUrl = baseApi[httpEnv]
|
||||
@@ -82,4 +82,65 @@ request.post = function (url, options) {
|
||||
})
|
||||
}
|
||||
|
||||
//流式
|
||||
let lastChunk = '' //流可能会被截断,需要补充
|
||||
export function streamRequest(url, data, onChunkReceived) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = getToken()
|
||||
let header = {}
|
||||
if (token) {
|
||||
header.Authorization = 'Bearer ' + token;
|
||||
}
|
||||
let requestTask = wx.request({
|
||||
url: baseUrl + url,
|
||||
data: data || {},
|
||||
method: 'POST',
|
||||
header,
|
||||
timeout: 100000,
|
||||
responseType: "arraybuffer",
|
||||
enableChunked: true, //关键!开启流式传输模式
|
||||
success: () => {
|
||||
resolve()
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log(e);
|
||||
reject()
|
||||
}
|
||||
})
|
||||
//处理流
|
||||
if (onChunkReceived) {
|
||||
lastChunk = ''
|
||||
requestTask.onChunkReceived((response) => {
|
||||
let data = response.data;
|
||||
// console.log(decodeStream(data));
|
||||
onChunkReceived(decodeStream(data))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
//流素具解码
|
||||
function decodeStream(data) {
|
||||
let uint8Array = new Uint8Array(data); // 将 ArrayBuffer 转换为 Uint8Array
|
||||
let responseText = decodeURIComponent(escape(String.fromCharCode.apply(null, uint8Array))); // 使用 apply 扩展字节
|
||||
// 处理数据,移除前缀等操作
|
||||
if (lastChunk) {
|
||||
responseText = "data: " + lastChunk + responseText
|
||||
lastChunk = ""
|
||||
}
|
||||
// 处理数据,移除前缀等操作
|
||||
responseText = responseText.replaceAll("data: ", '');
|
||||
let jsonStrings = responseText.trim().split(/\n+/);
|
||||
// 过滤掉解析失败的部分
|
||||
let jsonArray = jsonStrings.map(jsonStr => {
|
||||
try {
|
||||
return JSON.parse(jsonStr);
|
||||
} catch (e) {
|
||||
lastChunk = jsonStr
|
||||
console.error('JSON格式不正确(已做兼容处理):');
|
||||
return null; // 如果解析失败,返回null
|
||||
}
|
||||
}).filter(item => item !== null);
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
export default request
|
||||
@@ -1,29 +1,63 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param {Date | String | Number} date
|
||||
* @param {String} format 格式
|
||||
*/
|
||||
formatDate: function (date, format) {
|
||||
if (!format) {
|
||||
format = 'YYYY/MM/DD hh:mm:ss';
|
||||
}
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
date = getDate(date)
|
||||
var YYYY = date.getFullYear();
|
||||
var MM = ('0' + (date.getMonth() + 1)).slice(-2)
|
||||
var DD =('0' + date.getDate()).slice(-2);
|
||||
var hh = ("0" + date.getHours()).slice(-2);
|
||||
var mm = ("0" + date.getMinutes()).slice(-2);
|
||||
var ss = ("0" + date.getSeconds()).slice(-2);
|
||||
var result = format.replace(getRegExp('YYYY'), YYYY)
|
||||
.replace(getRegExp('MM'), MM)
|
||||
.replace(getRegExp('DD'), DD)
|
||||
.replace(getRegExp('hh'), hh)
|
||||
.replace(getRegExp('mm'), mm)
|
||||
.replace(getRegExp('ss'), ss)
|
||||
return result;
|
||||
},
|
||||
};
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param {Date | String | Number} date
|
||||
* @param {String} format 格式
|
||||
*/
|
||||
formatDate: function (date, format) {
|
||||
if (!format) {
|
||||
format = 'YYYY/MM/DD hh:mm:ss';
|
||||
}
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
date = getDate(date)
|
||||
var YYYY = date.getFullYear();
|
||||
var MM = ('0' + (date.getMonth() + 1)).slice(-2)
|
||||
var DD = ('0' + date.getDate()).slice(-2);
|
||||
var hh = ("0" + date.getHours()).slice(-2);
|
||||
var mm = ("0" + date.getMinutes()).slice(-2);
|
||||
var ss = ("0" + date.getSeconds()).slice(-2);
|
||||
var result = format.replace(getRegExp('YYYY'), YYYY)
|
||||
.replace(getRegExp('MM'), MM)
|
||||
.replace(getRegExp('DD'), DD)
|
||||
.replace(getRegExp('hh'), hh)
|
||||
.replace(getRegExp('mm'), mm)
|
||||
.replace(getRegExp('ss'), ss)
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* 获取浅色标签颜色
|
||||
* @param {string} hexColor 如 "#e3f9e9"
|
||||
*/
|
||||
getLightColor: function (color, level) {
|
||||
if (!color || color.indexOf('#') === -1) return color;
|
||||
|
||||
// 默认变浅程度
|
||||
var weight = level !== undefined ? level : 0.9;
|
||||
|
||||
// 处理 #fff 这种简写
|
||||
var hex = color.slice(1);
|
||||
if (hex.length === 3) {
|
||||
hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
|
||||
}
|
||||
|
||||
// 提取 RGB
|
||||
var r = parseInt(hex.substring(0, 2), 16);
|
||||
var g = parseInt(hex.substring(2, 4), 16);
|
||||
var b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
// 与白色(255)混合逻辑:新颜色 = 原颜色 * (1 - weight) + 255 * weight
|
||||
r = Math.floor(r * (1 - weight) + 255 * weight);
|
||||
g = Math.floor(g * (1 - weight) + 255 * weight);
|
||||
b = Math.floor(b * (1 - weight) + 255 * weight);
|
||||
|
||||
// 转回 16 进制
|
||||
var toString16 = function (n) {
|
||||
var s = n.toString(16);
|
||||
return s.length === 1 ? '0' + s : s;
|
||||
};
|
||||
|
||||
return '#' + toString16(r) + toString16(g) + toString16(b);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user