no message

This commit is contained in:
wangdongbo 2026-02-08 13:53:22 +08:00
parent dae3b4d125
commit 8963a60391
6 changed files with 59 additions and 247 deletions

View File

@ -418,6 +418,25 @@ $primary-color: #0877F1;
background: #2456c7; background: #2456c7;
} }
.disabled-btn {
background: #ccc;
color: #fff;
font-size: 28rpx;
font-weight: 600;
border: none;
border-radius: 40rpx;
height: 56rpx;
min-width: 112rpx;
padding: 0 32rpx;
box-shadow: 0 2rpx 8rpx rgba(200, 200, 200, 0.08);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
white-space: nowrap;
cursor: not-allowed;
}
.input-area { .input-area {
flex: 1; flex: 1;
margin: 0 8rpx; margin: 0 8rpx;

View File

@ -57,10 +57,12 @@ const props = defineProps({
default: "", default: "",
}, },
}); });
const emit = defineEmits(["streamText", "clearInput"]); const emit = defineEmits(["streamText", "clearInput", "generatingStateChange"]);
const typeSelectorRef = ref(null); const typeSelectorRef = ref(null);
const progressRef = ref(null); const progressRef = ref(null);
const isGenerating = ref(false);
const buttons = ref([ const buttons = ref([
{ {
id: "followUp", id: "followUp",
@ -183,6 +185,8 @@ const streamTextToInput = (text) => {
// //
emit("clearInput"); emit("clearInput");
isGenerating.value = true;
emit("generatingStateChange", true);
let currentIndex = 0; let currentIndex = 0;
const speed = 50; // const speed = 50; //
@ -196,6 +200,8 @@ const streamTextToInput = (text) => {
currentIndex++; currentIndex++;
} else { } else {
clearInterval(streamInterval); clearInterval(streamInterval);
isGenerating.value = false;
emit("generatingStateChange", false);
} }
}, speed); }, speed);
}, 100); }, 100);
@ -363,6 +369,11 @@ const handleRegenerateMedicalCase = (data) => {
const type = { id: data.caseType }; const type = { id: data.caseType };
handleCaseTypeSelect(type); handleCaseTypeSelect(type);
}; };
//
defineExpose({
isGenerating,
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -8,18 +8,21 @@
<view class="input-area"> <view class="input-area">
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..." <textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" @confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
:auto-height="true" :show-confirm-bar="false" :adjust-position="true" :cursor-spacing="60" :auto-height="true" :show-confirm-bar="false" :adjust-position="true" :cursor-spacing="30"
/> />
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord" <input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled> @touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
</input> </input>
</view> </view>
<button v-if="inputText.trim()" class="send-btn" @click="sendTextMessage"> <button v-if="inputText.trim() && !props.isGenerating" class="send-btn" @click="sendTextMessage">
发送 发送
</button> </button>
<view v-else class="plus-btn" @click="toggleMorePanel()"> <view v-else-if="!inputText.trim() && !props.isGenerating" class="plus-btn" @click="toggleMorePanel()">
<uni-icons type="plusempty" size="28" color="#666" /> <uni-icons type="plusempty" size="28" color="#666" />
</view> </view>
<view v-else class="send-btn disabled-btn">
<text>生成中...</text>
</view>
</view> </view>
<view class="more-panel" v-if="showMorePanel"> <view class="more-panel" v-if="showMorePanel">
<view v-for="btn in morePanelButtons" :key="btn.text" class="more-btn" @click="btn.action"> <view v-for="btn in morePanelButtons" :key="btn.text" class="more-btn" @click="btn.action">
@ -80,6 +83,7 @@ const props = defineProps({
patientId: { type: String, default: "" }, patientId: { type: String, default: "" },
corpId: { type: String, default: "" }, corpId: { type: String, default: "" },
orderStatus: { type: String, default: "" }, orderStatus: { type: String, default: "" },
isGenerating: { type: Boolean, default: false },
}); });
// Emits // Emits

View File

@ -140,12 +140,14 @@
!showConsultAccept && !showConsultAccept &&
orderStatus === 'processing' orderStatus === 'processing'
" "
ref="aiAssistantRef"
:groupId="groupId" :groupId="groupId"
:patientAccountId="chatInfo.userID || ''" :patientAccountId="chatInfo.userID || ''"
:patientId="patientId" :patientId="patientId"
:corpId="corpId" :corpId="corpId"
@streamText="handleStreamText" @streamText="handleStreamText"
@clearInput="handleClearInput" @clearInput="handleClearInput"
@generatingStateChange="handleGeneratingStateChange"
/> />
<!-- 聊天输入组件 --> <!-- 聊天输入组件 -->
@ -165,6 +167,7 @@
:corpId="corpId" :corpId="corpId"
:patientInfo="patientInfo" :patientInfo="patientInfo"
:orderStatus="orderStatus" :orderStatus="orderStatus"
:isGenerating="isGenerating"
@scrollToBottom="() => scrollToBottom(true)" @scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)" @messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult" @endConsult="handleEndConsult"
@ -217,6 +220,8 @@ const { initIMAfterLogin } = useAccountStore();
// //
const chatInputRef = ref(null); const chatInputRef = ref(null);
const aiAssistantRef = ref(null);
const isGenerating = ref(false);
const groupId = ref(""); const groupId = ref("");
const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId); const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId);
@ -316,7 +321,11 @@ const fetchGroupOrderStatus = async () => {
const teamName = result.data.team?.name || "群聊"; const teamName = result.data.team?.name || "群聊";
updateNavigationTitle(teamName); updateNavigationTitle(teamName);
teamId.value = result.data.teamId || result.data.team?.teamId || result.data.team?._id || ""; teamId.value =
result.data.teamId ||
result.data.team?.teamId ||
result.data.team?._id ||
"";
// //
if (result.data.patient) { if (result.data.patient) {
@ -765,7 +774,7 @@ onShow(() => {
startIMMonitoring(30000); startIMMonitoring(30000);
// 访 // 访
uni.$on('send-followup-message', handleSendFollowUpMessage); uni.$on("send-followup-message", handleSendFollowUpMessage);
}); });
// 访 // 访
@ -783,10 +792,10 @@ const handleSendFollowUpMessage = async (data) => {
}, 100); }, 100);
} }
} catch (error) { } catch (error) {
console.error('发送回访任务消息失败:', error); console.error("发送回访任务消息失败:", error);
uni.showToast({ uni.showToast({
title: '发送失败,请重试', title: "发送失败,请重试",
icon: 'none', icon: "none",
}); });
} }
}; };
@ -820,6 +829,11 @@ const handleClearInput = () => {
} }
}; };
//
const handleGeneratingStateChange = (generating) => {
isGenerating.value = generating;
};
// //
defineExpose({ defineExpose({
sendCommonPhrase, sendCommonPhrase,
@ -1016,7 +1030,7 @@ onUnmounted(() => {
timChatManager.setCallback("onError", null); timChatManager.setCallback("onError", null);
// 访 // 访
uni.$off('send-followup-message', handleSendFollowUpMessage); uni.$off("send-followup-message", handleSendFollowUpMessage);
}); });
</script> </script>

View File

@ -57,8 +57,6 @@ export async function sendImageMessage(imageUrl, imageName = '图片') {
return false; return false;
} }
// 直接调用 tim-chat 的 sendImageMessage 方法
// tim-chat.js 中的 getImageUrl 方法可以处理 URL 字符串
const result = await globalTimChatManager.sendImageMessage(imageUrl); const result = await globalTimChatManager.sendImageMessage(imageUrl);
if (result?.success) { if (result?.success) {

View File

@ -1,234 +0,0 @@
# 微信小程序分享功能使用指南
## 功能说明
提供了完整的微信小程序分享功能,包括:
- 分享给好友
- 分享到朋友圈
- 保存图片到相册
## 使用方法
### 1. 基础分享(在页面中)
```vue
<template>
<view>
<button open-type="share">分享给好友</button>
</view>
</template>
<script setup>
import { createShareMessage, createShareTimeline } from '@/utils/share'
// 分享给好友
function onShareAppMessage() {
return createShareMessage({
title: '分享标题',
path: '/pages/index/index?id=123',
imageUrl: 'https://example.com/share.jpg'
})
}
// 分享到朋友圈(需要在 app.json 中配置)
function onShareTimeline() {
return createShareTimeline({
title: '朋友圈标题',
query: 'id=123',
imageUrl: 'https://example.com/share.jpg'
})
}
// 导出分享方法
defineExpose({
onShareAppMessage,
onShareTimeline
})
</script>
```
### 2. 使用分享组件
```vue
<template>
<view>
<share-actions
@save="handleSave"
:show-save="true"
:show-share="true"
save-text="保存图片"
share-text="分享微信"
/>
</view>
</template>
<script setup>
import { saveImageToAlbum, createShareMessage } from '@/utils/share'
import shareActions from '@/components/share-actions.vue'
// 保存图片
async function handleSave() {
const imagePath = 'https://example.com/image.jpg'
await saveImageToAlbum(imagePath)
}
// 分享配置
function onShareAppMessage() {
return createShareMessage({
title: '分享标题',
path: '/pages/index/index'
})
}
defineExpose({
onShareAppMessage
})
</script>
```
### 3. 保存二维码图片
```vue
<template>
<view>
<uqrcode ref="qrcode" canvasId="qrcode" :value="qrcodeUrl" />
<button @click="saveQrcode">保存二维码</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { saveImageToAlbum } from '@/utils/share'
import { toast } from '@/utils/widget'
const qrcode = ref(null)
const qrcodeUrl = ref('https://example.com')
async function saveQrcode() {
try {
if (!qrcode.value) {
toast('二维码未加载完成')
return
}
// 获取二维码临时文件路径
const tempFilePath = qrcode.value.toTempFilePath()
if (tempFilePath) {
await saveImageToAlbum(tempFilePath)
} else {
toast('获取二维码失败')
}
} catch (err) {
console.error('保存失败:', err)
toast('保存失败')
}
}
</script>
```
### 4. 动态分享内容
```vue
<script setup>
import { ref, computed } from 'vue'
import { createShareMessage } from '@/utils/share'
const currentItem = ref({
id: '123',
title: '商品标题',
image: 'https://example.com/product.jpg'
})
// 动态生成分享配置
function onShareAppMessage() {
return createShareMessage({
title: currentItem.value.title,
path: `/pages/detail/detail?id=${currentItem.value.id}`,
imageUrl: currentItem.value.image
})
}
defineExpose({
onShareAppMessage
})
</script>
```
## 配置说明
### 1. 启用分享到朋友圈
`pages.json` 中配置页面:
```json
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enableShareTimeline": true
}
}
```
### 2. 全局分享配置
`App.vue` 中配置全局分享:
```vue
<script>
export default {
onShareAppMessage() {
return {
title: '默认分享标题',
path: '/pages/index/index'
}
},
onShareTimeline() {
return {
title: '默认朋友圈标题'
}
}
}
</script>
```
## API 说明
### createShareMessage(options)
创建分享给好友的配置
**参数:**
- `title` (string): 分享标题
- `path` (string): 分享路径
- `imageUrl` (string): 分享图片URL
**返回:** 分享配置对象
### createShareTimeline(options)
创建分享到朋友圈的配置
**参数:**
- `title` (string): 分享标题
- `query` (string): 分享路径参数
- `imageUrl` (string): 分享图片URL
**返回:** 分享配置对象
### saveImageToAlbum(filePath)
保存图片到相册
**参数:**
- `filePath` (string): 图片路径(本地临时路径或网络路径)
**返回:** Promise<boolean>
## 注意事项
1. 分享图片建议尺寸5:4推荐 500x400px
2. 分享路径必须是已注册的页面路径
3. 保存图片需要用户授权相册权限
4. 分享到朋友圈需要在页面配置中启用
5. 网络图片会自动下载后保存到相册