This commit is contained in:
Your Name
2026-03-16 15:08:28 +08:00
parent 294fee4d88
commit cda63b5ae2
26 changed files with 1695 additions and 492 deletions
+3 -2
View File
@@ -67,10 +67,11 @@ const userinfo = async (code) => {
wxcode(code)
}
if(res.code==1){
uni.setStorageSync('userData', res.data)
if(res.code==1 && res.data.diagnosis){
video(res.data)
}
uni.setStorageSync('userData', res.data)
} catch (err) {
+3 -3
View File
@@ -166,7 +166,7 @@
const SDKAppID = app.globalData.SDKAppID || options.SDKAppID;
targetUserID = options.targetUserID;
currentUserID = userID; // 保存当前用户ID
console.log('-------',userID,userSig,SDKAppID)
// 获取用户头像
const userData = uni.getStorageSync('userData');
if (userData && userData.avatar) {
@@ -180,7 +180,7 @@
}
conversationID = `C2C${targetUserID}`;
console.log('当前用户:', currentUserID, '对方用户:', targetUserID);
console.log('当前用户:', currentUserID, '对方用户:', targetUserID,'conversationID'+conversationID);
if (!app.globalData.imChat) {
chat = TencentCloudChat.create({
@@ -212,7 +212,7 @@
}
await loadHistoryMessages();
uni.showToast({ title: '聊天已就绪', icon: 'success' });
//uni.showToast({ title: '聊天已就绪', icon: 'success' });
// 监听键盘高度变化
uni.onKeyboardHeightChange((res) => {
@@ -0,0 +1,649 @@
<template>
<view class="page">
<!-- 加载中 -->
<view v-if="loading" class="loading-wrap">
<text class="loading-text">加载中...</text>
</view>
<!-- 医生详情 -->
<view v-else-if="doctor" class="main">
<!-- 医生头部 -->
<section class="hero-section">
<view class="hero-content">
<view class="avatar-wrap">
<image :src="doctor.avatar || defaultAvatar" class="avatar" mode="aspectFill"></image>
<view class="badge">{{ doctor.title || '主任医师' }}</view>
</view>
<view class="hero-info">
<text class="doctor-name">{{ doctor.name }}</text>
<text class="hospital">{{ doctor.hospital || '甄养堂医院' }}</text>
<view class="experience-row">
<text class="verified"></text>
<text class="experience">{{ doctor.experience || '24年临床经验' }}</text>
</view>
<view class="stats-row">
<view class="stat-item">
<text class="stat-value">{{ doctor.rating || '4.9' }}</text>
<text class="stat-label">评分</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value">{{ formatPatientCount(doctor.patient_count) }}</text>
<text class="stat-label">患者数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item" v-if="doctor.license_no">
<text class="stat-value">执业证书编号</text>
<text class="stat-label">{{doctor.license_no}}</text>
</view>
</view>
</view>
</view>
</section>
<!-- 擅长领域 -->
<section class="about-section">
<view class="section-title-bar">
<text class="section-title">擅长领域</text>
</view>
<view class="about-content">
<text class="about-text">{{ doctor.about || '擅长运用传统中医理论与现代诊断相结合...' }}</text>
</view>
</section>
<!-- 专业领域 -->
<section class="expertise-section">
<text class="section-title-plain">专业领域</text>
<view class="expertise-grid">
<view
v-for="(item, idx) in (doctor.expertise || [])"
:key="idx"
class="expertise-card"
:class="{ 'expertise-wide': item.wide }"
>
<view class="expertise-icon" :class="'icon-' + (item.icon || 'default')">
<text class="icon-emoji">{{ getExpertiseIcon(item.icon) }}</text>
</view>
<view class="expertise-text">
<text class="expertise-title">{{ item.title }}</text>
<text class="expertise-desc">{{ item.desc }}</text>
</view>
</view>
</view>
</section>
<!-- 患者评价 -->
<section class="reviews-section">
<view class="reviews-header">
<text class="section-title-plain">患者评价</text>
<!-- <text class="view-all" @click="viewAllReviews">查看全部</text> -->
</view>
<view class="reviews-list">
<view
v-for="review in reviews"
:key="review.id"
class="review-card"
>
<view class="review-header">
<view class="review-avatar">{{ review.patient_initials }}</view>
<view class="review-meta">
<text class="review-name">{{ review.patient_name }}</text>
<view class="review-stars">
<text v-for="i in 5" :key="i" class="star"></text>
</view>
</view>
<text class="review-time">{{ review.create_time }}</text>
</view>
<text class="review-content">"{{ review.content }}"</text>
</view>
</view>
</section>
</view>
<!-- 错误 -->
<view v-else-if="!loading && error" class="error-wrap">
<text class="error-text">{{ error }}</text>
<view class="retry-btn" @click="loadDetail">重试</view>
</view>
<!-- 底部操作栏 -->
<view v-if="doctor.enable_image_consult || doctor.enable_video_consult" class="bottom-bar">
<view class="btn-chat" @click="goChat" v-if="doctor.enable_image_consult">
<text class="btn-icon">💬</text>
<text class="btn-text">图文问诊</text>
</view>
<view class="btn-video" @click="goVideo" v-if="doctor.enable_video_consult">
<text class="btn-icon">📹</text>
<text class="btn-text">视频问诊</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { getCurrentInstance } from 'vue'
import * as GenerateTestUserSig from "@/debug/GenerateTestUserSig-es.js";
import { CallManager } from "@/TUICallKit/src/TUICallService/serve/callManager";
const { proxy } = getCurrentInstance()
let userId = ref("");
let userSig= ref("");
let SDKAppID=ref("");
uni.CallManager = new CallManager();
const doctorId = ref('')
const doctor = ref(null)
const reviews = ref([])
const loading = ref(true)
const error = ref('')
const defaultAvatar = '/static/user/user.png'
const formatPatientCount = (count) => {
if (count === undefined || count === null) return '1.2k+'
if (count >= 1000) return (count / 1000).toFixed(1) + 'k+'
return count + '+'
}
const getExpertiseIcon = (icon) => {
const map = { psychiatry: '🌿', eco: '🌱', vital_signs: '💓' }
return map[icon] || '📋'
}
const loadDetail = async () => {
if (!doctorId.value) return
loading.value = true
error.value = ''
try {
const res = await proxy.apiUrl({
url: '/api/doctor/detail',
method: 'GET',
data: { id: doctorId.value }
}, false)
if (res && res.code === 1) {
doctor.value = res.data
} else {
error.value = res?.msg || '加载失败'
}
} catch (e) {
error.value = '网络异常,请重试'
console.error(e)
} finally {
loading.value = false
}
}
const loadReviews = async () => {
if (!doctorId.value) return
try {
const res = await proxy.apiUrl({
url: '/api/doctor/reviews',
method: 'GET',
data: { doctor_id: doctorId.value, page_no: 1, page_size: 10 }
}, false)
if (res && res.code === 1) {
reviews.value = res.data?.lists || []
}
} catch (e) {
console.error(e)
}
}
const viewAllReviews = () => {
uni.showToast({ title: '查看全部评价', icon: 'none' })
}
const goChat = async () => {
// uni.navigateTo({
// url: `/TUIKit/pages/chat/chat?doctorId=${doctorId.value}&doctorName=${encodeURIComponent(doctor.value?.name || '')}`
// })
console.log('dfafafa',uni.getStorageSync('userData'))
if(!uni.getStorageSync('userData').diagnosis){
uni.navigateTo({
url: `/pages/Card/edit_card?add=1`
});
}else{
await loginHandler(uni.getStorageSync('userData').diagnosis.patient_id)
const doctor='doctor_'+doctorId.value
// 跳转到聊天页面
uni.navigateTo({
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(userId)}&userSig=${encodeURIComponent(userSig)}&SDKAppID=${SDKAppID}&targetUserID=${doctor}`
});
}
}
const goVideo = () => {
if(!uni.getStorageSync('userData').diagnosis){
uni.navigateTo({
url: `/pages/Card/edit_card?add=1`
});
}else{
uni.navigateTo({
url: `/pages/login/login?doctorId=${doctorId.value}&doctorName=${encodeURIComponent(doctor.value?.name || '')}`
})
}
}
const loginHandler = async (patient_id) => {
console.log('获取签名开始:',patient_id);
const res = await proxy.apiUrl({
url: '/api/tcm/getPatientSignature',
method: 'GET',
data: { patient_id: patient_id },
}, false)
const {userId } = res.data;
const { userSig, SDKAppID } = GenerateTestUserSig.genTestUserSig({
userID:userId,
});
console.log('获取签名成功:',userSig);
getApp().globalData.userID = userId;
getApp().globalData.userSig = userSig;
getApp().globalData.SDKAppID = SDKAppID;
getApp().globalData.isIMInitialized = false; // 标记IM未初始化
// 初始化视频通话
await uni.CallManager.init({
sdkAppID: SDKAppID,
userID: userId,
userSig: userSig,
globalCallPagePath: "TUICallKit/src/Components/TUICallKit",
});
console.log('初始化完成,跳转到聊天页面');
};
onMounted(() => {
const pages = getCurrentPages()
const current = pages[pages.length - 1]
const options = current.options || current.$page?.options || {}
doctorId.value = options.id || options.doctorId || ''
if (doctorId.value) {
loadDetail().then(() => loadReviews())
} else {
error.value = '缺少医生ID'
loading.value = false
}
})
</script>
<style lang="scss" scoped>
$bg: #faf9f5;
$primary: #204e2b;
$primary-container: #386641;
$on-primary: #ffffff;
$secondary-container: #fdc39a;
$on-secondary-container: #794e2e;
$surface-lowest: #ffffff;
$surface-low: #f4f4f0;
$surface-high: #e9e8e4;
$surface-container: #efeeea;
$on-surface: #1b1c1a;
$on-surface-variant: #414941;
$tertiary-container: #4b6500;
$outline-variant: #c1c9be;
.page {
background: $bg;
min-height: 100vh;
padding-bottom: 180rpx;
}
.loading-wrap, .error-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 60vh;
}
.loading-text, .error-text {
font-size: 28rpx;
color: $on-surface-variant;
}
.retry-btn {
margin-top: 24rpx;
padding: 16rpx 48rpx;
background: $primary;
color: $on-primary;
border-radius: 40rpx;
font-size: 28rpx;
}
.main {
padding: 32rpx 48rpx 40rpx;
}
.hero-section {
margin-bottom: 80rpx;
}
.hero-content {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.avatar-wrap {
position: relative;
width: 320rpx;
height: 320rpx;
flex-shrink: 0;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 24rpx;
background: $surface-high;
}
.badge {
position: absolute;
bottom: -16rpx;
right: -16rpx;
background: $tertiary-container;
color: $on-primary;
font-size: 22rpx;
padding: 12rpx 24rpx;
border-radius: 9999rpx;
font-weight: bold;
}
.hero-info {
flex: 1;
}
.doctor-name {
display: block;
font-size: 64rpx;
font-weight: bold;
color: $on-surface;
margin-bottom: 16rpx;
font-family: 'Noto Serif SC', 'STSong', 'SimSun', serif;
}
.hospital {
display: block;
font-size: 36rpx;
color: $primary;
font-weight: 500;
margin-bottom: 16rpx;
}
.experience-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
}
.verified {
font-size: 32rpx;
color: $primary;
}
.experience {
font-size: 28rpx;
color: $on-surface-variant;
}
.stats-row {
display: flex;
align-items: center;
gap: 32rpx;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-value {
font-size: 48rpx;
font-weight: bold;
color: $on-surface;
font-family: 'Noto Serif SC', serif;
}
.stat-label {
font-size: 20rpx;
text-transform: uppercase;
letter-spacing: 0.1em;
color: $on-surface-variant;
font-weight: bold;
margin-top: 4rpx;
}
.stat-divider {
width: 2rpx;
height: 64rpx;
background: $outline-variant;
opacity: 0.3;
}
.about-section {
margin-bottom: 96rpx;
}
.section-title-bar {
padding-left: 32rpx;
border-left: 8rpx solid $secondary-container;
margin-bottom: 32rpx;
}
.section-title {
font-size: 48rpx;
font-weight: bold;
color: $on-surface;
font-family: 'Noto Serif SC', serif;
}
.about-content {
background: $surface-low;
padding: 48rpx;
border-radius: 24rpx;
}
.about-text {
font-size: 28rpx;
color: $on-surface-variant;
line-height: 1.8;
}
.expertise-section {
margin-bottom: 96rpx;
}
.section-title-plain {
display: block;
font-size: 48rpx;
font-weight: bold;
color: $on-surface;
margin-bottom: 48rpx;
font-family: 'Noto Serif SC', serif;
}
.expertise-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32rpx;
}
.expertise-card {
background: $surface-lowest;
padding: 40rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.expertise-card.expertise-wide {
grid-column: span 2;
flex-direction: row;
align-items: center;
gap: 48rpx;
}
.expertise-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffdcc5;
display: flex;
align-items: center;
justify-content: center;
}
.expertise-icon.icon-eco {
background: #ccf078;
}
.expertise-icon.icon-vital_signs {
width: 96rpx;
height: 96rpx;
background: #bcefc0;
}
.icon-emoji {
font-size: 40rpx;
}
.expertise-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: $on-surface;
margin-bottom: 8rpx;
}
.expertise-desc {
font-size: 24rpx;
color: $on-surface-variant;
line-height: 1.5;
}
.reviews-section {
margin-bottom: 48rpx;
}
.reviews-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 48rpx;
}
.view-all {
font-size: 28rpx;
color: $primary;
font-weight: bold;
}
.reviews-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.review-card {
background: $surface-lowest;
padding: 48rpx;
border-radius: 24rpx;
}
.review-header {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 24rpx;
}
.review-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: $surface-container;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: bold;
color: $on-surface-variant;
}
.review-meta {
flex: 1;
}
.review-name {
display: block;
font-size: 28rpx;
font-weight: bold;
color: $on-surface;
margin-bottom: 8rpx;
}
.review-stars {
color: #805533;
font-size: 24rpx;
}
.review-time {
font-size: 24rpx;
color: $on-surface-variant;
}
.review-content {
font-size: 28rpx;
color: $on-surface-variant;
line-height: 1.6;
font-style: italic;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 48rpx;
display: flex;
gap: 32rpx;
background: rgba(250, 249, 245, 0.95);
z-index: 50;
}
.btn-chat {
flex: 1;
height: 112rpx;
background: $secondary-container;
color: $on-secondary-container;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
font-weight: bold;
font-size: 30rpx;
}
.btn-video {
flex: 1;
height: 112rpx;
background: linear-gradient(135deg, $primary 0%, $primary-container 100%);
color: $on-primary;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
font-weight: bold;
font-size: 30rpx;
}
.btn-icon {
font-size: 40rpx;
}
</style>
+35
View File
@@ -116,12 +116,47 @@ function apiUrl(promise, Loading = true) {
})
}
/**
* 解析页面参数(支持普通参数和扫码 scene 参数)
* @param {Object} options - 页面 options,如 getCurrentPages()[].options
* @returns {Object} 解析后的参数对象
*/
function parsePageParams(options) {
const params = {}
if (!options) return params
// 如果有 scene 参数(扫码进入),解析 scene
if (options.scene) {
try {
const decodedScene = decodeURIComponent(options.scene)
decodedScene.split('&').forEach(item => {
const [key, value] = item.split('=')
if (key && value) {
params[key] = value
}
})
} catch (error) {
console.error('解析scene参数失败:', error)
}
}
// 合并普通参数(普通跳转进入)
Object.keys(options).forEach(key => {
if (key !== 'scene' && options[key]) {
params[key] = options[key]
}
})
return params
}
export function createApp() {
const app = createSSRApp(App)
// Vue3挂载全局属性的方式
app.config.globalProperties.$url = baseUrl
app.config.globalProperties.apiUrl = apiUrl
app.config.globalProperties.$parsePageParams = parsePageParams
return {
app
+13 -2
View File
@@ -80,11 +80,22 @@
}
}
]
},
{
"root": "doctor",
"pages": [
{
"path": "pages/doctor/doctor",
"style": {
"navigationBarTitleText": "医生信息"
}
}
]
}
],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#FFA500",
"color": "#8a8a8a",
"selectedColor": "#204e2b",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
+34 -11
View File
@@ -1,9 +1,7 @@
<template>
<view class="card-container">
<!-- 顶部标题栏 -->
<view class="header">
<text class="header-title">我的就诊卡</text>
</view>
<!-- 就诊卡列表 -->
<view class="card-list">
@@ -61,6 +59,7 @@
<view v-if="!loading && cardList.length === 0" class="empty-state">
<uni-icons type="wallet" size="60" color="#999"></uni-icons>
<text class="empty-text">暂无就诊卡</text>
<view class="add-card-btn" @click="createCard">新建就诊卡</view>
</view>
<!-- 加载状态 -->
@@ -73,7 +72,7 @@
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue'
import { onLaunch, onShow, onHide, onError ,onShareAppMessage} from '@dcloudio/uni-app'
const { proxy } = getCurrentInstance()
// 数据
@@ -84,7 +83,9 @@ const loading = ref(false)
onMounted(() => {
loadCardList()
})
onShow(() => {
loadCardList()
})
// 加载就诊卡列表
const loadCardList = async () => {
loading.value = true
@@ -93,9 +94,9 @@ const loadCardList = async () => {
const token = uni.getStorageSync('token')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => {
uni.redirectTo({ url: '/pages/login/login' })
}, 1500)
// setTimeout(() => {
// uni.redirectTo({ url: '/pages/login/login' })
// }, 1500)
return
}
@@ -104,7 +105,7 @@ const loadCardList = async () => {
const patientId = userData?.diagnosis?.patient_id
if (!patientId) {
uni.showToast({ title: '患者信息不存在', icon: 'none' })
//uni.showToast({ title: '患者信息不存在', icon: 'none' })
return
}
@@ -137,6 +138,18 @@ const viewCardDetail = (card) => {
})
}
// 新建就诊卡(patient_id 创建后自动生成,仅需登录)
const createCard = () => {
const token = uni.getStorageSync('token')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
uni.navigateTo({
url: '/pages/Card/edit_card?add=1'
})
}
// 获取状态文本
const getStatusText = (status) => {
return status === 1 ? '启用' : '禁用'
@@ -257,8 +270,8 @@ const formatDate = (timestamp) => {
&.status-confirmed {
background: #f6ffed;
color: #52c41a;
border: 2rpx solid #b7eb8f;
color: #204e2b;
border: 2rpx solid #204e2b;
}
&.status-pending {
@@ -343,9 +356,19 @@ const formatDate = (timestamp) => {
.empty-text {
font-size: 32rpx;
color: #999999;
margin-bottom: 40rpx;
}
}
.add-card-btn {
padding: 24rpx 64rpx;
background: #1890ff;
color: #ffffff;
font-size: 30rpx;
font-weight: 500;
border-radius: 48rpx;
}
.loading-state {
display: flex;
justify-content: center;
+37 -14
View File
@@ -821,6 +821,7 @@ const agreedToTerms = ref(true) // 默认选中"是"
const showDatePicker = ref(false)
const diagnosisId = ref(null)
const patientId = ref(null)
const isAddMode = ref(false) // 新建模式
// 表单数据
const formData = ref({
@@ -1063,23 +1064,37 @@ const loadCardDetail = async () => {
try {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
diagnosisId.value = currentPage.options.id
if (!diagnosisId.value) {
uni.showToast({ title: '诊单ID不存在', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
return
}
const options = currentPage.options || {}
diagnosisId.value = options.id
isAddMode.value = options.add === '1' || options.add === 1
const userData = uni.getStorageSync('userData')
patientId.value = userData?.diagnosis?.patient_id
// 新建模式:patient_id 可选(创建后自动生成),仅需登录
if (isAddMode.value) {
const token = uni.getStorageSync('token')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => uni.redirectTo({ url: '/pages/login/login' }), 1500)
return
}
loading.value = false
return
}
if (!patientId.value) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => uni.redirectTo({ url: '/pages/login/login' }), 1500)
return
}
if (!diagnosisId.value) {
uni.showToast({ title: '诊单ID不存在', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
return
}
const res = await proxy.apiUrl({
url: '/api/tcm/diagnosisDetail',
method: 'GET',
@@ -1302,18 +1317,26 @@ const submitForm = async () => {
submitData.diagnosis_date = submitData.local_hospital_visit_date
}
const apiUrl = isAddMode.value ? '/api/tcm/addCard' : '/api/tcm/updateCard'
const apiData = isAddMode.value
? { ...submitData, ...(patientId.value ? { patient_id: patientId.value } : {}) }
: { id: diagnosisId.value, patient_id: patientId.value, ...submitData }
const res = await proxy.apiUrl({
url: '/api/tcm/updateCard',
url: apiUrl,
method: 'POST',
data: {
id: diagnosisId.value,
patient_id: patientId.value,
...submitData
}
data: apiData
})
if (res.code === 1) {
uni.showToast({ title: '保存成功', icon: 'success' })
// 新建成功:若有返回 patient_id,写入 userData 供后续列表查询
if (isAddMode.value && res.data?.patient_id) {
const userData = uni.getStorageSync('userData') || {}
if (!userData.diagnosis) userData.diagnosis = {}
userData.diagnosis.patient_id = res.data.patient_id
uni.setStorageSync('userData', userData)
}
uni.showToast({ title: isAddMode.value ? '创建成功' : '保存成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
+530 -374
View File
@@ -1,39 +1,123 @@
<template>
<view class="container">
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar">
<text class="search-icon">🔍</text>
<input class="search-input" placeholder="搜索药材、医生或症状..." type="text" />
<text class="search-filter"></text>
</view>
</view>
<!-- 科室分类 -->
<view class="department-section">
<view class="section-header">
<text class="section-title">科室分类</text>
<text class="more-link" @click="navigateToDepartments">查看全部</text>
</view>
<view class="department-grid">
<view class="dept-col-left">
<view
class="department-card large"
@click="navigateToDepartment(departments[0])"
>
<view class="dept-card-inner dept-green">
<view class="dept-icon"><text class="icon-text"></text></view>
<text class="dept-name text-white">针灸</text>
<text class="dept-desc text-white-sub">调理气血</text>
</view>
</view>
</view>
<view class="dept-col-right">
<view
class="department-card small"
@click="navigateToDepartment(departments[1])"
>
<view class="dept-card-inner dept-beige">
<view class="dept-icon-wrap">
<view class="dept-icon"><text class="icon-text"></text></view>
</view>
<view>
<text class="dept-name">中药</text>
<text class="dept-desc">古法方剂</text>
</view>
</view>
</view>
<view
class="department-card small"
@click="navigateToDepartment(departments[2])"
>
<view class="dept-card-inner dept-grey">
<view class="dept-icon-wrap">
<view class="dept-icon"><text class="icon-text"></text></view>
</view>
<view>
<text class="dept-name">推拿</text>
<text class="dept-desc">中医按摩</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 问诊服务 -->
<!-- 特色专科医师 -->
<!-- 名医推荐 -->
<view class="doctors-section">
<view class="section-header">
<view class="section-title-wrapper">
<view class="section-title">特色专科医师</view>
</view>
<view class="more-link" @click="navigateToAllDoctors">更多</view>
<text class="section-title">名医推荐</text>
<text class="more-link" @click="navigateToAllDoctors">查看全部</text>
</view>
<view class="doctors-grid">
<view class="doctors-list">
<view
v-for="doctor in doctors"
:key="doctor.id"
class="doctor-card"
@click="selectDoctor(doctor)"
>
<view class="doctor-avatar-wrapper">
<image :src="doctor.avatar" class="doctor-avatar" mode="aspectFill"></image>
<view class="avatar-frame"></view>
<image :src="doctor.avatar || defaultAvatar" class="doctor-photo" mode="aspectFill"></image>
<view class="doctor-info">
<view class="doctor-top">
<text class="doctor-name">{{ doctor.name }}</text>
<text class="doctor-rating"> {{ doctor.rating || '4.9' }}</text>
</view>
<text class="doctor-specialty">{{ doctor.specialty || doctor.title || '中医' }}</text>
<view class="doctor-price-row">
<text class="doctor-price">{{ doctor.price || '' }}</text>
<text class="doctor-unit">/</text>
</view>
<view class="doctor-actions">
<view class="appointment-btn" @click.stop="selectDoctor(doctor)">预约</view>
</view>
</view>
<view class="doctor-name">{{ doctor.name }}</view>
<view class="doctor-title">{{ doctor.title }}</view>
<view class="doctor-dept">{{ doctor.department || '内科' }}</view>
</view>
</view>
</view>
<!-- 健康百科 -->
<view class="encyclopedia-section">
<view class="section-header">
<text class="section-title">健康百科</text>
<text class="more-link" @click="navigateToEncyclopedia">查看全部</text>
</view>
<scroll-view class="encyclopedia-scroll no-scrollbar" scroll-x :show-scrollbar="false">
<view
v-for="article in articles"
:key="article.id"
class="article-card"
:class="{ 'article-card-secondary': article.id === 2 }"
@click="navigateToArticle(article)"
>
<view class="article-image-wrap">
<image :src="article.cover" class="article-image" mode="aspectFill"></image>
<view class="article-gradient" :class="{ 'gradient-secondary': article.id === 2 }"></view>
<view class="article-tag" :class="{ 'tag-secondary': article.id === 2 }">{{ article.tag }}</view>
</view>
<text class="article-title" :class="{ 'title-secondary': article.id === 2 }">{{ article.title }}</text>
<text class="article-desc">{{ article.desc }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
@@ -45,51 +129,46 @@ const { proxy } = getCurrentInstance()
const doctors = ref([])
const loading = ref(false)
const defaultAvatar = '/static/user/user.png'
// 科室分类
const departments = ref([
{ id: 1, name: '针灸', desc: '调理气血', icon: '✦', iconClass: 'icon-acupuncture', bgColor: 'linear-gradient(135deg, #2d5a3d 0%, #3d7a4d 100%)', dark: true, size: 'large', pattern: true },
{ id: 2, name: '中药', desc: '古法方剂', icon: '◉', iconClass: 'icon-herb', bgColor: '#F5E6D3', dark: false, size: 'small' },
{ id: 3, name: '推拿', desc: '中医按摩', icon: '☯', iconClass: 'icon-massage', bgColor: '#E8E4DF', dark: false, size: 'small' }
])
// 健康百科
const articles = ref([
{ id: 1, title: '晨间生姜:脾胃健康的催化剂', desc: '了解每天早晨一片生姜如何改善您的新陈代谢...', tag: '膳食营养', cover: 'https://picsum.photos/seed/herb/300/180' },
{ id: 2, title: '四时呼吸:顺应节气养生', desc: '顺应节气,调养身心...', tag: '正念冥想', cover: 'https://picsum.photos/seed/nature/300/180' },
{ id: 3, title: '中医养生之道', desc: '传统智慧,现代健康...', tag: '养生保健', cover: 'https://picsum.photos/seed/health/300/180' }
])
const selectDoctor = (doctor) => {
uni.navigateTo({
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
url: `/doctor/pages/doctor/doctor?id=${doctor.id}`
})
}
const navigateToExperts = () => {
uni.navigateTo({
url: '/pages/experts/index'
})
const navigateToDepartments = () => {
uni.showToast({ title: '科室列表', icon: 'none' })
}
const navigateToConsultation = () => {
uni.navigateTo({
url: '/pages/consultation/index'
})
}
const navigateToQuickConsult = () => {
uni.navigateTo({
url: '/pages/quick-consult/index'
})
const navigateToDepartment = (item) => {
uni.showToast({ title: item.name, icon: 'none' })
}
const navigateToAllDoctors = () => {
uni.navigateTo({
url: '/pages/doctors/index'
})
uni.showToast({ title: '更多医生', icon: 'none' })
}
const navigateToHome = () => {
// 已在首页
const navigateToEncyclopedia = () => {
uni.showToast({ title: '健康百科', icon: 'none' })
}
const navigateToMessages = () => {
uni.navigateTo({
url: '/pages/messages/index'
})
}
const navigateToProfile = () => {
uni.navigateTo({
url: '/pages/profile/index'
})
const navigateToArticle = (article) => {
uni.showToast({ title: article.title, icon: 'none' })
}
const fetchDoctors = async () => {
@@ -124,377 +203,454 @@ onMounted(() => {
</script>
<style scoped lang="scss">
/* TCM Care 设计系统 - 源自 code.html */
$background: #faf9f5;
$primary: #204e2b;
$primary-container: #386641;
$on-primary: #ffffff;
$on-primary-container: #afe2b3;
$secondary-container: #fdc39a;
$on-secondary-container: #794e2e;
$surface-container-lowest: #ffffff;
$surface-container-low: #f4f4f0;
$surface-container-high: #e9e8e4;
$surface-container-highest: #e3e2df;
$on-surface: #1b1c1a;
$on-surface-variant: #414941;
$tertiary-container: #4b6500;
$tertiary: #384c00;
$outline: #727970;
.container {
background: #f5f3f0;
background: $background;
min-height: 100vh;
padding-bottom: 120rpx;
padding: 24rpx 32rpx 120rpx;
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
// 古典卷轴横幅
.scroll-banner {
margin: 24rpx 16rpx;
.font-headline {
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
}
// 搜索栏 - surface-container-low
.search-section {
margin-bottom: 32rpx;
}
.search-bar {
display: flex;
align-items: center;
position: relative;
background: $surface-container-low;
border-radius: 24rpx;
padding: 24rpx 32rpx;
}
.scroll-rod {
width: 32rpx;
height: 160rpx;
background: linear-gradient(180deg, #8B7355 0%, #6B5644 50%, #8B7355 100%);
border-radius: 16rpx;
position: relative;
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2);
&::before {
content: '';
position: absolute;
top: 16rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 40rpx;
background: radial-gradient(circle, #D4AF37 0%, #B8941E 100%);
border-radius: 50%;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.3);
}
&::after {
content: '';
position: absolute;
bottom: 16rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 40rpx;
background: radial-gradient(circle, #D4AF37 0%, #B8941E 100%);
border-radius: 50%;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.3);
}
.search-icon {
font-size: 36rpx;
color: $outline;
margin-right: 24rpx;
}
.scroll-rod-left {
margin-right: -8rpx;
z-index: 2;
}
.scroll-rod-right {
margin-left: -8rpx;
z-index: 2;
}
.scroll-content {
.search-input {
flex: 1;
background: linear-gradient(135deg, #F5E6D3 0%, #E8D7C3 100%);
border: 3rpx solid #8B7355;
border-radius: 12rpx;
padding: 24rpx;
box-shadow: inset 0 2rpx 8rpx rgba(139, 115, 85, 0.2);
position: relative;
&::before {
content: '';
position: absolute;
top: 12rpx;
left: 12rpx;
right: 12rpx;
bottom: 12rpx;
border: 1rpx solid rgba(139, 115, 85, 0.3);
border-radius: 8rpx;
pointer-events: none;
}
}
.scroll-inner {
display: flex;
align-items: center;
gap: 20rpx;
position: relative;
z-index: 1;
}
.scroll-text {
flex: 1;
}
.scroll-title {
font-size: 32rpx;
font-weight: bold;
color: #3E2723;
margin-bottom: 8rpx;
font-family: 'STKaiti', 'KaiTi', serif;
}
.scroll-subtitle {
font-size: 24rpx;
color: #5D4037;
font-family: 'STKaiti', 'KaiTi', serif;
}
.scroll-image {
width: 200rpx;
height: 120rpx;
border-radius: 8rpx;
border: 2rpx solid #8B7355;
}
// 问诊服务
.services-section {
padding: 32rpx 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #3E2723;
margin-bottom: 24rpx;
font-family: 'STKaiti', 'KaiTi', serif;
position: relative;
padding-left: 16rpx;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 28rpx;
background: linear-gradient(180deg, #D4AF37 0%, #B8941E 100%);
border-radius: 3rpx;
}
}
.services-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.service-card {
border-radius: 20rpx;
padding: 24rpx 16rpx;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
min-height: 320rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.card-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0.15;
z-index: 0;
}
.bg-decoration {
width: 100%;
height: 100%;
}
.card-content {
position: relative;
z-index: 1;
flex: 1;
display: flex;
flex-direction: column;
}
.service-expert {
background: linear-gradient(135deg, #FFF8DC 0%, #F5E6D3 100%);
border: 2rpx solid #E8D7C3;
}
.service-health {
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
border: 2rpx solid #A5D6A7;
}
.service-quick {
background: linear-gradient(135deg, #E0F2F1 0%, #B2DFDB 100%);
border: 2rpx solid #80CBC4;
}
.service-title {
background: transparent;
font-size: 28rpx;
font-weight: bold;
color: #3E2723;
margin-bottom: 12rpx;
font-family: 'STKaiti', 'KaiTi', serif;
color: $on-surface;
}
.service-desc {
font-size: 22rpx;
color: #5D4037;
line-height: 1.5;
margin-bottom: 16rpx;
flex: 1;
.search-input::placeholder {
color: #414941;
opacity: 0.5;
}
.service-icon {
font-size: 56rpx;
text-align: center;
margin: 16rpx 0;
opacity: 0.8;
}
.service-btn {
color: #fff;
padding: 16rpx 24rpx;
border-radius: 32rpx;
font-size: 24rpx;
font-weight: bold;
text-align: center;
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.15);
position: relative;
z-index: 1;
}
.btn-orange {
background: linear-gradient(135deg, #D2691E 0%, #CD853F 100%);
}
.btn-green {
background: linear-gradient(135deg, #2E7D32 0%, #388E3C 100%);
}
.btn-purple {
background: linear-gradient(135deg, #4A148C 0%, #6A1B9A 100%);
}
// 特色专科医师
.doctors-section {
padding: 32rpx 16rpx;
.search-filter {
font-size: 36rpx;
color: $outline;
}
// 通用区块
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
align-items: flex-end;
margin-bottom: 32rpx;
}
.section-title-wrapper {
display: flex;
align-items: center;
gap: 8rpx;
}
.title-icon {
font-size: 28rpx;
.section-title {
font-size: 48rpx;
font-weight: bold;
color: $on-surface;
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
}
.more-link {
font-size: 24rpx;
color: #D4AF37;
font-size: 28rpx;
color: $primary;
font-weight: 500;
}
.doctors-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
// 科室分类 - grid 布局
.department-section {
margin-bottom: 64rpx;
}
.doctor-card {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
text-align: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.doctor-avatar-wrapper {
position: relative;
margin-bottom: 16rpx;
.department-grid {
display: flex;
justify-content: center;
gap: 32rpx;
}
.doctor-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 3rpx solid #E8E8E8;
.dept-col-left {
flex: 1;
}
.avatar-frame {
position: absolute;
top: -4rpx;
left: 50%;
transform: translateX(-50%);
width: 128rpx;
height: 128rpx;
border: 2rpx solid rgba(212, 175, 55, 0.3);
border-radius: 50%;
pointer-events: none;
}
.doctor-name {
font-size: 26rpx;
font-weight: bold;
color: #3E2723;
margin-bottom: 6rpx;
}
.doctor-title {
font-size: 22rpx;
color: #757575;
margin-bottom: 4rpx;
}
.doctor-dept {
font-size: 20rpx;
color: #9E9E9E;
}
// 底部导航
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1rpx solid #E8E8E8;
display: flex;
justify-content: space-around;
padding: 16rpx 0 24rpx 0;
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
z-index: 100;
}
.nav-item {
.dept-col-right {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
flex: 1;
gap: 32rpx;
}
&.active {
.nav-icon {
font-size: 40rpx;
}
.department-card {
&.large {
height: 100%;
min-height: 320rpx;
}
.nav-text {
color: #D4AF37;
font-weight: bold;
}
&.small {
flex: 1;
min-height: 160rpx;
}
}
.nav-icon {
font-size: 36rpx;
transition: all 0.3s ease;
.dept-card-inner {
border-radius: 24rpx;
padding: 40rpx;
height: 100%;
min-height: 160rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
.nav-text {
font-size: 22rpx;
color: #757575;
transition: all 0.3s ease;
/* 针灸 - primary-container */
.dept-green {
background: $primary-container;
color: $on-primary;
}
.dept-green .dept-icon .icon-text,
.dept-green .dept-name {
color: $on-primary;
}
.dept-green .dept-desc {
color: $on-primary-container;
font-size: 24rpx;
}
/* 中药 - secondary-container */
.dept-beige {
background: $secondary-container;
color: $on-secondary-container;
flex-direction: row;
align-items: center;
gap: 24rpx;
padding: 32rpx;
}
.dept-beige .dept-icon-wrap {
background: rgba(255, 255, 255, 0.4);
padding: 16rpx;
border-radius: 16rpx;
}
.dept-beige .dept-icon .icon-text {
font-size: 40rpx;
color: $on-secondary-container;
}
.dept-beige .dept-name {
color: $on-secondary-container;
margin-bottom: 4rpx;
display: block;
}
.dept-beige .dept-desc {
color: $on-secondary-container;
opacity: 0.8;
font-size: 20rpx;
display: block;
}
/* 推拿 - surface-container-high */
.dept-grey {
background: $surface-container-high;
color: $on-surface;
flex-direction: row;
align-items: center;
gap: 24rpx;
padding: 32rpx;
}
.dept-grey .dept-icon-wrap {
background: rgba(255, 255, 255, 0.6);
padding: 16rpx;
border-radius: 16rpx;
}
.dept-grey .dept-icon .icon-text {
font-size: 40rpx;
color: $primary;
}
.dept-grey .dept-name {
color: $on-surface;
display: block;
}
.dept-grey .dept-desc {
color: $on-surface-variant;
font-size: 20rpx;
display: block;
}
.dept-icon {
margin-bottom: 16rpx;
}
.dept-beige .dept-icon,
.dept-grey .dept-icon {
margin-bottom: 0;
}
.dept-icon .icon-text {
font-size: 56rpx;
color: $on-primary;
}
.dept-name {
font-size: 36rpx;
font-weight: 600;
color: $on-surface;
margin-bottom: 8rpx;
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
&.text-white {
color: $on-primary;
}
}
.dept-desc {
font-size: 26rpx;
color: $on-surface-variant;
&.text-white-sub {
color: $on-primary-container;
}
}
// 名医推荐
.doctors-section {
margin-bottom: 64rpx;
}
.doctors-list {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.doctor-card {
background: $surface-container-lowest;
border-radius: 24rpx;
padding: 40rpx;
display: flex;
align-items: center;
gap: 32rpx;
}
.doctor-photo {
width: 160rpx;
height: 230rpx;
border-radius: 16rpx;
flex-shrink: 0;
background: $surface-container-highest;
}
.doctor-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 192rpx;
}
.doctor-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8rpx;
}
.doctor-name {
font-size: 36rpx;
font-weight: bold;
color: $on-surface;
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
}
.doctor-rating {
font-size: 24rpx;
color: #805533;
font-weight: bold;
}
.doctor-specialty {
font-size: 28rpx;
color: $on-surface-variant;
margin-bottom: 16rpx;
}
.doctor-price-row {
margin-bottom: 16rpx;
}
.doctor-price {
font-size: 28rpx;
color: $primary;
font-weight: bold;
}
.doctor-unit {
font-size: 24rpx;
font-weight: normal;
color: $on-surface-variant;
}
.doctor-actions {
display: flex;
justify-content: flex-end;
}
.appointment-btn {
background: $primary;
color: $on-primary;
font-size: 24rpx;
font-weight: bold;
padding: 16rpx 32rpx;
border-radius: 9999rpx;
}
// 健康百科 - 横向滚动
.encyclopedia-section {
margin-bottom: 64rpx;
}
.encyclopedia-scroll {
white-space: nowrap;
margin: 0 -32rpx;
padding: 0 32rpx 32rpx;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.article-card {
display: inline-block;
width: 560rpx;
margin-right: 40rpx;
background: rgba(75, 101, 0, 0.06);
border-radius: 24rpx;
overflow: hidden;
vertical-align: top;
}
.article-card-secondary {
background: rgba(253, 195, 154, 0.1);
}
.article-card:last-child {
margin-right: 0;
}
.article-image-wrap {
position: relative;
width: 100%;
height: 256rpx;
overflow: hidden;
}
.article-image {
width: 100%;
height: 100%;
background: $tertiary-container;
opacity: 0.8;
}
.article-gradient {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: linear-gradient(to top, $tertiary-container, transparent);
}
.article-gradient.gradient-secondary {
background: linear-gradient(to top, $secondary-container, transparent);
}
.article-tag {
position: absolute;
bottom: 16rpx;
left: 24rpx;
background: $tertiary;
color: $on-primary;
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 9999rpx;
text-transform: uppercase;
font-weight: bold;
letter-spacing: 0.05em;
z-index: 1;
}
.article-tag.tag-secondary {
background: #805533;
color: #ffffff;
}
.article-title {
font-size: 28rpx;
font-weight: bold;
color: #394d00;
padding: 32rpx 32rpx 8rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
}
.article-title.title-secondary {
color: $on-secondary-container;
}
.article-desc {
font-size: 24rpx;
color: $on-surface-variant;
padding: 0 32rpx 32rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>
+4 -1
View File
@@ -64,15 +64,17 @@ import { ref,onMounted,getCurrentInstance} from "vue";
const { proxy } = getCurrentInstance()
import * as GenerateTestUserSig from "@/debug/GenerateTestUserSig-es.js";
import { CallManager } from "@/TUICallKit/src/TUICallService/serve/callManager";
import { onLaunch, onShow, onHide, onError ,onShareAppMessage} from '@dcloudio/uni-app'
let userID = ref("4");
let avatarUrl = ref(""); // 声明为响应式变量
uni.CallManager = new CallManager();
onMounted(() => {
onLaunch(() => {
uni.login({
provider: 'weixin',
success: function (loginRes) {
wxcode(loginRes.code)
// 获取用户信息
@@ -106,6 +108,7 @@ onShareAppMessage((res) =>{
const wxcode = async (code) => {
return
try {
// 通过 proxy 调用全局的 apiUrl
const res = await proxy.apiUrl({
+14 -5
View File
@@ -50,16 +50,17 @@
</template>
<script setup>
import { ref, getCurrentInstance } from "vue";
import { ref, getCurrentInstance,onMounted } from "vue";
import { onShow, onShareAppMessage } from '@dcloudio/uni-app'
const { proxy } = getCurrentInstance()
import * as GenerateTestUserSig from "@/debug/GenerateTestUserSig-es.js";
import { CallManager } from "@/TUICallKit/src/TUICallService/serve/callManager";
const { proxy } = getCurrentInstance()
let userId = ref("");
let userSig= ref("");
let SDKAppID=ref("");
uni.CallManager = new CallManager();
const doctorId = ref('')
const doctorName = ref('')
onShow(() => {
uni.login({
provider: 'weixin',
@@ -69,7 +70,15 @@ onShow(() => {
}
});
})
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// const options = current.options || current.$page?.options || {}
const options = proxy.$parsePageParams(currentPage.options|| currentPage.$page?.options || {});
doctorId.value = options.id || options.doctorId || ''
console.log('---------进入onMounted',options,doctorId.value)
})
onShareAppMessage((res) =>{
return {
title: '视频问诊通话邀请',
@@ -94,7 +103,7 @@ const wxcode = async (code) => {
data: { code: code },
}, false)
console.log('---------------',res.data)
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('userData', res.data)
loginHandler(res.data.diagnosis.patient_id)
} catch (err) {
@@ -103,7 +112,7 @@ const wxcode = async (code) => {
}
}
const url=()=>{
const doctor='doctor_'+uni.getStorageSync('userData').diagnosis.diagnosis_id
const doctor=doctorId.value?'doctor_'+doctorId.value:'doctor_'+uni.getStorageSync('userData').diagnosis.diagnosis_id
// 跳转到聊天页面
uni.navigateTo({
url: `/TUIKit/pages/chat/chat?userID=${encodeURIComponent(userId)}&userSig=${encodeURIComponent(userSig)}&SDKAppID=${SDKAppID}&targetUserID=${doctor}`
+3 -39
View File
@@ -519,51 +519,15 @@ const getDictData = async (type) => {
}
};
// 解析页面参数(支持普通参数和scene参数)
const parsePageParams = (options) => {
const params = {};
// 如果有scene参数(扫码进入),解析scene
if (options.scene) {
try {
// URL解码scene参数
const decodedScene = decodeURIComponent(options.scene);
// 解析键值对:id=4&share_user=1
decodedScene.split('&').forEach(item => {
const [key, value] = item.split('=');
if (key && value) {
params[key] = value;
}
});
} catch (error) {
console.error('解析scene参数失败:', error);
}
}
// 合并普通参数(普通跳转进入)
Object.keys(options).forEach(key => {
if (key !== 'scene' && options[key]) {
params[key] = options[key];
}
});
// 获取当前登录用户ID(从本地存储或全局状态获取)
// TODO: 根据你的项目实际情况获取用户ID
// 例如: const userInfo = uni.getStorageSync('userInfo');
// params.user_id = userInfo?.id || 0;
return params;
};
// 加载诊单详情
const loadDiagnosisDetail = async () => {
// 从页面参数获取诊单ID
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const params = parsePageParams(currentPage.options);
const params = proxy.$parsePageParams(currentPage.options);
dingdan_ok.value=uni.getStorageSync('dingdan_ok');
const diagnosisId = params.id;
console.log('页面参数:', params, '诊单ID:', diagnosisId);
console.log('进入onMounted:', params, '诊单ID:', diagnosisId);
if (!diagnosisId) {
uni.showToast({ title: '缺少诊单ID', icon: 'none' });
@@ -724,7 +688,7 @@ const handleConfirm = () => {
const confirmDiagnosis = async () => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const params = parsePageParams(currentPage.options);
const params = proxy.$parsePageParams(currentPage.options);
console.log('确认诊单参数:', params);
+1 -24
View File
@@ -100,36 +100,13 @@ onMounted(() => {
loadOrderDetail()
})
const parsePageParams = (options) => {
const params = {}
if (options.scene) {
try {
const decodedScene = decodeURIComponent(options.scene)
decodedScene.split('&').forEach(item => {
const [key, value] = item.split('=')
if (key && value) {
params[key] = value
}
})
} catch (error) {
console.error('解析scene参数失败:', error)
}
}
Object.keys(options).forEach(key => {
if (key !== 'scene' && options[key]) {
params[key] = options[key]
}
})
return params
}
const loadOrderDetail = async () => {
loading.value = true
errorMsg.value = ''
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const params = parsePageParams(currentPage.options || {})
const params = proxy.$parsePageParams(currentPage.options || {})
orderNo.value = params.order_no || ''
if (!orderNo.value) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

+5 -1
View File
@@ -78,5 +78,9 @@ export const doctorApi = {
getDoctorDetail: (id) => get('/api/doctor/detail', { id }, false),
// 获取医生排班
getDoctorRoster: (doctorId, date) => get('/api/doctor/roster', { doctor_id: doctorId, date }, false)
getDoctorRoster: (doctorId, date) => get('/api/doctor/roster', { doctor_id: doctorId, date }, false),
// 获取医生评价列表
getDoctorReviews: (doctorId, pageNo = 1, pageSize = 10) =>
get('/api/doctor/reviews', { doctor_id: doctorId, page_no: pageNo, page_size: pageSize }, false)
}
+43
View File
@@ -100,6 +100,41 @@
<el-input v-model="formData.title" placeholder="如:主任医师、副主任医师" clearable />
</el-form-item>
<!-- 执业证书编号 -->
<el-form-item label="执业证书编号" prop="license_no">
<el-input v-model="formData.license_no" placeholder="请输入执业证书编号" clearable />
</el-form-item>
<!-- 图文问诊 -->
<el-form-item label="图文问诊">
<el-switch
v-model="formData.enable_image_consult"
:active-value="1"
:inactive-value="0"
/>
<span class="ml-2 text-gray-500">{{ formData.enable_image_consult === 1 ? '已开启' : '已关闭' }}</span>
</el-form-item>
<!-- 视频问诊 -->
<el-form-item label="视频问诊">
<el-switch
v-model="formData.enable_video_consult"
:active-value="1"
:inactive-value="0"
/>
<span class="ml-2 text-gray-500">{{ formData.enable_video_consult === 1 ? '已开启' : '已关闭' }}</span>
</el-form-item>
<!-- 是否开启收费 -->
<el-form-item label="是否开启收费">
<el-switch
v-model="formData.enable_charge"
:active-value="1"
:inactive-value="0"
/>
<span class="ml-2 text-gray-500">{{ formData.enable_charge === 1 ? '已开启' : '已关闭' }}</span>
</el-form-item>
<!-- 所属科室 -->
<el-form-item label="所属科室" prop="department">
<el-input v-model="formData.department" placeholder="如:中医科、内科" clearable />
@@ -228,6 +263,10 @@ const formData = reactive({
jobs_id: [],
avatar: '',
title: '', // 职称
license_no: '', // 执业证书编号
enable_image_consult: 1, // 是否开启图文问诊
enable_video_consult: 1, // 是否开启视频问诊
enable_charge: 0, // 是否开启收费
department: '', // 所属科室
specialty: '', // 擅长领域
education: '', // 教育背景
@@ -353,6 +392,10 @@ const open = (type = 'add') => {
jobs_id: [],
avatar: '',
title: '',
license_no: '',
enable_image_consult: 1,
enable_video_consult: 1,
enable_charge: 0,
department: '',
specialty: '',
education: '',
+67 -3
View File
@@ -37,6 +37,15 @@
/>
</el-form-item>
<el-form-item label="快捷日期">
<el-radio-group v-model="formData.date_preset" @change="handleDatePresetChange" size="default">
<el-radio-button label="">不限</el-radio-button>
<el-radio-button label="today">今天</el-radio-button>
<el-radio-button label="tomorrow">明天</el-radio-button>
<el-radio-button label="day_after">后天</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
@@ -51,7 +60,7 @@
<el-tab-pane label="全部" name="all" />
<el-tab-pane name="1">
<template #label>
<span>已预约</span>
<span>未就诊</span>
<el-badge v-if="statusCount[1]" :value="statusCount[1]" class="ml-2" type="success" />
</template>
</el-tab-pane>
@@ -303,6 +312,7 @@
<script setup lang="ts">
import { usePaging } from '@/hooks/usePaging'
import { watch } from 'vue'
import { appointmentLists, cancelAppointment, completeAppointment, appointmentDetail } from '@/api/doctor'
import { getCallSignature, generateMiniProgramQrcode } from '@/api/tcm'
import feedback from '@/utils/feedback'
@@ -332,7 +342,8 @@ const formData = reactive({
doctor_name: '',
status: '' as string | number,
start_date: '',
end_date: ''
end_date: '',
date_preset: '' as '' | 'today' | 'tomorrow' | 'day_after'
})
const activeTab = ref('all')
@@ -348,6 +359,28 @@ const { pager, getLists, resetPage, resetParams } = usePaging({
params: formData
})
// 手动修改日期范围时清除快捷日期
watch(
() => [formData.start_date, formData.end_date],
([start, end]) => {
if (!formData.date_preset) return
const today = new Date()
const pad = (n: number) => String(n).padStart(2, '0')
const todayStr = `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`
const tomorrow = new Date(today)
tomorrow.setDate(tomorrow.getDate() + 1)
const tomorrowStr = `${tomorrow.getFullYear()}-${pad(tomorrow.getMonth() + 1)}-${pad(tomorrow.getDate())}`
const dayAfter = new Date(today)
dayAfter.setDate(dayAfter.getDate() + 2)
const dayAfterStr = `${dayAfter.getFullYear()}-${pad(dayAfter.getMonth() + 1)}-${pad(dayAfter.getDate())}`
const expected = formData.date_preset === 'today' ? todayStr : formData.date_preset === 'tomorrow' ? tomorrowStr : dayAfterStr
const startStr = (start || '').split(' ')[0]
if (startStr !== expected) {
formData.date_preset = ''
}
}
)
// 获取各状态数量
const getStatusCount = async () => {
try {
@@ -387,15 +420,46 @@ const loadData = async () => {
await getStatusCount()
}
// 快捷日期变更
const handleDatePresetChange = (val: string | number | boolean | undefined) => {
const v = String(val || '')
if (!v) {
formData.start_date = ''
formData.end_date = ''
loadData()
return
}
const today = new Date()
const pad = (n: number) => String(n).padStart(2, '0')
let target: Date
if (v === 'today') {
target = today
} else if (v === 'tomorrow') {
target = new Date(today)
target.setDate(target.getDate() + 1)
} else {
target = new Date(today)
target.setDate(target.getDate() + 2)
}
const dateStr = `${target.getFullYear()}-${pad(target.getMonth() + 1)}-${pad(target.getDate())}`
formData.start_date = dateStr
formData.end_date = dateStr
pager.page = 1
loadData()
}
// 搜索
const handleSearch = () => {
resetPage()
pager.page = 1
loadData()
}
// 重置
const handleReset = () => {
activeTab.value = 'all'
formData.date_preset = ''
resetParams()
getStatusCount()
}
const detailVisible = ref(false)
@@ -70,6 +70,10 @@ class AdminLogic extends BaseLogic
'education' => $params['education'] ?? null,
'experience' => $params['experience'] ?? null,
'honors' => $params['honors'] ?? null,
'license_no' => $params['license_no'] ?? '',
'enable_image_consult' => $params['enable_image_consult'] ?? 1,
'enable_video_consult' => $params['enable_video_consult'] ?? 1,
'enable_charge' => $params['enable_charge'] ?? 0,
]);
// 角色
@@ -120,6 +124,10 @@ class AdminLogic extends BaseLogic
'education' => $params['education'] ?? null,
'experience' => $params['experience'] ?? null,
'honors' => $params['honors'] ?? null,
'license_no' => $params['license_no'] ?? '',
'enable_image_consult' => $params['enable_image_consult'] ?? 1,
'enable_video_consult' => $params['enable_video_consult'] ?? 1,
'enable_charge' => $params['enable_charge'] ?? 0,
];
// 头像
@@ -254,6 +262,7 @@ class AdminLogic extends BaseLogic
'multipoint_login', 'avatar',
'gender', 'age', 'phone', 'title', 'department',
'specialty', 'education', 'experience', 'honors',
'license_no', 'enable_image_consult', 'enable_video_consult', 'enable_charge',
'work_wechat_userid'
])->findOrEmpty($params['id'])->toArray();
@@ -1437,6 +1437,75 @@ class DiagnosisLogic extends BaseLogic
}
}
/**
* @notes 新建就诊卡(供小程序调用)
* patient_id 可选:有则复用(同患者多张卡),无则自动生成(新患者首张卡)
* @param array $params
* @return array|bool 成功返回 ['id'=>诊单ID, 'patient_id'=>患者ID],失败返回false
*/
public static function addCard(array $params)
{
try {
$userId = $params['user_id'] ?? 0;
unset($params['user_id']); // 诊单表无此字段,仅用于创建 view_record
$patientId = $params['patient_id'] ?? 0;
if (!$patientId) {
$params['patient_id'] = self::generatePatientId();
}
$params['status'] = 1;
// 处理既往史数组
if (isset($params['past_history']) && is_array($params['past_history'])) {
$params['past_history'] = implode(',', $params['past_history']);
}
$multiSelectFields = [
'diet_condition', 'body_feeling', 'sleep_condition', 'eye_condition',
'head_feeling', 'sweat_condition', 'skin_condition', 'urine_condition',
'stool_condition', 'kidney_condition', 'local_hospital_diagnosis'
];
foreach ($multiSelectFields as $field) {
if (isset($params[$field]) && is_array($params[$field])) {
$params[$field] = implode(',', $params[$field]);
}
}
if (isset($params['tongue_images']) && is_array($params['tongue_images'])) {
$params['tongue_images'] = json_encode($params['tongue_images'], JSON_UNESCAPED_UNICODE);
}
if (isset($params['report_files']) && is_array($params['report_files'])) {
$params['report_files'] = json_encode($params['report_files'], JSON_UNESCAPED_UNICODE);
}
if (isset($params['diagnosis_date'])) {
$params['diagnosis_date'] = is_numeric($params['diagnosis_date']) ? $params['diagnosis_date'] : strtotime($params['diagnosis_date']);
}
$model = Diagnosis::create($params);
self::createPatientTrtcAccount($model->patient_id);
// 关联 diagnosis_view_records,便于用户通过 User::with('diagnosis') 获取就诊卡
if ($userId) {
$now = time();
\app\common\model\DiagnosisViewRecord::create([
'user_id' => $userId,
'diagnosis_id' => $model->id,
'patient_id' => $model->patient_id,
'share_user_id' => 0,
'view_count' => 1,
'first_view_time' => $now,
'last_view_time' => $now,
'is_confirmed' => 0,
'create_time' => $now,
'update_time' => $now
]);
}
return ['id' => $model->id, 'patient_id' => $model->patient_id];
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 更新就诊卡(供小程序调用)
* @param array $params
+19 -1
View File
@@ -11,7 +11,7 @@ use app\api\logic\DoctorLogic;
*/
class DoctorController extends BaseApiController
{
public array $notNeedLogin = ['lists', 'detail'];
public array $notNeedLogin = ['lists', 'detail', 'reviews'];
/**
* @notes 获取医生列表
@@ -70,4 +70,22 @@ class DoctorController extends BaseApiController
$result = DoctorLogic::getDoctorRoster($doctorId, $date);
return $this->success('', $result);
}
/**
* @notes 获取医生评价列表
* @return \think\response\Json
*/
public function reviews()
{
$doctorId = $this->request->get('doctor_id', 0);
$pageNo = $this->request->get('page_no', 1);
$pageSize = $this->request->get('page_size', 10);
if (!$doctorId) {
return $this->fail('医生ID不能为空');
}
$result = DoctorLogic::getDoctorReviews($doctorId, $pageNo, $pageSize);
return $this->success('', $result);
}
}
@@ -155,6 +155,74 @@ class TcmController extends BaseApiController
}
}
/**
* @notes 新建就诊卡(供小程序调用)
* @return \think\response\Json
*/
public function addCard()
{
$params = [
'user_id' => $this->userId,
'patient_id' => $this->request->post('patient_id'),
'patient_name' => $this->request->post('patient_name'),
'gender' => $this->request->post('gender'),
'age' => $this->request->post('age'),
'id_card' => $this->request->post('id_card'),
'height' => $this->request->post('height'),
'weight' => $this->request->post('weight'),
'systolic_pressure' => $this->request->post('systolic_pressure'),
'diastolic_pressure' => $this->request->post('diastolic_pressure'),
'fasting_blood_sugar' => $this->request->post('fasting_blood_sugar'),
'diabetes_discovery_year' => $this->request->post('diabetes_discovery_year'),
'local_hospital_diagnosis' => $this->request->post('local_hospital_diagnosis'),
'local_hospital_name' => $this->request->post('local_hospital_name'),
'local_hospital_visit_date' => $this->request->post('local_hospital_visit_date'),
'diagnosis_date' => $this->request->post('diagnosis_date'),
'diagnosis_type' => $this->request->post('diagnosis_type'),
'syndrome_type' => $this->request->post('syndrome_type'),
'diabetes_type' => $this->request->post('diabetes_type'),
'appetite' => $this->request->post('appetite'),
'water_intake' => $this->request->post('water_intake'),
'diet_condition' => $this->request->post('diet_condition'),
'weight_change' => $this->request->post('weight_change'),
'body_feeling' => $this->request->post('body_feeling'),
'sleep_condition' => $this->request->post('sleep_condition'),
'eye_condition' => $this->request->post('eye_condition'),
'head_feeling' => $this->request->post('head_feeling'),
'sweat_condition' => $this->request->post('sweat_condition'),
'skin_condition' => $this->request->post('skin_condition'),
'urine_condition' => $this->request->post('urine_condition'),
'stool_condition' => $this->request->post('stool_condition'),
'kidney_condition' => $this->request->post('kidney_condition'),
'fatty_liver_degree' => $this->request->post('fatty_liver_degree'),
'past_history' => $this->request->post('past_history'),
'trauma_history' => $this->request->post('trauma_history'),
'surgery_history' => $this->request->post('surgery_history'),
'allergy_history' => $this->request->post('allergy_history'),
'family_history' => $this->request->post('family_history'),
'pregnancy_history' => $this->request->post('pregnancy_history'),
'symptoms' => $this->request->post('symptoms'),
'tongue_coating' => $this->request->post('tongue_coating'),
'tongue_images' => $this->request->post('tongue_images'),
'report_files' => $this->request->post('report_files'),
'pulse' => $this->request->post('pulse'),
'treatment_principle' => $this->request->post('treatment_principle'),
'prescription' => $this->request->post('prescription'),
'doctor_advice' => $this->request->post('doctor_advice'),
'remark' => $this->request->post('remark')
];
// patient_id 可选:不传则创建时自动生成(新患者首张就诊卡)
try {
$result = DiagnosisLogic::addCard($params);
if ($result === false) {
return $this->fail(DiagnosisLogic::getError());
}
return $this->success('创建成功', $result);
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
}
/**
* @notes 更新就诊卡(供小程序调用)
* @return \think\response\Json
+76 -7
View File
@@ -6,6 +6,7 @@ use app\common\logic\BaseLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminRole;
use app\common\model\doctor\Roster;
use app\common\model\doctor\Appointment;
/**
* 医生逻辑层
@@ -86,7 +87,7 @@ class DoctorLogic extends BaseLogic
}
/**
* @notes 获取医生详情
* @notes 获取医生详情(含完整档案信息)
* @param int $doctorId
* @return array|null
*/
@@ -95,7 +96,7 @@ class DoctorLogic extends BaseLogic
// 检查该医生是否有医生角色(role_id = 1)
$hasRole = AdminRole::where('admin_id', $doctorId)
->where('role_id', 1)
->exists();
->count() > 0;
if (!$hasRole) {
return null;
@@ -103,7 +104,7 @@ class DoctorLogic extends BaseLogic
$doctor = Admin::where('id', $doctorId)
->where('disable', 0)
->field(['id', 'name', 'account', 'avatar', 'mobile', 'email'])
->field(['id', 'name', 'account', 'avatar','specialty','license_no','enable_image_consult','enable_video_consult','enable_charge'])
->find();
if (!$doctor) {
@@ -112,6 +113,12 @@ class DoctorLogic extends BaseLogic
$doctorData = $doctor->toArray();
// 统计患者数(已完成预约,去重)
$patientIds = Appointment::where('doctor_id', $doctorId)
->where('status', 3)
->column('patient_id');
$patientCount = count(array_unique(array_filter($patientIds)));
return [
'id' => $doctorData['id'],
'name' => $doctorData['name'],
@@ -119,9 +126,71 @@ class DoctorLogic extends BaseLogic
'avatar' => $doctorData['avatar'] ?? '',
'mobile' => $doctorData['mobile'] ?? '',
'email' => $doctorData['email'] ?? '',
'specialty' => '医生',
'title' => '医生',
'rating' => '4.8',
'license_no' => $doctorData['license_no'] ?? '',
'enable_image_consult' => $doctorData['enable_image_consult'] ?? 1,
'enable_video_consult' => $doctorData['enable_video_consult'] ?? 1,
'enable_charge' => $doctorData['enable_charge'] ?? 0,
'specialty' => '中医',
'title' => '主任医师',
'hospital' => '甄养堂互联网医院',
'experience' => '24年临床经验',
'rating' => '4.9',
'patient_count' => $patientCount,
'papers' => 15,
'about' => $doctorData['specialty'] ?? '擅长运用传统中医理论与现代诊断相结合,专注于慢性炎症及呼吸系统健康的调理。精通草本方剂,致力于为患者提供个性化的整体康复方案。',
'expertise' => [
['icon' => 'psychiatry', 'title' => '草本药方', 'desc' => '为整体康复提供量身定制的草本处方。'],
['icon' => 'eco', 'title' => '草本药方', 'desc' => '为整体康复提供量身定制的草本处方。'],
['icon' => 'vital_signs', 'title' => '切脉诊断', 'desc' => '运用传统方法对全身平衡和器官健康进行深度触诊评估。', 'wide' => true],
],
];
}
/**
* @notes 获取医生评价列表
* @param int $doctorId
* @param int $pageNo
* @param int $pageSize
* @return array
*/
public static function getDoctorReviews($doctorId, $pageNo = 1, $pageSize = 10)
{
$hasRole = AdminRole::where('admin_id', $doctorId)
->where('role_id', 1)
->count() > 0;
if (!$hasRole) {
return ['lists' => [], 'count' => 0];
}
// 默认评价数据(后续可扩展为独立评价表)
$defaultReviews = [
[
'id' => 1,
'patient_initials' => 'JS',
'patient_name' => 'James S.',
'rating' => 5,
'content' => '李医生对我慢性疲劳的治疗改变了我的生活。他在建议方剂之前足足听我倾诉了45分钟。真是一位大师级的人物。',
'create_time' => '2天前',
],
[
'id' => 2,
'patient_initials' => 'ML',
'patient_name' => 'Mei Ling',
'rating' => 5,
'content' => '非常专业,知识渊博。针灸疗程显著缓解了我的偏头痛。非常推荐这家医院。',
'create_time' => '1周前',
],
];
$offset = ($pageNo - 1) * $pageSize;
$lists = array_slice($defaultReviews, $offset, $pageSize);
return [
'lists' => $lists,
'count' => count($defaultReviews),
'page_no' => $pageNo,
'page_size' => $pageSize,
];
}
@@ -136,7 +205,7 @@ class DoctorLogic extends BaseLogic
// 检查该医生是否有医生角色
$hasRole = AdminRole::where('admin_id', $doctorId)
->where('role_id', 1)
->exists();
->count() > 0;
if (!$hasRole) {
return [];
+1
View File
@@ -70,6 +70,7 @@ class UserLogic extends BaseLogic
*/
public static function info(int $userId)
{
$user = User::where(['id' => $userId])
->with('diagnosis')
->field('id,sn,sex,account,password,nickname,real_name,avatar,mobile,age,create_time,user_money')
+7
View File
@@ -0,0 +1,7 @@
-- 医生表(la_admin)新增字段:执业证书编号、图文问诊、视频问诊、收费开关
-- 若项目使用 zyt_ 前缀,请将 la_admin 替换为 zyt_admin 后执行
ALTER TABLE `la_admin` ADD COLUMN `license_no` varchar(100) NULL DEFAULT '' COMMENT '执业证书编号';
ALTER TABLE `la_admin` ADD COLUMN `enable_image_consult` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否开启图文问诊:0-关闭 1-开启';
ALTER TABLE `la_admin` ADD COLUMN `enable_video_consult` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否开启视频问诊:0-关闭 1-开启';
ALTER TABLE `la_admin` ADD COLUMN `enable_charge` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否开启收费:0-关闭 1-开启';