This commit is contained in:
Your Name
2026-03-18 14:53:09 +08:00
parent 338b104d50
commit 7832514f28
315 changed files with 7071 additions and 1136 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ const wxcode = async (code) => {
}, false) // 不显示加载中
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('userData', res.data)
if(res.code==1){
if(res.code==1 &&res.data.diagnosis){
video(res.data)
}
console.log('请求结果:', res.data)
+488 -138
View File
@@ -46,6 +46,12 @@
<!-- 表情消息 -->
<text v-if="msg.type === 'face'" class="message-emoji">{{ msg.emoji }}</text>
<!-- 语音消息 -->
<view v-if="msg.type === 'audio'" class="message-audio" @click="playAudio(msg)">
<text class="audio-icon">{{ playingAudioId === msg.audioId ? '⏸' : '▶' }}</text>
<text class="audio-duration">{{ msg.duration || 0 }}</text>
</view>
<!-- 消息时间 -->
<view class="message-meta">
<text class="message-time">{{ formatTime(msg.time) }}</text>
@@ -73,22 +79,27 @@
<uni-icons type="tool " size="30" color="#999"></uni-icons>
<text class="tool-label">表情</text>
</view> -->
<view class="tool-item tool-item-left" @click="toggleInputMode">
<uni-icons :type="inputMode === 'voice' ? 'compose' : 'mic'" size="38" color="#576b95"></uni-icons>
<text class="tool-label" v-if="inputMode === 'voice'">文字</text>
<text class="tool-label" v-else>语音</text>
</view>
<view class="tool-item" @click="chooseImage">
<uni-icons type="image " size="30" color="#999"></uni-icons>
<uni-icons type="image " size="40" color="#576b95"></uni-icons>
<text class="tool-label">图片</text>
</view>
<view class="tool-item" @click="chooseFile">
<uni-icons type="paperclip " size="30" color="#999"></uni-icons>
<uni-icons type="paperclip " size="40" color="#576b95"></uni-icons>
<text class="tool-label">文件</text>
</view>
</view>
<!-- 输入 -->
<view class="input-wrapper">
<!-- 输入区域文字模式 -->
<view v-if="inputMode === 'text'" class="input-wrapper">
<input
class="message-input"
v-model="inputText"
placeholder="输入消息..."
placeholder="输入消息内容..."
:adjust-position="false"
:hold-keyboard="true"
@focus="onInputFocus"
@@ -99,6 +110,37 @@
<text class="send-text">发送</text>
</view>
</view>
<!-- 语音模式按住说话 -->
<view
v-else
class="voice-input-wrapper"
:class="{ 'voice-input-recording': isRecording }"
@touchstart="onVoiceTouchStart"
@touchmove="onVoiceTouchMove"
@touchend="onVoiceTouchEnd"
@touchcancel="onVoiceTouchCancel"
>
<text class="voice-input-text">{{ isRecording ? (willCancel ? '松开 取消' : '松开 发送') : '按住 说话' }}</text>
</view>
</view>
<!-- 录音浮层 - 微信风格 -->
<view v-if="isRecording" class="recording-overlay">
<view class="recording-popup" :class="{ 'recording-cancel': willCancel }">
<view class="recording-icon">
<view v-if="!willCancel" class="recording-waves">
<view class="wave"></view>
<view class="wave"></view>
<view class="wave"></view>
<view class="wave"></view>
<view class="wave"></view>
</view>
<text v-else class="cancel-icon"></text>
</view>
<text class="recording-hint">{{ willCancel ? '松开手指,取消发送' : '上滑取消' }}</text>
<text class="recording-duration">{{ recordingDuration }}"</text>
</view>
</view>
<!-- 表情选择器 -->
@@ -137,7 +179,15 @@
const showEmoji = ref(false);
const keyboardHeight = ref(0);
const userAvatar = ref(''); // 当前用户头像
const inputMode = ref('text'); // 'text' | 'voice'
const isRecording = ref(false);
const recordingDuration = ref(0);
const willCancel = ref(false); // 上滑取消
const playingAudioId = ref('');
let innerAudioContext = null;
let chat = null;
let recorderManager = null;
let recordingTimer = null;
let conversationID = '';
let targetUserID = '';
let currentUserID = ''; // 当前登录用户ID
@@ -158,6 +208,174 @@
'👏', '🙌', '👐', '🤲', '🤝', '🙏', '💪', '❤️'
];
// 切换输入模式(文字/语音)
function toggleInputMode() {
inputMode.value = inputMode.value === 'text' ? 'voice' : 'text';
}
// 初始化录音管理器
function initRecorderManager() {
// #ifdef MP-WEIXIN
recorderManager = uni.getRecorderManager();
recorderManager.onStop((res) => {
if (willCancel.value) {
willCancel.value = false;
} else if (res.tempFilePath && res.duration >= 1000) {
sendAudioMessage(res);
} else if (res.duration < 1000) {
uni.showToast({ title: '录音时间太短', icon: 'none' });
}
isRecording.value = false;
recordingDuration.value = 0;
if (recordingTimer) clearInterval(recordingTimer);
});
recorderManager.onError((err) => {
console.error('录音错误:', err);
isRecording.value = false;
recordingDuration.value = 0;
if (recordingTimer) clearInterval(recordingTimer);
uni.showToast({ title: '录音失败', icon: 'none' });
});
// #endif
}
// 按住说话 - 开始
function onVoiceTouchStart(e) {
// #ifdef MP-WEIXIN
e.preventDefault();
willCancel.value = false;
touchStartY = e.touches[0]?.clientY || 0;
if (!recorderManager) initRecorderManager();
startRecord();
// #endif
// #ifndef MP-WEIXIN
uni.showToast({ title: '当前平台暂不支持语音', icon: 'none' });
// #endif
}
// 松开 - 发送或取消
function onVoiceTouchEnd(e) {
// #ifdef MP-WEIXIN
e.preventDefault();
if (isRecording.value && recorderManager) {
recorderManager.stop();
}
// #endif
}
// 触摸取消(如滑出区域)
function onVoiceTouchCancel(e) {
onVoiceTouchEnd(e);
}
let touchStartY = 0;
// 监听上滑取消
function onVoiceTouchMove(e) {
if (!isRecording.value) return;
const touch = e.touches[0];
if (touch && touchStartY - touch.clientY > 80) {
willCancel.value = true;
}
}
function startRecord() {
// #ifdef MP-WEIXIN
uni.authorize({
scope: 'scope.record',
success: () => {
isRecording.value = true;
recordingDuration.value = 0;
recorderManager.start({
duration: 60000,
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 192000,
format: 'aac'
});
recordingTimer = setInterval(() => {
recordingDuration.value++;
}, 1000);
},
fail: () => {
uni.showModal({
title: '需要录音权限',
content: '请在设置中开启录音权限以发送语音消息',
confirmText: '去设置',
success: (res) => {
if (res.confirm) uni.openSetting();
}
});
}
});
// #endif
}
async function sendAudioMessage(recordRes) {
try {
uni.showLoading({ title: '发送中...' });
const durationSec = Math.max(1, Math.round(recordRes.duration / 1000));
const message = chat.createAudioMessage({
to: targetUserID,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: { file: recordRes },
onProgress: () => {}
});
const sendResult = await chat.sendMessage(message);
const payload = sendResult?.data?.message?.payload || {};
const audioUrl = payload.url || payload.file?.url || recordRes.tempFilePath;
messages.value.push({
type: 'audio',
audioUrl,
duration: durationSec,
audioId: sendResult?.data?.message?.ID || 'local-' + Date.now(),
time: Date.now(),
flow: 'out',
avatar: userAvatar.value
});
uni.hideLoading();
uni.showToast({ title: '发送成功', icon: 'success' });
scrollToBottom();
setTimeout(() => scrollToBottom(), 150);
} catch (error) {
console.error('发送语音失败:', error);
uni.hideLoading();
uni.showToast({ title: '发送失败', icon: 'none' });
}
}
function playAudio(msg) {
if (!msg.audioUrl) {
uni.showToast({ title: '无法播放', icon: 'none' });
return;
}
if (playingAudioId.value === msg.audioId) {
if (innerAudioContext) {
innerAudioContext.stop();
innerAudioContext.destroy();
innerAudioContext = null;
}
playingAudioId.value = '';
return;
}
if (innerAudioContext) {
innerAudioContext.stop();
innerAudioContext.destroy();
}
innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.src = msg.audioUrl;
innerAudioContext.play();
playingAudioId.value = msg.audioId;
innerAudioContext.onEnded(() => {
playingAudioId.value = '';
innerAudioContext = null;
});
innerAudioContext.onError(() => {
playingAudioId.value = '';
innerAudioContext = null;
uni.showToast({ title: '播放失败', icon: 'none' });
});
}
onLoad(async (options) => {
try {
const app = getApp();
@@ -236,6 +454,13 @@
onUnload(() => {
if (chat) chat.off(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
if (scrollTimer) clearTimeout(scrollTimer);
if (innerAudioContext) {
innerAudioContext.stop();
innerAudioContext.destroy();
}
if (isRecording.value && recorderManager) {
recorderManager.stop();
}
});
async function loadHistoryMessages() {
@@ -335,8 +560,11 @@
return null; // 解析失败的消息不显示
}
case TencentCloudChat.TYPES.MSG_AUDIO:
return { ...baseMsg, type: 'text', text: '[语音消息]' };
case TencentCloudChat.TYPES.MSG_AUDIO: {
const audioUrl = msg.payload?.url || msg.payload?.file?.url || '';
const audioDuration = msg.payload?.second ?? (msg.payload?.duration ? Math.round(msg.payload.duration / 1000) : 0);
return { ...baseMsg, type: 'audio', audioUrl, duration: Math.round(audioDuration), audioId: msg.ID || msg.id || '' };
}
case TencentCloudChat.TYPES.MSG_VIDEO:
return { ...baseMsg, type: 'text', text: '[视频消息]' };
@@ -625,37 +853,36 @@
</script>
<style scoped>
/* 容器 */
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
.chat-container {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
background: #f7f8fa;
background: #e8eaed;
}
/* 顶部导航栏 */
.chat-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 20rpx;
height: 100rpx;
padding: 0 28rpx;
background: #fff;
border-bottom: 1rpx solid #e7e7e7;
border-bottom: 4rpx solid #e0e0e0;
}
.header-left {
width: 88rpx;
height: 88rpx;
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: flex-start;
}
.back-icon {
font-size: 48rpx;
color: #576b95;
font-size: 52rpx;
color: #1890ff;
font-weight: 500;
}
@@ -667,51 +894,50 @@
}
.header-title {
font-size: 34rpx;
font-size: 40rpx;
font-weight: 500;
color: #000;
color: #1a1a1a;
}
.header-status {
font-size: 22rpx;
color: #888;
margin-top: 2rpx;
font-size: 28rpx;
color: #555;
margin-top: 4rpx;
}
.header-right {
width: 88rpx;
width: 100rpx;
display: flex;
justify-content: flex-end;
}
.video-call-btn {
width: 64rpx;
height: 64rpx;
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
.video-icon {
font-size: 44rpx;
color: #576b95;
font-size: 52rpx;
color: #1890ff;
}
/* 消息列表 */
.message-list {
flex: 1;
overflow: hidden;
}
.message-container {
padding: 20rpx 24rpx;
padding-bottom: 280rpx;
padding: 28rpx 32rpx;
padding-bottom: 320rpx;
min-height: 100%;
}
.message-wrapper {
display: flex;
margin-bottom: 24rpx;
margin-bottom: 32rpx;
animation: fadeIn 0.25s ease;
}
@@ -728,45 +954,45 @@
flex-direction: row-reverse;
}
/* 头像 */
/* 头像 - 更大 */
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
width: 100rpx;
height: 100rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-weight: 500;
background: #e8e8e8;
background: #e0e0e0;
}
.avatar-in {
background: #e8e8e8;
margin-right: 16rpx;
background: #e0e0e0;
margin-right: 20rpx;
}
.avatar-out {
background: #95ec69;
margin-left: 16rpx;
margin-left: 20rpx;
}
.avatar-text {
font-size: 28rpx;
font-size: 36rpx;
color: #fff;
}
.avatar-image {
width: 100%;
height: 100%;
border-radius: 8rpx;
border-radius: 12rpx;
}
/* 消息气泡 */
/* 消息气泡 - 更大内边距 */
.message-bubble {
max-width: 500rpx;
padding: 20rpx 24rpx;
border-radius: 8rpx;
max-width: 540rpx;
padding: 28rpx 32rpx;
border-radius: 16rpx;
position: relative;
}
@@ -778,113 +1004,110 @@
background: #95ec69;
}
/* 文本消息 */
/* 文本消息 - 大字号 */
.message-text {
font-size: 30rpx;
line-height: 1.5;
color: #000;
font-size: 38rpx;
line-height: 1.6;
color: #1a1a1a;
word-break: break-all;
}
.bubble-out .message-text {
color: #000;
color: #1a1a1a;
}
/* 图片消息 */
.message-image {
max-width: 100%;
border-radius: 8rpx;
border-radius: 12rpx;
display: block;
}
/* 文件消息 */
/* 文件消息 - 大触控区 */
.message-file {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx;
background: rgba(0, 0, 0, 0.04);
border-radius: 8rpx;
gap: 24rpx;
padding: 20rpx;
background: rgba(0, 0, 0, 0.05);
border-radius: 12rpx;
}
.bubble-out .message-file {
background: rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.08);
}
.file-icon-wrapper {
width: 72rpx;
height: 72rpx;
background: #576b95;
border-radius: 8rpx;
width: 88rpx;
height: 88rpx;
background: #1890ff;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.bubble-out .file-icon-wrapper {
background: rgba(0, 0, 0, 0.15);
background: rgba(0, 0, 0, 0.2);
}
.file-icon {
font-size: 40rpx;
font-size: 48rpx;
}
.file-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
gap: 12rpx;
}
.file-name {
font-size: 28rpx;
color: #000;
font-weight: 400;
font-size: 34rpx;
color: #1a1a1a;
font-weight: 500;
}
.bubble-out .file-name {
color: #000;
color: #1a1a1a;
}
.file-size {
font-size: 22rpx;
color: #888;
font-size: 28rpx;
color: #555;
}
.bubble-out .file-size {
color: #666;
color: #555;
}
.download-icon {
font-size: 28rpx;
color: #576b95;
font-size: 36rpx;
color: #1890ff;
}
.bubble-out .download-icon {
color: #000;
color: #1a1a1a;
}
/* 表情消息 */
.message-emoji {
font-size: 96rpx;
font-size: 112rpx;
line-height: 1;
}
/* 消息元信息 */
.message-meta {
margin-top: 8rpx;
margin-top: 12rpx;
display: flex;
align-items: center;
justify-content: flex-end;
}
.message-time {
font-size: 20rpx;
color: #b2b2b2;
font-size: 26rpx;
color: #666;
}
.bubble-out .message-time {
color: #888;
color: #555;
}
/* 空状态 */
@@ -894,92 +1117,220 @@
align-items: center;
justify-content: center;
height: 600rpx;
gap: 16rpx;
gap: 24rpx;
}
.empty-icon {
font-size: 100rpx;
opacity: 0.25;
font-size: 120rpx;
opacity: 0.3;
}
.empty-text {
font-size: 28rpx;
color: #b2b2b2;
font-weight: 400;
font-size: 38rpx;
color: #555;
font-weight: 500;
}
.empty-hint {
font-size: 24rpx;
color: #d0d0d0;
font-size: 32rpx;
color: #888;
}
/* 底部占位 */
.bottom-placeholder {
height: 40rpx;
height: 56rpx;
}
/* 输入区域 */
/* 输入区域 - 大触控区 */
.input-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #f7f8fa;
border-top: 1rpx solid #e7e7e7;
padding: 16rpx 20rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
background: #ededed;
border-top: 1rpx solid #d9d9d9;
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 100;
transition: padding-bottom 0.3s ease;
}
/* 工具栏 */
.toolbar {
display: flex;
gap: 32rpx;
margin-bottom: 16rpx;
padding: 0 8rpx;
gap: 48rpx;
margin-bottom: 24rpx;
padding: 0 12rpx;
}
.tool-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
gap: 12rpx;
min-height: 100rpx;
}
.tool-item-left {
margin-right: 16rpx;
}
.tool-icon {
font-size: 48rpx;
font-size: 56rpx;
}
.tool-label {
font-size: 20rpx;
color: #888;
font-size: 30rpx;
color: #555;
font-weight: 500;
}
/* 语音输入 - 微信风格 */
.voice-input-wrapper {
height: 96rpx;
background: #f7f7f7;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 56rpx;
border: 2rpx solid #e0e0e0;
}
.voice-input-recording {
background: #e8e8e8;
border-color: #c0c0c0;
}
.voice-input-text {
font-size: 34rpx;
color: #333;
}
/* 录音浮层 - 微信风格 */
.recording-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.recording-popup {
width: 320rpx;
padding: 48rpx;
background: rgba(0, 0, 0, 0.75);
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
}
.recording-popup.recording-cancel {
background: rgba(0, 0, 0, 0.6);
}
.recording-icon {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
}
.recording-waves {
display: flex;
align-items: flex-end;
gap: 8rpx;
height: 80rpx;
}
.recording-waves .wave {
width: 12rpx;
height: 24rpx;
background: #07c160;
border-radius: 6rpx;
animation: waveAnim 0.6s ease-in-out infinite;
}
.recording-waves .wave:nth-child(1) { animation-delay: 0s; }
.recording-waves .wave:nth-child(2) { animation-delay: 0.1s; }
.recording-waves .wave:nth-child(3) { animation-delay: 0.2s; }
.recording-waves .wave:nth-child(4) { animation-delay: 0.3s; }
.recording-waves .wave:nth-child(5) { animation-delay: 0.4s; }
@keyframes waveAnim {
0%, 100% { height: 24rpx; }
50% { height: 60rpx; }
}
.cancel-icon {
font-size: 80rpx;
color: #ff4d4f;
}
.recording-hint {
font-size: 28rpx;
color: #fff;
}
.recording-popup .recording-duration {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
}
.message-audio {
display: flex;
align-items: center;
gap: 16rpx;
padding: 20rpx 28rpx;
background: rgba(0, 0, 0, 0.05);
border-radius: 12rpx;
min-width: 120rpx;
}
.bubble-out .message-audio {
background: rgba(0, 0, 0, 0.08);
}
.audio-icon {
font-size: 36rpx;
color: #1890ff;
}
.audio-duration {
font-size: 34rpx;
color: #1a1a1a;
font-weight: 500;
}
/* 输入框 */
.input-wrapper {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 45rpx;
gap: 20rpx;
margin-bottom: 56rpx;
}
.message-input {
flex: 1;
height: 72rpx;
padding: 0 24rpx;
height: 96rpx;
padding: 0 32rpx;
background: #fff;
border-radius: 8rpx;
border: 1rpx solid #e7e7e7;
font-size: 28rpx;
color: #000;
border-radius: 16rpx;
border: 4rpx solid #e0e0e0;
font-size: 36rpx;
color: #1a1a1a;
}
.send-button {
width: 112rpx;
height: 72rpx;
background: #ededed;
border-radius: 8rpx;
width: 140rpx;
height: 96rpx;
background: #e0e0e0;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
@@ -991,16 +1342,15 @@
}
.send-text {
font-size: 28rpx;
font-size: 36rpx;
color: #888;
font-weight: 400;
font-weight: 500;
}
.send-button-active .send-text {
color: #fff;
}
/* 表情选择器 */
.emoji-panel {
position: fixed;
top: 0;
@@ -1017,8 +1367,8 @@
.emoji-content {
width: 100%;
max-height: 65vh;
background: #f7f8fa;
border-radius: 24rpx 24rpx 0 0;
background: #e8eaed;
border-radius: 28rpx 28rpx 0 0;
animation: slideUp 0.25s ease;
}
@@ -1031,20 +1381,20 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 28rpx 20rpx;
border-bottom: 1rpx solid #e7e7e7;
padding: 36rpx 36rpx 28rpx;
border-bottom: 4rpx solid #e0e0e0;
background: #fff;
}
.emoji-title {
font-size: 30rpx;
font-size: 38rpx;
font-weight: 500;
color: #000;
color: #1a1a1a;
}
.emoji-close {
width: 52rpx;
height: 52rpx;
width: 72rpx;
height: 72rpx;
background: #f0f0f0;
border-radius: 50%;
display: flex;
@@ -1053,27 +1403,27 @@
}
.close-icon {
font-size: 28rpx;
color: #888;
font-size: 36rpx;
color: #555;
}
.emoji-scroll {
max-height: 55vh;
padding: 20rpx;
background: #f7f8fa;
padding: 28rpx;
background: #e8eaed;
}
.emoji-grid {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
gap: 16rpx;
}
.emoji-cell {
width: 96rpx;
height: 96rpx;
width: 112rpx;
height: 112rpx;
background: #fff;
border-radius: 12rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
@@ -1086,6 +1436,6 @@
}
.emoji-icon {
font-size: 52rpx;
font-size: 60rpx;
}
</style>
@@ -0,0 +1,136 @@
<template>
<view class="container">
<!-- 加载中 -->
<view v-if="loading" class="loading-wrap">
<text class="loading-text">加载中...</text>
</view>
<block v-else-if="article">
<!-- 封面图 -->
<image v-if="article.image" :src="article.image" class="cover-image" mode="aspectFill" />
<!-- 内容区 -->
<view class="content-wrap">
<text class="article-title">{{ article.title }}</text>
<view class="meta-row">
<text class="meta-text">{{ article.author || '健康科普' }}</text>
<text class="meta-dot">·</text>
<text class="meta-text">{{ article.create_time }}</text>
<text class="meta-dot">·</text>
<text class="meta-text">{{ article.click }} 阅读</text>
</view>
<!-- 富文本内容 -->
<rich-text :nodes="article.content" class="article-content" />
</view>
</block>
<!-- 加载失败 -->
<view v-else class="empty-wrap">
<text class="empty-text">文章不存在或已下架</text>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
const article = ref(null)
const loading = ref(false)
const fetchDetail = async (id) => {
loading.value = true
try {
const response = await proxy.apiUrl({
url: '/api/article/detail',
method: 'GET',
data: { id }
})
if (response && response.code === 1) {
article.value = response.data
}
} catch (error) {
console.error('获取文章详情失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const id = currentPage?.options?.id
if (id) fetchDetail(id)
})
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
background: #f8f8f8;
}
.loading-wrap, .empty-wrap {
display: flex;
justify-content: center;
align-items: center;
padding-top: 200rpx;
}
.loading-text, .empty-text {
font-size: 28rpx;
color: #999;
}
.cover-image {
width: 100%;
height: 420rpx;
display: block;
}
.content-wrap {
background: #fff;
border-radius: 24rpx 24rpx 0 0;
margin-top: -24rpx;
padding: 40rpx 32rpx 60rpx;
min-height: calc(100vh - 396rpx);
}
.article-title {
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
line-height: 1.5;
display: block;
margin-bottom: 24rpx;
}
.meta-row {
display: flex;
align-items: center;
margin-bottom: 32rpx;
padding-bottom: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.meta-text {
font-size: 24rpx;
color: #999;
}
.meta-dot {
font-size: 24rpx;
color: #ccc;
margin: 0 12rpx;
}
.article-content {
font-size: 30rpx;
color: #333;
line-height: 1.8;
}
</style>
@@ -105,7 +105,7 @@
<text class="error-text">{{ error }}</text>
<view class="retry-btn" @click="loadDetail">重试</view>
</view>
<view v-if="doctor">
<!-- 底部操作栏 -->
<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">
@@ -117,6 +117,7 @@
<text class="btn-text">视频问诊</text>
</view>
</view>
</view>
</view>
</template>
@@ -0,0 +1,255 @@
<template>
<view class="container">
<!-- 顶部标题 -->
<view class="page-header">
<text class="page-title">名医推荐</text>
</view>
<!-- 医生列表 -->
<view class="doctors-list">
<view
v-for="doctor in doctors"
:key="doctor.id"
class="doctor-card"
@click="selectDoctor(doctor)"
>
<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" v-if="doctor.title">{{doctor.title}}</text>
</view>
<view class="doctor-actions">
<view class="appointment-btn" @click.stop="selectDoctor(doctor)">预约</view>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore" @click="loadMore">
<text class="load-more-text">{{ loading ? '加载中...' : '加载更多' }}</text>
</view>
<view class="no-more" v-else-if="doctors.length > 0">
<text class="no-more-text">没有更多了</text>
</view>
<!-- 空状态 -->
<view class="empty" v-if="!loading && doctors.length === 0">
<text class="empty-text">暂无医生信息</text>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
const doctors = ref([])
const loading = ref(false)
const pageNo = ref(1)
const pageSize = 10
const hasMore = ref(true)
const defaultAvatar = '/static/user/user.png'
const selectDoctor = (doctor) => {
uni.navigateTo({
url: `/doctor/pages/doctor/doctor?id=${doctor.id}`
})
}
const fetchDoctors = async (reset = false) => {
if (loading.value) return
loading.value = true
try {
const response = await proxy.apiUrl({
url: '/api/doctor/lists',
method: 'GET',
data: {
page_no: pageNo.value,
page_size: pageSize
}
})
if (response && response.code === 1) {
const list = response.data?.lists || []
if (reset) {
doctors.value = list
} else {
doctors.value.push(...list)
}
hasMore.value = list.length === pageSize
} else {
console.warn('获取医生列表失败:', response?.msg)
}
} catch (error) {
console.error('请求异常:', error)
} finally {
loading.value = false
}
}
const loadMore = () => {
if (!hasMore.value || loading.value) return
pageNo.value++
fetchDoctors()
}
onMounted(() => {
fetchDoctors(true)
})
</script>
<style scoped lang="scss">
$background: #faf9f5;
$primary: #204e2b;
$on-primary: #ffffff;
$on-primary-container: #afe2b3;
$surface-container-lowest: #ffffff;
$surface-container-high: #e9e8e4;
$surface-container-highest: #e3e2df;
$on-surface: #1b1c1a;
$on-surface-variant: #414941;
$outline: #727970;
.container {
background: $background;
min-height: 100vh;
padding: 24rpx 32rpx 120rpx;
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
.page-header {
margin-bottom: 40rpx;
}
.page-title {
font-size: 48rpx;
font-weight: bold;
color: $on-surface;
font-family: 'Noto Serif', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', serif;
}
.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;
}
.load-more {
text-align: center;
padding: 40rpx 0 20rpx;
}
.load-more-text {
font-size: 28rpx;
color: $primary;
}
.no-more {
text-align: center;
padding: 40rpx 0 20rpx;
}
.no-more-text {
font-size: 26rpx;
color: $outline;
}
.empty {
text-align: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 30rpx;
color: $outline;
}
</style>
+16 -4
View File
@@ -9,7 +9,7 @@
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录"
"navigationBarTitleText": "视频问诊"
}
},
{
@@ -27,7 +27,7 @@
{
"path": "TUICallKit/src/Components/TUICallKit",
"style": {
"navigationBarTitleText": "视频诊",
"navigationBarTitleText": "视频诊",
"navigationBarHidden": true,
"navigationStyle": "custom"
}
@@ -76,7 +76,7 @@
{
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "聊天"
"navigationBarTitleText": "图文问诊"
}
}
]
@@ -89,6 +89,18 @@
"style": {
"navigationBarTitleText": "医生信息"
}
},
{
"path": "pages/doctor/list/list",
"style": {
"navigationBarTitleText": "执业医生列表"
}
},
{
"path": "pages/article/article",
"style": {
"navigationBarTitleText": "健康百科"
}
}
]
}
@@ -112,7 +124,7 @@
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "甄养堂",
"navigationBarTitleText": "甄养堂互联网医院",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
+59 -56
View File
@@ -57,7 +57,7 @@
<!-- 空状态 -->
<view v-if="!loading && cardList.length === 0" class="empty-state">
<uni-icons type="wallet" size="60" color="#999"></uni-icons>
<uni-icons type="wallet" size="80" color="#1890ff"></uni-icons>
<text class="empty-text">暂无就诊卡</text>
<view class="add-card-btn" @click="createCard">新建就诊卡</view>
</view>
@@ -204,39 +204,41 @@ const formatDate = (timestamp) => {
</script>
<style lang="less" scoped>
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
.card-container {
min-height: 100vh;
background: linear-gradient(180deg, #f0f7ff 0%, #ffffff 100%);
padding-bottom: 40rpx;
background: linear-gradient(180deg, #e8f0fa 0%, #ffffff 100%);
padding-bottom: 56rpx;
}
.header {
background: #ffffff;
padding: 40rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
padding: 48rpx 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
.header-title {
font-size: 40rpx;
font-size: 48rpx;
font-weight: bold;
color: #1890ff;
}
}
.card-list {
padding: 30rpx;
padding: 40rpx;
}
.card-item {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
border-radius: 28rpx;
padding: 44rpx;
margin-bottom: 36rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s;
min-height: 200rpx;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
}
@@ -244,100 +246,100 @@ const formatDate = (timestamp) => {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
margin-bottom: 32rpx;
padding-bottom: 28rpx;
border-bottom: 4rpx solid #e8eaed;
}
.card-id {
.label {
font-size: 28rpx;
color: #666666;
font-size: 34rpx;
color: #555;
}
.value {
font-size: 32rpx;
font-size: 40rpx;
font-weight: bold;
color: #333333;
color: #1a1a1a;
}
}
.status-badge {
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
padding: 12rpx 28rpx;
border-radius: 28rpx;
font-size: 32rpx;
font-weight: 500;
&.status-confirmed {
background: #f6ffed;
color: #204e2b;
border: 2rpx solid #204e2b;
border: 4rpx solid #204e2b;
}
&.status-pending {
background: #fff7e6;
color: #fa8c16;
border: 2rpx solid #ffd591;
border: 4rpx solid #ffd591;
}
}
.card-info {
margin-bottom: 20rpx;
margin-bottom: 28rpx;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
margin-bottom: 24rpx;
.info-label {
font-size: 28rpx;
color: #666666;
min-width: 160rpx;
font-size: 34rpx;
color: #555;
min-width: 200rpx;
}
.info-value {
font-size: 30rpx;
color: #333333;
font-size: 36rpx;
color: #1a1a1a;
font-weight: 500;
}
}
.card-footer {
padding-top: 20rpx;
border-top: 2rpx solid #f0f0f0;
padding-top: 28rpx;
border-top: 4rpx solid #e8eaed;
}
.time-info {
display: flex;
align-items: center;
margin-bottom: 12rpx;
margin-bottom: 20rpx;
.time-label {
font-size: 24rpx;
color: #999999;
min-width: 140rpx;
font-size: 30rpx;
color: #555;
min-width: 180rpx;
}
.time-value {
font-size: 26rpx;
color: #666666;
font-size: 32rpx;
color: #555;
}
}
.view-count {
display: flex;
align-items: center;
margin-top: 8rpx;
margin-top: 12rpx;
.count-icon {
font-size: 28rpx;
margin-right: 8rpx;
font-size: 34rpx;
margin-right: 12rpx;
}
.count-text {
font-size: 24rpx;
color: #999999;
font-size: 30rpx;
color: #555;
}
}
@@ -346,37 +348,38 @@ const formatDate = (timestamp) => {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
padding: 160rpx 0;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
font-size: 140rpx;
margin-bottom: 40rpx;
}
.empty-text {
font-size: 32rpx;
color: #999999;
margin-bottom: 40rpx;
font-size: 40rpx;
color: #555;
margin-bottom: 56rpx;
}
}
.add-card-btn {
padding: 24rpx 64rpx;
padding: 36rpx 88rpx;
background: #1890ff;
color: #ffffff;
font-size: 30rpx;
font-size: 40rpx;
font-weight: 500;
border-radius: 48rpx;
border-radius: 56rpx;
min-height: 100rpx;
}
.loading-state {
display: flex;
justify-content: center;
padding: 80rpx 0;
padding: 120rpx 0;
.loading-text {
font-size: 28rpx;
color: #999999;
font-size: 36rpx;
color: #555;
}
}
</style>
+1 -1
View File
@@ -1837,7 +1837,7 @@ const goBack = () => {
color: #333333;
background: transparent;
box-sizing: border-box;
min-height: 60rpx;
min-height: 80rpx;
display: block;
}
+129 -93
View File
@@ -1,59 +1,59 @@
<template>
<view class="container">
<!-- 搜索栏 -->
<view class="search-section">
<!-- <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> -->
<!-- 科室分类 -->
<view class="department-section">
<!-- 平台资质 -->
<view class="qualification-section">
<view class="section-header">
<text class="section-title">科室分类</text>
<text class="more-link" @click="navigateToDepartments">查看全部</text>
<text class="section-title">平台资质</text>
<!-- <text class="more-link" @click="navigateToQualifications">查看详情</text> -->
</view>
<view class="department-grid">
<view class="dept-col-left">
<view class="qualification-grid">
<view class="qual-col-left">
<view
class="department-card large"
@click="navigateToDepartment(departments[0])"
class="qualification-card large"
@click="navigateToQualification(qualifications[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 class="qual-card-inner qual-primary">
<view class="qual-icon"><image src="/static/wjw.png" style="width: 100rpx; margin-left: -5px;" mode="widthFix"></image></view>
<text class="qual-name text-white">卫健委认证机构</text>
<text class="qual-desc text-white-sub">互联网诊疗资质备案</text>
</view>
</view>
</view>
<view class="dept-col-right">
<view class="qual-col-right">
<view
class="department-card small"
@click="navigateToDepartment(departments[1])"
class="qualification-card small"
@click="navigateToQualification(qualifications[1])"
>
<view class="dept-card-inner dept-beige">
<view class="dept-icon-wrap">
<view class="dept-icon"><text class="icon-text"></text></view>
<view class="qual-card-inner qual-beige">
<view class="qual-icon-wrap">
<view class="qual-icon"><image src="/static/yy.png" style="width: 100rpx; margin-left: -10px;" mode="widthFix"></image></view>
</view>
<view>
<text class="dept-name">中药</text>
<text class="dept-desc">古法方剂</text>
<text class="qual-name">互联网医院</text>
<text class="qual-desc">持证合规运营</text>
</view>
</view>
</view>
<view
class="department-card small"
@click="navigateToDepartment(departments[2])"
class="qualification-card small"
@click="navigateToAllDoctors"
>
<view class="dept-card-inner dept-grey">
<view class="dept-icon-wrap">
<view class="dept-icon"><text class="icon-text"></text></view>
<view class="qual-card-inner qual-grey">
<view class="qual-icon-wrap">
<view class="qual-icon"><image src="/static/ys.png" style="width: 90rpx; margin-left: -5px;" mode="widthFix"></image></view>
</view>
<view>
<text class="dept-name">推拿</text>
<text class="dept-desc">中医按摩</text>
<text class="qual-name">执业医师认证</text>
<text class="qual-desc">持证医师在线问诊</text>
</view>
</view>
</view>
@@ -84,7 +84,7 @@
<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>
<text class="doctor-unit" v-if="doctor.title">{{doctor.title}}</text>
</view>
<view class="doctor-actions">
<view class="appointment-btn" @click.stop="selectDoctor(doctor)">预约</view>
@@ -102,18 +102,18 @@
</view>
<scroll-view class="encyclopedia-scroll no-scrollbar" scroll-x :show-scrollbar="false">
<view
v-for="article in articles"
v-for="(article, index) in articles"
:key="article.id"
class="article-card"
:class="{ 'article-card-secondary': article.id === 2 }"
:class="{ 'article-card-secondary': index % 2 === 1 }"
@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 class="article-gradient" :class="{ 'gradient-secondary': index % 2 === 1 }"></view>
<view class="article-tag" :class="{ 'tag-secondary': index % 2 === 1 }">{{ article.tag }}</view>
</view>
<text class="article-title" :class="{ 'title-secondary': article.id === 2 }">{{ article.title }}</text>
<text class="article-title" :class="{ 'title-secondary': index % 2 === 1 }">{{ article.title }}</text>
<text class="article-desc">{{ article.desc }}</text>
</view>
</scroll-view>
@@ -124,26 +124,41 @@
<script setup>
import { ref, onMounted } from 'vue'
import { getCurrentInstance } from 'vue'
import { onShow, onShareAppMessage } from '@dcloudio/uni-app'
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 qualifications = ref([
{ id: 1, name: '卫健委认证机构', desc: '互联网诊疗资质备案', icon: '', dark: true, size: 'large' },
{ id: 2, name: '互联网医院', desc: '持证合规运营', icon: '', dark: false, size: 'small' },
{ id: 3, name: '执业医师认证', desc: '持证医师在线问诊', icon: '', 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 articles = ref([])
const fetchArticles = async () => {
try {
const response = await proxy.apiUrl({
url: '/api/article/lists',
method: 'GET',
data: { page_no: 1, page_size: 10 }
})
if (response && response.code === 1) {
articles.value = (response.data.lists || []).map(item => ({
...item,
cover: item.image,
tag: item.cid || ''
}))
}
} catch (error) {
console.error('获取文章列表失败:', error)
}
}
const selectDoctor = (doctor) => {
uni.navigateTo({
@@ -151,16 +166,21 @@ const selectDoctor = (doctor) => {
})
}
const navigateToDepartments = () => {
uni.showToast({ title: '科室列表', icon: 'none' })
const navigateToQualifications = () => {
uni.showToast({ title: '资质详情', icon: 'none' })
}
const navigateToDepartment = (item) => {
uni.showToast({ title: item.name, icon: 'none' })
const navigateToQualification = (item) => {
// 根据类型打开不同的协议页面
uni.navigateTo({
url: '/pages/Card/contact?type=health'
})
}
const navigateToAllDoctors = () => {
uni.showToast({ title: '更多医生', icon: 'none' })
uni.navigateTo({
url: '/doctor/pages/doctor/list/list'
})
}
const navigateToEncyclopedia = () => {
@@ -168,7 +188,7 @@ const navigateToEncyclopedia = () => {
}
const navigateToArticle = (article) => {
uni.showToast({ title: article.title, icon: 'none' })
uni.navigateTo({ url: `/doctor/pages/article/article?id=${article.id}` })
}
const fetchDoctors = async () => {
@@ -196,9 +216,28 @@ const fetchDoctors = async () => {
loading.value = false
}
}
const userinfo = async (code) => {
try {
// 通过 proxy 调用全局的 apiUrl
const res = await proxy.apiUrl({
url: '/api/user/info',
method: 'POST',
}, false) // 不显示加载中
uni.setStorageSync('userData', res.data)
} catch (err) {
}
}
onMounted(() => {
fetchDoctors()
fetchArticles()
userinfo()
})
</script>
@@ -289,28 +328,28 @@ $outline: #727970;
font-weight: 500;
}
// 科室分类 - grid 布局
.department-section {
// 平台资质 - grid 布局
.qualification-section {
margin-bottom: 64rpx;
}
.department-grid {
.qualification-grid {
display: flex;
gap: 32rpx;
}
.dept-col-left {
.qual-col-left {
flex: 1;
}
.dept-col-right {
.qual-col-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 32rpx;
}
.department-card {
.qualification-card {
&.large {
height: 100%;
min-height: 320rpx;
@@ -322,7 +361,7 @@ $outline: #727970;
}
}
.dept-card-inner {
.qual-card-inner {
border-radius: 24rpx;
padding: 40rpx;
height: 100%;
@@ -335,24 +374,24 @@ $outline: #727970;
overflow: hidden;
}
/* 针灸 - primary-container */
.dept-green {
/* 卫健委认证 - primary-container */
.qual-primary {
background: $primary-container;
color: $on-primary;
}
.dept-green .dept-icon .icon-text,
.dept-green .dept-name {
.qual-primary .qual-icon .icon-text,
.qual-primary .qual-name {
color: $on-primary;
}
.dept-green .dept-desc {
.qual-primary .qual-desc {
color: $on-primary-container;
font-size: 24rpx;
}
/* 中药 - secondary-container */
.dept-beige {
/* 互联网医院 - secondary-container */
.qual-beige {
background: $secondary-container;
color: $on-secondary-container;
flex-direction: row;
@@ -361,32 +400,28 @@ $outline: #727970;
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 {
.qual-beige .qual-icon .icon-text {
font-size: 40rpx;
color: $on-secondary-container;
}
.dept-beige .dept-name {
.qual-beige .qual-name {
color: $on-secondary-container;
margin-bottom: 4rpx;
display: block;
}
.dept-beige .dept-desc {
.qual-beige .qual-desc {
color: $on-secondary-container;
opacity: 0.8;
font-size: 20rpx;
display: block;
}
/* 推拿 - surface-container-high */
.dept-grey {
/* 执业医师认证 - surface-container-high */
.qual-grey {
background: $surface-container-high;
color: $on-surface;
flex-direction: row;
@@ -395,43 +430,39 @@ $outline: #727970;
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 {
.qual-grey .qual-icon .icon-text {
font-size: 40rpx;
color: $primary;
}
.dept-grey .dept-name {
.qual-grey .qual-name {
color: $on-surface;
display: block;
}
.dept-grey .dept-desc {
.qual-grey .qual-desc {
color: $on-surface-variant;
font-size: 20rpx;
display: block;
}
.dept-icon {
.qual-icon {
margin-bottom: 16rpx;
}
.dept-beige .dept-icon,
.dept-grey .dept-icon {
.qual-beige .qual-icon,
.qual-grey .qual-icon {
margin-bottom: 0;
}
.dept-icon .icon-text {
.qual-icon .icon-text {
font-size: 56rpx;
color: $on-primary;
}
.dept-name {
.qual-name {
font-size: 36rpx;
font-weight: 600;
color: $on-surface;
@@ -443,7 +474,7 @@ $outline: #727970;
}
}
.dept-desc {
.qual-desc {
font-size: 26rpx;
color: $on-surface-variant;
@@ -512,11 +543,13 @@ $outline: #727970;
font-size: 28rpx;
color: $on-surface-variant;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.doctor-price-row {
margin-bottom: 16rpx;
}
.doctor-price {
font-size: 28rpx;
@@ -533,6 +566,9 @@ $outline: #727970;
.doctor-actions {
display: flex;
justify-content: flex-end;
position: relative;
right: 0;
bottom: 0;
}
.appointment-btn {
+2 -2
View File
@@ -83,8 +83,8 @@ onShareAppMessage((res) =>{
return {
title: '视频问诊通话邀请',
path: '/pages/index/video',
imageUrl: '/static/share-img.png',
desc: '自定义分享描述',
imageUrl: '',
desc: '视频问诊通话邀请',
success() {
uni.showToast({ title: '分享成功', icon: 'success' });
},
+20 -3
View File
@@ -356,6 +356,7 @@
<script setup>
import { ref,onMounted,getCurrentInstance } from "vue";
import { onShow, onShareAppMessage } from '@dcloudio/uni-app'
// 2. 获取组件实例,通过 proxy 访问全局属性
const { proxy } = getCurrentInstance()
const formData = ref({
@@ -433,7 +434,21 @@ onMounted(() => {
getDictOptions();
loadDiagnosisDetail();
});
onShareAppMessage((res) =>{
return {
title: '诊单确认',
path: '/pages/order/monad/monad?id='+diagnosisId+'&share_user_id='+share_user_id,
imageUrl: '',
desc: '诊单确认',
success() {
uni.showToast({ title: '分享成功', icon: 'success' });
},
fail(err) {
console.log('分享失败:', err);
uni.showToast({ title: '分享失败', icon: 'none' });
}
}
});
// 获取字典数据
const getDictOptions = async () => {
try {
@@ -518,7 +533,8 @@ const getDictData = async (type) => {
return null;
}
};
let diagnosisId=''
let share_user_id='';
// 加载诊单详情
const loadDiagnosisDetail = async () => {
// 从页面参数获取诊单ID
@@ -526,7 +542,8 @@ const loadDiagnosisDetail = async () => {
const currentPage = pages[pages.length - 1];
const params = proxy.$parsePageParams(currentPage.options);
dingdan_ok.value=uni.getStorageSync('dingdan_ok');
const diagnosisId = params.id;
diagnosisId = params.id;
share_user_id = params.share_user_id;
console.log('进入onMounted:', params, '诊单ID:', diagnosisId);
if (!diagnosisId) {
+61 -57
View File
@@ -6,23 +6,23 @@
<image class="avatar" :src="userInfo.avatar" mode="aspectFill" />
<div class="user-info">
<text class="nickname">{{ userInfo.nickname || '未登录' }}</text>
<text class="vip-tag">VIP会员</text>
<!-- <text class="vip-tag">VIP会员</text> -->
</div>
<div class="qr-code" @click.stop="showQRCode">
<!-- <div class="qr-code" @click.stop="showQRCode">
<uni-icons type="scan" size="20" color="#333"></uni-icons>
</div>
</div> -->
</div>
<div class="stat-row">
<div class="stat-item">
<text class="value">{{ userInfo.balance ||0}}</text>
<text class="value">{{ userInfo.balance || 0 }}</text>
<text class="label">钱包()</text>
</div>
<div class="stat-item">
<text class="value">{{ userInfo.points ||0}}</text>
<text class="value">{{ userInfo.points || 0 }}</text>
<text class="label">积分</text>
</div>
<div class="stat-item">
<text class="value">{{ userInfo.coupons ||0}}</text>
<text class="value">{{ userInfo.coupons || 0 }}</text>
<text class="label">优惠券</text>
</div>
</div>
@@ -68,23 +68,23 @@
</div>
</div> -->
<!-- 服务菜单列表 -->
<!-- 服务菜单列表适老化显示全部大触控区 -->
<div class="service-list">
<div
class="service-item"
v-for="(item, index) in allServices.slice(4)"
v-for="(item, index) in allServices"
:key="index"
@click="navigateTo(item.path)"
>
<div class="left">
<div class="service-icon" :class="item.class">
<uni-icons :type="item.icon" size="20" color="#fff"></uni-icons>
<uni-icons :type="item.icon" size="28" color="#fff"></uni-icons>
</div>
<text class="service-name">{{ item.name }}</text>
</div>
<div class="right">
<text v-if="item.desc" class="desc">{{ item.desc }}</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
<uni-icons type="right" size="20" color="#666"></uni-icons>
</div>
</div>
</div>
@@ -92,6 +92,7 @@
</template>
<script>
import { onShow, onShareAppMessage } from '@dcloudio/uni-app'
export default {
name: 'profileB',
data() {
@@ -137,19 +138,15 @@ export default {
}
],
allServices: [
{ name: '收藏夹', icon: 'star', path: '/pages/favorite/index', class: 'bg-red' },
{ name: '浏览记录', icon: 'eye', path: '/pages/history/index', class: 'bg-blue' },
{ name: '收货地址', icon: 'location', path: '/pages/address/list', class: 'bg-green' },
{ name: '优惠券', icon: 'gift', path: '/pages/coupon/list', class: 'bg-purple' },
{ name: '就诊卡', icon: 'folder-add', path: '/pages/Card/Card', class: 'bg-cyan' },
{ name: '设置', icon: 'gear', path: '/pages/user/userinfo', class: 'bg-gray' }// ,
{ name: '设置', icon: 'gear', path: '/pages/user/userinfo', class: 'bg-gray' }// ,
// { name: '客服中心', icon: 'headphones', path: '/pages/service/index', class: 'bg-orange', desc: '在线客服' }
]
}
},
onShow() {
this.userInfo=uni.getStorageSync('userData')
this.user()
},
methods: {
navigateTo(path) {
@@ -163,56 +160,59 @@ export default {
</script>
<style lang="scss" scoped>
/* 适老化设计:50-80岁老人 - 大字号、高对比、大触控区 */
.mine-page {
min-height: 100vh;
background: #f5f6fa;
padding: 30rpx;
background: #e8eaed;
padding: 40rpx;
}
.user-card {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
border-radius: 28rpx;
padding: 48rpx;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
.user-header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
margin-bottom: 48rpx;
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
margin-right: 36rpx;
border: 4rpx solid #e0e0e0;
}
.user-info {
flex: 1;
.nickname {
font-size: 36rpx;
font-size: 48rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
color: #1a1a1a;
margin-bottom: 16rpx;
display: block;
letter-spacing: 1rpx;
}
.vip-tag {
background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
color: #fff;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 28rpx;
padding: 6rpx 20rpx;
border-radius: 24rpx;
display: inline-block;
}
}
.qr-code {
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
background: #f5f6fa;
display: flex;
align-items: center;
@@ -222,24 +222,26 @@ export default {
.stat-row {
display: flex;
padding-top: 30rpx;
border-top: 2rpx solid #f5f6fa;
padding-top: 40rpx;
border-top: 4rpx solid #e8eaed;
.stat-item {
flex: 1;
text-align: center;
min-height: 120rpx;
.value {
font-size: 40rpx;
font-size: 52rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
color: #1a1a1a;
margin-bottom: 12rpx;
display: block;
}
.label {
font-size: 24rpx;
color: #999;
font-size: 32rpx;
color: #555;
font-weight: 500;
}
}
}
@@ -354,16 +356,17 @@ export default {
.service-list {
background: #fff;
border-radius: 24rpx;
padding: 10rpx 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
border-radius: 28rpx;
padding: 20rpx 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
.service-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
border-bottom: 2rpx solid #f5f6fa;
padding: 44rpx 0;
border-bottom: 4rpx solid #e8eaed;
min-height: 120rpx;
&:last-child {
border-bottom: none;
@@ -374,18 +377,19 @@ export default {
align-items: center;
.service-icon {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
margin-right: 24rpx;
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
margin-right: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.service-name {
font-size: 28rpx;
color: #333;
font-size: 38rpx;
color: #1a1a1a;
font-weight: 500;
}
}
@@ -394,9 +398,9 @@ export default {
align-items: center;
.desc {
font-size: 24rpx;
color: #999;
margin-right: 16rpx;
font-size: 30rpx;
color: #555;
margin-right: 20rpx;
}
}
}
+66 -61
View File
@@ -21,8 +21,7 @@
mode="aspectFill"
/>
<view class="avatar-upload" @click="uploadAvatar">
<uni-icons type="upload-filled" size="14" color="#999" class="upload-icon"></uni-icons>
<uni-icons type="upload-filled" size="24" color="#FFA500" class="upload-icon"></uni-icons>
<text class="upload-text">更换头像</text>
</view>
</view>
@@ -281,9 +280,10 @@ const goBack = () => {
</script>
<style scoped>
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
.userinfo-container {
min-height: 100vh;
background: #f5f5f5;
background: #e8eaed;
}
.header {
@@ -291,30 +291,30 @@ const goBack = () => {
justify-content: space-between;
align-items: center;
background: #ffffff;
padding: 20rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
padding: 24rpx 40rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.header-back {
font-size: 40rpx;
font-size: 48rpx;
color: #FFA500;
width: 60rpx;
height: 60rpx;
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
font-size: 36rpx;
font-size: 44rpx;
font-weight: bold;
color: #333333;
color: #1a1a1a;
flex: 1;
text-align: center;
}
.header-placeholder {
width: 60rpx;
width: 88rpx;
}
.loading-state {
@@ -322,34 +322,34 @@ const goBack = () => {
justify-content: center;
align-items: center;
height: 100vh;
font-size: 28rpx;
color: #999999;
font-size: 36rpx;
color: #555;
}
.form-wrapper {
padding: 30rpx;
padding: 40rpx;
}
.form-section {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 36rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
}
.section-title {
font-size: 32rpx;
font-size: 40rpx;
font-weight: bold;
color: #FFA500;
margin-bottom: 24rpx;
margin-bottom: 32rpx;
display: block;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f0f0f0;
padding-bottom: 24rpx;
border-bottom: 4rpx solid #f0f0f0;
}
.form-group {
margin-bottom: 24rpx;
margin-bottom: 36rpx;
}
.form-group:last-child {
@@ -358,111 +358,116 @@ const goBack = () => {
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
font-size: 36rpx;
color: #1a1a1a;
font-weight: 500;
margin-bottom: 16rpx;
line-height: 1.5;
margin-bottom: 20rpx;
line-height: 1.6;
}
.form-input {
width: 100%;
padding: 20rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
line-height: 1.5;
color: #333333;
padding: 28rpx;
border: 4rpx solid #e0e0e0;
border-radius: 16rpx;
font-size: 36rpx;
line-height: 1.6;
color: #1a1a1a;
background: #ffffff;
box-sizing: border-box;
display: block;
height: auto;
min-height: 60rpx;
min-height: 96rpx;
}
.form-input::placeholder {
color: #cccccc;
color: #999;
font-size: 34rpx;
}
.avatar-container {
display: flex;
align-items: center;
gap: 20rpx;
gap: 32rpx;
}
.avatar-image {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
background: #f0f0f0;
border: 4rpx solid #e0e0e0;
}
.avatar-upload {
flex: 1;
padding: 20rpx;
border: 2rpx dashed #FFA500;
border-radius: 12rpx;
padding: 36rpx;
border: 4rpx dashed #FFA500;
border-radius: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
gap: 16rpx;
min-height: 120rpx;
}
.upload-icon {
font-size: 40rpx;
font-size: 56rpx;
}
.upload-text {
font-size: 24rpx;
font-size: 34rpx;
color: #FFA500;
font-weight: 500;
}
.sex-group {
display: flex;
gap: 20rpx;
gap: 28rpx;
}
.sex-btn {
flex: 1;
padding: 20rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
padding: 32rpx;
border: 4rpx solid #e0e0e0;
border-radius: 16rpx;
text-align: center;
font-size: 28rpx;
font-size: 38rpx;
line-height: 1.5;
color: #666666;
color: #555;
background: #ffffff;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
min-height: 60rpx;
min-height: 96rpx;
}
.sex-btn.active {
border-color: #FFA500;
background: #e6f7ff;
background: #fff7e6;
color: #FFA500;
font-weight: bold;
}
.button-group {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
margin-bottom: 40rpx;
gap: 28rpx;
margin-top: 56rpx;
margin-bottom: 56rpx;
}
.btn {
flex: 1;
padding: 20rpx;
border-radius: 12rpx;
font-size: 32rpx;
padding: 36rpx;
border-radius: 20rpx;
font-size: 40rpx;
font-weight: bold;
border: none;
cursor: pointer;
transition: all 0.3s;
min-height: 100rpx;
}
.btn:active {
@@ -476,7 +481,7 @@ const goBack = () => {
.btn-cancel {
background: #f0f0f0;
color: #666666;
color: #555;
}
.btn-submit {
@@ -485,6 +490,6 @@ const goBack = () => {
}
.btn-submit:active:not(:disabled) {
background: #0050b3;
background: #e69500;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB