feat: 个人信息认证
This commit is contained in:
parent
f6b504a2bd
commit
9b545b442d
@ -1,98 +0,0 @@
|
||||
// 患者管理模块 API 封装
|
||||
import api from '../utils/http.js'
|
||||
|
||||
// API 基础路径
|
||||
const BASE_PATH = '/order'
|
||||
|
||||
/**
|
||||
* 初始化咨询订单
|
||||
*/
|
||||
export function initConsultOrder({ doctorCode, accountId, openId, memberId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/init`, { doctorCode, accountId, openId, memberId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定订单的患者信息
|
||||
*/
|
||||
export function bindOrderPatient({ accountId, memberId, orderId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/bind-member`, { accountId, memberId, orderId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 补充病情描述
|
||||
*/
|
||||
export function submitOrderDescription({ accountId, orderId, description, diseases, images, hasVisitedHospital }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/supplement-description`, { accountId, orderId, description, diseases, images, hasVisitedHospital })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单信息
|
||||
*/
|
||||
export function getOrderInfo({ orderId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/get`, { orderId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
export function getOrderList({ accountId, page, pageSize: limit, orderStatus }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/list`, { accountId, page, limit, orderStatus })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新订单
|
||||
*/
|
||||
export function getLatestOrder({ accountId, chatGroupId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/latest`, { accountId, chatGroupId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单支付信息
|
||||
*/
|
||||
export function getOrderTradeNo({ orderId, accountId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/get-trade-no`, { accountId, orderId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
export function cancelOrder({ orderId, accountId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/cancel`, { orderId, accountId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号统计信息
|
||||
*/
|
||||
export function getAccountStats(accountId, statusList) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/account-stats`, { accountId, statusList })
|
||||
}
|
||||
|
||||
export function getConsultInfo({doctorCode, memberId, accountId}) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/get-consult-info`, { doctorCode, memberId, accountId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单最后一条消息ID
|
||||
*/
|
||||
export function updateLastMessageId({ orderId, lastMessageId }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/update-last-message`, { orderId, lastMessageId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息到群组
|
||||
* @param {Object} params
|
||||
* @param {string} params.groupId - 群组ID
|
||||
* @param {string} params.desc - 消息描述/类型,如:'WAIT_DOCTOR_ACCEPT'
|
||||
* @param {string} params.message - 消息内容
|
||||
* @param {string} params.ext - 扩展信息(JSON字符串)
|
||||
* @param {string} params.fromAccount - 发送者账号(可选)
|
||||
*/
|
||||
export function sendMessageToGroup({ groupId, desc, message, ext, fromAccount }) {
|
||||
return api.post(`${BASE_PATH}/consult-orders/send-message-to-group`, {
|
||||
groupId,
|
||||
desc,
|
||||
message,
|
||||
ext,
|
||||
fromAccount
|
||||
})
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import api from "../../utils/http.js";
|
||||
|
||||
const BASE_PATH = "/corp";
|
||||
|
||||
/**
|
||||
* 获取科室列表(按 corpId)
|
||||
* @param {Object} params
|
||||
* @param {string} params.corpId
|
||||
*/
|
||||
export function getDeptList(params = {}) {
|
||||
return api.post(`${BASE_PATH}`, { type: "getDeptList", ...params });
|
||||
}
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import api from '../../utils/http.js'
|
||||
|
||||
// API 基础路径
|
||||
const BASE_PATH = '/corp'
|
||||
|
||||
/**
|
||||
* 获取用户签名
|
||||
* @param {string} userId - 用户ID
|
||||
* @returns {Promise} 返回包含 userSig 信息的 Promise
|
||||
*/
|
||||
export const getUserSig = (userId) => {
|
||||
return api.post(`${BASE_PATH}/tencent-im/user-sig`, { userId })
|
||||
}
|
||||
|
||||
export async function getChatStatus(chatGroupId) {
|
||||
return api.post(`${BASE_PATH}/tencent-im/get-chat-status`, { chatGroupId, role: 'patient' })
|
||||
}
|
||||
|
||||
export const sendSystemMessage = (groupId, data, Desc = '', Ext = '') => {
|
||||
return api.post(`${BASE_PATH}/tencent-im/send-group-message`, {
|
||||
groupId,
|
||||
msgBody: [
|
||||
{
|
||||
MsgType: "TIMCustomElem",
|
||||
MsgContent: {
|
||||
Data: data,
|
||||
Desc,
|
||||
Ext
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组聊天记录(POST请求)
|
||||
* @param {string} groupId - 群组ID
|
||||
* @param {number} limit - 每页数量,默认20,最大100
|
||||
* @param {number} skip - 跳过数量,默认0
|
||||
* @returns {Promise} 返回包含聊天记录的 Promise
|
||||
*/
|
||||
export const getChatRecordsByGroupId = (groupId, limit = 20, skip = 0) => {
|
||||
return api.post(`${BASE_PATH}/tencent-im/chat-records`, {
|
||||
GroupId: groupId,
|
||||
limit,
|
||||
skip
|
||||
})
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import api from '../../utils/http.js'
|
||||
|
||||
// API 基础路径
|
||||
const BASE_PATH = '/corp/rate-records'
|
||||
|
||||
export const getRate = (id) => {
|
||||
return api.post(`${BASE_PATH}/get-by-id`, { id })
|
||||
}
|
||||
export function submitRate({ id, rate, words }) {
|
||||
return api.post(`${BASE_PATH}/submit`, { id, rate, words })
|
||||
}
|
||||
|
||||
export const getRateList = ({ page, pageSize, doctorId }) => {
|
||||
return api.post(`${BASE_PATH}/displayable-list`, { page, limit: pageSize, userId: doctorId })
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
import api from '../../utils/http.js'
|
||||
|
||||
const BASE_PATH = "/corp/doctors";
|
||||
|
||||
/**
|
||||
* 获取医生
|
||||
* @param {string} id - 医生ID
|
||||
* @returns {Promise<Object>} 医生数据
|
||||
*/
|
||||
export function getDoctorInfo(id) {
|
||||
return api.post(`${BASE_PATH}/get-by-id`, { id })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请医生
|
||||
* @param {string} doctorId - 医生ID
|
||||
* @returns {Promise<Object>} 邀请医生数据
|
||||
*/
|
||||
export function getDoctorByDoctorId(doctorId) {
|
||||
return api.get(`${BASE_PATH}/doctor/${doctorId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取医生
|
||||
* @param {string} accountId - 账户ID
|
||||
* @returns
|
||||
*/
|
||||
export function getDoctorInfoByAccountId(accountId) {
|
||||
return api.post(`${BASE_PATH}/get-by-account-id`, { accountId })
|
||||
}
|
||||
/**
|
||||
* 获取医生
|
||||
* @param {string} doctorId - 医生ID
|
||||
* @returns
|
||||
*/
|
||||
export function getDoctorInfoByDoctorId(doctorId) {
|
||||
return api.post(`${BASE_PATH}/get-by-doctor-id`, { doctorId })
|
||||
}
|
||||
/**
|
||||
* 创建医生
|
||||
* @param {Object} data - 医生数据
|
||||
* @param {string} data.accountId - 账户ID
|
||||
* @param {string} data.avatar - 医生头像
|
||||
* @param {string} data.name - 医生姓名
|
||||
* @param {string} data.phone - 医生手机号
|
||||
* @param {string} data.hospitalId - 医院ID
|
||||
* @param {string} data.hospitalName - 医院名称
|
||||
* @param {string} data.departmentId - 科室ID
|
||||
* @param {string} data.title - 医生职称
|
||||
* @param {string} data.specialty - 医生擅长
|
||||
* @param {string} data.intro - 个人简介
|
||||
* @param {string} data.titleCertificate - 职称证书
|
||||
* @param {string} data.practiceLicenseCode - 执业证编号
|
||||
* @param {string} data.practiceLicenseFront - 执业证正面
|
||||
* @param {string} data.practiceLicenseBack - 执业证背面
|
||||
* @param {string} data.medicalLicenseFront - 资格证正面
|
||||
* @param {string} data.medicalLicenseBack - 资格证背面
|
||||
* @param {string} data.medicalLicenseCode - 资格证编号
|
||||
* @param {string} data.idCardFront - 身份证正面
|
||||
* @param {string} data.idCardBack - 身份证背面
|
||||
* @param {string} data.workCard - 工作证
|
||||
* @returns {Promise<Object>} 创建结果
|
||||
*/
|
||||
export function createDoctorInfo(data) {
|
||||
return api.post(`${BASE_PATH}/create`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新医生
|
||||
* @param {Object} data - 医生数据
|
||||
* @returns {Promise<Object>} 更新结果
|
||||
*/
|
||||
export function updateDoctorInfo(data) {
|
||||
return api.post(`${BASE_PATH}/update`, data)
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// 患者管理模块 API 封装
|
||||
import api from '../utils/http.js'
|
||||
|
||||
// API 基础路径
|
||||
const BASE_PATH = '/medicine'
|
||||
|
||||
export function getDiagnosisList({ type, keyword, page, limit }) {
|
||||
return api.post(`${BASE_PATH}/diagnoses/list`, { type, keyword, page, limit })
|
||||
}
|
||||
|
||||
export function getAccountPrescriptions({ accountId: patientAccountId, doctorId, patientId, statusList, page, limit }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-by-patient-account`, { patientAccountId, doctorId, statusList, page, limit, patientId })
|
||||
}
|
||||
|
||||
export function getPrescriptionStats(accountId, statusList) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/account-stats`, { accountId, statusList })
|
||||
}
|
||||
|
||||
export function getPrescriptionDetail(id) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-by-id`, { id })
|
||||
}
|
||||
|
||||
export function getPrescriptionDetailWithRps(id) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-by-id-with-rps`, { id })
|
||||
}
|
||||
|
||||
export function bindSharePrescription({ id, accountId, openId, patientId }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/bind-member`, { id, accountId, openId, patientId })
|
||||
}
|
||||
|
||||
export function getPrescriptionTradeNo({ id, openId, accountId, logistics }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-trade-no`, { id, openId, accountId, logistics })
|
||||
}
|
||||
|
||||
export function cancelPrescription({ accountId, id }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/cancel`, { id, accountId })
|
||||
}
|
||||
|
||||
export function applyMedicines({ id, patientAccountId, openId }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/apply-represcription`, { id, patientAccountId, openId })
|
||||
}
|
||||
|
||||
export function getCartMedicines({ accountId, ids }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-cart-medicines`, { accountId, ids })
|
||||
}
|
||||
|
||||
export function getMergeTradeNo({ ids, openId, accountId, logistics }) {
|
||||
return api.post(`${BASE_PATH}/prescriptions/get-merge-trade-no`, { ids, openId, accountId, logistics })
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<picker mode="selector" :range="range" :disabled="disableChange" @change="change($event)">
|
||||
<picker mode="selector" :range="displayRange" :disabled="disableChange" @change="change($event)">
|
||||
<common-cell :name="name" :required="required">
|
||||
<view class="form-content__wrapper">
|
||||
<view class="flex-main-content truncate" :class="value ? '' : 'form__placeholder'">
|
||||
@ -42,12 +42,30 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const placeholder = computed(() => `请输入${props.name || ''}`)
|
||||
const value = computed(() => props.form && props.form && props.form[props.title] ? props.form[props.title] : '')
|
||||
const displayRange = computed(() => {
|
||||
// 如果range是对象数组,提取label作为显示文本
|
||||
if (Array.isArray(props.range) && props.range.length > 0 && typeof props.range[0] === 'object') {
|
||||
return props.range.map(item => item.label);
|
||||
}
|
||||
return props.range;
|
||||
})
|
||||
const value = computed(() => {
|
||||
if (!props.form || !props.form[props.title]) return '';
|
||||
|
||||
const currentValue = props.form[props.title];
|
||||
// 如果range是对象数组,找到对应的label显示
|
||||
if (Array.isArray(props.range) && props.range.length > 0 && typeof props.range[0] === 'object') {
|
||||
const option = props.range.find(item => item.value === currentValue);
|
||||
return option ? option.label : currentValue;
|
||||
}
|
||||
return currentValue;
|
||||
})
|
||||
|
||||
function change(e) {
|
||||
const selectedValue = props.range[e.detail.value];
|
||||
emits('change', {
|
||||
title: props.title,
|
||||
value: props.range[e.detail.value]
|
||||
value: selectedValue
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,417 +0,0 @@
|
||||
<template>
|
||||
<uni-popup ref="symptomPopup" type="bottom" :mask-click="false">
|
||||
<view class="symptom-popup">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">请描述您的病情</text>
|
||||
</view>
|
||||
|
||||
<view class="popup-content">
|
||||
<textarea class="symptom-textarea" v-model="symptomDescription" placeholder="请描述您的病情..." :maxlength="200"
|
||||
show-confirm-bar="false"></textarea>
|
||||
|
||||
<view class="section">
|
||||
<view class="checkbox-row">
|
||||
<text class="section-title">是否到线下医院就诊过</text>
|
||||
<uni-data-checkbox class="checkbox-group" mode="tag" v-model="hasVisitedHospital" :localdata="yesNoOptions"
|
||||
:multiple="false"></uni-data-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 诊断选择 - 只有选择"是"才显示 -->
|
||||
<view class="section" v-if="hasVisitedHospital === 'yes'">
|
||||
<text class="section-title">诊断*</text>
|
||||
<view class="diagnosis-container">
|
||||
<!-- 已选择的疾病标签 -->
|
||||
<view class="selected-diseases" v-if="selectedDiseases.length > 0">
|
||||
<view v-for="(disease, index) in selectedDiseases" :key="`${index}-common`" class="tag-item tag-item-active"
|
||||
@click="removeDiseaseTag(index)">
|
||||
{{ disease }}
|
||||
</view>
|
||||
<!-- <uni-tag v-for="(disease, index) in selectedDiseases" :key="index" :text="disease" type="primary"
|
||||
size="small" :circle="true" @click="removeDiseaseTag(index)"></uni-tag> -->
|
||||
</view>
|
||||
<!-- 添加疾病按钮 -->
|
||||
<view class="add-disease-btn" @click="goToSelectDisease">
|
||||
<view class="tag-item">+ 添加</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="upload-header">
|
||||
<text class="section-title">上传资料</text>
|
||||
<text class="upload-subtitle">病历、处方、检查检验报告等图片</text>
|
||||
</view>
|
||||
|
||||
<view class="upload-actions">
|
||||
<view class="upload-btn" @click="add()">
|
||||
<uni-icons type="camera" size="24" color="#666"></uni-icons>
|
||||
<text class="upload-text">添加照片</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已选择的图片预览 -->
|
||||
<view class="image-preview" v-if="images.length > 0">
|
||||
<view v-for="(image, index) in images" :key="image" class="preview-item">
|
||||
<image :src="image" class="preview-image" mode="aspectFill"></image>
|
||||
<view class="remove-btn" @click="remove(index)">
|
||||
<uni-icons type="close" size="16" color="white"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="popup-footer">
|
||||
<button class="popup-btn cancel-btn" @click="closeSymptomPopup">取消</button>
|
||||
<button class="popup-btn submit-btn" @click="submitSymptomDescription">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { submitOrderDescription } from '@/api/consult-order';
|
||||
import { registerOnceEvent } from '@/hooks/useOnceEvent'
|
||||
import useImageUpload from '@/hooks/useImageUpload'
|
||||
import { set } from '@/utils/cache'
|
||||
import { toast } from '@/utils/widget'
|
||||
|
||||
const emits = defineEmits(['close', 'submit'])
|
||||
// 定义组件的props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
orderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
accountId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isIMMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 定义组件的emit事件
|
||||
|
||||
// 响应式数据
|
||||
const symptomPopup = ref(null)
|
||||
const symptomDescription = ref('')
|
||||
const loading = ref(false)
|
||||
const { images, add, remove, uploadImages } = useImageUpload('order')
|
||||
|
||||
// 是否到线下医院就诊过
|
||||
const hasVisitedHospital = ref('')
|
||||
const yesNoOptions = ref([
|
||||
{ value: 'yes', text: '是' },
|
||||
{ value: 'no', text: '否' }
|
||||
])
|
||||
// 诊断选择
|
||||
const selectedDiseases = ref([])
|
||||
|
||||
|
||||
// 关闭病情描述弹窗
|
||||
const closeSymptomPopup = () => {
|
||||
emits('close')
|
||||
}
|
||||
|
||||
|
||||
// 跳转到疾病选择页面
|
||||
const goToSelectDisease = () => {
|
||||
const eventName = registerOnceEvent(changeDisease)
|
||||
set('diseaseSelected', selectedDiseases.value)
|
||||
// 将当前已选择的疾病数据传递到选择页面
|
||||
uni.navigateTo({
|
||||
url: `/pages-home/consultation/disease?eventName=${eventName}`
|
||||
})
|
||||
}
|
||||
|
||||
// 删除疾病标签
|
||||
const removeDiseaseTag = (index) => {
|
||||
selectedDiseases.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 提交病情描述
|
||||
const submitSymptomDescription = async () => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
if (!symptomDescription.value.trim() && images.value.length === 0) {
|
||||
toast('请输入病情描述或上传图片')
|
||||
loading.value = false;
|
||||
return
|
||||
}
|
||||
if (images.value.length) {
|
||||
const res = await uploadImages()
|
||||
if (!res) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 构造要提交的数据
|
||||
const descriptionData = {
|
||||
description: symptomDescription.value.trim(),
|
||||
hasVisitedHospital: hasVisitedHospital.value === 'yes',
|
||||
diseases: selectedDiseases.value,
|
||||
images: images.value
|
||||
}
|
||||
const res = await submitOrderDescription({
|
||||
accountId: props.accountId,
|
||||
orderId: props.orderId,
|
||||
...descriptionData
|
||||
})
|
||||
if (res && res.success) {
|
||||
toast(res.message);
|
||||
closeSymptomPopup()
|
||||
emits('submit', res.data)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function changeDisease(diseases) {
|
||||
selectedDiseases.value = diseases
|
||||
}
|
||||
|
||||
watch(() => props.visible, (n) => {
|
||||
if (n) {
|
||||
symptomPopup.value && symptomPopup.value.open()
|
||||
} else {
|
||||
symptomPopup.value && symptomPopup.value.close()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 病情描述弹窗样式 */
|
||||
.symptom-popup {
|
||||
background-color: white;
|
||||
border-top-left-radius: 16rpx;
|
||||
border-top-right-radius: 16rpx;
|
||||
max-height: 75vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 999; // 确保弹窗在最上层,避免被 ConsultationBar 遮挡
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 20rpx 20rpx 0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: $font-size-text;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
flex: 1;
|
||||
padding: 10rpx 20rpx 20rpx 20rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.symptom-textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
background-color: #f8f9fa;
|
||||
border: 1rpx solid #e9ecef;
|
||||
border-radius: 16rpx;
|
||||
padding: 12rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: $font-size-text;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: $font-size-tip;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.upload-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.upload-header .section-title {
|
||||
margin-bottom: 0;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.upload-header .upload-subtitle {
|
||||
margin-bottom: 0;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.upload-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background-color: #f8f9fa;
|
||||
border: 1rpx solid #e9ecef;
|
||||
border-radius: 16rpx;
|
||||
gap: 8rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.upload-btn:active {
|
||||
background-color: #e9ecef;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: $font-size-tip;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
position: relative;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #ff4757;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: $font-size-text;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f8f9fa;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.cancel-btn:active {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.popup-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.checkbox-row .uni-data-checklist {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 诊断容器样式 */
|
||||
.diagnosis-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.selected-diseases {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.add-disease-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
border: 1px solid #eee;
|
||||
padding: 8rpx 30rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tag-item-active {
|
||||
background-color: #0074ff;
|
||||
border-color: #0074ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<view v-if="data" class="symptom-detail">
|
||||
<view class="symptom-row">
|
||||
<text class="label-text">患者:</text>
|
||||
<text class="content-text">{{ data.patient }}</text>
|
||||
</view>
|
||||
<view class="symptom-row">
|
||||
<text class="label-text">病情描述:</text>
|
||||
<text class="content-text">{{ data.description }}</text>
|
||||
</view>
|
||||
<view v-if="data.diseases" class="symptom-row">
|
||||
<text class="label-text">线下确诊疾病:</text>
|
||||
<text class="content-text">{{ data.diseases }}</text>
|
||||
</view>
|
||||
<view v-if="data.images && data.images.length" class="symptom-row" @click="previewImage()">
|
||||
<text class="label-text">附件:</text>
|
||||
<text class="preview-btn">{{ data.images.length }}张 点击查看</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
payload: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
try {
|
||||
const extension = JSON.parse(props.payload.extension);
|
||||
return {
|
||||
patient: typeof extension.patient === 'string' ? extension.patient : '',
|
||||
description: typeof extension.description === 'string' ? extension.description : '',
|
||||
hasVisitedHospital: typeof extension.hasVisitedHospital === 'boolean' ? extension.hasVisitedHospital : false,
|
||||
diseases: typeof extension.diseases === 'string' ? extension.diseases : '',
|
||||
images: Array.isArray(extension.images) ? extension.images : [],
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
})
|
||||
|
||||
|
||||
function previewImage() {
|
||||
console.log('预览图片')
|
||||
uni.previewImage({
|
||||
urls: data.value.images
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style scpoed>
|
||||
/* 病情描述样式 */
|
||||
.symptom-detail {
|
||||
min-width: 360rpx;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.symptom-row {
|
||||
padding: 4rpx 0;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
color: #0074ff;
|
||||
line-height: 36rpx;
|
||||
font-size: 24rpx;
|
||||
padding: 0 12rpx;
|
||||
border-radius: 22rpx;
|
||||
}
|
||||
</style>
|
||||
@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<view v-if="data" class="card-detail">
|
||||
<view class="card-row">
|
||||
<text class="label-text">患者:</text>
|
||||
<text class="content-text">{{ data.patient }}</text>
|
||||
</view>
|
||||
<view class="card-row">
|
||||
<text class="label-text">诊断:</text>
|
||||
<text class="content-text">{{ data.diseases }}</text>
|
||||
</view>
|
||||
<view v-if="data.medicines.length && data.type === 'western'" class="card-row card-row--flex">
|
||||
<view class="row-label label-text">RP:</view>
|
||||
<view class="row-content">
|
||||
<view v-for="(i, idx) in data.medicines" :key="idx">
|
||||
<view class="content-text"> {{ i.info }}</view>
|
||||
<view class="label-text"> {{ i.useInfo }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="data.type === 'chinese'" class="card-row card-row--flex">
|
||||
<view class="row-label label-text">RP: </view>
|
||||
<view class="row-content">
|
||||
<view class="content-text">中药处方</view>
|
||||
<view class="label-text">
|
||||
共{{ data.doseNum }}剂, 每日{{ data.dailyDoseNum }}剂,1剂分{{ data.timesPerDosage }}次服用, {{ data.administrationTime
|
||||
}},
|
||||
{{ data.instructions || '' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="buy-btn" @click="toBuy()">立即购买</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getPrescriptionDetail } from '@/api/medicine';
|
||||
import { toast } from '@/utils/widget';
|
||||
const props = defineProps({
|
||||
payload: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
try {
|
||||
const extension = JSON.parse(props.payload.extension);
|
||||
return {
|
||||
...extension,
|
||||
id: extension.id,
|
||||
patient: typeof extension.patient === 'string' ? extension.patient : '',
|
||||
diseases: typeof extension.diseases === 'string' ? extension.diseases : '',
|
||||
medicines: Array.isArray(extension.medicines) ? formatWesternMedicine(extension.medicines) : [],
|
||||
type: typeof extension.type === 'string' ? extension.type : '',
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
})
|
||||
|
||||
function formatWesternMedicine(medicines) {
|
||||
return medicines.map(med => {
|
||||
const info = `${med.name} ${med.specification || ''} x${med.quantity || ''}${med.unit || ''}`;
|
||||
const useInfo = `${med.administrationTime || ''} ${med.usageName || ''}, ${med.frequencyName}, 每次${med.dosage || ''} ${med.dosageUnit || ''} ${med.medicationCycle ? `用药${med.medicationCycle}`:''} `
|
||||
return { info, useInfo }
|
||||
});
|
||||
}
|
||||
|
||||
async function toBuy() {
|
||||
const res = await getPrescriptionDetail(data.value.id);
|
||||
if (res.data && res.data.status === 'init') {
|
||||
uni.navigateTo({
|
||||
url: `/pages-cart/payment?ids=${data.value.id}`
|
||||
})
|
||||
} else if (res.data && res.data.status == 'abolished') {
|
||||
toast('处方已作废')
|
||||
} else if (res.data) {
|
||||
toast('处方已失效')
|
||||
} else {
|
||||
toast('查询处方信息失败')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
<style scpoed>
|
||||
/* 病情描述样式 */
|
||||
.card-detail {
|
||||
min-width: 360rpx;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
padding: 4rpx 0;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.card-row--flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card-row--flex .row-label {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-row--flex .row-content {
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
color: #0074ff;
|
||||
line-height: 36rpx;
|
||||
font-size: 24rpx;
|
||||
padding: 0 12rpx;
|
||||
border-radius: 22rpx;
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
background-color: #0074ff;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 60rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@ -1,574 +0,0 @@
|
||||
<template>
|
||||
<view class="message-card" :class="cardTypeClass">
|
||||
<view class="card-header">
|
||||
<text class="card-title">{{ title }}</text>
|
||||
<view v-if="showDetail" class="detail-btn" @click.stop="handleDetail">
|
||||
<text class="detail-text">详情</text>
|
||||
<uni-icons type="right" size="14" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<!-- 病情描述类型 -->
|
||||
<fill-description-card v-if="payload.description === 'PATIENT_FILL_CONSULTATION'" :payload="payload" />
|
||||
<medicines-card v-else-if="payload.description === 'DOCTOR_WRITE_PRESCRIPTION'" :payload="payload" />
|
||||
<refill-medicine-card v-else-if="payload.description === 'USER_REFILL_PRESCRIPTION'" :payload="payload" />
|
||||
<reject-refill-medicine-card v-else-if="payload.description === 'REJECT_REFILL_PRESCRIPTION'"
|
||||
:payload="payload" />
|
||||
|
||||
<!-- 续方申请类型 -->
|
||||
<template v-if="messageData.messageType === 'refill'">
|
||||
<view class="refill-detail">
|
||||
<!-- 诊断信息 -->
|
||||
<view class="refill-diagnosis">
|
||||
<text class="label-text">诊断:</text>
|
||||
<text class="content-text">{{ messageData.diagnosis }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 药品信息 -->
|
||||
<view class="refill-medicines">
|
||||
<text class="section-title">药品:</text>
|
||||
|
||||
<!-- 中药处方 -->
|
||||
<view v-if="messageData.prescriptionType === '中药处方'" class="chinese-medicine-refill">
|
||||
<text class="medicine-type-title">{{
|
||||
messageData.prescriptionType
|
||||
}}</text>
|
||||
<text class="medicine-description">{{
|
||||
messageData.prescriptionDesc
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 西药处方 -->
|
||||
<view v-else class="western-medicine-refill">
|
||||
<view v-for="(medicine, index) in messageData.medicines" :key="index" class="medicine-item-refill">
|
||||
<view class="medicine-info-refill">
|
||||
<text class="medicine-name-refill">{{ medicine.name }} {{ medicine.spec }}</text>
|
||||
<text class="medicine-count-refill">{{
|
||||
medicine.count
|
||||
}}</text>
|
||||
</view>
|
||||
<text class="medicine-usage-refill">{{ medicine.usage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 问卷调查类型 -->
|
||||
<template v-if="messageData.messageType === 'survey'">
|
||||
<view class="survey-detail">
|
||||
<!-- 问卷标题和描述 -->
|
||||
<view class="survey-header">
|
||||
<view class="survey-info">
|
||||
<text class="survey-title">{{ messageData.surveyTitle }}</text>
|
||||
<text class="survey-subtitle">{{
|
||||
messageData.surveyDescription
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 问卷详情 -->
|
||||
<view class="survey-content">
|
||||
<view class="survey-item">
|
||||
<text class="survey-label">问卷名称:</text>
|
||||
<text class="survey-value">{{ messageData.surveyName }}</text>
|
||||
</view>
|
||||
<view class="survey-item">
|
||||
<text class="survey-label">预计用时:</text>
|
||||
<text class="survey-value">{{ messageData.estimatedTime }}</text>
|
||||
</view>
|
||||
<view class="survey-item" v-if="messageData.reward">
|
||||
<text class="survey-label">完成奖励:</text>
|
||||
<text class="survey-value reward-text">{{
|
||||
messageData.reward
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 问卷说明 -->
|
||||
<view class="survey-note" v-if="messageData.note">
|
||||
<text class="note-text">{{ messageData.note }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 立即购买按钮 -->
|
||||
<view class="card-footer" @click="handlePurchase" v-if="messageData.messageType === 'prescription'">
|
||||
<text class="view-all-text">立即购药</text>
|
||||
</view>
|
||||
|
||||
<!-- 续方申请按钮 -->
|
||||
<view class="card-footer" @click="handleBuy" v-if="messageData.messageType === 'refill'">
|
||||
<text class="view-all-text">申请再次购买</text>
|
||||
</view>
|
||||
|
||||
<!-- 问卷调查按钮 -->
|
||||
<view class="card-footer" @click="handleBuy" v-if="messageData.messageType === 'survey'">
|
||||
<text class="view-all-text">去填写</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { sendSystemMessage } from "@/api/corp/im.js";
|
||||
|
||||
import fillDescriptionCard from "./fill-description-card.vue";
|
||||
import medicinesCard from "./medicines-card.vue";
|
||||
import refillMedicineCard from "./refill-medicine-card.vue";
|
||||
import rejectRefillMedicineCard from "./reject-refill-medicine-card.vue";
|
||||
|
||||
const props = defineProps({
|
||||
payload: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
messageData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
flow: {
|
||||
type: String,
|
||||
default: 'out'
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["viewDetail"]);
|
||||
const CardTitle = {
|
||||
PATIENT_FILL_CONSULTATION: "患者病情描述",
|
||||
USER_REFILL_PRESCRIPTION: '续方申请',
|
||||
DOCTOR_WRITE_PRESCRIPTION: "医嘱",
|
||||
REJECT_REFILL_PRESCRIPTION: '续方失败',
|
||||
symptom: "病情描述",
|
||||
prescription: "处方单",
|
||||
refill: "续方申请",
|
||||
survey: "问卷调查",
|
||||
};
|
||||
|
||||
// const showDetailBtn = computed(() => {
|
||||
// return ['DOCTOR_WRITE_PRESCRIPTION'].includes(props.payload.description)
|
||||
// });
|
||||
|
||||
// 计算卡片标题
|
||||
const title = computed(() => {
|
||||
return CardTitle[props.payload.description] || CardTitle[props.messageData.messageType] || "消息卡片";
|
||||
});
|
||||
|
||||
const extension = computed(() => {
|
||||
try {
|
||||
return JSON.parse(props.payload.extension);
|
||||
} catch (e) { }
|
||||
return {}
|
||||
})
|
||||
|
||||
const showDetail = computed(() => {
|
||||
if (props.payload.description === 'DOCTOR_WRITE_PRESCRIPTION' && extension.value.id) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
function handleDetail() {
|
||||
if (props.payload.description === 'DOCTOR_WRITE_PRESCRIPTION' && extension.value.id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-home/prescription/detail?id=${extension.value.id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 是否有附件
|
||||
const hasAttachment = computed(() => {
|
||||
return props.messageData.images && props.messageData.images.length > 0;
|
||||
});
|
||||
|
||||
// 附件数量
|
||||
const number = computed(() => {
|
||||
return `${props.messageData.images.length}张`;
|
||||
});
|
||||
|
||||
// 计算卡片样式类
|
||||
const cardTypeClass = computed(() => {
|
||||
return `card-${props.messageData.messageType}`;
|
||||
});
|
||||
|
||||
// 处理查看详情点击
|
||||
const handleBuy = () => {
|
||||
emit("viewDetail", props.messageData);
|
||||
};
|
||||
|
||||
// 处理立即购买点击
|
||||
const handlePurchase = () => {
|
||||
console.log('购买成功', props.messageData);
|
||||
const groupID = props.messageData.conversationID.replace('GROUP', '');
|
||||
sendSystemMessage(groupID, 'purchased');
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.message-card {
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-symptom,
|
||||
.card-prescription,
|
||||
.card-refill,
|
||||
.card-survey {
|
||||
background-color: $primary-color;
|
||||
}
|
||||
|
||||
|
||||
.card-header {
|
||||
padding: 12rpx 12rpx 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
color: white;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.detail-arrow {
|
||||
color: white;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin: 10rpx;
|
||||
padding: 12rpx 16rpx 16rpx;
|
||||
background-color: white;
|
||||
border-radius: 4rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 医院就诊信息样式 */
|
||||
.hospital-visit-info {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.visit-text {
|
||||
color: #666;
|
||||
font-size: 13rpx;
|
||||
}
|
||||
|
||||
/* 处方单样式 */
|
||||
.prescription-detail {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.prescription-section {
|
||||
width: 420rpx;
|
||||
margin-bottom: 8rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.medicine-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.medicine-item-wrapper {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
}
|
||||
|
||||
.medicine-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.medicine-usage {
|
||||
margin-top: 4rpx;
|
||||
padding-top: 4rpx;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.medicine-name {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.medicine-spec {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.medicine-count {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
min-width: 30rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.usage-text {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 中药处方样式 */
|
||||
.tcm-prescription {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.tcm-label {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background-color: $primary-color;
|
||||
border-radius: 4rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
text-align: center;
|
||||
margin: 0 -4rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.card-footer:active {
|
||||
background-color: $primary-color;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.view-all-text {
|
||||
color: white;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 续方申请样式 */
|
||||
.refill-detail {
|
||||
margin-bottom: 12rpx;
|
||||
width: 420rpx;
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.refill-patient-info {
|
||||
margin-bottom: 12rpx;
|
||||
padding-bottom: 8rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.refill-diagnosis {
|
||||
margin-bottom: 12rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.refill-diagnosis .label-text {
|
||||
min-width: 50rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.refill-medicines {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.refill-medicines .section-title {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
/* 中药续方样式 */
|
||||
.chinese-medicine-refill {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.medicine-type-title {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.medicine-description {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 西药续方样式 */
|
||||
.western-medicine-refill {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.medicine-item-refill {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
padding: 10rpx 12rpx;
|
||||
}
|
||||
|
||||
.medicine-info-refill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.medicine-name-refill {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.medicine-count-refill {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
min-width: 30rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.medicine-usage-refill {
|
||||
color: #666;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 问卷调查样式 */
|
||||
.survey-detail {
|
||||
margin-bottom: 12rpx;
|
||||
width: 420rpx;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.survey-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 12rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.survey-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.survey-title {
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.survey-subtitle {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.survey-content {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.survey-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.survey-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.survey-label {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
min-width: 80rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.survey-value {
|
||||
color: #333;
|
||||
font-size: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.reward-text {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.survey-note {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6rpx;
|
||||
padding: 10rpx 12rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.note-text {
|
||||
color: #666;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
@ -1,102 +0,0 @@
|
||||
<template>
|
||||
<view v-if="data" class="card-detail">
|
||||
<view class="card-row">
|
||||
<text class="label-text">患者:</text>
|
||||
<text class="content-text">{{ data.patient }}</text>
|
||||
</view>
|
||||
<view class="card-row">
|
||||
<text class="label-text">诊断:</text>
|
||||
<text class="content-text">{{ data.diseases }}</text>
|
||||
</view>
|
||||
<view v-if="data.medicines.length && data.type === 'western'" class="card-row card-row--flex">
|
||||
<view class="row-label label-text">RP:</view>
|
||||
<view class="row-content">
|
||||
<view v-for="i in data.medicines" :key="i._id">
|
||||
<view class="content-text"> {{ i.info }}</view>
|
||||
<view class="label-text"> {{ i.useInfo }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="data.type === 'chinese'" class="card-row card-row--flex">
|
||||
<view class="row-label label-text">RP: </view>
|
||||
<view class="row-content">
|
||||
<view class="content-text">中药处方</view>
|
||||
<view class="label-text">
|
||||
共{{ data.doseNum }}剂, 每日{{ data.dailyDoseNum }}剂,1剂分{{ data.timesPerDosage }}服用, {{ data.administrationTime
|
||||
}},
|
||||
{{ data.instructions || '' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
payload: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const data = computed(() => {
|
||||
try {
|
||||
const extension = JSON.parse(props.payload.extension);
|
||||
return {
|
||||
...extension,
|
||||
patient: typeof extension.patient === 'string' ? extension.patient : '',
|
||||
diseases: typeof extension.diseases === 'string' ? extension.diseases : '',
|
||||
medicines: Array.isArray(extension.medicines) ? formatWesternMedicine(extension.medicines) : [],
|
||||
type: typeof extension.type === 'string' ? extension.type : '',
|
||||
}
|
||||
} catch (e) { }
|
||||
return null
|
||||
})
|
||||
|
||||
function formatWesternMedicine(medicines) {
|
||||
return medicines.map(med => {
|
||||
const info = `${med.name} ${med.specification || ''} x${med.quantity || ''}${med.unit || ''}`;
|
||||
const useInfo = `${med.administrationTime || ''} ${med.usageName || ''}, ${med.frequencyName}, 每次${med.dosage || ''} ${med.dosageUnit || ''} `
|
||||
return { info, useInfo }
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scpoed>
|
||||
/* 病情描述样式 */
|
||||
.card-detail {
|
||||
min-width: 360rpx;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
padding: 4rpx 0;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.card-row--flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card-row--flex .row-label {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-row--flex .row-content {
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<view v-if="data" class="card-detail">
|
||||
<view class="card-row">
|
||||
<text class="label-text">理由:</text>
|
||||
<text class="content-text">{{ data.reason || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
payload: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
try {
|
||||
const extension = JSON.parse(props.payload.extension);
|
||||
return extension
|
||||
} catch (e) { }
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
<style scpoed>
|
||||
/* 病情描述样式 */
|
||||
.card-detail {
|
||||
min-width: 360rpx;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
padding: 4rpx 0;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -1,30 +0,0 @@
|
||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import dayjs from 'dayjs';
|
||||
import { getChatOrder, orderStatus } from "@/utils/order.js";
|
||||
|
||||
export default function useChatOrder(orderId) {
|
||||
const currentOrder = ref(null);// 当前聊天室的最新咨询订单
|
||||
const countdown = ref(''); // 倒计时
|
||||
|
||||
const chatRoomStatus = computed(() => {
|
||||
const order = currentOrder.value || {};
|
||||
const isWaiting = order.orderStatus === orderStatus.PAID;
|
||||
const isPending = order.orderStatus === orderStatus.CONSULTING;
|
||||
return { isWaiting, isPending };
|
||||
})
|
||||
|
||||
async function getCurrentOrder() {
|
||||
if (orderId.value) {
|
||||
currentOrder.value = await getChatOrder(orderId.value)
|
||||
} else {
|
||||
currentOrder.value = null
|
||||
}
|
||||
}
|
||||
|
||||
watch(orderId, n=>{
|
||||
getCurrentOrder()
|
||||
})
|
||||
|
||||
return { currentOrder, chatRoomStatus, countdown, getCurrentOrder }
|
||||
}
|
||||
@ -1,146 +0,0 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import { getChatStatus } from "@/api/corp/im.js";
|
||||
import { getDoctorByDoctorId } from '@/api/doctor/doctor';
|
||||
import { toast } from '@/utils/widget';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import useChatOrder from './chat-order-hook';
|
||||
|
||||
|
||||
/**
|
||||
* 获取聊天室当前进行中业务hook
|
||||
* @param {string聊天群组ID} groupID
|
||||
*/
|
||||
export default function useChatBusiness(groupID) {
|
||||
const groupInfo = ref({});
|
||||
|
||||
const assistant = computed(() => {
|
||||
if (groupInfo.value.assistant?.memberAccount) {
|
||||
return { assistantId: groupInfo.value.assistant.memberAccount, name: groupInfo.value.assistant.name }
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
const doctor = computed(() => {
|
||||
// 优先返回从API获取的详细医生信息
|
||||
if (doctorInfo.value) {
|
||||
return doctorInfo.value;
|
||||
}
|
||||
// 如果详细信息还未加载,返回基本信息
|
||||
if (groupInfo.value.doctor?.memberAccount) {
|
||||
return {
|
||||
doctorId: groupInfo.value.doctor.memberAccount,
|
||||
name: groupInfo.value.doctor.name,
|
||||
avatar: groupInfo.value.doctor.avatar
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
||||
const patient = computed(() => {
|
||||
if (groupInfo.value.patient?.memberAccount) {
|
||||
return { openId: groupInfo.value.patient.memberAccount, name: groupInfo.value.patient.name, patientId: groupInfo.value.patient.patientId }
|
||||
}
|
||||
return null;
|
||||
})
|
||||
const chatMember = computed(() => {
|
||||
const res = {
|
||||
assistant: { name: '医生助理', avatar: '/static/assistant.png' }
|
||||
};
|
||||
if (assistant.value) {
|
||||
res[assistant.value.assistantId] = { name: `医生助理${assistant.value.name}`, avatar: '/static/assistant.png' };
|
||||
}
|
||||
if (doctor.value) {
|
||||
|
||||
res[doctor.value.doctorId] = { name: `医生${doctor.value.name || ''}`, avatar: doctor.value.avatar || '/static/home/doctor.svg' };
|
||||
}
|
||||
if (patient.value) {
|
||||
res[patient.value.openId] = { name: '我' || `患者${patient.value.name}`, avatar: '/static/center/user-avatar.png' };
|
||||
}
|
||||
return res;
|
||||
})
|
||||
|
||||
const chatRoomBusiness = computed(() => groupInfo.value.groupBusiness || {});
|
||||
const isPending = computed(() => groupInfo.value.status === 'active');
|
||||
const isClosed = computed(() => !isPending.value);
|
||||
const doctorInfo = ref(null) // 医生信息
|
||||
|
||||
const orderId = computed(() => {
|
||||
return chatRoomBusiness.value.businessType === 'consultation' ? chatRoomBusiness.value.businessId : ''
|
||||
})
|
||||
|
||||
const { currentOrder, chatRoomStatus, countdown, getCurrentOrder } = useChatOrder(orderId)
|
||||
|
||||
const chatRoomCountDown = ref('') // 会话倒计时
|
||||
const showCountdown = computed(() => {
|
||||
const isPendingOrder = currentOrder.value && chatRoomStatus.value.isPending && orderId.value == currentOrder.value.orderId;
|
||||
const isRefill = chatRoomBusiness.value.businessType === 'refill_prescription'
|
||||
return chatRoomCountDown.value && (isPendingOrder || isRefill)
|
||||
})
|
||||
|
||||
async function getChatBusiness() {
|
||||
if (!groupID.value) return;
|
||||
const res = await getChatStatus(groupID.value);
|
||||
if (res && res.success) {
|
||||
groupInfo.value = res.data;
|
||||
setCountdown()
|
||||
getDoctor()
|
||||
} else {
|
||||
await toast('获取聊天室业务失败')
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
function setCountdown() {
|
||||
if (isPending.value && groupInfo.value.expireTime && dayjs(groupInfo.value.expireTime).isAfter(dayjs())) {
|
||||
if (getApp().chatRoomCountDownTimer) clearInterval(getApp().chatRoomCountDownTimer);
|
||||
getApp().chatRoomCountDownTimer = setInterval(() => {
|
||||
const now = dayjs();
|
||||
const endTime = dayjs(groupInfo.value.expireTime);
|
||||
const diff = endTime.diff(now, 'second');
|
||||
if (diff <= 0) {
|
||||
clearCountdown();
|
||||
getChatBusiness()
|
||||
return;
|
||||
}
|
||||
// 计算剩余时分秒
|
||||
const hours = Math.floor(diff / 3600);
|
||||
const minutes = Math.floor((diff % 3600) / 60);
|
||||
const seconds = diff % 60;
|
||||
|
||||
const pad = n => n.toString().padStart(2, '0');
|
||||
chatRoomCountDown.value = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`.replace(/^00\:/, '');
|
||||
}, 1000)
|
||||
} else {
|
||||
clearCountdown()
|
||||
}
|
||||
}
|
||||
|
||||
function clearCountdown() {
|
||||
if (getApp().chatRoomCountDownTimer) {
|
||||
clearInterval(getApp().chatRoomCountDownTimer);
|
||||
getApp().chatRoomCountDownTimer = null;
|
||||
}
|
||||
chatRoomCountDown.value = ''
|
||||
}
|
||||
|
||||
async function getDoctor() {
|
||||
const doctorId = groupInfo.value.doctor?.memberAccount;
|
||||
if (doctorId && (!doctorInfo.value || doctorInfo.value.doctorId !== doctorId)) {
|
||||
const res = await getDoctorByDoctorId(doctorId)
|
||||
doctorInfo.value = res && res.data ? res.data : null;
|
||||
}
|
||||
}
|
||||
|
||||
onUnload(() => {
|
||||
clearCountdown()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
getChatBusiness()
|
||||
})
|
||||
|
||||
|
||||
return { chatRoomBusiness, getChatBusiness, currentOrder, chatRoomStatus, countdown, getCurrentOrder, chatRoomCountDown, showCountdown, doctorInfo, isClosed, chatMember }
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
name="姓名"
|
||||
:required="true"
|
||||
:form="formData"
|
||||
title="name"
|
||||
title="anotherName"
|
||||
@change="handleFieldChange"
|
||||
/>
|
||||
<!-- 头像 -->
|
||||
@ -67,7 +67,6 @@
|
||||
</view>
|
||||
</common-cell>
|
||||
|
||||
|
||||
<!-- 个人介绍 -->
|
||||
<form-textarea
|
||||
name="个人介绍"
|
||||
@ -98,9 +97,10 @@ import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue
|
||||
import api from "@/utils/api.js";
|
||||
const { account, doctorInfo } = storeToRefs(useAccountStore());
|
||||
const { useLoad } = useGuard();
|
||||
const { getDoctorInfo } = useAccountStore();
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
name: "",
|
||||
anotherName: "",
|
||||
avatar: "",
|
||||
gender: "",
|
||||
mobile: "",
|
||||
@ -113,50 +113,38 @@ const formData = ref({
|
||||
});
|
||||
|
||||
// 选项数据
|
||||
const genderOptions = ["男", "女"];
|
||||
const genderOptions = [
|
||||
{ label: "男", value: "0" },
|
||||
{ label: "女", value: "1" },
|
||||
];
|
||||
const positionOptions = ["医生", "护士", "药师", "技师", "其他"];
|
||||
const titleOptions = ["主任医师", "副主任医师", "主治医师", "医师", "其他"];
|
||||
|
||||
// 字段变更处理
|
||||
const handleFieldChange = (e) => {
|
||||
formData.value[e.title] = e.value;
|
||||
if (e.title === "gender") {
|
||||
formData.value[e.title] = e.value.value;
|
||||
} else {
|
||||
formData.value[e.title] = e.value;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择头像
|
||||
const chooseAvatar = async () => {
|
||||
const uploadRes = await chooseAndUploadImage({
|
||||
businessType: "other",
|
||||
accessLevel: "public",
|
||||
loadingTitle: "上传中...",
|
||||
});
|
||||
if (uploadRes?.previewUrl) {
|
||||
formData.value.avatar = uploadRes.previewUrl;
|
||||
const url = await chooseAndUploadImage();
|
||||
if (url) {
|
||||
formData.value.avatar = url;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
// 验证必填字段
|
||||
if (!formData.value.name) {
|
||||
uni.showToast({
|
||||
title: "请输入姓名",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showLoading({
|
||||
title: "保存中...",
|
||||
});
|
||||
createDoctorInfo();
|
||||
};
|
||||
|
||||
useLoad(() => {
|
||||
debugger
|
||||
if (doctorInfo.value) {
|
||||
formData.value = doctorInfo.value;
|
||||
formData.value = { ...doctorInfo.value };
|
||||
} else {
|
||||
formData.value.mobile = account.value.mobile;
|
||||
}
|
||||
@ -164,12 +152,22 @@ useLoad(() => {
|
||||
|
||||
// 创建医生信息
|
||||
const createDoctorInfo = async () => {
|
||||
if (!formData.value.anotherName) {
|
||||
uni.showToast({
|
||||
title: "请输入姓名",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
anotherName: formData.value.name,
|
||||
anotherName: formData.value.anotherName,
|
||||
avatar: formData.value.avatar,
|
||||
gender: formData.value.gender,
|
||||
mobile: formData.value.mobile,
|
||||
weChatOpenId: account.value.openid,
|
||||
deptIds: [],
|
||||
loginTypes: ["wxApp"],
|
||||
corpId: account.value.corpId,
|
||||
};
|
||||
const res = await api("addCorpMember", {
|
||||
params,
|
||||
@ -179,6 +177,8 @@ const createDoctorInfo = async () => {
|
||||
title: "创建成功",
|
||||
icon: "success",
|
||||
});
|
||||
await getDoctorInfo();
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "创建失败",
|
||||
@ -187,6 +187,25 @@ const createDoctorInfo = async () => {
|
||||
console.error("创建医生信息失败:", res);
|
||||
}
|
||||
};
|
||||
|
||||
const updateDoctorInfo = async () => {
|
||||
let params = {
|
||||
anotherName: formData.value.anotherName,
|
||||
avatar: formData.value.avatar,
|
||||
gender: formData.value.gender,
|
||||
};
|
||||
const res = await api("updateCorpMember", {
|
||||
params,
|
||||
});
|
||||
if (res.success && res.data) {
|
||||
uni.showToast({
|
||||
title: "更新成功",
|
||||
icon: "success",
|
||||
});
|
||||
}
|
||||
await getDoctorInfo();
|
||||
uni.navigateBack();
|
||||
};
|
||||
// 打开科室选择
|
||||
const openDepartmentSelect = () => {
|
||||
uni.navigateTo({
|
||||
|
||||
@ -14,10 +14,7 @@ export default defineStore("accountStore", () => {
|
||||
const openid = ref("");
|
||||
// 医生信息
|
||||
const doctorInfo = ref(null);
|
||||
|
||||
|
||||
async function login(phoneCode = '') {
|
||||
debugger
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
@ -26,7 +23,6 @@ export default defineStore("accountStore", () => {
|
||||
provider: "weixin",
|
||||
scope: "snsapi_base",
|
||||
});
|
||||
console.log('logincode: ', code)
|
||||
if (code) {
|
||||
const res = await api('wxAppLogin', {
|
||||
phoneCode,
|
||||
@ -35,19 +31,12 @@ export default defineStore("accountStore", () => {
|
||||
loading.value = false;
|
||||
if (res.success && res.data) {
|
||||
if (!res.data.mobile) {
|
||||
const pages = getCurrentPages();
|
||||
const current = pages[pages.length - 1];
|
||||
const params = current && current.options
|
||||
? Object.keys(current.options).map(key => `${key}=${current.options[key]}`).join('&')
|
||||
: '';
|
||||
const redirectUrl = current && current.route ? `/${current.route}${params ? `?${params}` : ''}` : '';
|
||||
const target = redirectUrl ? `/pages/login/login?redirect=${encodeURIComponent(redirectUrl)}` : '/pages/login/login';
|
||||
const target = '/pages/login/login';
|
||||
uni.redirectTo({ url: target });
|
||||
return;
|
||||
}
|
||||
account.value = res.data;
|
||||
openid.value = res.data.openid;
|
||||
debugger;
|
||||
await getDoctorInfo(openid.value);
|
||||
return res.data
|
||||
}
|
||||
@ -58,11 +47,10 @@ export default defineStore("accountStore", () => {
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function getDoctorInfo(weChatOpenId) {
|
||||
async function getDoctorInfo() {
|
||||
try {
|
||||
const res = await api('getCorpMemberData', {
|
||||
weChatOpenId,
|
||||
weChatOpenId: account.value.openid,
|
||||
});
|
||||
if (res.success && res.data) {
|
||||
doctorInfo.value = res.data;
|
||||
@ -72,5 +60,5 @@ export default defineStore("accountStore", () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { account, openid, doctorInfo, login }
|
||||
return { account, openid, doctorInfo, login, getDoctorInfo }
|
||||
})
|
||||
@ -10,7 +10,8 @@ const urlsConfig = {
|
||||
getDeptList: 'getRealDeptList',
|
||||
getHospitalList: 'getRealHospital',
|
||||
addCorpMember: 'addCorpMember',
|
||||
getCorpMemberData: 'getCorpMemberData'
|
||||
getCorpMemberData: 'getCorpMemberData',
|
||||
updateCorpMember: 'updateCorpMember'
|
||||
},
|
||||
|
||||
knowledgeBase: {
|
||||
|
||||
@ -1,798 +0,0 @@
|
||||
/**
|
||||
* 聊天相关工具函数
|
||||
*/
|
||||
|
||||
// 通用消息提示
|
||||
export const showMessage = (title, icon = 'none') => {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon,
|
||||
});
|
||||
};
|
||||
|
||||
// 检查问诊状态
|
||||
export const checkConsultationStatus = (waitingForDoctor, consultationEnded) => {
|
||||
if (waitingForDoctor) {
|
||||
showMessage("等待医生接诊中,无法发送消息");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (consultationEnded) {
|
||||
showMessage("问诊已结束,无法发送消息");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 检查IM连接状态
|
||||
export const checkIMConnection = (timChatManager) => {
|
||||
if (!timChatManager.tim || !timChatManager.isLoggedIn) {
|
||||
// showMessage("IM连接异常,请重新进入");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 发送消息前的通用验证
|
||||
export const validateBeforeSend = (waitingForDoctor, consultationEnded, timChatManager) => {
|
||||
if (!checkConsultationStatus(waitingForDoctor, consultationEnded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkIMConnection(timChatManager)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 获取语音文件URL
|
||||
export const getVoiceUrl = (message) => {
|
||||
let voiceUrl = '';
|
||||
if (message.payload && message.payload.url) {
|
||||
voiceUrl = message.payload.url;
|
||||
} else if (message.payload && message.payload.file) {
|
||||
voiceUrl = message.payload.file;
|
||||
} else if (message.payload && message.payload.tempFilePath) {
|
||||
voiceUrl = message.payload.tempFilePath;
|
||||
} else if (message.payload && message.payload.filePath) {
|
||||
voiceUrl = message.payload.filePath;
|
||||
}
|
||||
return voiceUrl;
|
||||
};
|
||||
|
||||
// 验证语音URL格式
|
||||
export const validateVoiceUrl = (voiceUrl) => {
|
||||
if (!voiceUrl) {
|
||||
console.error('语音文件URL不存在');
|
||||
showMessage('语音文件不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!voiceUrl.startsWith('http') && !voiceUrl.startsWith('wxfile://') && !voiceUrl.startsWith('/')) {
|
||||
console.error('语音文件URL格式不正确:', voiceUrl);
|
||||
showMessage('语音文件格式错误');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 创建音频上下文
|
||||
export const createAudioContext = (voiceUrl) => {
|
||||
const audioContext = uni.createInnerAudioContext();
|
||||
audioContext.src = voiceUrl;
|
||||
|
||||
audioContext.onPlay(() => {
|
||||
console.log('语音开始播放');
|
||||
});
|
||||
|
||||
audioContext.onEnded(() => {
|
||||
console.log('语音播放结束');
|
||||
});
|
||||
|
||||
audioContext.onError((err) => {
|
||||
console.error('语音播放失败:', err);
|
||||
console.error('错误详情:', {
|
||||
errMsg: err.errMsg,
|
||||
errno: err.errno,
|
||||
src: voiceUrl
|
||||
});
|
||||
showMessage('语音播放失败');
|
||||
});
|
||||
|
||||
return audioContext;
|
||||
};
|
||||
|
||||
// ==================== 时间相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 验证时间戳格式
|
||||
* @param {number|string} timestamp - 时间戳
|
||||
* @returns {boolean} 是否为有效时间戳
|
||||
*/
|
||||
export const validateTimestamp = (timestamp) => {
|
||||
if (!timestamp) return false;
|
||||
|
||||
const num = Number(timestamp);
|
||||
if (isNaN(num)) return false;
|
||||
|
||||
// 检查是否为有效的时间戳范围(1970年到2100年)
|
||||
const minTimestamp = 0;
|
||||
const maxTimestamp = 4102444800000; // 2100年1月1日
|
||||
|
||||
return num >= minTimestamp && num <= maxTimestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化时间 - 今天/昨天显示文字,其他显示日期 + 空格 + 24小时制时间
|
||||
* @param {number|string} timestamp - 时间戳
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
export const formatTime = (timestamp) => {
|
||||
// 验证时间戳
|
||||
if (!validateTimestamp(timestamp)) {
|
||||
return "未知时间";
|
||||
}
|
||||
|
||||
// 确保时间戳是毫秒级
|
||||
let timeInMs = timestamp;
|
||||
if (timestamp < 1000000000000) {
|
||||
// 如果时间戳小于这个值,可能是秒级时间戳
|
||||
timeInMs = timestamp * 1000;
|
||||
}
|
||||
|
||||
const date = new Date(timeInMs);
|
||||
const now = new Date();
|
||||
|
||||
// 验证日期是否有效
|
||||
if (isNaN(date.getTime())) {
|
||||
return "未知时间";
|
||||
}
|
||||
|
||||
// 格式化时间:HH:MM (24小时制)
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const timeStr = `${hours}:${minutes}`;
|
||||
|
||||
// 检查是否是今天
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return `${timeStr}`;
|
||||
}
|
||||
|
||||
// 检查是否是昨天
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
if (date.toDateString() === yesterday.toDateString()) {
|
||||
return `昨天 ${timeStr}`;
|
||||
}
|
||||
|
||||
// 其他日期显示完整日期
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const dateStr = `${month}/${day}`;
|
||||
|
||||
return `${dateStr} ${timeStr}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算时间差
|
||||
* @param {number|string} startTime - 开始时间戳
|
||||
* @param {number|string} endTime - 结束时间戳
|
||||
* @returns {object} 包含天、小时、分钟、秒的时间差对象
|
||||
*/
|
||||
export const calculateTimeDiff = (startTime, endTime) => {
|
||||
if (!validateTimestamp(startTime) || !validateTimestamp(endTime)) {
|
||||
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
|
||||
}
|
||||
|
||||
let startMs = startTime;
|
||||
let endMs = endTime;
|
||||
|
||||
if (startTime < 1000000000000) startMs = startTime * 1000;
|
||||
if (endTime < 1000000000000) endMs = endTime * 1000;
|
||||
|
||||
const diffMs = Math.abs(endMs - startMs);
|
||||
|
||||
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
||||
|
||||
return { days, hours, minutes, seconds };
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化倒计时
|
||||
* @param {number|string} endTime - 结束时间戳
|
||||
* @param {number|string} currentTime - 当前时间戳(可选,默认使用当前时间)
|
||||
* @returns {string} 格式化后的倒计时字符串
|
||||
*/
|
||||
export const formatCountdown = (endTime, currentTime = Date.now()) => {
|
||||
const diff = calculateTimeDiff(currentTime, endTime);
|
||||
|
||||
if (diff.days > 0) {
|
||||
return `${diff.days}天${diff.hours}时${diff.minutes}分`;
|
||||
} else if (diff.hours > 0) {
|
||||
return `${diff.hours}时${diff.minutes}分${diff.seconds}秒`;
|
||||
} else if (diff.minutes > 0) {
|
||||
return `${diff.minutes}分${diff.seconds}秒`;
|
||||
} else {
|
||||
return `${diff.seconds}秒`;
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 媒体选择相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 检查并请求相册权限
|
||||
* @returns {Promise<boolean>} 是否有权限
|
||||
*/
|
||||
const checkAlbumPermission = () => {
|
||||
return new Promise((resolve) => {
|
||||
uni.getSetting({
|
||||
success: (res) => {
|
||||
const authStatus = res.authSetting['scope.album'];
|
||||
|
||||
if (authStatus === undefined) {
|
||||
// 未授权过,会自动弹出授权窗口
|
||||
resolve(true);
|
||||
} else if (authStatus === false) {
|
||||
// 已拒绝授权,需要引导用户手动开启
|
||||
uni.showModal({
|
||||
title: '需要相册权限',
|
||||
content: '请在设置中开启相册权限,以便选择图片',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting({
|
||||
success: (settingRes) => {
|
||||
if (settingRes.authSetting['scope.album']) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 已授权
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 获取设置失败,尝试直接调用
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择媒体文件
|
||||
* @param {object} options - 选择选项
|
||||
* @param {function} onSuccess - 成功回调
|
||||
* @param {function} onFail - 失败回调
|
||||
*/
|
||||
export const chooseMedia = async (options, onSuccess, onFail) => {
|
||||
// 如果需要从相册选择,先检查权限
|
||||
const sourceType = options.sourceType || ['album', 'camera'];
|
||||
if (sourceType.includes('album')) {
|
||||
const hasPermission = await checkAlbumPermission();
|
||||
if (!hasPermission) {
|
||||
console.log('用户未授予相册权限');
|
||||
if (onFail) {
|
||||
onFail({ errMsg: '未授权相册权限' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uni.chooseMedia({
|
||||
count: options.count || 1,
|
||||
mediaType: options.mediaType || ['image'],
|
||||
sizeType: options.sizeType || ['original', 'compressed'],
|
||||
sourceType: sourceType,
|
||||
success: function (res) {
|
||||
console.log('选择媒体成功:', res);
|
||||
if (onSuccess) onSuccess(res);
|
||||
},
|
||||
fail: function (err) {
|
||||
// 用户取消选择
|
||||
if (err.errMsg.includes('cancel')) {
|
||||
console.log('用户取消选择');
|
||||
return;
|
||||
}
|
||||
|
||||
// 权限相关错误
|
||||
if (err.errMsg.includes('permission') || err.errMsg.includes('auth') || err.errMsg.includes('拒绝')) {
|
||||
console.error('相册权限被拒绝:', err);
|
||||
uni.showModal({
|
||||
title: '需要相册权限',
|
||||
content: '请在设置中开启相册权限后重试',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (onFail) {
|
||||
onFail(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他错误
|
||||
console.error('选择媒体失败:', err);
|
||||
if (onFail) {
|
||||
onFail(err);
|
||||
} else {
|
||||
showMessage('选择图片失败,请重试');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择图片
|
||||
* @param {function} onSuccess - 成功回调
|
||||
* @param {function} onFail - 失败回调
|
||||
*/
|
||||
export const chooseImage = (onSuccess, onFail) => {
|
||||
chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sizeType: ['original', 'compressed'],
|
||||
sourceType: ['album', 'camera']
|
||||
}, onSuccess, onFail);
|
||||
};
|
||||
|
||||
/**
|
||||
* 拍照
|
||||
* @param {function} onSuccess - 成功回调
|
||||
* @param {function} onFail - 失败回调
|
||||
*/
|
||||
export const takePhoto = (onSuccess, onFail) => {
|
||||
chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sizeType: ['original', 'compressed'],
|
||||
sourceType: ['camera']
|
||||
}, onSuccess, onFail);
|
||||
};
|
||||
|
||||
// ==================== 录音相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 初始化录音管理器
|
||||
* @param {object} options - 录音选项
|
||||
* @param {function} onStop - 录音结束回调
|
||||
* @param {function} onError - 录音错误回调
|
||||
* @returns {object} 录音管理器实例
|
||||
*/
|
||||
export const initRecorderManager = (options = {}, onStop, onError) => {
|
||||
const recorderManager = wx.getRecorderManager();
|
||||
|
||||
// 监听录音结束事件
|
||||
recorderManager.onStop((res) => {
|
||||
console.log('录音成功,结果:', res);
|
||||
if (onStop) onStop(res);
|
||||
});
|
||||
|
||||
// 监听录音错误事件
|
||||
recorderManager.onError((err) => {
|
||||
console.error('录音失败:', err);
|
||||
if (onError) {
|
||||
onError(err);
|
||||
} else {
|
||||
showMessage("录音失败");
|
||||
}
|
||||
});
|
||||
|
||||
return recorderManager;
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始录音
|
||||
* @param {object} recorderManager - 录音管理器
|
||||
* @param {object} options - 录音参数
|
||||
*/
|
||||
export const startRecord = (recorderManager, options = {}) => {
|
||||
if (!recorderManager) {
|
||||
console.error('录音管理器未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
const recordOptions = {
|
||||
duration: 60000, // 录音的时长,单位 ms,最大值 600000(10 分钟)
|
||||
sampleRate: 44100, // 采样率
|
||||
numberOfChannels: 1, // 录音通道数
|
||||
encodeBitRate: 192000, // 编码码率
|
||||
format: 'aac', // 音频格式
|
||||
...options
|
||||
};
|
||||
|
||||
recorderManager.start(recordOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* 停止录音
|
||||
* @param {object} recorderManager - 录音管理器
|
||||
*/
|
||||
export const stopRecord = (recorderManager) => {
|
||||
if (!recorderManager) {
|
||||
console.error('录音管理器未初始化');
|
||||
return;
|
||||
}
|
||||
recorderManager.stop();
|
||||
};
|
||||
|
||||
// ==================== 消息发送相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 创建自定义消息
|
||||
* @param {string} messageType - 消息类型
|
||||
* @param {object} data - 消息数据
|
||||
* @param {function} formatTime - 时间格式化函数
|
||||
* @returns {object} 自定义消息对象
|
||||
*/
|
||||
export const createCustomMessage = (messageType, data, formatTime) => {
|
||||
return {
|
||||
messageType,
|
||||
time: formatTime(Date.now()),
|
||||
...data
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送自定义消息的通用方法
|
||||
* @param {object} messageData - 消息数据
|
||||
* @param {object} timChatManager - IM管理器
|
||||
* @param {function} validateBeforeSend - 发送前验证函数
|
||||
* @param {function} onSuccess - 成功回调
|
||||
*/
|
||||
export const sendCustomMessage = async (messageData, timChatManager, validateBeforeSend, onSuccess) => {
|
||||
if (!validateBeforeSend()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await timChatManager.sendCustomMessage(messageData);
|
||||
|
||||
if (result && result.success) {
|
||||
if (onSuccess) onSuccess();
|
||||
} else {
|
||||
console.error('发送自定义消息失败:', result?.error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送消息的通用方法
|
||||
* @param {string} messageType - 消息类型
|
||||
* @param {any} data - 消息数据
|
||||
* @param {object} timChatManager - IM管理器
|
||||
* @param {function} validateBeforeSend - 发送前验证函数
|
||||
* @param {function} onSuccess - 成功回调
|
||||
*/
|
||||
export const sendMessage = async (messageType, data, timChatManager, validateBeforeSend, onSuccess, cloudCustomData) => {
|
||||
if (!validateBeforeSend()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
switch (messageType) {
|
||||
case 'text':
|
||||
result = await timChatManager.sendTextMessage(data, cloudCustomData);
|
||||
break;
|
||||
case 'image':
|
||||
result = await timChatManager.sendImageMessage(data, cloudCustomData);
|
||||
break;
|
||||
case 'voice':
|
||||
result = await timChatManager.sendVoiceMessage(data.file, data.duration,cloudCustomData);
|
||||
break;
|
||||
default:
|
||||
console.error('未知的消息类型:', messageType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result && result.success) {
|
||||
if (onSuccess) onSuccess();
|
||||
} else {
|
||||
console.error('发送消息失败:', result?.error);
|
||||
showMessage('发送失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 状态检查相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 检查IM连接状态
|
||||
* @param {object} timChatManager - IM管理器
|
||||
* @param {function} onError - 错误回调
|
||||
* @returns {boolean} 连接状态
|
||||
*/
|
||||
export const checkIMConnectionStatus = (timChatManager, onError) => {
|
||||
if (!timChatManager.tim || !timChatManager.isLoggedIn) {
|
||||
const errorMsg = "IM连接异常,请重新进入";
|
||||
if (onError) {
|
||||
onError(errorMsg);
|
||||
} else {
|
||||
showMessage(errorMsg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查是否显示时间分割线
|
||||
* @param {object} message - 当前消息
|
||||
* @param {number} index - 消息索引
|
||||
* @param {Array} messageList - 消息列表
|
||||
* @returns {boolean} 是否显示时间分割线
|
||||
*/
|
||||
export const shouldShowTime = (message, index, messageList) => {
|
||||
if (index === 0) return true;
|
||||
|
||||
const prevMessage = messageList[index - 1];
|
||||
|
||||
// 使用工具函数验证时间戳
|
||||
if (!validateTimestamp(message.lastTime) || !validateTimestamp(prevMessage.lastTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const timeDiff = message.lastTime - prevMessage.lastTime;
|
||||
|
||||
return timeDiff > 5 * 60 * 1000; // 5分钟显示一次时间
|
||||
};
|
||||
|
||||
/**
|
||||
* 预览图片
|
||||
* @param {string} url - 图片URL
|
||||
*/
|
||||
export const previewImage = (url) => {
|
||||
uni.previewImage({
|
||||
urls: [url],
|
||||
current: url,
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 录音相关工具方法 ====================
|
||||
|
||||
/**
|
||||
* 检查录音时长并处理
|
||||
* @param {object} res - 录音结果
|
||||
* @param {Function} onTimeTooShort - 时间太短的回调
|
||||
* @returns {boolean} 录音时长是否有效
|
||||
*/
|
||||
export const checkRecordingDuration = (res, onTimeTooShort = null) => {
|
||||
const duration = Math.floor(res.duration / 1000);
|
||||
if (duration < 1) {
|
||||
console.log('录音时间太短,取消发送');
|
||||
if (onTimeTooShort) {
|
||||
onTimeTooShort();
|
||||
} else {
|
||||
showMessage('说话时间太短');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// ==================== 防抖和节流工具 ====================
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func - 要防抖的函数
|
||||
* @param {number} wait - 等待时间(毫秒)
|
||||
* @returns {Function} 防抖后的函数
|
||||
*/
|
||||
export const debounce = (func, wait = 300) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} func - 要节流的函数
|
||||
* @param {number} limit - 限制时间(毫秒)
|
||||
* @returns {Function} 节流后的函数
|
||||
*/
|
||||
export const throttle = (func, limit = 300) => {
|
||||
let inThrottle;
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== 自定义消息解析相关工具方法 ====================
|
||||
|
||||
// 自定义消息解析缓存
|
||||
const customMessageCache = new Map();
|
||||
|
||||
/**
|
||||
* 解析自定义消息(带缓存)
|
||||
* @param {object} message - 消息对象
|
||||
* @param {function} formatTime - 时间格式化函数
|
||||
* @returns {object} 解析后的消息对象
|
||||
*/
|
||||
export const parseCustomMessage = (message, formatTime) => {
|
||||
// 使用消息ID作为缓存键
|
||||
const cacheKey = message.ID;
|
||||
|
||||
// 检查缓存
|
||||
if (customMessageCache.has(cacheKey)) {
|
||||
return customMessageCache.get(cacheKey);
|
||||
}
|
||||
|
||||
try {
|
||||
const customData = JSON.parse(message.payload.data);
|
||||
const parsedMessage = {
|
||||
messageType: customData.messageType,
|
||||
content: customData.content,
|
||||
symptomContent: customData.symptomContent,
|
||||
hasVisitedHospital: customData.hasVisitedHospital,
|
||||
selectedDiseases: customData.selectedDiseases,
|
||||
images: customData.images,
|
||||
medicines: customData.medicines,
|
||||
diagnosis: customData.diagnosis,
|
||||
prescriptionType: customData.prescriptionType,
|
||||
prescriptionDesc: customData.prescriptionDesc,
|
||||
tcmPrescription: customData.tcmPrescription, // 新增中药处方字段
|
||||
patientName: customData.patientName,
|
||||
gender: customData.gender,
|
||||
age: customData.age,
|
||||
surveyTitle: customData.surveyTitle,
|
||||
surveyDescription: customData.surveyDescription,
|
||||
surveyName: customData.surveyName,
|
||||
estimatedTime: customData.estimatedTime,
|
||||
reward: customData.reward,
|
||||
note: customData.note,
|
||||
orderId: customData.orderId, // 新增订单ID字段
|
||||
timestamp: customData.timestamp, // 新增时间戳字段
|
||||
conversationID: message.conversationID, // 保留conversationID
|
||||
time: formatTime(message.lastTime),
|
||||
};
|
||||
|
||||
// 缓存解析结果
|
||||
customMessageCache.set(cacheKey, parsedMessage);
|
||||
return parsedMessage;
|
||||
} catch (error) {
|
||||
const fallbackMessage = {
|
||||
messageType: "unknown",
|
||||
content: "未知消息类型",
|
||||
};
|
||||
|
||||
// 缓存错误结果,避免重复解析
|
||||
customMessageCache.set(cacheKey, fallbackMessage);
|
||||
return fallbackMessage;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 清理消息缓存
|
||||
*/
|
||||
export const clearMessageCache = () => {
|
||||
customMessageCache.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取解析后的自定义消息(带缓存)
|
||||
* @param {object} message - 消息对象
|
||||
* @param {function} formatTime - 时间格式化函数
|
||||
* @returns {object} 解析后的消息对象
|
||||
*/
|
||||
export const getParsedCustomMessage = (message, formatTime) => {
|
||||
return parseCustomMessage(message, formatTime);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理查看详情
|
||||
* @param {object} message - 解析后的消息对象
|
||||
* @param {object} patientInfo - 患者信息
|
||||
*/
|
||||
export const handleViewDetail = (message, patientInfo) => {
|
||||
if (message.messageType === "symptom") {
|
||||
uni.showModal({
|
||||
title: "完整病情描述",
|
||||
content: message.symptomContent,
|
||||
showCancel: false,
|
||||
confirmText: "知道了",
|
||||
});
|
||||
} else if (message.messageType === "prescription") {
|
||||
// 处理处方单详情查看
|
||||
let content = `患者:${patientInfo.name}\n诊断:${message.diagnosis || '无'}\n\n`;
|
||||
|
||||
if (message.prescriptionType === '中药处方' && message.tcmPrescription) {
|
||||
content += `处方类型:中药处方\n处方详情:${message.tcmPrescription.description}\n`;
|
||||
if (message.tcmPrescription.usage) {
|
||||
content += `用法用量:${message.tcmPrescription.usage}\n`;
|
||||
}
|
||||
} else if (message.prescriptionType === '西药处方' && message.medicines) {
|
||||
content += `处方类型:西药处方\n药品清单:\n`;
|
||||
const medicineDetails = message.medicines
|
||||
.map((med) => `${med.name} ${med.spec} ×${med.count}`)
|
||||
.join("\n");
|
||||
content += medicineDetails + "\n";
|
||||
|
||||
// 添加用法用量
|
||||
const usageDetails = message.medicines
|
||||
.filter(med => med.usage)
|
||||
.map(med => `${med.name}:${med.usage}`)
|
||||
.join("\n");
|
||||
if (usageDetails) {
|
||||
content += `\n用法用量:\n${usageDetails}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
content += `\n开方时间:${message.time}`;
|
||||
|
||||
uni.showModal({
|
||||
title: "处方详情",
|
||||
content: content,
|
||||
showCancel: false,
|
||||
confirmText: "知道了",
|
||||
});
|
||||
} else if (message.messageType === "refill") {
|
||||
// 处理续方申请详情查看
|
||||
let content = `患者:${message.patientName} ${message.gender} ${message.age}岁\n诊断:${message.diagnosis}\n\n`;
|
||||
|
||||
if (message.prescriptionType === "中药处方") {
|
||||
content += `处方类型:${message.prescriptionType}\n处方详情:${message.prescriptionDesc}`;
|
||||
} else {
|
||||
const medicineDetails = message.medicines
|
||||
.map((med) => `${med.name} ${med.spec} ${med.count}\n${med.usage}`)
|
||||
.join("\n\n");
|
||||
content += `药品清单:\n${medicineDetails}`;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: "续方申请详情",
|
||||
content: content,
|
||||
showCancel: false,
|
||||
confirmText: "知道了",
|
||||
});
|
||||
} else if (message.messageType === "survey") {
|
||||
// 处理问卷调查详情查看或跳转
|
||||
uni.showModal({
|
||||
title: "问卷调查",
|
||||
content: `${message.surveyTitle}\n\n${message.surveyDescription
|
||||
}\n\n问卷名称:${message.surveyName}\n预计用时:${message.estimatedTime}${message.reward ? "\n完成奖励:" + message.reward : ""
|
||||
}${message.note ? "\n\n说明:" + message.note : ""}`,
|
||||
confirmText: "去填写",
|
||||
cancelText: "稍后再说",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 这里可以跳转到问卷页面
|
||||
uni.showToast({
|
||||
title: "正在跳转到问卷页面",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,21 +1,20 @@
|
||||
const env = __VITE_ENV__;
|
||||
|
||||
export async function uploadFile(tempFilePath, businessType, accessLevel = 'public') {
|
||||
export async function uploadFile(tempFilePath) {
|
||||
try {
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${env.MP_API_BASE_URL}/upload`,
|
||||
filePath: tempFilePath,
|
||||
name: 'file',
|
||||
formData: { businessType, accessLevel },
|
||||
success: (resp) => resolve(resp),
|
||||
fail: (err) => reject(err),
|
||||
});
|
||||
});
|
||||
|
||||
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
||||
if (data && data.success) {
|
||||
return data.data;
|
||||
if (data && data.success && data.filePath) {
|
||||
return `${env.MP_API_BASE_URL}${data.filePath}`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('upload file error:', e);
|
||||
@ -32,12 +31,7 @@ export async function chooseAndUploadImage(options = {}) {
|
||||
const {
|
||||
count = 1,
|
||||
sizeType = ['compressed'],
|
||||
sourceType = ['album', 'camera'],
|
||||
businessType = 'other',
|
||||
accessLevel = 'public',
|
||||
loadingTitle = '上传中...',
|
||||
successToast = '上传成功',
|
||||
failToast = '上传失败',
|
||||
sourceType = ['album', 'camera']
|
||||
} = options;
|
||||
|
||||
const imageResult = await new Promise((resolve) => {
|
||||
@ -54,20 +48,13 @@ export async function chooseAndUploadImage(options = {}) {
|
||||
if (!tempFilePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: loadingTitle });
|
||||
try {
|
||||
const uploadRes = await uploadFile(tempFilePath, businessType, accessLevel);
|
||||
uni.hideLoading();
|
||||
const uploadRes = await uploadFile(tempFilePath);
|
||||
if (uploadRes) {
|
||||
uni.showToast({ title: successToast, icon: 'success' });
|
||||
return uploadRes;
|
||||
}
|
||||
uni.showToast({ title: failToast, icon: 'none' });
|
||||
return null;
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: failToast, icon: 'none' });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { getOrderInfo } from "@/api/consult-order.js";
|
||||
|
||||
// 聊天页依赖的最小订单状态枚举(与后端 consult-order 一致)
|
||||
export const orderStatus = {
|
||||
INIT: "INIT",
|
||||
PAID: "PAID",
|
||||
CONSULTING: "CONSULTING",
|
||||
COMPLETED: "COMPLETED",
|
||||
CANCELLED: "CANCELLED",
|
||||
};
|
||||
|
||||
/**
|
||||
* 供 `pages/message/hooks/chat-order-hook.js` 使用:
|
||||
* 根据 orderId 获取订单信息
|
||||
*/
|
||||
export async function getChatOrder(orderId) {
|
||||
const res = await getOrderInfo({ orderId });
|
||||
if (res && res.success) return res.data;
|
||||
return Promise.reject(res?.message || "获取订单失败");
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user