Przeglądaj źródła

im功能更新

Signed-off-by: 李妹妹 <1639016684@qq.com>
李妹妹 2 miesięcy temu
rodzic
commit
5c6b33ade2
100 zmienionych plików z 10885 dodań i 7569 usunięć
  1. 2 2
      App.vue
  2. 9 9
      manifest.json
  3. BIN
      nativeplugins/Tuoyun-OpenIMSDK/android/open_im_sdk.aar
  4. BIN
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework.zip
  5. 12 4
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework/Versions/A/Headers/Open_im_sdk.objc.h
  6. BIN
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework/Versions/A/OpenIMCore
  7. BIN
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCoreNew.framework.zip
  8. BIN
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMUniPlugin.framework/Info.plist
  9. BIN
      nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMUniPlugin.framework/OpenIMUniPlugin
  10. 1 1
      nativeplugins/Tuoyun-OpenIMSDK/package.json
  11. 45 63
      pages.json
  12. 4 2
      pages/user/index.vue
  13. 0 15
      pages_im/api/im.js
  14. 48 18
      pages_im/api/imBll.js
  15. 21 11
      pages_im/api/login.js
  16. 0 19
      pages_im/api/luckyBag.js
  17. 9 14
      pages_im/common/config.js
  18. 2 3
      pages_im/components/ChooseIndexFooter/SelectedMember.vue
  19. 88 46
      pages_im/components/ChooseIndexFooter/index.vue
  20. 29 2
      pages_im/components/ChooseIndexList/index.vue
  21. 3 4
      pages_im/components/CustomNavBar/index.vue
  22. 29 32
      pages_im/components/MyAvatar/index.vue
  23. 0 277
      pages_im/components/SelectProject.vue
  24. 16 18
      pages_im/components/SettingItem/index.vue
  25. 40 39
      pages_im/components/UserItem/index.vue
  26. 1 2
      pages_im/constant/comp.js
  27. 0 1
      pages_im/constant/event.js
  28. 1 1
      pages_im/constant/im.js
  29. 167 0
      pages_im/constant/imConstants.js
  30. 572 577
      pages_im/pages/common/contactChoose/index.vue
  31. 60 46
      pages_im/pages/common/createGroup/index.vue
  32. 31 25
      pages_im/pages/common/detailsFileds/index.vue
  33. 6 7
      pages_im/pages/common/globalChatLosPreview/index.vue
  34. 118 159
      pages_im/pages/common/globalSearch/components/ComprehensivePanel.vue
  35. 132 140
      pages_im/pages/common/globalSearch/components/GlobalResultItem.vue
  36. 228 248
      pages_im/pages/common/globalSearch/index.vue
  37. 23 17
      pages_im/pages/common/groupCard/index.vue
  38. 4 4
      pages_im/pages/common/livePage/index.vue
  39. 1 2
      pages_im/pages/common/meetingCenter/index.vue
  40. 47 23
      pages_im/pages/common/searchUserOrGroup/index.vue
  41. 2 21
      pages_im/pages/common/sendAddRequest/index.vue
  42. 112 136
      pages_im/pages/common/setMemberMute/index.vue
  43. 391 454
      pages_im/pages/common/userCard/index.vue
  44. 30 25
      pages_im/pages/common/userCardMore/index.vue
  45. 1 0
      pages_im/pages/common/webH5/index.vue
  46. 0 268
      pages_im/pages/contact/applicationDetails/index.vue
  47. 54 46
      pages_im/pages/contact/applicationList/ApplicationItem.vue
  48. 139 204
      pages_im/pages/contact/applicationList/index.vue
  49. 0 81
      pages_im/pages/contact/applicationListDetails/index.vue
  50. 0 74
      pages_im/pages/contact/contactAdd/ActionItem.vue
  51. 0 117
      pages_im/pages/contact/contactAdd/index.vue
  52. 151 26
      pages_im/pages/contact/friendList/index.vue
  53. 0 55
      pages_im/pages/contact/groupList/GroupItem.vue
  54. 0 159
      pages_im/pages/contact/groupList/index.vue
  55. 17 17
      pages_im/pages/contact/index/components/ContactMenus.vue
  56. 110 120
      pages_im/pages/contact/index/index.vue
  57. 27 22
      pages_im/pages/contact/searchAddedFriend/index.vue
  58. 0 87
      pages_im/pages/contact/searchJoinedGroup/index.vue
  59. 0 57
      pages_im/pages/contact/searchUserOrGroup/index.vue
  60. 0 116
      pages_im/pages/contact/switchJoinGroup/index.vue
  61. 403 444
      pages_im/pages/conversation/chating/components/ChatingFooter/ChatingActionBar.vue
  62. 36 16
      pages_im/pages/conversation/chating/components/ChatingFooter/ChatingEmojiBar.vue
  63. 0 175
      pages_im/pages/conversation/chating/components/ChatingFooter/CollectLuckyBag.vue
  64. 133 253
      pages_im/pages/conversation/chating/components/ChatingFooter/CustomEditor.vue
  65. 78 241
      pages_im/pages/conversation/chating/components/ChatingFooter/RecordOverlay.vue
  66. 0 306
      pages_im/pages/conversation/chating/components/ChatingFooter/SelectLuckBag.vue
  67. 306 297
      pages_im/pages/conversation/chating/components/ChatingFooter/index.vue
  68. 252 251
      pages_im/pages/conversation/chating/components/ChatingHeader.vue
  69. 56 31
      pages_im/pages/conversation/chating/components/ChatingList.vue
  70. 51 25
      pages_im/pages/conversation/chating/components/ChatingList1.vue
  71. 1 1
      pages_im/pages/conversation/chating/components/ChatingListMe.vue
  72. 471 135
      pages_im/pages/conversation/chating/components/MessageItem/AudioMessageRender.vue
  73. 29 17
      pages_im/pages/conversation/chating/components/MessageItem/CardMessageRender.vue
  74. 1037 738
      pages_im/pages/conversation/chating/components/MessageItem/CustomMessageRender.vue
  75. 10 1
      pages_im/pages/conversation/chating/components/MessageItem/ErrorMessageRender.vue
  76. 1 17
      pages_im/pages/conversation/chating/components/MessageItem/FaceMessageRender.vue
  77. 108 53
      pages_im/pages/conversation/chating/components/MessageItem/FileMessageRender.vue
  78. 32 12
      pages_im/pages/conversation/chating/components/MessageItem/GroupAnnounceRender.vue
  79. 20 13
      pages_im/pages/conversation/chating/components/MessageItem/LocationMessageRender.vue
  80. 80 109
      pages_im/pages/conversation/chating/components/MessageItem/MassMessageRender.vue
  81. 97 27
      pages_im/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue
  82. 141 101
      pages_im/pages/conversation/chating/components/MessageItem/MeetingInviteMessageRender.vue
  83. 19 10
      pages_im/pages/conversation/chating/components/MessageItem/MergeMessageRender.vue
  84. 119 38
      pages_im/pages/conversation/chating/components/MessageItem/MessageMenu.vue
  85. 2 4
      pages_im/pages/conversation/chating/components/MessageItem/MessageReadState.vue
  86. 82 49
      pages_im/pages/conversation/chating/components/MessageItem/QuoteMessageRender.vue
  87. 19 12
      pages_im/pages/conversation/chating/components/MessageItem/RTCMessageRender.vue
  88. 49 23
      pages_im/pages/conversation/chating/components/MessageItem/TextMessageRender.vue
  89. 343 254
      pages_im/pages/conversation/chating/components/MessageItem/index.vue
  90. 1086 0
      pages_im/pages/conversation/chating/index.nvue
  91. 3 0
      pages_im/pages/conversation/chating/index1.vue
  92. 447 0
      pages_im/pages/conversation/chating/index11.vue
  93. BIN
      pages_im/pages/conversation/chating1.zip
  94. 472 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/ChatingActionBar.vue
  95. 51 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/ChatingEmojiBar.vue
  96. 441 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/CustomEditor.vue
  97. 151 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/CustomTextarea.vue
  98. 385 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/RecordOverlay.vue
  99. 123 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/chooseFile.js
  100. 968 0
      pages_im/pages/conversation/chating1/components/ChatingFooter/index.vue

+ 2 - 2
App.vue

@@ -1907,12 +1907,12 @@
 
 					if (total) {
 						uni.setTabBarBadge({
-							index: 0,
+							index: 3,
 							text: total < 99 ? total + '' : '99+'
 						});
 					} else {
 						uni.removeTabBarBadge({
-							index: 0
+							index: 3
 						});
 					}
 					this.$store.commit('contact/SET_UNHANDLE_FRIEND_APPLICATION_NUM', unHandleFriendApplicationNum);

+ 9 - 9
manifest.json

@@ -3,7 +3,7 @@
     "appid" : "__UNI__3983062",
     "description" : "",
     "versionName" : "1.0.3",
-    "versionCode" : 103,
+    "versionCode" : 1031,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {
@@ -277,11 +277,11 @@
                     "parameters" : {}
                 }
             },
-            "Tuoyun-OpenIMSDK" : {
+            "wxMerchantTransfer" : {
                 "__plugin_info__" : {
-                    "name" : "OpenIMSDK",
-                    "description" : "OpenIM SDK for uni-app advance v3.8.3-patch.3-e-v1.1.11",
-                    "platforms" : "Android,iOS",
+                    "name" : "微信商家转账插件",
+                    "description" : "微信商家转账插件",
+                    "platforms" : "Android",
                     "url" : "",
                     "android_package_name" : "",
                     "ios_bundle_id" : "",
@@ -291,11 +291,11 @@
                     "parameters" : {}
                 }
             },
-            "wxMerchantTransfer" : {
+            "Tuoyun-OpenIMSDK" : {
                 "__plugin_info__" : {
-                    "name" : "微信商家转账插件",
-                    "description" : "微信商家转账插件",
-                    "platforms" : "Android",
+                    "name" : "OpenIMSDK",
+                    "description" : "OpenIM SDK for uni-app advance v3.8.3-patch.12-e-v1.1.11",
+                    "platforms" : "Android,iOS",
                     "url" : "",
                     "android_package_name" : "",
                     "ios_bundle_id" : "",

BIN
nativeplugins/Tuoyun-OpenIMSDK/android/3.8.3-patch.3-e-v1.1.11.aar → nativeplugins/Tuoyun-OpenIMSDK/android/open_im_sdk.aar


BIN
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework.zip


+ 12 - 4
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework/Versions/A/Headers/Open_im_sdk.objc.h

@@ -179,6 +179,10 @@ FOUNDATION_EXPORT void Open_im_sdkDeleteConversationAndDeleteAllMsg(id<Open_im_s
 
 FOUNDATION_EXPORT void Open_im_sdkDeleteFriend(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable friendUserID);
 
+FOUNDATION_EXPORT void Open_im_sdkDeleteFriendRequests(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
+
+FOUNDATION_EXPORT void Open_im_sdkDeleteGroupRequests(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
+
 FOUNDATION_EXPORT void Open_im_sdkDeleteMessage(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable conversationID, NSString* _Nullable clientMsgID);
 
 FOUNDATION_EXPORT void Open_im_sdkDeleteMessageFromLocalStorage(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable conversationID, NSString* _Nullable clientMsgID);
@@ -213,17 +217,21 @@ FOUNDATION_EXPORT NSString* _Nonnull Open_im_sdkGetConversationIDBySessionType(N
 
 FOUNDATION_EXPORT void Open_im_sdkGetConversationListSplit(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, long offset, long count);
 
-FOUNDATION_EXPORT void Open_im_sdkGetFriendApplicationListAsApplicant(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID);
+FOUNDATION_EXPORT void Open_im_sdkGetFriendApplicationListAsApplicant(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
 
-FOUNDATION_EXPORT void Open_im_sdkGetFriendApplicationListAsRecipient(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID);
+FOUNDATION_EXPORT void Open_im_sdkGetFriendApplicationListAsRecipient(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
+
+FOUNDATION_EXPORT void Open_im_sdkGetFriendApplicationUnhandledCount(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
 
 FOUNDATION_EXPORT void Open_im_sdkGetFriendList(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, BOOL filterBlack);
 
 FOUNDATION_EXPORT void Open_im_sdkGetFriendListPage(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, int32_t offset, int32_t count, BOOL filterBlack);
 
-FOUNDATION_EXPORT void Open_im_sdkGetGroupApplicationListAsApplicant(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID);
+FOUNDATION_EXPORT void Open_im_sdkGetGroupApplicationListAsApplicant(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
+
+FOUNDATION_EXPORT void Open_im_sdkGetGroupApplicationListAsRecipient(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
 
-FOUNDATION_EXPORT void Open_im_sdkGetGroupApplicationListAsRecipient(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID);
+FOUNDATION_EXPORT void Open_im_sdkGetGroupApplicationUnhandledCount(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable req);
 
 FOUNDATION_EXPORT void Open_im_sdkGetGroupMemberList(id<Open_im_sdk_callbackBase> _Nullable callback, NSString* _Nullable operationID, NSString* _Nullable groupID, int32_t filter, int32_t offset, int32_t count);
 

BIN
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCore.framework/Versions/A/OpenIMCore


BIN
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMCoreNew.framework.zip


BIN
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMUniPlugin.framework/Info.plist


BIN
nativeplugins/Tuoyun-OpenIMSDK/ios/OpenIMUniPlugin.framework/OpenIMUniPlugin


+ 1 - 1
nativeplugins/Tuoyun-OpenIMSDK/package.json

@@ -2,7 +2,7 @@
   "name": "OpenIMSDK",
   "id": "Tuoyun-OpenIMSDK",
   "version": "3.8.3",
-  "description": "OpenIM SDK for uni-app advance v3.8.3-patch.3-e-v1.1.11",
+  "description": "OpenIM SDK for uni-app advance v3.8.3-patch.12-e-v1.1.11",
   "_dp_type": "nativeplugin",
   "_dp_nativeplugin": {
     "android": {

+ 45 - 63
pages.json

@@ -1,8 +1,8 @@
 {
 	"easycom": {
 		"^u-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
-		//"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
-		"^vue-file-(.*)": "packageName/path/to/vue-file-$1.vue", // 匹配node_modules内的vue文件
+		
+		"^vue-file-(.*)": "packageName/path/to/vue-file-$1.vue", 
 		"^z-(.*)": "@/uni_modules/zebra-swiper/components/z-$1/z-$1.vue"
 	},
 	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
@@ -2973,7 +2973,7 @@
 					}
 				},
 				{
-					"path": "pages/conversation/chating/index",
+					"path": "pages/conversation/chating1/index",
 					"style": {
 						"navigationBarTitleText": "",
 						"enablePullDownRefresh": false,
@@ -3333,24 +3333,24 @@
 						"navigationBarTextStyle": "black"
 					}
 				},
-				{
-					"path": "pages/contact/contactAdd/index",
-					"style": {
-						"navigationBarTitleText": "通讯录",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
-				{
-					"path": "pages/contact/switchJoinGroup/index",
-					"style": {
-						"navigationBarTitleText": "",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
+				// {
+				// 	"path": "pages/contact/contactAdd/index",
+				// 	"style": {
+				// 		"navigationBarTitleText": "通讯录",
+				// 		"enablePullDownRefresh": false,
+				// 		"navigationStyle": "custom",
+				// 		"navigationBarTextStyle": "black"
+				// 	}
+				// },
+				// {
+				// 	"path": "pages/contact/switchJoinGroup/index",
+				// 	"style": {
+				// 		"navigationBarTitleText": "",
+				// 		"enablePullDownRefresh": false,
+				// 		"navigationStyle": "custom",
+				// 		"navigationBarTextStyle": "black"
+				// 	}
+				// },
 				{
 					"path": "pages/contact/friendList/index",
 					"style": {
@@ -3360,24 +3360,24 @@
 						"navigationBarTextStyle": "black"
 					}
 				},
-				{
-					"path": "pages/contact/groupList/index",
-					"style": {
-						"navigationBarTitleText": "我的群组",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
-				{
-					"path": "pages/contact/searchUserOrGroup/index",
-					"style": {
-						"navigationBarTitleText": "",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
+				// {
+				// 	"path": "pages/contact/groupList/index",
+				// 	"style": {
+				// 		"navigationBarTitleText": "我的群组",
+				// 		"enablePullDownRefresh": false,
+				// 		"navigationStyle": "custom",
+				// 		"navigationBarTextStyle": "black"
+				// 	}
+				// },
+				// {
+				// 	"path": "pages/contact/searchUserOrGroup/index",
+				// 	"style": {
+				// 		"navigationBarTitleText": "",
+				// 		"enablePullDownRefresh": false,
+				// 		"navigationStyle": "custom",
+				// 		"navigationBarTextStyle": "black"
+				// 	}
+				// },
 				{
 					"path": "pages/contact/applicationList/index",
 					"style": {
@@ -3387,24 +3387,6 @@
 						"navigationBarTextStyle": "black"
 					}
 				},
-				{
-					"path": "pages/contact/applicationListDetails/index",
-					"style": {
-						"navigationBarTitleText": "",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
-				{
-					"path": "pages/contact/applicationDetails/index",
-					"style": {
-						"navigationBarTitleText": "",
-						"enablePullDownRefresh": false,
-						"navigationStyle": "custom",
-						"navigationBarTextStyle": "black"
-					}
-				},
 				{
 					"path": "pages/contact/searchAddedFriend/index",
 					"style": {
@@ -4714,12 +4696,6 @@
 		"iconWidth": "24px",
 		"spacing": "4px",
 		"list": [
-			// {
-			// 	"pagePath": "pages_im/pages/conversation/conversationList/index",
-			// 	"iconPath": "/static/image/tabbar/tab_new_icon.png",
-			// 	"selectedIconPath": "/static/image/tabbar/tab_new_on_icon.png",
-			// 	"text": "消息"
-			// },
 			{
 				"pagePath": "pages/index/index",
 				"iconPath": "/static/image/tabbar/home.png",
@@ -4744,6 +4720,12 @@
 				"selectedIconPath": "/static/image/tabbar/video_sel.png",
 				"text": "短视频"
 			},
+			{
+				"pagePath": "pages_im/pages/conversation/conversationList/index",
+				"iconPath": "/static/image/tabbar/tab_new_icon.png",
+				"selectedIconPath": "/static/image/tabbar/tab_new_on_icon.png",
+				"text": "消息"
+			},
 			// {
 			// 	"pagePath": "pages_enter/index",
 			// 	"iconPath": "/static/image/tabbar/tab_hall_icon.png",

+ 4 - 2
pages/user/index.vue

@@ -24,7 +24,7 @@
 					<image class="heads es-br-ban" @tap="navToUserInfo('./userInfo')" :src="user&&user.avatar||avatar"
 						mode="aspectFill"></image>
 
-					<view>
+					<view style="width: 10%;">
 						<view class="flexBetween">
 							<view class="flex-center es-c-22 fs20" @tap="navToUserInfo('./userInfo')">
 								<text
@@ -58,6 +58,8 @@
 							</view>
 						</view>
 					</view>
+					<image class="es-w-40 es-h-40" style="position: absolute;right: 0;" src="@/static/image/my/code_icon.png"
+						@tap="toSelfQr()" mode="aspectFill"></image>
 				</view>
 
 				<!-- 福币,币,券信息 -->
@@ -1176,7 +1178,7 @@
 		&-info {
 			padding: 0 16rpx;
 			@include u-flex(row, flex-start, flex-start);
-
+             position: relative;
 			&-name {
 				font-weight: 600;
 				font-size: 40rpx;

+ 0 - 15
pages_im/api/im.js

@@ -1,15 +0,0 @@
-import Request from '@/common/request.js';
-let request = new Request().http
-
-//我的好友
-export function getFriendApplyList(data) {
-	return request('/app/app/getFriendList', data,  'POST','application/json;charset=UTF-8');
-}
-
-export function bindWechat(data) {
-	return request('/app/app/bindWeChat', data, 'POST', 'application/json;charset=UTF-8');
-}
- 
- export function beMember(data) {
- 	return request('/store/app/user/beMember',data,'POST','application/json;charset=UTF-8');
- }

+ 48 - 18
pages_im/api/imBll.js

@@ -8,10 +8,11 @@ const ensureAdminToken = async () => {
 	  const token = resp?.token ?? resp?.data?.token;
 	  if (!token) throw new Error('IMAdminToken 获取失败');
 	  uni.setStorageSync('IMAdminToken', token);
+	  console.log("qxj IMAdminToken",uni.getStorageSync("IMAdminToken"));
 	  return token;
 };
 
-const delay = (ms) => new Promise((r) => setTimeout(r, ms));
+const delay = (ms) => new Promise((r) => setTimeout(r, ms));""
 
 export const imAccountCheckReg = (userID) => {
   return new Promise(async (resolve, reject) => {
@@ -45,23 +46,23 @@ export const imAccountCheckReg = (userID) => {
 export const getImUserTokenReq = async (userID, maxRetries = 3) => {
   for (let attempt = 1; attempt <= maxRetries; attempt++) {
     try {
-      await ensureAdminToken();
-      const userTokenData = await getImUserToken(userID);
-	  console.log("qxj getImUserTokenReq userTokenData",userTokenData);
-      uni.setStorage({ key: 'IMToken', data: userTokenData.token });
-      uni.setStorage({ key: 'IMUserID', data: userID });
-      return userTokenData;
+		  await ensureAdminToken();
+		  const userTokenData = await getImUserToken(userID);
+		  //console.log("qxj getImUserTokenReq userTokenData",userTokenData);
+		  uni.setStorage({ key: 'IMToken', data: userTokenData.token });
+		  uni.setStorage({ key: 'IMUserID', data: userID });
+		  return userTokenData;
     } catch (err) {
-	   console.log("qxj getImUserTokenReq err",err);
-      const isExpired = err?.errCode === 1501 || /TokenExpired/i.test(err?.errMsg || '');
-      if (!isExpired || attempt === maxRetries) {
-        console.error('获取 IM 用户 token 失败:', err);
-        throw err;
-      }
-      // 刷新管理员 token 后重试
-      uni.removeStorageSync('IMAdminToken');
-      try { await ensureAdminToken(); } catch (_) {}
-      await delay(Math.min(1000 * attempt, 3000));
+		   console.log("qxj getImUserTokenReq err",err);
+		  const isExpired = err?.errCode === 1501 || /TokenExpired/i.test(err?.errMsg || '');
+		  if (!isExpired || attempt === maxRetries) {
+			console.error('获取 IM 用户 token 失败:', err);
+			throw err;
+		  }
+		  // 刷新管理员 token 后重试
+		  uni.removeStorageSync('IMAdminToken');
+		  try { await ensureAdminToken(); } catch (_) {}
+		  await delay(Math.min(1000 * attempt, 3000));
     }
   }
 };
@@ -114,7 +115,7 @@ export const updateImUserReq = async (userID, nickname, faceURL, maxRetries = 3)
 };
 
 
-export const openImLoginApi =async (data,IMToken,IMUserID,succCallBack,failCallBack) => {
+export const openImLoginApi=async (data,IMToken,IMUserID,succCallBack,failCallBack) => {
 	console.log("----qxj IMUserID:"+IMUserID+" IMToken:"+IMToken);
     let userID=data.userID;
 	let nickName=data.nickName;
@@ -163,4 +164,33 @@ export const openImLoginApi =async (data,IMToken,IMUserID,succCallBack,failCallB
 		});
 	}
 
+};
+
+
+export const testImLoginApi=async (data,IMToken,IMUserID,succCallBack,failCallBack) => {
+	console.log("----qxj IMUserID:"+IMUserID+" IMToken:"+IMToken);
+	if(!!IMUserID || !!IMToken){
+		  getImUserTokenReq(IMUserID).then(res => {
+			   succCallBack(res.token);
+			},
+			rej => {
+				failCallBack();
+			}
+		).catch((err) => {
+		     failCallBack();
+		});
+	}
+	else{
+		
+		registerImUserReq(userID,nickName,faceURL).then(res => {
+				succCallBack();
+			},
+			rej => {
+				failCallBack();
+			}
+		).catch(() => {
+			
+		});
+	}
+
 };

+ 21 - 11
pages_im/api/login.js

@@ -12,7 +12,7 @@ export const businessReset = (params) =>
 
 export const businessModify = (params) =>
   uni.$u?.http.post(
-  "/account/password/change",
+    "/account/password/change",
     JSON.stringify({
       ...params,
     }),
@@ -39,7 +39,7 @@ export const businessInfoUpdate = (params) =>
 export const businessGetUserInfo = (userID) =>
   uni.$u?.http.post(
     "/user/find/full",
-    JSON.stringify({userIDs: [userID]}),
+    JSON.stringify({ userIDs: [userID] }),
     {
       header: {
         token: uni.getStorageSync("BusinessToken")
@@ -49,23 +49,33 @@ export const businessGetUserInfo = (userID) =>
 
 export const businessSearchUserInfo = (keyword) =>
   uni.$u?.http.post(
-    "/user/friendsSearch",
+    "/user/search/full",
     JSON.stringify({
-      keywords:keyword,
-      pageNum: 1,
-      pageSize: 20,
+      keyword,
+      pagination: {
+        pageNumber: 1,
+        showNumber: 10,
+      },
     }),
     {
-	 custom: {
-	    isAppApi: true,
-	 },
       header: {
-		'Content-Type': 'application/json',
+        token: uni.getStorageSync("AppToken"),
+      },
+    }
+  );
+export const getFriendUserApi = (keyword) =>
+  uni.$u?.http.get(
+    "/user/findUser?keyword=" + keyword,
+    {
+      custom: {
+        isAppApi: true,
+      },
+      header: {
+        'Content-Type': 'application/json',
         "AppToken": uni.getStorageSync("AppToken"),
       },
     }
   );
-
 export const businessSearchUser = (keyword) =>
   uni.$u?.http.post(
     "/friend/search",

+ 0 - 19
pages_im/api/luckyBag.js

@@ -1,19 +0,0 @@
-import Request from '@/common/request.js';
-let request = new Request().http
-
-//福袋列表
-export function luckyBagList(data) {
-	return request('/app/app/luckyBagList', data, 'GET');
-}
-//发送
-export function createFdMiniLink(data) {
-	return request('/app/app/createFdMiniLink', data, 'POST','application/json;charset=UTF-8');
-}
-//领取
-export function receiveLuckyBag(data) {
-	return request('/app/luckyBag/receiveLuckyBag', data, 'POST','application/json;charset=UTF-8');
-}
-//是否领取
-export function getLuckyBagInfo(data) {
-	return request('/app/luckyBag/getLuckyBagInfo', data, 'GET','application/json;charset=UTF-8');
-}

+ 9 - 14
pages_im/common/config.js

@@ -1,30 +1,25 @@
 // https
-const wsUrl = "wss://webim.zkhj6.com/msg_gateway";
+const wsUrl = "wss://webim.klbycp.com/msg_gateway";
 
-const apiUrl = "https://webim.zkhj6.com/api";
+const apiUrl = "https://webim.klbycp.com/api";
 
-const appUrl = "http://webim.zkhj6.com/app";
-
-const registerUrl = "https://webim.zkhj6.com/chat";
-
-export const version = 'UER 3.8.3+1 SDK v3.8.3-patch.3-e-v1.1.11'
+const registerUrl = "https://webim.klbycp.com/chat";
 
+export const version = 'UER 3.8.3-patch.12-e-v1.1.11'
 // 高德地图web api key  用于根据经纬度生成快照  当前key已绑定安卓包名  需要自行申请替换
 export const AmapWebKey = "094052d9eecdc24dfa16f3e1b18c660d";
 
 export const getRegisterUrl = () => uni.getStorageSync("IMRegisteUrl") || registerUrl;
 export const getApiUrl = () => uni.getStorageSync("IMApiUrl") || apiUrl;
-export const getAppUrl = () => appUrl;
 export const getWsUrl = () => uni.getStorageSync("IMWsUrl") || wsUrl;
 
 // 统一通过命名导出
 export const config = {
-  version,
-  AmapWebKey,
-  getRegisterUrl,
-  getApiUrl,
-  getAppUrl,
-  getWsUrl
+   version,
+   AmapWebKey,
+   getRegisterUrl,
+   getApiUrl,
+   getWsUrl
 };
 
 // module.exports = {

+ 2 - 3
pages_im/components/ChooseIndexFooter/SelectedMember.vue

@@ -5,9 +5,8 @@
         :src="source.faceURL"
         :desc="source.nickname || source.showName"
         :isGroup="Boolean(source.groupID)"
-        size="42"
-      />
-      <text>{{ source.nickname || source.groupName || source.showName }}</text>
+        size="42"/> 
+      <text style="margin-left: 18rpx;">{{ source.remark ||  source.nickname || source.groupName || source.showName }}</text>
     </view>
     <view>
       <u-button @click="action" plain text="移除" type="primary" />

+ 88 - 46
pages_im/components/ChooseIndexFooter/index.vue

@@ -1,38 +1,36 @@
 <template>
   <view class="member_checked_desc">
     <view @click="showSelected = true" class="left_info">
-      <view class="select_num">
+      <!-- <view class="select_num">
         <text class="text">{{ `已选择(${choosedData.length})` }}</text>
         <u-icon name="arrow-up" size="14" color="#007aff" />
       </view>
-      <view class="select_list">{{ selectedStr }}</view>
+      <view class="select_list">{{ selectedStr }}</view> -->
+      <view class="avatar_view" v-for="(item, index) in choosedData" :key="index">
+           <my-avatar :src="item.faceURL" size="42">
+			    <image v-if="getRole(item) == 1" mode="aspectFit" class="taoj" src="/static/svg/doctor.svg"></image>
+			    <image v-if="getRole(item) == 2" mode="aspectFit" class="taoj" src="/static/svg/guanjia.svg"></image>
+		  </my-avatar>
+		   
+      </view>
     </view>
     <view class="">
-      <u-button
-        :loading="comfirmLoading"
-        @click="clickComfirm"
-        :disabled="choosedData.length === 0"
-        type="primary"
-        :text="
-          isRemove
-            ? '移除'
-            : `确定(${choosedData.length}${
-                maxLength > 0 ? `/${maxLength}` : ``
-              })`
-        "
-      />
+      <u-button class="save_btn" :loading="comfirmLoading" @click="clickComfirm" :disabled="choosedData.length === 0 && !canConfirmEmpty" 
+        :text="isRemove
+          ? '移除'
+          : `确定(${choosedData.length}${maxLength > 0 ? `/${maxLength}` : ``
+          })`
+          " />
       <u-popup round="24" :show="showSelected" mode="bottom" @close="close">
         <view class="selected_container">
           <view class="top_desc">
             <text>{{ `已选择(${choosedData.length})` }}</text>
-            <text @click="close" class="comfirm_text">确认</text>
+            <text @click="close" class="comfirm_text">关闭</text>
           </view>
+		  
           <u-list class="selected_list">
-            <u-list-item
-              v-for="item in choosedData"
-              :key="item.userID || item.groupID"
-            >
-              <selected-member @removeItem="removeItem(item)" :source="item" />
+            <u-list-item v-for="item in choosedData" :key="item.userID || item.groupID">
+                <selected-member @removeItem="removeItem(item)" :source="item" />
             </u-list-item>
           </u-list>
         </view>
@@ -43,16 +41,22 @@
 
 <script>
 import SelectedMember from "./SelectedMember.vue";
+import MyAvatar from "../MyAvatar/index.vue";
 export default {
   name: "ChooseIndexFooter",
   components: {
     SelectedMember,
+    MyAvatar
   },
   props: {
     isRemove: Boolean,
     choosedData: Array,
     comfirmLoading: Boolean,
     maxLength: Number,
+    canConfirmEmpty: {
+        type: Boolean,
+        default: false
+    }
   },
   data() {
     return {
@@ -67,16 +71,34 @@ export default {
         .join("、");
     },
   },
+
   methods: {
     close() {
-      this.showSelected = false;
-    },
-    removeItem(item) {
-      this.$emit("removeItem", item);
-    },
-    clickComfirm() {
-      this.$emit("confirm");
-    },
+		  this.showSelected = false;
+		},
+		removeItem(item) {
+		  this.$emit("removeItem", item);
+		},
+		clickComfirm() {
+		  this.$emit("confirm");
+		},
+		getRole(item) {
+			let userType = 0;
+			let userId = item.userID;
+			console.log("qxj item",item);
+			if (userId != undefined && (userId != "" || userId.length > 0)) {
+				if (userId.indexOf('U') !== -1) {
+					userType = 0;
+				}
+				if (userId.indexOf('D') !== -1) {
+					userType = 1;
+				}
+				if (userId.indexOf('C') !== -1) {
+					userType = 2;
+				}
+			}
+			return userType;
+		}
   },
 };
 </script>
@@ -85,14 +107,27 @@ export default {
 .member_checked_desc {
   @include btwBox();
   background-color: #fff;
-  align-items: flex-start;
-  padding: 24rpx 44rpx 0;
-  height: 60px;
-  max-height: 60px;
+  align-items: center;
+  padding: 23rpx 24rpx 23rpx;
+  height: 70px;
+  max-height: 70px;
   box-shadow: 0px -1px 4px 1px rgba(0, 0, 0, 0.04);
-
+  border-top: 1rpx solid #E5E5E5;
+  .save_btn{
+		height: 40px !important;
+		line-height: 40px !important;
+		padding: 0 24rpx;
+		border-radius: 20rpx;
+		background-color: #FF5C03 !important;
+		color: #fff;
+  }
   .left_info {
-    @include colBox(false);
+    // @include colBox(false);
+    display: flex;
+    item-align: center;
+    .avatar_view {
+         margin-right: 12rpx;
+    }
     .select_num {
       display: flex;
       align-items: center;
@@ -100,12 +135,12 @@ export default {
     }
 
     .select_list {
-      font-size: 24rpx;
-      color: #8e9ab0;
-      overflow: hidden;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      max-width: 50vw;
+	  font-size: 24rpx;
+	  color: #8e9ab0;
+	  overflow: hidden;
+	  white-space: nowrap;
+	  text-overflow: ellipsis;
+	  max-width: 50vw;
     }
 
     .text {
@@ -122,14 +157,12 @@ export default {
 }
 
 .selected_container {
-  padding: 44rpx;
-
+  padding: 24rpx;
   .top_desc {
     @include btwBox();
     margin-bottom: 20rpx;
-
     .comfirm_text {
-      color: $uni-color-primary;
+      color: #FF5C03;
       margin-right: 24rpx;
     }
   }
@@ -137,6 +170,15 @@ export default {
   .selected_list {
     height: 60vh !important;
     overflow-y: auto;
-  }
+  }	
+  
+}
+
+.taoj {
+	  position: absolute;
+	  left: 0px;
+	  top: 0px;
+	  width: 100%;
+	  height: 100%;
 }
 </style>

+ 29 - 2
pages_im/components/ChooseIndexList/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<u-index-list @scrolltolower="scrolltolower" class="user_list" :style="{ height: height }" :index-list="indexList">
+	<u-index-list @scrolltolower="scrolltolower" class="user_list" activeColor="#FF5C03" inactiveColor="#757575" :style="{ height: height }" :index-list="indexList">
 		<template v-for="(item, index) in itemArr">
 			<u-index-item :key="index">
 				<u-index-anchor class="user_anchor" :text="indexList[index]"></u-index-anchor>
@@ -15,6 +15,8 @@
 				/>
 			</u-index-item>
 		</template>
+		
+		<view v-if="showBot" class="friendCount">{{ this.friendsNum }}个好友</view>
 	</u-index-list>
 </template>
 
@@ -51,8 +53,17 @@ export default {
 			default: false
 		}
 	},
+	mounted() {
+		setTimeout(()=>{
+			this.showBot=this.indexList.length>0;
+		},1000);
+		this.getFriendsCount();
+	},
 	data() {
-		return {};
+		return {
+			showBot:false,
+			friendsNum:0
+		};
 	},
 	methods: {
 		itemClick(item) {
@@ -63,6 +74,12 @@ export default {
 		},
 		scrolltolower() {
 			this.$emit('scrolltolower');
+		},
+		getFriendsCount(){
+			for (var index = 0; index < this.itemArr.length; index++) {
+				let  itemArr = this.itemArr[index];
+				this.friendsNum+=itemArr.length;
+			}
 		}
 	}
 };
@@ -80,4 +97,14 @@ export default {
 	background-color: #f8f8f8 !important;
 	border: none !important;
 }
+
+.friendCount{
+	font-size: 20px;
+	color: #666;
+	text-align: center;
+	height: 80rpx;
+	line-height: 80rpx;
+	margin-bottom: 30px;
+}
+	
 </style>

+ 3 - 4
pages_im/components/CustomNavBar/index.vue

@@ -1,12 +1,13 @@
 <template>
-	<u-navbar :title="title" placeholder class="custom_nav_bar">
+	<u-navbar :title="title" placeholder class="custom_nav_bar" :titleStyle="{fontSize: '18px'}">
 		<template slot="left">
 			<slot name="left">
 				<view class="u-nav-slot">
 					<image
 						@click="leftClick"
 						class="back_icon"
-						
+						width="12"
+						height="20"
 						src="../../static/images/common_left_arrow.png"
 						style="width: 12px;height: 20px;"
 						alt=""
@@ -77,8 +78,6 @@ export default {
 	.back_icon {
 		padding: 24rpx;
 		margin-left: 20rpx;
-		width: 12px;
-		height: 20px;
 	}
 
 	.more_dot {

+ 29 - 32
pages_im/components/MyAvatar/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<view style="position: relative;">
+	<view :style="[containerStyle, {position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center'}]">
 		<u-avatar
 		  @longpress="longpress"
 		  @click="click"
@@ -16,8 +16,7 @@
 	
 		<!-- <image  @longpress="longpress"  @click="click" :src="getAvatarUrl" class="headImg"></image> -->
 		
-		<slot >
-		</slot>
+		<slot></slot>
 		
 	</view>
   
@@ -40,7 +39,7 @@ export default {
     },
     size: {
       type: String,
-      default: "44",
+      default: "46",
     },
     isGroup: {
       type: Boolean,
@@ -60,37 +59,43 @@ export default {
     };
   },
   computed: {
+    containerStyle() {
+      let borderRadius = '4px';
+      if (this.shape === 'circle') {
+          borderRadius = '50%';
+      }
+      return {
+          borderRadius: borderRadius,
+          backgroundColor: '#F4F5F7' 
+      };
+    },
 	
     getAvatarUrl() {
-	 
-      if (this.src) {
-          return this.src;
-      }
-      else if (this.isGroup) {
-        return defaultGroupIcon;
+      if (this.src && this.src.trim() !== "" && this.src !== "undefined" && this.src !== "null") {
+         return this.src;
       }
-      else if (this.isNotify) {
+	  if (this.isGroup) {
+	     return defaultGroupIcon;
+	  }
+      if (this.isNotify) {
         return defaultNotifyIcon;
       }
-	  else{
-	  	 return defaultFaceIcon;
-	  }
-	  
-      // this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
-      // return "";
+      return defaultFaceIcon;
     },
     getDdefaultUrl() {
       return this.isGroup ? defaultGroupIcon : undefined;
     },
   },
+  watch: {
+    src(newVal, oldVal) {
+        //console.log("qxj MyAvatar src changed", oldVal, "→", newVal);
+        this.redirectShow();
+    },
+    desc() {
+      this.redirectShow();
+    },
+  },
   methods: {
-	getImgSrc(){
-	  	
-	  	if(!this.src){
-	  		return defaultFaceIcon;
-	  	}
-	  	return this.src;
-	},
     errorHandle() {
       this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
     },
@@ -106,14 +111,6 @@ export default {
       this.$emit("longpress");
     },
   },
-  watch: {
-    src() {
-      this.redirectShow();
-    },
-    desc() {
-      this.redirectShow();
-    },
-  },
 };
 </script>
 

+ 0 - 277
pages_im/components/SelectProject.vue

@@ -1,277 +0,0 @@
-<template>
-	<u-popup :show="selectProjectShow" mode="bottom" :round="10" @close="selectProjectShow=false">
-		<view class="popbox project-popup">
-			<view class="popbox-head">
-				<view class="title">选择项目</view>
-				<view class="close-btn" @click="selectProjectShow=false">
-					<u-icon name="close" color="#ccc" size="18"></u-icon>
-				</view>
-			</view>
-			<view class="search-box">
-				<u-search placeholder="搜索项目" v-model="searchKeyword" :showAction="true" actionText="搜索"
-					@search="searchProject" height="36" bgColor="#F5F5F5"></u-search>
-			</view>
-			<view class="popbox-body">
-				<scroll-view scroll-y="true" class="project-scroll-view">
-					<view class="tagbox-list">
-						<view class="empty-tip" v-if="filteredProjectList.length<=0">
-							暂无项目
-						</view>
-						<view class="tag-item" v-for="(item,index) in filteredProjectList" :key='item.dictValue'>
-							<u-tag :text="item.dictLabel"
-								:plain="(tempProjectItem && tempProjectItem.dictValue) != item.dictValue"
-								:type="(tempProjectItem && tempProjectItem.dictValue) == item.dictValue ? 'primary' : 'info'"
-								@click="handleProjectSelect(item)"></u-tag>
-						</view>
-					</view>
-				</scroll-view>
-			</view>
-			<view class="popbox-footer">
-				<u-button type="primary" text="确定" shape="circle" @click="confirmProject"></u-button>
-			</view>
-		</view>
-	</u-popup>
-
-</template>
-
-<script>
-	import {
-		beMember
-	} from '@/pages_im/api/im.js'
-	import {
-		getDictProject,
-	} from "@/pages_manage/api/courseManage.js"
-	export default {
-		name: "SelectProject",
-		props: {
-			selectProjectShow: {
-				type: Boolean,
-				default: false
-			},
-			application: Object,
-		},
-		data() {
-			return {
-				projectList: [],
-				tempProjectItem: null,
-				searchKeyword: '',
-			};
-		},
-		computed: {
-			filteredProjectList() {
-				if (!this.searchKeyword) {
-					return this.projectList;
-				}
-				return this.projectList.filter(item =>
-					item.dictLabel.toLowerCase().includes(this.searchKeyword.toLowerCase())
-				);
-			},
-		},
-		created() {
-			this.getDictProjectFun()
-		},
-		methods: {
-			handleProjectSelect(item) {
-				this.tempProjectItem = item;
-			},
-			async getDictProjectFun() {
-				const res = await getDictProject()
-				if (res.code == 200) {
-					this.projectList = res.data
-				}
-			},
-			async confirmProject() {
-				let companyUserInfo = uni.getStorageSync('companyUserInfo')
-				companyUserInfo = companyUserInfo && JSON.parse(companyUserInfo)
-				let userId = this.application.fromUserID
-				if (this.application.fromUserID.indexOf('U') !== -1) {
-					userId = this.application.fromUserID.slice(1)
-				}
-				let params = {
-					userId,
-					companyUserId: companyUserInfo.userId,
-					companyId: companyUserInfo.companyId,
-					projectId: this.tempProjectItem.dictValue
-				}
-				const res = await beMember(params)
-				if (res.code == 200) {
-					this.$emit('agreeFun')
-				} else {
-					uni.showToast({
-						icon: 'icon',
-						title: res.msg
-					})
-				}
-			},
-		}
-	}
-</script>
-
-<style lang="scss" scoped>
-	.project-popup {
-		width: 100%;
-		background-color: #fff;
-		border-radius: 20rpx 20rpx 0 0;
-		display: flex;
-		flex-direction: column;
-
-		.popbox-head {
-			display: flex;
-			justify-content: center;
-			align-items: center;
-			padding: 30rpx;
-			font-size: 32rpx;
-			font-weight: bold;
-			position: relative;
-			border-bottom: none;
-
-			.title {
-				flex: 1;
-				text-align: center;
-			}
-
-			.close-btn {
-				position: absolute;
-				right: 30rpx;
-				top: 50%;
-				transform: translateY(-50%);
-				padding: 10rpx;
-			}
-		}
-
-		.search-box {
-			padding: 0 30rpx 20rpx;
-		}
-
-		.popbox-body {
-			flex: 1;
-			overflow: hidden;
-			padding: 0;
-		}
-
-		.project-scroll-view {
-			height: 50vh;
-			padding: 0 30rpx;
-			box-sizing: border-box;
-		}
-
-		.tagbox-list {
-			display: flex;
-			flex-wrap: wrap;
-			padding-bottom: 20rpx;
-		}
-
-		.tag-item {
-			margin-right: 20rpx;
-			margin-bottom: 20rpx;
-		}
-
-		.empty-tip {
-			width: 100%;
-			text-align: center;
-			color: #999;
-			padding: 50rpx 0;
-			font-size: 28rpx;
-		}
-
-		.popbox-footer {
-			padding: 20rpx 30rpx 40rpx;
-			border-top: 1px solid #f5f5f5;
-		}
-	}
-
-	.popbox {
-		background-color: #fff;
-		border-radius: 12px;
-		font-family: PingFang SC, PingFang SC;
-		font-weight: 400;
-		font-size: 14px;
-		color: #333;
-
-		&-head {
-			padding: 15px;
-			font-weight: bold;
-			font-size: 15px;
-			text-align: center;
-			position: relative;
-		}
-
-		.close-circle {
-			position: absolute;
-			right: 10px;
-			top: 50%;
-			transform: translateY(-50%);
-		}
-
-		&-body {
-			padding: 10px 10px;
-		}
-
-		.radiobox {
-			&-item {
-				padding: 10px 0;
-				border-bottom: 1px solid #f5f5f5;
-
-				&:last-child {
-					border-bottom: none;
-				}
-			}
-		}
-
-		&-footer {
-			padding: 15px 0;
-		}
-
-		&-footer-btn {
-			flex: 1;
-			height: 44px;
-			line-height: 44px;
-			margin: 0 10px;
-			border-radius: 50px;
-			border: none;
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 14px;
-			color: #333;
-
-			&::after {
-				border: none;
-			}
-		}
-
-		.con-btn {
-			background-color: #1677ff;
-			color: #fff;
-		}
-
-		.choosetitle {
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 15px;
-			color: #333;
-		}
-
-		.invitetip {
-			margin-top: 10px;
-			background-color: #ebf5fb;
-			color: #1677ff;
-			padding: 5px 10px;
-			border-radius: 5px;
-		}
-
-		.sharePop-item {
-			padding: 0 10px;
-			box-sizing: border-box;
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 14px;
-			display: inline-flex !important;
-
-			image {
-				height: 48px;
-				width: 48px;
-				margin-bottom: 10px;
-			}
-		}
-	}
-</style>

+ 16 - 18
pages_im/components/SettingItem/index.vue

@@ -1,22 +1,11 @@
 <template>
-  <view
-    @click="onClick"
-    class="setting_item"
-    :class="{ setting_item_border: border }">
-    <text :style="{ color: danger ? '#FF381F' : '$uni-text-color' }">{{
-      title
-    }}</text>
-    <u-switch
-      :loading="loading"
-      @change="onChange"
-      :asyncChange="true"
-      v-if="is_switch"
-      size="20"
-      :value="switchValue"
-    />
+  <view @click="onClick" class="setting_item" :class="{ setting_item_border: border }">
+    <text :style="{ color: danger ? '#D32414' : '$uni-text-color' }">{{ title }}</text>
+    <u-switch :loading="loading" active-color="#FF5C03" @change="onChange" :asyncChange="true" v-if="is_switch" size="20"
+      :value="switchValue" />
     <view v-else class="setting_right">
       <slot></slot>
-      <u-icon v-if="arrow" name="arrow-right" color="#999" size="18" />
+      <u-icon v-if="arrow" name="arrow-right" color="#999" size="14" />
     </view>
   </view>
 </template>
@@ -69,15 +58,24 @@ export default {
 <style lang="scss" scoped>
 .setting_item {
   @include btwBox();
-  padding: 24rpx 36rpx;
+  padding: 28rpx 24rpx;
   color: $uni-text-color;
+  position: relative;
 
   .setting_right {
     @include vCenterBox();
   }
 
   &_border {
-    border-bottom: 1px solid rgba(153, 153, 153, 0.2);
+    &:after {
+      content: "";
+      position: absolute;
+      left: 24rpx;
+      right: 24rpx;
+      bottom: 0;
+      height: 1rpx;
+      background-color: #ECECEC;
+    }
   }
 }
 </style>

+ 40 - 39
pages_im/components/UserItem/index.vue

@@ -1,23 +1,24 @@
 <template>
 	<view @click="clickItem" class="user_item">
-		<view v-if="checkVisible" class="check_wrap" :class="{ check_wrap_active: checked, check_wrap_disabled: disabled }">
+		<view v-if="checkVisible" class="check_wrap"
+			:class="{ check_wrap_active: checked, check_wrap_disabled: disabled }">
 			<u-icon v-show="checked" name="checkbox-mark" size="12" color="#fff" />
 		</view>
 
-		<my-avatar :src="item.faceURL" :desc="item.remark || item.nickname || item.showName" :isGroup="item.groupName !== undefined || isGroupConversation" size="44">
-			<image v-if="getRole()==1" class="taoj"  src="/static/svg/doctor.svg"></image>
-			<image v-if="getRole()==2" class="taoj"  src="/static/svg/guanjia.svg"></image>
+		<my-avatar :src="item.faceURL" :desc="item.remark || item.nickname || item.showName"
+			:isGroup="item.groupName !== undefined || isGroupConversation" size="42">
+			<image v-if="getRole() == 1" class="taoj" src="/static/svg/doctor.svg"></image>
+			<image v-if="getRole() == 2" class="taoj" src="/static/svg/guanjia.svg"></image>
 		</my-avatar>
 
 		<view class="user_item_details">
 			<view class="user_name">
 				<text>{{ item.remark || item.nickname || item.groupName || item.showName }}</text>
-				<u-tag v-if="position" :text="position" plain shape="circle" style="margin-left: 8rpx" size="mini"></u-tag>
+				<u-tag v-if="position" :text="position" plain shape="circle" style="margin-left: 8rpx"
+					size="mini"></u-tag>
+				<text v-if="item.roleLevel === 100 && showRole" class="user_role">群主</text>
+				<text v-if="item.roleLevel === 60 && showRole" class="user_role admin_role">管理员</text>
 			</view>
-
-			<text v-if="item.roleLevel === 100 && showRole" class="user_role">群主</text>
-			<text v-if="item.roleLevel === 60 && showRole" class="user_role admin_role">管理员</text>
-			<!-- <view class="bottom_line" /> -->
 		</view>
 
 		<slot name="action"></slot>
@@ -64,7 +65,7 @@ export default {
 		}
 	},
 	mounted() {
-		
+
 	},
 	methods: {
 		clickItem() {
@@ -72,18 +73,18 @@ export default {
 				this.$emit(this.checkVisible ? 'updateCheck' : 'itemClick', this.item);
 			}
 		},
-		getRole(){
-			let userType=0;
-			let userId=this.item.userID;
-			if(userId!=undefined  && (userId!="" || userId.length>0)){
-				if(userId.indexOf('U')!==-1){
-					userType=0;
+		getRole() {
+			let userType = 0;
+			let userId = this.item.userID;
+			if (userId != undefined && (userId != "" || userId.length > 0)) {
+				if (userId.indexOf('U') !== -1) {
+					userType = 0;
 				}
-				if(userId.indexOf('D')!==-1){
-					userType=1;
+				if (userId.indexOf('D') !== -1) {
+					userType = 1;
 				}
-				if(userId.indexOf('C')!==-1){
-					userType=2;
+				if (userId.indexOf('C') !== -1) {
+					userType = 2;
 				}
 			}
 			return userType;
@@ -95,9 +96,10 @@ export default {
 <style lang="scss" scoped>
 .user_item {
 	@include vCenterBox();
-	padding: 24rpx 44rpx;
+	padding: 24rpx;
 	color: $uni-text-color;
 	position: relative;
+	background-color: #fff;
 
 	.check_wrap {
 		@include centerBox();
@@ -106,18 +108,18 @@ export default {
 		min-width: 40rpx;
 		height: 40rpx;
 		min-height: 40rpx;
-		border: 2px solid #979797;
+		border: 1px solid #979797;
 		border-radius: 50%;
 		margin-top: 16rpx;
 		margin-right: 24rpx;
 
 		&_active {
-			background-color: #1d6bed;
+			background-color: #FF5C03;
 			border: none;
 		}
 
 		&_disabled {
-			background-color: #c8c9cc;
+			background-color: #ccc;
 		}
 	}
 
@@ -149,15 +151,16 @@ export default {
 		}
 
 		.user_role {
-			font-size: 34rpx;
-			// background-color: #f4da9a;
-			// color: #FF8C00;
-			padding: 8rpx 24rpx;
-			border-radius: 24rpx;
+			font-size: 24rpx;
+			background-color: rgba(255, 92, 3, 0.12);
+			color: #FF5C03;
+			padding: 6rpx 8rpx;
+			border-radius: 8rpx;
 			margin-left: 24rpx;
-			color: $u-tips-color;
 		}
 
+		
+
 		.admin_role {
 			color: $u-tips-color;
 			// background-color: #A2C9F8;
@@ -172,13 +175,11 @@ export default {
 	}
 }
 
-.taoj{
-		position: absolute;
-		left: 0px;
-		top: 0px;
-		right: 0px;
-		bottom: 0px;
-		width: 84rpx;
-		height: 84rpx;
-	}
+.taoj {
+	position: absolute;
+	left: 0px;
+	top: 0px;
+	width: 100%;
+	height: 100%;
+}
 </style>

+ 1 - 2
pages_im/constant/comp.js

@@ -16,8 +16,7 @@ export const ChatingFooterActionTypes = {
   StoreOrder: "StoreOrder",
   CouponPackage:"CouponPackage",
   InquirySelect:"InquirySelect",
-  Live: "Live",
-  LuckyBag: "LuckyBag",
+  Course:"Course"
 };
 
 export const MessageMenuTypes = {

+ 0 - 1
pages_im/constant/event.js

@@ -18,5 +18,4 @@ export const PageEvents = {
   NativeCallEnd: "NativeCallEnd",
   GroupMessageReceipt: "GroupMessageReceipt",
   ReEditMessage: "ReEditMessage",
-  LuckyBag: "LuckyBag",
 };

+ 1 - 1
pages_im/constant/im.js

@@ -1,4 +1,4 @@
-import { MessageType } from "openim-uniapp-polyfill";
+import { MessageType } from "./imConstants";
 
 export const CustomType = {
   VideoCall: "c100",

+ 167 - 0
pages_im/constant/imConstants.js

@@ -0,0 +1,167 @@
+export const MessageReceiveOptType = {
+  Normal: 0,
+  NotReceive: 1,
+  NotNotify: 2,
+};
+
+export const AddFriendPermission = {
+  AddFriendAllowed: 0,
+  AddFriendAllowedNoReview: 1,
+  AddFriendDenied: 2,
+};
+
+export const AllowType = {
+  Allowed: 0,
+  NotAllowed: 1,
+};
+
+export const GroupType = {
+  Group: 2,
+  WorkingGroup: 2,
+};
+
+export const GroupJoinSource = {
+  Invitation: 2,
+  Search: 3,
+  QrCode: 4,
+};
+
+export const GroupMemberRole = {
+  Normal: 20,
+  Admin: 60,
+  Owner: 100,
+};
+
+export const GroupVerificationType = {
+  ApplyNeedInviteNot: 0,
+  AllNeed: 1,
+  AllNot: 2,
+};
+
+export const MessageStatus = {
+  Sending: 1,
+  Succeed: 2,
+  Failed: 3,
+};
+
+export const Platform = {
+  iOS: 1,
+  Android: 2,
+  Windows: 3,
+  MacOSX: 4,
+  Web: 5,
+  Linux: 7,
+  AndroidPad: 8,
+  iPad: 9,
+};
+
+export const LogLevel = {
+  Verbose: 6,
+  Debug: 5,
+  Info: 4,
+  Warn: 3,
+  Error: 2,
+  Fatal: 1,
+  Panic: 0,
+};
+
+export const ApplicationHandleResult = {
+  Unprocessed: 0,
+  Agree: 1,
+  Reject: -1,
+};
+
+export const MessageType = {
+  TextMessage: 101,
+  PictureMessage: 102,
+  VoiceMessage: 103,
+  VideoMessage: 104,
+  FileMessage: 105,
+  AtTextMessage: 106,
+  MergeMessage: 107,
+  CardMessage: 108,
+  LocationMessage: 109,
+  CustomMessage: 110,
+  TypingMessage: 113,
+  QuoteMessage: 114,
+  FaceMessage: 115,
+  MarkdownMessage: 118,
+  StreamMessage: 143,
+  FriendAdded: 1201,
+  OANotification: 1400,
+  GroupCreated: 1501,
+  GroupInfoUpdated: 1502,
+  MemberQuit: 1504,
+  GroupOwnerTransferred: 1507,
+  MemberKicked: 1508,
+  MemberInvited: 1509,
+  MemberEnter: 1510,
+  GroupDismissed: 1511,
+  GroupMemberMuted: 1512,
+  GroupMemberCancelMuted: 1513,
+  GroupMuted: 1514,
+  GroupCancelMuted: 1515,
+  GroupAnnouncementUpdated: 1519,
+  GroupNameUpdated: 1520,
+  BurnMessageChange: 1701,
+  RevokeMessage: 2101,
+
+  MsgPinned: 2400,
+};
+
+export const SessionType = {
+  Single: 1,
+  Group: 3,
+  WorkingGroup: 3,
+  Notification: 4,
+};
+
+export const GroupStatus = {
+  Normal: 0,
+  Banned: 1,
+  Dismissed: 2,
+  Muted: 3,
+};
+
+export const GroupAtType = {
+  AtNormal: 0,
+  AtMe: 1,
+  AtAll: 2,
+  AtAllAtMe: 3,
+  AtGroupNotice: 4,
+};
+
+export const GroupMemberFilter = {
+  All: 0,
+  Owner: 1,
+  Admin: 2,
+  Normal: 3,
+  AdminAndNormal: 4,
+  AdminAndOwner: 5,
+};
+
+export const Relationship = {
+  isBlack: 0,
+  isFriend: 1,
+};
+
+export const LoginStatus = {
+  Logout: 1,
+  Logging: 2,
+  Logged: 3,
+};
+
+export const OnlineState = {
+  Online: 1,
+  Offline: 0,
+};
+
+export const GroupMessageReaderFilter = {
+  Read: 0,
+  UnRead: 1,
+};
+
+export const ViewType = {
+  History: 0,
+  Search: 1,
+};

+ 572 - 577
pages_im/pages/common/contactChoose/index.vue

@@ -2,15 +2,21 @@
 	<view class="contact_choose_container">
 		<u-modal :show="showConfirmModal" showCancelButton asyncClose @confirm="modalConfirm"
 			@cancel="() => (showConfirmModal = false)" :content="`确定要发送${cardInfo.nickname}的名片吗?`" />
+		<u-modal :show="showReasonModal" title="入群申请" showCancelButton @confirm="confirmInvite"
+			@cancel="() => (showReasonModal = false)">
+			<view class="slot-content" style="width: 100%; padding: 24rpx;">
+				<u-input v-model="inviteReason" placeholder="请输入验证信息" border="surround"></u-input>
+			</view>
+		</u-modal>
 		<custom-nav-bar title="联系人" />
-
 		<view class="search_bar_wrap">
-			<u-search shape="square" placeholder="搜索" :showAction="false" v-model="keyword" />
+			<u-search class="search_input" shape="round" border-color="#F5F7FA" :showAction="false" placeholder="搜索关键词"
+				v-model="keyword" />
 		</view>
-
+		
 		<view class="tab_container">
 			<template v-if="activeTab === 0">
-				<template v-for="item in tabs.slice(0, storeSelfInfo.members.length ? 3 : 2)">
+				<template v-for="item in tabsGroup">
 					<setting-item v-if="!(!showGroup && item.idx === 2)" @click="tabChange(item.idx)" :key="item.idx"
 						:title="item.title" :border="false" />
 				</template>
@@ -57,501 +63,426 @@
 </template>
 
 <script>
-	import {
-		batchCreateCourseRecord
-	} from '@/pages_manage/api/courseManage.js'
-	import {
-		mapGetters
-	} from 'vuex';
-	import {
-		ContactChooseTypes,
-		PageEvents,
-		UpdateMessageTypes,
-		CustomType,
-		FileMessageTypes
-	} from '../../../constant';
-	import {
-		formatChooseData,
-		toastWithCallback
-	} from '../../../util/common';
-	import {
-		parseMessageByType
-	} from '../../../util/imCommon';
-	import IMSDK, {
-		GroupStatus,
-		IMMethods,
-		SessionType,
-		MessageStatus
-	} from 'openim-uniapp-polyfill';
-	import CustomNavBar from '../../../components/CustomNavBar/index.vue';
-	import UserItem from '../../../components/UserItem/index.vue';
-	import ChooseIndexList from '../../../components/ChooseIndexList/index.vue';
-	import ChooseIndexFooter from '../../../components/ChooseIndexFooter/index.vue';
-	import SettingItem from '../../../components/SettingItem/index.vue';
-
-	const showGroupTypes = [
-		ContactChooseTypes.Mute,
-		ContactChooseTypes.ForWard,
-		ContactChooseTypes.BatchForWard,
-		ContactChooseTypes.MergeForWard,
-		ContactChooseTypes.GetListWithGroup,
-		ContactChooseTypes.InviteMeeting,
-		ContactChooseTypes.SendImage
-	];
-
-	const showConversationTypes = [
-		ContactChooseTypes.ForWard,
-		ContactChooseTypes.BatchForWard,
-		ContactChooseTypes.MergeForWard,
-		ContactChooseTypes.ShareCard,
-		ContactChooseTypes.InviteMeeting,
-		ContactChooseTypes.SendImage
-	];
-
-	export default {
-		components: {
-			CustomNavBar,
-			UserItem,
-			ChooseIndexList,
-			ChooseIndexFooter,
-			SettingItem
+import { mapGetters } from 'vuex';
+import { batchCreateCourseRecord } from '@/pages_manage/api/courseManage.js'
+import { ContactChooseTypes, PageEvents, UpdateMessageTypes, CustomType, FileMessageTypes } from '../../../constant';
+import { formatChooseData, toastWithCallback } from '../../../util/common';
+import { parseMessageByType } from '../../../util/imCommon';
+import IMSDK, { GroupStatus, IMMethods, SessionType, MessageStatus, GroupVerificationType } from 'openim-uniapp-polyfill';
+import CustomNavBar from '../../../components/CustomNavBar/index.vue';
+import UserItem from '../../../components/UserItem/index.vue';
+import ChooseIndexList from '../../../components/ChooseIndexList/index.vue';
+import ChooseIndexFooter from '../../../components/ChooseIndexFooter/index.vue';
+import SettingItem from '../../../components/SettingItem/index.vue';
+
+const showGroupTypes = [
+	ContactChooseTypes.Mute,
+	ContactChooseTypes.ForWard,
+	ContactChooseTypes.BatchForWard,
+	ContactChooseTypes.MergeForWard,
+	ContactChooseTypes.GetListWithGroup,
+	ContactChooseTypes.InviteMeeting,
+	ContactChooseTypes.SendImage
+];
+
+const showConversationTypes = [
+	ContactChooseTypes.ForWard,
+	ContactChooseTypes.BatchForWard,
+	ContactChooseTypes.MergeForWard,
+	ContactChooseTypes.ShareCard,
+	ContactChooseTypes.InviteMeeting,
+	ContactChooseTypes.SendImage
+];
+
+export default {
+	components: {
+		CustomNavBar,
+		UserItem,
+		ChooseIndexList,
+		ChooseIndexFooter,
+		SettingItem
+	},
+	data() {
+		return {
+			keyword: '',
+			type: ContactChooseTypes.Card,
+			activeTab: 0,
+			showConfirmModal: false,
+			groupID: '',
+			cardInfo: {},
+			mergeInfo: {},
+			forwardMessage: '',
+			disabledUserIDList: [this.$store.getters.storeCurrentUserID],
+			disabledGroupIDList: [],
+			checkedUserInfoList: [],
+			checkedGroupInfoList: [],
+			originConversations: [],
+			comfirmLoading: false,
+			showReasonModal: false,
+			inviteReason: '',
+			groupInfo: {},
+			tabs: [
+				{
+					idx: 1,
+					title: '我的好友'
+				},
+				{
+					idx: 2,
+					title: '我的群组'
+				},
+				{
+					idx: 3,
+					title: '组织架构'
+				}
+			]
+		};
+	},
+	computed: {
+		...mapGetters([
+			'storeSelfInfo',
+			'storeFriendList',
+			'storeBlackList',
+			'storeGroupList',
+			'storeCurrentConversation',
+			'storeCurrentUserID',
+			'storeConversationList',
+			'storeCacheMap'
+		]),
+		showCheck() {
+			return this.type !== ContactChooseTypes.Card;
 		},
-		data() {
-			return {
-				keyword: '',
-				type: ContactChooseTypes.Card,
-				activeTab: 0,
-				showConfirmModal: false,
-				groupID: '',
-				cardInfo: {},
-				mergeInfo: {},
-				forwardMessage: '',
-				disabledUserIDList: [this.$store.getters.storeCurrentUserID],
-				disabledGroupIDList: [],
-				checkedUserInfoList: [],
-				checkedGroupInfoList: [],
-				originConversations: [],
-				comfirmLoading: false,
-				tabs: [{
-						idx: 1,
-						title: '我的好友'
-					},
-					{
-						idx: 2,
-						title: '我的群组'
-					},
-					{
-						idx: 3,
-						title: '组织架构'
-					}
-				]
-			};
+		showGroup() {
+			return showGroupTypes.includes(this.type);
 		},
-		computed: {
-			...mapGetters([
-				'storeSelfInfo',
-				'storeFriendList',
-				'storeBlackList',
-				'storeGroupList',
-				'storeCurrentConversation',
-				'storeCurrentUserID',
-				'storeConversationList',
-				'storeCacheMap'
-			]),
-			showCheck() {
-				return this.type !== ContactChooseTypes.Card;
-			},
-			showGroup() {
-				return showGroupTypes.includes(this.type);
-			},
-			showConversation() {
-				return showConversationTypes.includes(this.type);
-			},
-			getChooseData() {
-				if (this.keyword) {
-					return {
-						indexList: ['#'],
-						dataList: [this.storeFriendList.filter((friend) => friend.nickname.includes(this.keyword) || friend
-							.remark.includes(this.keyword))]
-					};
-				}
-				return formatChooseData(this.storeFriendList);
-			},
-			renderGroupList() {
-				return this.keyword ? this.storeGroupList.filter((group) => group.groupName.includes(this.keyword)) : this
-					.storeGroupList;
-			},
-			renderConversations() {
-				return this.keyword ? this.originConversations.filter((cve) => cve.showName.includes(this.keyword)) : this
-					.originConversations;
-			},
-			checkedUserIDList() {
-				return this.checkedUserInfoList.map((user) => user.userID);
-			},
-			checkedGroupIDList() {
-				return this.checkedGroupInfoList.map((group) => group.groupID);
-			},
-			getCheckedInfo() {
-				return [...this.checkedUserInfoList, ...this.checkedGroupInfoList];
+		showConversation() {
+			return showConversationTypes.includes(this.type);
+		},
+		getChooseData() {
+			if (this.keyword) {
+				return {
+					indexList: ['#'],
+					dataList: [this.storeFriendList.filter((friend) => friend.nickname.includes(this.keyword) || friend.remark.includes(this.keyword))]
+				};
 			}
+			return formatChooseData(this.storeFriendList);
 		},
-		onLoad(options) {
-			const {
-				groupID,
-				type,
-				mergeInfo,
-				cardInfo,
-				forwardMessage,
-				checkedUserInfoList,
-				checkedGroupInfoList
-			} = options;
-
-			this.type = type;
-			this.groupID = groupID;
-			this.cardInfo = cardInfo ? JSON.parse(cardInfo) : {};
-			this.mergeInfo = decodeURIComponent(mergeInfo);
-			this.forwardMessage = decodeURIComponent(forwardMessage) || '';
-			this.checkedUserInfoList = checkedUserInfoList ? JSON.parse(checkedUserInfoList) : [];
-			this.checkedGroupInfoList = checkedGroupInfoList ? JSON.parse(checkedGroupInfoList) : [];
-			this.originConversations = this.storeConversationList.filter((conversation) => conversation
-				.conversationType !== SessionType.Notification);
-			if (this.type === ContactChooseTypes.Invite) {
-				this.checkJoinedGroupUser();
+		renderGroupList() {
+			return this.keyword ? this.storeGroupList.filter((group) => group.groupName.includes(this.keyword)) : this.storeGroupList;
+		},
+		renderConversations() {
+			return this.keyword ? this.originConversations.filter((cve) => cve.showName.includes(this.keyword)) : this.originConversations;
+		},
+		checkedUserIDList() {
+			return this.checkedUserInfoList.map((user) => user.userID);
+		},
+		checkedGroupIDList() {
+			return this.checkedGroupInfoList.map((group) => group.groupID);
+		},
+		getCheckedInfo() {
+			return [...this.checkedUserInfoList, ...this.checkedGroupInfoList];
+		},
+		tabsGroup() {
+			return this.tabs.slice(0, this.storeSelfInfo.members.length ? 3 : 2)
+		}
+	},
+	onLoad(options) {
+		const { groupID, type, mergeInfo, cardInfo, forwardMessage, checkedUserInfoList, checkedGroupInfoList } = options;
+		this.type = type;
+		this.groupID = groupID;
+		this.cardInfo = cardInfo ? JSON.parse(cardInfo) : {};
+		this.mergeInfo = decodeURIComponent(mergeInfo);
+		this.forwardMessage = decodeURIComponent(forwardMessage) || '';
+		console.log('forwardMessage+++',type,forwardMessage,)
+		this.checkedUserInfoList  = checkedUserInfoList ? JSON.parse(checkedUserInfoList) : [];
+		this.checkedGroupInfoList = checkedGroupInfoList ? JSON.parse(checkedGroupInfoList) : [];
+		this.originConversations = this.storeConversationList.filter((conversation) => conversation.conversationType !== SessionType.Notification);
+		if (this.type === ContactChooseTypes.Invite) {
+			this.checkJoinedGroupUser();
+			this.getGroupInfo();
+		}
+		if (this.showConversation) {
+			this.checkBlackUser();
+		}
+		if (showGroupTypes.includes(this.type)) {
+			this.checkDisabledGroup();
+		}
+		if (!this.storeSelfInfo.members.length) {
+			this.tabChange(1);
+		}
+
+	},
+	methods: {
+		checkJoinedGroupUser() {
+			const friendIDList = this.storeFriendList.map((friend) => friend.userID);
+			IMSDK.asyncApi('getUsersInGroup', IMSDK.uuid(), {
+				groupID: this.groupID,
+				userIDList: friendIDList
+			}).then(({ data }) => {
+				this.disabledUserIDList = [...data, ...this.disabledUserIDList];
+			});
+		},
+		getGroupInfo() {
+			if (!this.groupID) return;
+			IMSDK.asyncApi(IMSDK.IMMethods.GetSpecifiedGroupsInfo, IMSDK.uuid(), [this.groupID]).then(({ data }) => {
+				this.groupInfo = data[0] || {};
+			});
+		},
+		checkBlackUser() {
+			const blackIDList = this.storeBlackList.map((black) => black.userID);
+			this.disabledUserIDList = [...this.disabledUserIDList, ...blackIDList];
+		},
+		checkDisabledGroup() {
+			this.disabledGroupIDList = this.storeGroupList.filter((group) => group.status !== GroupStatus.Nomal).map((group) => group.groupID);
+		},
+		tabChange(idx) {
+			this.keyword = '';
+			if (idx === 3) {
+				uni.$u.route('/pages_im/pages/contact/orgnization/index', {
+					title: this.$store.getters.storeOrganizationData.current.name,
+					departmentID: '',
+					checkedUserInfoList: JSON.stringify(this.checkedUserInfoList),
+					checkedGroupInfoList: JSON.stringify(this.checkedGroupInfoList),
+					disabledUserIDList: JSON.stringify(this.disabledUserIDList),
+					groupID: this.groupID,
+					chooseType: this.type
+				});
+				return;
 			}
-			if (this.showConversation) {
-				this.checkBlackUser();
+			this.activeTab = idx;
+		},
+		updateCheckedUserOrGroup(item) {
+			if (item.userID) {
+				this.updateCheckedUser(item);
+			} else {
+				this.updateCheckedGroup(item);
 			}
-			if (showGroupTypes.includes(this.type)) {
-				this.checkDisabledGroup();
+		},
+		updateCheckedUser(item) {
+			const idx = this.checkedUserInfoList.findIndex((user) => user.userID === item.userID);
+			if (idx > -1) {
+				const tmpArr = [...this.checkedUserInfoList];
+				tmpArr.splice(idx, 1);
+				this.checkedUserInfoList = [...tmpArr];
+			} else {
+				this.checkedUserInfoList = [...this.checkedUserInfoList, item];
 			}
 		},
-		methods: {
-			checkJoinedGroupUser() {
-				const friendIDList = this.storeFriendList.map((friend) => friend.userID);
-				IMSDK.asyncApi('getUsersInGroup', IMSDK.uuid(), {
-					groupID: this.groupID,
-					userIDList: friendIDList
-				}).then(({
-					data
-				}) => {
-					this.disabledUserIDList = [...data, ...this.disabledUserIDList];
+		updateCheckedGroup(item) {
+			console.log('updateCheckedGroup', item);
+			const idx = this.checkedGroupInfoList.findIndex((group) => group.groupID === item.groupID);
+			if (idx > -1) {
+				const tmpArr = [...this.checkedGroupInfoList];
+				tmpArr.splice(idx, 1);
+				this.checkedGroupInfoList = [...tmpArr];
+			} else {
+				this.checkedGroupInfoList = [...this.checkedGroupInfoList, item];
+			}
+		},
+		userClick(item) {
+			this.cardInfo = {
+				...item
+			};
+			this.showConfirmModal = true;
+		},
+		confirmInvite() {
+			this.showReasonModal = false;
+			this.comfirmLoading = true;
+			IMSDK.asyncApi(IMSDK.IMMethods.InviteUserToGroup, IMSDK.uuid(), {
+				groupID: this.groupID,
+				reason: this.inviteReason,
+				userIDList: this.getCheckedInfo.map((user) => user.userID)
+			})
+				.then(() => {
+					toastWithCallback('操作成功', () => uni.navigateBack());
+					this.comfirmLoading = false;
+				})
+				.catch(() => {
+					toastWithCallback('操作失败');
+					this.comfirmLoading = false;
 				});
-			},
-			checkBlackUser() {
-				const blackIDList = this.storeBlackList.map((black) => black.userID);
-				this.disabledUserIDList = [...this.disabledUserIDList, ...blackIDList];
-			},
-			checkDisabledGroup() {
-				this.disabledGroupIDList = this.storeGroupList.filter((group) => group.status !== GroupStatus.Nomal).map((
-					group) => group.groupID);
-			},
-			tabChange(idx) {
-				this.keyword = '';
-				if (idx === 3) {
-					uni.$u.route('/pages_im/pages/contact/orgnization/index', {
-						title: this.$store.getters.storeOrganizationData.current.name,
-						departmentID: '',
-						checkedUserInfoList: JSON.stringify(this.checkedUserInfoList),
-						checkedGroupInfoList: JSON.stringify(this.checkedGroupInfoList),
-						disabledUserIDList: JSON.stringify(this.disabledUserIDList),
-						groupID: this.groupID,
-						chooseType: this.type
-					});
-					return;
+		},
+		async confirm() {
+			let isBack = false
+			if (this.tabsGroup.length == 2 && !this.showGroup || this.tabsGroup.length == 1) {
+				isBack = true
+			}
+			if (this.activeTab && !isBack) {
+				this.activeTab = 0;
+				return;
+			}
+			this.comfirmLoading = true;
+			if (this.type === ContactChooseTypes.GetList || this.type === ContactChooseTypes.GetListWithGroup) {
+				let pages = getCurrentPages();
+				let prevPage = pages[pages.length - 2];
+				let data = this.getCheckedInfo;
+				if (this.type === ContactChooseTypes.GetListWithGroup) {
+					data = [this.checkedUserInfoList, this.checkedGroupInfoList];
 				}
-				this.activeTab = idx;
-			},
-			updateCheckedUserOrGroup(item) {
-				if (item.userID) {
-					this.updateCheckedUser(item);
-				} else {
-					this.updateCheckedGroup(item);
+				prevPage.$vm.getCheckedUsers(data);
+				this.comfirmLoading = false;
+
+				uni.navigateBack({
+					delta: 1
+				});
+				return;
+			}
+
+			if (this.type === ContactChooseTypes.Invite) {
+				if (this.groupInfo.needVerification === GroupVerificationType.AllNeed) {
+					this.showReasonModal = true;
+					this.comfirmLoading = false;
+					return;
 				}
-			},
-			updateCheckedUser(item) {
-				const idx = this.checkedUserInfoList.findIndex((user) => user.userID === item.userID);
-				if (idx > -1) {
-					const tmpArr = [...this.checkedUserInfoList];
-					tmpArr.splice(idx, 1);
-					this.checkedUserInfoList = [...tmpArr];
-				} else {
-					this.checkedUserInfoList = [...this.checkedUserInfoList, item];
+				IMSDK.asyncApi(IMSDK.IMMethods.InviteUserToGroup, IMSDK.uuid(), {
+					groupID: this.groupID,
+					reason: '',
+					userIDList: this.getCheckedInfo.map((user) => user.userID)
+				})
+					.then(() => {
+						toastWithCallback('操作成功', () => uni.navigateBack());
+						this.comfirmLoading = false;
+					})
+					.catch(() => toastWithCallback('操作失败'));
+				return;
+			}
+			//手动看课
+            if (this.type === ContactChooseTypes.SendCourse) {
+            	const courseItemData = uni.getStorageSync('courseItemData')
+            	uni.removeStorageSync('courseItemData')
+            	let companyUserInfo = uni.getStorageSync('companyUserInfo')
+            	const userID = this.getCheckedInfo.map(item => {
+            		return item.userID.replace(/^U/, '');
+            	})
+            	companyUserInfo = companyUserInfo && JSON.parse(companyUserInfo)
+            	let paramsData = {
+            		userIds: userID,
+            		companyUserId: companyUserInfo.userId,
+            		companyId: companyUserInfo.companyId,
+            		periodId: courseItemData.periodId,
+            		courseId: courseItemData.courseId,
+            		courseName: courseItemData.courseName,
+            		videoId: courseItemData.videoId,
+            		title: courseItemData.title,
+            		url: courseItemData.thumbnail,
+            		projectId: courseItemData.projectId,
+            		linkId: courseItemData.linkId
+            	}
+            	const result = await batchCreateCourseRecord(paramsData)
+            	if (result.code !== 200) {
+            		uni.showToast({
+            			icon: 'none',
+            			title: result.msg
+            		})
+            	}
+            }
+			this.getCheckedInfo.map(async (item) => {
+				let message = {};
+				let forwardMessage = {};
+
+				if (this.type === ContactChooseTypes.SendImage) {
+					message = JSON.parse(this.forwardMessage);
 				}
-			},
-			updateCheckedGroup(item) {
-				const idx = this.checkedGroupInfoList.findIndex((group) => group.groupID === item.groupID);
-				if (idx > -1) {
-					const tmpArr = [...this.checkedGroupInfoList];
-					tmpArr.splice(idx, 1);
-					this.checkedGroupInfoList = [...tmpArr];
-				} else {
-					this.checkedGroupInfoList = [...this.checkedGroupInfoList, item];
+                //手动看课
+				if (this.type === ContactChooseTypes.SendCourse) {
+					forwardMessage = JSON.parse(this.forwardMessage);
+					message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(),
+						forwardMessage);
 				}
-			},
-			userClick(item) {
-				this.cardInfo = {
-					...item
-				};
-				this.showConfirmModal = true;
-			},
-			async confirm() {
-				if (this.activeTab) {
-					this.activeTab = 0;
-					return;
+				if (this.type === ContactChooseTypes.ForWard) {
+					forwardMessage = JSON.parse(this.forwardMessage);
+					message = await IMSDK.asyncApi(IMMethods.CreateForwardMessage, IMSDK.uuid(), forwardMessage);
 				}
-				this.comfirmLoading = true;
-				if (this.type === ContactChooseTypes.GetList || this.type === ContactChooseTypes.GetListWithGroup) {
-					let pages = getCurrentPages();
-					let prevPage = pages[pages.length - 2];
-					let data = this.getCheckedInfo;
-					if (this.type === ContactChooseTypes.GetListWithGroup) {
-						data = [this.checkedUserInfoList, this.checkedGroupInfoList];
-					}
-					prevPage.$vm.getCheckedUsers(data);
-					this.comfirmLoading = false;
-
-					uni.navigateBack({
-						delta: 1
+				if (this.type === ContactChooseTypes.InviteMeeting) {
+					message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
+						data: JSON.stringify({
+							customType: CustomType.MeetingInvitation,
+							data: JSON.parse(this.forwardMessage)
+						}),
+						extension: '',
+						description: 'meeting'
 					});
-					return;
 				}
 
-				if (this.type === ContactChooseTypes.Invite) {
-					IMSDK.asyncApi(IMSDK.IMMethods.InviteUserToGroup, IMSDK.uuid(), {
-							groupID: this.groupID,
-							reason: '',
-							userIDList: this.getCheckedInfo.map((user) => user.userID)
-						})
-						.then(() => {
-							toastWithCallback('操作成功', () => uni.navigateBack());
-							this.comfirmLoading = false;
-						})
-						.catch(() => toastWithCallback('操作失败'));
-					return;
+				if (this.type === ContactChooseTypes.ShareCard) {
+					message = await IMSDK.asyncApi(IMMethods.CreateCardMessage, IMSDK.uuid(), this.cardInfo);
 				}
-				if (this.type === ContactChooseTypes.SendCourse) {
-					const courseItemData = uni.getStorageSync('courseItemData')
-					uni.removeStorageSync('courseItemData')
-					let companyUserInfo = uni.getStorageSync('companyUserInfo')
-					const userID = this.getCheckedInfo.map(item => {
-						return item.userID.replace(/^U/, '');
-					})
-					companyUserInfo = companyUserInfo && JSON.parse(companyUserInfo)
-					let paramsData = {
-						userIds: userID,
-						companyUserId: companyUserInfo.userId,
-						companyId: companyUserInfo.companyId,
-						periodId: courseItemData.periodId,
-						courseId: courseItemData.courseId,
-						courseName: courseItemData.courseName,
-						videoId: courseItemData.videoId,
-						title: courseItemData.title,
-						url: courseItemData.thumbnail,
-						projectId: courseItemData.projectId,
-						linkId: courseItemData.linkId
-					}
-					const result = await batchCreateCourseRecord(paramsData)
-					if (result.code !== 200) {
-						uni.showToast({
-							icon: 'none',
-							title: result.msg
-						})
-					}
+				if (this.type === ContactChooseTypes.MergeForWard) {
+					const { messageList, title, summaryList } = JSON.parse(this.mergeInfo);
+					message = await IMSDK.asyncApi(IMMethods.CreateMergerMessage, IMSDK.uuid(), {
+						messageList,
+						title,
+						summaryList
+					});
 				}
-				this.getCheckedInfo.map(async (item) => {
-					let message = {};
-					let forwardMessage = {};
-
-					if (this.type === ContactChooseTypes.SendImage) {
-						message = JSON.parse(this.forwardMessage);
-					}
-					if (this.type === ContactChooseTypes.SendCourse) {
-						forwardMessage = JSON.parse(this.forwardMessage);
-						message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(),
-							forwardMessage);
-					}
-
-					if (this.type === ContactChooseTypes.ForWard) {
-						forwardMessage = JSON.parse(this.forwardMessage);
-						message = await IMSDK.asyncApi(IMMethods.CreateForwardMessage, IMSDK.uuid(),
-							forwardMessage);
-					}
-					if (this.type === ContactChooseTypes.InviteMeeting) {
-						message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
-							data: JSON.stringify({
-								customType: CustomType.MeetingInvitation,
-								data: JSON.parse(this.forwardMessage)
-							}),
-							extension: '',
-							description: 'meeting'
-						});
-					}
-
-					if (this.type === ContactChooseTypes.ShareCard) {
-						message = await IMSDK.asyncApi(IMMethods.CreateCardMessage, IMSDK.uuid(), this
-							.cardInfo);
-					}
-					if (this.type === ContactChooseTypes.MergeForWard) {
-						const {
-							messageList,
-							title,
-							summaryList
-						} = JSON.parse(this.mergeInfo);
-						message = await IMSDK.asyncApi(IMMethods.CreateMergerMessage, IMSDK.uuid(), {
-							messageList,
-							title,
-							summaryList
-						});
-					}
-
-					if (this.type === ContactChooseTypes.BatchForWard) {
-						const {
-							messageList
-						} = JSON.parse(this.mergeInfo);
-						let arr = [];
-						const promiseArray = messageList.map(async (item) => {
-							const message = await IMSDK.asyncApi(IMMethods
-								.CreateForwardMessage, IMSDK.uuid(), item);
-							arr.push(message);
-						});
 
-						Promise.all(promiseArray).then(() => {
-								arr.map((message, idx) => {
-									let offlinePushInfo = {
-										title: message.senderNickname,
-										desc: parseMessageByType(message),
-										ex: "",
-										iOSPushSound: "",
-										iOSBadgeCount: true
-									};
-									IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
-											recvID: item.userID,
-											groupID: item.groupID,
-											message,
-											offlinePushInfo
-										}).then(({
-											data
-										}) => {
-											if (FileMessageTypes.includes(data
-													.contentType)) {
-												const path = this.storeCacheMap[
-													messageList[idx].clientMsgID].path;
-												if (path) {
-													this.$store.dispatch(
-														'user/addLocalPathToCache', {
-															key: data.clientMsgID,
-															path
-														});
-												}
-											}
-											if (!isInConversation) return;
-											this.$store.dispatch(
-												'message/updateOneMessage', {
-													message: data,
-													isSuccess: true
-												});
-										})
-										.catch(({
-											data,
-											errCode,
-											errMsg
-										}) => {
-											if (!isInConversation) return;
-											this.$store.dispatch(
-												'message/updateOneMessage', {
-													message: data,
-													type: UpdateMessageTypes.KeyWords,
-													keyWords: [{
-															key: 'status',
-															value: MessageStatus
-																.Failed
-														},
-														{
-															key: 'errCode',
-															value: errCode
-														}
-													]
-												});
+				if (this.type === ContactChooseTypes.BatchForWard) {
+					const { messageList } = JSON.parse(this.mergeInfo);
+					let arr = [];
+					const promiseArray = messageList.map(async (item) => {
+						const message = await IMSDK.asyncApi(IMMethods.CreateForwardMessage, IMSDK.uuid(), item);
+						arr.push(message);
+					});
+
+					Promise.all(promiseArray).then(() => {
+						// console.log(arr);
+						arr.map((message, idx) => {
+							let offlinePushInfo = {
+								title: message.senderNickname,
+								desc: parseMessageByType(message),
+								ex: "",
+								iOSPushSound: "",
+								iOSBadgeCount: true
+							};
+							IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
+								recvID: item.userID,
+								groupID: item.groupID,
+								message,
+								offlinePushInfo
+							}).then(({ data }) => {
+								if (FileMessageTypes.includes(data.contentType)) {
+									const path = this.storeCacheMap[messageList[idx].clientMsgID].path;
+									if (path) {
+										this.$store.dispatch('user/addLocalPathToCache', {
+											key: data.clientMsgID,
+											path
 										});
+									}
+								}
+								if (!isInConversation) return;
+								this.$store.dispatch('message/updateOneMessage', {
+									message: data,
+									isSuccess: true
 								});
 							})
-							.catch((error) => {
-								console.error('BatchForWard Error:', error);
-							});
-						return null;
-					}
-
-					const isInConversation = this.inCurrentConversation(item);
-					if (isInConversation) {
-						this.$store.dispatch('message/pushNewMessage', message);
-					}
-					let offlinePushInfo = {
-						title: message.senderNickname,
-						desc: parseMessageByType(message),
-						ex: "",
-						iOSPushSound: "",
-						iOSBadgeCount: true
-					};
-					IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
-							recvID: item.userID,
-							groupID: item.groupID,
-							message,
-							offlinePushInfo
-						})
-						.then(({
-							data
-						}) => {
-							if (this.type === ContactChooseTypes.ForWard && FileMessageTypes.includes(
-									data.contentType)) {
-								const path = this.storeCacheMap[forwardMessage.clientMsgID].path;
-								if (path) {
-									this.$store.dispatch('user/addLocalPathToCache', {
-										key: data.clientMsgID,
-										path
+								.catch(({ data, errCode, errMsg }) => {
+									if (!isInConversation) return;
+									this.$store.dispatch('message/updateOneMessage', {
+										message: data,
+										type: UpdateMessageTypes.KeyWords,
+										keyWords: [
+											{
+												key: 'status',
+												value: MessageStatus.Failed
+											},
+											{
+												key: 'errCode',
+												value: errCode
+											}
+										]
 									});
-								}
-							}
-							if (!isInConversation) return;
-							this.$store.dispatch('message/updateOneMessage', {
-								message: data,
-								isSuccess: true
-							});
-						})
-						.catch(({
-							data,
-							errCode,
-							errMsg
-						}) => {
-							if (!isInConversation) return;
-							this.$store.dispatch('message/updateOneMessage', {
-								message: data,
-								type: UpdateMessageTypes.KeyWords,
-								keyWords: [{
-										key: 'status',
-										value: MessageStatus.Failed
-									},
-									{
-										key: 'errCode',
-										value: errCode
-									}
-								]
-							});
+								});
 						});
-				});
-				uni.$emit(PageEvents.MutipleCheckUpdate, {
-					flag: false
-				});
-				toastWithCallback('转发成功', () => uni.navigateBack());
-				this.comfirmLoading = false;
-			},
-			orgConfirm(userList, groupList) {
-				const userArr = [...userList];
-				this.checkedGroupInfoList = [...groupList];
-				this.checkedUserInfoList = userArr;
-			},
-			async modalConfirm() {
-				const message = await IMSDK.asyncApi(IMMethods.CreateCardMessage, IMSDK.uuid(), this.cardInfo);
-				this.$store.dispatch('message/pushNewMessage', message);
+					})
+						.catch((error) => {
+							console.error('BatchForWard Error:', error);
+						});
+					return null;
+				}
+
+				const isInConversation = this.inCurrentConversation(item);
+				if (isInConversation) {
+					this.$store.dispatch('message/pushNewMessage', message);
+				}
 				let offlinePushInfo = {
 					title: message.senderNickname,
 					desc: parseMessageByType(message),
@@ -560,27 +491,34 @@
 					iOSBadgeCount: true
 				};
 				IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
-						recvID: this.storeCurrentConversation.userID,
-						groupID: this.storeCurrentConversation.groupID,
-						message,
-						offlinePushInfo
-					}).then(({
-						data
-					}) => {
+					recvID: item.userID,
+					groupID: item.groupID,
+					message,
+					offlinePushInfo
+				})
+					.then(({ data }) => {
+						if (this.type === ContactChooseTypes.ForWard && FileMessageTypes.includes(data.contentType)) {
+							const path = this.storeCacheMap[forwardMessage.clientMsgID].path;
+							if (path) {
+								this.$store.dispatch('user/addLocalPathToCache', {
+									key: data.clientMsgID,
+									path
+								});
+							}
+						}
+						if (!isInConversation) return;
 						this.$store.dispatch('message/updateOneMessage', {
 							message: data,
 							isSuccess: true
 						});
 					})
-					.catch(({
-						data,
-						errCode,
-						errMsg
-					}) => {
+					.catch(({ data, errCode, errMsg }) => {
+						if (!isInConversation) return;
 						this.$store.dispatch('message/updateOneMessage', {
 							message: data,
 							type: UpdateMessageTypes.KeyWords,
-							keyWords: [{
+							keyWords: [
+								{
 									key: 'status',
 									value: MessageStatus.Failed
 								},
@@ -591,122 +529,179 @@
 							]
 						});
 					});
-				// this.$nextTick(() => uni.$emit(PageEvents.ScrollToBottom, parsedMessage.clientMsgID))
-				toastWithCallback('发送成功', () => {
-					let pages = getCurrentPages();
-					let prevPage = pages[pages.length - 2];
-					prevPage.$vm.scrollToBottom();
-					this.activeTab = 0;
-					uni.navigateBack({
-						delta: 1
+			});
+			uni.$emit(PageEvents.MutipleCheckUpdate, { flag: false });
+			console.log()
+			toastWithCallback('转发成功', () => uni.navigateBack());
+			this.comfirmLoading = false;
+		},
+		orgConfirm(userList, groupList) {
+			const userArr = [...userList];
+			this.checkedGroupInfoList = [...groupList];
+			this.checkedUserInfoList = userArr;
+			console.log('orgConfirm', this.checkedUserInfoList);
+		},
+		async modalConfirm() {
+			const message = await IMSDK.asyncApi(IMMethods.CreateCardMessage, IMSDK.uuid(), this.cardInfo);
+			this.$store.dispatch('message/pushNewMessage', message);
+			let offlinePushInfo = {
+				title: message.senderNickname,
+				desc: parseMessageByType(message),
+				ex: "",
+				iOSPushSound: "",
+				iOSBadgeCount: true
+			};
+			IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
+				recvID: this.storeCurrentConversation.userID,
+				groupID: this.storeCurrentConversation.groupID,
+				message,
+				offlinePushInfo
+			}).then(({ data }) => {
+				this.$store.dispatch('message/updateOneMessage', {
+					message: data,
+					isSuccess: true
+				});
+			})
+				.catch(({ data, errCode, errMsg }) => {
+					this.$store.dispatch('message/updateOneMessage', {
+						message: data,
+						type: UpdateMessageTypes.KeyWords,
+						keyWords: [
+							{
+								key: 'status',
+								value: MessageStatus.Failed
+							},
+							{
+								key: 'errCode',
+								value: errCode
+							}
+						]
 					});
 				});
-				this.showConfirmModal = false;
-			},
-			inCurrentConversation(item) {
-				if (item.userID) {
-					return item.userID === this.storeCurrentConversation.userID;
-				}
-				return item.groupID === this.storeCurrentConversation.groupID;
-			}
-		},
-		onBackPress() {
-			if (this.activeTab) {
+			// this.$nextTick(() => uni.$emit(PageEvents.ScrollToBottom, parsedMessage.clientMsgID))
+			toastWithCallback('发送成功', () => {
+				let pages = getCurrentPages();
+				let prevPage = pages[pages.length - 2];
+				prevPage.$vm.scrollToBottom();
 				this.activeTab = 0;
-				return true;
+				uni.navigateBack({
+					delta: 1
+				});
+			});
+			this.showConfirmModal = false;
+		},
+		inCurrentConversation(item) {
+			if (item.userID) {
+				return item.userID === this.storeCurrentConversation.userID;
 			}
-			return false;
+			return item.groupID === this.storeCurrentConversation.groupID;
+		}
+	},
+	onBackPress() {
+
+		if (this.activeTab && this.storeSelfInfo.members.length) {
+			this.activeTab = 0;
+			return true;
 		}
-	};
+		return false;
+	}
+};
 </script>
 
 <style lang="scss" scoped>
-	::v-deep .u-popup {
-		flex: none;
+::v-deep .u-popup {
+	flex: none;
+}
+
+.contact_choose_container {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+
+	.icon_back {
+		font-weight: 400;
+		color: #757575;
+		padding-left: 12px;
 	}
 
-	.contact_choose_container {
-		height: 100vh;
-		display: flex;
-		flex-direction: column;
-
-		.search_bar_wrap {
-			height: 34px;
-			padding: 12px 22px;
-		}
+	.search_bar_wrap {
+		padding: 8px 24rpx;
+		background: #fff;
+	}
 
-		.tab_container {
-			@include colBox(false);
-			flex: 1;
-			overflow: hidden;
+	.tab_container {
+		@include colBox(false);
+		flex: 1;
+		overflow: hidden;
 
-			.setting_item {
-				padding: 32rpx 36rpx;
-			}
+		.setting_item {
+			padding: 32rpx 36rpx;
+		}
 
-			.title {
-				height: 60rpx;
-				display: flex;
-				justify-content: start;
-				align-items: center;
-				// padding: 16rpx 8rpx;
-				background: #f8f9fa;
-				color: #8e9ab0;
-				font-size: 24rpx;
-			}
+		.title {
+			height: 60rpx;
+			display: flex;
+			justify-content: start;
+			align-items: center;
+			// padding: 16rpx 8rpx;
+			background: #f8f9fa;
+			color: #8e9ab0;
+			font-size: 24rpx;
+		}
 
-			.title {
-				padding: 16rpx 8rpx;
-				background: #f8f9fa;
-				color: #8e9ab0;
-				font-size: 24rpx;
-			}
+		.title {
+			padding: 16rpx 8rpx;
+			background: #f8f9fa;
+			color: #8e9ab0;
+			font-size: 24rpx;
+		}
 
-			.tabs_bar {
-				@include vCenterBox();
-				justify-content: space-evenly;
+		.tabs_bar {
+			@include vCenterBox();
+			justify-content: space-evenly;
 
-				.tab_item {
-					@include colBox(false);
-					align-items: center;
+			.tab_item {
+				@include colBox(false);
+				align-items: center;
 
-					image {
-						width: 50px;
-						height: 50px;
-					}
+				image {
+					width: 50px;
+					height: 50px;
 				}
 			}
+		}
 
-			.tab_pane {
-				display: flex;
-				flex-direction: column;
-				flex: 1;
-				overflow: hidden;
-
-				.member_list {
-					flex: 1;
-					height: 80% !important;
+		.tab_pane {
+			display: flex;
+			flex-direction: column;
+			flex: 1;
+			overflow: hidden;
+			margin-top: 20rpx;
 
-					::v-deep uni-scroll-view {
-						max-height: 100% !important;
-					}
-				}
+			.member_list {
+				flex: 1;
+				height: 80% !important;
 
-				.user_list {
-					height: 100% !important;
+				::v-deep uni-scroll-view {
+					max-height: 100% !important;
 				}
 			}
 
-			.member_anchor {
-				background-color: #f8f8f8 !important;
-				border: none !important;
+			.user_list {
+				height: 100% !important;
 			}
+		}
 
-			.orgnization_row {
-				border-top: 24px solid #f9f9fb;
-				box-sizing: border-box;
-				margin-top: 0;
-			}
+		.member_anchor {
+			background-color: #f8f8f8 !important;
+			border: none !important;
+		}
+
+		.orgnization_row {
+			border-top: 24px solid #f9f9fb;
+			box-sizing: border-box;
+			margin-top: 0;
 		}
 	}
-</style>
+}
+</style>

+ 60 - 46
pages_im/pages/common/createGroup/index.vue

@@ -3,47 +3,36 @@
     <custom-nav-bar title="发起群聊" />
     <u-toast ref="uToast"></u-toast>
     <view class="main">
-      <view class="group_base_info">
-        <my-avatar
-          @click="chooseImage"
-          :isGroup="true"
-          :src="groupFaceUrl"
-          size="44"/>
-        <u--input
-          placeholder="取个群名方便后续搜索"
-          border="none"
-          maxlength="16"
-          v-model="groupName"></u--input>
+      <view class="member_row">
+        <view class="desc_title">
+          群名称
+        </view>
+        <view class="member_list">
+          <my-avatar @click="chooseImage" :isGroup="true" :src="groupFaceUrl" size="48" />
+          <image class="edit_img" src="/pages_im/static/images/chat/icon_edit.png" alt="" />
+          <u-input v-model="groupName" disabledColor="transparent" maxlength="16" placeholder="取个群名方便后续搜索" clearable
+            :border="false">
+          </u-input>
+        </view>
       </view>
-
       <view class="member_row" @click="toChooseMember">
         <view class="desc_title">
           <text>群成员</text>
-          <text>{{ `${checkedMemberList.length}人` }}</text>
+          <text class="number" v-if="checkedMemberList.length">{{ `${checkedMemberList.length}人` }}</text>
         </view>
-		
         <view class="member_list">
-          <view
-            v-for="member in checkedMemberList.slice(0, 5)"
-            :key="member.userID"
-            class="member_item">
-            <my-avatar :src="member.faceURL"   :desc="member.nickname" size="42" />
+          <image class="add_img" src="/pages_im/static/images/group_setting_invite.png" alt="" />
+          <view v-for="member in checkedMemberList.slice(0, 5)" :key="member.userID" class="member_item">
+            <my-avatar :src="member.faceURL" :desc="member.nickname" size="36" />
           </view>
         </view>
       </view>
     </view>
-	
     <view class="action_bar">
-      <u-button
-        :loading="createLoading"
-        :disabled="disabledCreate"
-        @click="complateCreate"
-        type="primary"
-        text="完成创建">
-		
-		</u-button>
+      <u-button :loading="createLoading" :disabled="disabledCreate" @click="complateCreate" text="完成创建">
+      </u-button>
     </view>
-	
+
   </view>
 </template>
 
@@ -82,16 +71,17 @@ export default {
     this.checkedMemberList = checkedMemberList
       ? JSON.parse(checkedMemberList)
       : [];
+    console.log(this.checkedMemberList,"-------1111-----");
   },
   methods: {
     toChooseMember() {
       uni.navigateTo({
-        url: `/pages_im/pages/common/contactChoose/index?type=${
-          ContactChooseTypes.GetList
-        }&checkedUserInfoList=${JSON.stringify(this.checkedMemberList)}`,
+        url: `/pages_im/pages/common/contactChoose/index?type=${ContactChooseTypes.GetList
+          }&checkedUserInfoList=${encodeURIComponent(JSON.stringify(this.checkedMemberList))}`,
       });
     },
     complateCreate() {
+      console.log(this.checkedMemberList,"-------2222-----", this.checkedMemberList.map((member) => member.userID));
       this.createLoading = true;
       const options = {
         adminUserIDs: [],
@@ -102,7 +92,7 @@ export default {
           faceURL: this.groupFaceUrl,
         },
       };
-     
+
       IMSDK.asyncApi(IMSDK.IMMethods.CreateGroup, IMSDK.uuid(), options)
         .then(({ data }) => {
           toastWithCallback("创建成功", () =>
@@ -149,7 +139,7 @@ export default {
         },
         fail: function (err) {
           console.log(err)
-          if(err.errMsg === 'chooseImage:fail cancel') return
+          if (err.errMsg === 'chooseImage:fail cancel') return
           uni.$u.toast("上传失败");
         },
       });
@@ -158,43 +148,59 @@ export default {
 };
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 .create_group_container {
   @include colBox(false);
   height: 100vh;
   background-color: #f6f6f6;
 
+  .edit_img {
+    width: 36rpx;
+    height: 36rpx;
+    margin-left: 24rpx;
+    margin-right: 10rpx;
+  }
+
+  .add_img {
+    width: 36px;
+    height: 36px;
+    margin-right: 10rpx
+  }
+
   .main {
     display: flex;
     flex-direction: column;
     flex: 1;
   }
 
-  .group_base_info {
-    @include vCenterBox();
-    padding: 44rpx;
-    background-color: #fff;
-    margin: 36rpx 0;
 
-    .u-input {
-      margin-left: 48rpx;
-    }
-  }
 
   .member_row {
-    padding: 44rpx;
+
+    padding: 24rpx 28rpx;
+    margin: 0 24rpx;
+    margin-top: 24rpx;
+    border-radius: 16rpx;
     background-color: #fff;
     color: #999;
 
     .desc_title {
       @include vCenterBox();
       justify-content: space-between;
+      font-weight: 400;
+      font-size: 16px;
+      color: #222222;
+
+      .number {
+        color: #757575;
+      }
     }
 
     .member_list {
       display: flex;
       flex-wrap: wrap;
       margin-top: 24rpx;
+      align-items: center;
 
       .member_item {
         @include colBox(false);
@@ -212,7 +218,15 @@ export default {
 
   .action_bar {
     background-color: #fff;
-    padding: 44rpx 44rpx;
+    padding: 20rpx 24rpx;
+    font-weight: 400;
+    font-size: 16px;
+
+    uni-button {
+      border-radius: 16rpx !important;
+      background-color: #FF5C03 !important;
+      color: #fff !important;
+    }
   }
 }
 </style>

+ 31 - 25
pages_im/pages/common/detailsFileds/index.vue

@@ -1,30 +1,32 @@
 <template>
 	<view class="details_container">
 		<custom-nav-bar title="个人资料" />
-
-		<view class="info_list">
-			<user-info-row-item class="info_item" lable="头像" arrow>
-				<my-avatar :src="sourceInfo.faceURL" :desc="sourceInfo.nickname" size="26" />
-			</user-info-row-item>
-			<user-info-row-item class="info_item" lable="昵称" arrow>
-				<text class="right_content">{{ sourceInfo.nickname }}</text>
-			</user-info-row-item>
-			<user-info-row-item class="info_item" lable="性别" arrow>
-				<text class="right_content">{{ getGender }}</text>
-			</user-info-row-item>
-			<user-info-row-item class="info_item" lable="生日" arrow>
-				<text class="right_content">{{ getBirthStr }}</text>
-			</user-info-row-item>
-		</view>
-
-		<view class="info_list">
-			<user-info-row-item class="info_item" lable="手机号码" arrow>
-				<text class="right_content">{{ sourceInfo.phoneNumber || '-' }}</text>
-			</user-info-row-item>
-			<user-info-row-item class="info_item" lable="邮箱" arrow>
-				<text class="right_content">{{ sourceInfo.email || '-' }}</text>
-			</user-info-row-item>
-		</view>
+         <view class="row_wrap">
+			 <view class="info_list">
+			 	<user-info-row-item class="info_item" lable="头像" arrow>
+			 		<my-avatar :src="sourceInfo.faceURL" :desc="sourceInfo.nickname" size="26" />
+			 	</user-info-row-item>
+			 	<user-info-row-item class="info_item" lable="昵称" arrow>
+			 		<text class="right_content">{{ sourceInfo.nickname }}</text>
+			 	</user-info-row-item>
+			 	<user-info-row-item class="info_item" lable="性别" arrow>
+			 		<text class="right_content">{{ getGender }}</text>
+			 	</user-info-row-item>
+			 	<user-info-row-item class="info_item" lable="生日" arrow>
+			 		<text class="right_content">{{ getBirthStr }}</text>
+			 	</user-info-row-item>
+			 </view>
+			 
+			 <view class="info_list">
+			 	<user-info-row-item class="info_item" lable="手机号码" arrow>
+			 		<text class="right_content">{{ sourceInfo.phoneNumber || '-' }}</text>
+			 	</user-info-row-item>
+			 	<user-info-row-item class="info_item" lable="邮箱" arrow>
+			 		<text class="right_content">{{ sourceInfo.email || '-' }}</text>
+			 	</user-info-row-item>
+			 </view>
+		 </view>
+	
 	</view>
 </template>
 
@@ -73,7 +75,11 @@ export default {
 	@include colBox(false);
 	height: 100vh;
 	background-color: #f6f6f6;
-
+.row_wrap {
+		flex: 1;
+	    overflow-y: auto;
+		 touch-action: none; // 禁止触摸时的默认滚动/回弹行为
+	}
 	.info_list {
 		border-radius: 6px;
 		overflow: hidden;

+ 6 - 7
pages_im/pages/common/globalChatLosPreview/index.vue

@@ -1,12 +1,12 @@
 <template>
   <view class="page_container">
     <view class="search_bar_wrap">
-      <image
+      <img
         @click="back"
         class="back_icon"
-       
+        width="12"
+        height="20"
         src="../../../static/images/common_left_arrow.png"
-		style="width: 12px;height: 20px;"
         alt=""
         srcset=""
       />
@@ -79,7 +79,7 @@ export default {
   methods: {
     clickItem() {
       if (this.chatData.conversationType === 3) {
-        uni.$u.route("/pages_im/pages/common/groupCard/index", {
+        uni.$u.route("/pages/common/groupCard/index", {
           sourceID: this.chatData.groupID,
         });
         return;
@@ -88,7 +88,7 @@ export default {
         const ids = this.chatData.conversationID.split("_");
         console.log(ids, this.storeSelfInfo.userID);
         const id = ids[1] === this.storeSelfInfo.userID ? ids[2] : ids[1];
-        uni.$u.route("/pages_im/pages/common/userCard/index", {
+        uni.$u.route("/pages/common/userCard/index", {
           sourceID: id,
         });
         return;
@@ -98,7 +98,7 @@ export default {
       uni.navigateBack();
     },
     jumpToMessage(message) {
-      uni.$u.route("/pages_im/pages/common/previewHistoryMessage/index", {
+      uni.$u.route("../../../pages/common/previewHistoryMessage/index", {
         conversationData: JSON.stringify({
           ...this.chatData,
           messageList: undefined,
@@ -127,7 +127,6 @@ export default {
 
     .back_icon {
       margin-right: 26rpx;
-	
     }
   }
 

+ 118 - 159
pages_im/pages/common/globalSearch/components/ComprehensivePanel.vue

@@ -1,182 +1,141 @@
 <template>
-  <view class="comprehensive_container">
-    <view v-show="data.contacts.length > 0" class="comprehensive_row">
-      <view class="row_title">
-        <text class="row_title_text">联系人</text>
-      </view>
-      <global-result-item
-        v-for="user in data.contacts.slice(0, 2)"
-        :item="user"
-        :key="user.userID"
-      />
-      <view
-        v-show="data.contacts.length > 2"
-        @click="toggleTab(1)"
-        class="more"
-      >
-        <text>查看更多相关联系人</text>
-        <view class="more_right">
-          <u-icon name="arrow-right" color="#999" size="18" />
-        </view>
-      </view>
-    </view>
+	<view class="comprehensive_container">
+		<view v-show="data.contacts.length > 0" class="comprehensive_row">
+			<view class="row_title">
+				<text class="row_title_text">联系人</text>
+			</view>
+			<global-result-item v-for="user in data.contacts.slice(0, 2)" :item="user" :key="user.userID" />
+			<view v-show="data.contacts.length > 2" @click="toggleTab(1)" class="more">
+				<text>查看更多相关联系人</text>
+				<view class="more_right">
+					<u-icon name="arrow-right" color="#999" size="18" />
+				</view>
+			</view>
+		</view>
 
-    <view v-show="data.groups.length > 0" class="comprehensive_row">
-      <view class="row_title">
-        <text class="row_title_text">群组</text>
-      </view>
-      <global-result-item
-        v-for="group in data.groups.slice(0, 2)"
-        :item="group"
-        :key="group.groupID"
-      />
-      <view v-show="data.groups.length > 2" @click="toggleTab(2)" class="more">
-        <text>查看更多相关群组</text>
-        <view class="more_right">
-          <u-icon name="arrow-right" color="#999" size="18" />
-        </view>
-      </view>
-    </view>
+		<view v-show="data.groups.length > 0" class="comprehensive_row">
+			<view class="row_title">
+				<text class="row_title_text">群组</text>
+			</view>
+			<global-result-item v-for="group in data.groups.slice(0, 2)" :item="group" :key="group.groupID" />
+			<view v-show="data.groups.length > 2" @click="toggleTab(2)" class="more">
+				<text>查看更多相关群组</text>
+				<view class="more_right">
+					<u-icon name="arrow-right" color="#999" size="18" />
+				</view>
+			</view>
+		</view>
 
-    <view v-show="data.chatLogs.length > 0" class="comprehensive_row">
-      <view class="row_title">
-        <text class="row_title_text">聊天记录</text>
-      </view>
-      <global-result-item
-        v-for="logs in data.chatLogs.slice(0, 2)"
-        :item="logs"
-        :key="logs.conversationID"
-      />
-      <view
-        v-show="data.chatLogs.length > 2"
-        @click="toggleTab(3)"
-        class="more"
-      >
-        <text>查看更多相关聊天记录</text>
-        <view class="more_right">
-          <u-icon name="arrow-right" color="#999" size="18" />
-        </view>
-      </view>
-    </view>
+		<view v-show="data.chatLogs.length > 0" class="comprehensive_row">
+			<view class="row_title">
+				<text class="row_title_text">聊天记录</text>
+			</view>
+			<global-result-item v-for="logs in data.chatLogs.slice(0, 2)" :item="logs" :key="logs.conversationID" />
+			<view v-show="data.chatLogs.length > 2" @click="toggleTab(3)" class="more">
+				<text>查看更多相关聊天记录</text>
+				<view class="more_right">
+					<u-icon name="arrow-right" color="#999" size="18" />
+				</view>
+			</view>
+		</view>
 
-    <view v-show="data.documents.length > 0" class="comprehensive_row">
-      <view class="row_title">
-        <text class="row_title_text">文件</text>
-      </view>
-      <global-result-item
-        v-for="document in data.documents.slice(0, 2)"
-        :item="document"
-        :key="document.clientMsgID"
-        isFile
-      />
-    </view>
-    <view v-show="data.documents.length > 2" @click="toggleTab(4)" class="more">
-      <text>查看更多相关文档</text>
-      <view class="more_right">
-        <u-icon name="arrow-right" color="#999" size="18" />
-      </view>
-    </view>
+		<view v-show="data.documents.length > 0" class="comprehensive_row">
+			<view class="row_title">
+				<text class="row_title_text">文件</text>
+			</view>
+			<global-result-item v-for="document in data.documents.slice(0, 2)" :item="document" :key="document.clientMsgID" isFile />
+		</view>
+		<view v-show="data.documents.length > 2" @click="toggleTab(4)" class="more">
+			<text>查看更多相关文档</text>
+			<view class="more_right">
+				<u-icon name="arrow-right" color="#999" size="18" />
+			</view>
+		</view>
 
-    <view v-show="data.org.length > 0" class="comprehensive_row">
-      <view class="row_title">
-        <text class="row_title_text">组织成员</text>
-      </view>
-      <global-result-item
-        v-for="user in data.org.slice(0, 2)"
-        :item="user"
-        :key="user.userID"
-      />
-      <view
-        v-show="storeSelfInfo.members.length && data.org.length > 2"
-        @click="toggleTab(5)"
-        class="more"
-      >
-        <text>查看更多组织成员</text>
-        <view class="more_right">
-          <u-icon name="arrow-right" color="#999" size="18" />
-        </view>
-      </view>
-    </view>
-
-    <u-empty
-      icon="../../../../static/images/global_search_empty.png"
-      v-show="isEmpty"
-      text="没有更多搜索结果"
-    />
-  </view>
+		<view v-show="data.org.length > 0" class="comprehensive_row">
+			<view class="row_title">
+				<text class="row_title_text">组织成员</text>
+			</view>
+			<global-result-item v-for="user in data.org.slice(0, 2)" :item="user" :key="user.userID" />
+			<view v-show="storeSelfInfo.members.length && data.org.length > 2" @click="toggleTab(5)" class="more">
+				<text>查看更多组织成员</text>
+				<view class="more_right">
+					<u-icon name="arrow-right" color="#999" size="18" />
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-import { mapGetters } from "vuex";
-import GlobalResultItem from "./GlobalResultItem.vue";
+import { mapGetters } from 'vuex';
+import GlobalResultItem from './GlobalResultItem.vue';
 export default {
-  components: {
-    GlobalResultItem,
-  },
-  props: {
-    data: Object,
-  },
-  data() {
-    return {};
-  },
-  computed: {
-    ...mapGetters(["storeSelfInfo"]),
-    isEmpty() {
-      return (
-        Object.values(this.data).find((item) => item.length > 0) === undefined
-      );
-    },
-  },
-  methods: {
-    toggleTab(tab) {
-      this.$emit("toggleTab", {
-        index: tab,
-      });
-    },
-  },
+	components: {
+		GlobalResultItem
+	},
+	props: {
+		data: Object
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		...mapGetters(['storeSelfInfo']),
+		isEmpty() {
+			return Object.values(this.data).find((item) => item.length > 0) === undefined;
+		}
+	},
+	methods: {
+		toggleTab(tab) {
+			this.$emit('toggleTab', {
+				index: tab
+			});
+		}
+	}
 };
 </script>
 
 <style lang="scss">
 .comprehensive_container {
-  flex: 1;
-  overflow-y: auto;
+	flex: 1;
+	overflow-y: auto;
 
-  .comprehensive_row {
-    margin: 24rpx;
-    background-color: #fff;
-    border-radius: 6px;
-    overflow: hidden;
+	.comprehensive_row {
+		margin: 24rpx;
+		background-color: #fff;
+		border-radius: 6px;
+		overflow: hidden;
 
-    .row_title {
-      display: flex;
-      justify-content: space-between;
-      padding: 24rpx 36rpx 0;
-      font-size: 24rpx;
+		.row_title {
+			display: flex;
+			justify-content: space-between;
+			padding: 24rpx 36rpx 0;
+			font-size: 24rpx;
 
-      &_text {
-        color: $u-tips-color;
-      }
+			&_text {
+				color: $u-tips-color;
+			}
 
-      // .more {
-      // 	color: #1B72EC;
-      // }
-    }
+			// .more {
+			// 	color: #1B72EC;
+			// }
+		}
 
-    .more {
-      @include btwBox();
-      padding: 20rpx 36rpx 20rpx;
-      border-top: 1px solid rgba(153, 153, 153, 0.2);
-      color: $uni-color-primary;
+		.more {
+			@include btwBox();
+			padding: 20rpx 36rpx 20rpx;
+			border-top: 1px solid rgba(153, 153, 153, 0.2);
+			color: $uni-color-primary;
 
-      .more_right {
-        @include vCenterBox();
-      }
-    }
-  }
+			.more_right {
+				@include vCenterBox();
+			}
+		}
+	}
 
-  .u-empty {
-    margin-top: 20vh !important;
-  }
+	.u-empty {
+		margin-top: 20vh !important;
+	}
 }
 </style>

+ 132 - 140
pages_im/pages/common/globalSearch/components/GlobalResultItem.vue

@@ -1,159 +1,151 @@
 <template>
-  <view @click="clickItem" class="result_item">
-    <view class="wrap">
-      <my-avatar
-        :src="item.faceURL || item.senderFaceUrl"
-        :desc="item.remark || item.nickname || item.showName || item.senderNickname"
-        :isGroup="item.groupID !== undefined && !item.senderNickname"
-        size="42"
-        v-if="!isFile"
-      />
-      <my-avatar
-        v-else
-        src="../../../../static/images/chating_message_file.png"
-        size="42"
-      />
-      <view class="result_item_details">
-        <view class="title">
-          <view class="name">
-            {{
-              item.remark ||
-              item.nickname ||
-              item.groupName ||
-              item.showName ||
-              item.senderNickname
-            }}
-          </view>
-          <text class="desc">{{ item.position }}</text>
-        </view>
-        <view
-          class="sub_title"
-          v-if="item.company || item.latestMsg || messageContent"
-        >
-          {{ item.company || item.latestMsg || messageContent }}
-        </view>
-      </view>
-    </view>
-    <view v-if="item.sendTime !== undefined" class="time">
-      {{ getLatestTime }}
-    </view>
-  </view>
+	<view @click="clickItem" class="result_item">
+		<view class="wrap">
+			<my-avatar
+				:src="item.faceURL || item.senderFaceUrl"
+				:desc="item.remark || item.nickname || item.showName || item.senderNickname"
+				:isGroup="item.groupID !== undefined && !item.senderNickname"
+				size="42"
+				v-if="!isFile"
+			/>
+			<my-avatar v-else src="../../../../static/images/chating_message_file.png" size="42" />
+			<view class="result_item_details">
+				<view class="title">
+					<view class="name">
+						{{ item.remark || item.nickname || item.groupName || item.showName || item.senderNickname }}
+					</view>
+					<text class="desc">{{ item.position }}</text>
+				</view>
+				<view class="sub_title" v-if="item.company || item.latestMsg || messageContent">
+					{{ item.company || item.latestMsg || messageContent }}
+				</view>
+			</view>
+		</view>
+		<view v-if="item.sendTime !== undefined" class="time">
+			{{ getLatestTime }}
+		</view>
+	</view>
 </template>
 
 <script>
-import MyAvatar from "../../../../components/MyAvatar/index.vue";
-import { formatConversionTime, parseMessageByType } from "../../../../util/imCommon";
+import MyAvatar from '../../../../components/MyAvatar/index.vue';
+import { formatConversionTime, parseMessageByType } from '../../../../util/imCommon';
 export default {
-  name: "GlobalResultItem",
-  components: {
-    MyAvatar,
-  },
-  props: {
-    item: Object,
-    isPreview: Boolean,
-    isFile: {
-      type: Boolean,
-      default: false,
-    },
-  },
-  data() {
-    return {};
-  },
-  computed: {
-    getLatestTime() {
-      return formatConversionTime(this.item.sendTime);
-    },
-    messageContent() {
-      if (this.item.clientMsgID) {
-        return parseMessageByType(this.item);
-      }
-      return "";
-    },
-  },
-  methods: {
-    clickItem() {
-      if (this.isPreview) {
-        this.$emit("click", this.item);
-        return;
-      }
-      if (this.item.userID) {
-        uni.$u.route("/pages_im/pages/common/userCard/index", {
-          sourceID: this.item.userID,
-        });
-        return;
-      }
-      if (this.item.groupName) {
-        uni.$u.route("/pages_im/pages/common/groupCard/index", {
-          sourceID: this.item.groupID,
-        });
-        return;
-      }
-      if (this.item.conversationID) {
-        uni.$u.route("/pages_im/pages/common/globalChatLosPreview/index", {
-          chatData: encodeURIComponent(JSON.stringify(this.item)),
-        });
-        return;
-      }
-      if (this.item.fileElem) {
-        uni.$u.route("/pages_im/pages/conversation/previewFile/index", {
-          fileEl: JSON.stringify(this.item.fileElem),
-          msgID: this.item.clientMsgID,
-        });
-      }
-    },
-  },
+	name: 'GlobalResultItem',
+	components: {
+		MyAvatar
+	},
+	props: {
+		item: Object,
+		isPreview: Boolean,
+		isFile: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		getLatestTime() {
+			return formatConversionTime(this.item.sendTime);
+		},
+		messageContent() {
+			if (this.item.clientMsgID) {
+				return parseMessageByType(this.item, this.$store.getters.storeCurrentUserID);
+			}
+			return '';
+		}
+	},
+	methods: {
+		clickItem() {
+			if (this.isPreview) {
+				this.$emit('click', this.item);
+				return;
+			}
+			if (this.item.userID) {
+				let url='/pages_im/pages/common/userCard/index?sourceID='+this.item.userID+"&isFromSearch=1";
+				console.log("qxj userCard item",this.item);
+				uni.navigateTo({
+					url
+				});
+				// uni.$u.route('../../../../pages/common/userCard/index', {
+				// 	sourceID: this.item.userID
+				// });
+				return;
+			}
+			if (this.item.groupName) {
+				uni.$u.route('/pages_im/pages/common/groupCard/index', {
+					sourceID: this.item.groupID
+				});
+				return;
+			}
+			if (this.item.conversationID) {
+				uni.$u.route('/pages_impages/common/globalChatLosPreview/index', {
+					chatData: encodeURIComponent(JSON.stringify(this.item))
+				});
+				return;
+			}
+			if (this.item.fileElem) {
+				uni.$u.route('/pages_im/pages/conversation/previewFile/index', {
+					fileEl: JSON.stringify(this.item.fileElem),
+					msgID: this.item.clientMsgID
+				});
+			}
+		}
+	}
 };
 </script>
 
 <style lang="scss" scoped>
 .result_item {
-  display: flex;
-  padding: 24rpx 36rpx;
-  color: $uni-text-color;
-  position: relative;
-  background-color: #fff;
+	display: flex;
+	padding: 24rpx 36rpx;
+	color: $uni-text-color;
+	position: relative;
+	background-color: #fff;
 
-  .wrap {
-    @include vCenterBox();
-    flex: 1;
-    overflow: hidden;
-  }
+	.wrap {
+		@include vCenterBox();
+		flex: 1;
+		overflow: hidden;
+	}
 
-  &_details {
-    flex: 1;
-    margin-left: 20rpx;
-    overflow: hidden;
+	&_details {
+		flex: 1;
+		margin-left: 20rpx;
+		overflow: hidden;
 
-    .title {
-      display: flex;
-      align-items: baseline;
+		.title {
+			display: flex;
+			align-items: baseline;
 
-      .name {
-        @include nomalEllipsis();
-        max-width: 280rpx;
-        color: $uni-text-color;
-      }
+			.name {
+				@include nomalEllipsis();
+				max-width: 280rpx;
+				color: $uni-text-color;
+			}
 
-      .desc {
-        @include nomalEllipsis();
-        margin-left: 20rpx;
-        font-size: 24rpx;
-        color: #666;
-      }
-    }
+			.desc {
+				@include nomalEllipsis();
+				margin-left: 20rpx;
+				font-size: 24rpx;
+				color: #666;
+			}
+		}
 
-    .sub_title {
-      @include nomalEllipsis();
-      font-size: 24rpx;
-      color: #999;
-      max-width: 360rpx;
-      margin-top: 8rpx;
-    }
-  }
+		.sub_title {
+			@include nomalEllipsis();
+			font-size: 24rpx;
+			color: #999;
+			max-width: 360rpx;
+			margin-top: 8rpx;
+		}
+	}
 
-  .time {
-    font-size: 24rpx;
-    color: #adadad;
-  }
+	.time {
+		font-size: 24rpx;
+		color: #adadad;
+	}
 }
 </style>

+ 228 - 248
pages_im/pages/common/globalSearch/index.vue

@@ -1,271 +1,251 @@
 <template>
-  <view class="page_container">
-    <view class="search_bar_wrap">
-      <image
-        @click="back"
-        class="back_icon"
-        style="width: 12px;height: 20px;"
-        src="../../../static/images/common_left_arrow.png"
-        alt=""
-        />
-      <u-search
-        class="search_bar"
-        shape="square"
-        placeholder="搜索"
-        v-model="keyword"
-        :show-action="false"
-        @search="enterSearch"/>
-    </view>
+	<view class="page_container">
+		<view class="search_bar_wrap">
+			<image @click="back" class="back_icon" style="width: 24rpx;height: 40rpx;" src="../../../static/images/common_left_arrow.png" alt="" srcset="" />
+			<u-search class="search_bar" shape="square" placeholder="搜索" v-model="keyword" :show-action="false" @search="enterSearch" />
+		</view>
 
-    <u-tabs
-      class="top_tab"
-      lineColor="#1B72EC"
-      :activeStyle="{ color: '#1B72EC' }"
-      :inactiveStyle="{ color: '#ADADAD' }"
-      :scrollable="false"
-      :list="tabList.slice(0, storeSelfInfo.members.length ? 6 : 5)"
-      :current="currentTab"
-      @click="clickTab"
-    ></u-tabs>
+		<u-tabs
+			class="top_tab"
+			:lineColor="qconfig.mainThemeHColor"
+			:activeStyle="{ color: qconfig.mainThemeHColor }"
+			:inactiveStyle="{ color: '#ADADAD' }"
+			:scrollable="false"
+			:list="tabList.slice(0, (storeSelfInfo && storeSelfInfo.members || []).length ? 6 : 5)"
+			:current="currentTab"
+			@click="clickTab"></u-tabs>
 
-    <comprehensive-panel
-      v-show="currentTab === 0"
-      :data="dataSource"
-      @toggleTab="clickTab"
-    />
+		<comprehensive-panel v-show="currentTab === 0" :data="dataSource" @toggleTab="clickTab" />
 
-    <generic-panel
-      v-show="currentTab > 0 && genericDataSource.length > 0"
-      :generic-list="genericDataSource"
-      @scrolltolower="scrolltolower"
-      :isFile="currentTab === 4"
-    />
+		<generic-panel v-show="currentTab > 0 && genericDataSource.length > 0" :generic-list="genericDataSource" @scrolltolower="scrolltolower" :isFile="currentTab === 4" />
 
-    <u-empty
-      icon="../../../static/images/global_search_empty.png"
-      v-show="currentTab > 0 && genericDataSource.length === 0"
-      text="没有更多搜索结果"
-    />
-  </view>
+		<view class="empty-container" v-show="isContentEmpty">
+			<u-empty textSize="28rpx" icon="../../../static/images/global_search_empty.png" text="没有更多搜索结果" />
+		</view>
+	
+	</view>
 </template>
 
 <script>
-import { mapGetters } from "vuex";
-import IMSDK, { MessageType } from "openim-uniapp-polyfill";
-import ComprehensivePanel from "./components/ComprehensivePanel.vue";
-import GenericPanel from "./components/GenericPanel.vue";
-import { searchInOrganization } from "../../../api/orgnizaition";
-import { parseMessageByType } from "../../../util/imCommon";
-import { businessSearchUser } from "../../../api/login";
+import { mapGetters } from 'vuex';
+import IMSDK, { MessageType } from 'openim-uniapp-polyfill';
+import ComprehensivePanel from './components/ComprehensivePanel.vue';
+import GenericPanel from './components/GenericPanel.vue';
+import { searchInOrganization } from '../../../api/orgnizaition';
+import { parseMessageByType } from '../../../util/imCommon';
+import { businessSearchUser } from '../../../api/login';
+import { qconfig } from '@/utils/config.js';
 
 export default {
-  components: {
-    ComprehensivePanel,
-    GenericPanel,
-  },
-  data() {
-    return {
-      currentTab: 0,
-      keyword: "",
-      tabList: [
-        {
-          name: "综合",
-        },
-        {
-          name: "联系人",
-        },
-        {
-          name: "群组",
-        },
-        {
-          name: "聊天记录",
-        },
-        {
-          name: "文件",
-        },
-        {
-          name: "组织架构",
-        },
-      ],
-      dataSource: {
-        contacts: [],
-        groups: [],
-        chatLogs: [],
-        documents: [],
-        org:[],
-      },
-      documentsPageIndex: 1,
-      documentsHasMore: true,
-      documentsLoading: false,
-    };
-  },
-  computed: {
-    ...mapGetters(["storeSelfInfo"]),
-    genericDataSource() {
-      switch (this.currentTab) {
-        case 1:
-          return this.dataSource.contacts;
-        case 2:
-          return this.dataSource.groups;
-        case 3:
-          return this.dataSource.chatLogs;
-        case 4:
-          return this.dataSource.documents;
-        case 5:
-          return this.dataSource.org;
-        default:
-          return [];
-      }
-    },
-  },
-  methods: {
-    clickTab({ index }) {
-      this.currentTab = index;
-    },
-    back() {
-      uni.navigateBack();
-    },
-    enterSearch() {
-      setTimeout(() => {
-        this.documentsPageIndex = 1;
-        this.documentsHasMore = true;
-        this.searchUser();
-        this.searchGroup();
-        this.searchChatLogs();
-        this.searchFileLogs();
-        this.searchOrg();
-      }, 300);
-    },
-    scrolltolower() {
-      if (this.currentTab !== 4 || this.documentsLoading || !this.documentsHasMore) {
-          return;
-      }
-      this.searchFileLogs();
-    },
-    searchUser() {
-      const options = {
-      	keywordList: [this.keyword],
-      	isSearchUserID: false,
-      	isSearchNickname: true,
-      	isSearchRemark: true,
-      }
-      IMSDK.asyncApi(IMSDK.IMMethods.SearchFriends, IMSDK.uuid(), options)
-      	.then(({
-      		data
-      	}) => {
-      		this.dataSource.contacts = data
-      	})
-    },
-    searchGroup() {
-      const options = {
-        keywordList: [this.keyword],
-        isSearchGroupID: true,
-        isSearchGroupName: true,
-      };
-      IMSDK.asyncApi(IMSDK.IMMethods.SearchGroups, IMSDK.uuid(), options).then(
-        ({ data }) => {
-          this.dataSource.groups = data;
-        },
-      );
-    },
-    searchChatLogs() {
-      const options = {
-        conversationID: "",
-        keywordList: [this.keyword],
-        keywordListMatchType: 0,
-        senderUserIDList: [],
-        messageTypeList: [],
-        searchTimePosition: 0,
-        searchTimePeriod: 0,
-        pageIndex: 0,
-        count: 0,
-      };
-      IMSDK.asyncApi(
-        IMSDK.IMMethods.SearchLocalMessages,
-        IMSDK.uuid(),
-        options,
-      ).then(({ data }) => {
-        const searchData = data.searchResultItems ?? [];
-        searchData.map((item) => {
-          item.groupID = item.messageList[0].groupID;
-          item.sendTime = item.messageList[0].sendTime;
-          item.latestMsg =
-            item.messageCount > 1
-              ? `${item.messageCount}条相关聊天记录`
-              : parseMessageByType(item.messageList[0]);
-        });
-        this.dataSource.chatLogs = [...searchData];
-      });
-    },
-    searchFileLogs() {
-      const options = {
-        conversationID: "",
-        keywordList: [this.keyword],
-        keywordListMatchType: 0,
-        senderUserIDList: [],
-        messageTypeList: [MessageType.FileMessage],
-        searchTimePosition: 0,
-        searchTimePeriod: 0,
-        pageIndex: this.documentsPageIndex,
-        count: 20,
-      };
-      IMSDK.asyncApi(
-        IMSDK.IMMethods.SearchLocalMessages,
-        IMSDK.uuid(),
-        options,
-      ).then(({ data }) => {
-        const searchData = data.searchResultItems
-          ? data.searchResultItems[0].messageList
-          : [];
-        const prevData =
-          this.documentsPageIndex === 1 ? [] : this.dataSource.documents;
-        this.dataSource.documents = [...prevData, ...searchData];
-        this.documentsHasMore = searchData.length === 20;
-        this.documentsPageIndex += 1;
-      });
-    },
-    async searchOrg() {
-      const res = await searchInOrganization(this.keyword)
-      this.dataSource.org = res.users || []
-    },
-  },
+	components: {
+		ComprehensivePanel,
+		GenericPanel
+	},
+	data() {
+		return {
+			qconfig,
+			currentTab: 0,
+			keyword: '',
+			tabList: [
+				{
+					name: '综合'
+				},
+				{
+					name: '联系人'
+				},
+				{
+					name: '群组'
+				},
+				{
+					name: '聊天记录'
+				},
+				{
+					name: '文件'
+				},
+				{
+					name: '组织架构'
+				}
+			],
+			dataSource: {
+				contacts: [],
+				groups: [],
+				chatLogs: [],
+				documents: [],
+				org: []
+			},
+			documentsPageIndex: 1,
+			documentsHasMore: true,
+			documentsLoading: false
+		};
+	},
+	computed: {
+		...mapGetters(['storeSelfInfo', 'storeCurrentUserID']),
+		genericDataSource() {
+			switch (this.currentTab) {
+				case 1:
+					return this.dataSource.contacts;
+				case 2:
+					return this.dataSource.groups;
+				case 3:
+					return this.dataSource.chatLogs;
+				case 4:
+					return this.dataSource.documents;
+				case 5:
+					return this.dataSource.org;
+				default:
+					return [];
+			}
+		},
+		isContentEmpty() {
+			if (this.currentTab === 0) {
+				const { contacts, groups, chatLogs, documents, org } = this.dataSource;
+				return (
+					contacts.length === 0 &&
+					groups.length === 0 &&
+					chatLogs.length === 0 &&
+					documents.length === 0 &&
+					org.length === 0
+				);
+			}
+			return this.genericDataSource.length === 0;
+		}
+	},
+	methods: {
+		clickTab({ index }) {
+			this.currentTab = index;
+		},
+		back() {
+			uni.navigateBack();
+		},
+		enterSearch() {
+			setTimeout(() => {
+				this.documentsPageIndex = 1;
+				this.documentsHasMore = true;
+				this.searchUser();
+				this.searchGroup();
+				this.searchChatLogs();
+				this.searchFileLogs();
+				this.searchOrg();
+			}, 300);
+		},
+		scrolltolower() {
+			if (this.currentTab !== 4 || this.documentsLoading || !this.documentsHasMore) {
+				return;
+			}
+			this.searchFileLogs();
+		},
+		searchUser() {
+			const options = {
+				keywordList: [this.keyword],
+				isSearchUserID: false,
+				isSearchNickname: true,
+				isSearchRemark: true
+			};
+			IMSDK.asyncApi(IMSDK.IMMethods.SearchFriends, IMSDK.uuid(), options).then(({ data }) => {
+				this.dataSource.contacts = data;
+			});
+		},
+		searchGroup() {
+			const options = {
+				keywordList: [this.keyword],
+				isSearchGroupID: true,
+				isSearchGroupName: true
+			};
+			IMSDK.asyncApi(IMSDK.IMMethods.SearchGroups, IMSDK.uuid(), options).then(({ data }) => {
+				this.dataSource.groups = data;
+			});
+		},
+		searchChatLogs() {
+			const options = {
+				conversationID: '',
+				keywordList: [this.keyword],
+				keywordListMatchType: 0,
+				senderUserIDList: [],
+				messageTypeList: [],
+				searchTimePosition: 0,
+				searchTimePeriod: 0,
+				pageIndex: 0,
+				count: 0
+			};
+			IMSDK.asyncApi(IMSDK.IMMethods.SearchLocalMessages, IMSDK.uuid(), options).then(({ data }) => {
+				const searchData = data.searchResultItems ?? [];
+				searchData.map((item) => {
+					item.groupID = item.messageList[0].groupID;
+					item.sendTime = item.messageList[0].sendTime;
+					item.latestMsg = item.messageCount > 1 ? `${item.messageCount}条相关聊天记录` : parseMessageByType(item.messageList[0], this.storeCurrentUserID);
+				});
+				this.dataSource.chatLogs = [...searchData];
+			});
+		},
+		searchFileLogs() {
+			const options = {
+				conversationID: '',
+				keywordList: [this.keyword],
+				keywordListMatchType: 0,
+				senderUserIDList: [],
+				messageTypeList: [MessageType.FileMessage],
+				searchTimePosition: 0,
+				searchTimePeriod: 0,
+				pageIndex: this.documentsPageIndex,
+				count: 20
+			};
+			IMSDK.asyncApi(IMSDK.IMMethods.SearchLocalMessages, IMSDK.uuid(), options).then(({ data }) => {
+				const searchData = data.searchResultItems ? data.searchResultItems[0].messageList : [];
+				const prevData = this.documentsPageIndex === 1 ? [] : this.dataSource.documents;
+				this.dataSource.documents = [...prevData, ...searchData];
+				this.documentsHasMore = searchData.length === 20;
+				this.documentsPageIndex += 1;
+			});
+		},
+		async searchOrg() {
+			const res = await searchInOrganization(this.keyword);
+			this.dataSource.org = res.users || [];
+		}
+	}
 };
 </script>
 
 <style lang="scss">
 .page_container {
-  background-color: #f8f8f8;
-  overflow: hidden;
-  margin-top: var(--status-bar-height);
+	background-color: #ffffff;
+	overflow: hidden;
+	margin-top: var(--status-bar-height);
 
-  .search_bar_wrap {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    height: 34px;
-    padding: 12px 22px 0;
-    background-color: #fff;
+	.search_bar_wrap {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 44px;
+		padding: 12px 22px 0;
+		background-color: #fff;
 
-    .search_bar {
-      margin-left: 26rpx !important;
-    }
-  }
+		.search_bar {
+			margin-left: 26rpx !important;
+		}
+	}
 
-  .u-tabs {
-    background-color: #fff;
-    border-bottom: 1px solid #eaeaea;
+	.u-tabs {
+		background-color: #fff;
+		border-bottom: 1px solid #eaeaea;
 
-    ::v-deep .u-tabs__wrapper__nav__item__text {
-      word-break: keep-all;
-    }
+		::v-deep .u-tabs__wrapper__nav__item__text {
+			word-break: keep-all;
+		}
 
-    ::v-deep .u-tabs__wrapper__nav__line {
-      bottom: 0;
-    }
-  }
+		::v-deep .u-tabs__wrapper__nav__line {
+			bottom: 0;
+		}
+	}
 
-  .u-empty {
-    margin-top: 20vh !important;
-  }
+	.empty-container {
+		position: fixed;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+	}
+	
+	.search_bar{
+		font-size: 36rpx !important;
+	}
 }
 </style>

+ 23 - 17
pages_im/pages/common/groupCard/index.vue

@@ -33,20 +33,11 @@
           <u-icon name="arrow-right" color="#999" size="18"></u-icon>
         </view>
         <view class="member_list">
-          <my-avatar
-            v-for="member in getRenderMemberList"
-            :key="member.userID"
-            class="member_item"
-            size="42"
-            :src="member.faceURL"
-            :desc="member.nickname"
-          ></my-avatar>
-          <u-avatar
-            bgColor="#5496EB"
-            icon="more-dot-fill"
-            shape="square"
-            size="42"
-          ></u-avatar>
+          <my-avatar v-for="member in getRenderMemberList" :key="member.userID"  class="member_item" size="42" :src="member.faceURL"  :desc="member.nickname">
+		    <image v-if="getRole(member)==1" class="taoj"  src="/static/svg/doctor.svg"></image>
+		    <image v-if="getRole(member)==2" class="taoj"  src="/static/svg/guanjia.svg"></image>
+		  </my-avatar>
+          <u-avatar  bgColor="#5496EB"  icon="more-dot-fill" shape="square" size="42"></u-avatar>
         </view>
       </view>
 
@@ -197,10 +188,25 @@ export default {
       }
     },
     showToast(message) {
-      this.$refs.uToast.show({
-        message,
-      });
+       this.$refs.uToast.show({message});
     },
+	getRole(member){
+		let userType=0;
+		let userId=member.userID;
+		console.log("qxj member",member);
+		if(userId!=undefined  && (userId!="" || userId.length>0)){
+			if(userId.indexOf('U')!==-1){
+				userType=0;
+			}
+			if(userId.indexOf('D')!==-1){
+				userType=1;
+			}
+			if(userId.indexOf('C')!==-1){
+				userType=2;
+			}
+		}
+		return userType;
+	}
   },
 };
 </script>

+ 4 - 4
pages_im/pages/common/livePage/index.vue

@@ -55,11 +55,11 @@ export default {
     const data = JSON.parse(options.invitationData);
     this.invitationData = data.invitation;
     this.callState.isGroup =
-      this.invitationData.sessionType !== SessionType.Single;
+    this.invitationData.sessionType !== SessionType.Single;
     this.callState.isVideo = this.invitationData.mediaType === "video";
     this.callState.isCalled = JSON.parse(options.isCalled);
     if (options.connectData) {
-      this.connectData = JSON.parse(options.connectData);
+       this.connectData = JSON.parse(options.connectData);
     }
     const connectDataStr =
       this.callState.isGroup && options.connectData
@@ -81,10 +81,10 @@ export default {
       connectDataStr +
       participantStr +
       invitedMemberStr;
-    console.log("qxj livePage:",this.hybridUrl);
+   
     this.setRTCListener();
     this.backPlayRing(this.callState.isCalled ? beCalledRing : callingRing);
-	
+     console.log("qxj livePage-----");
   },
   onReady() {
     this.checkRecordPermission();

+ 1 - 2
pages_im/pages/common/meetingCenter/index.vue

@@ -162,8 +162,7 @@ export default {
   },
   onBackPress(options) {
     uni.switchTab({
-      // url: "/pages_im/pages/conversation/conversationList/index",
-      url: "/pages/index/index"
+      url: "/pages_im/pages/conversation/conversationList/index",
     });
     return true;
   },

+ 47 - 23
pages_im/pages/common/searchUserOrGroup/index.vue

@@ -1,19 +1,19 @@
 <template>
 	<view class="search_container">
-		<custom-nav-bar :route="false">
-			<view slot="left"></view>
-			<view class="search_bar" slot="center">
-				<u-search actionText="取消" @change="keywordChange" @custom="cancel" @search="startSearch" shape="square" :placeholder="getPlaceholder" v-model="keyword" />
-			</view>
+		<custom-nav-bar>
+			<view slot="center" class="page_title"> 添加好友 </view>
 		</custom-nav-bar>
-
-		<view v-show="!empty && !searching" @click="startSearch(keyword)" class="result_row">
+		<view class="search_bar">
+			<u-search class="search_input" shape="round" :showAction="false" @change="keywordChange" @custom="cancel"
+				@search="startSearch" :placeholder="getPlaceholder" v-model="keyword" />
+		</view>
+		<!-- <view v-show="!empty && !searching" @click="startSearch(keyword)" class="result_row">
 			<image class="icon" :src="getIcon" alt="" />
 			<view class="">
 				<text>查找:</text>
 				<text>{{ keyword }}</text>
 			</view>
-		</view>
+		</view> -->
 
 		<view v-show="searching && !empty" class="result_row result_row_empty">
 			<u-loading-icon></u-loading-icon>
@@ -31,7 +31,7 @@ import CustomNavBar from '../../../components/CustomNavBar/index.vue';
 
 import searchGroup from '../../../static/images/contact_add_join_group_fill.png';
 import searchUser from '../../../static/images/contact_add_search_user_fill.png';
-import { businessSearchUserInfo } from '../../../api/login';
+import { getFriendUserApi } from '../../../api/login';
 import { friendsSearch } from '@/api/user';
 
 import { filterEmptyValue } from '../../../util/common';
@@ -56,7 +56,7 @@ export default {
 			return this.isSearchGroup ? '请输入群聊ID' : '搜索用户ID添加好友';
 		}
 	},
-	onLoad(options) {
+	async onLoad(options) {
 		const { isSearchGroup } = options;
 		this.isSearchGroup = JSON.parse(isSearchGroup);
 	},
@@ -70,6 +70,23 @@ export default {
 			}
 		},
 		async startSearch(value) {
+			// let userId = `U${value}`
+			// IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [userId]).then((res) => {
+			// 	console.log("----res------", res);
+			// 	if (res?.data?.length) {
+			// 		const imData = res.data[0];
+			// 		let info = { ...imData, ...{ id: userId } };
+			// 		console.log('-----info---' + JSON.stringify(info));
+			// 		uni.navigateTo({
+			// 			url: `/pages_im/pages/common/userCard/index?pageTitle=添加好友&sourceInfo=${encodeURIComponent(JSON.stringify(info))}`
+			// 		});
+			// 	} else {
+			// 		this.empty = true;
+			// 	}
+			// }).catch((err) => {
+			// 	console.log("----err------", err);
+			// });
+			// return
 			if (!value) return;
 			this.searching = true;
 			try {
@@ -89,20 +106,19 @@ export default {
 				} else {
 					let info = this.$store.getters.storeFriendList.find((item) => item.userID === value);
 					if (!info) {
-						const respUsers = await businessSearchUserInfo(value);
-						const users=respUsers.list;
-						if (users.length > 0) {
-							let userId="U"+users[0].id;
-							console.log("qxj startSearch userId",userId);
-							const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [userId]);
-							const imData = data[0];
-							info = { ...imData, ...users[0] };
-						}
+						const users = await getFriendUserApi(value);
+						//console.log("----users------", users);
+						let userId = "U" + users.userId;
+						//userId=value;
+						const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [userId]);
+						//console.log("----userId------", data);
+						const imData = data[0];
+						info = { ...imData, ...users };
 					}
 					if (info) {
-						console.log('-----qxj info:' + JSON.stringify(info));
+						console.log('-----users---' + JSON.stringify(info));
 						uni.navigateTo({
-							url: `/pages_im/pages/common/userCard/index?sourceInfo=${JSON.stringify(info)}`
+							url: `/pages_im/pages/common/userCard/index?pageTitle=添加好友&sourceInfo=${encodeURIComponent(JSON.stringify(info))}`
 						});
 					} else {
 						this.empty = true;
@@ -120,11 +136,19 @@ export default {
 <style lang="scss" scoped>
 .search_container {
 	height: 100vh;
-	background-color: #f8f8f8;
+	background-color: #FFFFFF;
+	.search_input{
+		font-size: 16px;
+	}
+	.title {
+		font-family: PingFang SC, PingFang SC;
+		color: #222222;
+	}
 
 	.search_bar {
 		width: 100%;
-		padding: 0 44rpx;
+		padding: 0 32rpx;
+		margin-top: 16rpx
 	}
 
 	.result_row {

+ 2 - 21
pages_im/pages/common/sendAddRequest/index.vue

@@ -2,7 +2,7 @@
   <view class="request_join_container">
     <custom-nav-bar :title="isGroup ? '群聊验证' : '好友验证'">
       <view class="top_right_btn" slot="more">
-        <u-button @click="sendRequest" text="发送" type="primary"></u-button>
+        <u-button @click="sendRequest" :color="$qconfig.mainThemeHColor" text="发送" type="primary"></u-button>
       </view>
     </custom-nav-bar>
 
@@ -19,13 +19,10 @@
       >
       </u--textarea>
     </view>
-	
-	<SelectProject @agreeFun="agreeFun" :selectProjectShow="selectProjectShow" :application="application" />
   </view>
 </template>
 
 <script>
-import SelectProject from "@/pages_im/components/SelectProject.vue"
 import IMSDK, { GroupJoinSource } from "openim-uniapp-polyfill";
 import CustomNavBar from "../../../components/CustomNavBar/index.vue";
 import { navigateToDesignatedConversation } from "../../../util/imCommon";
@@ -33,7 +30,6 @@ import { navigateToDesignatedConversation } from "../../../util/imCommon";
 export default {
   components: {
     CustomNavBar,
-	SelectProject,
   },
   data() {
     return {
@@ -43,8 +39,6 @@ export default {
       isScan: false,
       notNeedVerification: false,
       sessionType: 0,
-	  selectProjectShow: false,
-	  application:{fromUserID:null}
     };
   },
   onLoad(options) {
@@ -53,24 +47,11 @@ export default {
     this.isGroup = JSON.parse(isGroup);
     this.isScan = JSON.parse(isScan);
     this.sourceID = sourceID;
-	this.application.fromUserID = sourceID
     this.notNeedVerification = JSON.parse(notNeedVerification);
     this.sessionType = sessionType ?? 0;
   },
   methods: {
-	agreeFun(){
-		this.selectProjectShow = false;
-		this.sendRequestFun()
-	},
-    sendRequest(){
-	  const companyUserId = uni.getStorageSync('companyUserId')
-		if (companyUserId) {
-			this.selectProjectShow = true
-			return
-		}
-		this.sendRequestFun()
-    },
-    sendRequestFun() {
+    sendRequest() {
       let func;
       if (this.isGroup) {
         const joinSource = this.isScan

+ 112 - 136
pages_im/pages/common/setMemberMute/index.vue

@@ -1,150 +1,126 @@
 <template>
-  <view class="page_container">
-    <custom-nav-bar title="设置禁言">
-      <view class="top_right_action" slot="more">
-        <u-button
-          v-show="canCancelMute || choosed || customValue"
-          @click="confirmMute"
-          :loading="loading"
-          type="primary"
-          text="完成"
-          size="mini"
-        ></u-button>
-      </view>
-    </custom-nav-bar>
+	<view class="page_container">
+		<custom-nav-bar title="设置禁言">
+			<view class="top_right_action" slot="more">
+				<u-button v-show="canCancelMute || choosed || customValue" @click="confirmMute" :loading="loading" color="#FF5C03" text="完成" size="small"></u-button>
+			</view>
+		</custom-nav-bar>
 
-    <view class="choose_area">
-      <view
-        @click="chooseItem(item)"
-        v-for="item in chooseList.slice(canCancelMute ? 0 : 1, chooseList.length)"
-        :key="item.title"
-        class="choose_item"
-      >
-        <view>{{ item.title }}</view>
-        <u-icon
-          v-show="choosed === item.value && customValue === ''"
-          name="checkbox-mark"
-          color="#1D6BED"
-        >
-        </u-icon>
-      </view>
-      <view class="choose_item">
-        <view>自定义</view>
-        <view style="width: 70px">
-          <u-input
-            placeholder="请输入"
-            border="none"
-            type="number"
-            v-model="customValue"
-          >
-            <template slot="suffix">
-              <text>天</text>
-            </template>
-          </u-input>
-        </view>
-      </view>
-    </view>
-  </view>
+		<view class="choose_area">
+			<view @click="chooseItem(item)" v-for="item in chooseList.slice(canCancelMute ? 0 : 1, chooseList.length)" :key="item.title" class="choose_item">
+				<view>{{ item.title }}</view>
+				<u-icon v-show="choosed === item.value && customValue === ''" name="checkbox-mark" color="#FF5C03"></u-icon>
+			</view>
+			<view class="choose_item">
+				<view>自定义</view>
+				<view style="width: 70px">
+					<u-input placeholder="请输入" border="none" type="number" v-model="customValue">
+						<template slot="suffix">
+							<text>天</text>
+						</template>
+					</u-input>
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-import IMSDK from "openim-uniapp-polyfill";
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
+	
+import IMSDK from 'openim-uniapp-polyfill';
+import CustomNavBar from '../../../components/CustomNavBar/index.vue';
 export default {
-  components: {
-    CustomNavBar,
-  },
-  data() {
-    return {
-      choosed: 0,
-      loading: false,
-      customValue: "",
-      fromList: 0,
-      sourceList: [],
-      chooseList: [
-        {
-          title: "取消禁言",
-          value: 0,
-        },
-        {
-          title: "10分钟",
-          value: 600,
-        },
-        {
-          title: "1小时",
-          value: 3600,
-        },
-        {
-          title: "12小时",
-          value: 43200,
-        },
-        {
-          title: "1天",
-          value: 86400,
-        },
-      ],
-      canCancelMute: false
-    };
-  },
-  onLoad(options) {
-    const { sourceInfo, fromList } = options;
-    this.sourceList = JSON.parse(sourceInfo);
-    this.fromList = fromList ?? 0
-    this.canCancelMute = this.sourceList.find(item => item.muteEndTime ?? 0 > Date.now())
-  },
-  methods: {
-    confirmMute() {
-      this.loading = true;
-      let seconds = this.choosed;
-      if (this.customValue !== "") {
-        seconds = this.customValue * 86400;
-      }
-      const promiseArray = this.sourceList.map(async ({ userID, groupID }) => {
-          await IMSDK.asyncApi(IMSDK.IMMethods.ChangeGroupMemberMute, IMSDK.uuid(), {
-            groupID,
-            userID,
-            mutedSeconds: seconds,
-          })
-      })
+	components: {
+		CustomNavBar
+	},
+	data() {
+		return {
+			choosed: 0,
+			loading: false,
+			customValue: '',
+			fromList: 0,
+			sourceList: [],
+			chooseList: [
+				{
+					title: '取消禁言',
+					value: 0
+				},
+				{
+					title: '10分钟',
+					value: 600
+				},
+				{
+					title: '1小时',
+					value: 3600
+				},
+				{
+					title: '12小时',
+					value: 43200
+				},
+				{
+					title: '1天',
+					value: 86400
+				}
+			],
+			canCancelMute: false
+		};
+	},
+	onLoad(options) {
+		const { sourceInfo, fromList } = options;
+		this.sourceList = JSON.parse(sourceInfo);
+		this.fromList = fromList ?? 0;
+		this.canCancelMute = this.sourceList.find((item) => item.muteEndTime ?? 0 > Date.now());
+	},
+	methods: {
+		confirmMute() {
+			this.loading = true;
+			let seconds = this.choosed;
+			if (this.customValue !== '') {
+				seconds = this.customValue * 86400;
+			}
+			const promiseArray = this.sourceList.map(async ({ userID, groupID }) => {
+				await IMSDK.asyncApi(IMSDK.IMMethods.ChangeGroupMemberMute, IMSDK.uuid(), {
+					groupID,
+					userID,
+					mutedSeconds: seconds
+				});
+			});
 
-      Promise.all(promiseArray)
-        .then(() => {
-          uni.$u.toast("设置成功");
-          if(this.fromList){
-            setTimeout(() => uni.navigateBack({ delta:2 }), 1000);
-          }else{
-            setTimeout(() => uni.navigateBack(), 1000);
-          }
-        })
-        .catch(() => uni.$u.toast("设置失败"))
-        .finally(() => (this.loading = false));
-    },
-    chooseItem({ value }) {
-      this.choosed = value;
-      this.customValue = "";
-    },
-  },
+			Promise.all(promiseArray)
+				.then(() => {
+					uni.$u.toast('设置成功');
+					if (this.fromList) {
+						setTimeout(() => uni.navigateBack({ delta: 2 }), 1000);
+					} else {
+						setTimeout(() => uni.navigateBack(), 1000);
+					}
+				})
+				.catch(() => uni.$u.toast('设置失败'))
+				.finally(() => (this.loading = false));
+		},
+		chooseItem({ value }) {
+			this.choosed = value;
+			this.customValue = '';
+		}
+	}
 };
 </script>
 
 <style lang="scss">
 .page_container {
-  background-color: #f2f2f2;
-
-  .top_right_action {
-    margin-right: 44rpx;
-  }
-
-  .choose_area {
-    background-color: #fff;
-
-    .choose_item {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: 24rpx 44rpx;
-      border-bottom: 1px solid #f0f0f0;
-    }
-  }
+	background-color: #f2f2f2;
+	.top_right_action {
+		margin-right: 44rpx;
+	}
+	.choose_area {
+		background-color: #fff;
+		.choose_item {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 24rpx 44rpx;
+			border-bottom: 1px solid #f0f0f0;
+		}
+	}
 }
 </style>

+ 391 - 454
pages_im/pages/common/userCard/index.vue

@@ -1,23 +1,19 @@
 <template>
 	<view class="user_card_container">
 		<u-loading-page :loading="isLoading" loading-text="loading..."></u-loading-page>
-		<custom-nav-bar title="" :route="!isScan" @leftClick="leftClick" />
-		<u-empty style="padding-top: 20px" mode="data"
-			icon="https://zkzh-2025.oss-cn-beijing.aliyuncs.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png" text="暂无数据"
-			v-if="!isLoading && !sourceID"></u-empty>
-		<view v-if="!isLoading && sourceID" style="flex: 1; display: flex; flex-direction: column">
+		<custom-nav-bar title="" />
+		<view v-if="!isLoading" style="flex: 1; display: flex; flex-direction: column">
 			<view class="base_info">
-				<my-avatar :desc="sourceUserInfo.remark || sourceUserInfo.nickname" :src="sourceUserInfo.faceURL"
-					size="44">
-					<image v-if="getRole()==1" class="taoj" src="/static/svg/doctor.svg"></image>
-					<image v-if="getRole()==2" class="taoj" src="/static/svg/guanjia.svg"></image>
-				</my-avatar>
+				<my-avatar :desc="sourceUserInfo.remark || sourceUserInfo.nickname" :src="sourceUserInfo.faceURL" size="44">
+					<image v-if="getRole()==1" class="taoj"  src="/static/svg/doctor.svg"></image>
+					<image v-if="getRole()==2" class="taoj"  src="/static/svg/guanjia.svg"></image>
+				</my-avatar>	
 				<view class="user_name">
 					<text class="text">{{ getShowName }}</text>
 					<text class="id" @click="copy(sourceUserInfo.userID)">{{ sourceUserInfo.userID }}</text>
 				</view>
-				<view class="add_btn" @click="toAddFriend" v-if="trySendRequest && !friend">
-					<u-button type="primary" icon="man-add" text="添加"></u-button>
+				<view class="add_btn" @click="toAddFriend" v-if="trySendRequest">
+					<u-button style="background-color: #FF5C03;border: none;" type="primary" icon="man-add" text="添加"></u-button>
 				</view>
 			</view>
 
@@ -29,8 +25,7 @@
 
 			<view v-if="!isSelf && (showSetRole || showSetMuteMember)" class="info_row">
 				<user-info-row-item v-if="showSetRole" lable="设为管理员" arrow>
-					<u-switch :loading="switchLoading" @change="changeMemberRole" :asyncChange="true" size="20"
-						:value="isAdmin" />
+					<u-switch :loading="switchLoading" @change="changeMemberRole" :asyncChange="true" size="20" :value="isAdmin" />
 				</user-info-row-item>
 				<user-info-row-item v-if="showSetMuteMember" @click="toMuteMember" arrow lable="设置禁言">
 					<view class="mute_right">
@@ -57,509 +52,451 @@
 				<user-info-row-item v-if="!isSelf" @click="toMoreInfo" lable="个人资料" arrow />
 				<!-- <user-info-row-item @click="toPublisher" lable="查看动态" arrow /> -->
 			</view>
-			
-			<view v-if="!isSelf || !trySendRequest || friend" class="action_row">
+
+			<view v-if="!isSelf && !trySendRequest" class="action_row">
 				<view v-if="!isBlack" @click="callUser" class="action_item">
-					<image style="width: 40rpx;height: 40rpx;" src="../../../static/images/user_card_call.png" alt="" />
+					<image style="width: 40rpx;height: 40rpx;" src="../../../static/images/user_card_call.png" alt=""/>
 					<text>音视频通话</text>
 				</view>
 				<view @click="toDesignatedConversation" class="action_item">
-					<image style="width: 40rpx;height: 40rpx;" src="../../../static/images/user_card_message.png"
-						alt="" />
+					<image style="width: 40rpx;height: 40rpx;" src="../../../static/images/user_card_message.png" alt="" />
 					<text>发消息</text>
 				</view>
 			</view>
-
 		</view>
 	</view>
 </template>
 
 <script>
-	import {
-		mapGetters
-	} from 'vuex';
-	import {
-		CommonIsAllow
-	} from '../../../constant';
-	import {
-		getUserInDepartment
-	} from '../../../api/orgnizaition';
-	import {
-		navigateToDesignatedConversation,
-		callingModule
-	} from '../../../util/imCommon';
-	import IMSDK, {
-		GroupJoinSource,
-		GroupMemberRole,
-		SessionType
-	} from 'openim-uniapp-polyfill';
-	import MyAvatar from '../../../components/MyAvatar/index.vue';
-	import CustomNavBar from '../../../components/CustomNavBar/index.vue';
-	import UserInfoRowItem from './components/UserInfoRowItem.vue';
-	import dayjs from 'dayjs';
-	import {
-		businessSearchUserInfo
-	} from '../../../api/login';
-
-	export default {
-		components: {
-			CustomNavBar,
-			MyAvatar,
-			UserInfoRowItem
+import { mapGetters } from 'vuex';
+import { CommonIsAllow } from '../../../constant';
+import { getUserInDepartment } from '../../../api/orgnizaition';
+import { navigateToDesignatedConversation, callingModule } from '../../../util/imCommon';
+import IMSDK, { GroupJoinSource, GroupMemberRole, SessionType } from 'openim-uniapp-polyfill';
+import MyAvatar from '../../../components/MyAvatar/index.vue';
+import CustomNavBar from '../../../components/CustomNavBar/index.vue';
+import UserInfoRowItem from './components/UserInfoRowItem.vue';
+import dayjs from 'dayjs';
+import { businessSearchUserInfo } from '../../../api/login';
+
+export default {
+	components: {
+		CustomNavBar,
+		MyAvatar,
+		UserInfoRowItem
+	},
+	data() {
+		return {
+			isLoading: true,
+			sourceID: '',
+			sourceUserInfo: {},
+			organizationData: [],
+			memberInfo: null,
+			switchLoading: false,
+			showSetRole: false,
+			showSetMuteMember: false,
+			disableAdd: false,
+			isFromSearch:false,
+			friend: false,
+			isScan: false,
+		};
+	},
+	computed: {
+		...mapGetters(['storeFriendList', 'storeBlackList', 'storeCurrentMemberInGroup', 'storeCurrentUserID', 'storeSelfInfo', 'storeOrganizationData']),
+		isFriend() {
+			return this.storeFriendList.findIndex((friend) => friend.userID === this.sourceID) !== -1;
 		},
-		data() {
-			return {
-				isLoading: true,
-				sourceID: '',
-				sourceUserInfo: {},
-				organizationData: [],
-				memberInfo: null,
-				switchLoading: false,
-				showSetRole: false,
-				showSetMuteMember: false,
-				disableAdd: false,
-				isScan: false,
-				friend: false,
-			};
+		isBlack() {
+			return this.storeBlackList.some((black) => black.userID === this.sourceID);
 		},
-		computed: {
-			...mapGetters(['storeFriendList', 'storeBlackList', 'storeCurrentMemberInGroup', 'storeCurrentUserID',
-				'storeSelfInfo', 'storeOrganizationData'
-			]),
-			isFriend() {
-				return this.storeFriendList.findIndex((friend) => friend.userID === this.sourceID) !== -1;
-			},
-			isBlack() {
-				return this.storeBlackList.some((black) => black.userID === this.sourceID);
-			},
-			trySendRequest() {
-				return !this.isFriend && !this.isSelf && !this.disableAdd && this.sourceUserInfo.allowAddFriend !== 2;
-			},
-			isSelf() {
-				return this.sourceID === this.storeSelfInfo.userID;
-			},
-			isAdmin() {
-				return this.memberInfo?.roleLevel === GroupMemberRole.Admin;
-			},
-			getJoinGroupTime() {
-				return this.memberInfo ? dayjs(this.memberInfo.joinTime).format('YYYY-MM-DD') : '';
-			},
-			getJoinSource() {
-				if (!this.memberInfo) {
-					return '';
-				}
-
-				switch (this.memberInfo.joinSource) {
-					case GroupJoinSource.Invitation:
-						return '邀请进群';
-					case GroupJoinSource.QrCode:
-						return '扫码进群';
-					case GroupJoinSource.Search:
-						return '搜索进群';
-					default:
-						return '-';
-				}
-			},
-			getShowName() {
-				let suffix = '';
-				if (this.sourceUserInfo.remark) {
-					suffix = `(${this.sourceUserInfo.remark})`;
-				}
-				return this.sourceUserInfo.nickname + suffix;
-			},
-			getMuteTime() {
-				if (!this.memberInfo.muteEndTime || this.memberInfo.muteEndTime < Date.now()) {
-					return '';
-				}
-				return dayjs(this.memberInfo.muteEndTime).format('YYYY/MM/DD HH:mm');
-			}
+		trySendRequest() {
+			return !this.isFriend && !this.isSelf && !this.disableAdd && this.sourceUserInfo.allowAddFriend !== 2;
 		},
-		onLoad(options) {
-			const {
-				sourceID,
-				sourceInfo,
-				memberInfo,
-				disableAdd,
-				isScan
-			} = options;
-			this.friend = options.friend
-			this.disableAdd = disableAdd ? JSON.parse(disableAdd) : false;
-			this.isScan = isScan;
-			if (sourceID) {
-				this.sourceID = sourceID;
-			} else {
-				const info = JSON.parse(sourceInfo);
-				this.sourceID = info.userID;
+		isSelf() {
+			return this.sourceID === this.storeSelfInfo.userID;
+		},
+		isAdmin() {
+			return this.memberInfo?.roleLevel === GroupMemberRole.Admin;
+		},
+		getJoinGroupTime() {
+			return this.memberInfo ? dayjs(this.memberInfo.joinTime).format('YYYY-MM-DD') : '';
+		},
+		getJoinSource() {
+			if (!this.memberInfo) {
+				return '';
 			}
-			if (!this.sourceID) {
-				this.isLoading = false;
-				return
+
+			switch (this.memberInfo.joinSource) {
+				case GroupJoinSource.Invitation:
+					return '邀请进群';
+				case GroupJoinSource.QrCode:
+					return '扫码进群';
+				case GroupJoinSource.Search:
+					return '搜索进群';
+				default:
+					return '-';
 			}
-			this.getSourceUserInfo();
-			if (memberInfo) {
-				this.memberInfo = JSON.parse(memberInfo);
-				this.checkCurrentMember();
+		},
+		getShowName() {
+			let suffix = '';
+			if (this.sourceUserInfo.remark) {
+				suffix = `(${this.sourceUserInfo.remark})`;
 			}
-			this.getOrganizationData();
-			this.setIMListener();
+			return this.sourceUserInfo.nickname + suffix;
 		},
-		onUnload() {
-			this.disposeIMListener();
+		getMuteTime() {
+			if (!this.memberInfo.muteEndTime || this.memberInfo.muteEndTime < Date.now()) {
+				return '';
+			}
+			return dayjs(this.memberInfo.muteEndTime).format('YYYY/MM/DD HH:mm');
+		}
+	},
+	onLoad(options) {
+		const { sourceID, sourceInfo, memberInfo, disableAdd,isScan} = options;
+		this.disableAdd = disableAdd ? JSON.parse(disableAdd) : false;
+		this.isFromSearch=options.isFromSearch;
+		this.isScan = isScan;
+		if (sourceID) {
+			this.sourceID = sourceID;
+		} else {
+			const info = JSON.parse(sourceInfo);
+			this.sourceID = info.userID;
+		}
+		this.getSourceUserInfo();
+		if (memberInfo) {
+			this.memberInfo = JSON.parse(memberInfo);
+			this.checkCurrentMember();
+		}
+		this.getOrganizationData();
+		this.setIMListener();
+	},
+	onUnload() {
+		this.disposeIMListener();
+	},
+	methods: {
+		copy(userID) {
+			uni.setClipboardData({
+				showToast: false,
+				data: userID,
+				success: function () {
+					uni.showToast({
+						icon: 'none',
+						title: '复制成功'
+					});
+				}
+			});
 		},
-
-		methods: {
-			copy(userID) {
-				uni.setClipboardData({
-					showToast: false,
-					data: userID,
-					success: function() {
-						uni.showToast({
-							icon: 'none',
-							title: '复制成功'
-						});
-					}
-				});
-			},
-			toPublisher() {
-				let baseUserInfo = JSON.stringify({
+		toPublisher() {
+			let baseUserInfo=JSON.stringify({
 					userID: this.sourceUserInfo.userID,
 					nickname: this.sourceUserInfo.nickname,
 					faceURL: this.sourceUserInfo.faceURL
-				});
-				uni.navigateTo({
-					url: `/pages_im/pages/moments/designatedMoments/index?baseUserInfo=${baseUserInfo}`
-				});
-			},
-			async getSourceUserInfo() {
-				let info = {};
-				const friendInfo = this.storeFriendList.find((item) => item.userID === this.sourceID);
-				if (friendInfo) {
-					info = {
-						...friendInfo
-					};
-				} else {
-					const {
-						data
-					} = await IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [this.sourceID]);
+			});
+			uni.navigateTo({
+				url: `/pages_im/pages/moments/designatedMoments/index?baseUserInfo=${baseUserInfo}`
+			});
+		},
+		async getSourceUserInfo() {
+			let info = {};
+			const friendInfo = this.storeFriendList.find((item) => item.userID === this.sourceID);
+			if (friendInfo) {
+				info = { ...friendInfo };
+			} else {
+				const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [this.sourceID]);
+				info = { ...(data[0] ?? {}) };
+			}
+			this.sourceUserInfo = {
+				...info
+			};
+			this.isLoading = false;
+			try {
+				const { total, users } = await businessSearchUserInfo(this.sourceID);
+				if (total > 0) {
 					info = {
-						...(data[0] ?? {})
+						...info,
+						...users[0]
 					};
 				}
-				this.sourceUserInfo = {
-					...info
-				};
-				this.isLoading = false;
+			} catch (err) {
+				console.log(err);
+			}
+			this.sourceUserInfo = {
+				...info
+			};
+		},
+		
+		getOrganizationData() {
+			getUserInDepartment(this.sourceID).then((res) => {
+				this.organizationData = res.users.map((item) => ({
+					companyName: this.$store.getters.storeOrganizationData.current.name,
+					departmentName: item.members[0].department.name,
+					position: item.members[0].position
+				}));
+			});
+		},
+		async checkCurrentMember() {
+			let role;
+			if (this.storeCurrentMemberInGroup.groupID === this.memberInfo.groupID) {
+				role = this.storeCurrentMemberInGroup.roleLevel;
+			} else {
 				try {
-
-					const {
-						total,
-						users
-					} = await businessSearchUserInfo(this.sourceID);
-					if (total > 0) {
-						info = {
-							...info,
-							...users[0]
-						};
-					}
-				} catch (err) {
-					console.log(err);
+					const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.GetSpecifiedGroupMembersInfo, IMSDK.uuid(), {
+						groupID: this.memberInfo.groupID,
+						userIDList: [this.storeCurrentUserID]
+					});
+					role = data[0]?.roleLevel;
+				} catch (e) {}
+			}
+			this.showSetRole = role === GroupMemberRole.Owner;
+			this.showSetMuteMember = role === GroupMemberRole.Owner || (role === GroupMemberRole.Admin && this.memberInfo.roleLevel === GroupMemberRole.Nomal);
+		},
+		toAddFriend() {
+			console.log(" qxj sourceID",this.sourceID);
+			//this.sourceID="4870137574";
+			uni.$u.route('/pages_im/pages/common/sendAddRequest/index', {
+				isGroup: false,
+				sourceID: this.sourceID,
+				isScan: false,
+				notNeedVerification: false
+			});
+		},
+		callUser() {
+			uni.showActionSheet({
+				itemList: ['语音通话', '视频通话'],
+				cancelText: '取消',
+				success: ({ tapIndex }) => {
+					callingModule.startLiveChat(!!tapIndex, [this.sourceID], '', this.$store.getters.storeCurrentUserID);
 				}
+			});
+		},
+		toFullOrganization() {
+			uni.$u.route('/pages_im/pages/common/fullOrganization/index', {
+				organizationData: JSON.stringify(this.organizationData)
+			});
+		},
+		toDesignatedConversation() {
+			// if(!!this.isFromSearch){
+			// 	navigateToDesignatedConversation(this.sourceID, SessionType.Single, this.memberInfo !== null,false).catch(() => uni.$u.toast('获取会话信息失败'));
+			// }else{
+			// 	 navigateToDesignatedConversation(this.sourceID, SessionType.Single, this.memberInfo !== null,false).catch(() => uni.$u.toast('获取会话信息失败'));
+			// }
+			if (this.sourceID || this.isScan) {
+				navigateToDesignatedConversation(this.sourceID, SessionType.Single, this.memberInfo !== null, false)
+					.catch(() => uni.$u.toast('获取会话信息失败'));
+			} else {
+				// uni.navigateBack({
+				// 	delta: 2
+				// });
+				let url = `/pages_im/pages/conversation/chating/index?back2Tab=false`;
+				uni.navigateTo({url});
+			}
+		},
+		toMoreInfo() {
+			uni.navigateTo({
+				url: `/pages_im/pages/common/userCardMore/index?sourceInfo=${JSON.stringify(this.sourceUserInfo)}`
+			});
+		},
+		changeMemberRole(flag) {
+			this.switchLoading = true;
+			const newRole = flag ? GroupMemberRole.Admin : GroupMemberRole.Nomal;
+			IMSDK.asyncApi(IMSDK.IMMethods.SetGroupMemberInfo, IMSDK.uuid(), {
+				groupID: this.memberInfo.groupID,
+				userID: this.sourceUserInfo.userID,
+				roleLevel: newRole
+			}).catch(() => uni.$u.toast('设置失败'))
+			 .finally(() => (this.switchLoading = false));
+		},
+		toMuteMember() {
+			uni.navigateTo({
+				url: `/pages_im/pages/common/setMemberMute/index?sourceInfo=${JSON.stringify([this.memberInfo])}`
+			});
+		},
+		friendInfoChangeHandler({ data }) {
+			if (data.userID === this.sourceUserInfo.userID) {
 				this.sourceUserInfo = {
-					...info
+					...this.sourceUserInfo,
+					...data
 				};
-			},
-
-			getOrganizationData() {
-				getUserInDepartment(this.sourceID).then((res) => {
-					this.organizationData = res.users.map((item) => ({
-						companyName: this.$store.getters.storeOrganizationData.current.name,
-						departmentName: item.members[0].department.name,
-						position: item.members[0].position
-					}));
-				});
-			},
-			async checkCurrentMember() {
-				let role;
-				if (this.storeCurrentMemberInGroup.groupID === this.memberInfo.groupID) {
-					role = this.storeCurrentMemberInGroup.roleLevel;
-				} else {
-					try {
-						const {
-							data
-						} = await IMSDK.asyncApi(IMSDK.IMMethods.GetSpecifiedGroupMembersInfo, IMSDK.uuid(), {
-							groupID: this.memberInfo.groupID,
-							userIDList: [this.storeCurrentUserID]
-						});
-						role = data[0]?.roleLevel;
-					} catch (e) {}
-				}
-				this.showSetRole = role === GroupMemberRole.Owner;
-				this.showSetMuteMember = role === GroupMemberRole.Owner || (role === GroupMemberRole.Admin && this
-					.memberInfo.roleLevel === GroupMemberRole.Nomal);
-			},
-			toAddFriend() {
-				console.log("qxj toAddFriend");
-				uni.$u.route('/pages_im/pages/common/sendAddRequest/index', {
-					isGroup: false,
-					sourceID: this.sourceID,
-					isScan: false,
-					notNeedVerification: false
-				});
-			},
-			callUser() {
-				uni.showActionSheet({
-					itemList: ['语音通话', '视频通话'],
-					cancelText: '取消',
-					success: ({
-						tapIndex
-					}) => {
-						callingModule.startLiveChat(!!tapIndex, [this.sourceID], '', this.$store.getters
-							.storeCurrentUserID);
-					}
-				});
-			},
-			toFullOrganization() {
-				uni.$u.route('/pages_im/pages/common/fullOrganization/index', {
-					organizationData: JSON.stringify(this.organizationData)
-				});
-			},
-			toDesignatedConversation() {
-				if (this.isScan) {
-					navigateToDesignatedConversation(this.sourceID, SessionType.Single, this.memberInfo !== null, false)
-						.catch(() => uni.$u.toast('获取会话信息失败'));
-				} else {
-					// uni.navigateBack({
-					// 	delta: 2
-					// });
-					let url = `/pages_im/pages/conversation/chating/index?back2Tab=false`;
-					uni.navigateTo({url});
-				}
-
-			},
-			toMoreInfo() {
-				uni.navigateTo({
-					url: `/pages_im/pages/common/userCardMore/index?sourceInfo=${JSON.stringify(this.sourceUserInfo)}`
-				});
-			},
-			changeMemberRole(flag) {
-				this.switchLoading = true;
-				const newRole = flag ? GroupMemberRole.Admin : GroupMemberRole.Nomal;
-				IMSDK.asyncApi(IMSDK.IMMethods.SetGroupMemberInfo, IMSDK.uuid(), {
-						groupID: this.memberInfo.groupID,
-						userID: this.sourceUserInfo.userID,
-						roleLevel: newRole
-					}).catch(() => uni.$u.toast('设置失败'))
-					.finally(() => (this.switchLoading = false));
-			},
-			toMuteMember() {
-				uni.navigateTo({
-					url: `/pages_im/pages/common/setMemberMute/index?sourceInfo=${JSON.stringify([this.memberInfo])}`
-				});
-			},
-			friendInfoChangeHandler({
-				data
-			}) {
-				if (data.userID === this.sourceUserInfo.userID) {
-					this.sourceUserInfo = {
-						...this.sourceUserInfo,
-						...data
-					};
+			}
+		},
+		groupMemberInfoChangeHandler({ data }) {
+			if (data.userID === this.memberInfo.userID && data.groupID === this.memberInfo.groupID) {
+				this.memberInfo = {
+					...this.memberInfo,
+					...data
+				};
+			}
+		},
+		setIMListener() {
+			uni.$on(IMSDK.IMEvents.OnFriendInfoChanged, this.friendInfoChangeHandler);
+			uni.$on(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
+		},
+		disposeIMListener() {
+			uni.$off(IMSDK.IMEvents.OnFriendInfoChanged, this.friendInfoChangeHandler);
+			uni.$off(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
+		},
+		getRole(){
+			let userType=0;
+			let userId=this.sourceUserInfo.userID;
+			if(userId!=undefined  && (userId!="" || userId.length>0)){
+				if(userId.indexOf('U')!==-1){
+					userType=0;
 				}
-			},
-			groupMemberInfoChangeHandler({
-				data
-			}) {
-				if (data.userID === this.memberInfo.userID && data.groupID === this.memberInfo.groupID) {
-					this.memberInfo = {
-						...this.memberInfo,
-						...data
-					};
+				if(userId.indexOf('D')!==-1){
+					userType=1;
 				}
-			},
-			setIMListener() {
-				uni.$on(IMSDK.IMEvents.OnFriendInfoChanged, this.friendInfoChangeHandler);
-				uni.$on(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
-			},
-			disposeIMListener() {
-				uni.$off(IMSDK.IMEvents.OnFriendInfoChanged, this.friendInfoChangeHandler);
-				uni.$off(IMSDK.IMEvents.OnGroupMemberInfoChanged, this.groupMemberInfoChangeHandler);
-			},
-			getRole() {
-				let userType = 0;
-				let userId = this.sourceUserInfo.userID;
-				if (userId != undefined && (userId != "" || userId.length > 0)) {
-					if (userId.indexOf('U') !== -1) {
-						userType = 0;
-					}
-					if (userId.indexOf('D') !== -1) {
-						userType = 1;
-					}
-					if (userId.indexOf('C') !== -1) {
-						userType = 2;
-					}
+				if(userId.indexOf('C')!==-1){
+					userType=2;
 				}
-				return userType;
-			},
-			leftClick() {
-				uni.switchTab({
-					url: 'pages_im/pages/conversation/conversationList/index'
-				});
 			}
-		},
-		onBackPress() {
-			uni.switchTab({
-				url: 'pages_im/pages/conversation/conversationList/index'
-			});
-			return false;
+			return userType;
 		}
-	};
+	}
+};
 </script>
 
 <style lang="scss" scoped>
-	.user_card_container {
-		@include colBox(false);
-		height: 100vh;
-		background-color: #f6f6f6;
-		overflow-y: auto;
-		position: relative;
-
-		.base_info {
-			@include vCenterBox();
-			background-color: #fff;
-			padding: 44rpx;
-			margin-bottom: 18rpx;
-
-			.add_btn {
+.user_card_container {
+	@include colBox(false);
+	height: 100vh;
+	background-color: #f6f6f6;
+	overflow-y: auto;
+	position: relative;
+
+	.base_info {
+		@include vCenterBox();
+		background-color: #fff;
+		padding: 44rpx;
+		margin-bottom: 18rpx;
+
+		.add_btn {
+			width: 140rpx;
+			height: 60rpx;
+			margin-left: auto;
+
+			.u-button {
 				width: 140rpx;
 				height: 60rpx;
-				margin-left: auto;
-
-				.u-button {
-					width: 140rpx;
-					height: 60rpx;
-				}
-			}
-
-			.u-avatar {
-				margin-right: 24rpx;
-			}
-
-			.user_name {
-				display: flex;
-				flex-direction: column;
-				justify-content: space-between;
-				margin-bottom: 12rpx;
-				height: 46px;
-				margin-left: 20rpx;
-
-				.text {
-					@include nomalEllipsis();
-					max-width: 300rpx;
-				}
-			}
-
-			.company {
-				font-size: 28rpx;
-				color: $u-primary;
 			}
 		}
 
-		.info_row {
-			background-color: #fff;
-			margin-bottom: 24rpx;
+		.u-avatar {
+			margin-right: 24rpx;
 		}
 
-		.mute_right {
+		.user_name {
 			display: flex;
-			align-items: center;
+			flex-direction: column;
+			justify-content: space-between;
+			margin-bottom: 12rpx;
+			height: 46px;
+            margin-left: 20rpx;
+			.text {
+				@include nomalEllipsis();
+				max-width: 300rpx;
+			}
 		}
 
-		.company_row {
-			padding: 20rpx 0;
+		.company {
+			font-size: 28rpx;
+			color: $u-primary;
+		}
+	}
 
-			.desc_title {
-				padding-left: 44rpx;
-			}
+	.info_row {
+		background-color: #fff;
+		margin-bottom: 24rpx;
+	}
 
-			.company_info {
-				.more_dep {
-					display: flex;
-					justify-content: center;
-					color: #1d6bed;
-				}
-			}
+	.mute_right {
+		display: flex;
+		align-items: center;
+	}
 
-			::v-deep .title {
-				width: 200rpx;
-				color: #999 !important;
-			}
-		}
+	.company_row {
+		padding: 20rpx 0;
 
-		.action_row {
-			@include vCenterBox();
-			align-items: flex-end;
-			justify-content: space-around;
-			margin: 44rpx;
-			flex: 1;
+		.desc_title {
+			padding-left: 44rpx;
+		}
 
-			.action_item {
-				width: 100%;
-				@include colBox(true);
-				flex-direction: row;
-				align-items: center;
+		.company_info {
+			.more_dep {
+				display: flex;
 				justify-content: center;
-				padding: 22rpx 0;
-				background: $u-primary;
-				color: #fff;
-				border-radius: 6px;
-
-				&:first-child {
-					background-color: #fff;
-					color: $uni-text-color;
-					margin-right: 24rpx;
-				}
-
-				img {
-					margin-right: 16rpx;
-					width: 40rpx;
-					height: 40rpx;
-				}
+				color: #1d6bed;
 			}
 		}
 
-		.id {
-			font-size: 24rpx;
-			color: #999;
+		::v-deep .title {
+			width: 200rpx;
+			color: #999 !important;
 		}
+	}
 
-		.online_state {
-			@include vCenterBox();
-			margin-left: 24rpx;
-			font-size: 24rpx;
-			color: #999;
-
-			.dot {
-				background-color: #10cc64;
-				width: 12rpx;
-				height: 12rpx;
-				border-radius: 50%;
-				margin-right: 12rpx;
+	.action_row {
+		@include vCenterBox();
+		align-items: flex-end;
+		justify-content: space-around;
+		margin: 44rpx;
+		flex: 1;
+
+		.action_item {
+			width: 100%;
+			@include colBox(true);
+			flex-direction: row;
+			align-items: center;
+			justify-content: center;
+			padding: 22rpx 0;
+			background: #FF5C03;
+			color: #fff;
+			border-radius: 6px;
+
+			&:first-child {
+				background-color: #fff;
+				color: $uni-text-color;
+				margin-right: 24rpx;
 			}
 
-			.online_str {
-				@include nomalEllipsis();
-				max-width: 280rpx;
+			img {
+				margin-right: 16rpx;
+				width: 40rpx;
+				height: 40rpx;
 			}
 		}
+	}
+
+	.id {
+		font-size: 24rpx;
+		color: #999;
+	}
 
-		.taoj {
-			position: absolute;
-			left: 0px;
-			top: 0px;
-			right: 0px;
-			bottom: 0px;
-			width: 84rpx;
-			height: 84rpx;
+	.online_state {
+		@include vCenterBox();
+		margin-left: 24rpx;
+		font-size: 24rpx;
+		color: #999;
+
+		.dot {
+			background-color: #10cc64;
+			width: 12rpx;
+			height: 12rpx;
+			border-radius: 50%;
+			margin-right: 12rpx;
 		}
+
+		.online_str {
+			@include nomalEllipsis();
+			max-width: 280rpx;
+		}
+	}
+	
+	.taoj {
+		position: absolute;
+		left: 0px;
+		top: 0px;
+		width: 100%;
+		height: 100%;
 	}
-</style>
+}
+</style>

+ 30 - 25
pages_im/pages/common/userCardMore/index.vue

@@ -1,31 +1,33 @@
 <template>
 	<view class="user_more_container">
 		<custom-nav-bar title="好友设置" />
-
-		<view class="info_row">
-			<user-info-row-item v-if="isFriend" @click="toMark" lable="设置备注" arrow />
-			<user-info-row-item @click="toMore" lable="个人资料" arrow />
-			<user-info-row-item @click="toShare" lable="推荐给朋友" arrow />
-		</view>
-
-		<view class="info_row">
-			<user-info-row-item lable="加入黑名单" arrow>
-				<u-switch asyncChange :loading="blackLoading" size="20" :value="isBlacked" @change="change"></u-switch>
-			</user-info-row-item>
+        <view class="row_wrap">
+			<view class="info_row">
+				<user-info-row-item v-if="isFriend" @click="toMark" lable="设置备注" arrow />
+				<user-info-row-item @click="toMore" lable="个人资料" arrow />
+				<user-info-row-item @click="toShare" lable="推荐给朋友" arrow />
+			</view>
+			
+			<view class="info_row">
+				<user-info-row-item lable="加入黑名单" arrow>
+					<u-switch asyncChange :loading="blackLoading" size="20" :value="isBlacked" @change="change"></u-switch>
+				</user-info-row-item>
+			</view>
+			
+			<view v-if="isFriend" class="info_row">
+				<u-button @click="() => (showConfirm = true)" type="error" plain text="解除好友关系"></u-button>
+			</view>
+			<u-toast ref="uToast"></u-toast>
+			<u-modal
+				:content="`确定要解除与${sourceInfo.nickname}的好友关系吗?`"
+				asyncClose
+				:show="showConfirm"
+				showCancelButton
+				@confirm="confirmRemove"
+				@cancel="() => (showConfirm = false)"
+			></u-modal>
 		</view>
-
-		<view v-if="isFriend" class="info_row">
-			<u-button @click="() => (showConfirm = true)" type="error" plain text="解除好友关系"></u-button>
-		</view>
-		<u-toast ref="uToast"></u-toast>
-		<u-modal
-			:content="`确定要解除与${sourceInfo.nickname}的好友关系吗?`"
-			asyncClose
-			:show="showConfirm"
-			showCancelButton
-			@confirm="confirmRemove"
-			@cancel="() => (showConfirm = false)"
-		></u-modal>
+	
 	</view>
 </template>
 
@@ -110,7 +112,10 @@ export default {
 	@include colBox(false);
 	height: 100vh;
 	background-color: #f6f6f6;
-
+	.row_wrap {
+		flex: 1;
+	    overflow-y: auto;
+	}
 	.info_row {
 		background-color: #fff;
 		margin: 24rpx;

+ 1 - 0
pages_im/pages/common/webH5/index.vue

@@ -14,6 +14,7 @@
  		}
  	},
 	onLoad(options) {
+		console.log('options.url',options.url)
 	    this.durl = options.url;
 		if(options.name){
 			this.pageTitle = options.name; // 保存标题到变量

+ 0 - 268
pages_im/pages/contact/applicationDetails/index.vue

@@ -1,268 +0,0 @@
-<template>
-  <view class="page_container">
-    <custom-nav-bar :title="isGroupApplication ? '群通知' : '好友请求'" />
-
-    <view class="application_item">
-      <view class="base_info_row">
-        <view class="base_info_left" @click="toSourceDetails">
-          <my-avatar :src="getSourceFaceURL" :desc="getSourceName" />
-          <view class="base_info_details">
-            <text class="nickname">{{ getSourceName }}</text>
-          </view>
-        </view>
-
-        <u-icon name="arrow-right" size="18" color="#999"></u-icon>
-      </view>
-
-      <view class="request_message">
-        <view v-if="isGroupApplication" class="title">
-          <text>申请加入 </text>
-          <text class="group_name">{{ currentApplication.groupName }}</text>
-        </view>
-        <text v-else>{{ `${getSourceName}:` }}</text>
-        <text>{{ currentApplication.reqMsg }}</text>
-      </view>
-
-      <view v-if="isGroupApplication" class="join_source">
-        <text>{{ `来源:${getGroupJoinSource}` }}</text>
-      </view>
-    </view>
-
-    <view class="action_row">
-      <u-button
-        :loading="loadingState.accept"
-        @click="acceptAplication"
-        type="primary"
-        :plain="true"
-        :text="`通过${isGroupApplication ? '入群' : '好友'}申请`"
-      ></u-button>
-    </view>
-
-    <view class="action_row">
-      <u-button
-        :loading="loadingState.refuse"
-        @click="refuseAplication"
-        type="primary"
-        :plain="true"
-        :text="`拒绝${isGroupApplication ? '入群' : '好友'}申请`"
-      ></u-button>
-    </view>
-  </view>
-</template>
-
-<script>
-import { mapGetters } from "vuex";
-import IMSDK, { GroupJoinSource } from "openim-uniapp-polyfill";
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import MyAvatar from "../../../components/MyAvatar/index.vue";
-export default {
-  components: {
-    CustomNavBar,
-    MyAvatar,
-  },
-  data() {
-    return {
-      currentApplication: {},
-      isOnline: false,
-      loadingState: {
-        accept: false,
-        refuse: false,
-      },
-    };
-  },
-  computed: {
-    ...mapGetters(["storeSelfInfo"]),
-    isGroupApplication() {
-      return this.currentApplication.groupID !== undefined;
-    },
-    getSourceID() {
-      return (
-        this.currentApplication.fromUserID ?? this.currentApplication.userID
-      );
-    },
-    getSourceName() {
-      return (
-        this.currentApplication.fromNickname ?? this.currentApplication.nickname
-      );
-    },
-    getSourceFaceURL() {
-      return (
-        this.currentApplication.fromFaceURL ?? this.currentApplication.faceURL
-      );
-    },
-    getGroupJoinSource() {
-      if (this.currentApplication.joinSource === GroupJoinSource.Invitation) {
-        return "群成员邀请";
-      }
-      if (this.currentApplication.joinSource === GroupJoinSource.Search) {
-        return "搜索群ID";
-      }
-      return "扫描二维码";
-    },
-  },
-  onLoad(options) {
-    const { application } = options;
-    this.currentApplication = JSON.parse(application);
-  },
-  methods: {
-    toSourceDetails() {
-      uni.navigateTo({
-        url: `/pages_im/pages/common/userCard/index?sourceID=${this.getSourceID}`,
-      });
-    },
-    acceptAplication() {
-      this.loadingState.accept = true;
-      let func;
-      if (this.isGroupApplication) {
-        func = IMSDK.asyncApi(
-          IMSDK.IMMethods.AcceptGroupApplication,
-          IMSDK.uuid(),
-          {
-            groupID: this.currentApplication.groupID,
-            fromUserID: this.currentApplication.userID,
-            handleMsg: "",
-          },
-        );
-      } else {
-        console.log(this.currentApplication);
-        func = IMSDK.asyncApi(
-          IMSDK.IMMethods.AcceptFriendApplication,
-          IMSDK.uuid(),
-          {
-            toUserID: this.currentApplication.fromUserID,
-            handleMsg: "",
-          },
-        );
-      }
-      func
-        .then(() => {
-          uni.$u.toast("操作成功");
-          setTimeout(() => uni.navigateBack(), 500);
-        })
-        .catch((e) => {
-          console.log(e);
-          uni.$u.toast("操作失败");
-        })
-        .finally(() => (this.loadingState.accept = false));
-    },
-    refuseAplication() {
-      this.loadingState.refuse = true;
-      let func;
-      if (this.isGroupApplication) {
-        func = IMSDK.asyncApi(
-          IMSDK.IMMethods.RefuseGroupApplication,
-          IMSDK.uuid(),
-          {
-            groupID: this.currentApplication.groupID,
-            fromUserID: this.currentApplication.userID,
-            handleMsg: "",
-          },
-        );
-      } else {
-        func = IMSDK.asyncApi(
-          IMSDK.IMMethods.RefuseFriendApplication,
-          IMSDK.uuid(),
-          {
-            toUserID: this.currentApplication.fromUserID,
-            handleMsg: "",
-          },
-        );
-      }
-      func
-        .then(() => {
-          uni.$u.toast("操作成功");
-          setTimeout(() => uni.navigateBack(), 250);
-        })
-        .catch(() => uni.$u.toast("操作失败"))
-        .finally(() => (this.loadingState.refuse = false));
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.page_container {
-  background-color: #f8f8f8;
-
-  .application_item {
-    padding: 72rpx 44rpx 24rpx;
-    background-color: #fff;
-
-    .base_info_row {
-      @include btwBox();
-
-      .base_info_left {
-        @include vCenterBox();
-      }
-
-      .base_info_details {
-        margin-left: 24rpx;
-
-        .nickname {
-          @include nomalEllipsis();
-          max-width: 600rpx;
-        }
-
-        .online_state {
-          @include vCenterBox();
-          flex-direction: row;
-          font-size: 24rpx;
-          color: #999;
-          margin-top: 6rpx;
-
-          .dot {
-            background-color: #10cc64;
-            width: 12rpx;
-            height: 12rpx;
-            border-radius: 50%;
-            margin-right: 12rpx;
-          }
-        }
-      }
-    }
-
-    .request_message {
-      background-color: #eee;
-      margin-top: 48rpx;
-      padding: 24rpx 36rpx;
-      border-radius: 12rpx;
-      font-size: 28rpx;
-      color: #666;
-      min-height: 240rpx;
-
-      .title {
-        margin-bottom: 12rpx;
-        color: $uni-text-color;
-
-        .group_name {
-          @nomalEllipsis();
-          max-width: 400rpx;
-          color: $uni-color-primary;
-          margin-left: 12rpx;
-        }
-      }
-    }
-
-    .join_source {
-      margin-top: 20rpx;
-      font-size: 24rpx;
-      color: #666;
-      text-align: right;
-    }
-  }
-
-  .action_row {
-    margin-top: 24rpx;
-
-    .u-button {
-      border: none;
-    }
-
-    &:last-child {
-      .u-button {
-        color: #999 !important;
-      }
-    }
-  }
-}
-</style>

+ 54 - 46
pages_im/pages/contact/applicationList/ApplicationItem.vue

@@ -1,15 +1,14 @@
 <template>
   <view @click="clickItem" class="application_item">
-    <my-avatar
-      :src="getAvatarUrl"
-      :isGroup="isGroupApplication"
-      :desc="application[isRecv ? 'fromNickname' : 'toNickname']"
-      size="42"
-    />
+    <my-avatar :src="getAvatarUrl" :isGroup="isGroupApplication"
+      :desc="application[isRecv ? 'fromNickname' : 'toNickname']" size="42">
+	  	<image v-if="getRole() == 1" class="taoj" src="/static/svg/doctor.svg"></image>
+	  	<image v-if="getRole() == 2" class="taoj" src="/static/svg/guanjia.svg"></image>
+	  </my-avatar>
+	  
     <view class="application_item_details">
       <view class="content">
         <text class="user_name">{{ getShowName }}</text>
-
         <view v-if="isGroupApplication" class="title">
           申请加入
           <text class="group_name">{{ application.groupName }}</text>
@@ -19,31 +18,18 @@
 
       <view class="application_action">
         <text v-if="showStateStr" class="status_tip">{{ getStateStr }}</text>
-        <text v-if="showGreet" @tap.stop="greetToUser" class="status_tip greet"
-          >打招呼</text
-        >
-        <button
-          :loading="accessLoading"
-          v-if="showAccept"
-          class="access_btn"
-          @tap.stop="acceptApplication"
-          type="primary"
-          :plain="true"
-          size="mini"
-        >
+        <text v-if="showGreet" @tap.stop="greetToUser" class="status_tip greet">已添加</text>
+        <button :loading="accessLoading" v-if="showAccept" class="access_btn" @tap.stop="acceptApplication" :plain="true" size="mini">
           {{ isGroupApplication ? "同意" : "接受" }}
         </button>
       </view>
 
       <view class="bottom_line"> </view>
     </view>
-	
-	<SelectProject @agreeFun="agreeFun" :selectProjectShow="selectProjectShow" :application="application" />
   </view>
 </template>
 
 <script>
-	import SelectProject from "@/pages_im/components/SelectProject.vue"
 import { navigateToDesignatedConversation } from "../../../util/imCommon";
 import IMSDK, { SessionType } from "openim-uniapp-polyfill";
 import MyAvatar from "../../../components/MyAvatar/index.vue";
@@ -51,7 +37,6 @@ export default {
   name: "ApplicationItem",
   components: {
     MyAvatar,
-	SelectProject,
   },
   props: {
     application: Object,
@@ -60,7 +45,6 @@ export default {
   data() {
     return {
       accessLoading: false,
-	  selectProjectShow: false,
     };
   },
   computed: {
@@ -109,10 +93,6 @@ export default {
     },
   },
   methods: {
-	agreeFun(){
-		this.selectProjectShow = false;
-		this.addFriend()
-	},
     clickItem() {
       if (this.showAccept) {
         uni.navigateTo({
@@ -133,15 +113,7 @@ export default {
         });
       }
     },
-	acceptApplication(){
-		const companyUserId = uni.getStorageSync('companyUserId')
-		if (companyUserId) {
-			this.selectProjectShow = true
-			return
-		}
-		this.addFriend()
-	},
-    addFriend() {
+    acceptApplication() {
       this.accessLoading = true;
       let func;
       if (this.isGroupApplication) {
@@ -164,11 +136,11 @@ export default {
           },
         );
       }
-      func
-        .then(() => {
-			uni.$emit('updList')
-			uni.$u.toast("操作成功")
-		})
+      func.then(() => {
+          uni.$u.toast("操作成功")
+          this.$store.dispatch("contact/getRecvFriendApplications");
+          this.$store.dispatch("contact/getRecvGroupApplications");
+        })
         .catch(() => uni.$u.toast("操作失败"))
         .finally(() => (this.accessLoading = false));
     },
@@ -177,6 +149,27 @@ export default {
         SessionType.Single,
       ).catch(() => uni.$u.toast("获取会话信息失败"));
     },
+	
+	getRole() {
+		let userType = 0;
+		let userId = this.application.fromUserID;
+		if (userId != undefined && (userId != "" || userId.length > 0)) {
+			if (userId.indexOf('U') !== -1) {
+				userType = 0;
+			}
+			if (userId.indexOf('D') !== -1) {
+				userType = 1;
+			}
+			if (userId.indexOf('C') !== -1) {
+				userType = 2;
+			}
+		}
+		return userType;
+	}
+	
+	
+	
+	
   },
 };
 </script>
@@ -237,13 +230,17 @@ export default {
       }
 
       .access_btn {
-        padding: 0 12rpx;
-        height: 48rpx;
-        line-height: 48rpx;
+        padding: 0rpx 12rpx;
+        height: 70rpx;
+        line-height: 70rpx;
+        background: #FF5C03;
+        border-radius: 10rpx;
+        color: #fff;
+        border-color: #fff;
       }
 
       .greet {
-        color: #418ae5;
+        color: #999999;
       }
     }
 
@@ -262,4 +259,15 @@ export default {
     height: 0;
   }
 }
+
+.taoj {
+	position: absolute;
+	left: 0px;
+	top: 0px;
+	width: 100%;
+	height: 100%;
+}
+
+
+
 </style>

+ 139 - 204
pages_im/pages/contact/applicationList/index.vue

@@ -4,16 +4,12 @@
 		<view class="pane_row" :style="{ transform: `translateX(${isRecv ? '0' : '-100%'})` }">
 			<view class="pane_content">
 				<u-list v-if="getRecvRenderData.length > 0" class="application_list">
-					<u-list-item v-for="application in getRecvRenderData" :key="
-              application[!isGroupApplication ? 'fromUserID' : 'userID'] +
-              application.groupID
-            ">
+					<u-list-item v-for="application in getRecvRenderData" :key="application[!isGroupApplication ? 'fromUserID' : 'userID'] + application.groupID">
 						<application-item :isRecv="true" :application="application" />
 					</u-list-item>
 				</u-list>
 				<u-list v-else-if="getSendRenderData.length > 0" class="application_list">
-					<u-list-item v-for="application in getSendRenderData"
-						:key="application[!isGroupApplication ? 'toUserID' : 'groupID']">
+					<u-list-item v-for="application in getSendRenderData" :key="application[!isGroupApplication ? 'toUserID' : 'groupID']">
 						<application-item :application="application" />
 					</u-list-item>
 				</u-list>
@@ -27,224 +23,163 @@
 </template>
 
 <script>
-	import {
-		getFriendApplyList
-	} from '@/pages_im/api/im.js';
-	import IMSDK, {
-		IMMethods
-	} from 'openim-uniapp-polyfill';
-	import {
-		mapGetters
-	} from "vuex";
-	import {
-		ContactMenuTypes
-	} from "../../../constant";
-	import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-	import ApplicationItem from "./ApplicationItem.vue";
-	export default {
-		components: {
-			CustomNavBar,
-			ApplicationItem,
+import { mapGetters } from 'vuex';
+import { ContactMenuTypes } from '../../../constant';
+import CustomNavBar from '../../../components/CustomNavBar/index.vue';
+import ApplicationItem from './ApplicationItem.vue';
+export default {
+	components: {
+		CustomNavBar,
+		ApplicationItem
+	},
+	data() {
+		return {
+			keyword: '',
+			isRecv: true,
+			isGroupApplication: false
+		};
+	},
+	computed: {
+		...mapGetters([
+			'storeRecvFriendApplications',
+			'storeSentFriendApplications',
+			'storeRecvGroupApplications',
+			'storeSentGroupApplications',
+			'storeUnHandleFriendApplicationNum',
+			'storeUnHandleGroupApplicationNum'
+		]),
+		getRecvRenderData() {
+			const tmpList = this.isGroupApplication ? this.storeRecvGroupApplications : this.storeRecvFriendApplications;
+			tmpList.sort((a, b) => (a.handleResult === 0 ? -1 : 1));
+			return tmpList.slice(0, 4);
 		},
-		data() {
-			return {
-				keyword: "",
-				isRecv: true,
-				isGroupApplication: false,
-				getRecvRenderData: [],
-			};
+		getSendRenderData() {
+			const tmpList = this.isGroupApplication ? this.storeSentGroupApplications : this.storeSentFriendApplications;
+			tmpList.sort((a, b) => (a.handleResult === 0 ? -1 : 1));
+			return tmpList.slice(0, 4);
 		},
-		computed: {
-			...mapGetters([
-				"storeRecvFriendApplications",
-				"storeSentFriendApplications",
-				"storeRecvGroupApplications",
-				"storeSentGroupApplications",
-				"storeUnHandleFriendApplicationNum",
-				"storeUnHandleGroupApplicationNum",
-			]),
-			// getRecvRenderData() {
-			// 	const tmpList = this.isGroupApplication ?
-			// 		this.storeRecvGroupApplications :
-			// 		this.storeRecvFriendApplications;
-			// 	tmpList.sort((a, b) => (a.handleResult === 0 ? -1 : 1));
-			// 	return tmpList.slice(0, 4);
-			// },
-			getSendRenderData() {
-				const tmpList = this.isGroupApplication ?
-					this.storeSentGroupApplications :
-					this.storeSentFriendApplications;
-				tmpList.sort((a, b) => (a.handleResult === 0 ? -1 : 1));
-				return tmpList.slice(0, 4);
-			},
-			tabList() {
-				return [{
-						name: this.isGroupApplication ? "入群申请" : "好友请求",
-					},
-					{
-						name: "我的请求",
-					},
-				];
-			},
-		},
-		onLoad(params) {
-			this.friendApplicationList()
-			const {
-				applicationType
-			} = params;
-			this.isGroupApplication = applicationType === ContactMenuTypes.NewGroup;
+		tabList() {
+			return [
+				{
+					name: this.isGroupApplication ? '入群申请' : '好友请求'
+				},
+				{
+					name: '我的请求'
+				}
+			];
+		}
+	},
+	onLoad(params) {
+		const { applicationType } = params;
+		this.isGroupApplication = applicationType === ContactMenuTypes.NewGroup;
+	},
+	onUnload() {
+		this.updateUnhandleNum();
+	},
+	methods: {
+		clickTab({ index }) {
+			this.isRecv = index === 0;
 		},
-		onShow() {
-			uni.$on('updList', () => {
-				this.friendApplicationList()
-			})
+		previewAll() {
+			uni.navigateTo({
+				url: `/pages_im/pages/contact/applicationListDetails/index?isGroupApplication=${this.isGroupApplication}&isRecv=${this.isRecv}`
+			});
 		},
-		onUnload() {
-			this.updateUnhandleNum();
+		toSearch() {
+			uni.navigateTo({
+				url: `/pages_im/pages/common/searchUserOrGroup/index?isSearchGroup=${this.isGroupApplication}`
+			});
 		},
-		methods: {
-			async friendApplicationList() {
-				const user = JSON.parse(uni.getStorageSync('userInfo'));
-				let parasm = {
-					applyType: 3,
-					userID: 'U' + user.userId,
-					pagination: {
-						pageNumber: 1,
-						showNumber: 50
+		updateUnhandleNum() {
+			if (!this.isRecv) return;
+			const numStoreKey = this.isGroupApplication ? 'storeUnHandleGroupApplicationNum' : 'storeUnHandleFriendApplicationNum';
+			console.log("qxj updateUnhandleNum:"+numStoreKey, this[numStoreKey]);
+			if (this[numStoreKey] > 0) {
+				const currentUserID = this.$store.getters.storeCurrentUserID;
+				const storageKey = `${currentUserID}_${this.isGroupApplication ? 'accessedGroupApplications' : 'accessedFriendApplications'}`;
+				const storeKey = this.isGroupApplication ? 'storeRecvGroupApplications' : 'storeRecvFriendApplications';
+				const applicationKey = this.isGroupApplication ? 'userID' : 'fromUserID';
+				const accessedData = uni.getStorageSync(storageKey) || [];
+				this[storeKey].forEach((application) => {
+					const item = `${application[applicationKey]}_${application.createTime}`;
+					if (application.handleResult === 0 && !accessedData.includes(item)) {
+						accessedData.push(item);
 					}
-				}
-				const res = await getFriendApplyList(parasm)
-				if(res.code ==200 ){
-					this.getRecvRenderData = res.data.data.FriendRequests || []
-				}
-				// const res = await IMSDK.asyncApi(IMMethods.GetFriendApplicationListAsRecipient, IMSDK.uuid(), {
-				// 	offset: 10,
-				// 	count: 50,
-				// })
-				// this.getRecvRenderData = res.data
-			},
-			clickTab({
-				index
-			}) {
-				this.isRecv = index === 0;
-			},
-			previewAll() {
-				uni.navigateTo({
-					url: `/pages_im/pages/contact/applicationListDetails/index?isGroupApplication=${this.isGroupApplication}&isRecv=${this.isRecv}`,
 				});
-			},
-			toSearch() {
-				uni.navigateTo({
-					url: `/pages_im/pages/common/searchUserOrGroup/index?isSearchGroup=${this.isGroupApplication}`,
+				uni.setStorage({
+					key: storageKey,
+					data: accessedData,
+					success: () => {
+						const mutationMethod = this.isGroupApplication ? 'SET_RECV_GROUP_APPLICATIONS' : 'SET_RECV_FRIEND_APPLICATIONS';
+						this.$store.commit(`contact/${mutationMethod}`, [...this[storeKey]]);
+					}
 				});
-			},
-			updateUnhandleNum() {
-				if (!this.isRecv) return;
-				const numStoreKey = this.isGroupApplication ?
-					"storeUnHandleGroupApplicationNum" :
-					"storeUnHandleFriendApplicationNum";
-				console.log(numStoreKey, this[numStoreKey]);
-				if (this[numStoreKey] > 0) {
-					const currentUserID = this.$store.getters.storeCurrentUserID;
-					const storageKey = `${currentUserID}_${
-          this.isGroupApplication
-            ? "accessedGroupApplications"
-            : "accessedFriendApplications"
-        }`;
-					const storeKey = this.isGroupApplication ?
-						"storeRecvGroupApplications" :
-						"storeRecvFriendApplications";
-
-					const applicationKey = this.isGroupApplication ?
-						"userID" :
-						"fromUserID";
-					const accessedData = uni.getStorageSync(storageKey) || [];
-					this[storeKey].forEach((application) => {
-						const item = `${application[applicationKey]}_${application.createTime}`;
-						if (application.handleResult === 0 && !accessedData.includes(item)) {
-							accessedData.push(item);
-						}
-					});
-					uni.setStorage({
-						key: storageKey,
-						data: accessedData,
-						success: () => {
-							const mutationMethod = this.isGroupApplication ?
-								"SET_RECV_GROUP_APPLICATIONS" :
-								"SET_RECV_FRIEND_APPLICATIONS";
-							this.$store.commit(`contact/${mutationMethod}`, [
-								...this[storeKey],
-							]);
-						},
-					});
-				}
-			},
-		},
-	};
+			}
+		}
+	}
+};
 </script>
 
 <style lang="scss" scoped>
-	.empty {
-		@include centerBox();
-		flex-direction: column;
-		margin-top: 25vh !important;
-
-		&_text {
-			margin-top: 26rpx;
-			color: #8e9ab0;
-		}
+.empty {
+	@include centerBox();
+	flex-direction: column;
+	margin-top: 25vh !important;
 
-		image {
-			width: 237rpx;
-			height: 244rpx;
-		}
+	&_text {
+		margin-top: 26rpx;
+		color: #8e9ab0;
 	}
+	image {
+		width: 160rpx;
+		height: 160rpx;
+	}
+}
+.application_list_container {
+	@include colBox(false);
+	height: 100vh;
+	background-color: #f8f9fa;
+	overflow-x: hidden;
 
-	.application_list_container {
-		@include colBox(false);
-		height: 100vh;
-		background-color: #f8f9fa;
-		overflow-x: hidden;
-
-		.search_bar_wrap {
-			height: 34px;
-			padding: 12px 22px;
-			background-color: #fff;
-		}
+	.search_bar_wrap {
+		height: 34px;
+		padding: 12px 22px;
+		background-color: #fff;
+	}
 
-		.u-tabs {
-			background-color: #fff;
-		}
+	.u-tabs {
+		background-color: #fff;
+	}
 
-		.pane_row {
-			display: flex;
-			flex: 1;
-			transition: all 0.3s ease 0s !important;
-			background-color: #fff;
-			margin-top: 20rpx;
+	.pane_row {
+		display: flex;
+		flex: 1;
+		transition: all 0.3s ease 0s !important;
+		background-color: #fff;
+		margin-top: 20rpx;
 
-			.pane_content {
-				@include colBox(false);
-				height: 100%;
-				flex: 0 0 100%;
+		.pane_content {
+			@include colBox(false);
+			height: 100%;
+			flex: 0 0 100%;
 
-				.pane_title {
-					font-size: 28rpx;
-					color: #999;
-					padding: 12rpx 44rpx;
-					background-color: #f8f8f8;
-				}
+			.pane_title {
+				font-size: 28rpx;
+				color: #999;
+				padding: 12rpx 44rpx;
+				background-color: #f8f8f8;
+			}
 
-				.application_list {
-					flex: 1;
-					height: 100% !important;
-				}
+			.application_list {
+				flex: 1;
+				height: 100% !important;
 			}
 		}
+	}
 
-		.view_all {
-			background-color: #fff;
-			padding: 44rpx 44rpx;
-		}
+	.view_all {
+		background-color: #fff;
+		padding: 44rpx 44rpx;
 	}
-</style>
+}
+</style>

+ 0 - 81
pages_im/pages/contact/applicationListDetails/index.vue

@@ -1,81 +0,0 @@
-<template>
-  <view class="application_list_container">
-    <custom-nav-bar :title="getTitle" />
-
-    <u-list class="application_list">
-      <u-list-item
-        v-for="application in getRenderData"
-        :key="getKey(application)"
-      >
-        <application-item :isRecv="isRecv" :application="application" />
-      </u-list-item>
-    </u-list>
-  </view>
-</template>
-
-<script>
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import ApplicationItem from "../applicationList/ApplicationItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    ApplicationItem,
-  },
-  data() {
-    return {
-      isGroupApplication: false,
-      isRecv: false,
-    };
-  },
-  computed: {
-    getRenderData() {
-      let getterKey = this.isRecv
-        ? "storeRecvFriendApplications"
-        : "storeSentFriendApplications";
-      if (this.isGroupApplication) {
-        getterKey = this.isRecv
-          ? "storeRecvGroupApplications"
-          : "storeSentGroupApplications";
-      }
-      return [...this.$store.getters[getterKey]].sort((a, b) =>
-        a.handleResult === 0 ? -1 : 1,
-      );
-    },
-    getKey() {
-      return (application) => {
-        if (this.isGroupApplication) {
-          return this.isRecv
-            ? application.userID + application.groupID
-            : application.groupID;
-        }
-        return application[this.isRecv ? "fromUserID" : "toUserID"];
-      };
-    },
-    getTitle() {
-      if (!this.isRecv) {
-        return "我的申请";
-      }
-      return this.isGroupApplication ? "群通知" : "好友请求";
-    },
-  },
-  onLoad(options) {
-    const { isGroupApplication, isRecv } = options;
-    this.isGroupApplication = JSON.parse(isGroupApplication);
-    this.isRecv = JSON.parse(isRecv);
-  },
-  methods: {},
-};
-</script>
-
-<style lang="scss" scoped>
-.application_list_container {
-  @include colBox(false);
-  height: 100vh;
-  background-color: #f8f8f8;
-
-  .application_list {
-    margin-top: 24rpx;
-    flex: 1;
-  }
-}
-</style>

+ 0 - 74
pages_im/pages/contact/contactAdd/ActionItem.vue

@@ -1,74 +0,0 @@
-<template>
-  <view class="action_item" @click="onClick">
-    <slot name="icon">
-      <view class="action_icon">
-        <image :src="action.icon" mode=""></image>
-      </view>
-    </slot>
-
-    <view class="action_details">
-      <text class="title">{{ action.title }}</text>
-      <text class="desc">{{ action.desc }}</text>
-      <view class="bottom_line"></view>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  name: "",
-  props: {
-    action: Object,
-  },
-  data() {
-    return {};
-  },
-  methods: {
-    onClick() {
-      this.$emit("click", this.action);
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.action_item {
-  @include vCenterBox();
-  padding: 24rpx 44rpx;
-
-  .action_icon {
-    width: 30px;
-    height: 30px;
-
-    image {
-      max-width: 100%;
-      max-height: 100%;
-    }
-  }
-
-  .action_details {
-    @include colBox(false);
-    margin-left: 48rpx;
-    width: 100%;
-    position: relative;
-
-    .title {
-      font-weight: 500;
-      padding-bottom: 12rpx;
-    }
-
-    .desc {
-      font-size: 24rpx;
-      color: #999;
-    }
-
-    .bottom_line {
-      height: 1px;
-      width: 100%;
-      background-color: #f0f0f0;
-      position: absolute;
-      bottom: -24rpx;
-    }
-  }
-}
-</style>

+ 0 - 117
pages_im/pages/contact/contactAdd/index.vue

@@ -1,117 +0,0 @@
-<template>
-  <view class="contact_add_container">
-    <custom-nav-bar title="添加" />
-
-    <view class="action_row">
-      <action-item
-        @click="friendAction(item)"
-        v-for="item in friendActionMenus"
-        :action="item"
-        :key="item.idx"
-      />
-      <action-item
-        @click="groupAction(item)"
-        v-for="item in groupActionMenus"
-        :action="item"
-        :key="item.idx"
-      />
-    </view>
-  </view>
-</template>
-
-<script>
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import { scanQrCodeResult } from "../../../util/imCommon";
-import ActionItem from "./ActionItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    ActionItem,
-  },
-  data() {
-    return {
-      groupActionMenus: [
-        // {
-        //   idx: 0,
-        //   title: "创建群组",
-        //   desc: "创建群组,全面使用好生活福满多",
-        //   icon: require("../../../static/images/contact_add_create_group.png"),
-        // },
-        // {
-        //   idx: 1,
-        //   title: "添加群组",
-        //   desc: "向管理员或团队成员询问ID",
-        //   icon: require("../../../static/images/contact_add_join_group.png"),
-        // },
-      ],
-      friendActionMenus: [
-        {
-          idx: 1,
-          title: "扫一扫",
-          desc: "扫描二维码名片",
-          icon: require("../../../static/images/contact_add_qr_user.png"),
-        },
-        {
-          idx: 0,
-          title: "添加好友",
-          desc: "通过手机号/ID号/搜索添加",
-          icon: require("../../../static/images/contact_add_search_user.png"),
-        },
-      ],
-    };
-  },
-  methods: {
-    groupAction({ idx }) {
-      if (idx === 0) {
-        uni.navigateTo({
-          url: `/pages_im/pages/common/createGroup/index`,
-        });
-      } else {
-        uni.navigateTo({
-          url: "/pages_im/pages/contact/switchJoinGroup/index",
-        });
-      }
-    },
-    friendAction({ idx }) {
-      if (!idx) {
-        uni.navigateTo({
-          url: "/pages_im/pages/common/searchUserOrGroup/index?isSearchGroup=false",
-        });
-      } else {
-        // uni.scanCode({
-        //   scanType: ["qrCode"],
-        //   success: ({ result }) => scanQrCodeResult(result),
-        // });
-		
-			uni.navigateTo({
-				url: '/pages_watch/healthMonitoring/scanCode?typeFun=getIndexScanCode&isFromIm=1'
-			});
-      }
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.contact_add_container {
-  height: 100vh;
-  background-color: #f8f8f8;
-
-  .desc_title {
-    font-size: 24rpx;
-    color: #999;
-    padding: 24rpx 44rpx;
-  }
-
-  .action_row {
-    margin-top: 24rpx;
-    background-color: #fff;
-
-    .action_item:last-child {
-      .bottom_line {
-        height: 0;
-      }
-    }
-  }
-}
-</style>

+ 151 - 26
pages_im/pages/contact/friendList/index.vue

@@ -1,19 +1,27 @@
 <template>
 	<view class="friend_list_container">
-		<custom-nav-bar title="我的好友" />
-		
-		<view class="search_bar_wrap">
-			<u-search class="search_bar" shape="square" placeholder="搜索" :showAction="false" disabled @click="toSearch" />
+		<custom-nav-bar>
+			<view slot="center" class="page_title">通讯录</view>
+			<!-- <view class="right_view" slot="more">
+				<image class="back_icon" @tap="handleSearch"
+					src="/pages_im/static/images/chat/title_bar_search_icon24@2x.png" alt="" />
+				<image class="back_icon" @tap="handleAddFriend" src="/pages_im/static/images/chat/add_icon24@2x.png"
+					alt="" />
+			</view> -->
+		</custom-nav-bar>
+		<view class="group_content">
+			<view class="group_wrap" v-for="(item, index) in groupData" :key="index" @tap="handlePath(item)">
+				<image class="left_icon" :src="item.icon" mode="scaleToFill" />
+				<view class="left_name">{{ item.name }}</view>
+				<view v-if="item.name === '验证消息' && contactUnreadTotal > 0" class="contact-badge">
+					<text class="contact-badge-text">{{ contactUnreadTotal > 99 ? '99+' : contactUnreadTotal }}</text>
+				</view>
+			</view>
 		</view>
-
-		<choose-index-list
-			v-if="getIndexData.dataList.length > 0"
-			@itemClick="userClick"
-			:height="`${listHeight}px`"
-			:indexList="getIndexData.indexList"
-			:itemArr="getIndexData.dataList"/>
-			
-		  <u-empty v-else mode="list" />
+		<choose-index-list v-if="getIndexData.dataList.length > 0" @itemClick="userClick" :height="`${listHeight}px`"
+			:indexList="getIndexData.indexList" :itemArr="getIndexData.dataList" />
+		<u-empty v-else mode="data" style="padding-top: 20%;" text="暂无数据" width="80" height="80"
+			icon="/pages_im/static/images/chat/empty.png" />
 	</view>
 </template>
 
@@ -23,7 +31,7 @@ import { formatChooseData } from '../../../util/common';
 import CustomNavBar from '../../../components/CustomNavBar/index.vue';
 import ChooseIndexList from '../../../components/ChooseIndexList/index.vue';
 import { navigateToDesignatedConversation, callingModule } from '../../../util/imCommon';
-import IMSDK, {  SessionType } from 'openim-uniapp-polyfill';
+import IMSDK, { SessionType } from 'openim-uniapp-polyfill';
 export default {
 	components: {
 		CustomNavBar,
@@ -32,25 +40,68 @@ export default {
 	data() {
 		return {
 			keyword: '',
-			listHeight: 0
+			listHeight: 0,
+			groupData: [
+				{
+					icon: '/pages_im/static/images/chat/yzxx_icon40@2x.png',
+					name: '验证消息',
+					path: '/pages_im/pages/contact/applicationList/index?applicationType=NewFriend'
+				},
+				// {
+				// 	icon: '/pages_im/static/images/chat/blacklist_icon40@2x.png',
+				// 	name: '黑名单'
+				// },
+				// {
+				// 	icon: '/pages_im/static/images/chat/my_group_chat_icon40@2x.png',
+				// 	name: '我的群聊'
+				// },
+				// {
+				// 	icon: '/pages_im/static/images/chat/add_mass_sending_icon40@2x.png',
+				// 	name: '新建群聊',
+				// 	path: '/pages_im/pages/common/createGroup/index'
+				// },
+			]
 		};
 	},
 	computed: {
-		...mapGetters(['storeFriendList']),
+		...mapGetters(['storeFriendList', 'storeUnHandleFriendApplicationNum', 'storeUnHandleGroupApplicationNum']),
+		contactUnreadTotal() {
+			return (this.storeUnHandleFriendApplicationNum || 0) + (this.storeUnHandleGroupApplicationNum || 0);
+		},
 		getIndexData() {
+			console.log("--storeFriendList--", this.storeFriendList);
 			return formatChooseData(this.storeFriendList);
 		}
 	},
+	onLoad() {
+		if(this.$companyUserIsLogin()){
+			this.groupData.push({
+				 icon: '/pages_im/static/images/chat/add_mass_sending_icon40@2x.png',
+				 name: '新建群聊',
+				 path: '/pages_im/pages/common/createGroup/index'
+			});
+		}
+	},
 	mounted() {
 		this.getListHeight();
 	},
 	methods: {
-		toSearch() {
-			console.log("qxj toSearch");
+		handleAddFriend() {
+			uni.navigateTo({
+				url: `/pages_im/pages/common/searchUserOrGroup/index?isSearchGroup=false`
+			});
+		},
+		handleSearch() {
 			uni.navigateTo({
 				url: `/pages_im/pages/contact/searchAddedFriend/index`
 			});
-			//uni.$u.route('/pages_im/pages/contact/searchAddedFriend/index');
+		},
+		handlePath(item) {
+			if (item.path) {
+				uni.navigateTo({
+					url: item.path
+				});
+			}
 		},
 		userClick(friend) {
 			this.toDesignatedConversation(friend.userID);
@@ -60,7 +111,7 @@ export default {
 		},
 		async getListHeight() {
 			const windowInfo = uni.getWindowInfo();
-			const data = await this.getEl('.search_bar_wrap');
+			const data = await this.getEl('.group_content');
 			const searchBarHeight = Number(data.height.toFixed());
 			this.listHeight = windowInfo.windowHeight - windowInfo.statusBarHeight - 44 - searchBarHeight;
 		},
@@ -80,14 +131,88 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.friend_list_container {
-	.search_bar_wrap {
-		height: 54px;
-		padding: 12px 22px;
+.group_content {
+	padding: 0rpx 0 0rpx 0;
+	background-color: #fff;
+	display: flex;
+	flex-direction: column;
+	.group_wrap {
+		margin-bottom: 24rpx;
+		display: flex;
+		align-items: center;
+		padding: 0 32rpx;
+		height: 80rpx;
+		.left_icon {
+			width: 80rpx;
+			height: 100%;
+			margin-right: 24rpx;
+		}
+
+		&:last-child {
+			.left_name::after {
+				display: none;
+			}
+		}
+
+		.left_name {
+			color: #222222;
+			flex: 1;
+			height: 100%;
+			line-height: 80rpx;
+			position: relative;
+
+			&::after {
+				content: '';
+				position: absolute;
+				left: 0;
+				bottom: -20rpx;
+				width: 100%;
+				height: 1rpx;
+				background-color: #f5f5f5;
+			}
+		}
 	}
+}
+
+.friend_list_container {
+	background-color: #F7F8FA;
 
-	.u-empty {
-		margin-top: 25vh !important;
+
+
+
+}
+
+.page_title {
+	font-family: PingFang SC, PingFang SC;
+	color: #222222;
+}
+
+.right_view {
+	display: flex;
+	align-items: center;
+	padding-right: 24rpx;
+	gap: 24rpx;
+
+	image {
+		width: 48rpx;
+		height: 48rpx;
 	}
+
+}
+.contact-badge {
+	background-color: #ff4d4f;
+	border-radius: 20rpx;
+	padding-left: 8rpx;
+	padding-right: 8rpx;
+	height: 32rpx;
+	justify-content: center;
+	align-items: center;
+	min-width: 32rpx;
+	display: flex;
+}
+.contact-badge-text {
+	color: #ffffff;
+	font-size: 20rpx;
+	line-height: 20rpx;
 }
 </style>

+ 0 - 55
pages_im/pages/contact/groupList/GroupItem.vue

@@ -1,55 +0,0 @@
-<template>
-  <view @click="toGroupCard" class="group_item">
-    <my-avatar :src="groupInfo.faceURL" :isGroup="true" size="42" />
-    <view class="group_info">
-      <text class="group_name">{{ groupInfo.groupName }}</text>
-      <view class="group_details">
-        <text>{{ `${groupInfo.memberCount}人` }}</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import MyAvatar from "../../../components/MyAvatar/index.vue";
-export default {
-  name: "",
-  components: {
-    MyAvatar,
-  },
-  props: {
-    groupInfo: Object,
-  },
-  data() {
-    return {};
-  },
-  methods: {
-    toGroupCard() {
-      uni.navigateTo({
-        url: `/pages_im/pages/common/groupCard/index?sourceID=${this.groupInfo.groupID}`,
-      });
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.group_item {
-  @include vCenterBox();
-  padding: 24rpx 44rpx;
-
-  .group_info {
-    margin-left: 24rpx;
-
-    .group_name {
-      @include nomalEllipsis() max-width: 400rpx;
-    }
-
-    .group_details {
-      font-size: 28rpx;
-      color: #999;
-      margin-top: 8rpx;
-    }
-  }
-}
-</style>

+ 0 - 159
pages_im/pages/contact/groupList/index.vue

@@ -1,159 +0,0 @@
-<template>
-  <view class="group_list_container">
-    <custom-nav-bar title="我的群组"> </custom-nav-bar>
-    <view class="search_bar_wrap">
-      <u-search
-        class="search_bar"
-        shape="square"
-        placeholder="搜索"
-        @click="toSearch"
-        disabled
-        :showAction="false"
-      />
-    </view>
-
-    <u-tabs :scrollable="false" :list="tabList" @click="clickTab"></u-tabs>
-
-    <view
-      class="pane_row"
-      :style="{ transform: `translateX(${isMyCreate ? '0' : '-100%'})` }"
-    >
-      <view class="pane_content">
-        <u-list
-          v-if="getMyCreateGroupList.length > 0"
-          class="group_list"
-          :height="`${getListHeight}px`"
-        >
-          <u-list-item
-            v-for="group in getMyCreateGroupList"
-            :key="group.groupID"
-          >
-            <group-item :groupInfo="group" />
-          </u-list-item>
-        </u-list>
-        <u-empty v-else mode="list" />
-      </view>
-
-      <view class="pane_content">
-        <u-list
-          v-if="getMyJoinedGroupList.length > 0"
-          class="group_list"
-          :height="`${getListHeight}px`"
-        >
-          <u-list-item
-            v-for="group in getMyJoinedGroupList"
-            :key="group.groupID"
-          >
-            <group-item :groupInfo="group" />
-          </u-list-item>
-        </u-list>
-        <u-empty v-else mode="list" />
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { mapGetters } from "vuex";
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import GroupItem from "./GroupItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    GroupItem,
-  },
-  data() {
-    return {
-      keyword: "",
-      tabList: [
-        {
-          name: "我创建的",
-        },
-        {
-          name: "我加入的",
-        },
-      ],
-      isMyCreate: true,
-    };
-  },
-  computed: {
-    ...mapGetters(["storeGroupList", "storeCurrentUserID"]),
-    getMyCreateGroupList() {
-      return this.storeGroupList.filter(
-        (group) => group.ownerUserID === this.storeCurrentUserID,
-      );
-    },
-    getListHeight() {
-      const statusBar = uni.getWindowInfo().statusBarHeight;
-      const searchBar = 58;
-      const tabAndNavBar = 44 * 2;
-      const titleBar = 32;
-      return (
-        uni.getWindowInfo().safeArea.height -
-        statusBar -
-        searchBar -
-        tabAndNavBar -
-        titleBar
-      );
-    },
-    getMyJoinedGroupList() {
-      // console.log(this.storeGroupList.filter(group => group.ownerUserID !== this.storeCurrentUserID));
-      return this.storeGroupList.filter(
-        (group) => group.ownerUserID !== this.storeCurrentUserID,
-      );
-    },
-  },
-  mounted() {},
-  methods: {
-    clickTab({ index }) {
-      this.isMyCreate = index === 0;
-    },
-    toCreateGroup() {
-      uni.navigateTo({
-        url: `/pages_im/pages/common/createGroup/index`,
-      });
-    },
-    toSearch() {
-      uni.$u.route("/pages_im/pages/contact/searchJoinedGroup/index");
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.group_list_container {
-  @include colBox(false);
-  height: 100vh;
-  overflow: hidden;
-
-  .nav_right_action {
-    padding-right: 44rpx;
-  }
-
-  .search_bar_wrap {
-    height: 34px;
-    padding: 12px 22px;
-  }
-
-  .pane_row {
-    display: flex;
-    flex: 1;
-    transition: all 0.3s ease 0s !important;
-    border-top: 2rpx solid #e8eaef;
-    // overflow-x: hidden;
-
-    .pane_content {
-      @include colBox(false);
-      height: 100%;
-      flex: 0 0 100%;
-
-      .pane_title {
-        font-size: 14px;
-        color: #999;
-        padding: 6px 22px;
-        background-color: #f8f8f8;
-      }
-    }
-  }
-}
-</style>

+ 17 - 17
pages_im/pages/contact/index/components/ContactMenus.vue

@@ -66,13 +66,13 @@ export default {
 					icon: require('../../../../static/images/contact_new_friend.png'),
 					badge: this.storeUnHandleFriendApplicationNum
 				},
-				// {
-				// 	idx: 1,
-				// 	type: ContactMenuTypes.NewGroup,
-				// 	title: '新的群组',
-				// 	icon: require('../../../../static/images/contact_new_group.png'),
-				// 	badge: this.storeUnHandleGroupApplicationNum
-				// }
+				{
+					idx: 1,
+					type: ContactMenuTypes.NewGroup,
+					title: '新的群组',
+					icon: require('../../../../static/images/contact_new_group.png'),
+					badge: this.storeUnHandleGroupApplicationNum
+				}
 			];
 		},
 		getFriendsMenus() {
@@ -91,24 +91,24 @@ export default {
 				// 	icon: require('../../../../static/images/contact_my_group.png'),
 				// 	badge: 0
 				// }
-				// ,{
+				// {
 				// 	idx: 4,
 				// 	type: ContactMenuTypes.Lable,
 				// 	title: '标签',
-				// 	icon: require("../../../../static/images/contact_my_lable.png"),
+				// 	icon: require("static/images/contact_my_lable.png"),
 				// 	badge: 0
-				// }
+				// },
 			];
 		},
 		getMomentsMenus() {
 			return [
-				// {
-				// 	idx: 4,
-				// 	type: ContactMenuTypes.Moments,
-				// 	title: '朋友圈',
-				// 	icon: require('../../../../static/images/contact_moments.png'),
-				// 	badge: 0
-				// }
+				{
+					idx: 4,
+					type: ContactMenuTypes.Moments,
+					title: '朋友圈',
+					icon: require('../../../../static/images/contact_moments.png'),
+					badge: 0
+				}
 			];
 		}
 	},

+ 110 - 120
pages_im/pages/contact/index/index.vue

@@ -1,134 +1,124 @@
 <template>
-	<view class="contact_container">
-		<custom-nav-bar :title="isFromUser?'通讯录':''">
-			<view class="contact_title u-f" slot="left" v-if="!isFromUser" @tap="goBack">
-				<view class="u-f-ajc es-mr-10">
-					<u-icon size="20" name="arrow-left" color="#333333"></u-icon>
-				</view>
-				<view>通讯录</view>
+  <view class="contact_container">
+    <custom-nav-bar>
+		  <view @click="routeBack" class="contact_title" slot="left">
+			 <image class="back_icon" src="../../../static/images/common_left_arrow.png" style="width: 12px; height: 20px" alt="" srcset="" />
+		  </view>
+		  <view class="contact_action" slot="more">
+			<view class="search_icon" @click="toSearch">
+			  <image src="../../../static/images/common_search.png" alt="" srcset="" />
 			</view>
-			<view class="contact_action" slot="more">
-				<view class="search_icon" @click="toSearch">
-					<image src="../../../static/images/common_search.png" alt="" srcset="" />
-				</view>
-				<view @click="contactAddClick" class="search_icon">
-					<image src="../../../static/images/common_add.png" alt="" srcset="" />
-				</view>
-			</view>
-		</custom-nav-bar>
-
-		<contact-menus />
+			<!-- <view @click="contactAddClick" class="search_icon">
+			  <image src="../../../static/images/common_add.png" alt="" srcset="" />
+			</view> -->
+		  </view>
+    </custom-nav-bar>
 
-		<!-- <orgnization-row v-if="storeSelfInfo.members.length" ref="orgnizationRef" /> -->
+    <contact-menus />
 
-	</view>
+    <orgnization-row v-if="storeSelfInfo.members.length" ref="orgnizationRef" />
+	
+  </view>
 </template>
 
 <script>
-	import {
-		mapGetters
-	} from "vuex";
-	import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-	import ContactMenus from "./components/ContactMenus.vue";
-	import OrgnizationRow from "./components/OrgnizationRow.vue";
-	import UserItem from "../../../components/UserItem/index.vue";
+import { mapGetters } from "vuex";
+import CustomNavBar from "../../../components/CustomNavBar/index.vue";
+import ContactMenus from "./components/ContactMenus.vue";
+import OrgnizationRow from "./components/OrgnizationRow.vue";
+import UserItem from "../../../components/UserItem/index.vue";
 
-	export default {
-		components: {
-			CustomNavBar,
-			ContactMenus,
-			OrgnizationRow,
-			UserItem
-		},
-		data() {
-			return {
-				frequentContacts: [],
-				isFromUser: false
-			};
-		},
-		onLoad(options) {
-			const {
-				isFromUser
-			} = options;
-			this.isFromUser = isFromUser;
-		},
-		onShow() {
-			this.getFrequentContacts();
-			//this.$nextTick(() => this.$refs.orgnizationRef.getOrganizationData());
-		},
-		computed: {
-			...mapGetters(["storeSelfInfo"]),
-		},
-		methods: {
-			goBack() {
-				uni.navigateBack()
-			},
-			contactAddClick() {
-				uni.navigateTo({
-					url: "/pages_im/pages/contact/contactAdd/index",
-				});
-			},
-			routeBack() {
-				uni.switchTab({
-					// url: '/pages_im/pages/conversation/conversationList/index'
-					url: "/pages/index/index"
-				});
-			},
-			getFrequentContacts() {
-				const tmpData =
-					uni.getStorageSync(
-						`${this.$store.getters.storeCurrentUserID}_frequentContacts`,
-					) || [];
-				this.frequentContacts = [...tmpData].sort((a, b) => b - a);
-			},
-			userClick(item) {
-				uni.navigateTo({
-					url: `/pages_im/pages/common/userCard/index?sourceID=${item.userID}`,
-				});
-			},
-			toSearch() {
-				uni.$u.route("/pages_im/pages/common/globalSearch/index");
-			},
-		},
-	};
+export default {
+  components: {
+    CustomNavBar,
+    ContactMenus,
+    OrgnizationRow,
+    UserItem
+  },
+  data() {
+    return {
+      frequentContacts: [],
+    };
+  },
+  onShow() {
+    this.getFrequentContacts();
+    this.$nextTick(() => this.$refs.orgnizationRef.getOrganizationData());
+  },
+  computed: {
+    ...mapGetters(["storeSelfInfo"]),
+  },
+  methods: {
+    contactAddClick() {
+      uni.navigateTo({
+        url: "/pages_im/pages/contact/contactAdd/index",
+      });
+    },
+	routeBack(){
+		uni.switchTab({
+			url: '/pages_im/pages/conversation/conversationList/index'
+		});
+	},
+    getFrequentContacts() {
+      const tmpData =
+        uni.getStorageSync(
+          `${this.$store.getters.storeCurrentUserID}_frequentContacts`,
+        ) || [];
+      console.log(tmpData);
+      this.frequentContacts = [...tmpData].sort((a, b) => b - a);
+    },
+    userClick(item) {
+      uni.navigateTo({
+        url: `/pages_im/pages/common/userCard/index?sourceID=${item.userID}`,
+      });
+    },
+    toSearch() {
+      uni.$u.route("/pages_im/pages/common/globalSearch/index");
+    },
+  },
+};
 </script>
 
 <style lang="scss" scoped>
-	.contact_container {
-
-		background-color: #f6f6f6;
-
-		.contact_title {
-			padding-left: 44rpx;
-			font-size: 40rpx;
-			font-weight: 600;
-			color: #0c1c33;
-		}
-
-		.contact_action {
-			padding-right: 10rpx;
-			display: flex;
-
-			.search_icon {
-				margin: 0 16rpx;
+.contact_container {
+  @include colBox(false);
+  height: 100vh;
+  background-color: #f6f6f6;
+  .contact_title {
+    padding-left: 0rpx;
+    font-size: 40rpx;
+    font-weight: 600;
+    color: #0c1c33;
+  }
+  
+  .back_icon {
+  	padding: 24rpx;
+  	margin-left: 20rpx;
+  }
 
-				image {
-					width: 56rpx;
-					height: 56rpx;
-				}
-			}
-		}
+  .contact_action {
+    padding-right: 36rpx;
+    display: flex;
+    .search_icon {
+      margin: 0 16rpx;
+      image {
+        width: 56rpx;
+        height: 56rpx;
+      }
+    }
+  }
+  
+  
 
-		.list_title {
-			font-size: 24rpx;
-			color: #999;
-			margin-left: 44rpx;
-		}
+  .list_title {
+    font-size: 24rpx;
+    color: #999;
+    margin-left: 44rpx;
+  }
 
-		.user_list {
-			flex: 1;
-			margin-top: 24rpx;
-			background-color: #fff;
-		}
-	}
-</style>
+  .user_list {
+    flex: 1;
+    margin-top: 24rpx;
+    background-color: #fff;
+  }
+}
+</style>

+ 27 - 22
pages_im/pages/contact/searchAddedFriend/index.vue

@@ -1,23 +1,22 @@
 <template>
 	<view class="page_container">
-		<custom-nav-bar title="搜索好友" />
-		<view class="search_bar_wrap">
-			<u-search shape="square" placeholder="搜索好友" :inputStyle="{'font-size':'18px'}"  v-model="keyword" :showAction="false" @search="searchFriend" />
+		<custom-nav-bar title="我的好友" />
+		<view class="search_bar">
+			<u-search class="search_input" shape="round" :showAction="false" @search="searchFriend" placeholder="搜索好友"
+				v-model="keyword" />
 		</view>
-
-		<view v-show="searchResults.length > 0" class="search_title">我的好友</view>
-		<u-list v-show="searchResults.length > 0" class="user_list" height="1">
+		<u-list v-if="searchResults.length > 0" class="user_list" height="1">
 			<u-list-item v-for="user in searchResults" :key="user.userID">
 				<user-item :item="user" @itemClick="itemClick" />
 			</u-list-item>
 		</u-list>
-
-		<u-empty v-show="searchResults.length === 0" mode="search" />
+		<u-empty v-else mode="data" style="padding-top: 50%;" text="暂无数据" width="80" height="80"
+			icon="/pages_im/static/images/chat/empty.png" />
 	</view>
 </template>
 
 <script>
-import IMSDK, {  SessionType } from 'openim-uniapp-polyfill';
+import IMSDK, { SessionType } from 'openim-uniapp-polyfill';
 import CustomNavBar from '../../../components/CustomNavBar/index.vue';
 import UserItem from '../../../components/UserItem/index.vue';
 import { navigateToDesignatedConversation } from '../../../util/imCommon';
@@ -35,15 +34,21 @@ export default {
 	},
 	methods: {
 		searchFriend() {
-			const options = {
-				keywordList: [this.keyword],
-				isSearchUserID: false,
-				isSearchNickname: true,
-				isSearchRemark: true
-			};
-			IMSDK.asyncApi(IMSDK.IMMethods.SearchFriends, IMSDK.uuid(), options).then(({ data }) => {
-				this.searchResults = data;
+			if (!this.keyword) {
+				this.searchResults = [];
+				return;
+			}
+			
+			const friendList = this.$store.getters.storeFriendList || [];
+			const keyword = this.keyword.toLowerCase();
+			this.searchResults = friendList.filter(friend => {
+				const nickname = friend.nickname?.toLowerCase() || '';
+				const remark = friend.remark?.toLowerCase() || '';
+				const userID = friend.userID?.toLowerCase() || '';
+				return nickname.includes(keyword) || remark.includes(keyword) || userID.includes(keyword);
 			});
+			
+			console.log('searchFriend results:', this.searchResults);
 		},
 		itemClick(user) {
 			// uni.$u.route('/pages_im/pages/common/userCard/index', {
@@ -62,12 +67,12 @@ export default {
 .page_container {
 	@include colBox(false);
 	height: 100vh;
-	background-color: #f8f8f8;
+	background-color: #fff;
 
-	.search_bar_wrap {
-		height: 54px;
-		padding: 12px 22px;
-		background-color: #fff;
+	.search_bar {
+		width: 100%;
+		padding: 0 32rpx;
+		margin-top: 16rpx
 	}
 
 	.search_title {

+ 0 - 87
pages_im/pages/contact/searchJoinedGroup/index.vue

@@ -1,87 +0,0 @@
-<template>
-  <view class="page_container">
-    <custom-nav-bar title="搜索群组" />
-    <view class="search_bar_wrap">
-      <u-search
-        shape="square"
-        placeholder="搜索群组"
-        v-model="keyword"
-        :showAction="false"
-        @search="searchGroup"
-      />
-    </view>
-
-    <view v-show="searchResults.length > 0" class="search_title">
-      我的群组
-    </view>
-    <u-list v-show="searchResults.length > 0" class="group_list" height="1">
-      <u-list-item v-for="group in searchResults" :key="group.groupID">
-        <group-item :groupInfo="group" />
-      </u-list-item>
-    </u-list>
-
-    <u-empty v-show="searchResults.length === 0" mode="search" />
-  </view>
-</template>
-
-<script>
-import IMSDK from "openim-uniapp-polyfill";
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import GroupItem from "../groupList/GroupItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    GroupItem,
-  },
-  data() {
-    return {
-      keyword: "",
-      searchResults: [],
-    };
-  },
-  methods: {
-    searchGroup() {
-      const options = {
-        keywordList: [this.keyword],
-        isSearchGroupID: true,
-        isSearchGroupName: true,
-      };
-      IMSDK.asyncApi(IMSDK.IMMethods.SearchGroups, IMSDK.uuid(), options).then(
-        ({ data }) => {
-          this.searchResults = data;
-        },
-      );
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.page_container {
-  @include colBox(false);
-  height: 100vh;
-  background-color: #f8f8f8;
-
-  .search_bar_wrap {
-    height: 34px;
-    padding: 12px 22px;
-    background-color: #fff;
-  }
-
-  .search_title {
-    padding-top: 8px;
-    padding-left: 22px;
-    color: #999;
-    background-color: #fff;
-  }
-
-  .group_list {
-    flex: 1;
-    background-color: #fff;
-  }
-
-  .u-empty {
-    padding-top: 20vh;
-  }
-}
-</style>

+ 0 - 57
pages_im/pages/contact/searchUserOrGroup/index.vue

@@ -1,57 +0,0 @@
-<template>
-  <view class="search_group_container">
-    <custom-nav-bar title="搜索群组" />
-    <view class="search_bar_wrap">
-      <u-search
-        class="search_bar"
-        shape="square"
-        placeholder="搜索群组"
-        :showAction="false"
-        v-model="keyword"
-      />
-    </view>
-
-    <view class="search_results">
-      <u-empty mode="search" />
-    </view>
-  </view>
-</template>
-
-<script>
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import GroupItem from "../groupList/GroupItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    GroupItem,
-  },
-  data() {
-    return {
-      keyword: "",
-    };
-  },
-  mounted() {},
-  methods: {
-    userClick() {},
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.search_group_container {
-  @include colBox(false);
-  height: 100vh;
-
-  .search_bar_wrap {
-    height: 34px;
-    padding: 12px 22px;
-  }
-
-  .search_results {
-    flex: 1;
-    .group_list {
-      height: 100% !important;
-    }
-  }
-}
-</style>

+ 0 - 116
pages_im/pages/contact/switchJoinGroup/index.vue

@@ -1,116 +0,0 @@
-<template>
-  <view class="switch_join_container">
-    <custom-nav-bar title="加入群组" />
-
-    <view class="action_row">
-      <action-item
-        @click="actionClick(item)"
-        v-for="item in joinGroupMenus"
-        :action="item"
-        :key="item.idx">
-        <view
-          class="custom_icon"
-          :class="{ custom_icon_id: item.idx === 1 }"
-          slot="icon">
-          <image :src="item.icon" mode=""> </image>
-        </view>
-      </action-item>
-    </view>
-  </view>
-</template>
-
-<script>
-import { scanQrCodeResult } from "../../../util/imCommon";
-import CustomNavBar from "../../../components/CustomNavBar/index.vue";
-import ActionItem from "../contactAdd/ActionItem.vue";
-export default {
-  components: {
-    CustomNavBar,
-    ActionItem,
-  },
-  data() {
-    return {
-      joinGroupMenus: [
-        {
-          idx: 0,
-          title: "扫码加入",
-          desc: "扫描二维码名片",
-          icon: require("../../../static/images/switch_join_qr.png"),
-        },
-        {
-          idx: 1,
-          title: "群ID号加入",
-          desc: "向管理员或团队成员询问ID",
-          icon: require("../../../static/images/switch_join_id.png"),
-        },
-      ],
-    };
-  },
-  methods: {
-    actionClick({ idx }) {
-      if (idx) {
-        uni.navigateTo({
-          url: "/pages_im/pages/common/searchUserOrGroup/index?isSearchGroup=true",
-        });
-      } else {
-		  
-        // uni.scanCode({
-        //   scanType: ["qrCode"],
-        //   success: ({ result }) => scanQrCodeResult(result),
-        // });
-		
-		uni.navigateTo({
-			url: '/pages_watch/healthMonitoring/scanCode?typeFun=getIndexScanCode&isFromIm=1'
-		});
-		
-		
-      }
-    },
-  },
-};
-</script>
-
-<style lang="scss" scoped>
-.switch_join_container {
-  height: 100vh;
-  background-color: #f8f8f8;
-
-  .desc_title {
-    font-size: 24rpx;
-    color: #999;
-    padding: 24rpx 44rpx;
-  }
-
-  .action_row {
-    background-color: #fff;
-
-    .custom_icon {
-      @include centerBox();
-      width: 44px;
-      min-width: 44px;
-      height: 44px;
-      border-radius: 50%;
-      background-color: #5496eb;
-
-      image {
-        width: 20px;
-        height: 20px;
-      }
-
-      &_id {
-        background-color: #ffc563;
-      }
-    }
-
-    /deep/ .action_item {
-      align-items: start;
-    }
-
-    .action_item:last-child {
-      .bottom_line {
-        height: 0;
-      }
-    }
-  }
-}
-</style>

+ 403 - 444
pages_im/pages/conversation/chating/components/ChatingFooter/ChatingActionBar.vue

@@ -1,338 +1,298 @@
 <template>
 	<view class="chat_action_bar">
-		<u-row class="action_row">
-			<u-col v-for="item in actionList" :key="item.idx" @click="actionClick(item)" span="3">
-				<view class="action_item">
-					<image :src="item.icon" alt="" srcset="" />
-					<text class="action_item_title">{{ item.title }}</text>
+		<scroll-view :scroll-y="true" :show-scrollbar="false" class="action_scroll_view">
+			<view class="action_row">
+				<view v-for="item in actionList" :key="item.idx" @click="actionClick(item)" class="action_col">
+					<view class="action_item">
+						<image :src="item.icon" class="action_icon" mode="aspectFit" />
+						<text class="action_item_title">{{ item.title }}</text>
+					</view>
 				</view>
-			</u-col>
-		</u-row>
+			</view>
+		</scroll-view>
 	</view>
 </template>
 
 <script>
-	import {
-		luckyBagList,
-		createFdMiniLink,
-		receiveLuckyBag
-	} from '@/pages_im/api/luckyBag.js'
-	import {
-		mapGetters,
-		mapActions
-	} from 'vuex';
-	import {
-		ChatingFooterActionTypes,
-		ContactChooseTypes,
-		PageEvents
-	} from '../../../../../constant';
-	import {
-		AmapWebKey
-	} from '../../../../../common/config';
-	import IMSDK, {
-		IMMethods
-	} from 'openim-uniapp-polyfill';
-	import {
-		getPurePath
-	} from '../../../../../util/common';
-	import {
-		callingModule
-	} from '../../../../../util/imCommon';
-	import {
-		premissionCheck
-	} from "@/js_sdk/wa-permission/permission.js"
-	// import { gotoAppPermissionSetting,requestAndroidPermission } from "../../../../../util/permission";
+import { mapGetters, mapActions } from 'vuex';
+import { ChatingFooterActionTypes, ContactChooseTypes, PageEvents } from '../../../../../constant';
+import { AmapWebKey } from '../../../../../common/config';
+import IMSDK, { IMMethods } from 'openim-uniapp-polyfill';
+// import { getPurePath } from '../../../../../util/common';
+import { callingModule } from '../../../../../util/imCommon';
+import { premissionCheck } from "@/js_sdk/wa-permission/permission.js"
 
-	export default {
-		components: {},
-		data() {
-			return {
-				userID: '',
-				isUser: false,
-				readFilePermission: false,
-				actionList: [{
-						idx: 0,
-						type: ChatingFooterActionTypes.takePhoto,
-						title: '拍照',
-						icon: require('../../../../../static/images/chating_action_camera.png')
-					},
-					{
-						idx: 1,
-						type: ChatingFooterActionTypes.Album,
-						title: '相册',
-						icon: require('../../../../../static/images/chating_action_image.png')
-					},
-					{
-						idx: 2,
-						type: ChatingFooterActionTypes.Camera,
-						title: '视频',
-						icon: require('../../../../../static/images/take-photo.svg')
-					},
-					{
-						idx: 3,
-						type: ChatingFooterActionTypes.pickCamera,
-						title: '录像',
-						icon: require('../../../../../static/images/take-video.svg')
-					},
-					{
-						idx: 4,
-						type: ChatingFooterActionTypes.Call,
-						title: '语音通话',
-						icon: require('../../../../../static/images/audio-calling.svg')
-					},
-					{
-						idx: 5,
-						type: ChatingFooterActionTypes.VideoCall,
-						title: '视频通话',
-						icon: require('../../../../../static/images/chating_action_call.png')
-					}
-					// ,{
-					//   idx: 4,
-					//   type: ChatingFooterActionTypes.Call,
-					//   title: "语音通话",
-					//   icon: require("../../../../../static/images/audio-calling.svg"),
-					// },
-					//       {
-					//         idx: 5,
-					//         type: ChatingFooterActionTypes.VideoCall,
-					//         title: "视频通话",
-					//         icon: require("../../../../../static/images/chating_action_call.png"),
-					//       },
-					//       {
-					//         idx: 6,
-					//         type: ChatingFooterActionTypes.File,
-					//         title: "问诊订单",
-					//         icon: require("../../../../../static/images/chating_action_file.png"),
-					//       },
-					// {
-					//   idx: 4,
-					//   type: ChatingFooterActionTypes.Card,
-					//   title: "名片",
-					//   icon: require("../../../../../static/images/chating_action_card.png"),
-					// },
-					// {
-					//   idx: 5,
-					//   type: ChatingFooterActionTypes.Location,
-					//   title: "位置",
-					//   icon: require("../../../../../static/images/chating_action_location.png"),
-					// },
-				]
-			};
+
+export default {
+	components: {},
+	data() {
+		return {
+			userID: '',
+			isUser: false,
+			readFilePermission:false,
+			actionList: [
+				{
+					idx: 0,
+					type: ChatingFooterActionTypes.takePhoto,
+					title: '拍照',
+					icon: '/pages_im/static/images/new/take_pictures_icon.png'
+				},
+				{
+					idx: 1,
+					type: ChatingFooterActionTypes.Album,
+					title: '相册',
+					icon: '/pages_im/static/images/new/album_icon.png'
+				},
+				{
+					idx: 2,
+					type: ChatingFooterActionTypes.Camera,
+					title: '视频',
+					icon: '/pages_im/static/images/new/video_icon.png'
+				},
+				{
+					idx: 3,
+					type: ChatingFooterActionTypes.pickCamera,
+					title: '录像',
+					icon: '/pages_im/static/images/new/picture_recording_icon.png'
+				}
+				,{
+				  idx: 4,
+				  type: ChatingFooterActionTypes.Call,
+				  title: "语音通话",
+				  icon: "/pages_im/static/images/new/voice_call_icon.png",
+				},
+				{
+					idx: 5,
+					type: ChatingFooterActionTypes.VideoCall,
+					title: "视频通话",
+					icon: "/pages_im/static/images/new/video_call_icon.png",
+				 }
+				//{
+				//         idx: 6,
+				//         type: ChatingFooterActionTypes.File,
+				//         title: "问诊订单",
+				//         icon: require("../../../../../static/images/news/consultation_order_icon.png"),
+				// },
+				// {
+				//   idx: 4,
+				//   type: ChatingFooterActionTypes.Card,
+				//   title: "名片",
+				//   icon: require("../../../../../static/images/chating_action_card.png"),
+				// },
+				// {
+				//   idx: 12,
+				//   type: ChatingFooterActionTypes.Location,
+				//   title: "位置",
+				//   icon: require("../../../../../static/images/chating_action_location.png"),
+				// }
+			]
+		};
+	},
+	computed: {
+		...mapGetters(['timStore']),
+		imType() {
+			return this.timStore?.imType ?? '';
 		},
-		computed: {
-			...mapGetters(['timStore']),
-			imType() {
-				return this.timStore?.imType ?? '';
-			},
-			orderId() {
-				return this.timStore?.orderId ?? '';
-			},
-			orderType() {
-				return this.timStore?.orderType ?? '';
-			}
+		orderId() {
+			return this.timStore?.orderId ?? '';
 		},
-		mounted() {
-			let userID = this.$store.getters.storeCurrentConversation.userID;
+		orderType() {
+			return this.timStore?.orderType ?? '';
+		}
+	},
+	mounted() {
+		let conversation = this.$store.getters.storeCurrentConversation;
+		let userID = (conversation && conversation.userID) ? conversation.userID : '';
+		if (userID) {
 			this.userID = userID.substring(1);
 			this.isUser = userID.indexOf('U') != -1;
-			let isOpen = uni.getStorageSync("isOpen");
-			if (this.$companyUserIsLogin() && this.isUser) {
-				this.actionList.push({
-					idx: 12,
-					type: ChatingFooterActionTypes.LuckyBag,
-					title: '福袋',
-					icon: require('../../../../../static/images/luckyBag/lucky_bag_icon.png')
-				})
-				if (!!isOpen) {
-					this.actionList.push({
-						idx: 6,
-						type: ChatingFooterActionTypes.Live,
-						title: '直播',
-						icon: require('../../../../../static/images/chating_action_file.png')
-					});
-				}
-
-				// this.actionList.push({
-				// 	idx: 8,
-				// 	type: ChatingFooterActionTypes.StoreOrder,
-				// 	title: '药品订单',
-				// 	icon: require('../../../../../static/images/chating_action_file.png')
-				// });
-				// this.actionList.push({
-				// 	idx: 9,
-				// 	type: ChatingFooterActionTypes.Package,
-				// 	title: '疗法',
-				// 	icon: require('../../../../../static/images/chating_action_file.png')
-				// });
-
-				// this.actionList.push({
-				// 	idx: 10,
-				// 	type: ChatingFooterActionTypes.CouponPackage,
-				// 	title: '私域疗法券',
-				// 	icon: require('../../../../../static/images/chating_action_file.png')
-				// });
-
-				// this.actionList.push({
-				// 	idx: 11,
-				// 	type: ChatingFooterActionTypes.InquirySelect,
-				// 	title: '会诊',
-				// 	icon: require('../../../../../static/images/chating_action_file.png')
-				// });
-
-				return;
-			}
-
-			//if((this.imType==1&&this.orderType==2)||this.imType==2){  //语音通话
+		}
+		
+		if (this.$companyUserIsLogin() && this.isUser) {
 			// this.actionList.push({
 			// 	idx: 4,
 			// 	type: ChatingFooterActionTypes.Call,
 			// 	title: '语音通话',
 			// 	icon: require('../../../../../static/images/audio-calling.svg')
 			// });
-			//}
-			//if((this.imType==1&&this.orderType==2)||this.imType==2){  //视频通话
 			// this.actionList.push({
 			// 	idx: 5,
 			// 	type: ChatingFooterActionTypes.VideoCall,
 			// 	title: '视频通话',
 			// 	icon: require('../../../../../static/images/chating_action_call.png')
 			// });
-			// }
+			this.actionList.push({
+				idx: 6,
+				type: ChatingFooterActionTypes.Order,
+				title: '问诊订单',
+				icon: '/pages_im/static/images/new/consultation_order_icon.png'
+			});
+			this.actionList.push({
+				idx: 8,
+				type: ChatingFooterActionTypes.StoreOrder,
+				title: '药品订单',
+				icon: '/pages_im/static/images/new/drug_orders_icon.png'
+			});
+			this.actionList.push({
+				idx: 9,
+				type: ChatingFooterActionTypes.Package,
+				title: '疗法',
+				icon: '/pages_im/static/images/new/therapy_icon.png'
+			});
+			
+			this.actionList.push({
+				idx: 10,
+				type: ChatingFooterActionTypes.CouponPackage,
+				title: '私域疗法券',
+				icon: '/pages_im/static/images/new/therapy_voucher_icon.png'
+			});
+			
+			this.actionList.push({ 
+				idx: 11,
+				type: ChatingFooterActionTypes.InquirySelect,
+				title: '会诊',
+				icon: '/pages_im/static/images/new/consultation_doctors_icon.png'
+			});
+			
+			return;
+		}
 
-			// if (this.imType == 1) {
-			// 	//问诊订单
-			// 	this.actionList.push({
-			// 		idx: 6,
-			// 		type: ChatingFooterActionTypes.Order,
-			// 		title: '问诊订单',
-			// 		icon: require('../../../../../static/images/chating_action_file.png')
-			// 	});
-			// }
-			// if (this.imType == 2) {
-			// 	//视频通话
-			// 	this.actionList.push({
-			// 		idx: 7,
-			// 		type: ChatingFooterActionTypes.Follow,
-			// 		title: '随访单',
-			// 		icon: require('../../../../../static/images/chating_action_file.png')
-			// 	});
-			// 	this.actionList.push({
-			// 		idx: 8,
-			// 		type: ChatingFooterActionTypes.StoreOrder,
-			// 		title: '药品订单',
-			// 		icon: require('../../../../../static/images/chating_action_file.png')
-			// 	});
-			// }
-		},
+		//if((this.imType==1&&this.orderType==2)||this.imType==2){  //语音通话
+			// this.actionList.push({
+			// 	idx: 4,
+			// 	type: ChatingFooterActionTypes.Call,
+			// 	title: '语音通话',
+			// 	icon: require('../../../../../static/images/audio-calling.svg')
+			// });
+		//}
+		//if((this.imType==1&&this.orderType==2)||this.imType==2){  //视频通话
+			// this.actionList.push({
+			// 	idx: 5,
+			// 	type: ChatingFooterActionTypes.VideoCall,
+			// 	title: '视频通话',
+			// 	icon: require('../../../../../static/images/chating_action_call.png')
+			// });
+		//}
+		
+		if (this.imType == 1) {
+			//问诊订单
+			this.actionList.push({
+				idx: 6,
+				type: ChatingFooterActionTypes.Order,
+				title: '问诊订单',
+				icon: '/pages_im/static/images/new/consultation_order_icon.png'
+			});
+		}
+		if (this.imType == 2) {
+			//视频通话
+			this.actionList.push({
+				idx: 7,
+				type: ChatingFooterActionTypes.Follow,
+				title: '随访单',
+				icon: '/pages_im/static/images/chating_action_file.png'
+			});
+			this.actionList.push({
+				idx: 8,
+				type: ChatingFooterActionTypes.StoreOrder,
+				title: '药品订单',
+				icon: '/pages_im/static/image/new/drug_orders_icon.png'
+			});
+		}
+		
+	},
 
-		methods: {
-			async actionClick(action) {
-				switch (action.type) {
-					case ChatingFooterActionTypes.takePhoto:
-						if (uni.$u.os() != 'ios') {
-							this.requestPressmition();
-							if (!this.readFilePermission) {
-								return;
-							}
-						}
-						this.$emit('prepareMediaMessage', {
-							type: ChatingFooterActionTypes.Camera,
-							index: 0
-						});
-						break;
-					case ChatingFooterActionTypes.Album:
-						if (uni.$u.os() != 'ios') {
-							this.requestPressmition();
-							if (!this.readFilePermission) {
-								return;
-							}
-						}
-						this.$emit('prepareMediaMessage', {
-							type: ChatingFooterActionTypes.Album,
-							index: 0
-						});
-						break;
-					case ChatingFooterActionTypes.Camera:
-						if (uni.$u.os() != 'ios') {
-							this.requestPressmition();
-							if (!this.readFilePermission) {
-								return;
-							}
-						}
-						this.$emit('prepareMediaMessage', {
-							type: ChatingFooterActionTypes.Album,
-							index: 1
-						});
-						break;
-					case ChatingFooterActionTypes.pickCamera:
-						if (uni.$u.os() != 'ios') {
-							this.requestPressmition();
-							if (!this.readFilePermission) {
-								return;
-							}
+	methods: {
+		async actionClick(action) {
+			switch (action.type) {
+				case ChatingFooterActionTypes.takePhoto:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Camera, index: 0 });
+					break;
+				case ChatingFooterActionTypes.Album:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Album, index: 0 });
+					break;
+				case ChatingFooterActionTypes.Camera:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Album, index: 1 });
+					break;
+				case ChatingFooterActionTypes.pickCamera:
+					if (uni.$u.os() != 'ios') {
+						this.requestPressmition();
+						if(!this.readFilePermission){
+							return;
 						}
-						this.$emit('prepareMediaMessage', {
-							type: ChatingFooterActionTypes.Camera,
-							index: 1
-						});
-						break;
-					case ChatingFooterActionTypes.Call:
-						if (!this.$store.getters.storeCurrentConversation.groupID) {
+					}
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Camera, index: 1 });
+					break;
+				case ChatingFooterActionTypes.Call:
+					if (!this.$store.getters.storeCurrentConversation.groupID) {
+						uni.$emit(PageEvents.RtcCall, 'audio');
+						return;
+					}
+					IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
+						if (data.invitation) {
+							uni.showModal({
+								title: '提示',
+								content: '群通话进行中,是否直接加入?',
+								confirmText: '确认',
+								cancelText: '取消',
+								success: (res) => {
+									if (res.confirm) {
+										callingModule.joinRoomLiveChat(data);
+									}
+								}
+							});
+						} else {
 							uni.$emit(PageEvents.RtcCall, 'audio');
-							return;
 						}
-						IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters
-							.storeCurrentConversation.groupID).then(({
-							data
-						}) => {
-							if (data.invitation) {
-								uni.showModal({
-									title: '提示',
-									content: '群通话进行中,是否直接加入?',
-									confirmText: '确认',
-									cancelText: '取消',
-									success: (res) => {
-										if (res.confirm) {
-											callingModule.joinRoomLiveChat(data);
-										}
+					});
+					break;
+				case ChatingFooterActionTypes.VideoCall:
+					if (!this.$store.getters.storeCurrentConversation.groupID) {
+						uni.$emit(PageEvents.RtcCall, 'video');
+						return;
+					}
+					IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
+						if (data.invitation) {
+							uni.showModal({
+								title: '提示',
+								content: '群通话进行中,是否直接加入?',
+								confirmText: '确认',
+								cancelText: '取消',
+								success: (res) => {
+									if (res.confirm) {
+										callingModule.joinRoomLiveChat(data);
 									}
-								});
-							} else {
-								uni.$emit(PageEvents.RtcCall, 'audio');
-							}
-						});
-						break;
-					case ChatingFooterActionTypes.VideoCall:
-						if (!this.$store.getters.storeCurrentConversation.groupID) {
+								}
+							});
+						} else {
 							uni.$emit(PageEvents.RtcCall, 'video');
-							return;
 						}
-						IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters
-							.storeCurrentConversation.groupID).then(({
-							data
-						}) => {
-							if (data.invitation) {
-								uni.showModal({
-									title: '提示',
-									content: '群通话进行中,是否直接加入?',
-									confirmText: '确认',
-									cancelText: '取消',
-									success: (res) => {
-										if (res.confirm) {
-											callingModule.joinRoomLiveChat(data);
-										}
-									}
-								});
-							} else {
-								uni.$emit(PageEvents.RtcCall, 'video');
-							}
-						});
-						break;
+					});
+					break;
 
-					case ChatingFooterActionTypes.Order: {
+				case ChatingFooterActionTypes.Order:
+					this.$emit('enterSubPage');
+					{
 						if (this.$companyUserIsLogin()) {
 							uni.navigateTo({
-								url: '/pages/company/inquiryOrderList?userId=' + this.userID
+								url: '/pages/company/inquiryOrderIMList?userId=' + this.userID
 							});
 						} else {
 							uni.navigateTo({
@@ -341,17 +301,14 @@
 						}
 					}
 					break;
-					case ChatingFooterActionTypes.Follow:
-						uni.navigateTo({
-							url: '/pages/user/followDetails?followId=' + this.timStore.followId
-						});
-						break;
-					case ChatingFooterActionTypes.Live:
-						uni.navigateTo({
-							url: '/pages/course/video/livingList'
-						});
-						break;
-					case ChatingFooterActionTypes.StoreOrder: {
+				case ChatingFooterActionTypes.Follow:
+					this.$emit('enterSubPage');
+					uni.navigateTo({
+						url: '/pages/user/followDetails?followId=' + this.timStore.followId
+					});
+					break;
+				case ChatingFooterActionTypes.StoreOrder:
+					{
 						if (this.$companyUserIsLogin()) {
 							uni.navigateTo({
 								url: '/pages/company/storeOrderList?userId=' + this.userID
@@ -364,7 +321,7 @@
 					}
 					break;
 
-					case ChatingFooterActionTypes.Package: //疗法
+				case ChatingFooterActionTypes.Package:  //疗法
 					{
 						if (this.$companyUserIsLogin()) {
 							uni.navigateTo({
@@ -372,159 +329,161 @@
 							});
 						}
 					}
-					break;
-
-					case ChatingFooterActionTypes.CouponPackage: {
+				break;
+					
+				case ChatingFooterActionTypes.CouponPackage:
+					{
 						if (this.$companyUserIsLogin()) {
 							uni.navigateTo({
 								url: '/pages/company/couponList?couponType=5&isIM=1'
 							});
 						}
 					}
-					break;
-
-					case ChatingFooterActionTypes.InquirySelect: {
+				break;
+					
+				case ChatingFooterActionTypes.InquirySelect:
+					{
 						if (this.$companyUserIsLogin()) {
-							let companyId = uni.getStorageSync('companyId');
-							let companyUserId = uni.getStorageSync('companyUserId');
+							let companyId=uni.getStorageSync('companyId');
+							let companyUserId=uni.getStorageSync('companyUserId');	
 							uni.navigateTo({
-								url: '/pages/store/inquirySelectType?companyId=' + companyId +
-									'&companyUserId=' + companyUserId + '&isIM=1'
+								url: '/pages/store/inquirySelectType?companyId='+companyId+'&companyUserId='+companyUserId+'&isIM=1'
 							});
 						}
 					}
-					break;
+				break;
 
-					case ChatingFooterActionTypes.Card:
-						uni.navigateTo({
-							url: `/pages_im/pages/common/contactChoose/index?type=${ContactChooseTypes.Card}`
-						});
-						break;
+				case ChatingFooterActionTypes.Card:
+					uni.navigateTo({
+						url: `/pages_im/pages/common/contactChoose/index?type=${ContactChooseTypes.Card}`
+					});
+					break;
 
-					case ChatingFooterActionTypes.File:
-						if (uni.$u.os() != 'ios') {
-							this.requestPressmition();
-							if (!this.readFilePermission) {
-								return;
-							}
+				case ChatingFooterActionTypes.File:
+					if (uni.$u.os() != 'ios') {
+						this.requestPressmition();
+						if(!this.readFilePermission){
+							return;
 						}
-						IMSDK.pickFile().then(async (path) => {
-							console.log(path);
-							const idx = path.lastIndexOf('/');
-							const fileName = path.slice(idx + 1);
-							const message = await IMSDK.asyncApi(IMMethods.CreateFileMessageFromFullPath,
-								IMSDK.uuid(), {
-									filePath: getPurePath(path),
-									fileName
-								});
-							this.$emit('sendMessage', message);
+					}
+					IMSDK.pickFile().then(async (path) => {
+						console.log(path);
+						const idx = path.lastIndexOf('/');
+						const fileName = path.slice(idx + 1);
+						const message = await IMSDK.asyncApi(IMMethods.CreateFileMessageFromFullPath, IMSDK.uuid(), {
+							filePath: this.getPurePath(path),
+							fileName
 						});
-						break;
-					case ChatingFooterActionTypes.Location:
-						uni.chooseLocation({
-							success: async (res) => {
-								if (res) {
-									const options = {
-										name: res.name,
-										latng: `${res.latitude},${res.longitude}`,
-										addr: res.address,
-										city: res.address,
-										module: 'locationPicker',
-										latitude: res.latitude,
-										longitude: res.longitude,
-										url: `https://restapi.amap.com/v3/staticmap?size=600*300&markers=-1,https://cache.amap.com/lbs/static/cuntom_marker1.png,0:${res.longitude},${res.latitude}&key=${AmapWebKey}`
-									};
-									const message = await IMSDK.asyncApi(IMMethods.CreateLocationMessage,
-										IMSDK.uuid(), {
-											description: JSON.stringify(options),
-											longitude: res.longitude,
-											latitude: res.latitude
-										});
-									this.$emit('sendMessage', message);
-								} else {
-									uni.$u.toast('获取位置失败');
-								}
-							},
-							fail: ({
-								errMsg
-							}) => {
-								if (!errMsg.includes('cancel')) {
-									uni.$u.toast('获取位置失败');
-								}
+						this.$emit('sendMessage', message);
+					});
+					break;
+				case ChatingFooterActionTypes.Location:
+					this.$emit('enterSubPage');
+					uni.chooseLocation({
+						success: async (res) => {
+							if (res) {
+								const options = {
+									name: res.name,
+									latng: `${res.latitude},${res.longitude}`,
+									addr: res.address,
+									city: res.address,
+									module: 'locationPicker',
+									latitude: res.latitude,
+									longitude: res.longitude,
+									url: `https://restapi.amap.com/v3/staticmap?size=600*300&markers=-1,https://cache.amap.com/lbs/static/cuntom_marker1.png,0:${res.longitude},${res.latitude}&key=${AmapWebKey}`
+								};
+								const message = await IMSDK.asyncApi(IMMethods.CreateLocationMessage, IMSDK.uuid(), {
+									description: JSON.stringify(options),
+									longitude: res.longitude,
+									latitude: res.latitude
+								});
+								this.$emit('sendMessage', message);
+							} else {
+								uni.$u.toast('获取位置失败');
 							}
-						});
-						break;
-					case ChatingFooterActionTypes.LuckyBag:
-						uni.$emit(PageEvents.LuckyBag);
-						// 				luckyBagList({companyId:12}).then(res=>{
-						// 					console.log(res);
-						// 				})
-						// 				return
-						// 				let params ={
-						//   "companyId": 12,
-						//   "luckyBagId": 1,
-						//   "companyUserId": 8585
-						// }
-						// 				createFdMiniLink(params).then(res=>{
-						// 					console.log(res);
-						// })
-						// 				let params ={
-						//   "userId": 77632,
-						//   "recordId": 2
-						// }
-						// 				receiveLuckyBag(params).then(res=>{
-						// 					console.log(res);
-						// 				})
-						// 					if (uni.$u.os() != 'ios') {
-						// 						this.requestPressmition();
-						// 						if(!this.readFilePermission){
-						// 							return;
-						// 						}
-						// 					}
-						break;
-					default:
-						break;
-				}
-			},
-
-			async requestPressmition() {
-				let result = await premissionCheck("EXTERNAL_STORAGE");
-				console.log("premission result:" + result);
-				if (result == 1) {
-					this.readFilePermission = true;
-				}
+						},
+						fail: ({ errMsg }) => {
+							if (!errMsg.includes('cancel')) {
+								uni.$u.toast('获取位置失败');
+							}
+						}
+					});
+					break;
+				default:
+					break;
 			}
-
+		},
+	
+	    async requestPressmition(){
+	    	let result = await premissionCheck("EXTERNAL_STORAGE");
+	    	console.log("premission result:"+result);
+	    	if(result == 1) {
+	    		this.readFilePermission=true;
+	    	}
+	    },
+		getPurePath(path){
+			  const prefix = "file://";
+			  const relativeRrefix = "_doc/";
+			  if (path.includes(prefix)) {
+				path = path.replace(prefix, "");
+			  }
+			  if (path.includes(relativeRrefix)) {
+				path = plus.io.convertLocalFileSystemURL(path);
+			  }
+			  return path;
 		}
-	};
+	
+	}
+};
 </script>
 
-<style lang="scss" scoped>
-	.chat_action_bar {
-		position: relative;
-		background: #f0f2f6;
-		padding: 24rpx 36rpx;
+<style scoped>
+.chat_action_bar {
+	width: 750rpx;
+	height: 550rpx;
+	background-color: #F5F7FA;
+	border-top-width: 1px;
+	border-top-color: #EAEBED;
+}
 
-		.action_row {
-			flex-wrap: wrap;
-			margin-bottom: 24rpx;
-		}
+.action_scroll_view {
+	width: 750rpx;
+	flex-direction: column;
+	padding: 24rpx 0;
+}
 
-		.action_item {
-			@include centerBox();
-			flex-direction: column;
-			margin-top: 24rpx;
+.action_row {
+	flex-direction: row;
+	flex-wrap: wrap;
+	width: 750rpx;
+}
 
-			image {
-				width: 96rpx;
-				height: 96rpx;
-			}
+.action_col {
+	width: 187rpx;
+	align-items: center;
+	justify-content: center;
+	margin-bottom: 24rpx;
+	
+}
 
-			&_title {
-				font-size: 24rpx;
-				color: #999;
-				margin-top: 6rpx;
-			}
-		}
-	}
-</style>
+.action_item {
+	align-items: center;
+	justify-content: center;
+	background: #fff;
+	border-radius: 10px;
+	width: 130rpx;
+	padding:8px 0;
+	margin:5px 0;
+}
+
+.action_icon {
+	width: 50rpx;
+	height: 50rpx;
+	margin-bottom: 10rpx;
+}
+
+.action_item_title {
+	font-size: 24rpx;
+	color: #666666;
+}
+</style>

+ 36 - 16
pages_im/pages/conversation/chating/components/ChatingFooter/ChatingEmojiBar.vue

@@ -1,16 +1,17 @@
 <template>
   <view class="chat_emoji_bar">
-    <u-row class="emoji_row">
-      <u-col
-        @click="clickEmoji(emoji)"
-        v-for="emoji in emojiList"
-        :key="emoji"
-        :id="emoji"
-        span="2"
-        style="display: flex;justify-content: center;align-items: center;">
-        <text style="font-size: 20px;margin-bottom: 6px;">{{ emoji }}</text>
-      </u-col>
-    </u-row>
+    <scroll-view :scroll-y="true" class="emoji_scroll_view">
+      <view class="emoji_row">
+        <view
+          @click="clickEmoji(emoji)"
+          v-for="emoji in emojiList"
+          :key="emoji"
+          :id="emoji"
+          class="emoji_col">
+          <text class="emoji_text">{{ emoji }}</text>
+        </view>
+      </view>
+    </scroll-view>
   </view>
 </template>
 
@@ -40,12 +41,31 @@ export default {
 
 <style lang="scss" scoped>
 .chat_emoji_bar {
-  padding: 24rpx 36rpx;
+  padding: 0;
   position: relative;
+  height: 550rpx;
+  background-color: #f0f2f6;
+}
+
+.emoji_scroll_view {
+  flex: 1;
+  padding: 24rpx 36rpx;
+}
+
+.emoji_row {
+  flex-direction: row;
+  flex-wrap: wrap;
+  padding-bottom: 24rpx;
+}
+
+.emoji_col {
+  width: 113rpx;
+  height: 80rpx;
+  align-items: center;
+  justify-content: center;
+}
 
-  .emoji_row {
-    flex-wrap: wrap;
-    margin-bottom: 12rpx;
-  }
+.emoji_text {
+  font-size: 50rpx;
 }
 </style>

+ 0 - 175
pages_im/pages/conversation/chating/components/ChatingFooter/CollectLuckyBag.vue

@@ -1,175 +0,0 @@
-<template>
-	<view>
-		<u-popup :show="show" mode="center" bgColor="transparent" @close="close"
-			:customStyle="{background: 'transparent'}">
-			<view class="receive-lucky-bag-container">
-				<!-- 红包主体 -->
-				<view class="red-packet-card">
-					<!-- 顶部用户信息 -->
-					<view class="user-info">
-						<image class="avatar" :src="luckyBagExtension.avatar || ''" mode="aspectFill"></image>
-						<text class="username">{{ luckyBagExtension.nickName || '' }}发出的福袋</text>
-					</view>
-
-					<!-- 标题 -->
-					<view class="title">专属兑换福袋</view>
-
-					<!-- 底部圆形“领”按钮 -->
-					<view class="open-btn-container" @click="openBag">
-						<image src="/pages_im/static/images/luckyBag/collar_btn.png" mode=""></image>
-					</view>
-				</view>
-
-				<!-- 关闭按钮 -->
-				<view class="close-btn" @click="close">
-					<image class="es-icon-64" src="/pages_im/static/images/luckyBag/close_tc_icon.png" mode=""></image>
-				</view>
-			</view>
-		</u-popup>
-	</view>
-</template>
-
-<script>
-	import IMSDK, {
-		IMMethods,
-		MessageStatus
-	} from 'openim-uniapp-polyfill';
-	import {
-		mapGetters,
-		mapActions
-	} from 'vuex';
-	import {
-		receiveLuckyBag
-	} from '@/pages_im/api/luckyBag.js'
-	export default {
-		name: 'ReceiveLuckBag',
-		props: {
-			show: {
-				type: Boolean,
-				default: false
-			},
-			luckyBagExtension: {
-				type: Object,
-				default: {}
-			}
-		},
-		methods: {
-			close() {
-				this.$emit('close');
-			},
-			async openBag() {
-				let userInfo = uni.getStorageSync('userInfo')
-				const userId = userInfo && JSON.parse(userInfo).userId
-				let params = {
-					userId,
-					recordId: this.luckyBagExtension.recordId
-				}
-				const res = await receiveLuckyBag(params)
-				if (res.code == 200) {
-					uni.showToast({
-						icon: 'non',
-						title: '领取成功!'
-					})
-					setTimeout(() => {
-						this.close()
-					}, 1000)
-				} else {
-					uni.showToast({
-						icon: 'non',
-						title: res.msg
-					})
-				}
-			}
-		}
-	}
-</script>
-
-<style lang="scss" scoped>
-	.receive-lucky-bag-container {
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		justify-content: center;
-
-
-		.red-packet-card {
-			width: 600rpx;
-			height: 800rpx;
-			border-radius: 40rpx;
-			position: relative;
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			padding-top: 120rpx;
-			overflow: hidden;
-			background-image: url('/pages_im/static/images/luckyBag/collar_lucky_bag_bg.png');
-			background-size: 100% 100%;
-			background-repeat: no-repeat;
-
-			.user-info {
-				position: relative;
-				z-index: 1;
-				display: flex;
-				align-items: center;
-				margin-bottom: 40rpx;
-
-				.avatar {
-					width: 60rpx;
-					height: 60rpx;
-					border-radius: 10rpx;
-					margin-right: 16rpx;
-					border: 2rpx solid rgba(255, 255, 255, 0.3);
-				}
-
-				.username {
-					font-size: 30rpx;
-					color: #FFE4C4;
-					font-weight: 500;
-				}
-			}
-
-			.title {
-				position: relative;
-				z-index: 1;
-				font-size: 52rpx;
-				color: #FFE4C4;
-				font-weight: bold;
-				margin-bottom: 20rpx;
-				letter-spacing: 2rpx;
-			}
-
-			.open-btn-container {
-				position: absolute;
-				bottom: 50rpx;
-				z-index: 10;
-
-				image {
-					width: 196rpx;
-					height: 196rpx;
-				}
-			}
-
-			// 底部弧形装饰
-			&::after {
-				content: '';
-				position: absolute;
-				bottom: 0;
-				left: 0;
-				width: 100%;
-				height: 200rpx;
-				background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.1) 100%);
-				border-radius: 0 0 40rpx 40rpx;
-				z-index: 0;
-			}
-		}
-
-		.close-btn {
-			margin-top: 60rpx;
-			opacity: 0.8;
-
-			&:active {
-				opacity: 0.6;
-			}
-		}
-	}
-</style>

+ 133 - 253
pages_im/pages/conversation/chating/components/ChatingFooter/CustomEditor.vue

@@ -1,277 +1,157 @@
 <template>
-  <view
-    :insertImageFlag="insertImageFlag"
-    :insertAtFlag="insertAtFlag"
-    :insertEmojiFlag="insertEmojiFlag"
-    :change:insertEmojiFlag="input.insertEmojiFlagUpdate"
-    :change:insertAtFlag="input.insertAtFlagUpdate"
-    :change:insertImageFlag="input.insertImageFlagUpdate"
-    class="editor_wrap">
-    <editor
-      :placeholder="placeholder"
-      id="editor2"
-      @ready="editorReady"
-      @focus="editorFocus"
-      @blur="editorBlur"
-      @input="editorInput"/>
-    <view class="canvas_container">
-      <canvas
-        v-if="canvasData.show"
-        canvas-id="atCanvas"
-        :style="{ width: canvasData.width }"
-        id="atCanvas"
-      ></canvas>
-    </view>
-  </view>
+	<view class="editor_wrap">
+		<textarea
+			class="editor_component"
+			id="editor2"
+			ref="editor2"
+			:value="textValue"
+			:placeholder="placeholder"
+			:maxlength="-1"
+			:adjust-position="false"
+			:show-confirm-bar="false"
+			:auto-height="true"
+			:disable-default-padding="true"
+			@input="onInput"
+			@focus="onFocus"
+			@blur="onBlur"
+			@confirm="onConfirm"
+		/>
+	</view>
 </template>
 
 <script>
-import { html2Text } from "../../../../../util/common";
-export default {
-  props: {
-    placeholder: {
-      type: String,
-      default: "",
-    },
-  },
-  data() {
-    return {
-      editorCtx: null,
-      canvasData: {
-        width: 0,
-        show: false,
-      },
-      imageData: {},
-      insertImageFlag: null,
-      insertAtFlag: null,
-      lastStr: "",
-      emojiData: "",
-      insertEmojiFlag: null
-    };
-  },
-  methods: {
-    editorReady() {
-      uni
-        .createSelectorQuery()
-        .select("#editor2")
-        .context((res) => {
-          this.$emit("ready", res);
-          this.editorCtx = res.context;
-        }).exec();
-    },
-    insertImage(imageData) {
-      this.imageData = imageData;
-      this.insertImageFlag = true;
-    },
-    insertEmoji(emoji) {
-      this.$nextTick(() => {
-        this.insertEmojiFlag = true;
-        this.emojiData = emoji;
-      });
-    },
-    internalInsertEmoji() {
-      this.editorCtx.insertText({
-        text: this.emojiData,
-        complete: () => {
-          this.insertEmojiFlag = false;
-          this.emojiData = null
-        },
-      });
-    },
-    internalInsertImage() {
-      this.editorCtx.insertImage({
-        ...this.imageData,
-        complete: () => {
-          this.insertImageFlag = false;
-          this.insertAtFlag = null;
-          // plus.key.showSoftKeybord()
-          // this.setDraftTextItem();
-        },
-      });
-    },
-    internalInsertAtEl({ text, width, userID, nickname }) {
-      this.canvasData.width = `${width}px`;
-      this.canvasData.show = true;
-      setTimeout(() => {
-        const ctx = uni.createCanvasContext("atCanvas");
-        const fontSize = 14;
-        ctx.setFontSize(fontSize);
-        ctx.setFillStyle("#3e44ff");
-        ctx.fillText(text, 0, 16);
-        ctx.draw();
-        this.canvasToTempFilePath(userID, nickname, width);
-      }, 20);
-    },
-    createCanvasData(userID, nickname) {
-      this.$nextTick(() => {
-        this.insertAtFlag = {
-          userID,
-          nickname,
-        };
-      });
-    },
-    canvasToTempFilePath(sendID, senderNickname, width) {
-      uni.canvasToTempFilePath({
-        canvasId: "atCanvas",
-        success: (res) => {
-          this.insertImage({
-            src: res.tempFilePath,
-            width,
-            height: "20px",
-            data: {
-              sendID,
-              senderNickname,
-            },
-            extClass: "at_el",
-          });
-          uni.createCanvasContext("atCanvas").clearRect();
-        },
-      });
-    },
-    editorFocus() {
-      this.$emit("focus");
-    },
-    editorBlur() {
-      this.$emit("blur");
-    },
-    editorInput(e) {
-      let str = e.detail.html;
-      const oldArr = (this.lastStr ?? '').split("");
-      let contentStr = str;
-      oldArr.forEach((str) => {
-        contentStr = contentStr.replace(str, "");
-      });
-      contentStr = html2Text(contentStr);
-      if (contentStr === "@") {
-        this.$emit("tryAt");
-      }
-      this.$emit("input", e);
-      this.lastStr = e.detail.html;
-    },
-  },
-};
-</script>
-
-<script module="input" lang="renderjs">
 export default {
+	props: {
+		placeholder: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			textValue: '',
+			cursorPos: -1,
+		};
+	},
+	mounted() {
+		this.$nextTick(() => {
+			const mockCtx = {
+				clear: () => {
+					this.textValue = '';
+					this.$emit('input', { detail: { html: '' } });
+				},
+				undo: () => {
+					// nvue textarea 暂不支持撤销
+				}
+			};
+			this.$emit('ready', { context: mockCtx });
+		});
+	},
 	methods: {
-    insertEmojiFlagUpdate(newValue, oldValue, ownerVm, vm) {
-			if (newValue === null) {
-				return;
-			}
-			if (newValue) {
-				this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
-				ownerVm.callMethod('internalInsertEmoji')
-			} else {
-				this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
-			}
-		},
-		insertImageFlagUpdate(newValue, oldValue, ownerVm, vm) {
-			if (newValue === null) {
-				return;
-			}
-			if (newValue) {
-				this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
-				ownerVm.callMethod('internalInsertImage')
-			} else {
-				this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
+		/**
+		 * 输入事件处理
+		 */
+		onInput(e) {
+			this.textValue = e.detail.value;
+			this.cursorPos = e.detail.cursor;
+			
+			// 保持与 editor 相同的事件格式
+			this.$emit('input', { detail: { html: this.textValue } });
+
+			// 检测 @ 输入
+			if (e.detail.cursor > 0) {
+				const lastChar = this.textValue.slice(e.detail.cursor - 1, e.detail.cursor);
+				if (lastChar === '@') {
+					this.$emit('tryAt');
+				}
 			}
 		},
-		insertAtFlagUpdate(newValue, oldValue, ownerVm, vm){
-			if (newValue === null) {
-				return;
-			}
-			if (newValue) {
-				const data = this.truncateText(`@${newValue.nickname}`,120)
-				ownerVm.callMethod('internalInsertAtEl', {...data,...newValue})
-			}
-		},
-		truncateText(text, maxWidth) {
-			const container = document.createElement("div");
-			container.style.width = "auto";
-			container.style.overflow = "hidden";
-			container.style.textOverflow = "ellipsis";
-			container.style.whiteSpace = "nowrap";
-			container.style.position = "absolute";
-			container.style.visibility = "hidden";
 
-			const textNode = document.createTextNode(text);
-			container.appendChild(textNode);
-
-			document.body.appendChild(container);
+		/**
+		 * 聚焦事件
+		 */
+		onFocus(e) {
+			this.$emit('focus');
+		},
 
-			const isOverflowing = container.scrollWidth > maxWidth;
+		/**
+		 * 失焦事件
+		 */
+		onBlur(e) {
+			this.cursorPos = e.detail.cursor;
+			this.$emit('blur');
+		},
+		
+		/**
+		 * 确认事件(如点击键盘完成)
+		 */
+		onConfirm(e) {
+			// 如果需要处理回车发送,可以在这里添加逻辑
+		},
 
-			if (!isOverflowing) {
-				const width = container.clientWidth + 4
-				document.body.removeChild(container);
-				return {
-					text,
-					width
-				};
+		/**
+		 * 插入表情(文本形式)
+		 */
+		insertEmoji(emoji) {
+			if (this.cursorPos < 0) {
+				this.textValue += emoji;
+			} else {
+				const left = this.textValue.substring(0, this.cursorPos);
+				const right = this.textValue.substring(this.cursorPos);
+				this.textValue = left + emoji + right;
+				this.cursorPos += emoji.length;
 			}
+			this.$emit('input', { detail: { html: this.textValue } });
+		},
 
-			container.style.width = maxWidth + "px";
-			container.style.visibility = "visible";
-
-			let truncatedText = text;
-			while (container.scrollWidth > maxWidth) {
-			truncatedText = truncatedText.slice(0, -1);
-			container.textContent = truncatedText + "...";
+		/**
+		 * 插入 @ 人员(文本形式,替代原本的 canvas 图片方案)
+		 */
+		createCanvasData(userID, nickname) {
+			const atText = `@${nickname} `;
+			if (this.cursorPos < 0) {
+				this.textValue += atText;
+			} else {
+				// 如果光标前已经是 @,则替换它?
+				// 简单起见,直接插入
+				const left = this.textValue.substring(0, this.cursorPos);
+				const right = this.textValue.substring(this.cursorPos);
+				this.textValue = left + atText + right;
+				this.cursorPos += atText.length;
 			}
-
-			document.body.removeChild(container);
-			return {
-				text: `${truncatedText}...`,
-				width: maxWidth + 4
-			};
+			this.$emit('input', { detail: { html: this.textValue } });
+		},
+		
+		/**
+		 * 聚焦方法
+		 */
+		focus() {
+			// nvue textarea 通常无法通过方法聚焦,依赖属性 focus
+			// 如果需要,可以在 data 中添加 focus 属性绑定到 textarea
 		}
-	},
-}
+	}
+};
 </script>
 
 <style lang="scss" scoped>
 .editor_wrap {
-  position: relative;
+	position: relative;
+    flex: 1;
+    flex-direction: column;
 }
 
 #editor2 {
-  background-color: #fff;
-  min-height: 30px;
-  max-height: 120px;
-  height: auto;
-  word-break: break-all;
-}
-
-/deep/.ql-editor {
-  img {
-    vertical-align: sub !important;
-  }
-
-  p {
-    padding: 4px;
-  }
-}
-
-.canvas_container {
-  position: fixed;
-  bottom: -99px;
-  z-index: -100;
-
-  &_name {
-    max-width: 480rpx;
-    display: inline-block;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-
-  #atCanvas {
-    height: 20px;
-  }
-
-  .convas_container_name {
-    font-size: 16px !important;
-  }
+    flex: 1;
+    width: 100%;
+	background-color: #fff;
+	min-height: 72rpx;
+	max-height: 240rpx;
+    padding-left: 10rpx;
+    padding-right: 10rpx;
+    padding-top: 20rpx;
+    padding-bottom: 20rpx;
+	font-size: 32rpx;
+	color: #333;
+    /* 垂直居中可以通过 line-height 或者 flex 布局辅助,但在 nvue textarea 中 padding 是最直接的 */
 }
 </style>

+ 78 - 241
pages_im/pages/conversation/chating/components/ChatingFooter/RecordOverlay.vue

@@ -1,28 +1,31 @@
 <template>
-  <u-overlay :show="visible" opacity="0">
+  <view class="record_overlay" v-if="visible" @touchmove.stop.prevent>
     <view class="record_container">
-
       <view class="layer_container">
         <view class="prompt-layer prompt-layer-1" v-if="!isCancelActive">
-          <text style="color: white; margin-bottom: 36rpx">{{ duration }}</text>
+          <text style="color: white; font-size: 32rpx; margin-bottom: 20rpx">{{ duration }}</text>
           <view class="prompt-loader">
-            <view class="em" v-for="(item, index) in 20" :key="index"></view>
+            <view
+              class="prompt-bar"
+              v-for="(h, index) in barHeights"
+              :key="index"
+              :style="{ height: h + 'px' }"
+            ></view>
           </view>
-          <text style="color: white; margin-top: 36rpx">手指上滑 取消发送</text>
+          <text style="color: #cccccc; font-size: 26rpx; margin-top: 20rpx">手指上滑 取消发送</text>
         </view>
         <view class="prompt-layer prompt-layer-1 cancel" v-else>
-          <image src="../../../../../static/images/recorder_cancel.png"></image>
-          <text style="color: white; margin-top: 36rpx">松开手指 取消发送</text>
+           <image src="/pages_im/static/images/recorder_cancel.png" mode="aspectFit" class="cancel-icon"></image>
+           <text style="color: white; font-size: 26rpx; margin-top: 20rpx">松开手指 取消发送</text>
         </view>
       </view>
     </view>
-  </u-overlay>
+  </view>
 </template>
 
 <script>
 const recorderManager = uni.getRecorderManager();
 const windowWidth = uni.getWindowInfo().windowWidth;
-let timer;
 
 const RecordActions = {
   Cancel: "Cancel",
@@ -40,6 +43,9 @@ export default {
       cancelFlag: false,
       activeAction: null,
       duration: 0,
+      barHeights: Array(20).fill(4), // Initial heights in px
+      timer: null,
+      waveTimer: null
     };
   },
   computed: {
@@ -96,64 +102,89 @@ export default {
       recorderManager.stop();
     },
     startTimer() {
-      timer = setInterval(() => this.duration++, 1000);
+      this.timer = setInterval(() => this.duration++, 1000);
+      this.startWave();
     },
     clearTimer() {
-      if (timer) {
-        clearInterval(timer);
+      if (this.timer) {
+        clearInterval(this.timer);
+        this.timer = null;
         this.duration = 0;
       }
+      this.stopWave();
+    },
+    startWave() {
+      // Simulate voice wave
+      this.waveTimer = setInterval(() => {
+        // Generate random heights between 4px and 24px (container is ~30px/60rpx)
+        this.barHeights = Array.from({ length: 20 }, () => Math.floor(Math.random() * 20) + 4);
+      }, 300);
+    },
+    stopWave() {
+      if (this.waveTimer) {
+        clearInterval(this.waveTimer);
+        this.waveTimer = null;
+      }
+      this.barHeights = Array(20).fill(4);
     },
   },
 };
 </script>
 
 <style lang="scss">
+.record_overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  width: 750rpx;
+  background-color: transparent;
+  justify-content: center;
+  align-items: center;
+}
+
+.record_container {
+  width: 750rpx;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+}
+
+.layer_container {
+  align-items: center;
+  justify-content: center;
+}
+
 .prompt-layer {
-  width: 250rpx;
-  height: 250rpx;
+  width: 300rpx;
+  height: 300rpx;
   border-radius: 16rpx;
   background: #0c1c33;
-  // opacity: 0.7;
   padding: 26rpx;
   box-sizing: border-box;
-  position: relative;
-  // position: absolute;
-  // left: 50%;
-  // transform: translateX(-50%);
-  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
+  align-items: center;
+  justify-content: center;
 }
 
-// .prompt-layer::after {
-// 	content: '';
-// 	display: block;
-// 	border: 24rpx solid rgba(0, 0, 0, 0);
-// 	border-top-color: #fff;
-// 	position: absolute;
-// 	bottom: -40rpx;
-// 	left: 50%;
-// 	transform: translateX(-50%);
-// }
-
 .prompt-layer-1 {
-  font-size: 24rpx;
-  width: 380rpx;
+  width: 320rpx;
+  height: 320rpx;
   text-align: center;
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  top: -600rpx;
 }
 
 .cancel {
   background: #ff381f;
-  // opacity: 0.7;
+  justify-content: center;
+  align-items: center;
+}
 
-  image {
-    width: 70rpx;
-    height: 100rpx;
-  }
+.cancel-icon {
+  width: 100rpx;
+  height: 100rpx;
 }
 
 .prompt-layer-1 .p {
@@ -169,217 +200,23 @@ export default {
 
 /* 语音音阶------------- */
 .prompt-loader {
-  width: 260rpx;
-  height: 40rpx;
-  display: flex;
+  width: 240rpx;
+  height: 60rpx;
+  flex-direction: row;
   align-items: center;
   justify-content: space-between;
   margin-bottom: 12rpx;
 }
 
-.prompt-loader .em {
-  display: block;
-  background: white;
-  width: 1px;
-  height: 10%;
-  margin-right: 2.5px;
-  float: left;
-}
-
-.prompt-loader .em:last-child {
-  margin-right: 0px;
-}
-
-.prompt-loader .em:nth-child(1) {
-  animation: load 2.5s 1.4s infinite linear;
-}
-
-.prompt-loader .em:nth-child(2) {
-  animation: load 2.5s 1.2s infinite linear;
-}
-
-.prompt-loader .em:nth-child(3) {
-  animation: load 2.5s 1s infinite linear;
-}
-
-.prompt-loader .em:nth-child(4) {
-  animation: load 2.5s 0.8s infinite linear;
-}
-
-.prompt-loader .em:nth-child(5) {
-  animation: load 2.5s 0.6s infinite linear;
-}
-
-.prompt-loader .em:nth-child(6) {
-  animation: load 2.5s 0.4s infinite linear;
-}
-
-.prompt-loader .em:nth-child(7) {
-  animation: load 2.5s 0.2s infinite linear;
-}
-
-.prompt-loader .em:nth-child(8) {
-  animation: load 2.5s 0s infinite linear;
+.prompt-bar {
+  background-color: #ffffff;
+  width: 8rpx;
+  height: 4px;
+  border-radius: 4rpx;
 }
 
-.prompt-loader .em:nth-child(9) {
-  animation: load 2.5s 0.2s infinite linear;
-}
-
-.prompt-loader .em:nth-child(10) {
-  animation: load 2.5s 0.4s infinite linear;
-}
-
-.prompt-loader .em:nth-child(11) {
-  animation: load 2.5s 0.6s infinite linear;
-}
-
-.prompt-loader .em:nth-child(12) {
-  animation: load 2.5s 0.8s infinite linear;
-}
-
-.prompt-loader .em:nth-child(13) {
-  animation: load 2.5s 1s infinite linear;
-}
-
-.prompt-loader .em:nth-child(14) {
-  animation: load 2.5s 1.2s infinite linear;
-}
 
-.prompt-loader .em:nth-child(15) {
-  animation: load 2.5s 1.4s infinite linear;
-}
-
-.prompt-loader .em:nth-child(16) {
-  animation: load 2.5s 1.6s infinite linear;
-}
-
-.prompt-loader .em:nth-child(17) {
-  animation: load 2.5s 1.8s infinite linear;
-}
-
-.prompt-loader .em:nth-child(18) {
-  animation: load 2.5s 2s infinite linear;
-}
-
-.prompt-loader .em:nth-child(19) {
-  animation: load 2.5s 2.2s infinite linear;
-}
-
-.prompt-loader .em:nth-child(20) {
-  animation: load 2.5s 2.4s infinite linear;
-}
 
-@keyframes load {
-  0% {
-    height: 10%;
-  }
 
-  50% {
-    height: 100%;
-  }
 
-  100% {
-    height: 10%;
-  }
-}
-
-.record_container {
-  display: flex;
-  flex-direction: column-reverse;
-  height: 100%;
-  width: 100%;
-
-  .record_area {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    height: 260rpx;
-    width: 150%;
-    background-color: #dfdfdf;
-    border-radius: 50% / 100% 100% 0 0;
-    position: relative;
-    left: -25%;
-
-    image {
-      width: 36px;
-      height: 36px;
-    }
-
-    &:after {
-      content: attr(data-tip);
-      font-size: 26rpx;
-      color: #bebebe;
-      position: absolute;
-      left: 50%;
-      transform: translateX(-50%);
-      top: -64rpx;
-    }
-  }
-
-  .record_action_bar {
-    display: flex;
-    width: 80%;
-    min-height: 200rpx;
-    justify-content: space-between;
-    margin-left: 10%;
-    margin-bottom: 48rpx;
-
-    .record_action {
-      height: 144rpx;
-      width: 144rpx;
-      border-radius: 50%;
-      background-color: #484848;
-      color: #8e8e8e;
-      transition: all 0.25s;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      font-size: 56rpx;
-      font-weight: 500;
-      transform: rotate(-24deg);
-      position: relative;
-
-      &:after {
-        content: attr(data-tip);
-        position: absolute;
-        font-size: 26rpx;
-        color: #bebebe;
-        transform: rotate(24deg);
-        top: -50%;
-        left: 50%;
-        width: 120rpx;
-        text-align: center;
-      }
-
-      &:last-child {
-        transform: rotate(24deg);
-
-        &:after {
-          transform: rotate(-24deg);
-          left: auto;
-          right: 40%;
-        }
-      }
-
-      &_cancling {
-        transform: rotate(-24deg) scale(1.2);
-        background-color: #eeedf0;
-        color: #696768;
-      }
-
-      &_translating {
-        transform: rotate(24deg) scale(1.2) !important;
-        background-color: #eeedf0;
-        color: #696768;
-      }
-    }
-  }
-
-  .layer_container {
-    display: flex;
-    justify-content: center;
-    width: 100%;
-  }
-}
 </style>

+ 0 - 306
pages_im/pages/conversation/chating/components/ChatingFooter/SelectLuckBag.vue

@@ -1,306 +0,0 @@
-<template>
-	<view>
-		<u-popup :show="show" mode="center" bgColor="transparent" @close="close"
-			:customStyle="{background: 'transparent'}">
-			<view class="luck-bag-container">
-				<!-- 顶部福袋图片 -->
-				<image class="bag-image" src="/pages_im/static/images/luckyBag/lucky_bag_img.png"></image>
-
-				<!-- 主体内容区域 -->
-				<view class="content-box">
-					<view>
-						<image class="lucky_bag_font" src="/pages_im/static/images/luckyBag/lucky_bag_font.png" mode="">
-						</image>
-					</view>
-
-					<!-- 福袋个数 -->
-					<view class="form-item es-mt-32" @click="selectCount">
-						<text class="label">福袋</text>
-						<view class="input-wrap">
-							<text v-if="count" class="value">{{ count }}</text>
-							<text v-else class="placeholder">请选择</text>
-							<u-icon name="arrow-right" color="#999" size="14"></u-icon>
-						</view>
-					</view>
-
-					<!-- 提交按钮 -->
-					<view class="submit-btn" @click="submit">
-						<text class="btn-text">塞进福袋</text>
-					</view>
-				</view>
-
-				<!-- 关闭按钮 -->
-				<view class="close-btn" @click="close">
-					<image class="es-icon-64" src="/pages_im/static/images/luckyBag/close_tc_icon.png" mode=""></image>
-				</view>
-			</view>
-		</u-popup>
-
-		<!-- 个数选择器 -->
-		<u-picker :show="showPicker" keyName="name" :columns="pickerColumns" @confirm="confirmPicker"
-			@cancel="showPicker = false" @close="showPicker = false"></u-picker>
-	</view>
-</template>
-
-<script>
-	import {
-		mapGetters,
-		mapActions
-	} from 'vuex';
-	import {
-		parseMessageByType
-	} from '@/pages_im/util/imCommon';
-	import IMSDK, {
-		IMMethods,
-		MessageStatus
-	} from 'openim-uniapp-polyfill';
-	import {
-		luckyBagList,
-		createFdMiniLink,
-		receiveLuckyBag
-	} from '@/pages_im/api/luckyBag.js'
-	export default {
-		name: 'SelectLuckBag',
-		props: {
-			show: {
-				type: Boolean,
-				default: false
-			}
-		},
-		computed: {
-			...mapGetters(['storeCurrentConversation'])
-		},
-		data() {
-			return {
-				count: '',
-				showPicker: false,
-				list: [],
-				pickerColumns: []
-			}
-		},
-		methods: {
-			...mapActions('message', ['pushNewMessage', 'updateOneMessage']),
-			close() {
-				this.$emit('close');
-			},
-			async selectCount() {
-				let companyUserInfo = uni.getStorageSync('companyUserInfo')
-				const companyId = companyUserInfo && JSON.parse(companyUserInfo).companyId
-				const res = await luckyBagList({
-					companyId
-				})
-				if (res.code == 200) {
-					this.list = res.rows
-					this.pickerColumns = [res.rows.map(item => item.name)]
-					this.showPicker = true;
-				} else {
-					uni.showToast({
-						icon: 'non',
-						title: res.msg
-					})
-				}
-			},
-			confirmPicker(e) {
-				this.count = e.value[0];
-				this.luckyBagId = this.list[e.indexs[0]].id;
-				this.showPicker = false;
-			},
-			async submit() {
-				if (!this.count) {
-					return uni.showToast({
-						title: '请选择福袋',
-						icon: 'none'
-					});
-				}
-				let companyUserInfo = uni.getStorageSync('companyUserInfo')
-				companyUserInfo = companyUserInfo && JSON.parse(companyUserInfo)
-				let params = {
-					companyId: companyUserInfo.companyId,
-					luckyBagId: this.luckyBagId,
-					companyUserId: companyUserInfo.userId
-				}
-				let that = this
-				const res = await createFdMiniLink(params)
-				if (res.code == 200) {
-					if (!res.data.recordId) return
-					let payloadJson = {
-						payload: {
-							data: "luckyBag",
-							extension: {
-								isReceived: false,
-								nickName: companyUserInfo.nickName,
-								userName: companyUserInfo.userName,
-								companyId: companyUserInfo.companyId,
-								avatar: companyUserInfo.avatar,
-								recordId: res.data.recordId || '',
-							}
-						}
-					};
-					let forwardMessage = {
-						data: JSON.stringify(payloadJson),
-						extension: '',
-						description: 'luckyBag'
-					};
-					let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), forwardMessage);
-					that.pushNewMessage(message);
-
-					let offlinePushInfo = {
-						title: message.senderNickname,
-						desc: parseMessageByType(message),
-						ex: "",
-						iOSPushSound: "",
-						iOSBadgeCount: true
-					};
-					IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
-						recvID: that.storeCurrentConversation.userID,
-						groupID: that.storeCurrentConversation.groupID,
-						message,
-						offlinePushInfo
-					}).then(({
-						data
-					}) => {
-						that.close();
-						that.updateOneMessage({
-							message: data,
-							isSuccess: true
-						});
-					}).catch(({
-						data,
-						errCode,
-						errMsg
-					}) => {
-						that.updateOneMessage({
-							message: data,
-							type: UpdateMessageTypes.KeyWords,
-							keyWords: [{
-									key: 'status',
-									value: MessageStatus.Failed
-								},
-								{
-									key: 'errCode',
-									value: errCode
-								}
-							]
-						});
-					});
-				} else {
-					uni.showToast({
-						icon: 'non',
-						title: res.msg
-					})
-				}
-			}
-		}
-	}
-</script>
-
-<style lang="scss" scoped>
-	.luck-bag-container {
-		width: 600rpx;
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		position: relative;
-
-		.bag-image {
-			width: 100%;
-			height: 308rpx;
-			z-index: 10;
-			margin-bottom: -50rpx; // 让图片下沉覆盖到红框上
-		}
-
-		.content-box {
-			width: 100%;
-			background: linear-gradient(180deg, #FD303D 0%, #FEB6A7 99.99%);
-			border-radius: 30rpx;
-			padding: 90rpx 40rpx 50rpx 40rpx;
-			box-sizing: border-box;
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
-
-			.lucky_bag_font {
-				width: 240rpx;
-				height: 80rpx;
-			}
-
-			.form-item {
-				width: 100%;
-				height: 100rpx;
-				background: #fff;
-				border-radius: 16rpx;
-				display: flex;
-				align-items: center;
-				justify-content: space-between;
-				padding: 0 30rpx;
-				box-sizing: border-box;
-				margin-bottom: 16rpx;
-
-				.label {
-					font-size: 32rpx;
-					color: #333;
-					font-weight: 600;
-				}
-
-				.input-wrap {
-					display: flex;
-					align-items: center;
-					flex: 1;
-					justify-content: flex-end;
-
-					.value {
-						font-size: 32rpx;
-						color: #333;
-						margin-right: 10rpx;
-					}
-
-					.placeholder {
-						font-size: 30rpx;
-						color: #999;
-						margin-right: 10rpx;
-					}
-
-					.input {
-						text-align: right;
-						font-size: 32rpx;
-						color: #333;
-						width: 100%;
-						height: 100%;
-					}
-				}
-			}
-
-			.submit-btn {
-				width: 100%;
-				height: 108rpx;
-				background-image: url('@/pages_im/static/images/luckyBag/lucky_bag_btn.png');
-				background-repeat: no-repeat;
-				background-size: 100% 100%;
-				border-radius: 50rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				margin-top: 32rpx;
-
-				.btn-text {
-					margin-bottom: 20rpx;
-					font-size: 40rpx;
-					color: #fff;
-					font-weight: bold;
-					text-shadow: 0 2rpx 4rpx rgba(186, 76, 0, 0.3);
-				}
-			}
-		}
-
-		.close-btn {
-			margin-top: 50rpx;
-			width: 70rpx;
-			height: 70rpx;
-			border-radius: 50%;
-			display: flex;
-			align-items: center;
-			justify-content: center;
-		}
-	}
-</style>

+ 306 - 297
pages_im/pages/conversation/chating/components/ChatingFooter/index.vue

@@ -1,30 +1,30 @@
 <template>
-	<view class="">
+	
+	<view class="chating_footer" :style="{ paddingBottom: keyboardHeight + 'px' }">
 		<view class="forbidden_footer" v-if="getPlaceholder.length > 0">
-			<image style="margin-right: 8rpx" src="../../../../../static/images/forbidden_footer.png" />
+		<image style="margin-right: 8rpx;width:50rpx" src="/static/images/forbidden_footer.png" mode="aspectFit" />
 			<text>{{ getPlaceholder }}</text>
 		</view>
 		
-		<view v-show="getPlaceholder.length <= 0" :style="{ 'pointer-events': getPlaceholder ? 'none' : 'auto' }">
+		<view v-if="getPlaceholder.length <= 0" :style="{ 'pointer-events': getPlaceholder ? 'none' : 'auto' }">
 			<view class="chat_footer">
 				<image v-if="showRecord"
 					@click="updateRecordBtn"
-					style="width: 26px; height: 26px;"
-					src="../../../../../static/images/chating_footer_audio_recording.png" />
-
-                 <image v-else
-                 	@click="updateRecordBtn"
-                 	style="width: 26px; height: 26px;"
-                 	src="../../../../../static/images/chating_footer_audio.png" />
+					class="footer_icon_left"
+					src="/pages_im/static/images/chating_footer_audio_recording.png" />
+                <image v-else
+                	@click="updateRecordBtn"
+                	class="footer_icon_left"
+                	src="/pages_im/static/images/chating_footer_audio.png" />
 
 				<view class="input_content">
-					<CustomEditor
+					<CustomEditor 
 						:placeholder="getPlaceholder"
-						v-show="!showRecord"
+						v-if="!showRecord"
 						class="custom_editor"
 						ref="customEditor"
 						@ready="editorReady"
-						@focus="editorFocus"
+						@focus="editorFocus"  
 						@blur="editorBlur"
 						@input="editorInput"
 						@tryAt="showAtPanel"/>
@@ -33,29 +33,29 @@
 						<view class="content">
 							<u-parse :content="getQuotedContent" />
 						</view>
-						<image @click="cancelQuote" style="width: 16px; height: 16px" src="../../../../../static/images/chating_footer_quote_close.png" alt="" />
+						<image @click="cancelQuote" style="width: 16px; height: 16px" src="/pages_im/static/images/chating_footer_quote_close.png" alt="" />
+					</view>
+					
+					<view class="record_btn" ref="recordBtn" @touchstart="recordStartTouch" @longpress="longpressRecord" @touchmove="touchMoveSpeech" @touchend="endRecord" v-if="showRecord">
+						<text class="record_text">{{ recording ? '松手发送' : '按住说话' }}</text>
 					</view>
-					<button class="record_btn" @longpress="longpressRecord" @touchmove="touchMoveSpeech" @touchend="endRecord" v-show="showRecord" customStyle="height:30px;">
-						{{ recording ? '松手发送' : '按住说话' }}
-					</button>
 				</view>
 				<view class="footer_action_area">
-					<image @click="updateEmojiBar" class="emoji_action" src="../../../../../static/images/chating_footer_emoji.png" alt="" srcset="" />
-
-					<image v-show="!hasContent" @click.prevent="updateActionBar" src="../../../../../static/images/chating_footer_add.png" alt="" srcset="" />
+					<image @click.prevent="updateEmojiBar" class="footer_icon_right" src="/pages_im/static/images/chating_footer_emoji.png" alt="" /> 
 					
-					<button class="btnSend" v-show="hasContent && !emojiBarVisible" @touchend.prevent="sendTextMessage">发送</button>
-					<button class="btnSend" v-show="hasContent && emojiBarVisible" @touchend.prevent="sendTextMessage">发送</button>
-					
-					<!-- <image v-show="hasContent && !emojiBarVisible" @touchend.prevent="sendTextMessage" src="../../../../../static/images/send_btn.png" alt="" srcset="" /> -->
-					<!-- <image v-show="hasContent && emojiBarVisible" @click.prevent="sendTextMessage" src="../../../../../static/images/send_btn.png" alt="" srcset="" /> -->
+					<image v-if="!hasContent" @click.prevent="updateActionBar" class="footer_icon_right" style="margin-right: 0;" src="/pages_im/static/images/chating_footer_add.png" alt="" />
 					
+					<view class="btnSend" v-if="hasContent" @touchend.prevent="sendTextMessage">
+						<text class="btnSend_text">发送</text>
+					</view>
 				</view>
 			</view>
 
-			<chating-action-bar @LuckyBag="LuckyBag" @sendMessage="sendMessage" @prepareMediaMessage="prepareMediaMessage" v-show="actionBarVisible" />
-
-			<chating-emoji-bar @insertEmoji="emojiClick" @backspace="backspace" v-show="emojiBarVisible" />
+			<view class="panel-container" :style="{ height: (actionBarVisible || emojiBarVisible) ? panelHeight+'rpx' : '0px' }">
+				<chating-action-bar @sendMessage="sendMessage" @prepareMediaMessage="prepareMediaMessage" @enterSubPage="$emit('enterSubPage')" v-if="actionBarVisible" />
+				<chating-emoji-bar @insertEmoji="emojiClick" @backspace="backspace" v-if="emojiBarVisible" />
+			</view>
+			
 			<u-action-sheet
 				:safeAreaInsetBottom="true"
 				round="12"
@@ -67,17 +67,14 @@
 				@close="showActionSheet = false"></u-action-sheet>
 			<record-overlay @recordFinish="recordFinish" ref="recordOverlayRef" :visible="recording" />
 		</view>
-		
-		<select-luck-bag :show="luckyBagShow" @close="luckyBagShow=false"/>
 	</view>
+
 </template>
 
 <script>
-	import SelectLuckBag from './SelectLuckBag.vue'
-	// import SelectLuckBag from '@/pages_im/pages/conversation/chating/components/ChatingFooter/SelectLuckBag.vue'
 import { mapGetters, mapActions } from 'vuex';
-import { formatInputHtml, getPurePath, html2Text } from '../../../../../util/common';
-import { parseMessageByType, parseAt } from '../../../../../util/imCommon';
+// import { formatInputHtml, getPurePath, html2Text } from '../../../../../util/common';
+import { parseMessageByType, getIMSDK} from '../../../../../util/imCommon';
 import { ChatingFooterActionTypes, UpdateMessageTypes, GroupMemberListTypes, PageEvents } from '../../../../../constant';
 import IMSDK, { GroupMemberRole, GroupStatus, IMMethods, MessageStatus, MessageType, SessionType } from 'openim-uniapp-polyfill';
 import UParse from '../../../../../components/gaoyia-parse/parse.vue';
@@ -86,47 +83,20 @@ import ChatingActionBar from './ChatingActionBar.vue';
 import ChatingEmojiBar from './ChatingEmojiBar.vue';
 import RecordOverlay from './RecordOverlay.vue';
 import { gotoAppPermissionSetting, requestAndroidPermission, judgeIosPermission } from '../../../../../util/permission';
-import { premissionCheck } from "@/js_sdk/wa-permission/permission.js"
+
 const needClearTypes = [
-	// MessageType.AdvanceTextMessage,
 	MessageType.TextMessage,
 	MessageType.AtTextMessage,
 	MessageType.QuoteMessage
 ];
 
-const albumChoose = [
-	{
-		name: '图片',
-		type: ChatingFooterActionTypes.Album,
-		idx: 0
-	},
-	{
-		name: '视频',
-		type: ChatingFooterActionTypes.Album,
-		idx: 1
-	}
-];
-const cameraChoose = [
-	{
-		name: '拍照',
-		type: ChatingFooterActionTypes.Camera,
-		idx: 0
-	},
-	{
-		name: '录制',
-		type: ChatingFooterActionTypes.Camera,
-		idx: 1
-	}
-];
-
 export default {
 	components: {
 		CustomEditor,
 		ChatingActionBar,
 		ChatingEmojiBar,
 		UParse,
-		RecordOverlay,
-		SelectLuckBag
+		RecordOverlay
 	},
 	props: {
 		footerOutsideFlag: Number
@@ -142,38 +112,43 @@ export default {
 			actionSheetMenu: [],
 			showActionSheet: false,
 			recording: false,
-			recordTop: uni.getWindowInfo().windowHeight - 130,
+			startPageY: 0,
+			recordTop: 0,
 			conversationID: null,
 			isJoinGroup: true,
-			luckyBagShow:false
+			isSending: false,
+			keyboardHeight: 0,
+			panelHeight:0,
 		};
 	},
 	computed: {
-		...mapGetters(['storeQuoteMessage', 'storeCurrentConversation', 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeBlackList', 'storeRevokeMap','timStore']),
+		...mapGetters(['storeQuoteMessage', 'storeCurrentConversation', 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeBlackList', 'storeRevokeMap','timStore', 'storeCurrentUserID']),
 		getQuotedContent() {
 			if (!this.storeQuoteMessage) {
 				return '';
 			}
-			return `回复${parseMessageByType(this.storeQuoteMessage)}`;
+			return '';
+			return `回复${parseMessageByType(this.storeQuoteMessage, this.storeCurrentUserID)}`;
 		},
 		hasContent() {
-			return html2Text(this.inputHtml) !== '';
+			return this.html2Text(this.inputHtml) !== '';
 		},
 		isNomal() {
-			return this.storeCurrentMemberInGroup.roleLevel === GroupMemberRole.Nomal;
+			return this.storeCurrentMemberInGroup?.roleLevel === GroupMemberRole.Nomal;
 		},
 		getPlaceholder() {
+			if (!this.storeCurrentConversation?.conversationID) return '';
 			const isSingle = this.storeCurrentConversation.conversationType === SessionType.Single;
-			if (!isSingle && this.storeCurrentGroup.status === GroupStatus.Muted) {
+			if (!isSingle && this.storeCurrentGroup?.status === GroupStatus.Muted) {
 				return !this.isNomal ? '' : '群主或管理员已开启全体禁言';
 			}
 			if (!isSingle && !this.isJoinGroup) {
 				return '您已不在该群组';
 			}
-			if (!isSingle && this.storeCurrentMemberInGroup.muteEndTime > Date.now()) {
+			if (!isSingle && this.storeCurrentMemberInGroup?.muteEndTime > Date.now()) {
 				return `您已被管理员或群主禁言!`;
 			}
-			if (isSingle && this.storeBlackList.find((black) => black.userID === this.storeCurrentConversation.userID)) {
+			if (isSingle && this.storeBlackList?.find((black) => black.userID === this.storeCurrentConversation.userID)) {
 				return '对方已被拉入黑名单';
 			}
 			return '';
@@ -197,20 +172,23 @@ export default {
 	mounted() {
 		this.setIMListener();
 		this.setKeyboardListener();
-		if (this.storeCurrentConversation.groupID) {
-			IMSDK.asyncApi(IMMethods.IsJoinGroup, IMSDK.uuid(), this.storeCurrentConversation.groupID).then((res) => {
+		if (this.storeCurrentConversation?.groupID) {
+			IMSDK.asyncApi(IMSDK.IMMethods.IsJoinGroup, IMSDK.uuid(), this.storeCurrentConversation.groupID).then((res) => {
 				this.isJoinGroup = res.data;
 			});
 		}
-		this.conversationID = this.storeCurrentConversation.conversationID;
-		this.parseDraftText(this.storeCurrentConversation.draftText);
-		IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
-			conversationID: this.conversationID,
-			draftText: ''
-		});
-		uni.$on(PageEvents.LuckyBag, ()=>{
-			this.luckyBagShow=true
-		});
+		this.conversationID = this.storeCurrentConversation?.conversationID;
+		if (this.storeCurrentConversation?.draftText) {
+			this.parseDraftText(this.storeCurrentConversation.draftText);
+		}
+		
+		if (this.conversationID) {
+			IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
+				conversationID: this.conversationID,
+				draftText: ''
+			});
+		}
+		this.panelHeight=this.$companyUserIsLogin()?550:380;
 		uni.$on(PageEvents.ReEditMessage, this.setReEditMessage);
 	    uni.$on("sendIM", this.sendPackageImMsg);
 		uni.$on("sendIMCts", this.sendCtsImMsg);
@@ -221,7 +199,7 @@ export default {
 		if (this.inputHtml && this.inputHtml.trim()) {
 			IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
 				conversationID: this.conversationID,
-				draftText: html2Text(this.inputHtml)
+				draftText: this.html2Text(this.inputHtml)
 			});
 		}
 		uni.$off("sendIM", this.sendPackageImMsg);
@@ -237,6 +215,7 @@ export default {
 			}, 50);
 		},
 		parseDraftText(draftText) {
+			if (!draftText) return;
 			let currentText = '';
 			for (let i = 0; i < draftText.length; i++) {
 				const currentChar = draftText[i];
@@ -251,6 +230,7 @@ export default {
 					const imgEndIndex = draftText.indexOf('>', i);
 					if (imgEndIndex !== -1) {
 						const imgTag = draftText.substring(i, imgEndIndex + 1);
+
 						if (imgTag.includes('class="at_el"')) {
 							const customDataReg = /data-custom=".+"/;
 							const atInfoArr = imgTag.match(customDataReg)[0].slice(13, -1).split('&amp;');
@@ -268,9 +248,14 @@ export default {
 				}, 700);
 			}
 		},
+		recordStartTouch(e) {
+			if (e.touches && e.touches.length > 0) {
+				this.startPageY = e.touches[0].pageY;
+			}
+		},
 		longpressRecord() {
 			this.recording = true;
-			this.$nextTick(() => this.getRecordAreaData());
+			this.recordTop = this.startPageY - 50;
 		},
 		touchMoveSpeech(e) {
 			uni.$u.throttle(this.$refs.recordOverlayRef.touchMoveSpeech(e, this.recordTop), 250);
@@ -281,29 +266,15 @@ export default {
 		},
 		async recordFinish(path, duration) {
 			const message = await IMSDK.asyncApi(IMMethods.CreateSoundMessageFromFullPath, IMSDK.uuid(), {
-				soundPath: getPurePath(path),
+				soundPath: this.getPurePath(path),
 				duration
 			});
 			this.sendMessage(message);
 		},
-		getRecordAreaData() {
-			this.getEl('.record_area').then((data) => {
-				if (data) {
-					this.recordTop = data.top;
-				}
-			});
-		},
-		getEl(el) {
-			return new Promise((resolve) => {
-				const query = uni.createSelectorQuery().in(this);
-				query.select(el).boundingClientRect((data) => {
-					resolve(data);
-				}).exec();
-			});
-		},
+
 		async createTextMessage() {
 			let message = '';
-			const { text, atUsersInfo } = formatInputHtml(this.inputHtml);
+			const { text, atUsersInfo } = this.formatInputHtml(this.inputHtml);
 			if (atUsersInfo.length === 0) {
 				if (this.storeQuoteMessage) {
 					message = await IMSDK.asyncApi(IMMethods.CreateQuoteMessage, IMSDK.uuid(), {
@@ -326,19 +297,27 @@ export default {
 			return message;
 		},
 		async sendTextMessage() {
-			// if(this.emojiBarVisible){
-			// 	uni.hideKeyboard()
-			// }
 			if (!this.hasContent) return;
 			IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
 				conversationID: this.storeCurrentConversation.conversationID,
 				focus: false
 			});
-			const message = await this.createTextMessage();
-			this.sendMessage(message);
+			
+			 try {
+			      this.isSending = true;
+			      const message = await this.createTextMessage();
+			      await this.sendMessage(message);
+			    } catch (error) {
+					  console.error('发送消息失败:', error);
+					  uni.showToast({
+						 title: '发送失败',
+						 icon: 'none'
+					  });
+			    } finally {
+			        this.isSending = false;
+			  }
 		},
 		async sendCtsPackageMessage(item){
-			 console.log("qxj item:");
 			let payloadJson={
 				payload:{
 					 data:"package", 
@@ -362,6 +341,7 @@ export default {
 			console.log("qxj package:"+item.packageId);
 			this.sendCtsPackageMessage(item);
 		},
+		
 		async sendCtsMessage(item){
 			 let payloadJson={};
 			 if(item.type=="couponPackage"){
@@ -371,12 +351,11 @@ export default {
 			     		 extension:{
 			     			couponId:item.couponId,
 			     			title:item.title,
-							price:item.price,
-							minPrice:item.minPrice,
-							limitTime:item.limitTime,
+			     			price:item.price,
+			     			minPrice:item.minPrice,
+			     			limitTime:item.limitTime,
 			     			companyId:item.companyId,
 			     			companyUserId:item.companyUserId,
-			     			//image:item.imgUrl
 			     		 }
 			          }
 			     };
@@ -389,23 +368,13 @@ export default {
 			     		 extension:{
 			     			packageId:item.packageId,
 			     			title:item.lable,
-							type:item.value,
+			     			type:item.value,
 			     			companyId:item.companyId,
 			     			companyUserId:item.companyUserId,
-			     			//image:item.imgUrl
 			     		 }
 			          }
 			     };
 			 }
-			 if(item.type=="live"){
-			     payloadJson={
-			     	payload:{
-			     		 data:item.type, 
-			     		 extension:item
-			          }
-			     };
-			 }
-			 console.log("qxj live payloadJson",payloadJson)
 			let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
 				 data: JSON.stringify(payloadJson),
 				 extension: '',
@@ -414,9 +383,9 @@ export default {
 			this.sendMessage(message);
 		},
 		sendCtsImMsg(item){
-			console.log("qxj sendCtsImMsg");
 			this.sendCtsMessage(item);
 		},
+		
 		sendMessage(message) {
 			var customerData={
 			  	type:this.timStore?.type,
@@ -437,12 +406,13 @@ export default {
 			}
 			let offlinePushInfo={
 				title:message.senderNickname,
-				desc: parseMessageByType(message),
+				desc:"",
+				desc: parseMessageByType(message, this.storeCurrentUserID),
 				ex: "",
 				iOSPushSound: "",
 				iOSBadgeCount: true
 			};
-			console.log("qxj sendMessage",offlinePushInfo);
+			
 			this.$emit('scrollToBottom');
 			IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
 				recvID: this.storeCurrentConversation.userID,
@@ -454,6 +424,9 @@ export default {
 						message: data,
 						isSuccess: true
 					});
+                    this.$nextTick(() => {
+                        this.$emit('scrollToBottom');
+                    });
 				}).catch(({ data, errCode, errMsg }) => {
 					this.updateOneMessage({
 						message: data,
@@ -474,7 +447,6 @@ export default {
 				this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
 			}
 		},
-		// action
 		cancelQuote() {
 			this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
 		},
@@ -495,8 +467,17 @@ export default {
 			if (this.showRecord) {
 				this.showRecord = false;
 			}
-			this.onClickEmojiBarOutside();
-			this.actionBarVisible = !this.actionBarVisible;
+			
+			// Toggle logic: if already visible, close it. If not, open it and close others.
+			if (this.actionBarVisible) {
+				this.actionBarVisible = false;
+			} else {
+				this.emojiBarVisible = false;
+				this.actionBarVisible = true;
+				if (this.keyboardHeight > 0) {
+					uni.hideKeyboard();
+				}
+			}
 		},
 		updateEmojiBar() {
 			if (this.recording) {
@@ -505,24 +486,36 @@ export default {
 			if (this.showRecord) {
 				this.showRecord = false;
 			}
-			this.onClickActionBarOutside();
-			this.emojiBarVisible = !this.emojiBarVisible;
+			
+			// Toggle logic
+			if (this.emojiBarVisible) {
+				this.emojiBarVisible = false;
+			} else {
+				this.actionBarVisible = false;
+				this.emojiBarVisible = true;
+				if (this.keyboardHeight > 0) {
+					uni.hideKeyboard();
+				}
+			}
 		},
 		editorReady(e) {
+			console.log("qxj editorReady");
 			this.customEditorCtx = e.context;
 			this.customEditorCtx.clear();
 		},
 		editorFocus() {
+			//console.log("qxj editorFocus");
 			this.isInputFocus = true;
 			this.$emit('scrollToBottom');
-			this.keyboardChangeHander({
-				height: 0
-			});
+			this.actionBarVisible = false;
+			this.emojiBarVisible = false;
 		},
 		editorBlur() {
+			//console.log("qxj editorBlur");
 			this.isInputFocus = false;
 		},
 		editorInput(e) {
+			//console.log("qxj editorInput");
 			this.inputHtml = e.detail.html;
 			uni.$u.throttle(this.updateTyping, 2000);
 		},
@@ -543,9 +536,21 @@ export default {
 			if (!this.showRecord) {
 				this.checkRecordPermission();
 			}
+			
+			// Close panels if open
+			this.emojiBarVisible = false;
+			this.actionBarVisible = false;
+			
 			this.showRecord = !this.showRecord;
-			this.onClickActionBarOutside();
-			this.onClickEmojiBarOutside();
+			if (this.showRecord) {
+				if (this.keyboardHeight > 0) {
+					uni.hideKeyboard();
+				}
+			} else {
+				// Focus editor when switching back to text
+				// this.$refs.editor?.focus(); 
+				// nvue editor focus is tricky, usually requires user tap or specific method
+			}
 		},
 		async checkRecordPermission() {
 			if (uni.$u.os() === 'ios') {
@@ -569,31 +574,23 @@ export default {
 			}
 		},
 		prepareMediaMessage(data) {
-			// if (data.type === ChatingFooterActionTypes.Album) {
-			// 	this.actionSheetMenu = [...albumChoose];
-			// } else {
-			// 	this.actionSheetMenu = [...cameraChoose];
-			// }
-			// this.showActionSheet = false;
-			console.log("qxj prepareMediaMessage data:"+JSON.stringify(data));
 			this.selectClick(data);
 		},
 		updateTyping() {
-			if (this.storeCurrentConversation.conversationType === SessionType.Single) {
+			if (this.storeCurrentConversation?.conversationType === SessionType.Single) {
 				IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
 					conversationID: this.storeCurrentConversation.conversationID,
 					focus: true
 				});
 			}
 		},
-
-		// from comp
+ 
 		emojiClick(emoji) {
 			this.$refs.customEditor.insertEmoji(emoji);
 		},
 		batchCreateImageMesage(paths) {
 			paths.forEach(async (path) => {
-				const message = await IMSDK.asyncApi(IMMethods.CreateImageMessageFromFullPath, IMSDK.uuid(), getPurePath(path));
+				const message = await IMSDK.asyncApi(IMMethods.CreateImageMessageFromFullPath, IMSDK.uuid(), this.getPurePath(path));
 				this.sendMessage(message);
 			});
 		},
@@ -607,14 +604,14 @@ export default {
 			} 
 			else {
 				const whenGetFile = (data) => {
-					const purePath = getPurePath(data.path);
+					const purePath = this.getPurePath(data.path);
 					IMSDK.getVideoCover(purePath).then(async ({ path }) => {
-						console.log(getPurePath(path));
+						// console.log(this.getPurePath(path));
 						const message = await IMSDK.asyncApi(IMMethods.CreateVideoMessageFromFullPath, IMSDK.uuid(), {
 							videoPath: purePath,
 							videoType: data.videoType,
 							duration: Number(data.duration.toFixed()),
-							snapshotPath: getPurePath(path)
+							snapshotPath: this.getPurePath(path)
 						});
 						this.sendMessage(message);
 					});
@@ -626,11 +623,7 @@ export default {
 				}
 			}
 		},
-		async chooseOrShotImage(sourceType) {
-			let result = await premissionCheck("EXTERNAL_STORAGE");
-			if(result != 1) {
-			   return;
-			}
+		chooseOrShotImage(sourceType) {
 			return new Promise((resolve, reject) => {
 				uni.chooseImage({
 					count: 9,
@@ -646,12 +639,7 @@ export default {
 				});
 			});
 		},
-		
-		async chooseOrShotVideo(sourceType) {
-			let result = await premissionCheck("EXTERNAL_STORAGE");
-			if(result != 1) {
-			    return;
-			}
+		chooseOrShotVideo(sourceType) {
 			return new Promise((resolve, reject) => {
 				uni.chooseVideo({
 					compressed: true,
@@ -663,7 +651,7 @@ export default {
 						if (tempFilePath.includes('_doc/')) {
 							tempFilePath = `file://${plus.io.convertLocalFileSystemURL(tempFilePath)}`;
 						}
-						console.log(tempFilePath);
+						// console.log(tempFilePath);
 						resolve({
 							path: tempFilePath,
 							videoType,
@@ -678,14 +666,13 @@ export default {
 			});
 		},
 		insertAt(userID, nickname) {
-			const { atUsersInfo } = formatInputHtml(this.inputHtml);
+			const { atUsersInfo } = this.formatInputHtml(this.inputHtml);
 			if (atUsersInfo.find((item) => item.atUserID === userID)) {
 				return;
 			}
 			this.$refs.customEditor.createCanvasData(userID, nickname);
 		},
 
-		// im listener
 		sendProgressHandler({ data: { progress, message } }) {
 			this.updateOneMessage({
 				message,
@@ -713,162 +700,184 @@ export default {
 			uni.$off(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
 			uni.$off(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
 		},
-
-		// keyboard
-		keyboardChangeHander({ height }) {
-			if (height > 0) {
-				if (this.emojiBarVisible) {
-					this.emojiBarVisible = false;
-				}
-				if (this.actionBarVisible) {
-					this.actionBarVisible = false;
-				}
-			}
-		},
+        keyboardChangeHander({ height }) {
+			this.keyboardHeight = height;
+        	if (height > 0) {
+        		if (this.emojiBarVisible) {
+        			this.emojiBarVisible = false;
+        		}
+        		if (this.actionBarVisible) {
+        			this.actionBarVisible = false;
+        		}
+				this.$emit('scrollToBottom');
+        	}
+        },
 		setKeyboardListener() {
+			// #ifdef APP-NVUE
 			uni.onKeyboardHeightChange(this.keyboardChangeHander);
+			// #endif
 		},
 		disposeKeyboardListener() {
+			// #ifdef APP-NVUE
 			uni.offKeyboardHeightChange(this.keyboardChangeHander);
+			// #endif
+		},
+		html2Text(html){
+			  if (!html) {
+				return "";
+			  }
+			  return html
+				.replace(/<\/p><p>/g, "\n")
+				.replace(/\&nbsp;/g, " ")
+				.replace(/<p>/g, "")
+				.replace(/<\/p>/g, "")
+				.replace(/<br>/g, "")
+				.trim();
+		},
+		getPurePath(path){
+			  const prefix = "file://";
+			  const relativeRrefix = "_doc/";
+			  if (path.includes(prefix)) {
+				path = path.replace(prefix, "");
+			  }
+			  if (path.includes(relativeRrefix)) {
+				path = plus.io.convertLocalFileSystemURL(path);
+			  }
+			  return path;
+		},
+		formatInputHtml(html){
+		  let atUsersInfo = [];
+		  let text = this.html2Text(html);
+		  const imgReg = new RegExp("(i?)(<img)([^>]+>)", "gmi");
+		  const customDataReg = /data-custom=".+"/;
+		  text = text.replace(imgReg, (img) => {
+		    if (img.includes('class="at_el"')) {
+		      const atInfoArr = img
+		        .match(customDataReg)[0]
+		        .slice(13, -1)
+		        .split("&amp;");
+		      atUsersInfo.push({
+		        atUserID: atInfoArr[0].slice(7),
+		        groupNickname: atInfoArr[1].slice(15),
+		      });
+		      return `@${atInfoArr[0].slice(7)} `;
+		    }
+		    if (img.includes('class="emoji_el"')) {
+		      return img.match(customDataReg)[0].slice(23, -1);
+		    }
+		    return "";
+		  });
+		  return {
+		    text,
+		    atUsersInfo,
+		  };
 		}
 	}
 };
 </script>
 
-<script module="snap" lang="renderjs">
-export default {
-	methods: {
-		getSnapFlagUpdate(newValue, oldValue, ownerVm, vm) {
-			if (newValue === null) {
-				return;
-			}
-			const {
-				path
-			} = newValue
-			this.getVideoSnshot(path).then(base64 => {
-				ownerVm.callMethod('receiveSnapBase64', {
-					...newValue,
-					base64
-				})
-			})
-		},
-		getVideoSnshot(item) {
-			return new Promise((reslove, reject) => {
-				var video = document.createElement("VIDEO");
-				video.style.display = 'none';
-				video.setAttribute('crossOrigin', 'anonymous');
-				video.setAttribute("autoplay", "autoplay");
-				video.setAttribute("muted", "muted");
-				video.innerHTML = "<source src=" + item + ' type="audio/mp4">';
-				var canvas = document.createElement("canvas");
-				var ctx = canvas.getContext("2d");
-				video.addEventListener("canplay", function() {
-					var anw = document.createAttribute("width");
-					anw.nodeValue = video.videoWidth;
-					var anh = document.createAttribute("height");
-					anh.nodeValue = video.videoHeight;
-					canvas.setAttributeNode(anw);
-					canvas.setAttributeNode(anh);
-					ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
-					var base64 = canvas.toDataURL("image/png");
-					video.pause();
-					reslove(base64);
-				});
-			});
-		},
-	},
+<style lang="scss" scoped>
+.chating_footer {
+    background-color: #f0f2f6;
 }
-</script>
 
-<style lang="scss" scoped>
 .custom_editor {
-	img {
-		vertical-align: sub;
-	}
+    flex: 1;
 }
 
 .forbidden_footer {
-	width: 100%;
-	height: 112rpx;
-	color: #8e9ab0;
-	display: flex;
-	flex-direction: row;
-	justify-content: center;
-	align-items: center;
-	background: #f0f2f6;
+    width: 750rpx;
+    height: 112rpx;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    background-color: #f0f2f6;
 }
 
 .chat_footer {
-	display: flex;
-	align-items: center;
-	// background-color: #e9f4ff;
-	background: #f0f2f6;
-	// height: 50px;
-	max-height: 120px;
-	padding: 24rpx 20rpx;
+    flex-direction: row;
+    align-items: flex-end; 
+    background-color: #f0f2f6;
+    padding: 16rpx 20rpx;
+}
 
-	.input_content {
-		flex: 1;
-		min-height: 30px;
-		max-height: 120px;
-		margin: 0 24rpx;
-		border-radius: 8rpx;
-		position: relative;
-		font-size: 36rpx;
-		.record_btn {
-			// background-color: #3c9cff;
-			background: #fff;
-			color: black;
-			height: 30px;
-			line-height: 30px;
-			
-		}
-	}
+.input_content {
+    flex: 1;
+    margin-left: 20rpx;
+    margin-right: 20rpx;
+    background-color: #ffffff;
+    border-radius: 10rpx;
+    min-height: 72rpx;
+    /* padding: 10rpx; 移除了 padding,让 editor 占满点击区域 */
+}
 
-	.quote_message {
-		@include vCenterBox();
-		justify-content: space-between;
-		margin-top: 12rpx;
-		padding: 8rpx;
-		// padding-top: 20rpx;
-		border-radius: 6rpx;
-		background-color: #fff;
-		color: #666;
+.record_btn {
+    background-color: #ffffff;
+    height: 72rpx;
+    justify-content: center;
+    align-items: center;
+    border-radius: 10rpx;
+}
 
-		.content {
-			::v-deep uni-view {
-				@include ellipsisWithLine(2);
-			}
-		}
-	}
+.record_text {
+	font-size: 30rpx;
+	color: #333333;
+	font-weight: bold;
+}
 
-	.footer_action_area {
-		display: flex;
-		align-items: center;
-		.emoji_action {
-			margin-right: 24rpx;
-		}
-		.btnSend{
-			font-size: 36rpx;
-			color: #fff;
-			border-radius:6px;
-			background-color:#1ab84d ;
-			padding:20rpx 26rpx;
-		}
+.quote_message {
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 12rpx;
+    padding: 8rpx;
+    background-color: #f5f5f5;
+    border-radius: 6rpx;
+}
 
-		image {
-			width: 26px;
-			height: 26px;
-		}
-	}
+.quote_content {
+    flex: 1;
+    lines: 2;
+    text-overflow: ellipsis;
+    font-size: 24rpx;
+    color: #666666;
+}
 
-	.send_btn {
-		height: 30px;
-		line-height: 30px;
-		background-color: #4a9cfc;
-		padding: 0 8px;
-		border-radius: 4px;
-		color: #fff;
-	}
+.footer_action_area {
+    flex-direction: row;
+    align-items: center;
+    height: 72rpx; 
+}
+
+.footer_icon_left {
+	width: 56rpx;
+	height: 56rpx;
+	margin-bottom: 8rpx;
+}
+
+.footer_icon_right {
+	width: 56rpx;
+	height: 56rpx;
+	margin-right: 20rpx;
+}
+
+.btnSend {
+    width: 110rpx;
+    height: 64rpx;
+    background-color: #1ab84d;
+    border-radius: 8rpx;
+    justify-content: center;
+    align-items: center;
+}
+
+.btnSend_text {
+	color: #fff;
+	font-size: 28rpx;
+}
+
+.panel-container {
+	width: 750rpx;
+	background-color: #f0f2f6;
+	overflow: hidden;
 }
-</style>
+</style>

+ 252 - 251
pages_im/pages/conversation/chating/components/ChatingHeader.vue

@@ -1,69 +1,66 @@
 <template>
-	<u-navbar @click="click" placeholder class="chating_header">
-		<view @click="routeBack" class="u-nav-slot back_view " slot="left">
-			<image class="back_icon" src="../../../../static/images/common_left_arrow.png"  alt="" srcset="" />
-		</view>
-		<view class="u-nav-slot" slot="center">
-			<view class="chating_info" :class="{ chating_info_single: isSingle }">
-				<view class="conversation_info">
-					<view class="title">{{ storeCurrentConversation.showName }}</view>
-					<view v-if="!isSingle && !isNotify" class="sub_title">{{ groupMemberCount }}</view>
-					<!-- <image style="width: 16px; height: 16px" v-if="isRecvMsgOpt" src="../../../../static/images/conversation_not_accept.png" /> -->
-				</view>
-				<view v-if="isSingle" class="online_state">
-					<!-- <view v-if="isSingle && onlineStr" class="dot" :style="{ backgroundColor: isOnline ? '#10CC64' : '#999' }" />
-					<view class="online_str" v-if="isSingle">{{ onlineStr }}</view> -->
-					<!-- <text v-show="isTyping">正在输入...</text> -->
-				</view>
+	<view class="chating_header_container" :style="{ paddingTop: statusBarHeight + 'px' }" @click="click">
+		<view class="chating_header_content">
+			<view @click="routeBack" class="header_left">
+				<image class="back_icon" style="width: 12px; height: 20px;" src="/pages_im/static/images/common_left_arrow.png" mode="aspectFit" />
 			</view>
-
-			<view v-if="showGroupAnnouncement" @click="toGroupAnnouncement(true)" class="group_announcement_tab">
-				<view class="announcement_header">
-					<view class="announcement_header_left">
-						<image src="../../../../static/images/chating_message_notice.png" mode=""></image>
-						<text style="margin-left: 8rpx">群公告</text>
+			
+			<view class="header_center">
+				<view class="chating_info">
+					<view class="conversation_info">
+						<text class="title">{{ storeCurrentConversation ? storeCurrentConversation.showName : '' }}</text>
+						<text v-if="!isSingle && !isNotify" class="sub_title">{{ groupMemberCount }}</text>
+						<image style="width: 16px; height: 16px; margin-left: 4px;" v-if="isRecvMsgOpt" src="/pages_im/static/images/conversation_not_accept.png" />
 					</view>
-					<view @click.stop="toGroupAnnouncement(false)">
-						<image src="../../../../static/images/announcement_close.png" mode=""></image>
+					<view v-if="isSingle" class="online_state">
+						<!-- <view v-if="isSingle && onlineStr" class="dot" :style="{ backgroundColor: isOnline ? '#10CC64' : '#999' }" />
+						<text class="online_str" v-if="isSingle">{{ onlineStr }}</text> -->
 					</view>
 				</view>
-				<view class="announcement_content">
-					{{ getGroupAnnouncementContent }}
-				</view>
 			</view>
-			<view class="group_calling_tab" v-if="callingData">
-				<view class="base_row" @click="showMoreMember = !showMoreMember">
-					<image src="../../../../static/images/group_calling_icon.png" />
-					<text>{{ `${callingData.participant.length}人正在${callingData.invitation.mediaType === 'video' ? '视频' : '语音'}通话` }}</text>
-					<image class="arrow" src="../../../../static/images/group_calling_arrow.png" />
+
+			<view class="header_right">
+				<view class="right_action">
+					<image @click="goSetting" class="action_item" style="width: 23px; height: 23px;" src="/pages_im/static/images/common_more.png" />
 				</view>
-				<view class="member_row" v-show="showMoreMember">
-					<my-avatar
-						size="42"
-						:src="item.userInfo.faceURL"
-						:desc="item.userInfo.nickname"
-						v-for="item in callingData.participant.slice(0, 11)"
-						:key="item.userInfo.userID"/>
+			</view>
+		</view>
+
+		<!-- Group Announcement & Calling (Moved out of header_content for better layout control) -->
+		<view v-if="showGroupAnnouncement" @click="toGroupAnnouncement(true)" class="group_announcement_tab">
+			<view class="announcement_header">
+				<view class="announcement_header_left">
+					<image class="announcement_icon" style="width: 16px; height: 16px;" src="/pages_im/static/images/chating_message_notice.png" mode=""></image>
+					<text class="announcement_text" style="margin-left: 8rpx">群公告</text>
 				</view>
-				<view class="action_row" v-show="showMoreMember" @click="joinRtc">
-					<text>加入</text>
+				<view @click.stop="toGroupAnnouncement(false)">
+					<image class="announcement_close" style="width: 16px; height: 16px;" src="/pages_im/static/images/announcement_close.png" mode=""></image>
 				</view>
 			</view>
+			<text class="announcement_content_text">
+				{{ getGroupAnnouncementContent }}
+			</text>
 		</view>
-		<view class="u-nav-slot" slot="right">
-			<view class="right_action">
-				  <!-- <image
-					v-if="inThisGroup || !isBlack"
-					@click="routeCall"
-					class="action_item"
-					style="width: 23px;height: 23px;"
-					src="../../../../static/images/common_call.png"
-					srcset=""/> -->
-					<image @click="goSetting" class="action_item" style="width: 23px;height: 23px;" src="../../../../static/images/common_more.png" />
 		
+		<view class="group_calling_tab" v-if="callingData">
+			<view class="base_row" @click="showMoreMember = !showMoreMember">
+				<image style="width: 20px; height: 20px;" src="/pages_im/static/images/group_calling_icon.png" />
+				<text style="font-size: 14px; margin-left: 5px; color: #333;">{{ `${callingData.participant.length}人正在${callingData.invitation.mediaType === 'video' ? '视频' : '语音'}通话` }}</text>
+				<image class="arrow" style="width: 9px; height: 6px;" src="/pages_im/static/images/group_calling_arrow.png" />
+			</view>
+			<view class="member_row" v-show="showMoreMember">
+				<my-avatar
+					size="42"
+					:src="item.userInfo.faceURL"
+					:desc="item.userInfo.nickname"
+					v-for="item in callingData.participant.slice(0, 11)"
+					:key="item.userInfo.userID"/>
+			</view>
+			<view class="action_row" v-show="showMoreMember" @click="joinRtc">
+				<text style="color: #006FFF; font-size: 14px;">加入</text>
 			</view>
 		</view>
-	</u-navbar>
+	</view>
 </template>
 
 <script>
@@ -72,7 +69,7 @@ import { PageEvents } from '../../../../constant';
 import IMSDK, { IMMethods, GroupAtType, SessionType, MessageReceiveOptType } from 'openim-uniapp-polyfill';
 import { Platform } from '../../../../constant/im';
 import MyAvatar from '../../../../components/MyAvatar/index.vue';
-import { callingModule } from '../../../../util/imCommon';
+import { callingModule, getIMSDK } from '../../../../util/imCommon';
 
 export default {
 	name: 'ChatingHeader',
@@ -80,10 +77,19 @@ export default {
 		MyAvatar
 	},
 	props: {
-		mutipleCheckVisible: Boolean
+		mutipleCheckVisible: Boolean,
+		storeCurrentConversation: {
+			type: Object,
+			default:{}
+		}
 	},
 	data() {
+		let statusBarHeight = 20;
+		try {
+			statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 20;
+		} catch (e) {}
 		return {
+			statusBarHeight: statusBarHeight,
 			isOnline: false,
 			isTyping: false,
 			onlineStr: '',
@@ -94,12 +100,14 @@ export default {
 		};
 	},
 	computed: {
-		...mapGetters(['storeCurrentConversation', 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeSelfInfo', 'storeBlackList']),
+		...mapGetters([ 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeSelfInfo', 'storeBlackList']),
 		isRecvMsgOpt() {
-			return this.storeCurrentConversation.recvMsgOpt !== MessageReceiveOptType.Nomal;
+			// 默认不显示,只有明确设置为非 Normal 状态才显示
+			const opt = this.storeCurrentConversation?.recvMsgOpt;
+			return opt !== undefined && opt !== MessageReceiveOptType.Normal;
 		},
 		isSingle() {
-			return this.storeCurrentConversation.conversationType === SessionType.Single;
+			return this.storeCurrentConversation?.conversationType === SessionType.Single;
 		},
 		isBlack() {
 			if (!this.isSingle) {
@@ -111,14 +119,19 @@ export default {
 			return this.storeCurrentConversation.conversationType === SessionType.Notification;
 		},
 		groupMemberCount() {
-			return `(${this.storeCurrentGroup?.memberCount ?? 0})`;
+			const count = this.storeCurrentGroup?.memberCount;
+			return (count && count > 0) ? `(${count})` : '';
 		},
 		showGroupAnnouncement() {
 			return this.$store.getters.storeCurrentConversation.groupAtType === GroupAtType.AtGroupNotice;
 		},
 		getGroupAnnouncementContent() {
 			if (this.showGroupAnnouncement) {
-				return this.$store.getters.storeCurrentGroup.notification;
+				const content = this.$store.getters.storeCurrentGroup.notification;
+				if (content) {
+					// nvue text组件不支持html标签,需要过滤掉
+					return content.replace(/<[^>]+>/g, '');
+				}
 			}
 			return '';
 		},
@@ -130,18 +143,23 @@ export default {
 		}
 	},
 	mounted() {
-		this.setIMListenter();
-		if (this.storeCurrentConversation.groupID) {
-			this.joinedGroupChangeHandler({
-				data: { groupID: this.storeCurrentConversation.groupID }
-			});
-		} else {
-			this.getOnlineState();
-		}
+		// console.log("qxj storeCurrentConversation",this.storeCurrentConversation);
+		// 延迟加载 IM 监听,避免阻塞 UI 渲染
+		setTimeout(() => {
+			this.setIMListenter();
+			if (this.storeCurrentConversation.groupID) {
+				this.joinedGroupChangeHandler({
+					data: { groupID: this.storeCurrentConversation.groupID }
+				});
+			} else {
+				this.getOnlineState();
+			}
+		}, 200);
 	},
 	beforeDestroy() {
 		this.disposeIMListener();
-		IMSDK.asyncApi('unsubscribeUsersStatus', IMSDK.uuid(), [this.storeCurrentConversation.userID]);
+		const _IMSDK = getIMSDK();
+		_IMSDK.asyncApi('unsubscribeUsersStatus', _IMSDK.uuid(), [this.storeCurrentConversation.userID]);
 	},
 	methods: {
 		click(e) {
@@ -152,21 +170,24 @@ export default {
 				this.$emit('mutipleCheckUpdate');
 				return;
 			}
+			this.$emit('back');
 			// uni.navigateBack()
 			uni.switchTab({
-				// url: '/pages_im/pages/conversation/conversationList/index'
-				url: "/pages/index/index"
+				url: '/pages_im/pages/conversation/conversationList/index'
 			});
 		},
 		goSetting() {
+			this.$emit('enterSubPage');
 			const url = this.isSingle ? '/pages_im/pages/conversation/singleSettings/index' : '/pages_im/pages/conversation/groupSettings/index';
 			uni.navigateTo({url});
 		},
 		joinRtc() {
+			this.$emit('enterSubPage');
 			callingModule.joinRoomLiveChat(this.callingData);
 		},
 		routeCall() {
 			if (!this.$store.getters.storeCurrentConversation.groupID) {
+				this.$emit('enterSubPage');
 				uni.$emit(PageEvents.RtcCall);
 				return;
 			}
@@ -179,11 +200,13 @@ export default {
 						cancelText: '取消',
 						success: (res) => {
 							if (res.confirm) {
+								this.$emit('enterSubPage');
 								callingModule.joinRoomLiveChat(data);
 							}
 						}
 					});
 				} else {
+					this.$emit('enterSubPage');
 					uni.$emit(PageEvents.RtcCall);
 				}
 			});
@@ -202,35 +225,38 @@ export default {
 					
 				});
 		},
-		updateTyping() {
-			if (this.isTyping) {
-				return;
-			}
-			this.isTyping = true;
-			setTimeout(() => {
-				this.isTyping = false;
-			}, 1500);
-		},
-		updateCallingData(data) {
-			this.callingData = data;
-		},
 		checkOnline() {
-			if (!this.isOnline && this.isSingle) {
+			if (this.isSingle) {
 				this.getOnlineState();
 			}
 		},
-		toGroupAnnouncement(toPage) {
-			IMSDK.asyncApi(IMSDK.IMMethods.ResetConversationGroupAtType, IMSDK.uuid(), this.$store.getters.storeCurrentConversation.conversationID);
-			if (toPage) {
+		toGroupAnnouncement(bool) {
+			if (bool) {
 				uni.navigateTo({
-					url: `/pages_im/pages/conversation/groupAnnouncement/index`
+					url: '/pages_im/pages/conversation/groupAnnouncement/index'
+				});
+			} else {
+				IMSDK.asyncApi('setConversationPrivateChat', IMSDK.uuid(), {
+					conversationID: this.storeCurrentConversation.conversationID,
+					isPrivate: false
+				}).then(() => {
+					this.$store.commit('conversation/UPDATE_CURRENT_CONVERSATION', {
+						key: 'groupAtType',
+						value: GroupAtType.AtNormal
+					});
 				});
 			}
 		},
+		setIMListenter() {
+			// this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight; // 移除此处的重复设置,防止布局跳动
+			IMSDK.subscribe(IMSDK.IMEvents.OnUserStatusChanged, this.userStatusChangeHandler);
+			uni.$on(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
+			uni.$on(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
+		},
 		userStatusChangeHandler({ data }) {
 			if (data.userID === this.storeCurrentConversation.userID) {
-				this.isOnline = !!data.status;
-				if (data.status) {
+				this.isOnline = data.status === 1;
+				if (data.status === 1) {
 					const platformStr = data.platformIDs.map((id) => Platform[id]).join('/');
 					this.onlineStr = platformStr + '在线';
 				} else {
@@ -238,6 +264,16 @@ export default {
 				}
 			}
 		},
+		updateTyping() {
+			this.isTyping = true;
+			if (this.typingTimer) clearTimeout(this.typingTimer);
+			this.typingTimer = setTimeout(() => {
+				this.isTyping = false;
+			}, 2000);
+		},
+		updateCallingData(data) {
+			this.callingData = data;
+		},
 		joinedGroupChangeHandler({ data }) {
 			if (data.groupID === this.storeCurrentConversation.groupID) {
 				IMSDK.asyncApi(IMMethods.IsJoinGroup, IMSDK.uuid(), data.groupID).then((res) => {
@@ -245,11 +281,6 @@ export default {
 				});
 			}
 		},
-		setIMListenter() {
-			IMSDK.subscribe(IMSDK.IMEvents.OnUserStatusChanged, this.userStatusChangeHandler);
-			uni.$on(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
-			uni.$on(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
-		},
 		disposeIMListener() {
 			IMSDK.unsubscribe(IMSDK.IMEvents.OnUserStatusChanged, this.userStatusChangeHandler);
 			uni.$off(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
@@ -260,186 +291,156 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.chating_header {
-	border: 2rpx solid #e8eaef;
-	::v-deep .u-navbar__content__left {
-		padding: 0;
-	}
-	.chating_info {
-		@include vCenterBox();
-		flex-direction: column;
+.chating_header_container {
+	background-color: #ffffff;
+	border-bottom-width: 1px;
+	border-bottom-color: #e8eaef;
+	position: relative; /* Ensure absolute children position relative to this */
+	flex-direction: column;
+}
 
-		&_single {
-			margin-bottom: 24rpx;
-		}
+.chating_header_content {
+	height: 44px;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+}
 
-		.conversation_info {
-			flex-direction: row;
-			justify-content: center;
-			@include vCenterBox();
-			.title {
-				@include nomalEllipsis();
-				// max-width: 280rpx;
-				font-size: 17px;
-				font-weight: 500;
-				margin-top: 5px;
-			}
-			.sub_title {
-				margin-left: 8rpx;
-			}
-		}
+.header_left {
+	width: 60px;
+	height: 44px;
+	flex-direction: row;
+	align-items: center;
+	justify-content: flex-start;
+	padding-left: 12px;
+}
 
-		.online_state {
-			@include vCenterBox();
-			flex-direction: row;
-			margin-top: 6rpx;
-			// position: absolute;
-			// top: 2px;
-			// left: 50%;
-			// transform: translateX(-50%);
-			font-size: 20rpx;
-			color: #999;
+.header_center {
+	flex: 1;
+	height: 44px;
+	justify-content: center;
+	align-items: center;
+}
 
-			.dot {
-				background-color: #10cc64;
-				width: 12rpx;
-				height: 12rpx;
-				border-radius: 50%;
-				margin-right: 12rpx;
-			}
+.header_right {
+	width: 60px;
+	height: 44px;
+	flex-direction: row;
+	align-items: center;
+	justify-content: flex-end;
+	padding-right: 12px;
+}
 
-			.online_str {
-				@include nomalEllipsis();
-				max-width: 280rpx;
-			}
-		}
-	}
-	::v-deep .u-navbar__content__right {
-		padding: 0;
-	}
-	.right_action {
-		@include vCenterBox();
-		flex-direction: row;
-		margin-right: 24rpx;
-		.action_item {
-			padding: 12rpx;
-		}
-		.u-icon {
-			margin-left: 12rpx;
-		}
-	}
+.back_icon {
+	width: 12px;
+	height: 20px;
 }
 
+.action_item {
+	width: 23px;
+	height: 23px;
+}
 
-.back_view{
-	width:45px;
-	height: 40px;
+.chating_info {
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
 }
 
-.back_icon {
-	padding: 24rpx;
-	margin-left: 10rpx;
-	width: 24rpx;
-	height: 40rpx;
+.conversation_info {
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
 }
 
+.title {
+	font-size: 19px;
+	font-weight: 500;
+	lines: 1;
+	text-overflow: ellipsis;
+	color: #333333;
+}
+
+.sub_title {
+	margin-left: 4px;
+	font-size: 14px;
+	color: #333333;
+}
 
+.online_state {
+	flex-direction: row;
+	align-items: center;
+	margin-top: 2px;
+}
+
+.right_action {
+	flex-direction: row;
+	align-items: center;
+}
+
+/* Group Announcement */
 .group_announcement_tab {
-	display: flex;
-	flex-direction: column;
-	align-items: flex-start;
-	width: 80%;
-	position: absolute;
-	left: 6%;
-	// bottom: -44px;
-	margin-top: 40rpx;
-	padding: 14rpx 32rpx;
+	/* Position changed to flow layout */
+	padding: 10px 16px;
 	background-color: #f0f6ff;
-	border-radius: 12rpx;
-
-	.announcement_header {
-		@include vCenterBox();
-		width: 100%;
-		flex-direction: row;
-		justify-content: space-between;
-		margin-bottom: 16rpx;
-		&_left {
-			@include vCenterBox();
-		}
-	}
-	.announcement_content {
-		@include ellipsisWithLine(2);
-		margin: 0 12rpx;
-		font-size: 24rpx;
-		color: #617183;
-	}
-	image {
-		width: 16px;
-		height: 16px;
-		min-width: 16px;
-	}
+	border-radius: 6px;
+	flex-direction: column;
+	margin: 10px 10px 0 10px; /* Add top margin */
 }
 
-.group_calling_tab {
-	position: absolute;
-	left: 0;
-	width: 80%;
-	margin-top: 12px;
-	margin-left: 5%;
-	padding: 24rpx;
-	background-color: #f4f9ff;
-	border-radius: 8rpx;
-	color: #5496eb;
-	font-size: 24rpx;
-	.base_row {
-		display: flex;
-		align-items: center;
+.announcement_header {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 8px;
+}
 
-		image {
-			width: 10px;
-			height: 10px;
-		}
+.announcement_header_left {
+	flex-direction: row;
+	align-items: center;
+}
 
-		text {
-			margin-left: 16rpx;
-		}
+.announcement_text {
+	font-size: 14px;
+	color: #333;
+}
 
-		.arrow {
-			width: 9px;
-			height: 6px;
-			position: absolute;
-			right: 24rpx;
-		}
-	}
+.announcement_content_text {
+	lines: 2;
+	text-overflow: ellipsis;
+	font-size: 12px;
+	color: #617183;
+}
 
-	.member_row {
-		display: flex;
-		// justify-content: space-between;
-		flex-wrap: wrap;
-		padding: 24rpx 28rpx;
-		margin-top: 24rpx;
-		background-color: #fff;
-		border-bottom: 1px solid rgba(151, 151, 151, 0.16);
-		border-top-left-radius: 8rpx;
-		border-top-right-radius: 8rpx;
+/* Group Calling */
+.group_calling_tab {
+	/* Position changed to flow layout */
+	padding: 12px;
+	background-color: #f4f9ff;
+	border-radius: 4px;
+	margin: 10px 10px 0 10px; /* Add top margin */
+}
 
-		.u-avatar {
-			margin-bottom: 16rpx;
+.base_row {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+}
 
-			&:not(:nth-child(6n)) {
-				margin-right: calc(6% / 2);
-			}
-		}
-	}
+.member_row {
+	flex-direction: row;
+	flex-wrap: wrap;
+	padding: 12px 14px;
+	margin-top: 12px;
+	background-color: #ffffff;
+	border-bottom-width: 1px;
+	border-bottom-color: rgba(151, 151, 151, 0.16);
+}
 
-	.action_row {
-		display: flex;
-		justify-content: center;
-		padding: 24rpx;
-		background-color: #fff;
-		font-size: 28rpx;
-		border-bottom-left-radius: 8rpx;
-		border-bottom-right-radius: 8rpx;
-	}
+.action_row {
+	flex-direction: row;
+	justify-content: center;
+	padding: 12px;
+	background-color: #ffffff;
 }
 </style>

+ 56 - 31
pages_im/pages/conversation/chating/components/ChatingList.vue

@@ -17,17 +17,18 @@
 
 		<view id="scroll_wrap">
 			<u-loadmore nomoreText="" v-show="showLoading" :status="loadMoreStatus" />
-			<view v-for="(item, index) in storeHistoryMessageList" :key="item.clientMsgID">
-				<view v-if="getTimeLine" class="time_gap_line">
-					{{ getTimeLine(item, storeHistoryMessageList[index - 1]) }}
+			<view v-for="(item, index) in storeHistoryMessageList" :key="item.clientMsgID+index">
+				<view v-if="getTimeLine" :class="bgUrl==''?'time_gap_line':'time_gap_line color'">
+					{{ getTimeLine(item, storeHistoryMessageList[index - 1])}}
 				</view>
-				<message-item-render
-					:mutipleCheckVisible="mutipleCheckVisible"
-					:menuOutsideFlag="menuOutsideFlag"
-					@messageItemRender="messageItemRender"
-					:source="item"
-					:isSender="item.sendID === storeCurrentUserID"
-					@closeMenu="closeMenu"/>
+                <message-item-render
+                    :mutipleCheckVisible="mutipleCheckVisible"
+                    :menuOutsideFlag="menuOutsideFlag"
+                    @messageItemRender="messageItemRender"
+                    @messageItemReady="messageItemReady"
+                    :source="item"
+                    :isSender="item.sendID === storeCurrentUserID"
+                    @closeMenu="closeMenu"/>
 					 <view v-if="sendFailedDesc" class="time_gap_line send_failed_tip">
 						{{ sendFailedDesc(item) }}
 					</view>
@@ -58,7 +59,7 @@ import { mapGetters, mapActions } from 'vuex';
 import dayjs from 'dayjs';
 import { SendMessageFailedType } from '../../../../constant';
 import MessageItemRender from './MessageItem/index.vue';
-import { MessageStatus } from 'openim-uniapp-polyfill';
+import { MessageStatus } from '@/pages_im/constant/imConstants';
 
 export default {
 	name: '',
@@ -71,6 +72,7 @@ export default {
 	},
 	data() {
 		return {
+			 scrollViewHeight: 0, // 滚动容器高度
 			scrollIntoView: '',
 			scrollWithAnimation: false,
 			scrollTop: 0,
@@ -169,6 +171,7 @@ export default {
 		},
 		async loadMessageList(isLoadMore = false) {
 			this.showLoading=isLoadMore;
+			console.log("qxj conversationID",this.storeCurrentConversation.conversationID);
 			this.messageLoadState.loading = true;
 			const lastMsgID = this.storeHistoryMessageList[0]?.clientMsgID;
 			const options = {
@@ -177,6 +180,7 @@ export default {
 				startClientMsgID: this.storeHistoryMessageList[0]?.clientMsgID ?? '',
 				viewType: 0
 			};
+			
 			try {
 				const { emptyFlag } = await this.getHistoryMesageList(options);
 				if (emptyFlag) {
@@ -212,7 +216,7 @@ export default {
 			}
 		},
 		scrollToBottom(isInit = false, isRecv = false) {
-			console.log("qxj scrollToBottom");
+			
 			if (isRecv && !this.needScoll) {
 				return;
 			}
@@ -220,7 +224,15 @@ export default {
 				this.withAnimation = true;
 				setTimeout(() => (this.withAnimation = false), 100);
 			}
-
+			// 使用底部锚点,避免通过 scrollTop 造成闪烁
+			// this.$nextTick(() => {
+			// 	this.scrollIntoView = 'auchormessage_bottom_item';
+			// 	if (isInit) {
+			// 		// 再次 nextTick 触发,保证定位后通知页面
+			// 		this.$nextTick(() => this.$emit('initSuccess'));
+			// 	}
+			// });
+			
 			this.$nextTick(() => {
 				uni.createSelectorQuery()
 					.in(this)
@@ -228,23 +240,30 @@ export default {
 					.boundingClientRect((res) => {
 						// let top = res.height - this.scrollViewHeight;
 						// if (top > 0) {
-						this.scrollTop = this.old.scrollTop;
-						this.$nextTick(() => (this.scrollTop = res.height));
-						if (isInit) {
-							this.$emit('initSuccess');
-						}
+							this.scrollTop = this.old.scrollTop;
+							this.$nextTick(() => (this.scrollTop = res.height));
+							if (isInit) {
+								this.$emit('initSuccess');
+							}
 						// }
-					})
-					.exec();
-			});
-		},
-		scrollToAnchor(auchor) {
-			this.$nextTick(function () {
-				this.scrollIntoView = auchor;
+					}).exec();
 			});
 		},
+    scrollToAnchor(auchor) {
+      this.$nextTick(function () {
+          this.scrollIntoView = auchor;
+      });
+    },
+     messageItemReady(clientMsgID) {
+		  // const last = this.storeHistoryMessageList[this.storeHistoryMessageList.length - 1];
+		  // if (!last) return;
+		  // if (clientMsgID === last.clientMsgID) {
+			 //  this.scrollToBottom(true);
+		  // }
+       },
 		checkInitHeight() {
 			this.getEl('#scroll_view').then(({ height }) => {
+				// console.log('height',height)
 				this.bgHeight = `${height}px`;
 			});
 		},
@@ -270,11 +289,12 @@ export default {
 
 <style lang="scss" scoped>
 #scroll_view {
-	flex: 1;
+	flex:1;
 	background-repeat: no-repeat;
 	position: relative;
+	background-attachment: fixed;
+	z-index: 10 !important;
 }
-
 .watermark-view {
 	width: 100%;
 	height: 100%;
@@ -305,7 +325,8 @@ export default {
 	bottom: 12px;
 	left: 50%;
 	transform: translateX(-50%);
-	width: fit-content;
+	/* width: fit-content; */
+    align-self: center;
 	font-size: 24rpx;
 	color: #006aff;
 }
@@ -314,8 +335,11 @@ export default {
 	position: relative;
 	padding: 0 10vw 12rpx;
 	text-align: center;
-	font-size: 16px;
+	font-size: 14px;
 	color: #A5A5A5;
+	&.color{
+		color:#fff
+	}
 }
 
 .fade-leave,
@@ -325,11 +349,12 @@ export default {
 
 .fade-leave-active,
 .fade-enter-active {
-	transition: all 0.3s;
+	transition-property: opacity;
+	transition-duration: 0.3s;
 }
 
 .fade-leave-to,
 .fade-enter {
 	opacity: 0;
 }
-</style>
+</style>

+ 51 - 25
pages_im/pages/conversation/chating/components/ChatingList1.vue

@@ -18,8 +18,8 @@
 		<view id="scroll_wrap">
 			<u-loadmore nomoreText="" v-show="showLoading" :status="loadMoreStatus" />
 			<view v-for="(item, index) in storeHistoryMessageList" :key="item.clientMsgID">
-				<view v-if="getTimeLine" class="time_gap_line">
-					{{ getTimeLine(item, storeHistoryMessageList[index - 1]) }}
+				<view v-if="getTimeLine" :class="bgUrl==''?'time_gap_line':'time_gap_line color'">
+					{{ getTimeLine(item, storeHistoryMessageList[index - 1])}}
 				</view>
 				<message-item-render
 					:mutipleCheckVisible="mutipleCheckVisible"
@@ -58,7 +58,7 @@ import { mapGetters, mapActions } from 'vuex';
 import dayjs from 'dayjs';
 import { SendMessageFailedType } from '../../../../constant';
 import MessageItemRender from './MessageItem/index.vue';
-import { MessageStatus } from 'openim-uniapp-polyfill';
+import { MessageStatus } from '@/pages_im/constant/imConstants';
 
 export default {
 	name: '',
@@ -71,6 +71,7 @@ export default {
 	},
 	data() {
 		return {
+			 scrollViewHeight: 0, // 滚动容器高度
 			scrollIntoView: '',
 			scrollWithAnimation: false,
 			scrollTop: 0,
@@ -112,24 +113,42 @@ export default {
 			};
 		},
 		getTimeLine() {
-		    return (message, preMessage) => {
-		        const sendTime = message.sendTime;
-		        const preSendTime = preMessage?.sendTime;
-		        if (preSendTime && sendTime - preSendTime > 600000) {
-		            const now = dayjs();
-		            const messageDate = dayjs(sendTime);
-		            // 检查是否在几秒内
-		            if (now.diff(messageDate, 'second') < 60) {
-		                return '刚刚';
-		            }
-		            // 根据年份决定显示格式
-		            return messageDate.isSame(now, 'year')
-		                ? messageDate.format('MM-DD') // 本年度
-		                : messageDate.format('YYYY-MM-DD'); // 非本年度
+		  return (message, preMessage) => {
+		    const sendTime = message.sendTime;
+		    const preSendTime = preMessage?.sendTime;
+		    
+		    // 只在时间间隔大于10分钟时才显示时间线
+		    if (preSendTime && sendTime - preSendTime > 600000) {
+		      const now = dayjs();
+		      const messageDate = dayjs(sendTime);
+		      
+		      // 检查是否在今天
+		      if (messageDate.isSame(now, 'day')) {
+		        // 检查是否在几秒内
+		        if (now.diff(messageDate, 'second') < 60) {
+		          return '刚刚';
 		        }
-		        
-		        return null;
-		    };
+		        // 今天且大于一分钟,显示小时:分钟
+		        return messageDate.format('HH:mm');
+		      }
+		      
+		      // 检查是否是昨天
+		      if (messageDate.isSame(now.subtract(1, 'day'), 'day')) {
+		        return '昨天 ' + messageDate.format('HH:mm');
+		      }
+		      
+		      // 检查是否在今年
+		      if (messageDate.isSame(now, 'year')) {
+		        // 今年但不是今天或昨天,显示月日 小时:分钟
+		        return messageDate.format('MM-DD HH:mm');
+		      }
+		      
+		      // 今年以前,显示年月日 小时:分钟
+		      return messageDate.format('YYYY-MM-DD HH:mm');
+		    }
+		    
+		    return null;
+		  };
 		}
 	},
 	beforeMount() {
@@ -137,6 +156,7 @@ export default {
 	},
 	mounted() {
 		this.loadMessageList();
+		 // 监听窗口大小变化(适配旋转屏幕等场景)
 	},
 	methods: {
 		...mapActions('message', ['getHistoryMesageList']),
@@ -227,6 +247,7 @@ export default {
 		},
 		checkInitHeight() {
 			this.getEl('#scroll_view').then(({ height }) => {
+				console.log('height',height)
 				this.bgHeight = `${height}px`;
 			});
 		},
@@ -252,11 +273,12 @@ export default {
 
 <style lang="scss" scoped>
 #scroll_view {
-	flex: 1;
+	flex:1;
 	background-repeat: no-repeat;
 	position: relative;
+	background-attachment: fixed;
+	z-index: 10 !important;
 }
-
 .watermark-view {
 	width: 100%;
 	height: 100%;
@@ -287,7 +309,7 @@ export default {
 	bottom: 12px;
 	left: 50%;
 	transform: translateX(-50%);
-	width: fit-content;
+	/* width: fit-content; */
 	font-size: 24rpx;
 	color: #006aff;
 }
@@ -298,6 +320,9 @@ export default {
 	text-align: center;
 	font-size: 16px;
 	color: #A5A5A5;
+	&.color{
+		color:#fff
+	}
 }
 
 .fade-leave,
@@ -307,11 +332,12 @@ export default {
 
 .fade-leave-active,
 .fade-enter-active {
-	transition: all 0.3s;
+	transition-property: opacity;
+	transition-duration: 0.3s;
 }
 
 .fade-leave-to,
 .fade-enter {
 	opacity: 0;
 }
-</style>
+</style>

+ 1 - 1
pages_im/pages/conversation/chating/components/ChatingListMe.vue

@@ -52,7 +52,7 @@ import { mapGetters, mapActions } from 'vuex';
 import dayjs from 'dayjs';
 import { SendMessageFailedType } from '../../../../constant';
 import MessageItemRender from './MessageItem/index.vue';
-import { MessageStatus } from 'openim-uniapp-polyfill';
+import { MessageStatus } from '@/pages_im/constant/imConstants';
 import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
 import MescrollMoreItemMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js";
 

+ 471 - 135
pages_im/pages/conversation/chating/components/MessageItem/AudioMessageRender.vue

@@ -2,19 +2,91 @@
   <view
     @click="playAudio"
     class="audio_message_container bg_container"
-    :class="{ audio_message_container_self: isSender }"
-  >
-    <view class="cricleplay">
-      <view class="small"></view>
-      <view class="middle" :class="{ stopanimate: !playing }"></view>
-      <view class="large" :class="{ stopanimate: !playing }"></view>
+    :class="[isSender ? 'audio_message_container_self' : '']">
+    <view class="cricleplay" :class="[isSender ? 'cricleplay_self' : '']">
+      <view class="play-icon-small" :class="[isSender ? 'play-icon-small-self' : '']"></view>
+      <view class="play-icon-middle" :class="[isSender ? 'play-icon-middle-self' : '']" :style="{ opacity: (playing && animateStep < 2) ? 0 : 1 }"></view>
+      <view class="play-icon-large" :class="[isSender ? 'play-icon-large-self' : '']" :style="{ opacity: (playing && animateStep < 3) ? 0 : 1 }"></view>
     </view>
     <text class="audio_duration">{{ message.soundElem.duration }}''</text>
+    <view v-if="showUnreadIndicator" class="audio_unread_dot"></view>
   </view>
 </template>
 
 <script>
-import { secFormat } from "../../../../../util/imCommon";
+// 音频播放单例管理
+let _audioContext = null;
+let _currentVM = null;
+
+function getAudioContext() {
+  if (!_audioContext) {
+    _audioContext = uni.createInnerAudioContext();
+    // #ifdef APP-PLUS
+    // if (uni.getSystemInfoSync().platform === 'ios') {
+    //   uni.setInnerAudioOption({
+    //     obeyMuteSwitch: false
+    //   });
+    // }
+    // #endif
+
+    _audioContext.onPlay(() => {
+      if (_currentVM) {
+        _currentVM.playing = true;
+        _currentVM.paused = false;
+        // Animation is started immediately on click to reduce perceived latency
+        if (_currentVM.animateStep === 3) { // Only start if not already running
+             _currentVM.startAnimate();
+        }
+        _currentVM.startSafetyTimer();
+      }
+    });
+    
+    _audioContext.onPause(() => {
+      if (_currentVM) {
+        _currentVM.playing = false;
+        _currentVM.paused = true;
+        _currentVM.stopAnimate();
+        _currentVM.stopSafetyTimer();
+      }
+    });
+    
+    _audioContext.onStop(() => {
+      if (_currentVM) {
+        _currentVM.playing = false;
+        _currentVM.paused = false;
+        _currentVM.stopAnimate();
+        _currentVM.stopSafetyTimer();
+      }
+    });
+    
+    _audioContext.onEnded(() => {
+      if (_currentVM) {
+        _currentVM.playing = false;
+        _currentVM.paused = false;
+        _currentVM.stopAnimate();
+        _currentVM.stopSafetyTimer();
+      }
+    });
+
+    _audioContext.onTimeUpdate(() => {
+      if (_currentVM && _currentVM.playing) {
+        _currentVM.handleTimeUpdate();
+      }
+    });
+    
+    _audioContext.onError((err) => {
+      console.error('Audio play error:', err);
+      if (_currentVM) {
+        _currentVM.playing = false;
+        _currentVM.paused = false;
+        _currentVM.stopAnimate();
+        _currentVM.stopSafetyTimer();
+      }
+    });
+  }
+  return _audioContext;
+}
+
 export default {
   name: "AudioMessageRender",
   props: {
@@ -25,169 +97,433 @@ export default {
     return {
       playing: false,
       paused: false,
-      innerAudioContext: uni.createInnerAudioContext(),
+      hasRead: false,
+      animateStep: 3,
+      animateTimer: null,
+      safetyTimer: null,
     };
   },
   mounted() {
-    this.setPlayListener();
+    this.checkReadState();
+  },
+  watch: {
+    message: {
+      handler() {
+        this.checkReadState();
+      },
+    },
+    playing(val) {
+      if (val) {
+        this.startAnimate();
+        this.startSafetyTimer();
+      } else {
+        this.stopAnimate();
+        this.stopSafetyTimer();
+      }
+    },
   },
   beforeDestroy() {
-    this.innerAudioContext.destroy();
+    this.stopAnimate();
+    this.stopSafetyTimer();
+    // 如果当前组件正在播放,销毁前停止播放
+    if (_currentVM === this) {
+      const ctx = getAudioContext();
+      if (ctx) {
+        ctx.stop();
+      }
+      _currentVM = null;
+    }
+  },
+  computed: {
+    showUnreadIndicator() {
+      return !this.isSender && !this.hasRead && Boolean(this.message?.clientMsgID);
+    },
   },
   methods: {
-    setPlayListener() {
-      const onPlayHandler = () => {
+    getStorageKey() {
+      const userID = this.$store?.getters?.storeCurrentUserID || "";
+      return `${userID || "GLOBAL"}_AudioMessageReadIds`;
+    },
+    loadReadIds() {
+      const key = this.getStorageKey();
+      const stored = uni.getStorageSync(key);
+      const readIds = Array.isArray(stored) ? stored : [];
+      return { key, readIds };
+    },
+    checkReadState() {
+      const clientMsgID = this.message?.clientMsgID;
+      if (!clientMsgID) {
+        this.hasRead = true;
+        return;
+      }
+      const { readIds } = this.loadReadIds();
+      this.hasRead = readIds.includes(clientMsgID);
+    },
+    markAsRead() {
+      const clientMsgID = this.message?.clientMsgID;
+      if (this.hasRead || !clientMsgID) {
+        return;
+      }
+      const { key, readIds } = this.loadReadIds();
+      if (readIds.includes(clientMsgID)) {
+        this.hasRead = true;
+        return;
+      }
+      uni.setStorageSync(key, [...readIds, clientMsgID]);
+      this.hasRead = true;
+    },
+    playAudio() {
+      const ctx = getAudioContext();
+      
+      // 如果点击的是当前正在播放/暂停的音频
+      if (_currentVM === this) {
+        if (this.playing) {
+          ctx.pause();
+          this.playing = false;
+          this.paused = true;
+        } else {
+          ctx.play();
+          this.playing = true;
+          this.paused = false;
+          this.markAsRead();
+        }
+        return;
+      }
+
+      // 如果有其他音频正在播放,先停止它
+      if (_currentVM) {
+        _currentVM.playing = false;
+        _currentVM.paused = false;
+        _currentVM.stopAnimate();
+      }
+      
+      // 切换到当前音频
+      _currentVM = this;
+      
+      // 注意:不要调用 ctx.stop(),因为在 iOS 或某些 Android 设备上,stop() 会触发 onStop 事件
+      // 如果 onStop 是异步执行的,可能会错误地将 _currentVM.playing 设置为 false
+      // 直接设置 src 并 play 会自动停止上一个音频
+      
+      const sourceUrl = this.message.soundElem.sourceUrl;
+      const soundPath = this.message.soundElem.soundPath;
+
+      // 1. 尝试播放本地文件 (通常是自己发送的消息)
+      if (soundPath && this.isFileExist(soundPath)) {
+        this.playLocalFile(ctx, soundPath);
+        return;
+      }
+
+      // 2. 尝试播放缓存文件
+      const cachedKey = 'AUDIO_CACHE_' + sourceUrl;
+      const cachedPath = uni.getStorageSync(cachedKey);
+      if (cachedPath) {
+        if (this.isFileExist(cachedPath)) {
+          this.playLocalFile(ctx, cachedPath);
+          return;
+        } else {
+          // 缓存文件不存在(可能被系统清理),清除缓存记录
+          uni.removeStorageSync(cachedKey);
+        }
+      }
+
+      // 3. 优先尝试流式播放(减少等待时间),同时后台下载缓存
+      if (sourceUrl) {
+        // 立即播放远程地址
+        ctx.src = sourceUrl;
+        ctx.play();
         this.playing = true;
         this.paused = false;
-      };
-      const onPauseOrStorHandler = () => {
+        this.startAnimate(); // 立即开始动画,减少用户感知的延迟
+        this.markAsRead();
+        
+        // 后台静默下载缓存
+        this.downloadFileSilently(sourceUrl);
+      } else {
+        uni.showToast({ title: '无效的音频地址', icon: 'none' });
         this.playing = false;
-        this.paused = true;
-      };
-      this.innerAudioContext.onPlay(onPlayHandler);
-      this.innerAudioContext.onPause(onPauseOrStorHandler);
-      // this.innerAudioContext.onStop(onPauseOrStorHandler)
-      this.innerAudioContext.onEnded(onPauseOrStorHandler);
+        _currentVM = null;
+      }
     },
-    playAudio() {
-      this.setPlayListener();
-      if (this.paused) {
-        this.innerAudioContext.play();
-        return;
+    
+    isFileExist(path) {
+      try {
+        uni.getFileSystemManager().accessSync(path);
+        return true;
+      } catch (e) {
+        return false;
       }
-      if (this.playing) {
-        this.innerAudioContext.pause();
+    },
+    
+    playLocalFile(ctx, path) {
+        ctx.src = path;
+        ctx.play();
+        // 立即更新状态,确保UI响应
+        this.playing = true;
+        this.paused = false;
+        this.startAnimate(); // 立即开始动画
+        this.markAsRead();
+    },
+
+    downloadFileSilently(url) {
+        // 检查是否已有缓存或正在下载
+        if (uni.getStorageSync('AUDIO_CACHE_' + url)) return;
+        
+        uni.downloadFile({
+            url: url,
+            success: (res) => {
+                if (res.statusCode === 200) {
+                    uni.setStorageSync('AUDIO_CACHE_' + url, res.tempFilePath);
+                }
+            },
+            fail: (err) => {
+                console.error('Silent audio download failed:', err);
+            }
+        });
+    },
+
+    // 保留旧方法作为备用(如果需要)
+    downloadAndPlay(ctx, url) {
+        uni.downloadFile({
+            url: url,
+            success: (res) => {
+                // 如果用户已经切换到其他音频,丢弃本次下载结果
+                if (_currentVM !== this) return;
+
+                if (res.statusCode === 200) {
+                    const tempFilePath = res.tempFilePath;
+                    // 缓存路径
+                    uni.setStorageSync('AUDIO_CACHE_' + url, tempFilePath);
+                    // 播放
+                    ctx.src = tempFilePath;
+                    ctx.play();
+                    
+                    // 立即更新状态
+                    this.playing = true;
+                    this.paused = false;
+                    this.markAsRead();
+                } else {
+                    console.error('Audio download failed:', res);
+                    uni.showToast({ title: '音频加载失败', icon: 'none' });
+                    this.playing = false;
+                    _currentVM = null;
+                }
+            },
+            fail: (err) => {
+                // 如果用户已经切换到其他音频,忽略错误
+                if (_currentVM !== this) return;
+
+                console.error('Audio download error:', err);
+                uni.showToast({ title: '音频下载出错', icon: 'none' });
+                this.playing = false;
+                _currentVM = null;
+            }
+        });
+    },
+
+    startAnimate() {
+      this.stopAnimate();
+      this.animateStep = 1;
+      this.animateTimer = setInterval(() => {
+        this.animateStep++;
+        if (this.animateStep > 3) {
+          this.animateStep = 1;
+        }
+      }, 500);
+    },
+
+    stopAnimate() {
+      if (this.animateTimer) {
+        clearInterval(this.animateTimer);
+        this.animateTimer = null;
+      }
+      this.animateStep = 3;
+      // Force update by using $nextTick if available or simple assignment
+      // In nvue/weex, sometimes direct assignment doesn't trigger UI update if value is same type but not detected change?
+      // But here 3 should trigger opacity 1. 
+      // Let's verify template logic: :style="{ opacity: animateStep >= 2 ? 1 : 0 }"
+      // If animateStep is 3, opacity is 1. 
+    },
+
+    handleTimeUpdate() {
+      const ctx = getAudioContext();
+      if (!ctx) return;
+      
+      const duration = this.message.soundElem.duration;
+      // Check if playback exceeded duration (with small tolerance)
+      if (duration && ctx.currentTime >= duration + 0.5) {
+        this.forceStop();
         return;
       }
-      this.innerAudioContext.src = this.message.soundElem.sourceUrl;
-      this.innerAudioContext.play();
+      
+      // Reset safety timer based on remaining time + buffer
+      this.resetSafetyTimer(duration - ctx.currentTime);
+    },
+    
+    startSafetyTimer() {
+      const duration = this.message.soundElem.duration || 60;
+      // Initial safe buffer: duration + 5s (to account for buffering)
+      this.resetSafetyTimer(duration + 5);
+    },
+    
+    stopSafetyTimer() {
+      if (this.safetyTimer) {
+        clearTimeout(this.safetyTimer);
+        this.safetyTimer = null;
+      }
+    },
+    
+    resetSafetyTimer(seconds) {
+      this.stopSafetyTimer();
+      const ms = Math.max(1000, seconds * 1000 + 1000); // Minimum 1s, Buffer 1s
+      this.safetyTimer = setTimeout(() => {
+        this.forceStop();
+      }, ms);
+    },
+    
+    forceStop() {
+      this.playing = false;
+      this.paused = false;
+      this.stopAnimate();
+      this.stopSafetyTimer();
+      
+      if (_currentVM === this) {
+        const ctx = getAudioContext();
+        if (ctx) ctx.stop();
+        _currentVM = null;
+      }
     },
   },
 };
 </script>
 
 <style lang="scss" scoped>
-.audio_message_container {
-  @include vCenterBox();
+.bg_container {
+  padding: 16rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  background-color: #ffffff;
+}
 
-  .audio_duration {
-    font-size: 28rpx;
-  }
+.audio_wrapper {
+  flex-direction: row;
+  align-items: center;
+}
 
-  .cricleplay {
-    display: flex;
-    align-items: center;
-    margin-right: 12rpx;
-
-    .small {
-      width: 5px;
-      height: 5px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-    }
+.audio_wrapper_self {
+  flex-direction: row-reverse;
+}
 
-    .middle {
-      width: 10px;
-      height: 10px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-      margin-left: -5px;
-      animation: show2 2s ease-in-out infinite;
-      opacity: 1;
-    }
+.audio_message_container {
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 16rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #E8EAEF;
+  min-width: 140rpx;
+  background-color: #ffffff;
+  /* z-index: 1; */
+}
 
-    @keyframes show2 {
-      0% {
-        opacity: 0;
-      }
+.audio_message_container_self {
+  flex-direction: row-reverse;
+  border-radius: 12rpx 0 12rpx 12rpx;
+  background-color: #95EC69 !important;
+  border-color: #95EC69;
+}
 
-      30% {
-        opacity: 1;
-      }
+.audio_duration {
+  font-size: 18px;
+  color: #333333;
+}
 
-      100% {
-        opacity: 0;
-      }
-    }
+.cricleplay {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-right: 12rpx;
+  flex-shrink: 0;
+}
 
-    .large {
-      width: 20px;
-      height: 20px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-      margin-left: -15px;
-      animation: show3 2s ease-in-out infinite;
-      opacity: 1;
-    }
+.cricleplay_self {
+  margin-right: 0;
+  margin-left: 12rpx;
+  flex-direction: row-reverse;
+}
 
-    @keyframes show3 {
-      0% {
-        opacity: 0;
-      }
+.play-icon-small {
+  width: 7px;
+  height: 5px;
+  flex-shrink: 0;
+  border-style: solid;
+  border-width: 1px;
+  border-radius: 50%;
+  border-color: transparent;
+  border-right-color: #3870e4;
+}
 
-      60% {
-        opacity: 1;
-      }
+.play-icon-small-self {
+  border-color: transparent;
+  border-left-color: #000000;
+  border-right-color: transparent;
+}
 
-      100% {
-        opacity: 0;
-      }
-    }
+.play-icon-middle {
+  width: 10px;
+  height: 10px;
+  flex-shrink: 0;
+  border-style: solid;
+  border-width: 1px;
+  border-radius: 50%;
+  border-color: transparent;
+  border-right-color: #3870e4;
+  margin-left: -5px;
+  opacity: 1;
+}
 
-    .stopanimate {
-      -moz-animation-name: none;
-      -webkit-animation-name: none;
-      -ms-animation-name: none;
-      animation-name: none;
-    }
-  }
+.play-icon-middle-self {
+  border-color: transparent;
+  border-left-color: #000000;
+  border-right-color: transparent;
+  margin-left: 0;
+  margin-right: -5px;
+}
 
-  &_self {
-    flex-direction: row-reverse;
+.play-icon-large {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+  border-style: solid;
+  border-width: 1px;
+  border-radius: 50%;
+  border-color: transparent;
+  border-right-color: #3870e4;
+  margin-left: -15px;
+  opacity: 1;
+}
 
-    .cricleplay {
-      margin-right: 0;
-      margin-left: 12rpx;
-      flex-direction: row-reverse;
+.play-icon-large-self {
+  border-color: transparent;
+  border-left-color: #000000;
+  border-right-color: transparent;
+  margin-left: 0;
+  margin-right: -15px;
+}
 
-      .small,
-      .middle,
-      .large {
-        border-left-color: #3870e4;
-        border-right-color: transparent;
-      }
+/* Animations in NVUE are limited, using opacity might work but @keyframes are not fully supported in all nvue modes. 
+   Ideally should use bindingx or weex animation module. 
+   For now, we remove complex animation or keep it simple. */
 
-      .middle {
-        margin-left: 0;
-        margin-right: -5px;
-      }
 
-      .large {
-        margin-left: 0;
-        margin-right: -15px;
-      }
-    }
-  }
+
+.audio_unread_dot {
+  width: 14rpx;
+  height: 14rpx;
+  border-radius: 50%;
+  background-color: #FF4D4F;
+  margin-left: 10rpx;
 }
+
 </style>

+ 29 - 17
pages_im/pages/conversation/chating/components/MessageItem/CardMessageRender.vue

@@ -30,6 +30,7 @@ export default {
   computed: {},
   methods: {
     toUserInfo() {
+      this.$emit('enterSubPage');
       uni.navigateTo({
         url: `/pages_im/pages/common/userCard/index?sourceID=${this.message.cardElem.userID}`,
       });
@@ -39,27 +40,38 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-	
 .card_message_container {
-  @include colBox(false);
+  display: flex;
+  flex-direction: column;
   background-color: #fff;
   box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
   border-radius: 12rpx;
   width: 444rpx;
-  border: 1px solid #ececec;
-  .card_info {
-		@include vCenterBox();
-		padding: 24rpx 32rpx;
-		border-bottom: 1px solid #e9e9e9;
-		.card_name {
-		  @include ellipsisWithLine(1);
-		  margin-left: 24rpx;
-		}
-  }
-  .card_desc {
-		padding: 8rpx 0 8rpx 42rpx;
-		color: #999;
-  }
-  
+  border-width: 1px;
+  border-style: solid;
+  border-color: #ececec;
+}
+
+.card_info {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 24rpx 32rpx;
+  border-bottom-width: 1px;
+  border-bottom-style: solid;
+  border-bottom-color: #e9e9e9;
+}
+
+.card_name {
+  lines: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  margin-left: 24rpx;
+  font-size: 16px; /* 补充可能的默认字体大小 */
+}
+
+.card_desc {
+  padding: 8rpx 0 8rpx 42rpx;
+  color: #999;
 }
 </style>

+ 1037 - 738
pages_im/pages/conversation/chating/components/MessageItem/CustomMessageRender.vue

@@ -1,155 +1,185 @@
 <template>
-	<view class="custom" @click="handleCustomerItem">
+	<view class="custom" @click.stop="handleCustomerItem" @touchstart="handleTouchStart" @longpress="handleLongPress">
 		<template v-if="isCustom === 'order'">
-			<view class="order-item">
-				<view class="title">问诊订单</view>
-				<view class="text">患者:{{ extension.patientName }} {{ extension.sex }} {{ extension.mobile }}</view>
-				<view class="text">患病时间:{{ extension.duration }}</view>
-				<view class="text">就诊情况:{{ extension.isVisit }}</view>
-				<view class="text">病情描述:{{ extension.title }}</view>
+			<view class="wechat-bill-card">
+				<view class="wechat-bill-header">
+					<text class="wechat-bill-title">问诊订单</text>
+					<text class="wechat-bill-status">已创建</text>
+				</view>
+				<view class="wechat-bill-body">
+					<view class="wechat-bill-row">
+						<text class="wechat-bill-label">患者信息</text>
+						<text class="wechat-bill-value">{{ extension.patientName }} {{ extension.sex }} {{ extension.mobile }}</text>
+					</view>
+					<view class="wechat-bill-row">
+						<text class="wechat-bill-label">患病时间</text>
+						<text class="wechat-bill-value">{{ extension.duration }}</text>
+					</view>
+					<view class="wechat-bill-row">
+						<text class="wechat-bill-label">就诊情况</text>
+						<text class="wechat-bill-value">{{ extension.isVisit }}</text>
+					</view>
+					<view class="wechat-bill-row">
+						<text class="wechat-bill-label">病情描述</text>
+						<text class="wechat-bill-value wechat-bill-value-multi">{{ extension.title }}</text>
+					</view>
+				</view>
+				<view class="wechat-bill-footer">
+					<text class="wechat-bill-action">查看订单详情</text>
+					<text class="wechat-bill-arrow">></text>
+				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'prescribe'">
-			<view class="prescribe-item">
-				<view class="title">电子处方单</view>
-				<view class="text">诊断:{{ extension.diagnose }}</view>
-				<view class="btns">
-					<view class="btn">查看处方</view>
+			<view class="wechat-service-card">
+				<view class="wechat-service-header">
+					<text class="wechat-service-icon">💊</text>
+					<text class="wechat-service-title">电子处方单</text>
+				</view>
+				<view class="wechat-service-body">
+					<text class="wechat-service-desc">诊断结果:{{ extension.diagnose }}</text>
+				</view>
+				<view class="wechat-service-footer">
+					<text class="wechat-service-action">查看处方</text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'report'">
-			<view class="report-item">
-				<view class="title">问诊报告单</view>
-				<view class="btns">
-					<view class="btn">查看报告单</view>
+			<view class="wechat-service-card">
+				<view class="wechat-service-header">
+					<text class="wechat-service-icon">📝</text>
+					<text class="wechat-service-title">问诊报告单</text>
+				</view>
+				<view class="wechat-service-body">
+					<text class="wechat-service-desc">点击查看详细问诊报告</text>
+				</view>
+				<view class="wechat-service-footer">
+					<text class="wechat-service-action">查看报告单</text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'follow'">
-			<view class="prescribe-item">
-				<view class="title">随访单</view>
-				<view class="btns">
-					<view class="btn">查看随访</view>
+			<view class="wechat-service-card">
+				<view class="wechat-service-header">
+					<text class="wechat-service-icon">📋</text>
+					<text class="wechat-service-title">随访单</text>
+				</view>
+				<view class="wechat-service-body">
+					<text class="wechat-service-desc">请填写随访记录</text>
+				</view>
+				<view class="wechat-service-footer">
+					<text class="wechat-service-action">查看随访</text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'drugReport'">
-			<view class="report-item">
-				<view class="title">用药报告单</view>
-				<view class="btns">
-					<view class="btn">查看报告单</view>
+			<view class="wechat-service-card">
+				<view class="wechat-service-header">
+					<text class="wechat-service-icon">🔬</text>
+					<text class="wechat-service-title">用药报告单</text>
+				</view>
+				<view class="wechat-service-body">
+					<text class="wechat-service-desc">点击查看详细用药报告</text>
+				</view>
+				<view class="wechat-service-footer">
+					<text class="wechat-service-action">查看报告单</text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'package'">
-			<view class="package-item">
-				<view class="title">{{extension.title}}</view>
-				<view class="cover" v-if="!!extension.image">
-					<image :src="extension.image" mode="aspectFill"></image>
+			<view class="wechat-link-card">
+				<view class="wechat-link-content">
+					<text class="wechat-link-title">{{extension.title}}</text>
 				</view>
-				<view v-else class="btns">
-					<view class="btn">查看疗法</view>
+				<image class="wechat-link-image" :src="extension.image" mode="aspectFill" v-if="!!extension.image"></image>
+				<view class="wechat-link-placeholder" v-else>
+					<text class="wechat-link-icon">🔗</text>
 				</view>
 			</view>
 		</template>
-
-
+		
+		
 		<template v-else-if="isCustom === 'couponPackage'">
-			<view class="package-item">
-				<view class="title">{{extension.title}}</view>
-				<view class="stitle"><i class="psub">¥</i> <i class="psub price"> {{ extension.price.toFixed(2) }} </i>
-					{{ "(满"+extension.minPrice.toFixed(2)+"元可用)" }}
-				</view>
-				<view class="stitle stitle1">{{ extension.limitTime+"到期 不可叠加使用"}}</view>
-				<view class="cover" v-if="!!extension.image">
-					<image :src="extension.image" mode="aspectFill"></image>
+			<view class="wechat-coupon-card">
+				<view class="wechat-coupon-body">
+					<view class="wechat-coupon-left">
+						<text class="wechat-coupon-symbol">¥</text>
+						<text class="wechat-coupon-price">{{ extension.price.toFixed(2) }}</text>
+					</view>
+					<view class="wechat-coupon-right">
+						<text class="wechat-coupon-title">{{extension.title}}</text>
+						<text class="wechat-coupon-cond">满{{ extension.minPrice.toFixed(2) }}元可用</text>
+					</view>
 				</view>
-				<view v-else class="btns">
-					<view class="btn">查看私域疗法券</view>
+				<view class="wechat-coupon-footer">
+					<text class="wechat-coupon-date">{{ extension.limitTime }} 到期</text>
+					<text class="wechat-coupon-action">查看详情 ></text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'inquirySelect'">
-			<view class="package-item">
-				<view class="title">{{extension.title}}</view>
-				<view class="stitle">医疗专家团队远程咨询</view>
-				<view class="cover" v-if="!!extension.image">
-					<image :src="extension.image" mode="aspectFill"></image>
+			<view class="wechat-link-card">
+				<view class="wechat-link-content">
+					<text class="wechat-link-title">{{extension.title}}</text>
+					<text class="wechat-link-desc">医疗专家团队远程咨询</text>
 				</view>
-				<view v-else class="btns">
-					<view class="btn">查看会诊</view>
+				<image class="wechat-link-image" :src="extension.image" mode="aspectFill" v-if="!!extension.image"></image>
+				<view class="wechat-link-placeholder" v-else>
+					<text class="wechat-link-icon">🔗</text>
 				</view>
 			</view>
 		</template>
-
+		
+		<template v-else-if="isCustom === 'applyForm'">
+			<view class="package-item">
+				<view class="title">{{extension.diagnose}}</view>
+				<!-- <view class="stitle">医疗专家团队远程咨询</view> -->
+				<view  class="btns">
+					<view class="btn">申请确认</view>
+				</view>
+			</view>
+		</template>
+		
 		<template v-else-if="isCustom === 'startInquiry'">
-			<view class="content content-in">
-				<span>{{ extension.title }}</span>
+			<view class="system-bubble">
+			  <text class="system-bubble-text">{{ extension.title }}</text>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'finishInquiry'">
-			<view class="content content-ou">
-				<span>{{ extension.title }}</span>
+			<view class="system-bubble">
+			    <text class="system-bubble-text">{{ extension.title }}</text>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'course'">
-			<view class="box">
-				<view class="list-item">
-					<view class="list-con border-line">
-						<view class="list-itemtxt">
-							<!-- <text class="list-title textOne">{{extension.courseName}}</text> -->
-							<text class="list-desc textTwo">{{extension.title}}</text>
-						</view>
-						<view class="list-cover">
-							<image :src="extension.courseUrl" mode="aspectFill"></image>
-						</view>
-					</view>
-
-					<!-- <view class="list-footer">
-						<view style="flex: 1;overflow: hidden;">
-							<image :src="extension.imageUrl" mode="aspectFill"></image>
-							<text class="list-time textOne">{{extension.qwUserName}}</text>
-						</view>
-						<text class="list-time">过期时间:{{extension.updateTime}}</text>
-					</view> -->
-
+			<view class="wechat-link-card">
+				<view class="wechat-link-content course-item" style="min-height: 80px;">
+					<text class="wechat-link-title course-title" style="font-size: 21px; ">{{extension.title}}</text>
+					<text class="wechat-link-desc course-desc" v-if="extension.courseName">{{extension.courseName}}</text>
 				</view>
-			</view>
-		</template>
-
-		<template v-else-if="isCustom === 'live'">
-			<view class="box">
-				<view class="liveList">
-					<view class="list-item">
-						<image :src="liveDefUrl" mode="aspectFill"></image>
-						<view class="info">
-							<text>{{extension.liveName}}</text>
-						</view>
-						<view class="livebox">直播</view>
-					</view>
+				<image class="wechat-link-image" style="width: 130rpx;height: 130rpx;margin-top:3px " :src="extension.courseUrl" mode="aspectFill" v-if="!!extension.courseUrl"></image>
+				<view class="wechat-link-placeholder" v-else>
+					<text class="wechat-link-icon">🎬</text>
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else-if="isCustom === 'luckyBag'">
 			<view class="box">
-				<view class="luckyBagBox">
-					<view class="u-f">
+				<view class="luckyBagBox" :class="isSender ? 'image_right' : 'image_left'">
+					<view class="u-f es-ac">
 						<view class="u-f-ajc es-mr-20">
-							<image class="es-icon-56" src="/pages_im/static/images/luckyBag/new_lucky_bag_icon.png"
-								mode=""></image>
+							<image class="es-icon-56" src="/pages_im/static/images/luckyBag/new_lucky_bag_icon.png" mode=""></image>
 						</view>
 						<view class="es-fs-32 es-fw-500 es-c-white">
-							福袋
+							给你发送福袋
 						</view>
 					</view>
 					<view class="es-mt-16 luckyBagBox-line"></view>
@@ -159,654 +189,923 @@
 				</view>
 			</view>
 		</template>
-
+		
 		<template v-else>
 			<view class="content"></view>
 		</template>
-
-		<CollectLuckyBag :luckyBagExtension="luckyBagExtension" @close="collectLuckyBagShow=false"
-			:show="collectLuckyBagShow" />
 	</view>
 </template>
 
 <script>
-	import {
-		getLuckyBagInfo
-	} from '@/pages_im/api/luckyBag.js'
-	import CollectLuckyBag from '@/pages_im/pages/conversation/chating/components/ChatingFooter/CollectLuckyBag.vue'
-	import MyAvatar from '../../../../../components/MyAvatar/index.vue';
-
-	export default {
-		name: 'CustomMessageRender',
-		components: {
-			MyAvatar,
-			CollectLuckyBag
-		},
-		props: {
-			message: {
-				type: Object,
-				default: () => ({
-					customElem: {
-						data: '{}' // 默认空JSON
-					},
-					cardElem: {
-						userID: ''
-					}
-				})
-			}
-		},
-		data() {
-			return {
-				isCustom: {}, // 明确初始化null
-				payload: {}, // 与服务端数据结构对齐
-				extension: {}, // 保持响应式基础结构
-				liveDefUrl: "https://fbylive.obs.cn-southwest-2.myhuaweicloud.com/fs/20250920/1758354962430.png",
-				collectLuckyBagShow: false,
-				luckyBagExtension: '',
-			};
-		},
-		mounted() {
-			this.safeParseData();
-		},
-		methods: {
-			safeParseData() {
-				try {
-
-					const rawData = this.message?.customElem?.data || '{}';
-					const parsed = JSON.parse(rawData);
-
-					//结构化赋值
-					this.payload = parsed.payload || {};
-					this.extension = this.payload.extension || {};
-					this.isCustom = this.payload.data ?? null;
-
-				} catch (e) {
-					console.error('[消息解析失败]', e);
-					this.$emit('parse-error', {
-						raw: this.message,
-						error: e
-					});
-				}
-			},
-			async handleCustomerItem() {
-				const rawData = this.message?.customElem.data;
-				let item = JSON.parse(rawData);
-				console.log("handleCustomerItem:::");
-				console.log(item);
-				if (item.payload.data == 'order') {
-					uni.navigateTo({
-						url: '/pages/store/inquiryOrderDetails?orderId=' + item.payload.description
-					});
-				} else if (item.payload.data == 'prescribe') {
-					var prescribe = item.payload.extension;
-					uni.navigateTo({
-						url: '/pages/store/prescribeDetails?prescribeId=' + prescribe.prescribeId
-					});
-				} else if (item.payload.data == 'package') {
-					var packageItem = item.payload.extension;
-					let companyId = packageItem.companyId;
-					let companyUserId = packageItem.companyUserId;
-					let nUrl = "/pages/store/packageDetails?packageId=" + packageItem.packageId + "&companyId=" +
-						companyId + "&companyUserId=" + companyUserId;
-					uni.navigateTo({
-						url: nUrl
-					});
-				} else if (item.payload.data == 'couponPackage') {
-					console.log("qxj item", item);
-					var cItem = item.payload.extension;
-					let companyId = cItem.companyId;
-					let companyUserId = cItem.companyUserId;
-					let nUrl = '/pages/company/couponDetails?couponId=' + cItem.couponId;
-					if (this.$companyUserIsLogin()) {
-						nUrl += "&isShare=1"
-					}
-					uni.navigateTo({
-						url: nUrl
-					});
-				} else if (item.payload.data == 'inquirySelect') {
-					var cItem = item.payload.extension;
-					let companyId = cItem.companyId;
-					let companyUserId = cItem.companyUserId;
-					let type = cItem.type;
-					if (type == 3) {
-						let nUrl = "/pages/store/inquiryForm2_2?inquiryType=2&orderType=2&inquirySubType=" + type +
-							"&companyId=" + companyId + "&companyUserId=" + companyUserId;
-						if (this.$companyUserIsLogin()) {
-							nUrl += "&isShare=1"
-						}
-						uni.navigateTo({
-							url: nUrl
-						});
-					} else if (type == 4) {
-
-					} else {
-						let nUrl = "/pages/store/inquiryForm2_1?inquiryType=2&orderType=2&inquirySubType=" + type +
-							"&companyId=" + companyId + "&companyUserId=" + companyUserId;
-						if (this.$companyUserIsLogin()) {
-							nUrl += "&isShare=1"
-						}
-						uni.navigateTo({
-							url: nUrl
-						});
-					}
-				} else if (item.payload.data == 'follow') {
-					var follow = item.payload.extension;
-					if (follow.writeStatus == 0) {
-						uni.navigateTo({
-							url: '/pages/user/doFollow?followId=' + follow.followId
-						});
-					} else if (follow.writeStatus == 1) {
-						uni.navigateTo({
-							url: '/pages/user/followDetails?followId=' + follow.followId
-						});
-					}
-				} else if (item.payload.data == 'report') {
-					uni.navigateTo({
-						url: '/pages/store/inquiryOrderReport?orderId=' + item.payload.description
-					});
-				} else if (item.payload.data == 'drugReport') {
-					// var report=JSON.parse(item.payload.extension);
-					uni.navigateTo({
-						url: '/pages/user/drugReportDetails?reportId=' + item.payload.description
-					});
-
-				} else if (item.payload.data == 'course') {
-					uni.removeStorageSync('showCourseBox')
-					console.log("qxj extension", item);
-					let couseItem = item.payload.extension;
-					if (uni.getStorageSync('companyUserId')) {
-						const queryString = couseItem.appRealLink.split('?')[1] || '';
-						const courseParamMatch = queryString.match(/course=({[^&]+})/);
-						if (!courseParamMatch) {
-							uni.showToast({
-								icon: 'none',
-								title: '看课链接不存在'
-							})
-						}
-						const courseJson = decodeURIComponent(courseParamMatch[1]);
-						const courseData = JSON.parse(courseJson);
-						return uni.navigateTo({
-							url: `/pages_managedata/coursedetail?videoId=${courseData.videoId}&id=${courseData.id}&periodId=${courseData.periodId}`
-						});
-					}
-					if (couseItem.appRealLink) {
-						if (/^\//.test(couseItem.appRealLink)) {
-							uni.navigateTo({
-								url: couseItem.appRealLink,
-							})
-						} else {
-							uni.navigateTo({
-								url: "/pages_im/pages/common/webH5/index?url=" + couseItem.appRealLink
-							});
-						}
-					} else {
-						uni.showToast({
-							icon: 'none',
-							title: "暂无看课链接",
-						});
-					}
-				} else if (item.payload.data == 'live') {
-					var liveItem = item.payload.extension;
-					let userInfo = JSON.parse(uni.getStorageSync("userInfo"));
-					let timeSpan = (new Date()).getTime();
-					let liveUrl =
-						`http://h5.fbylive.com/videoH5/pages/home/living?liveId=${liveItem.liveId}&userId=${userInfo.userId}&t=${timeSpan}`;
-					console.log("qxj liveUrl", liveUrl);
-					//let liveUrl=`http://localhost:8081/videoH5/pages/home/living?liveId=${liveItem.liveId}&name=直播间`
-					if (liveUrl) {
-						uni.navigateTo({
-							url: "/pages_im/pages/common/webH5/index?url=" + liveUrl + "&name=直播间"
-						});
-					} else {
-						uni.showToast({
-							icon: 'none',
-							title: "暂无直播间链接",
-						});
-					}
-				} else if (item.payload.data == "luckyBag") {
-					const res = await getLuckyBagInfo({
-						recordId: item.payload.extension.recordId
-					})
-					if (res.code == 200) {
-						let msg = ''
-						if (res.data.collectType == 0) {
-							let companyUserInfo = uni.getStorageSync('companyUserInfo')
-							if (companyUserInfo) {
-								return uni.showToast({
-									icon: 'none',
-									title: '福袋还未领取'
-								})
-							}
-							this.luckyBagExtension = item.payload.extension
-							this.collectLuckyBagShow = true
-							return
-						} else if (res.data.collectType == 1) {
-							msg = '福袋已领取'
-						} else if (res.data.collectType == 2) {
-							msg = '福袋已失效'
-						}
-						uni.showToast({
-							icon: 'none',
-							title: msg
-						})
-					} else {
-						uni.showToast({
-							icon: 'none',
-							title: res.msg
-						})
-					}
-				}
-			}
-		}
-	};
-</script>
-
-<style lang="scss" scoped>
-	@mixin u-flex($flexD, $alignI, $justifyC) {
-		display: flex;
-		flex-direction: $flexD;
-		align-items: $alignI;
-		justify-content: $justifyC;
-	}
-
-	.card_message_container {
-		@include colBox(false);
-		background-color: #fff;
-		box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
-		border-radius: 12rpx;
-		width: 444rpx;
-		border: 1px solid #ececec;
-
-		.card_info {
-			@include vCenterBox();
-			padding: 24rpx 32rpx;
-			border-bottom: 1px solid #e9e9e9;
-
-			.card_name {
-				@include ellipsisWithLine(1);
-				margin-left: 24rpx;
-			}
-		}
-
-		.card_desc {
-			padding: 8rpx 0 8rpx 42rpx;
-			color: #999;
-		}
-	}
-
-
-	.custom {
-		padding: 0 0 12px 0;
-
-		.order-item {
-			width: 400rpx;
-			padding: 15rpx;
-			padding: 15rpx;
-			border: 1rpx solid #ececec;
-			box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
-			background-color: #fff;
-			border-radius: 15rpx;
-			display: flex;
-			flex-direction: column;
-			align-items: flex-start;
-			justify-content: flex-start;
-
-			.title {
-				font-size: 16px;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #111111;
-			}
-
-			.text {
-				font-size: 12px;
-				font-family: PingFang SC;
-				color: #9a9a9c;
-				padding: 6rpx 0;
-			}
-		}
-
-		.prescribe-item {
-			width: 400rpx;
-			padding: 15rpx;
-			border: 1rpx solid #ececec;
-			box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
-			background-color: #fff;
-			border-radius: 15rpx;
-			display: flex;
-			flex-direction: column;
-			align-items: flex-start;
-			justify-content: flex-start;
-
-			.title {
-				font-size: 16px;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #111111;
-			}
-
-			.text {
-				margin-top: 10rpx;
-				font-size: 12px;
-				font-family: PingFang SC;
-				color: #9a9a9c;
-			}
-
-			.btns {
-				padding: 15rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				width: 100%;
-
-				.btn {
-					border-radius: 30rpx;
-					padding: 15rpx 30rpx;
-					border: 1rpx solid #ececec;
-					font-family: PingFang SC;
-					color: #333333;
-				}
-			}
-		}
-
-		.report-item {
-			width: 400rpx;
-			padding: 15rpx;
-			border: 1rpx solid #ececec;
-			box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
-			background-color: #fff;
-			border-radius: 15rpx;
-			display: flex;
-			flex-direction: column;
-			align-items: flex-start;
-			justify-content: flex-start;
-
-			.title {
-				font-size: 16px;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #111111;
-			}
-
-			.text {
-				margin-top: 10rpx;
-				font-size: 12px;
-				font-family: PingFang SC;
-				color: #9a9a9c;
-			}
-
-			.btns {
-				padding: 15rpx;
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				width: 100%;
-
-				.btn {
-					border-radius: 30rpx;
-					padding: 15rpx 30rpx;
-					border: 1rpx solid #ececec;
-					font-family: PingFang SC;
-					color: #333333;
-				}
-			}
-		}
-
-		.package-item {
-			width: 450rpx;
-			padding: 15rpx;
-			border: 1rpx solid #ececec;
-			box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
-			background-color: #fff;
-			border-radius: 15rpx;
-			display: flex;
-			flex-direction: column;
-			align-items: flex-start;
-			justify-content: flex-start;
-
-			.title {
-				font-size: 16px;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #333;
-				margin-bottom: 10rpx;
-			}
-
-			.stitle {
-				font-size: 15px;
-				font-family: PingFang SC;
-				font-weight: 500;
-				color: #666;
-				margin-bottom: 10rpx;
-			}
-
-			.psub {
-				color: #e41f19;
-				font-size: 15px;
-				font-style: normal;
-			}
-
-			.price {
-				font-size: 18px;
-				font-style: normal;
-				font-weight: bold;
-			}
-
-			.stitle1 {
-				font-size: 14px;
-			}
-
-			.cover {
-				width: calc(100%);
-				height: 400rpx;
-				border-radius: 16rpx;
-				box-sizing: border-box;
-
-				image {
-					width: 100%;
-					height: 100%;
-				}
-			}
-		}
-
-	}
-
-
-	.content {
-		padding: 10px;
-		background: #f0f0f0;
-
-		&-in {
-			border-radius: 0px 10px 10px 10px;
-		}
-
-		&-out {
-
-			border-radius: 10px 0px 10px 10px;
-		}
+import MyAvatar from '../../../../../components/MyAvatar/index.vue';
+
+export default {
+  name: 'CustomMessageRender',
+  components: { MyAvatar },
+  props: {
+    message: {
+      type: Object,
+      default: () => ({
+        customElem: {
+          data: '{}' // 默认空JSON
+        },
+        cardElem: {
+          userID: ''
+        }
+      })
+    },
+	isSender: {
+		type: Boolean,
+		default: false
 	}
-
-
-	.list {
-
-		&-item {
-			width: 450rpx;
-			padding: 0rpx;
-			border: 1rpx solid #ececec;
-			box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
-			background-color: #fff;
-			border-radius: 16rpx;
-			margin-bottom: 20rpx;
-			box-sizing: border-box;
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 15px;
-			color: #333333;
-			line-height: 36rpx;
-
-			image {
-				flex-shrink: 0;
-				width: 150rpx;
-				height: 120rpx;
-				margin-left: 16rpx;
-				background: #F5F6F6;
-				border-radius: 10rpx 10rpx 10rpx 10rpx;
-				overflow: hidden;
+  },
+  data() {
+    return {
+      isCustom: null,
+      payload: {},
+      extension: {},
+      touchStartTime: 0,
+      isLongPress: false
+    };
+  },
+  watch: {
+  	message: {
+  		handler(val) {
+  			this.safeParseData();
+  		},
+  		deep: true,
+  		immediate: true
+  	}
+  },
+  methods: {
+    safeParseData() {
+      try {
+        const rawData = this.message?.customElem?.data || '{}';
+        const parsed = JSON.parse(rawData);
+        //结构化赋值
+        this.payload = parsed.payload || {};
+        this.extension = this.payload.extension || {};
+        this.isCustom = this.payload.data ?? null;
+		
+
+      } catch (e) {
+			console.error('[消息解析失败]', e);
+			this.$emit('parse-error', {
+			  raw: this.message,
+			  error: e
+			});
+      }
+    },
+
+    /**
+     * 处理触摸开始事件,记录时间戳
+     */
+    handleTouchStart() {
+      this.touchStartTime = Date.now();
+      this.isLongPress = false;
+    },
+
+    /**
+     * 处理长按事件
+     */
+    handleLongPress() {
+      this.isLongPress = true;
+      this.$emit('longpress', this.message);
+    },
+
+    /**
+     * 处理点击事件,根据触摸时长拦截长按触发的点击
+     */
+    handleCustomerItem() {
+      // 如果是长按触发的(通过标志位或时间差判断),则拦截点击
+      const duration = Date.now() - this.touchStartTime;
+      if (this.isLongPress || duration > 350) return;
+      
+      const rawData = this.message?.customElem.data;
+      let item = JSON.parse(rawData);
+	  // console.log("handleCustomerItem:::", item);
+      if (item.payload.data == 'order') {
+		this.$emit('enterSubPage');
+      	uni.navigateTo({
+      		url: '/pages/store/inquiryOrderDetails?orderId=' + item.payload.description
+      	});
+      } else if (item.payload.data == 'prescribe') {
+      	var prescribe = item.payload.extension;
+		this.$emit('enterSubPage');
+      	uni.navigateTo({
+      		url: '/pages/store/prescribeDetails?prescribeId=' + prescribe.prescribeId
+      	});
+      } else if (item.payload.data == 'package') {
+			var packageItem = item.payload.extension;
+			let companyId=packageItem.companyId;
+			let companyUserId=packageItem.companyUserId;		
+			let nUrl="/pages/store/packageDetails?packageId="+packageItem.packageId+"&companyId="+companyId+"&companyUserId="+companyUserId;
+			this.$emit('enterSubPage');
+			uni.navigateTo({url: nUrl});
+      } 
+	  
+	  
+	  else if (item.payload.data == 'couponPackage') {
+		    // console.log("qxj item",item);
+			var cItem = item.payload.extension;
+			let companyId=cItem.companyId;
+			let companyUserId=cItem.companyUserId;		
+			let nUrl='/pages/company/couponDetails?couponId='+cItem.couponId+"&isShare=1";
+			this.$emit('enterSubPage');
+			uni.navigateTo({url: nUrl});
+	  } 
+	  
+	  else if (item.payload.data == 'inquirySelect') {
+			var cItem = item.payload.extension;
+			let companyId=cItem.companyId;
+			let companyUserId=cItem.companyUserId;	
+			let type=cItem.type;
+			this.$emit('enterSubPage');
+			if(type==3){
+				let nUrl="/pages/store/inquiryForm2_2?inquiryType=2&orderType=2&inquirySubType="+type+"&companyId="+companyId+"&companyUserId="+companyUserId+"&isShare=1";
+				uni.navigateTo({url: nUrl});
+			} else if(type==4){
+				
+			}  else {
+				let nUrl="/pages/store/inquiryForm2_1?inquiryType=2&orderType=2&inquirySubType="+type+"&companyId="+companyId+"&companyUserId="+companyUserId+"&isShare=1";
+				uni.navigateTo({url: nUrl});
 			}
-		}
-
-		&-con {
-			width: 100%;
-			padding: 24rpx;
-			box-sizing: border-box;
-			@include u-flex(column, flex-start, space-between);
-		}
-
-		&-cover {
-			width: 100%;
-			margin-top: 20rpx;
-			@include u-flex(row, flex-start, flex-end);
-		}
-
-
-		&-itemtxt {
-			flex: 1;
-			overflow: hidden;
-			word-break: break-all;
-			display: block;
-		}
-
-		&-title {
-			word-break: break-all;
-			display: block;
-		}
-
-		&-desc {
-			margin-top: 0rpx;
-			font-size: 18px;
-			color: #333;
-			line-height: 44rpx;
-		}
-
-		&-time {
-			font-size: 11px;
-			color: #999999;
-		}
-
-		&-footer {
-			padding: 20rpx 24rpx;
-			box-sizing: border-box;
-			@include u-flex(row, center, space-between);
-			position: relative;
-
-			view {
-				@include u-flex(row, center, flex-start);
-			}
-
-			image {
-				height: 50rpx;
-				width: 50rpx;
-				border-radius: 50%;
-				margin: 0 10rpx 0 0;
+	  } 
+	  else if (item.payload.data == 'follow') {
+      	var follow = item.payload.extension;
+		this.$emit('enterSubPage');
+      	if (follow.writeStatus == 0) {
+      		uni.navigateTo({
+      			url: '/pages/user/doFollow?followId=' + follow.followId
+      		});
+      	} else if (follow.writeStatus == 1) {
+      		uni.navigateTo({
+      			url: '/pages/user/followDetails?followId=' + follow.followId
+      		});
+      	}
+      } else if (item.payload.data == 'report') {
+			this.$emit('enterSubPage');
+			uni.navigateTo({
+				url: '/pages/store/inquiryOrderReport?orderId=' + item.payload.description
+			});
+      } else if (item.payload.data == 'drugReport') {
+			// var report=JSON.parse(item.payload.extension);
+			this.$emit('enterSubPage');
+			uni.navigateTo({
+				url: '/pages/user/drugReportDetails?reportId=' + item.payload.description
+			});
+      }else if (item.payload.data == 'course') {
+		    //console.log("qxj extension:",item);
+			var couseItem=item.payload.extension;
+			if(couseItem.appRealLink) {
+				this.$emit('enterSubPage');
+				uni.navigateTo({
+					url: couseItem.appRealLink
+				});
+			}else {
+				uni.showToast({
+					icon:'none',
+					title: "暂无看课链接",
+				});
 			}
-		}
-	}
-
-
-	.liveList {
-
-		// display: flex;
-		// justify-content: space-between;
-		// flex-wrap: wrap;
-		.list-item {
-			border-radius: 16rpx;
-			width: 300rpx;
-			height: 500rpx;
-			background-color: #0d0d0d;
-			margin-bottom: 24rpx;
-			overflow: hidden;
-			position: relative;
-
-			.livebox {
-				background: #ff5500;
-				border-radius: 20rpx;
-				font-size: 26rpx;
-				color: #fff;
-				text-align: center;
-				position: absolute;
-				left: 18rpx;
-				top: 18rpx;
-				padding: 8rpx 14rpx;
-			}
-
-			image {
-				width: 100%;
-				height: 100%;
-				margin-left: 0;
-			}
-
-			video {
-				width: 100%;
-				height: 100%;
-				object-fit: cover;
-			}
-
-			.info {
-				position: absolute;
-				left: 20rpx;
-				bottom: 14rpx;
-				right: 20rpx;
-				color: #ffffff;
-				display: flex;
-				align-items: center;
-
-				.live-badge {
-					background-color: #e74c3c;
-					padding: 4rpx 12rpx;
-					border-radius: 8rpx;
-					font-size: 20rpx;
-					margin-right: 12rpx;
-				}
-
-				.record-badge {
-					background-color: #3498db;
-					padding: 4rpx 12rpx;
-					border-radius: 8rpx;
-					font-size: 20rpx;
-					margin-right: 12rpx;
-				}
-			}
-
-			.error-tip {
-				position: absolute;
-				top: 50%;
-				left: 50%;
-				transform: translate(-50%, -50%);
-				color: #fff;
-				background-color: rgba(0, 0, 0, 0.7);
-				padding: 16rpx 24rpx;
-				border-radius: 8rpx;
-				font-size: 24rpx;
+	    
+		} else if (item.payload.data == "luckyBag") {
+			console.log("qxj extension:",item);
+			let luckyBagItem=item.payload.extension;
+			if(luckyBagItem.appRealLink) {
+				let str = luckyBagItem.appRealLink.split('?link=')[1]
+				uni.navigateTo({
+					url: '/pages/courseAnswer/luckyBag?link='+str
+				});
+			}else {
+				uni.showToast({
+					icon:'none',
+					title: "福袋信息为空",
+				});
 			}
 		}
+    }
+  }
+};
+</script>
 
-		// 使列表项均匀分布
-		.list-item:nth-child(2n) {
-			margin-right: 0;
-		}
-	}
-
-	.luckyBagBox {
-		width: 384rpx;
-		height: 160rpx;
-		border-radius: 8rpx;
-		padding: 24rpx;
-		background: linear-gradient(180deg, #FD303D 0%, #FEB6A7 99.99%);
-
-		.luckyBagBox-line {
-			width: 100%;
-			height: 1rpx;
-			background: rgba(255, 255, 255, 0.2);
-		}
+<style lang="scss" scoped>
+	
+.card_message_container {
+	display: flex;
+	flex-direction: column;
+	background-color: #fff;
+	box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
+	border-radius: 12rpx;
+	width: 444rpx;
+	border-width: 1px;
+	border-style: solid;
+	border-color: #ececec;
+}
+
+.card_info {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 24rpx 32rpx;
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #e9e9e9;
+}
+
+.card_name {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	lines: 1;
+	margin-left: 24rpx;
+	font-size: 16px;
+}
+
+.card_desc {
+	padding: 8rpx 0 8rpx 42rpx;
+	color: #999;
+}
+
+
+.custom {
+	padding:0 0 12px 0;
+}
+
+.order-item {
+	width: 400rpx;
+	padding: 15rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
+	background-color: #fff;
+	border-radius: 15rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+}
+
+.order-item-title {
+	font-size: 16px;
+	font-family: PingFang SC;
+	font-weight: bold;
+	color: #111111;
+}
+
+.order-item-text {
+	font-size: 16px;
+	font-family: PingFang SC;
+	color: #9a9a9c;
+	padding:6rpx 0;
+}
+
+.prescribe-item {
+	width: 400rpx;
+	padding: 15rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
+	background-color: #fff;
+	border-radius: 15rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+}
+
+.prescribe-item-title {
+	font-size: 16px;
+	font-family: PingFang SC;
+	font-weight: bold;
+	color: #111111;
+}
+
+.prescribe-item-text {
+	margin-top: 10rpx;
+	font-size: 14px;
+	font-family: PingFang SC;
+	color: #9a9a9c;
+}
+
+.prescribe-item-btns {
+	padding: 15rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+}
+
+.prescribe-item-btn {
+	border-radius: 30rpx;
+	padding: 15rpx 30rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	font-family: PingFang SC;
+	color: #333333;
+}
+
+
+.report-item {
+	width: 400rpx;
+	padding: 15rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
+	background-color: #fff;
+	border-radius: 15rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+}
+
+.report-item-title {
+	font-size: 17px;
+	font-family: PingFang SC;
+	font-weight: bold;
+	color: #111111;
+}
+
+.report-item-text {
+	margin-top: 10rpx;
+	font-size: 15px;
+	font-family: PingFang SC;
+	color: #9a9a9c;
+}
+
+.report-item-btns {
+	padding: 15rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+}
+
+.report-item-btn {
+	border-radius: 30rpx;
+	padding: 15rpx 30rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	font-family: PingFang SC;
+	color: #333333;
+}
+
+
+.package-item {
+	width: 450rpx;
+	padding: 15rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
+	background-color: #fff;
+	border-radius: 15rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+}
+
+.package-item-title {
+	font-size: 17px;
+	font-family: PingFang SC;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 10rpx;
+}
+
+.package-item-stitle {
+	font-size: 15px;
+	font-family: PingFang SC;
+	font-weight: 500;
+	color: #666;
+	margin-bottom: 10rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+
+.package-item-stitle-text {
+    font-size: 15px;
+    font-family: PingFang SC;
+    font-weight: 500;
+    color: #666;
+}
+
+.package-item-psub {
+	color: #e41f19;
+	font-size: 15px;
+	font-style: normal;
+}
+
+.package-item-price {
+	font-size: 18px;
+	font-style: normal;
+	font-weight: bold;
+}
+
+.package-item-stitle1 {
+	font-size: 16px;
+}
+
+.package-item-cover {
+	width: 100%;
+	height: 400rpx;
+	border-radius: 16rpx;
+	box-sizing: border-box;
+}
+
+.package-item-image {
+	width: 100%;
+	height: 100%;
+}
+
+.package-item-btns {
+	padding: 15rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+}
+
+.package-item-btn {
+	border-radius: 30rpx;
+	padding: 15rpx 30rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	font-family: PingFang SC;
+	color: #333333;
+}
+
+
+.content {
+	padding: 10px;
+	background-color: #f0f0f0;
+}
+
+.content-in {
+	border-radius: 0px 10px 10px 10px;
+}
+
+.content-ou {
+	border-radius: 10px 0px 10px 10px;
+}
+
+.content-text {
+    font-size: 16px;
+    color: #333;
+}
+
+
+.list-item {
+	width: 450rpx;
+	padding: 0rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ececec;
+	box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.05);
+	background-color: #fff;
+	border-radius: 16rpx;
+	margin-bottom: 20rpx;
+	box-sizing: border-box;
+	font-family: PingFang SC, PingFang SC;
+	font-weight: 400;
+	font-size: 16px;
+	color: #333333;
+	line-height: 36rpx;
+}
+
+.list-cover-image {
+	width: 150rpx;
+	height: 120rpx;
+	margin-left: 16rpx;
+	background-color: #F5F6F6;
+	border-radius: 10rpx 10rpx 10rpx 10rpx;
+}
+
+
+.list-con {
+	width: 100%;
+	padding: 24rpx;
+	box-sizing: border-box;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: space-between;
+}
+
+.list-cover {
+	width: 100%;
+	margin-top: 20rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: flex-start;
+	justify-content: flex-end;
+}
+
+
+.list-itemtxt {
+	flex: 1;
+	overflow: hidden;
+	word-break: break-all;
+	display: flex;
+	flex-direction: column;
+}
+
+.list-title {
+	word-break: break-all;
+}
+
+.list-desc {
+	margin-top: 0rpx;
+	font-size: 16px;
+	color: #333;
+	line-height: 22px;
+}
+
+.list-time {
+	font-size: 14px;
+	color: #999999;
+}
+
+.list-footer {
+	padding: 20rpx 24rpx;
+	box-sizing: border-box;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	position: relative;
+}
+
+.list-footer-view {
+    display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: flex-start;
+}
+
+.list-footer-image {
+	height: 50rpx;
+	width: 50rpx;
+	border-radius: 50%;
+	margin: 0 10rpx 0 0;
+}
+
+
+/* WeChat Style Link Card (Package) */
+.wechat-link-card {
+	width: 500rpx;
+	background-color: #FFFFFF;
+	border-radius: 10rpx;
+	padding: 24rpx;
+	display: flex;
+	flex-direction: row;
+	align-items: flex-start;
+	justify-content: space-between;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ededed;
+}
+
+.wechat-link-content {
+	flex: 1;
+	margin-right: 20rpx;
+	min-height: 100rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.wechat-link-title {
+	font-size: 19px;
+	color: #333333;
+	lines: 2;
+	text-overflow: ellipsis;
+	line-height: 44rpx;
+	font-weight: 400;
+}
+
+.wechat-link-desc {
+	font-size: 16px;
+	color: #666666;
+	lines: 1;
+	text-overflow: ellipsis;
+	margin-top: 8rpx;
+}
+
+.course-title{
+	text-overflow: ellipsis;
+	lines: 3;
+}
+
+.course-item {
+	justify-content: center;
+}
+
+.course-desc {
+	text-overflow: inherit;
+}
+
+.wechat-link-image {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 8rpx;
+	background-color: #f5f5f5;
+}
+
+.wechat-link-placeholder {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 8rpx;
+	background-color: #f5f5f5;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.wechat-link-icon {
+	font-size: 40rpx;
+}
+
+
+/* WeChat Style Coupon Card (CouponPackage) */
+.wechat-coupon-card {
+	width: 500rpx;
+	background-color: #FFFFFF;
+	border-radius: 12rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ededed;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+}
+
+.wechat-coupon-body {
+	display: flex;
+	flex-direction: row;
+	padding: 30rpx 24rpx;
+	align-items: center;
+}
+
+.wechat-coupon-left {
+	display: flex;
+	flex-direction: row;
+	align-items: baseline;
+}
+
+.wechat-coupon-symbol {
+	font-size: 14px;
+	color: #fa5151;
+	font-weight: 500;
+	margin-right: 4rpx;
+}
+
+.wechat-coupon-price {
+	font-size: 56rpx;
+	color: #fa5151;
+	font-weight: bold;
+	line-height: 56rpx;
+}
+
+.wechat-coupon-right {
+	flex: 1;
+	margin-left: 24rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+}
+
+.wechat-coupon-title {
+	font-size: 17px;
+	color: #333333;
+	font-weight: bold;
+	lines: 1;
+	text-overflow: ellipsis;
+	margin-bottom: 8rpx;
+}
+
+.wechat-coupon-cond {
+	font-size: 14px;
+	color: #999999;
+	lines: 1;
+	text-overflow: ellipsis;
+}
+
+.wechat-coupon-footer {
+	background-color: #fafafa;
+	padding: 16rpx 24rpx;
+	border-top-width: 1rpx;
+	border-top-style: dashed;
+	border-top-color: #e0e0e0;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.wechat-coupon-date {
+	font-size: 14px;
+	color: #999999;
+}
+
+.wechat-coupon-action {
+	font-size: 14px;
+	color: #999999;
+}
+
+
+/* WeChat Style Bill Card (Order) */
+.wechat-bill-card {
+	width: 500rpx;
+	background-color: #FFFFFF;
+	border-radius: 12rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ededed;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+}
+
+.wechat-bill-header {
+	padding: 24rpx;
+	border-bottom-width: 1rpx;
+	border-bottom-style: solid;
+	border-bottom-color: #f0f0f0;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.wechat-bill-title {
+	font-size: 17px;
+	font-weight: bold;
+	color: #333333;
+}
+
+.wechat-bill-status {
+	font-size: 14px;
+	color: #fa5151; /* Or green #07c160 depending on state */
+}
+
+.wechat-bill-body {
+	padding: 24rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.wechat-bill-row {
+	display: flex;
+	flex-direction: row;
+	margin-bottom: 12rpx;
+	align-items: flex-start;
+}
+
+.wechat-bill-label {
+	font-size: 14px;
+	color: #999999;
+	width: 140rpx;
+	flex-shrink: 0;
+}
+
+.wechat-bill-value {
+	font-size: 14px;
+	color: #333333;
+	flex: 1;
+}
+
+.wechat-bill-value-multi {
+	lines: 2;
+	text-overflow: ellipsis;
+}
+
+.wechat-bill-footer {
+	padding: 20rpx 24rpx;
+	border-top-width: 1rpx;
+	border-top-style: solid;
+	border-top-color: #f0f0f0;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.wechat-bill-action {
+	font-size: 14px;
+	color: #333333;
+}
+
+.wechat-bill-arrow {
+	font-size: 14px;
+	color: #c7c7cc;
+}
+
+
+/* WeChat Style Service Card (Prescribe, Report, Follow) */
+.wechat-service-card {
+	width: 500rpx;
+	background-color: #FFFFFF;
+	border-radius: 12rpx;
+	border-width: 1rpx;
+	border-style: solid;
+	border-color: #ededed;
+	padding: 30rpx 24rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+}
+
+.wechat-service-header {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-bottom: 16rpx;
+}
+
+.wechat-service-icon {
+	font-size: 60rpx;
+	margin-bottom: 10rpx;
+}
+
+.wechat-service-title {
+	font-size: 17px;
+	font-weight: bold;
+	color: #333333;
+}
+
+.wechat-service-body {
+	margin-bottom: 24rpx;
+}
+
+.wechat-service-desc {
+	font-size: 14px;
+	color: #666666;
+	text-align: center;
+	lines: 2;
+	text-overflow: ellipsis;
+}
+
+.wechat-service-footer {
+	width: 100%;
+}
+
+.wechat-service-action {
+	width: 100%;
+	height: 72rpx;
+	line-height: 72rpx;
+	text-align: center;
+	background-color: #f7f7f7;
+	border-radius: 8rpx;
+	font-size: 28rpx;
+	color: #576b95; /* WeChat Link Blue */
+	font-weight: 500;
+}
+
+
+/* System Bubble */
+.system-bubble {
+	padding: 10rpx 24rpx;
+	background-color: rgba(0, 0, 0, 0.1); /* Semi-transparent gray */
+	border-radius: 8rpx;
+	align-self: center; /* Center in chat flow */
+	margin: 10rpx 0;
+}
+
+.system-bubble-text {
+	font-size: 17px;
+	color: #FFFFFF; 
+	color: #666666; 
+}
+
+.luckyBagBox {
+	width: 384rpx;
+	height: 160rpx;
+	border-radius: 8rpx;
+	padding: 24rpx 24rpx 20rpx 24rpx;
+	background-repeat: no-repeat;
+	background-size: 100% 100%;
+	.luckyBagBox-line {
+		width: 100%;
+		height: 1rpx;
+		background: rgba(255, 255, 255,0.2);
 	}
-</style>
+}
+.image_right {
+	background-image: url('/pages_im/static/images/luckyBag/lucky_bag_new_bg_right.png');
+}
+.image_left {
+	background-image: url('/pages_im/static/images/luckyBag/lucky_bag_new_bg_left.png');
+}
+</style>

+ 10 - 1
pages_im/pages/conversation/chating/components/MessageItem/ErrorMessageRender.vue

@@ -14,4 +14,13 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.bg_container {
+  padding: 18rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  background-color: #ffffff;
+}
+.text_message_container {
+    /* placeholder if needed */
+}
+</style>

+ 1 - 17
pages_im/pages/conversation/chating/components/MessageItem/FaceMessageRender.vue

@@ -66,7 +66,7 @@ export default {
 					}
 				},
 				fail(err) {
-					console.log(err);
+					// console.log(err);
 				}
 			});
 		}
@@ -79,21 +79,5 @@ export default {
 	position: relative;
 	border-radius: 16rpx;
 	overflow: hidden;
-
-	.play_icon {
-		width: 48px;
-		height: 48px;
-		position: absolute;
-		top: 50%;
-		left: 50%;
-		transform: translate(-50%, -50%);
-	}
-
-	.video_duration {
-		position: absolute;
-		bottom: 12rpx;
-		right: 24rpx;
-		color: #fff;
-	}
 }
 </style>

+ 108 - 53
pages_im/pages/conversation/chating/components/MessageItem/FileMessageRender.vue

@@ -5,12 +5,12 @@
       <text class="file_size">{{ getSizeStr }}</text>
     </view>
     <view style="position: relative;">
-      <image :src="getIcon" alt="" srcset="" />
+      <image :src="getIcon" class="file_icon" alt="" srcset="" />
       <image v-if="!localPath" class="mask"
-        src="../../../../../static/images/file_message/file_mask.png" alt="" srcset="" />
-      <image class="mask download" v-if="currentFileIsDownloading" src="../../../../../static/images/file_message/file_suspend.png" alt="" srcset="" />
+        src="@/pages_im/static/images/file_message/file_mask.png" alt="" srcset="" />
+      <image class="mask download" v-if="currentFileIsDownloading" src="@/pages_im/static/images/file_message/file_suspend.png" alt="" srcset="" />
       <image v-if="!currentFileIsDownloading && !localPath" class="download"
-        src="../../../../../static/images/file_message/file_download.png" alt="" srcset="" />
+        src="@/pages_im/static/images/file_message/file_download.png" alt="" srcset="" />
     </view>
   </view>
 </template>
@@ -18,7 +18,7 @@
 <script>
 import { mapGetters } from "vuex";
 import { bytesToSize } from "../../../../../util/imCommon";
-import { getFileIcon, checkFileIsExist } from "../../../../../util/common";
+// import { getFileIcon, checkFileIsExist } from "../../../../../util/common";
 export default {
   name: "",
   props: {
@@ -67,7 +67,6 @@ export default {
         this.$u.toast("无效的链接");
         return;
       }
-
       if (this.currentFileIsDownloading) return;
       this.$store.dispatch("user/addCacheTask", {
         key: this.message.clientMsgID,
@@ -75,70 +74,126 @@ export default {
       });
     },
     openFile() {
-      if(!this.localPath) {
-        this.download()
-        return
-      }
-      
-      uni.openDocument({
-        filePath: this.localPath,
-        fail: () => {
-          uni.showToast({
-            title: '文件打开失败,请前往文件管理器打开',
-            icon: 'none'
-          })
-        },
-      });
+		  if(!this.localPath) {
+			 this.download();
+			 return
+		  }
+		  uni.openDocument({
+			filePath: this.localPath,
+			fail: () => {
+			  uni.showToast({
+				title: '文件打开失败,请前往文件管理器打开',
+				icon: 'none'
+			  })
+			},
+		  });
     },
+	getFileIcon(fileName){
+		  const fileType = getFileType(fileName);
+		  const wordType = ["doc", "docx", "docm", "dot"];
+		  const pdfType = ["pdf"];
+		  const pptType = ["pptx", "pptm", "ppt"];
+		  const excelType = ["xlsx", "xlsm", "xlsb", "xltx"];
+		  const zipType = ["zip", "rar", "tar", "gz"];
+		  if (wordType.includes(fileType)) {
+			  return word;
+		  }
+		  if (pdfType.includes(fileType)) {
+			  return pdf;
+		  }
+		  if (pptType.includes(fileType)) {
+			  return ppt;
+		  }
+		  if (excelType.includes(fileType)) {
+			  return excel;
+		  }
+		  if (zipType.includes(fileType)) {
+			  return zip;
+		  }
+		  return unknown;
+	},
+	checkFileIsExist({ key, path }){
+	  return new Promise((resolve) => {
+	    if (!path) {
+	      resolve("");
+	      return;
+	    }
+	    plus.io.resolveLocalFileSystemURL(
+	      path,
+	      (res) => {
+	        resolve(path);
+	      },
+	      (err) => {
+	        console.log(err);
+	        getStore().dispatch("user/deleteCacheData", key);
+	        resolve("");
+	      },
+	    );
+	  });
+	}
+
   },
 };
 </script>
 
 <style lang="scss" scoped>
 .file_message_container {
-  @include vCenterBox();
+  display: flex;
+  flex-direction: row;
+  align-items: center;
   padding: 24rpx 32rpx;
-  background: #fff;
+  background-color: #fff;
   justify-content: space-between;
   width: 380rpx;
   box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
   border-radius: 12rpx;
-  border: 1px solid #ececec;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #ececec;
+}
 
-  .file_info {
-    @include colBox(true);
-    height: 44px;
-    margin-right: 24rpx;
-    max-width: 280rpx;
+.file_info {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  height: 44px;
+  margin-right: 24rpx;
+  max-width: 280rpx;
+}
 
-    .file_name {
-      @include nomalEllipsis();
-    }
+.file_name {
+  lines: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  font-size: 14px;
+  color: #333;
+}
 
-    .file_size {
-      color: #999;
-      font-size: 24rpx;
-    }
-  }
+.file_size {
+  color: #999;
+  font-size: 12px;
+}
 
-  image {
-    width: 38px;
-    height: 44px;
-  }
+.file_icon {
+  width: 38px;
+  height: 44px;
+}
 
-  .mask {
-    top: 0;
-    left: 0;
-    position: absolute;
-  }
+.mask {
+  top: 0;
+  left: 0;
+  position: absolute;
+  width: 38px;
+  height: 44px;
+}
 
-  .download {
-    width: 44rpx;
-    height: 44rpx;
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-  }
+.download {
+  width: 44rpx;
+  height: 44rpx;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  margin-top: -22rpx;
+  margin-left: -22rpx;
 }
 </style>

+ 32 - 12
pages_im/pages/conversation/chating/components/MessageItem/GroupAnnounceRender.vue

@@ -1,11 +1,11 @@
 <template>
-  <view class="bg_container" style="background: none !important;border: 1px solid #E8EAEF !important; width: 380rpx;" @click="doubleClick">
+  <view class="group_announce_container" @click="doubleClick">
     <view class="announcement_title">
-      <image src="../../../../../static/images/chating_message_notice.png" mode=""></image>
-      <text>群公告</text>
+      <image class="announcement_icon" src="/pages_im/static/images/chating_message_notice.png" mode="aspectFit"></image>
+      <text class="announcement_text">群公告</text>
     </view>
     <view class="announcement_content">
-      {{ getContent }}
+      <text class="announcement_content_text">{{ getContent }}</text>
     </view>
   </view>
 </template>
@@ -41,6 +41,7 @@ export default {
 
       const diff = curTime - lastTime;
       if (diff < 300) {
+        this.$emit('enterSubPage');
         uni.navigateTo({
           url: `/pages/conversation/groupAnnouncement/index`,
         });
@@ -51,22 +52,41 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.group_announce_container {
+  padding: 18rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  background-color: transparent;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #E8EAEF;
+  width: 380rpx;
+}
 .announcement_title {
   display: flex;
+  flex-direction: row;
   align-items: center;
+}
+.announcement_text {
   color: #666;
-
-  image {
+  font-size: 20px;
+}
+.announcement_icon {
     margin-right: 8rpx;
-    width: 16px;
-    height: 16px;
-  }
+    width: 32rpx;
+    height: 32rpx;
 }
 
 .announcement_content {
-  max-width: 70vw;
-  word-break: break-all;
-  overflow: hidden;
   margin-top: 12rpx;
 }
+
+.announcement_content_text {
+  font-size: 18px;
+  color: #333;
+  lines: 10;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  margin-top: 5px;
+}
+
 </style>

+ 20 - 13
pages_im/pages/conversation/chating/components/MessageItem/LocationMessageRender.vue

@@ -40,6 +40,7 @@ export default {
   },
   methods: {
     toDetails() {
+      this.$emit('enterSubPage');
       uni.navigateTo({
         url: `/pages/conversation/previewLocation/index?latng=${this.locationDetails.latng}`,
       });
@@ -52,22 +53,28 @@ export default {
 .location_message_container {
   width: 444rpx;
   border-radius: 12rpx;
-  border: 1px solid #e6e6e6;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #e6e6e6;
   background-color: #fff !important;
+}
 
-  .location_title,
-  .location_subTitle {
-    @include nomalEllipsis();
-    color: #0c1c33;
-  }
+.location_title {
+  lines: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  color: #0c1c33;
+}
 
-  .location_subTitle {
-    font-size: 24rpx;
-    color: #999;
-  }
+.location_subTitle {
+  lines: 1;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  color: #999;
+  font-size: 24rpx;
+}
 
-  .desc_container {
-    padding: 24rpx;
-  }
+.desc_container {
+  padding: 24rpx;
 }
 </style>

+ 80 - 109
pages_im/pages/conversation/chating/components/MessageItem/MassMessageRender.vue

@@ -75,127 +75,98 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.audio_message_container {
-  @include vCenterBox();
-
-  .audio_duration {
-    font-size: 28rpx;
-  }
-
-  .cricleplay {
-    display: flex;
-    align-items: center;
-    margin-right: 12rpx;
-
-    .small {
-      width: 5px;
-      height: 5px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-    }
-
-    .middle {
-      width: 10px;
-      height: 10px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-      margin-left: -5px;
-      animation: show2 2s ease-in-out infinite;
-      opacity: 1;
-    }
+.bg_container {
+  padding: 18rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  background-color: #ffffff;
+}
 
-    @keyframes show2 {
-      0% {
-        opacity: 0;
-      }
+.audio_message_container {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
 
-      30% {
-        opacity: 1;
-      }
+.audio_duration {
+  font-size: 28rpx;
+}
 
-      100% {
-        opacity: 0;
-      }
-    }
+.cricleplay {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-right: 12rpx;
+}
 
-    .large {
-      width: 20px;
-      height: 20px;
-      border-style: solid;
-      border-color: #3870e4;
-      border-top-color: transparent;
-      border-left-color: transparent;
-      border-bottom-color: transparent;
-      border-radius: 50%;
-      box-sizing: border-box;
-      vertical-align: middle;
-      display: inline-block;
-      margin-left: -15px;
-      animation: show3 2s ease-in-out infinite;
-      opacity: 1;
-    }
+.cricleplay_small {
+  width: 5px;
+  height: 5px;
+  border-width: 1px; /* Default border width if needed, or specific border widths below */
+  border-style: solid;
+  border-color: #3870e4;
+  border-top-color: transparent;
+  border-left-color: transparent;
+  border-bottom-color: transparent;
+  border-radius: 50%;
+}
 
-    @keyframes show3 {
-      0% {
-        opacity: 0;
-      }
+.cricleplay_middle {
+  width: 10px;
+  height: 10px;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #3870e4;
+  border-top-color: transparent;
+  border-left-color: transparent;
+  border-bottom-color: transparent;
+  border-radius: 50%;
+  margin-left: -5px;
+  opacity: 1;
+}
 
-      60% {
-        opacity: 1;
-      }
+.cricleplay_large {
+  width: 20px;
+  height: 20px;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #3870e4;
+  border-top-color: transparent;
+  border-left-color: transparent;
+  border-bottom-color: transparent;
+  border-radius: 50%;
+  margin-left: -15px;
+  opacity: 1;
+}
 
-      100% {
-        opacity: 0;
-      }
-    }
+.stopanimate {
+  opacity: 0.5;
+}
 
-    .stopanimate {
-      -moz-animation-name: none;
-      -webkit-animation-name: none;
-      -ms-animation-name: none;
-      animation-name: none;
-    }
-  }
+.audio_message_container_self {
+  flex-direction: row-reverse;
+}
 
-  &_self {
+/* Helper classes for self alignment if needed, or just rely on flex-direction: row-reverse */
+.cricleplay_self {
+    margin-right: 0;
+    margin-left: 12rpx;
     flex-direction: row-reverse;
+}
 
-    .cricleplay {
-      margin-right: 0;
-      margin-left: 12rpx;
-      flex-direction: row-reverse;
-
-      .small,
-      .middle,
-      .large {
-        border-left-color: #3870e4;
-        border-right-color: transparent;
-      }
+.cricleplay_small_self,
+.cricleplay_middle_self,
+.cricleplay_large_self {
+    border-left-color: #3870e4;
+    border-right-color: transparent;
+}
 
-      .middle {
-        margin-left: 0;
-        margin-right: -5px;
-      }
+.cricleplay_middle_self {
+    margin-left: 0;
+    margin-right: -5px;
+}
 
-      .large {
-        margin-left: 0;
-        margin-right: -15px;
-      }
-    }
-  }
+.cricleplay_large_self {
+    margin-left: 0;
+    margin-right: -15px;
 }
 </style>

+ 97 - 27
pages_im/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue

@@ -1,22 +1,24 @@
 <template>
 	<view class="media_message_container" @click="clickMediaItem">
-		<!-- <view :style="{height:wrapperHeight}" class="media_message_container"> -->
-		<u--image :showLoading="true" width="150" :height="maxHeight" mode="widthFix" :src="getImgUrl" @click="clickMediaItem">
-			<template v-slot:loading>
-				<u-loading-icon color="red"></u-loading-icon>
-			</template>
-		</u--image>
-		<image v-if="isVideo" class="play_icon" src="../../../../../static/images/chating_message_video_play.png" alt="" srcset="" />
+		<image
+			class="image_content"
+			:src="getImgUrl"
+			:style="{ width: '120px', height: maxHeight + 'px' }"
+			mode="scaleToFill"
+		></image>
+		<view v-if="isVideo" class="play_icon_wrapper">
+			<image class="play_icon" src="@/pages_im/static/images/chating_message_video_play.png" alt="" srcset="" />
+		</view>
 		<text v-if="isVideo" class="video_duration">{{ getDuration }}</text>
 	</view>
 </template>
 
 <script>
 import { mapGetters } from 'vuex';
-import { checkFileIsExist } from '../../../../../util/common';
+// import { checkFileIsExist } from '../../../../../util/common';
 import { secFormat } from '../../../../../util/imCommon';
 import { myPreview } from '../../../../../util/preview';
-import { MessageType } from 'openim-uniapp-polyfill';
+import { MessageType } from '@/pages_im/constant/imConstants';
 export default {
 	name: '',
 	props: {
@@ -34,6 +36,7 @@ export default {
 			if (this.isVideo) {
 				return this.message.videoElem.snapshotUrl;
 			}
+			
 			return this.message.pictureElem.snapshotPicture?.url ?? this.message.pictureElem.sourcePath;
 		},
 		getDuration() {
@@ -46,26 +49,78 @@ export default {
 			const imageHeight = this.isVideo ? this.message.videoElem.snapshotHeight : this.message.pictureElem.sourcePicture.height;
 			const imageWidth = this.isVideo ? this.message.videoElem.snapshotWidth : this.message.pictureElem.sourcePicture.width;
 			const aspectRatio = imageHeight / imageWidth;
-			return 120 * aspectRatio;
+			// 200rpx = 100px
+			return 100 * aspectRatio;
 		}
 	},
 	methods: {
+		/**
+		 * 触摸开始,记录时间
+		 */
+		handleTouchStart() {
+			this.touchStartTime = Date.now();
+			this.isLongPress = false;
+		},
+		/**
+		 * 长按事件
+		 */
+		handleLongPress() {
+			this.isLongPress = true;
+			this.$emit('longpress', this.message);
+		},
+		/**
+		 * 点击媒体项,拦截长按误触
+		 */
 		async clickMediaItem() {
+			// 通过时间差和标志位双重拦截
+			const duration = Date.now() - this.touchStartTime;
+			if (this.isLongPress || duration > 350) return;
+			
+			// console.log('clickMediaItem triggered', this.isVideo);
+			// uni.showToast({ title: 'Clicked', icon: 'none' }); 
 			if (this.isVideo) {
 				const path = this.storeCacheMap[this.message.clientMsgID]?.path;
-				const localPath = await checkFileIsExist({
+				const localPath = await this.checkFileIsExist({
 					key: this.message.clientMsgID,
 					path
 				});
 				const previewVideoUrl = localPath || this.message.videoElem.videoUrl;
+				this.$emit('enterSubPage');
 				uni.navigateTo({
 					url: `/pages_im/pages/conversation/previewVideo/index?clientMsgID=${this.message.clientMsgID}&previewVideoUrl=${previewVideoUrl}&snapshotUrl=${this.message.videoElem.snapshotUrl}`
 				});
 			} else {
+				this.$emit('enterSubPage');
 				const list = this.$store.getters.storePreviewImageList;
-				const idx = list.findIndex((item) => item === this.message.pictureElem.sourcePicture.url);
-				myPreview(idx, list);
+				const currentUrl = this.message.pictureElem.sourcePicture.url;
+				const idx = list.findIndex((item) => item === currentUrl);
+				
+				// Fallback if not found in list
+				if (idx === -1) {
+					myPreview(0, [currentUrl]);
+				} else {
+					myPreview(idx, list);
+				}
 			}
+		},
+		checkFileIsExist({ key, path }){
+		  return new Promise((resolve) => {
+		    if (!path) {
+		      resolve("");
+		      return;
+		    }
+		    plus.io.resolveLocalFileSystemURL(
+		      path,
+		      (res) => {
+		        resolve(path);
+		      },
+		      (err) => {
+		        // console.log(err);
+		        getStore().dispatch("user/deleteCacheData", key);
+		        resolve("");
+		      },
+		    );
+		  });
 		}
 	}
 };
@@ -76,21 +131,36 @@ export default {
 	position: relative;
 	border-radius: 16rpx;
 	overflow: hidden;
+	width: 120px;
+	background-color: #E0E0E0;
+}
 
-	.play_icon {
-		width: 48px;
-		height: 48px;
-		position: absolute;
-		top: 50%;
-		left: 50%;
-		transform: translate(-50%, -50%);
-	}
+.image_content {
+	width: 120px;
+	border-radius: 12rpx;
+}
 
-	.video_duration {
-		position: absolute;
-		bottom: 12rpx;
-		right: 24rpx;
-		color: #fff;
-	}
+.play_icon_wrapper {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	justify-content: center;
+	align-items: center;
+	z-index: 10;
+}
+
+.play_icon {
+	width: 30px;
+	height: 30px;
+}
+
+.video_duration {
+	position: absolute;
+	bottom: 12rpx;
+	right: 24rpx;
+	color: #fff;
+	font-size: 16px;
 }
 </style>

+ 141 - 101
pages_im/pages/conversation/chating/components/MessageItem/MeetingInviteMessageRender.vue

@@ -1,19 +1,26 @@
 <template>
-  <view class="meeting_wrap" @click="tryJoinMeeting">
-    <view class="title">
-      <image src="../../../../../static/images/meeting_icon.png" />
-      <view class="subject">{{ data.subject }}</view>
-    </view>
-    <ul>
-      <li>开始时间:{{ startTimeStr }}</li>
-      <li>会议时长:{{ durationStr }}</li>
-      <li>会议号:{{ data.id }}</li>
-    </ul>
-    <view class="join_tip">
-      <text>进入会议</text>
-      <image style="width: 24px; height: 24px; margin-left: 4px" src="../../../../../static/images/meeting_join_arrow.png" />
-    </view>
-  </view>
+	<view class="meeting_wrap" @click="tryJoinMeeting">
+		<view class="meeting_corner_mark"></view>
+		<view class="title">
+			<image class="meeting_icon" src="../../../../../static/images/meeting_icon.png" mode="aspectFit"></image>
+			<text class="subject">{{ data.subject }}</text>
+		</view>
+		<view class="meeting_info_list">
+			<view class="meeting_info_item">
+				<text class="meeting_info_text">开始时间:{{ startTimeStr }}</text>
+			</view>
+			<view class="meeting_info_item">
+				<text class="meeting_info_text">会议时长:{{ durationStr }}</text>
+			</view>
+			<view class="meeting_info_item">
+				<text class="meeting_info_text">会议号:{{ data.id }}</text>
+			</view>
+		</view>
+		<view class="join_tip">
+			<text class="join_tip_text">进入会议</text>
+			<image class="join_arrow" src="../../../../../static/images/meeting_join_arrow.png" mode="aspectFit"></image>
+		</view>
+	</view>
 </template>
 
 <script>
@@ -24,106 +31,139 @@ import config from "../../../../../common/config";
 import { joinMeeting } from '../../../../../api/meeting'
 
 const secondsToTime = {
-  1800: "30分钟",
-  3600: "1小时",
-  5400: "1.5小时",
-  7200: "2小时",
+	1800: "30分钟",
+	3600: "1小时",
+	5400: "1.5小时",
+	7200: "2小时",
 };
 
 export default {
-  name: "MeetingInviteMessageRender",
-  components: {},
-  props: {
-    data: Object,
-  },
-  data() {
-    return {};
-  },
-  computed: {
-    ...mapGetters(["storeSelfInfo"]),
-    startTimeStr() {
-      return dayjs(this.data.start * 1000).format("M月D日 HH:mm");
-    },
-    durationStr() {
-      return secondsToTime[this.data.duration];
-    },
-  },
-  methods: {
-    tryJoinMeeting() {
-      const params = {
-        meetingID: this.data.id,
-        userID: this.storeSelfInfo.userID,
-        password: "",
-      };
-      joinMeeting(params)
-        .then((data) => {
-          const meetingData = {
-            liveURL: data.liveKit?.url,
-            token: data.liveKit?.token,
-            roomID: this.data.roomID,
-            imToken: uni.getStorageSync("IMToken"),
-            baseURL: config.getApiUrl()
-          };
-          meetingModule.startMeeting(meetingData);
-        })
-        .catch(meetingErrorHandle);
-    },
-  },
+	name: "MeetingInviteMessageRender",
+	components: {},
+	props: {
+		data: Object,
+		isSender: {
+			type: Boolean,
+			default: false,
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		...mapGetters(["storeSelfInfo"]),
+		startTimeStr() {
+			return dayjs(this.data.start * 1000).format("M月D日 HH:mm");
+		},
+		durationStr() {
+			return secondsToTime[this.data.duration];
+		},
+	},
+	methods: {
+		tryJoinMeeting() {
+			const params = {
+				meetingID: this.data.id,
+				userID: this.storeSelfInfo.userID,
+				password: "",
+			};
+			joinMeeting(params)
+				.then((data) => {
+					const meetingData = {
+						liveURL: data.liveKit?.url,
+						token: data.liveKit?.token,
+						roomID: this.data.roomID,
+						imToken: uni.getStorageSync("IMToken"),
+						baseURL: config.getApiUrl()
+					};
+					meetingModule.startMeeting(meetingData);
+				})
+				.catch(meetingErrorHandle);
+		},
+	},
 };
 </script>
 
 <style lang="scss" scoped>
 .meeting_wrap {
-  padding: 16rpx 26rpx;
-  border-radius: 12rpx;
-  background-color: #fff;
-  position: relative;
-  border-radius: 12rpx;
-  border: 1px solid #ececec;
-  overflow: hidden;
+	padding: 16rpx 26rpx;
+	background-color: #fff;
+	position: relative;
+	border-radius: 12rpx;
+	border-width: 1px;
+	border-style: solid;
+	border-color: #ececec;
+	overflow: hidden;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: column;
+	/* #endif */
+}
 
-  .title {
-    display: flex;
-    align-items: center;
+.meeting_corner_mark {
+	position: absolute;
+	left: 4px;
+	top: 4px;
+	width: 0;
+	height: 0;
+	border-width: 5px;
+	border-style: solid;
+	border-top-color: #1e74de;
+	border-left-color: #1e74de;
+	border-right-color: transparent;
+	border-bottom-color: transparent;
+}
 
-    image {
-      min-width: 20px;
-      width: 20px;
-      height: 20px;
-      margin-right: 4px
-    }
+.title {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
 
-    .subject {
-      @include nomalEllipsis();
-    }
-  }
+.meeting_icon {
+	width: 20px;
+	height: 20px;
+	margin-right: 4px;
+}
 
-  ul {
-    margin-top: 16rpx;
-    padding-left: 36rpx;
-    color: #131f41;
+.subject {
+	font-size: 16px;
+	color: #333;
+	lines: 1;
+	text-overflow: ellipsis;
+	overflow: hidden;
+	flex: 1;
+}
 
-    li {
-      margin-bottom: 12rpx;
-    }
-  }
+.meeting_info_list {
+	margin-top: 16rpx;
+	padding-left: 36rpx;
+}
 
-  .join_tip {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: #1e74de;
-    margin-top: 6px;
-  }
+.meeting_info_item {
+	margin-bottom: 12rpx;
+}
+
+.meeting_info_text {
+	color: #131f41;
+	font-size: 14px;
+}
+
+.join_tip {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	margin-top: 6px;
+}
+
+.join_tip_text {
+	color: #1e74de;
+	font-size: 14px;
+}
 
-  &:before {
-    content: "";
-    position: absolute;
-    left: 4px;
-    top: 4px;
-    border-width: 5px;
-    border-style: solid;
-    border-color: #1e74de transparent transparent #1e74de;
-  }
+.join_arrow {
+	width: 24px;
+	height: 24px;
+	margin-left: 4px;
 }
 </style>

+ 19 - 10
pages_im/pages/conversation/chating/components/MessageItem/MergeMessageRender.vue

@@ -1,15 +1,14 @@
 <template>
   <view @click="toPreview" class="merge_message_container">
     <view class="merge_title">
-      <text>{{ title }}</text>
+      <text class="merge_title_text">{{ title }}</text>
     </view>
     <view class="merge_desc">
       <text
         v-for="(item, idx) in message.mergeElem.abstractList"
         v-if="idx < 4"
         :key="idx"
-        class="merge_item"
-      >
+        class="merge_item">
         {{ item }}
       </text>
     </view>
@@ -18,7 +17,7 @@
 
 <script>
 export default {
-  name: "CardMessageRender",
+  name: "MergeMessageRender",
   components: {},
   props: {
     message: Object,
@@ -37,6 +36,7 @@ export default {
   },
   methods: {
     toPreview() {
+	  console.log("qxj toPreview");
       uni.$u.route("/pages_im/pages/conversation/previewMerge/index", {
         mergeData: encodeURIComponent(JSON.stringify(this.message.mergeElem)),
       });
@@ -51,26 +51,35 @@ export default {
   width: 444rpx;
   box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
   border-radius: 12rpx;
-  border: 1px solid #ececec;
-  background: #fff;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #ececec;
+  background-color: #fff;
 
   .merge_title {
     padding: 12rpx 24rpx;
-    border-bottom: 1px solid #ececec;
+    border-bottom-width: 1px;
+    border-bottom-style: solid;
+    border-bottom-color: #ececec;
 
     text {
-      @include ellipsisWithLine(1);
+      lines: 1;
+      text-overflow: ellipsis;
+      overflow: hidden;
     }
   }
 
   .merge_desc {
-    @include colBox(false);
+    display: flex;
+    flex-direction: column;
     padding: 12rpx 24rpx;
     font-size: 24rpx;
     color: #666;
 
     .merge_item {
-      @include ellipsisWithLine(3);
+      lines: 3;
+      text-overflow: ellipsis;
+      overflow: hidden;
       padding: 4rpx 0;
     }
   }

+ 119 - 38
pages_im/pages/conversation/chating/components/MessageItem/MessageMenu.vue

@@ -1,8 +1,8 @@
 <template>
-	<view :style="{ left: getLeft, right: getRight,'grid-template-columns': `repeat(${menuList.length}, 1fr`  }" class="message_menu_container"  :class="{ message_menu_container_bottom: is_bottom }">
+	<view :style="popupStyle" class="message_menu_container"  :class="{ message_menu_container_bottom: is_bottom }">
 		<view class="message_menu_item" v-for="item in menuList"   @click="menuClick(item)" :key="item.idx">
-			<image :src="item.icon"  />
-			<text>{{ item.title }}</text>
+			<image :src="item.icon" class="message_menu_icon" />
+			<text class="message_menu_text">{{ item.title }}</text>
 		</view>
 	</view>
 </template>
@@ -12,7 +12,6 @@ import { mapGetters, mapActions } from 'vuex';
 import { MessageMenuTypes, ContactChooseTypes, PageEvents } from '../../../../../constant';
 import IMSDK, { GroupMemberRole, MessageType, SessionType } from 'openim-uniapp-polyfill';
 import { parseAt } from '../../../../../util/imCommon';
-import { isDoctorAction } from '../../../../../util/common';
 import copy from '../../../../../static/images/chating_message_copy.png';
 import forward from '../../../../../static/images/chating_message_forward.png';
 import reply from '../../../../../static/images/chating_message_reply.png';
@@ -25,15 +24,23 @@ const canCopyTypes = [MessageType.TextMessage, MessageType.AtTextMessage, Messag
 export default {
 	components: {},
 	props: {
-		message: {
-			type: Object
+		message: Object,
+		isSender: {
+			type: Boolean,
+			default: false
+		},
+		paterWidth: {
+			type: [Number, String],
+			default: 0
 		},
 		is_bottom: {
 			type: Boolean,
-			default: true
+			default: false
 		},
-		isSender: Boolean,
-		paterWidth: Number
+		rect: {
+			type: Object,
+			default: () => ({})
+		}
 	},
 	data() {
 		return {
@@ -58,6 +65,61 @@ export default {
 			}
 			return 'auto';
 		},
+		popupStyle() {
+			// 如果有 rect 参数,说明是全局定位
+			if (this.rect && this.rect.top !== undefined) {
+				const style = {};
+				// 假设屏幕高度大概是 750px (rpx) 或者根据实际像素
+				// nvue 中单位默认是 px
+				// 简单的判断:如果 top < 200,向下弹;否则向上弹
+				const isTop = this.rect.top < 200;
+				
+				if (isTop) {
+					style.top = (this.rect.top + this.rect.height + 10) + 'px';
+				} else {
+					// 向上弹,需要知道 menu 高度。这里无法预知,只能 bottom 定位?
+					// 但是 bottom 是相对于屏幕底部的。
+					// 我们可以用 top: rect.top - menuHeight
+					// 还是 bottom 比较稳妥
+					// style.bottom = (windowHeight - this.rect.top + 10) + 'px';
+					// nvue 没有 windowHeight 直接可用,需要 uni.getSystemInfoSync
+					// 为了简单,我们尽量用 top
+					
+					// 临时方案:始终向下弹?不行,底部消息会被遮挡。
+					// 我们还是用 fixed bottom 吧,但是需要知道屏幕高度。
+					const sys = uni.getSystemInfoSync();
+					style.bottom = (sys.windowHeight - this.rect.top + 10) + 'px';
+				}
+
+				// 水平定位
+				// 居中于消息气泡,或者左对齐
+				// style.left = this.rect.left + 'px';
+				// 为了不超出屏幕,可能需要 clamp
+				let left = this.rect.left;
+				// 简单的防止右溢出
+				const sys = uni.getSystemInfoSync();
+				if (left + 200 > sys.windowWidth) { // 假设菜单宽 200
+					left = sys.windowWidth - 220; 
+				}
+				style.left = left + 'px';
+				
+				return style;
+			}
+
+			// 原有的相对定位逻辑(保留以兼容 vue 页面或非全局模式)
+			let style = {
+				left: this.getLeft,
+				right: this.getRight
+			};
+			if (this.is_bottom) {
+				style.top = '100%';
+				style.marginTop = '10px';
+			} else {
+				style.bottom = '100%';
+				style.marginBottom = '12px';
+			}
+			return style;
+		},
 		canRevoke() {
 			const interval = this.message.sendTime < Date.now() - 5 * 60 * 1000;
 			if (interval) {
@@ -73,6 +135,10 @@ export default {
 			return this.message.contentType === MessageType.GroupAnnouncementUpdated;
 		},
 		menuList() {
+			console.log("qxj MessageMenu menuList compute start");
+			console.log("qxj message:", this.message);
+			console.log("qxj contentType:", this.message.contentType);
+			console.log("qxj canCopyTypes:", canCopyTypes);
 		  // 公共基础菜单(非医生也能看到的项)
 		  const baseMenus = [
 			 {
@@ -120,7 +186,7 @@ export default {
 				  visible: !this.isGroupAnnouncement && this.message.contentType==MessageType.VoiceMessage
 				}
 		  ];
-		  let tempMenuArrs=isDoctorAction(this.storeCurrentConversation.userID) ? baseMenus: [...baseMenus, ...menus1];
+		  let tempMenuArrs=this.isDoctorAction(this.storeCurrentConversation.userID) ? baseMenus: [...baseMenus, ...menus1];
 		  let menuList=[];
 		   tempMenuArrs.forEach((item) => {
 		  	  if(item.visible){
@@ -174,6 +240,7 @@ export default {
 					}
 					break;
 				case MessageMenuTypes.Forward:
+					this.$emit('enterSubPage');
 					uni.navigateTo({
 						url: `/pages_im/pages/common/contactChoose/index?type=${ContactChooseTypes.ForWard}&forwardMessage=${encodeURIComponent(JSON.stringify(this.message))}`
 					});
@@ -188,7 +255,8 @@ export default {
 					this.$store.commit('message/SET_QUOTE_MESSAGE', this.message);
 					break;
 				case MessageMenuTypes.Revoke:
-					IMSDK.asyncApi(IMSDK.IMMethods.RevokeMessage, IMSDK.uuid(), {
+					const _IMSDK = getIMSDK();
+					_IMSDK.asyncApi(_IMSDK.IMMethods.RevokeMessage, _IMSDK.uuid(), {
 						conversationID: this.$store.getters.storeCurrentConversation.conversationID,
 						clientMsgID: this.message.clientMsgID
 					})
@@ -302,6 +370,18 @@ export default {
 				return this.message.quoteElem.text;
 			}
 			return this.message.textElem.content;
+		},
+		isDoctorAction(userId){
+			let isDoctorAct=false;
+			if(userId!=undefined  && (userId!="" || userId.length>0)){
+				if(userId.indexOf('D')!==-1){
+					isDoctorAct=true;
+				}
+				if(userId.indexOf('C')!==-1){
+					
+				}
+			}
+			return isDoctorAct;
 		}
 	}
 };
@@ -309,36 +389,37 @@ export default {
 
 <style scoped lang="scss">
 .message_menu_container {
-	display: grid;
-	//grid-template-columns: repeat(5, 1fr);
+	display: flex;
+    flex-direction: row;
 	background-color: #5b5b5b;
 	padding: 8rpx 0;
 	border-radius: 16rpx;
-	position: absolute;
-	top: -12px;
-	transform: translateY(-100%);
-	z-index: 9;
-    width: max-content;  /* 宽度根据内容自适应 */
-	&_bottom {
-		top: unset;
-		bottom: 0;
-		transform: translateY(110%);
-	}
+	position: fixed;
+    /* z-index: 999; */
+}
+.message_menu_container_bottom {
+    /* top: unset; */
+    /* bottom: 0; */
+}
 
-	.message_menu_item {
-		@include colBox(false);
-		align-items: center;
-		font-size: 12px;
-		color: #fff;
-		padding: 16rpx 24rpx;
-		image {
-			width: 17px;
-			height: 19px;
-			margin-bottom: 4px;
-		}
-		text {
-			min-width: max-content;
-		}
-	}
+.message_menu_item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: #fff;
+    padding: 16rpx 24rpx;
+}
+.message_menu_icon {
+    width: 17px;
+    height: 19px;
+    margin-bottom: 4px;
+}
+.message_menu_text {
+    /* min-width: max-content; nvue not support */
+    color: #fff;
+    font-size: 12px;
+    lines: 1;
 }
 </style>

+ 2 - 4
pages_im/pages/conversation/chating/components/MessageItem/MessageReadState.vue

@@ -6,8 +6,7 @@
 </template>
 
 <script>
-import IMSDK from "openim-uniapp-polyfill";
-import { SessionType } from "openim-uniapp-polyfill";
+import { SessionType } from "@/pages_im/constant/imConstants";
 export default {
   name: "",
   components: {},
@@ -56,7 +55,6 @@ export default {
 
 <style lang="scss" scoped>
 .read_state {
-  // font-size: 26rpx;
-  font-size: 0.75rem;
+  font-size: 24rpx;
 }
 </style>

+ 82 - 49
pages_im/pages/conversation/chating/components/MessageItem/QuoteMessageRender.vue

@@ -1,39 +1,36 @@
 <template>
-  <view class="message_quote_wrap">
+ <view class="message_quote_wrap">
     <view class="message_quote_text">
-      <u-parse :content="getSenderNickname" />
+      <rich-text :nodes="nodes" class="quote_content_text"></rich-text>
       <view style="margin-left: 6rpx" class="message_quote_media">
         <image
+          class="message_quote_image"
           v-if="isReplyFile"
-          src="../../../../../static/images/chating_message_file.png"
-          alt="file"
-        />
+          src="@/pages_im/static/images/chating_message_file.png"
+          alt="file"/>
         <image
+          class="message_quote_image"
           v-if="isReplyImage"
           :src="getQuoteMessage.pictureElem.sourcePicture.url"
           alt="img"
-          @click="clickImageItem"
-        />
+          @click="clickImageItem"/>
         <my-avatar
           v-if="isReplyCard"
           :src="getQuoteMessage.cardElem.faceURL"
           :desc="getQuoteMessage.cardElem.nickname"
-          size="26"
-		      @click="clickCardUser"
-        />
+          size="26" @click="clickCardUser"/>
         <image
+          class="message_quote_image"
           v-if="isReplyVideo"
           :src="getQuoteMessage.videoElem.snapshotUrl"
-          alt="video"
-        />
+          alt="video"/>
         <image
           v-if="isReplyVideo"
           class="play_icon"
-          src="../../../../../static/images/chating_message_video_play.png"
+          src="@/pages_im/static/images/chating_message_video_play.png"
           alt=""
           srcset=""
-          @click.stop="clickMediaItem"
-        />
+          @click.stop="clickMediaItem"/>
       </view>
     </view>
   </view>
@@ -41,23 +38,22 @@
 
 <script>
 import { mapGetters } from "vuex";
-import { checkFileIsExist } from "../../../../../util/common";
-import UParse from "../../../../../components/gaoyia-parse/parse.vue";
+// import { checkFileIsExist } from "../../../../../util/common";
 import { parseMessageByType } from "../../../../../util/imCommon";
-import { MessageType } from "openim-uniapp-polyfill";
+import { MessageType } from "@/pages_im/constant/imConstants";
 import MyAvatar from "../../../../../components/MyAvatar/index.vue";
 import { myPreview } from "../../../../../util/preview";
+import Parser from '@/uni_modules/uview-plus/components/u-parse/parser.js';
 const extraTypes = [
-  MessageType.FileMessage,
-  MessageType.LocationMessage,
-  MessageType.PictureMessage,
-  MessageType.VideoMessage,
-  MessageType.CardMessage,
+	  MessageType.FileMessage,
+	  MessageType.LocationMessage,
+	  MessageType.PictureMessage,
+	  MessageType.VideoMessage,
+	  MessageType.CardMessage,
 ];
 export default {
   name: "",
   components: {
-    UParse,
     MyAvatar,
   },
   props: {
@@ -68,7 +64,8 @@ export default {
   },
   computed: {
     ...mapGetters([
-      "storeCacheMap"
+        "storeCacheMap",
+        "storeCurrentUserID"
     ]),
     hasExtra() {
       return extraTypes.includes(this.message.quoteElem.quoteMessage.contentType);
@@ -106,64 +103,101 @@ export default {
       if (this.isRevokedMessage) {
         return "引用内容已被撤回";
       }
-      return parseMessageByType(this.getQuoteMessage);
+      return parseMessageByType(this.getQuoteMessage, this.storeCurrentUserID);
     },
     getSenderNickname() {
       return `${this.getQuoteMessage.senderNickname}: ${this.getMessageContent}`;
     },
+    nodes() {
+      if (!this.getSenderNickname) return [];
+      const parser = new Parser({});
+      return parser.parse(this.getSenderNickname);
+    },
   },
   methods: {
     clickImageItem() {
+      this.$emit('enterSubPage');
       myPreview(0, [this.getQuoteMessage.pictureElem.sourcePicture.url])
     },
     async clickMediaItem() {
       if (this.isReplyVideo) {
         const message = this.getQuoteMessage
         const path = this.storeCacheMap[message.clientMsgID]?.path;
-        const localPath = await checkFileIsExist({
+        const localPath = await this.checkFileIsExist({
           key: message.clientMsgID,
           path
         })
         const previewVideoUrl = localPath || message.videoElem.videoUrl
+        this.$emit('enterSubPage');
         uni.navigateTo({
           url: `/pages_im/pages/conversation/previewVideo/index?clientMsgID=${message.clientMsgID}&previewVideoUrl=${previewVideoUrl}&snapshotUrl=${message.videoElem.snapshotUrl}`,
         });
       }
     },
 	clickCardUser() {
-	  uni.navigateTo({
-	    url: `/pages_im/pages/common/userCard/index?sourceID=${this.getQuoteMessage.cardElem.userID}`,
-	  });
+		  this.$emit('enterSubPage');
+		  uni.navigateTo({
+			url: `/pages_im/pages/common/userCard/index?sourceID=${this.getQuoteMessage.cardElem.userID}`,
+		  });
 	},
+	checkFileIsExist({ key, path }){
+		return new Promise((resolve) => {
+				if (!path) {
+				  resolve("");
+				  return;
+				}
+				plus.io.resolveLocalFileSystemURL(
+				  path,
+				  (res) => {
+					resolve(path);
+				  },
+				  (err) => {
+					console.log(err);
+					getStore().dispatch("user/deleteCacheData", key);
+					resolve("");
+				  },
+				);
+		});
+	}
+
   },
 };
 </script>
 
 <style lang="scss" scoped>
 .message_quote_wrap {
-  display: flex;
-  justify-content: flex-end;
-  margin-top: 8rpx;
-
-  .message_quote_text {
     display: flex;
     flex-direction: row;
-    align-items: center;
-    justify-content: space-between;
-    width: fit-content;
-    padding: 16rpx 16rpx;
-    border-radius: 6rpx;
-    color: #666;
-    font-size: 24rpx;
-    background-color: #f0f0f0;
+    justify-content: flex-end;
+    margin-top: 8rpx;
 
-    ::v-deep uni-view {
-      @include ellipsisWithLine(3);
+    .message_quote_text {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: space-between;
+      /* width: fit-content; nvue not support, flex-direction: row in parent helps */
+      padding: 16rpx 16rpx;
+      border-radius: 6rpx;
+      color: #666;
+      font-size: 24rpx;
+      background-color: #f0f0f0;
+    }
+    
+    .quote_content_text {
+      color: #666;
+      font-size: 24rpx;
+      lines: 3;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      flex: 1;
+      /* max-width: 350rpx; */
     }
-  }
 
   .message_quote_media {
     position: relative;
+    width: 26px;
+    height: 26px;
 
     image {
       width: 26px;
@@ -174,9 +208,8 @@ export default {
       width: 13px !important;
       height: 13px !important;
       position: absolute;
-      top: 50%;
-      left: 50%;
-      transform: translate(-50%, -50%);
+      top: 6.5px;
+      left: 6.5px;
     }
   }
 }

+ 19 - 12
pages_im/pages/conversation/chating/components/MessageItem/RTCMessageRender.vue

@@ -69,23 +69,30 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.bg_container {
+  padding: 18rpx 24rpx;
+  border-radius: 0rpx 12rpx 12rpx 12rpx;
+  background-color: #ffffff;
+}
+
 .rtc_wrap {
 	display: flex;
+	flex-direction: row;
 	align-items: center;
+}
 
-	image {
-		width: 20px;
-		height: 20px;
-		margin-right: 12rpx;
-	}
+.rtc_wrap_self {
+	flex-direction: row-reverse;
+}
 
-	&_self {
-		flex-direction: row-reverse;
+.rtc_icon {
+	width: 20px;
+	height: 20px;
+	margin-right: 12rpx;
+}
 
-		image {
-			margin-right: 0 !important;
-			margin-left: 12rpx;
-		}
-	}
+.rtc_icon_self {
+	margin-right: 0 !important;
+	margin-left: 12rpx;
 }
 </style>

+ 49 - 23
pages_im/pages/conversation/chating/components/MessageItem/TextMessageRender.vue

@@ -1,45 +1,52 @@
 <template>
-  <view class="text_message_container bg_container">
-   <!-- <mp-html
-      @linktap="navigate"
-      :previewImg="false"
-      :showImgMenu="false"
-      :lazyLoad="false"
-      :content="getContent"
-    /> -->
-	{{getContent}}
-    <!-- <u-parse :imgOptions="false" @navigate="navigate" :content="getContent" /> -->
+  <view class="text_message_container bg_container" :class="[isSender ? 'text_message_container_self' : '']">
+    <rich-text :nodes="nodes" class="plain_text"></rich-text>
   </view>
 </template>
 
 <script>
-import { parseBr } from "../../../../../util/common";
 import { parseAt, parseLink } from "../../../../../util/imCommon";
-import { MessageType } from "openim-uniapp-polyfill";
+import { MessageType } from "@/pages_im/constant/imConstants";
+import Parser from "@/uni_modules/uview-plus/components/u-parse/parser.js";
 
 export default {
   name: "TextMessageRender",
-  components: {},
   props: {
     message: Object,
+    isSender: {
+      type: Boolean,
+      default: false,
+    },
   },
   computed: {
     getContent() {
+      let content = "";
       if (this.message.contentType === MessageType.QuoteMessage) {
-        return this.message.quoteElem.text;
-      }
-      if (this.message.contentType === MessageType.AtTextMessage) {
-        return parseAt(this.message.atTextElem);
+        content = this.message.quoteElem.text || "";
+      } else if (this.message.contentType === MessageType.AtTextMessage) {
+        content = parseAt(this.message.atTextElem) || "";
+      } else {
+        content = this.message.textElem?.content || "";
+        content = parseLink(content);
       }
-      return parseLink(parseBr(this.message.textElem?.content));
+      // 统一处理换行符为 <br>,确保 rich-text 能正确换行
+      return content.replace(/\n/g, ' ');
+    },
+    nodes() {
+        try {
+            const content = this.getContent;
+            if (!content) return [];
+            const parser = new Parser({});
+            const nodeList = parser.parse(content);
+            return nodeList;
+        } catch (e) {
+            console.error("RichText parse error:", e);
+            return [];
+        }
     },
-  },
-  data() {
-    return {};
   },
   methods: {
     navigate(link) {
-      // console.log(link);
       if (link.innerText.includes("@")) {
         this.$emit("showInfo", link.href);
       }
@@ -48,4 +55,23 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.plain_text {
+  font-size: 20px;
+  color: #333;
+  line-height: 48rpx;
+  /* 确保文本能正常换行且不被截断 */
+  word-wrap: break-word;
+  word-break: break-all;
+}
+.bg_container {
+    padding: 18rpx 24rpx;
+    border-radius: 0rpx 12rpx 12rpx 12rpx;
+    background-color: #ffffff;
+    max-width: 514rpx;
+}
+.text_message_container_self {
+    border-radius: 12rpx 0 12rpx 12rpx;
+    background-color: #95EC69 !important;
+}
+</style>

+ 343 - 254
pages_im/pages/conversation/chating/components/MessageItem/index.vue

@@ -1,112 +1,91 @@
 <template>
 	<view
-		v-if="!getNoticeContent"
+		v-if="!getNoticeContent && source"
 		@click="clickMessageItem"
 		:id="`auchor${source.clientMsgID}`"
 		class="message_item"
-		:class="{ message_item_self:isSender,message_item_active: isActive }">
-		<view v-if="mutipleCheckVisible" class="check_wrap" :class="{ check_wrap_active: source.checked, check_wrap_disabled: canMutipleCheck }">
+		:class="[isSender ? 'message_item_self' : '', isActive ? 'message_item_active' : '']">
+		<view v-if="mutipleCheckVisible" class="check_wrap" :class="[isSender ? 'check_wrap_self' : '', source.checked ? 'check_wrap_active' : '', canMutipleCheck ? 'check_wrap_disabled' : '']">
 			<u-icon v-show="source.checked" name="checkbox-mark" size="12" color="#fff" />
 		</view>
-
-		<my-avatar @longpress="atUser" @click="showInfo"  :desc="avatarDesc" :src="announcePublisher.faceURL || source.senderFaceUrl" size="44">
-			<image v-if="getRole()==1" class="taoj"  src="/static/svg/doctor.svg"></image>
-		    <image v-if="getRole()==2" class="taoj"  src="/static/svg/guanjia.svg"></image>
-		</my-avatar>
+         <view class="avatar-box">
+			 <my-avatar @longpress="atUser" @click="showInfo"  :desc="avatarDesc" :src="senderFaceUrlComputed" size="44">
+			 	<image v-if="getRole()==1" class="taoj"  src="/static/svg/doctor.svg"></image>
+			     <image v-if="getRole()==2" class="taoj"  src="/static/svg/guanjia.svg"></image>
+			 </my-avatar>
+		 </view>
 		
-		<view class="message_container">
+		<view class="message_container" :class="[isSender ? 'message_container_self' : '']">
 			<view class="message_sender" :style="{ 'flex-direction': !isSender ? 'row-reverse' : 'row' }">
-				<!-- <text>{{ formattedMessageTime }}</text> -->
 				<text style="margin-left: 2rpx; margin-right: 2rpx">{{ '' }}</text>
 				<text v-if="!isSingle">{{ announcePublisher.nickname || source.senderNickname }}</text>
 			</view>
-
-			<view class="message_send_state_box">
+			<view class="message_send_state_box" :class="[isSender ? 'message_send_state_box_self' : '']">
 				<view style="height: 100%; display: flex; justify-items: center; align-items: center">
-					<view class="message_send_state">
-						<u-loading-icon v-if="showSending && !isPreview" />
+					<view class="message_send_state" :class="[isSender ? 'message_send_state_self' : '']">
+						<u-loading-icon v-if="false && showSending && !isPreview" />
 						<image @click="reSendMessage" v-if="isFailedMessage && !isPreview" src="../../../../../static/images/chating_message_failed.png" />
 						<text v-if="readLimitSensitive" class="read_limit_count">{{ `${count}s` }}</text>
 					</view>
 				</view>
 
-				<view @longpress.prevent="showMenu" class="message_content_wrap" :class="{ message_content_wrap_shadow: !!meetingMessageData }">
-					<text-message-render v-if="showTextRender" :message="source" @showInfo="showInfo" />
-					<media-message-render v-else-if="showMediaRender" :message="source" />
+				<view ref="messageContent" @longpress.prevent="showMenu" class="message_content_wrap" :class="[!!meetingMessageData ? 'message_content_wrap_shadow' : '', isSender ? 'message_content_wrap_self' : '']">
+                   <text-message-render v-if="showTextRender" :message="source" :isSender="isSender" @showInfo="showInfo" @htmlReady="onMessageHtmlReady" />
+                    <media-message-render v-else-if="showMediaRender" :message="source" :isSender="isSender" @enterSubPage="$emit('enterSubPage')" />
 					<audio-message-render v-else-if="showAudioRender" :message="source" :isSender="isSender" />
-					<face-message-render v-else-if="showFaceRender" :message="source" />
-					<file-message-render v-else-if="showFileRender" :message="source" />
-					<card-message-render v-else-if="showCardRender" :message="source" />
+					<face-message-render v-else-if="showFaceRender" :message="source" :isSender="isSender" />
+					<file-message-render v-else-if="showFileRender" :message="source" :isSender="isSender" />
+					<card-message-render v-else-if="showCardRender" :message="source" :isSender="isSender" @enterSubPage="$emit('enterSubPage')" />
 					<rtc-message-render v-else-if="rtcMessageData" :data="rtcMessageData" :isSender="isSender" />
-					<merge-message-render v-else-if="showMergeRender" :message="source" />
-					<location-message-render v-else-if="showLocationRender" :message="source" />
-					<group-announce-render v-else-if="showGroupAnnouncement" :message="source" />
-					<meeting-invite-message-render v-else-if="meetingMessageData" :data="meetingMessageData" />
+					<merge-message-render v-else-if="showMergeRender" :message="source" :isSender="isSender" />
+					<location-message-render v-else-if="showLocationRender" :message="source" :isSender="isSender" @enterSubPage="$emit('enterSubPage')" />
+					<group-announce-render v-else-if="showGroupAnnouncement" :message="source" :isSender="isSender" @enterSubPage="$emit('enterSubPage')" />
+					<meeting-invite-message-render v-else-if="meetingMessageData" :data="meetingMessageData" :isSender="isSender" />
 					<mass-message-render v-else-if="massMessageData" :data="massMessageData" :isSender="isSender" />
-					<custom-message-render v-else-if="showCustomRender" :message="source" />
+					<custom-message-render v-else-if="showCustomRender" :message="source" :isSender="isSender" @enterSubPage="$emit('enterSubPage')" />
 					<error-message-render v-else />
 				</view>
 			</view>
-
-			<quote-message-render :message="source" v-if="isQuoteMessage" />
-			<message-read-state :message="source" v-if="1!=1&& isSender && isSuccessMessage && !showGroupAnnouncement && !isPreview && !rtcMessageData && isShowAsRead" />
-
-			<transition name="fade">
-				<message-menu
-					v-if="menuState.visible"
-					:message="source"
-					:isSender="isSender"
-					:is_bottom="menuState.isBottom"
-					:paterWidth="menuState.paterWidth"
-					@close="menuState.visible = false"/>
-			</transition>
+			<quote-message-render :message="source" v-if="isQuoteMessage" @enterSubPage="$emit('enterSubPage')" />
 		</view>
-		
-		<!-- <view class="message_send_state">
-      <u-loading-icon v-if="showSending && !isPreview" />
-      <image
-        @click="reSendMessage"
-        v-if="isFailedMessage && !isPreview"
-        src="@/static/images/chating_message_failed.png"
-      />
-      <text v-if="readLimitSensitive" class="read_limit_count">{{
-        `${count}s`
-      }}</text>
-    </view> -->
-	
 	
 	</view>
 
-	<view v-else class="notice_message_container" :id="`auchor${source.clientMsgID}`">
-		<!-- <text>{{ getNoticeContent }}</text> -->
-		<mp-html @linktap="navigate" :previewImg="false" :showImgMenu="false" :lazyLoad="false" :content="getNoticeContent" />
+	<view v-else-if="source" class="notice_message_container"  :id="`auchor${source.clientMsgID}`">
+		<text class="notice_content" >{{ noticeContentText }}</text>
+		<text v-if="showEditButton" class="re_edit_text" @click="handleReEdit">重新编辑</text>
 	</view>
+	
 </template>
 
 <script>
 import { mapGetters, mapActions } from 'vuex';
-import IMSDK, { AllowType, IMMethods, MessageStatus, MessageType, SessionType } from 'openim-uniapp-polyfill';
+import { AllowType, MessageStatus, MessageType, SessionType } from '@/pages_im/constant/imConstants';
 import MyAvatar from '../../../../../components/MyAvatar/index.vue';
-import ChatingList from '../ChatingList.vue';
 import TextMessageRender from './TextMessageRender.vue';
+
 import MediaMessageRender from './MediaMessageRender.vue';
+
 import AudioMessageRender from './AudioMessageRender.vue';
 import FaceMessageRender from './FaceMessageRender.vue';
+
 import FileMessageRender from './FileMessageRender.vue';
+
 import CardMessageRender from './CardMessageRender.vue';
 import CustomMessageRender from './CustomMessageRender.vue';
 import MergeMessageRender from './MergeMessageRender.vue';
+
 import QuoteMessageRender from './QuoteMessageRender.vue';
+
 import LocationMessageRender from './LocationMessageRender.vue';
 import GroupAnnounceRender from './GroupAnnounceRender.vue';
 import RtcMessageRender from './RTCMessageRender.vue';
 import MeetingInviteMessageRender from './MeetingInviteMessageRender.vue';
 import ErrorMessageRender from './ErrorMessageRender.vue';
-import MessageMenu from './MessageMenu.vue';
 import MessageReadState from './MessageReadState.vue';
 import MassMessageRender from './MassMessageRender.vue';
 import { CustomType, noticeMessageTypes, PageEvents, UpdateMessageTypes } from '../../../../../constant';
-import { tipMessaggeFormat,  formatMessageTime ,parseMessageByType} from '../../../../../util/imCommon';
+import { tipMessaggeFormat,  formatMessageTime ,parseMessageByType, getIMSDK} from '../../../../../util/imCommon';
 import { businessSearchUserInfo } from '../../../../../api/login';
 const textRenderTypes = [MessageType.TextMessage, MessageType.AtTextMessage, MessageType.QuoteMessage];
 const mediaRenderTypes = [MessageType.VideoMessage, MessageType.PictureMessage];
@@ -128,12 +107,19 @@ export default {
 		RtcMessageRender,
 		MeetingInviteMessageRender,
 		ErrorMessageRender,
-		MessageMenu,
 		MessageReadState,
 		MassMessageRender
 	},
 	props: {
 		source: Object,
+		senderFaceUrl: {
+			type: String,
+			default: ''
+		},
+		senderNickname: {
+			type: String,
+			default: ''
+		},
 		isSender: {
 			type: Boolean,
 			default: false
@@ -148,12 +134,6 @@ export default {
 	},
 	data() {
 		return {
-			menuState: {
-				visible: false,
-				isBottom: false,
-				paterWidth: false,
-				sendingDelay: true
-			},
 			count: 30,
 			timer: null,
 			announcePublisher: {},
@@ -163,114 +143,161 @@ export default {
 	computed: {
 		...mapGetters(['storeCurrentConversation', 'storeCurrentGroup', 'storeSelfInfo', 'storeRevokeMap','storeCurrentUserID','storeFriendList']),
 		isSingle() {
-			return this.storeCurrentConversation.conversationType === SessionType.Single;
+			return this.storeCurrentConversation?.conversationType === SessionType.Single;
 		},
 		formattedMessageTime() {
-			return formatMessageTime(this.source.sendTime);
+			return this.source ? formatMessageTime(this.source.sendTime) : '';
 		},
 		massMessageData() {
-			if (this.source.contentType === MessageType.CustomMessage) {
+			if (this.source?.contentType === MessageType.CustomMessage) {
 				try {
-					const customData = JSON.parse(this.source.customElem.data);
-					if (customData.customType === CustomType.MassMsg) {
-						return customData.data;
+					if (this.source.customElem?.data) {
+						const customData = JSON.parse(this.source.customElem.data);
+						if (customData.customType === CustomType.MassMsg) {
+							return customData.data;
+						}
 					}
 				} catch (e) {}
 			}
 			return null;
 		},
 		showTextRender() {
-			return textRenderTypes.includes(this.source.contentType);
+			return this.source && textRenderTypes.includes(this.source.contentType);
 		},
 		showMediaRender() {
-			return mediaRenderTypes.includes(this.source.contentType);
+			return this.source && mediaRenderTypes.includes(this.source.contentType);
 		},
 		showAudioRender() {
-			return this.source.contentType === MessageType.VoiceMessage;
+			return this.source?.contentType === MessageType.VoiceMessage;
 		},
 		showFaceRender() {
-			return this.source.contentType === MessageType.FaceMessage;
+			return this.source?.contentType === MessageType.FaceMessage;
 		},
 		showFileRender() {
-			return this.source.contentType === MessageType.FileMessage;
+			return this.source?.contentType === MessageType.FileMessage;
 		},
 		showCardRender() {
-			return this.source.contentType === MessageType.CardMessage;
+			return this.source?.contentType === MessageType.CardMessage;
 		},
 		showMergeRender() {
-			return this.source.contentType === MessageType.MergeMessage;
+			return this.source?.contentType === MessageType.MergeMessage;
 		},
 		showLocationRender() {
-			return this.source.contentType === MessageType.LocationMessage;
+			return this.source?.contentType === MessageType.LocationMessage;
 		},
 		showCustomRender(){
-			return this.source.contentType===MessageType.CustomMessage;
+			return this.source?.contentType===MessageType.CustomMessage;
 		},
 		rtcMessageData() {
-			if (this.source.contentType === MessageType.CustomMessage) {
+			if (this.source?.contentType === MessageType.CustomMessage) {
 				try {
-					const customData = JSON.parse(this.source.customElem.data);
-					if (customData.customType === CustomType.Call) {
-						return customData.data;
+					if (this.source.customElem?.data) {
+						const customData = JSON.parse(this.source.customElem.data);
+						if (customData.customType === CustomType.Call) {
+							return customData.data;
+						}
 					}
 				} catch (e) {}
 			}
 			return null;
 		},
+
 		meetingMessageData() {
-			if (this.source.contentType === MessageType.CustomMessage) {
+			
+			if (this.source?.contentType === MessageType.CustomMessage) {
 				try {
-					const customData = JSON.parse(this.source.customElem.data);
-					if (customData.customType === CustomType.MeetingInvitation) {
-						return customData.data;
+					if (this.source.customElem?.data) {
+						const customData = JSON.parse(this.source.customElem.data);
+						if (customData.customType === CustomType.MeetingInvitation) {
+							return customData.data;
+						}
 					}
 				} catch (e) {}
 			}
 			return null;
 		},
 		showGroupAnnouncement() {
-			return this.source.contentType === MessageType.GroupAnnouncementUpdated;
+			return this.source?.contentType === MessageType.GroupAnnouncementUpdated;
 		},
 		getNoticeContent() {
 			if (this.showGroupAnnouncement) {
 				return '';
 			}
+			if (!this.source) return '';
 			const isNoticeMessage = noticeMessageTypes.includes(this.source.contentType);
 			const showEdit = Boolean(this.storeRevokeMap[this.source.clientMsgID]);
 			const perfix = showEdit ? `<a style="color:#0089FF; text-decoration:none;" href="${this.source.clientMsgID}"'>重新编辑</a>` : '';
 			return !isNoticeMessage ? '' : tipMessaggeFormat(this.source, this.$store.getters.storeCurrentUserID) + perfix;
 		},
+		noticeContentText() {
+			if (this.showGroupAnnouncement) {
+				return '';
+			}
+			if (!this.source) return '';
+			const isNoticeMessage = noticeMessageTypes.includes(this.source.contentType);
+			if (!isNoticeMessage) return '';
+			
+			// 获取格式化后的消息
+			const formatted = tipMessaggeFormat(this.source, this.$store.getters.storeCurrentUserID);
+			// 去除 HTML 标签
+			return formatted.replace(/<[^>]+>/g, '');
+		},
+		showEditButton() {
+			if (!this.source) return false;
+			return Boolean(this.storeRevokeMap[this.source.clientMsgID]);
+		},
 		isQuoteMessage() {
-			return this.source.contentType === MessageType.QuoteMessage || this.source.atTextElem?.quoteMessage;
+			return this.source?.contentType === MessageType.QuoteMessage || this.source?.atTextElem?.quoteMessage;
 		},
 		isSuccessMessage() {
-			return this.source.status === MessageStatus.Succeed;
+			return this.source?.status === MessageStatus.Succeed;
 		},
 		isFailedMessage() {
-			return this.source.status === MessageStatus.Failed;
+			return this.source?.status === MessageStatus.Failed;
 		},
 		showSending() {
-			return this.source.status === MessageStatus.Sending && !this.sendingDelay;
+			return this.source?.status === MessageStatus.Sending && !this.sendingDelay;
 		},
 		readLimitSensitive() {
-			return this.source.attachedInfoElem?.isPrivateChat && this.source.isRead && !this.isPreview;
+			return this.source?.attachedInfoElem?.isPrivateChat && this.source.isRead && !this.isPreview;
 		},
 		canMutipleCheck() {
-			return this.source.disabled || this.source.contentType === MessageType.GroupAnnouncementUpdated;
+			return this.source?.disabled || this.source?.contentType === MessageType.GroupAnnouncementUpdated;
 		},
 		isShowAsRead() {
-			return this.source.sessionType === SessionType.Single || (this.source.sessionType === SessionType.WorkingGroup && this.storeCurrentGroup.displayIsRead);
+			return this.source?.sessionType === SessionType.Single || (this.source?.sessionType === SessionType.WorkingGroup && this.storeCurrentGroup?.displayIsRead);
 		},
 		avatarDesc() {
 			if (this.isSingle) {
-				return this.storeCurrentConversation.showName;
+				return this.storeCurrentConversation?.showName;
 			}
-			return this.announcePublisher.nickname || this.source.senderNickname;
+			return this.announcePublisher.nickname || this.senderNickname || this.source?.senderNickname;
+		},
+		senderFaceUrlComputed() {
+			let url = this.senderFaceUrl || this.source?.senderFaceUrl;
+			// console.log("qxj senderFaceUrlComputed",url);
+			if(this.isSender){
+				const selfFace = this.$store.getters.storeSelfInfo?.faceURL;
+				if(selfFace && selfFace !== 'undefined' && selfFace !== 'null') {
+					return selfFace;
+				}
+			}
+			
+			if (!url || typeof url !== 'string') {
+				return '';
+			}
+			url = url.trim();
+			if (url === '' || url === 'undefined' || url === 'null') {
+				return '';
+			}
+			return url;
 		}
 	},
 	mounted() {
-		this.conversationID = this.$store.getters.storeCurrentConversation.conversationID;
-		this.$emit('messageItemRender', this.source.clientMsgID);
+		this.conversationID = this.$store.getters.storeCurrentConversation?.conversationID;
+		if (this.source?.clientMsgID) {
+			this.$emit('messageItemRender', this.source.clientMsgID);
+		}
 		this.isReadObserver();
 		this.setSendingDelay();
 	},
@@ -280,11 +307,6 @@ export default {
 		}
 	},
 	watch: {
-		menuOutsideFlag(newVal) {
-			if (this.menuState.visible) {
-				this.menuState.visible = false;
-			}
-		},
 		readLimitSensitive: {
 			handler(newVal) {
 				if (newVal) {
@@ -304,6 +326,10 @@ export default {
 	},
 	methods: {
 		...mapActions('message', ['updateOneMessage', 'deleteMessages']),
+		handleReEdit() {
+			if (!this.source) return;
+			uni.$emit(PageEvents.ReEditMessage, this.source.clientMsgID);
+		},
 		navigate(link) {
 			if (link.innerText == '重新编辑') {
 				uni.$emit(PageEvents.ReEditMessage, link.href);
@@ -312,6 +338,7 @@ export default {
 			this.showInfo(link.href);
 		},
 		reSendMessage() {
+			if (!this.source) return;
 			this.updateOneMessage({
 				message: this.source,
 				type: UpdateMessageTypes.KeyWords,
@@ -323,13 +350,14 @@ export default {
 				]
 			});
 			let offlinePushInfo={
-				title:message.senderNickname,
-				desc: parseMessageByType(message),
+				title:this.source.senderNickname,
+				desc: parseMessageByType(this.source, this.storeCurrentUserID),
 				ex: "",
 				iOSPushSound: "",
 				iOSBadgeCount: true
 			};
-			IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
+			const _IMSDK = getIMSDK();
+			_IMSDK.asyncApi(_IMSDK.IMMethods.SendMessage, _IMSDK.uuid(), {
 				recvID: this.storeCurrentConversation.userID,
 				groupID: this.storeCurrentConversation.groupID,
 				message: this.source,
@@ -356,36 +384,60 @@ export default {
 					});
 				});
 		},
-		async showMenu() {
+		showMenu() {
 			if (this.isPreview) {
 				return;
 			}
 			this.$emit('closeMenu');
+			// console.log("qxj showMenu");
 			this.$nextTick(() => {
+				// #ifdef APP-NVUE
+				const dom = uni.requireNativePlugin('dom');
+				dom.getComponentRect(this.$refs.messageContent, (res) => {
+					// console.log("qxj getComponentRect res",res);
+					if (res && res.size) {
+						// console.log("qxj top:", res.size.top, "width:", res.size.width);
+						this.$emit('longPress', {
+							message: this.source,
+							rect: res.size,
+							isSender: this.isSender
+						});
+					} else {
+						console.error("qxj getComponentRect failed");
+					}
+				});
+				// #endif
+				// #ifndef APP-NVUE
 				uni.createSelectorQuery()
 					.in(this)
 					.select('.message_content_wrap')
 					.boundingClientRect((res) => {
-						console.log(res.top);
-						this.menuState.paterWidth = res.width;
-						this.menuState.isBottom = res.top < 250;
-						this.menuState.visible = true;
+						// console.log(res.top);
+						this.$emit('longPress', {
+							message: this.source,
+							rect: res,
+							isSender: this.isSender
+						});
 					})
 					.exec();
+				// #endif
 			});
 		},
 		getAnnouncementPublisher() {
+			if (!this.source) return;
 			let group = {};
 			try {
 				group = JSON.parse(this.source.notificationElem.detail).group;
 			} catch (e) {}
 			if (!group.notificationUserID) return;
-			IMSDK.asyncApi(IMSDK.IMMethods.GetSpecifiedGroupMembersInfo, IMSDK.uuid(), {
+			const _IMSDK = getIMSDK();
+			_IMSDK.asyncApi(_IMSDK.IMMethods.GetSpecifiedGroupMembersInfo, _IMSDK.uuid(), {
 				groupID: group.groupID,
 				userIDList: [group.notificationUserID]
 			}).then(({ data }) => (this.announcePublisher = data[0] ?? {}));
 		},
 		checkPrivateMessage() {
+			if (!this.source) return;
 			if (this.source.attachedInfoElem?.isPrivateChat) {
 				this.clearPrivateMessage();
 			}
@@ -394,6 +446,7 @@ export default {
 			}
 		},
 		startCount() {
+			if (!this.source) return;
 			this.count = this.source.attachedInfoElem?.burnDuration || 30;
 			this.timer = setInterval(() => {
 				if (this.count > 0) {
@@ -404,7 +457,9 @@ export default {
 			}, 1000);
 		},
 		clearPrivateMessage() {
-			IMSDK.asyncApi(IMSDK.IMMethods.DeleteMessage, IMSDK.uuid(), {
+			if (!this.source) return;
+			const _IMSDK = getIMSDK();
+			_IMSDK.asyncApi(_IMSDK.IMMethods.DeleteMessage, _IMSDK.uuid(), {
 				conversationID: this.conversationID,
 				clientMsgID: this.source.clientMsgID
 			}).then(() => {
@@ -412,13 +467,14 @@ export default {
 			}).catch(() => uni.$u.toast('删除失败'));
 		},
 		setSendingDelay() {
-			if (this.source.status === MessageStatus.Sending) {
+			if (this.source?.status === MessageStatus.Sending) {
 				setTimeout(() => {
 					this.sendingDelay = false;
 				}, 2000);
 			}
 		},
 		markGroupMessageReadState() {
+			if (!this.source) return;
 			if (this.source.isRead || this.source.seq === 0) return;
 			if (!this.source.groupID) {
 				this.source.isRead = true;
@@ -431,12 +487,13 @@ export default {
 			}
 		},
 		isReadObserver() {
+			if (!this.source) return;
 			const isNoticeMessage = noticeMessageTypes.includes(this.source.contentType);
 
 			if (this.isPreview || this.isSender || isNoticeMessage) {
 				return;
 			}
-			const observer = uni.createIntersectionObserver(ChatingList);
+			const observer = uni.createIntersectionObserver(this);
 			observer.relativeTo('#scroll_view').observe(`#auchor${this.source.clientMsgID}`, ({ intersectionRatio }) => {
 				if (intersectionRatio > 0) {
 					this.markGroupMessageReadState();
@@ -456,6 +513,7 @@ export default {
 			});
 		},
 		atUser() {
+			if (!this.source) return;
 			if (!this.isSender && this.source.groupID && !this.isPreview) {
 				uni.$emit(PageEvents.AtSomeOne, {
 					userID: this.source.sendID,
@@ -464,6 +522,7 @@ export default {
 			}
 		},
 		async showInfo(userID) {
+			if (!this.source) return;
 			if (this.showGroupAnnouncement || this.isPreview || this.mutipleCheckVisible) {
 				return;
 			}
@@ -495,6 +554,7 @@ export default {
 		clickMessageItem() {
 			// console.log("--qxj msg item:");
 			// console.log(this.source);
+			if (!this.source) return;
 			if (this.mutipleCheckVisible && !this.canMutipleCheck) {
 				this.updateOneMessage({
 					message: {
@@ -505,6 +565,7 @@ export default {
 			}
 		},
 		getRole(){
+			if (!this.source) return 0;
 			let userType=0;
 			let userId="";
 			if(this.isSender){
@@ -533,7 +594,8 @@ export default {
 			if (friendInfo) {
 				info = { ...friendInfo };
 			} else {
-				const { data } = await IMSDK.asyncApi(IMSDK.IMMethods.GetUsersInfo, IMSDK.uuid(), [sourceID]);
+				const _IMSDK = getIMSDK();
+				const { data } = await _IMSDK.asyncApi(_IMSDK.IMMethods.GetUsersInfo, _IMSDK.uuid(), [sourceID]);
 				info = { ...(data[0] ?? {}) };
 			}
 			this.sourceUserInfo = {
@@ -554,11 +616,28 @@ export default {
 			let sourceUserInfo = {
 				...info
 			};
-			
+			let sourceInfoStr = '';
+			try {
+				sourceInfoStr = JSON.stringify(sourceUserInfo);
+			} catch (e) {
+				console.error('JSON stringify sourceUserInfo failed', e);
+				const safeInfo = {
+					userID: sourceUserInfo.userID,
+					nickname: sourceUserInfo.nickname,
+					faceURL: sourceUserInfo.faceURL,
+					remark: sourceUserInfo.remark
+				};
+				sourceInfoStr = JSON.stringify(safeInfo);
+			}
+
+			this.$emit('enterSubPage');
 			uni.navigateTo({
-				url: `/pages_im/pages/common/userCardMore/index?sourceInfo=${JSON.stringify(sourceUserInfo)}`
+				url: `/pages_im/pages/common/userCardMore/index?sourceInfo=${sourceInfoStr}`
 			});
 		},
+		onMessageHtmlReady(clientMsgID) {
+			this.$emit('messageItemReady', clientMsgID);
+		},
 		
 	}
 };
@@ -569,168 +648,178 @@ export default {
 	display: flex;
 	flex-direction: row;
 	padding: 16rpx 24rpx;
-	// padding-top: 48rpx;
 	position: relative;
-    background: #F5F7FA;
-	.check_wrap {
-		@include centerBox();
-		box-sizing: border-box;
-		width: 40rpx;
-		min-width: 40rpx;
-		height: 40rpx;
-		min-height: 40rpx;
-		border: 2px solid #979797;
-		border-radius: 50%;
-		margin-top: 16rpx;
-		margin-right: 24rpx;
-
-		&_active {
-			background-color: #1d6bed;
-			border: none;
-		}
+}
 
-		&_disabled {
-			background-color: #c8c9cc;
-		}
-	}
+.message_item_self {
+    flex-direction: row-reverse;
+}
 
-	.message_container {
-		display: flex;
-		flex-direction: column;
-		align-items: flex-start;
-		margin-left: 20rpx;
-		// text-align: start;
-		max-width: 80%;
-		position: relative;
-
-		.message_sender {
-			@include nomalEllipsis();
-			display: flex;
-			max-width: 480rpx;
-			// font-size: 24rpx;
-			font-size: 0.85rem;
-			color: #666;
-			margin-bottom: 6rpx;
-		}
+.check_wrap {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    box-sizing: border-box;
+    width: 40rpx;
+    height: 40rpx;
+    border-width: 2px;
+    border-style: solid;
+    border-color: #979797;
+    border-radius: 20rpx;
+    margin-top: 16rpx;
+    margin-right: 24rpx;
+}
 
-		.message_content_wrap {
-			@include vCenterBox();
-			text-align: start;
-			// font-size: 14px;
-			color: #000000;
-			width: fit-content;
-			overflow: hidden;
-			.bg_container {
-				padding: 18rpx 24rpx;
-				border-radius: 0rpx 12rpx 12rpx 12rpx;
-				background-color: #ffffff;
-			}
-		}
-	}
+.check_wrap_self {
+    margin-right: 0;
+    margin-left: 24rpx;
+}
 
-	.message_send_state_box {
-		display: flex;
-		flex-direction: row-reverse;
-		max-width: 100%;
-	}
+.check_wrap_active {
+    background-color: #1d6bed;
+    border-width: 0;
+}
 
-	.message_send_state {
-		@include centerBox();
-		margin-left: 12rpx;
-		// margin-top: 48rpx;
-		min-width: 48rpx;
-		height: 48rpx;
-
-		.read_limit_count {
-			// font-size: 24rpx;
-			font-size: 0.85rem;
-			color: #999;
-		}
+.check_wrap_disabled {
+    background-color: #c8c9cc;
+}
 
-		image {
-			width: 16px;
-			height: 16px;
-		}
-	}
+.avatar-box {
+    display: flex;
+    flex-direction: row;
+    align-items: flex-start;
+}
 
-	::v-deep .emoji_display {
-		width: 24px;
-		height: 18px;
-		vertical-align: sub;
-	}
+.taoj {
+	position: absolute;
+	left: 0px;
+	top: 0px;
+	width: 100%;
+	height: 100%;
+}
 
-	&_self {
-		flex-direction: row-reverse;
+.message_container {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    margin-left: 20rpx;
+    max-width: 562rpx;
+    position: relative;
+}
 
-		.check_wrap {
-			margin-right: 0;
-			margin-left: 24rpx;
-		}
+.message_container_self {
+    margin-left: 0;
+    margin-right: 20rpx;
+    align-items: flex-end;
+}
 
-		.message_container {
-			margin-left: 0;
-			margin-right: 20rpx;
-			// text-align: end;
-			align-items: flex-end;
-			.message_content_wrap {
-				flex-direction: row-reverse;
-				overflow: hidden;
-				.bg_container {
-					border-radius: 12rpx 0 12rpx 12rpx;
-					background-color: #95EC69 !important;
-				}
-			}
-		}
+.message_sender {
+    display: flex;
+    flex-direction: row;
+    max-width: 480rpx;
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 6rpx;
+    lines: 1;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
 
-		.message_send_state_box {
-			flex-direction: row;
-		}
+.message_content_wrap {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+}
 
-		.message_send_state {
-			margin-left: 0rpx;
-			margin-right: 12rpx;
-		}
-	}
+.message_content_wrap_self {
+    flex-direction: row-reverse;
+}
 
-	&_active {
-		background-color: #fdf5e9;
-	}
+.message_content_wrap_shadow {
+    /* box-shadow: 0 2px 4px rgba(0,0,0,0.1); */
+}
+
+.re_edit_text {
+	color: #0089FF;
+	font-size: 14px;
+	margin-left: 5px;
+}
+.bg_container {
+    padding: 18rpx 24rpx;
+    border-radius: 0rpx 12rpx 12rpx 12rpx;
+    background-color: #ffffff;
+}
+
+
+.message_send_state_box {
+    display: flex;
+    flex-direction: row-reverse;
+}
+
+.message_send_state_box_self {
+    flex-direction: row;
+}
+
+.message_send_state {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    margin-left: 12rpx;
+    width: 48rpx;
+    height: 48rpx;
+}
+
+.message_send_state_self {
+    margin-left: 0rpx;
+    margin-right: 12rpx;
+}
+
+
+
+.read_limit_count {
+    font-size: 12px;
+    color: #999;
 }
 
 .notice_message_container {
-	@include ellipsisWithLine(2);
-	text-align: center;
-	margin: 24rpx 48rpx;
-	// font-size: 24rpx;
-	font-size: 0.85rem;
-	color: #999;
-	position: relative;
+    lines: 2;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    text-align: center;
+    margin: 24rpx 48rpx;
+    font-size: 14px;
+    color: #999;
+    position: relative;
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+}
+
+.notice_content{
+	 font-size: 18px;
+	 color: #999;
+}
+
+.message_item_active {
+    background-color: #fdf5e9;
 }
 
 .fade-leave,
 .fade-enter-to {
-	opacity: 1;
+    opacity: 1;
 }
 
 .fade-leave-active,
 .fade-enter-active {
-	transition: all 0.5s;
+    transition-property: opacity;
+    transition-duration: 0.5s;
 }
 
 .fade-leave-to,
 .fade-enter {
-	opacity: 0;
+    opacity: 0;
 }
-
-.taoj{
-		position: absolute;
-		left: 0px;
-		top: 0px;
-		right: 0px;
-		bottom: 0px;
-		width: 84rpx;
-		height: 84rpx;
-	}
-
-
 </style>

+ 1086 - 0
pages_im/pages/conversation/chating/index.nvue

@@ -0,0 +1,1086 @@
+<template>
+	<view class="chating_container">
+	<!-- 	<view style="margin:100px;">1111</view> -->
+		<chating-header ref="chatingHeaderRef" style="z-index: 100;" :storeCurrentConversation="currentConversation" :mutipleCheckVisible="mutipleCheckVisible" @mutipleCheckUpdate="mutipleCheckUpdate" @back="onHeaderBack" @enterSubPage="onEnterSubPage"></chating-header>
+		
+		<view class="chat_content_wrap">
+			<list v-if="renderContent" class="chat-list" :class="[isInvertedMode ? 'inverted' : '']" ref="list" :show-scrollbar="false" @click="onListClick" :style="{opacity: isLayoutReady ? 1 : 0}">
+				
+				<!-- Normal Mode: Top Loader (History) -->
+				<cell ref="normal-top-loader" @appear="onTopLoaderAppear" v-if="!isInvertedMode && storeHasMoreMessage && storeHistoryMessageList.length >= 10 && !initLoading" class="normal-loader">
+					<view class="top_loader_container">
+						<loading-indicator class="top_loader_indicator" :animating="true"></loading-indicator>
+					</view>
+				</cell>
+
+				<!-- Inverted Mode: Bottom Anchor (Visual Bottom / Physical Top) -->
+				<cell v-if="isInvertedMode" ref="invertedBottomAnchor">
+					<view style="height: 1px; width: 750rpx;"></view>
+				</cell>
+
+				<cell v-for="(item, index) in currentRenderList" :key="item.clientMsgID" :ref="'msg-' + (item.clientMsgID || index)">
+					<view :class="[isInvertedMode ? 'inverted-content' : '']">
+						<!-- 预加载触发器:当渲染到列表第 5 条消息时触发(实际上是倒数第5条,因为是历史消息) -->
+						<!-- 注意:Normal 模式下 index 0 是最旧的消息,index 5 是较旧的消息 -->
+						<!-- 我们希望用户向上滚动接近顶部时触发,即接近 index 0 -->
+						<!-- 所以当 index === 5 的元素出现时,说明用户已经滚到了很上面 -->
+						<view v-if="index === 5 && !isInvertedMode" @appear="onPreloadAppear" style="height: 1px; width: 1px; opacity: 0; position: absolute;"></view>
+						<view v-if="isInvertedMode && index === currentRenderList.length - 5" @appear="onPreloadAppear" style="height: 1px; width: 1px; opacity: 0; position: absolute;"></view>
+						
+						<view v-if="shouldShowTime(item, index) && !isInvertedMode" class="time_gap_line">
+							<text class="time_gap_text">{{ getTimeGap(item, index) }}</text>
+						</view>
+						<message-item-render 
+							:source="item" 
+							:senderFaceUrl="item.senderFaceUrl"
+							:senderNickname="item.senderNickname"
+							:isSender="item.sendID === storeCurrentUserID"
+							:mutipleCheckVisible="mutipleCheckVisible"
+							@longPress="handleLongPress"
+							@enterSubPage="onEnterSubPage">
+						</message-item-render>
+						<view v-if="shouldShowTime(item, index) && isInvertedMode" class="time_gap_line">
+							<text class="time_gap_text">{{ getTimeGap(item, index) }}</text>
+						</view>
+					</view>
+				</cell>
+				
+				<!-- Normal Mode: Bottom Anchor -->
+				<cell v-if="!isInvertedMode" ref="normalBottomAnchor">
+					<view style="height: 1px; width: 750rpx;"></view>
+				</cell>
+				
+				<!-- Inverted Mode: Top Loader (Visual Top / Physical Bottom) -->
+				<cell ref="inverted-top-loader" @appear="onTopLoaderAppear" v-if="isInvertedMode && storeHasMoreMessage && !initLoading">
+					<view class="top_loader_container" :class="[isInvertedMode ? 'inverted-content' : '']">
+						<loading-indicator class="top_loader_indicator" :animating="true"></loading-indicator>
+					</view>
+				</cell>
+			</list>
+
+			<view v-if="initLoading" class="page_loading_container" @touchmove.stop.prevent>
+				<!-- <loading-indicator class="page_loading_indicator" :animating="true"></loading-indicator> -->
+				<!-- <text class="page_loading_text">加载中...</text> -->
+			</view>
+		</view>
+		
+		
+		<chating-footer ref="chatingFooterRef" :footerOutsideFlag="footerOutsideFlag" @scrollToBottom="scrollToBottom" @enterSubPage="onEnterSubPage" v-if="!mutipleCheckVisible"></chating-footer>
+		<view v-if="mutipleCheckVisible" class="mutiple_action_container">
+				<view @click="forward('MergeForWard')" class="action_item">
+					<image class="action_icon" src="/pages_im/static/images/mutiple_merge.png" />
+					<text class="action_text">合并转发</text>
+				</view>
+				<view @click="mutipleRemove" class="action_item">
+					<image class="action_icon" src="/pages_im/static/images/mutiple_delete.png" />
+					<text class="action_text action_text_red">删除</text>
+				</view>
+		</view>
+		<message-menu
+			v-if="menuData.visible"
+			:message="menuData.message"
+			:isSender="menuData.isSender"
+			:paterWidth="menuData.paterWidth"
+			:rect="menuData.rect"
+			@close="closeMenuGlobal"
+			@enterSubPage="onEnterSubPage"/>
+			
+			
+	</view>
+</template>
+
+<script>
+import '../../../util/polyfill-nvue';
+import { mapGetters, mapActions } from 'vuex';
+import IMSDK from 'openim-uniapp-polyfill';
+import { MessageStatus, SessionType, MessageType } from '@/pages_im/constant/imConstants';
+import { ContactChooseTypes, GroupMemberListTypes, PageEvents, SendMessageFailedType } from '../../../constant';
+import ChatingHeader from './components/ChatingHeader.vue';
+import ChatingFooter from './components/ChatingFooter/index.vue';
+import MessageItemRender from './components/MessageItem/index.vue';
+import MessageMenu from './components/MessageItem/MessageMenu.vue';
+import dayjs from 'dayjs';
+import { markConversationAsRead, parseMessageByType, callingModule, getIMSDK } from '../../../util/imCommon';
+
+const dom = uni.requireNativePlugin('dom');
+
+// 封装获取节点矩形信息的工具函数
+
+export default {
+	components: {
+		ChatingHeader,
+		ChatingFooter,
+		MessageItemRender,
+		MessageMenu
+	},
+	data() {
+		return {
+			menuData: {
+				visible: false,
+				message: null,
+				rect: {},
+				isSender: false,
+				paterWidth: 0
+			},
+			renderContent: true,
+			footerOutsideFlag: 0,
+			menuOutsideFlag: 0,
+			initLoading: true,
+			mutipleCheckVisible: false,
+			back2Tab: false,
+			timer: null,
+			refreshing: false,
+			messageLoadState: {
+				loading: false
+			},
+			canShowBot: false,
+			conversationID: '',
+			localConversation: null,
+			isBackAction: false,
+			isEnterSubPage: false,
+			loadingStartTime: 0,
+			layoutRetryCount: 0,
+			stabilityCounter: 0,
+			layoutTimer: null,
+			isLayoutReady: false,
+			isInvertedMode: false, // 默认使用 Normal Mode,避免少消息时初始倒置导致靠底
+			checkModeTimer: null,
+			isHistoryLoading: false, // 标记是否正在加载历史消息
+			historyAnchorID: '' // 加载历史消息时的定位锚点ID
+		};
+	},
+	computed: {
+		...mapGetters([
+			'timStore', 
+			'storeHistoryMessageList', 
+			'storeHasMoreMessage', 
+			'storeCurrentUserID', 
+			'storeFriendList', 
+			'storeCurrentGroup'
+		]),
+		currentConversation() {
+			return this.localConversation || {};
+		},
+		imType() {
+			return this.timStore?.imType || '';
+		},
+		// 反转消息列表:[最新消息, ..., 最旧消息]
+		reversedMessageList() {
+			// 确保按时间倒序排列:时间越大(越新)越靠前
+			// 配合 Inverted Mode,index 0 (最新) 在视觉底部,index N (最旧) 在视觉顶部
+			return [...this.storeHistoryMessageList].sort((a, b) => (a.sendTime || 0) - (b.sendTime || 0)).reverse();
+		},
+		currentRenderList() {
+			if (this.isInvertedMode) {
+				return this.reversedMessageList;
+			}
+			// Normal Mode: 按时间正序排列 (Old -> New)
+			return [...this.storeHistoryMessageList].sort((a, b) => (a.sendTime || 0) - (b.sendTime || 0));
+		}
+	},
+	watch: {
+		storeHistoryMessageList: {
+			handler(newVal, oldVal) {
+				// 如果正在加载历史消息,不触发自动滚动
+				if (this.isHistoryLoading) return;
+				// 只有当有新消息新增(即列表末尾元素变化)时,才滚动到底部
+				// 避免加载历史消息(列表头部变化)时触发滚动
+				const newLast = newVal && newVal.length > 0 ? newVal[newVal.length - 1] : null;
+				const oldLast = oldVal && oldVal.length > 0 ? oldVal[oldVal.length - 1] : null;
+				
+				if (!oldLast || (newLast && newLast.clientMsgID !== oldLast.clientMsgID)) {
+					// 新消息来了,触发滚动
+					this.triggerModeCheck();
+				}
+			},
+		}
+	},
+	onShow() {
+			try {
+				const app = getApp();
+				if (app && app.globalData) {
+					if (app.globalData.IM_back2Tab !== undefined) {
+						this.back2Tab = app.globalData.IM_back2Tab;
+					}
+					if (app.globalData.IM_CURRENT_CONVERSATION) {
+						const newConv = app.globalData.IM_CURRENT_CONVERSATION;
+						// 检查是否需要重新初始化会话
+						// 只要页面可见,且会话ID一致,就必须确保页面可见性状态正确
+						if (this.conversationID === newConv.conversationID) {
+							// 同一会话,无需重新加载,直接确保显示即可
+							// 如果之前被隐藏了(虽然现在我们不再隐藏),这里做个双保险
+							this.initLoading = false;
+							this.isLayoutReady = true;
+							this.renderContent = true;
+							
+							// 恢复监听器
+							this.setPageListener();
+							// 刷新一下未读状态等
+							this.checkCalling();
+							let userID = this.currentConversation?.userID;
+							this.canShowBot = this.isUserInFriendList(userID, this.storeFriendList);
+							
+						} else if (!this.conversationID || this.conversationID !== newConv.conversationID) {
+							// 不同会话,执行完整初始化
+							this.handleConversationInit(newConv);
+						}
+					}
+				}
+			} catch (e) {
+				console.error('onShow error', e);
+			}
+		},
+	onLoad(options) {
+		const app = getApp();
+		let conversation = null;
+		// 1. 尝试从 GlobalData 获取
+		if (app && app.globalData && app.globalData.IM_CURRENT_CONVERSATION) {
+			conversation = app.globalData.IM_CURRENT_CONVERSATION;
+			if (app.globalData.IM_back2Tab !== undefined) {
+				this.back2Tab = app.globalData.IM_back2Tab;
+			}
+		}
+		// 2. 尝试从 Storage 获取 (兜底)
+		if (!conversation) {
+			try {
+				const storageData = uni.getStorageSync('IM_CURRENT_CONVERSATION');
+				if (storageData) {
+					conversation = storageData;
+				}
+			} catch (e) {}
+		}
+		if (conversation) {
+			this.localConversation = conversation;
+			this.conversationID = conversation.conversationID;
+			this.renderContent = true;
+			this.initLoading = true; // 保持 Loading 直到消息加载并计算完布局
+			
+			// 同步到 Store
+			this.$store.commit('conversation/SET_CURRENT_CONVERSATION', conversation);
+			
+			// 如果是工作群,获取群信息
+			if (conversation.conversationType === SessionType.WorkingGroup) {
+				this.$store.dispatch("conversation/getCurrentGroup", conversation.groupID);
+				this.$store.dispatch("conversation/getCurrentMemberInGroup", conversation.groupID);
+			}
+		}
+		
+		this.initLogic();
+		this.checkCalling();
+		
+		let userID = this.currentConversation?.userID;
+		this.canShowBot = this.isUserInFriendList(userID, this.storeFriendList);
+		
+		// 监听预加载事件
+		uni.$on('IM_PRELOAD_CONVERSATION', this.preloadConversation);
+	},
+	onHide() {
+		// 当页面隐藏时(如返回上一页),重置部分状态
+		// 但不要重置 conversationID,因为下次进来可能还是同一个会话
+		// 关键:必须允许重新加载
+		this.messageLoadState.loading = false;
+		if (this.stopLoadingTimer) clearTimeout(this.stopLoadingTimer);
+		// 优化:不要在 onHide 时隐藏内容,避免返回时白屏
+		// this.isLayoutReady = false; 
+		this.disposePageListener();
+		markConversationAsRead({...this.currentConversation}, true);
+		if (this.timer) clearInterval(this.timer);
+	},
+	onUnload() {
+		//uni.$off('IM_PRELOAD_CONVERSATION', this.preloadConversation);
+		this.disposePageListener();
+		markConversationAsRead({...this.currentConversation}, true);
+		this.resetConversationState();
+		this.resetMessageState();
+		if (this.timer) clearInterval(this.timer);
+		
+		this.localConversation = null; 
+		this.conversationID = '';
+		this.initLoading = true;
+		this.renderContent = false;
+		this.isInvertedMode = false; // 退出时重置为 Normal Mode
+	},
+	
+	onBackPress() {
+		if (this.mutipleCheckVisible) {
+			this.mutipleCheckUpdateHandler({ flag: false });
+			return true;
+		}
+		this.isBackAction = true;
+		return false;
+	},
+	methods: {
+		...mapActions('message', ['resetMessageState', 'deleteMessages']),
+		...mapActions('conversation', ['resetConversationState']),
+		
+		
+		/**
+		 * 处理会话初始化逻辑(复用于 onShow)
+		 */
+		async handleConversationInit(newConv) {
+			this.disposePageListener();
+			
+			// 内存优化:手动释放本地大对象引用
+			this.localConversation = null;
+			this.menuData.message = null;
+			
+			this.resetConversationState();
+			// 性能优化:并行执行重置和加载,不强制等待 reset 完成,只要保证新数据覆盖即可
+			// resetMessageState 主要是为了清空 store,防止旧数据闪现
+			this.resetMessageState(); 
+			
+			this.localConversation = newConv;
+			this.conversationID = newConv.conversationID;
+			this.renderContent = true;
+			this.initLoading = true; // 重新加载时显示 Loading
+			this.isLayoutReady = false;
+			this.loadingStartTime = Date.now();
+			this.layoutRetryCount = 0;
+			if (this.stopLoadingTimer) clearTimeout(this.stopLoadingTimer);
+			
+			// 关键:重置本地加载状态
+			this.messageLoadState.loading = false;
+			this.isHistoryLoading = false;
+			this.historyAnchorID = '';
+			this.isInvertedMode = false; // 切换会话时重置为 Normal Mode
+			
+			this.$nextTick(() => {
+				this.$store.commit('conversation/SET_CURRENT_CONVERSATION', this.localConversation);
+				if (this.localConversation.conversationType === SessionType.WorkingGroup) {
+					this.$store.dispatch("conversation/getCurrentGroup", this.localConversation.groupID);
+					this.$store.dispatch("conversation/getCurrentMemberInGroup", this.localConversation.groupID);
+				}
+				// 立即启动加载,不再等待
+				this.initLogic(false);
+				let userID = this.currentConversation?.userID;
+				this.canShowBot = this.isUserInFriendList(userID, this.storeFriendList);
+			});
+		},
+		
+		
+		// --- 事件处理 onLoad emit---
+		preloadConversation(newConv) {
+			if (!newConv) return;
+			
+			// 清理旧的监听器,防止重复注册
+			this.disposePageListener();
+			
+			this.localConversation = newConv;
+			this.conversationID = newConv.conversationID;
+			this.$store.commit('conversation/SET_CURRENT_CONVERSATION', newConv);
+			
+			// 确保先清空消息状态,再进行后续操作
+			this.resetMessageState().then(() => {
+				// 重置渲染状态,强制列表重新渲染,避免视图残留或 DOM 异常
+				this.renderContent = false;
+				
+				// 关键:重置本地加载状态,防止因上一次加载未完成而导致死锁
+				this.messageLoadState.loading = false;
+				this.isHistoryLoading = false;
+				this.historyAnchorID = '';
+				this.isInvertedMode = false; // 预加载时重置为 Normal Mode
+				
+				this.initLoading = true; 
+				this.isLayoutReady = false;
+				this.loadingStartTime = Date.now();
+				this.layoutRetryCount = 0;
+				if (this.stopLoadingTimer) clearTimeout(this.stopLoadingTimer);
+				
+				if (this.localConversation.conversationType === SessionType.WorkingGroup) {
+					this.$store.dispatch("conversation/getCurrentGroup", this.localConversation.groupID);
+					this.$store.dispatch("conversation/getCurrentMemberInGroup", this.localConversation.groupID);
+				}
+				
+				this.$nextTick(() => {
+					this.renderContent = true;
+					this.initLogic(false);
+				});
+			});
+		},
+		
+		
+		/**
+		 * 页面初始化主逻辑
+		 */
+		async initLogic(shouldReset = true) {
+			if (shouldReset) {
+				await this.resetMessageState();
+				this.initLoading = true;
+				this.isLayoutReady = false;
+				this.loadingStartTime = Date.now();
+				this.layoutRetryCount = 0;
+				this.isInvertedMode = false; // 初始化时重置为 Normal Mode
+				if (this.stopLoadingTimer) clearTimeout(this.stopLoadingTimer);
+			}
+			
+			this.setPageListener();
+			this.loadMessageList(false);
+			
+			// 兜底:5秒后强制结束 loading
+			setTimeout(() => {
+				if (this.initLoading) {
+					this.initLoading = false;
+				}
+			}, 5000);
+		},
+
+
+		/**
+		 * 加载消息列表
+		 * @param {boolean} isHistory 是否加载历史消息
+		 */
+		loadMessageList(isHistory = false) {
+			if (this.messageLoadState.loading) return;
+			// 预加载命中优化
+			if (!isHistory && this.storeHistoryMessageList.length > 0) {
+				// 先滚动到底部,且不显示页面(保持透明)
+				this.$nextTick(() => {
+					this.scrollToBottom();
+					
+					// 延迟一点点再显示页面,确保滚动已经完成,避免视觉跳动
+					setTimeout(() => {
+						this.isLayoutReady = true;
+						this.initLoading = false;
+					}, 50);
+				});
+				return;
+			}
+			this.messageLoadState.loading = true;
+			// 标记正在加载历史消息,防止 watch 触发滚动到底部
+			if (isHistory) {
+				this.isHistoryLoading = true;
+			}
+			
+			const conversationID = this.conversationID || this.currentConversation?.conversationID;
+			
+			if (!conversationID) {
+				this.messageLoadState.loading = false;
+				this.initLoading = false;
+				this.isHistoryLoading = false;
+				return;
+			}
+			
+			const startClientMsgID = isHistory && this.storeHistoryMessageList.length > 0 
+				? this.storeHistoryMessageList[0].clientMsgID : '';
+
+			this.$store.dispatch('message/getHistoryMesageList', {
+				conversationID,
+				startClientMsgID,
+				count: 20
+			}).then(res => {
+				this.messageLoadState.loading = false;
+				if (!isHistory) {
+					// 强制使用 Normal 模式 (从上往下),解决小屏倒置问题
+					// 除非消息数量极大(>1000),否则不使用 Inverted 模式
+					// 当消息数量不足 10 条时,使用 Normal 模式
+					// 需求:当超过10条时,使用倒置模式,消息从底部往上排列(时间越大越靠底)
+					if (this.storeHistoryMessageList.length < 10) {
+						this.isInvertedMode = false;
+						
+						// Normal Mode (Top Aligned)
+						// 不进行滚动,直接显示
+						this.initLoading = false;
+						this.isLayoutReady = true;
+					} else {
+						this.isInvertedMode = true;
+						
+						// Inverted Mode (Bottom Aligned)
+						// 优化:Inverted List 的物理顶部就是视觉底部,且数据是按时间倒序排列(最新在Index 0)
+						// 所以渲染完成后,默认位置就是最新消息处,无需手动滚动,直接显示,消除闪烁和白屏等待。
+						this.$nextTick(() => {
+							this.initLoading = false;
+							this.isLayoutReady = true;
+						});
+					}
+				} else {
+					if (this.isInvertedMode) {
+						// 加载历史消息:无感保持位置
+						// Inverted Mode 下,新消息添加在列表末尾(物理底部),
+						// 原有的列表内容位置不变,用户只需继续滚动即可看到新内容
+						// 不需要手动 scrollToElement,否则会导致抖动
+						this.$nextTick(() => {
+							// 延迟一点点确保渲染完成后再允许 watch 触发
+							setTimeout(() => {
+								this.isHistoryLoading = false;
+								this.historyAnchorID = ''; // 清除锚点
+							}, 50);
+						});
+					} else {
+						// Normal Mode 下加载历史消息,需要手动滚动到锚点
+						const anchorID = this.historyAnchorID || startClientMsgID;
+						if (anchorID) {
+							this.$nextTick(() => {
+								const targetRef = 'msg-' + anchorID;
+								const el = this.$refs[targetRef];
+								
+								if (el && Array.isArray(el) && el.length > 0) {
+									dom.scrollToElement(el[0], { animated: false, offset: 0 });
+								}
+								
+								setTimeout(() => {
+									this.isHistoryLoading = false;
+									this.historyAnchorID = ''; // 清除锚点
+								}, 50);
+							});
+						} else {
+							this.isHistoryLoading = false;
+						}
+					}
+				}
+				// 只有非历史消息(即初始加载或发送新消息)才触发模式检查和自动滚动
+				// 加载历史消息时,我们已经手动处理了位置保持,绝对不能再触发自动滚动
+				if (!isHistory) {
+					if (this.storeHistoryMessageList.length <= 20) { // 简单判断是否初始
+						this.triggerModeCheck();
+					}
+				}
+			}).catch(err => {
+				this.messageLoadState.loading = false;
+				this.initLoading = false;
+				this.isHistoryLoading = false;
+				console.error("加载消息失败", err);
+			});
+		},
+		
+		
+		
+		/**
+		 * 触发模式检查
+		 */
+		triggerModeCheck() {
+			if (this.checkModeTimer) clearTimeout(this.checkModeTimer);
+			this.checkModeTimer = setTimeout(() => {
+				this.checkLayoutMode();
+			}, 50);
+		},
+
+		/**
+		 * 检查并切换布局模式
+		 * Normal Mode: 如果内容超过一屏,切换到 Inverted Mode
+		 * Inverted Mode: 保持不变 (除非清空消息,这里暂不处理)
+		 */
+		async checkLayoutMode() {
+			// 仅用于触发滚动和结束Loading
+			// 如果是初始加载,不要在这里立即结束 Loading,交给 loadMessageList 的回调处理
+			// 避免滚动还没完成就显示了
+			if (!this.initLoading && !this.isInvertedMode) {
+				this.scrollToBottom();
+			}
+		},
+
+		tryStopLoading(delay = 100) {
+			if (this.initLoading) {
+				// 计算剩余需要的等待时间,确保复杂消息至少有一定渲染时间
+				const now = Date.now();
+				const lastItem = this.reversedMessageList[0]; // 注意:reversedList[0] 是最新消息
+				// 即使是普通文本消息,也给予至少 500ms 的 Loading 时间
+				// 复杂消息给予 1000ms
+				const minDuration = this.isComplexMessage(lastItem) ? 200 : 0;
+				const elapsed = now - (this.loadingStartTime || 0);
+				const remaining = Math.max(0, minDuration - elapsed);
+				
+				const finalDelay = Math.max(delay, remaining);
+				
+				// 清除之前的定时器,防止过早结束
+				if (this.stopLoadingTimer) clearTimeout(this.stopLoadingTimer);
+				
+				// 延迟结束 Loading,确保布局调整后的渲染已上屏
+				this.stopLoadingTimer = setTimeout(() => {
+					this.initLoading = false;
+					this.stopLoadingTimer = null;
+					// 只有当 Loading 真正结束时,才允许显示列表
+					this.$nextTick(() => {
+						this.isLayoutReady = true;
+					});
+				}, finalDelay);
+			}
+		},
+
+		scrollToBottom() {
+			this.$nextTick(() => {
+				if (this.isInvertedMode) {
+					// Inverted Mode: 滚动到 Visual Bottom (Physical Top)
+					// 我们的模板中有一个专门的 anchor: invertedBottomAnchor
+					const bottomAnchor = this.$refs['invertedBottomAnchor'];
+					if (bottomAnchor) {
+						dom.scrollToElement(bottomAnchor, { animated: false, offset: 0 });
+					} else {
+						// 兜底:滚动到第一条数据 (最新消息)
+						if (this.currentRenderList.length > 0) {
+							const firstItem = this.currentRenderList[0];
+							const refName = 'msg-' + (firstItem.clientMsgID || 0);
+							const el = this.$refs[refName];
+							if (el && Array.isArray(el) && el.length > 0) {
+								dom.scrollToElement(el[0], { animated: false, offset: 0 });
+							}
+						}
+					}
+					return;
+				}
+				
+				// Normal Mode: 滚动到最后一条消息
+				// 优先滚动到最后一条消息,比滚动到锚点更精准
+				const listLen = this.currentRenderList.length;
+				
+				if (listLen > 0) {
+					const lastItem = this.currentRenderList[listLen - 1];
+					// 确保 ref key 与模板中一致:'msg-' + (item.clientMsgID || index)
+					const refName = 'msg-' + (lastItem.clientMsgID || (listLen - 1));
+					const el = this.$refs[refName];
+					
+					if (el && Array.isArray(el) && el.length > 0) {
+						dom.scrollToElement(el[0], { animated: false, offset: 0 });
+						return;
+					}
+				}
+				
+				// 兜底:滚动到锚点
+				const anchorName = 'normalBottomAnchor';
+				const bottomAnchor = this.$refs[anchorName];
+				if (bottomAnchor) {
+					dom.scrollToElement(bottomAnchor, { animated: false, offset: 0 });
+				}
+			});
+		},
+		
+		isComplexMessage(item) {
+			if (!item) return false;
+			// 图片、视频、文件等视为复杂消息
+			const type = item.type;
+			return type === 'IMG' || type === 'VIDEO' || type === 'FILE' || type === 'CUSTOM';
+		},
+
+		onTopLoaderAppear() {
+			if (this.initLoading || this.messageLoadState.loading || !this.storeHasMoreMessage) return;
+			// 增加防抖
+			if (this.preloadTimer) clearTimeout(this.preloadTimer);
+			this.preloadTimer = setTimeout(() => {
+				// 记录当前顶部消息ID作为锚点
+				if (this.currentRenderList.length > 0) {
+					this.historyAnchorID = this.currentRenderList[0].clientMsgID;
+				}
+				this.loadMessageList(true);
+			}, 100);
+		},
+		
+		onPreloadAppear() {
+			// 预加载触发
+			if (this.messageLoadState.loading || !this.storeHasMoreMessage) return;
+			
+			// 增加防抖
+			if (this.preloadTimer) clearTimeout(this.preloadTimer);
+			this.preloadTimer = setTimeout(() => {
+				// 记录触发点(Index 5)作为锚点,避免大幅跳动
+				if (this.currentRenderList.length > 5) {
+					this.historyAnchorID = this.currentRenderList[5].clientMsgID;
+				} else if (this.currentRenderList.length > 0) {
+					this.historyAnchorID = this.currentRenderList[0].clientMsgID;
+				}
+				this.loadMessageList(true);
+			}, 100);
+		},
+
+		// --- 辅助函数 ---
+		isUserInFriendList(userID, friendList) {
+			if (!friendList) return false;
+			return friendList.some(f => f.userID === userID);
+		},
+		
+		/**
+		 * 判断是否显示时间
+		 * @param {Object} item 当前消息
+		 * @param {Number} index 当前消息在 reversedMessageList 中的索引
+		 */
+		shouldShowTime(item, index) {
+			if (this.isInvertedMode) {
+				// Inverted Mode (New -> Old)
+				// 视觉最顶部是列表最后一条 (index = length - 1)
+				// 但是因为是倒置列表,我们遍历是从 index 0 开始(视觉最底部)
+				// 我们的目标是:如果当前消息 (index) 和上一条消息 (index+1, 更旧) 时间间隔大,则在当前消息上方显示时间
+				
+				// 1. 最旧的消息(视觉最顶部,列表最后一条)总是显示时间(如果它是第一条被加载的)
+				if (index === this.currentRenderList.length - 1) return true;
+				
+				// 2. 比较当前消息 (index) 和更旧的消息 (index + 1)
+				const prevItem = this.currentRenderList[index + 1];
+				if (!prevItem) return true;
+				
+				// 注意:这里比较的是 当前消息时间 - 上一条(更旧)消息时间
+				return (item.sendTime - prevItem.sendTime) > 5 * 60 * 1000;
+			} else {
+				// Normal Mode (Old -> New)
+				// 视觉最顶部是列表第一条 (index = 0)
+				if (index === 0) return true;
+				
+				// 上一条消息(时间更早)是 index - 1
+				const prevItem = this.currentRenderList[index - 1];
+				if (!prevItem) return true;
+				
+				return (item.sendTime - prevItem.sendTime) > 5 * 60 * 1000;
+			}
+		},
+		
+		getTimeGap(message, index) {
+			// 获取用于显示的时间内容
+			// 逻辑与 shouldShowTime 配套
+			
+			// 无论哪种模式,我们显示的都是当前消息 message.sendTime 的格式化时间
+			// 区别仅在于它是基于和哪条消息对比得出的显示结论
+			
+			const sendTime = message.sendTime;
+			const now = dayjs();
+			const messageDate = dayjs(sendTime);
+			
+			if (messageDate.isSame(now, 'day')) {
+				if (now.diff(messageDate, 'second') < 60) return '刚刚';
+				return messageDate.format('HH:mm');
+			}
+			if (messageDate.isSame(now.subtract(1, 'day'), 'day')) {
+				return '昨天 ' + messageDate.format('HH:mm');
+			}
+			if (messageDate.isSame(now, 'year')) {
+				return messageDate.format('MM-DD HH:mm');
+			}
+			return messageDate.format('YYYY-MM-DD HH:mm');
+		},
+		
+	
+		onHeaderBack() {
+			this.setPageListener();
+			this.loadMessageList(false);
+		},
+		
+		setPageListener() {
+			uni.$on('sendMessage', () => this.scrollToBottom());
+			IMSDK.subscribe('onRecvC2CReadReceipt', ({ data }) => console.log('onRecvC2CReadReceipt', data));
+			uni.$on(PageEvents.TypingUpdate, this.typingHandler);
+			uni.$on(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
+			uni.$on(PageEvents.ScrollToBottom, this.scrollToBottom);
+			uni.$on(PageEvents.AtSomeOne, this.atSomeOneHandler);
+			uni.$on(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
+			uni.$on(PageEvents.RtcCall, this.rtcInvitePrepare);
+			uni.$on(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
+		},
+		
+		disposePageListener() {
+			uni.$off(PageEvents.TypingUpdate, this.typingHandler);
+			uni.$off(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
+			uni.$off(PageEvents.ScrollToBottom, this.scrollToBottom);
+			uni.$off(PageEvents.AtSomeOne, this.atSomeOneHandler);
+			uni.$off(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
+			uni.$off(PageEvents.RtcCall, this.rtcInvitePrepare);
+			uni.$off(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
+		},
+
+		// ... 保留其他业务逻辑方法 ...
+		onEnterSubPage() {
+			this.isEnterSubPage = true;
+		},
+		onListClick() {
+			uni.hideKeyboard();
+			this.menuOutsideFlag += 1;
+			this.footerOutsideFlag += 1;
+			if (this.menuData.visible) {
+				this.closeMenuGlobal();
+			}
+		},
+		nativeCallEndHandler() {
+			if (typeof plus === 'undefined' || !this.currentConversation.groupID) return;
+			IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.currentConversation.groupID).then(({ data }) => {
+				if (this.$refs.chatingHeaderRef) {
+					this.$refs.chatingHeaderRef.updateCallingData(data.invitation ? data : null);
+				}
+			});
+		},
+		checkCalling() {
+			if (typeof plus === 'undefined' || !this.currentConversation.groupID) return;
+			if (this.timer) clearInterval(this.timer);
+			this.nativeCallEndHandler();
+			this.timer = setInterval(() => this.nativeCallEndHandler(), 10000);
+		},
+		mutipleRemove() {
+			const checkedMessages = this.storeHistoryMessageList.filter((message) => message.checked);
+			if (checkedMessages.length === 0) {
+				uni.$u.toast('请先选择要操作的消息');
+				return;
+			}
+			let count = 0;
+			checkedMessages.map((message) => {
+				IMSDK.asyncApi(IMSDK.IMMethods.DeleteMessage, IMSDK.uuid(), {
+					conversationID: this.currentConversation.conversationID,
+					clientMsgID: message.clientMsgID
+				}).then(() => {
+					count++;
+					if (count === checkedMessages.length) {
+						this.deleteMessages(checkedMessages);
+						this.mutipleCheckUpdateHandler({ flag: false });
+						uni.$u.toast('删除成功');
+					}
+				});
+			});
+		},
+		forward(type) {
+			const checkedMessages = this.storeHistoryMessageList.filter((message) => message.checked);
+			if (checkedMessages.length === 0) {
+				uni.$u.toast('请先选择要操作的消息');
+				return;
+			}
+			const currentConversation = this.currentConversation;
+			const title = currentConversation.groupID ? `群聊${currentConversation.showName}的聊天记录` : `和${currentConversation.showName}的聊天记录`;
+
+			const mergeInfo = {
+				messageList: [...checkedMessages],
+				summaryList: checkedMessages.map((message) => `${message.senderNickname}:${parseMessageByType(message)}`),
+				title
+			};
+			uni.navigateTo({
+				url: `/pages_im/pages/common/contactChoose/index?type=${type}&mergeInfo=${encodeURIComponent(JSON.stringify(mergeInfo))}`
+			});
+		},
+		closeMenuGlobal() {
+			this.menuData.visible = false;
+			this.menuData.message = null;
+		},
+		handleLongPress(data) {
+			uni.hideKeyboard();
+			this.menuData = {
+				visible: true,
+				message: data.message,
+				rect: data.rect,
+				isSender: data.isSender,
+				paterWidth: data.rect.width
+			};
+		},
+		typingHandler() {
+			if (this.$refs.chatingHeaderRef) {
+				this.$refs.chatingHeaderRef.updateTyping();
+			}
+		},
+		onlineCheckHandler() {
+			if (this.$refs.chatingHeaderRef) {
+				this.$refs.chatingHeaderRef.checkOnline();
+			}
+		},
+		atSomeOneHandler({ userID, nickname }) {
+			// 震动反馈
+			if (typeof plus !== 'undefined') {
+				if (plus.os.name == 'iOS') {
+					var UIImpactFeedbackGenerator = plus.ios.importClass('UIImpactFeedbackGenerator');
+					var impact = new UIImpactFeedbackGenerator();
+					impact.prepare();
+					impact.init(1);
+					impact.impactOccurred();
+				} else {
+					uni.vibrateLong();
+				}
+			} else {
+				uni.vibrateLong();
+			}
+			if (this.$refs.chatingFooterRef) {
+				this.$refs.chatingFooterRef.insertAt(userID, nickname);
+			}
+		},
+		mutipleCheckUpdateHandler({ flag, message }) {
+			this.mutipleCheckVisible = flag;
+			if (message) {
+				this.$store.dispatch('message/updateOneMessage', {
+					message: { ...message, checked: true }
+				});
+			}
+			if (!flag) {
+				const tmpMessageList = [...this.storeHistoryMessageList];
+				tmpMessageList.map((message) => (message.checked = false));
+				this.$store.commit('message/SET_HISTORY_MESSAGE_LIST', tmpMessageList);
+			}
+		},
+		mutipleCheckUpdate() {
+			this.mutipleCheckUpdateHandler({ flag: false });
+		},
+		rtcInvitePrepare(mediaType) {
+			this.chooseCallMediaType(mediaType).then((callMediaType) => {
+				if (this.currentConversation.userID) {
+					this.sendRtcInvite(callMediaType);
+				} else {
+					uni.$u.route('/pages_im/pages/conversation/groupMemberList/index', {
+						type: GroupMemberListTypes.CallInvite,
+						groupID: this.currentConversation.groupID,
+						callMediaType: callMediaType
+					});
+				}
+			});
+		},
+		chooseCallMediaType(mediaType) {
+			return new Promise((resolve, reject) => {
+				if (mediaType) {
+					resolve(mediaType);
+					return;
+				}
+				uni.showActionSheet({
+					itemList: ['语音通话', '视频通话'],
+					success: ({ tapIndex }) => {
+						resolve(tapIndex ? 'video' : 'audio');
+					},
+					fail: () => reject()
+				});
+			});
+		},
+		sendRtcInvite(mediaType, userIDList) {
+			const isVideo = mediaType === 'video';
+			const inviteeUserIDList = userIDList ?? [this.currentConversation.userID];
+			callingModule.startLiveChat(isVideo, inviteeUserIDList, this.currentConversation.groupID, this.storeCurrentUserID);
+		},
+		onNavigationBarButtonTap(e) {
+			if (e?.id === 'more' || e?.index === 0) {
+				this.onEnterSubPage();
+				const isSingle = this.currentConversation?.conversationType === SessionType.Single;
+				const url = isSingle ? '/pages_im/pages/conversation/singleSettings/index' : '/pages_im/pages/conversation/groupSettings/index';
+				uni.navigateTo({ url });
+			}
+		}
+	}
+};
+</script>
+
+<style scoped>
+.inverted {
+	transform: rotate(180deg);
+}
+
+.inverted-content {
+	transform: rotate(180deg);
+}
+
+.chating_container {
+	flex: 1;
+	width: 750rpx;
+	background-color: #F5F7FA;
+	position: relative;
+	flex-direction: column;
+}
+
+.chat_content_wrap {
+	flex: 1;
+	width: 750rpx;
+	position: relative;
+}
+
+.chat-list {
+	flex: 1;
+	width: 750rpx;
+	background-color: #F5F7FA;
+}
+
+.page_loading_container {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: #F5F7FA;
+	align-items: center;
+	justify-content: center;
+	z-index: 999;
+}
+
+.top_loader_container {
+	align-items: center;
+	justify-content: center;
+	height: 40px;
+	width: 750rpx;
+	flex-direction: row;
+}
+
+.top_loader_indicator {
+	height: 20px;
+	width: 20px;
+	color: #888;
+}
+
+.no_more_text {
+	font-size: 14px;
+	color: #999999;
+	padding: 10px 0;
+}
+
+.time_gap_line {
+	padding: 20rpx 0;
+	align-items: center;
+	justify-content: center;
+}
+
+.time_gap_text {
+	font-size: 18px;
+	color: #A5A5A5;
+	background-color: transparent;
+}
+
+.mutiple_action_container {
+	flex-direction: row;
+	border-top-width: 1px;
+	border-top-style: solid;
+	border-top-color: #eaebed;
+	background-color: #f0f2f6;
+	justify-content: space-evenly;
+	padding: 12px 24px;
+}
+
+.action_item {
+	align-items: center;
+	justify-content: center;
+}
+
+.action_icon {
+	width: 48px;
+	height: 48px;
+}
+
+.action_text {
+	margin-top: 6px;
+	font-size: 14px;
+	color: #333;
+}
+
+.action_text_red {
+	color: #ff381f;
+}
+
+.loading_overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: transparent;
+	align-items: center;
+	justify-content: center;
+}
+
+.loading_indicator {
+	width: 30px;
+	height: 30px;
+	color: #999;
+}
+
+.page_loading_container {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	justify-content: center;
+	align-items: center;
+	background-color: #F5F7FA;
+}
+
+.page_loading_indicator {
+	height: 60rpx;
+	width: 60rpx;
+	color: #999;
+}
+
+.page_loading_text {
+	margin-top: 20rpx;
+	font-size: 28rpx;
+	color: #999;
+}
+</style>

+ 3 - 0
pages_im/pages/conversation/chating/index1.vue

@@ -193,6 +193,9 @@ export default {
 			const isVideo = mediaType === 'video';
 			const inviteeUserIDList = userIDList ?? [this.$store.getters.storeCurrentConversation.userID];
 			callingModule.startLiveChat(isVideo, inviteeUserIDList, this.$store.getters.storeCurrentConversation.groupID, this.$store.getters.storeCurrentUserID);
+			console.log("qxj inviteeUserIDList",inviteeUserIDList);
+			console.log("qxj groupID",this.$store.getters.storeCurrentConversation.groupID);
+			console.log("qxj groupID",this.$store.getters.storeCurrentUserID);
 		},
 		chooseCallMediaType(mediaType) {
 			return new Promise((resolve, reject) => {

+ 447 - 0
pages_im/pages/conversation/chating/index11.vue

@@ -0,0 +1,447 @@
+<template>
+	<page-meta :root-font-size="$store.getters.storeRootFontSize"></page-meta>
+	<view :style="{ backgroundColor: '#F5F7FA' }" class="chating_container">
+		<!-- #ifndef APP-PLUS  -->
+		    <chating-header @click="pageClick" ref="chatingHeaderRef" :mutipleCheckVisible="mutipleCheckVisible" @mutipleCheckUpdate="mutipleCheckUpdate" />
+	    <!-- #endif -->
+		
+		<chating-list
+			@click="pageClick"
+			:mutipleCheckVisible="mutipleCheckVisible"
+			ref="chatingListRef"
+			@initSuccess="initSuccess"
+			:menuOutsideFlag="menuOutsideFlag"
+			@closeMenu="closeMenu" />
+		<chating-footer v-if="imType == 1 || imType == 2 || $companyUserIsLogin() || canShowBot"  v-show="!mutipleCheckVisible" ref="chatingFooterRef" :footerOutsideFlag="footerOutsideFlag" @scrollToBottom="scrollToBottom" />
+		<view v-show="mutipleCheckVisible" class="mutiple_action_container">
+			<!-- <view @click="forward('BatchForWard')" class="action_item">
+					<image src="@/static/images/mutiple_batch.png" />
+					<text style="margin-top: 12rpx;"> 逐条转发 </text>
+           </view> -->
+			<view @click="forward('MergeForWard')" class="action_item">
+				<image src="../../../static/images/mutiple_merge.png" />
+				<text style="margin-top: 12rpx">合并转发</text>
+			</view>
+			<view @click="mutipleRemove" class="action_item">
+				<image src="../../../static/images/mutiple_delete.png" />
+				<text style="margin-top: 12rpx; color: #ff381f">删除</text>
+			</view>
+		</view>
+		<u-loading-page :loading="initLoading"></u-loading-page>
+	</view>
+</template>
+
+<script>
+import { mapGetters, mapActions } from 'vuex';
+import IMSDK, { SessionType } from 'openim-uniapp-polyfill';
+import { ContactChooseTypes, GroupMemberListTypes, PageEvents } from '../../../constant';
+import ChatingHeader from './components/ChatingHeader.vue';
+import ChatingFooter from './components/ChatingFooter/index.vue';
+import ChatingList from './components/ChatingList.vue';
+import { markConversationAsRead, parseMessageByType, callingModule } from '../../../util/imCommon';
+import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+export default {
+	mixins: [ MescrollMixin ], //mescroll-body写在子组件时
+	components: {
+		ChatingHeader,
+		ChatingFooter,
+		ChatingList
+	},
+	data() {
+		return {
+			listHeight: 0,
+			footerOutsideFlag: 0,
+			menuOutsideFlag: 0,
+			initLoading: false,
+			mutipleCheckVisible: false,
+			back2Tab: false,
+			timer: null,
+			fontSize:"40px",
+			canShowBot:false,
+		};
+	},
+	computed: {
+	  ...mapGetters(["timStore","storeFriendList","storeCurrentConversation","storeCurrentGroup"]),
+	  // 响应式派生数据
+	  imType() {
+		   return this.timStore?.imType || '';
+	  },
+	  orderId() {
+	  	   return this.timStore?.orderId || '';
+	  },
+	  isSingle() {
+	       return this.storeCurrentConversation.conversationType === SessionType.Single;
+	  },
+	  isNotify() {
+	     	return this.storeCurrentConversation.conversationType === SessionType.Notification;
+	  },
+	  groupMemberCount() {
+	  	return `(${this.storeCurrentGroup?.memberCount ?? 0})`;
+	  },
+	  getGroupAnnouncementContent() {
+	  	if (this.showGroupAnnouncement) {
+	  		return this.$store.getters.storeCurrentGroup.notification;
+	  	}
+	  	return '';
+	  },
+	},
+	onLoad(options) {
+		this.setPageListener();
+		this.checkCalling();
+		if (options?.back2Tab) {
+			this.back2Tab = JSON.parse(options.back2Tab);
+		}
+		
+		// 尝试从参数恢复会话信息,解决nvue跳转时store可能未同步的问题
+		if (options?.conversationID) {
+			const currentID = this.$store.getters.storeCurrentConversation?.conversationID;
+			console.log("qxj currentID",currentID);
+			if (currentID !== options.conversationID) {
+				const list = this.$store.getters.storeConversationList || [];
+				const target = list.find(item => item.conversationID === options.conversationID);
+				if (target) {
+					this.$store.commit("conversation/SET_CURRENT_CONVERSATION", target);
+				}
+			}
+		}
+
+		let userID = this.$store.getters.storeCurrentConversation?.userID || '';
+		//this.userID = userID.substring(1);
+		//this.isUser = userID.indexOf('U') != -1;
+		if (userID) {
+			this.canShowBot = this.isUserInFriendList(userID, this.storeFriendList);
+		}
+		
+	},
+	onShow() {
+		
+	},
+	onReady() {
+		let title = this.$store.getters.storeCurrentConversation?.showName || '';
+		if(!this.isSingle && !this.isNotify){
+			title=title+this.groupMemberCount;
+		}
+		uni.setNavigationBarTitle({ title });
+	},
+	onUnload() {
+		this.disposePageListener();
+		markConversationAsRead(
+		    {...this.$store.getters.storeCurrentConversation},
+			true
+		);
+		this.resetConversationState();
+		this.resetMessageState();
+		if (this.timer) {
+			clearInterval(this.timer);
+		}
+	},
+	
+	methods: {
+		...mapActions('message', ['resetMessageState', 'deleteMessages']),
+		...mapActions('conversation', ['resetConversationState']),
+		scrollToBottom(isRecv = false) {
+			//this.$refs.chatingListRef.scrollToAnchor(`auchor${clientMsgID}`, isRecv);
+			//this.$refs.chatingListRef.scrollToBottom(false, isRecv);
+			this.$refs.chatingListRef.scrollToBottom();
+		},
+		nativeCallEndHandler() {
+			if (typeof plus === 'undefined') {
+				return;
+			}
+			if (!this.$store.getters.storeCurrentConversation.groupID) {
+				return;
+			}
+			IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
+				if (this.$refs.chatingHeaderRef) {
+					if (data.invitation) {
+						this.$refs.chatingHeaderRef.updateCallingData(data);
+					} else {
+						this.$refs.chatingHeaderRef.updateCallingData(null);
+					}
+				}
+			});
+		},
+		checkCalling() {
+			if (typeof plus === 'undefined') {
+				return;
+			}
+			if (!this.$store.getters.storeCurrentConversation.groupID) {
+				return;
+			}
+			if (this.timer) {
+				clearInterval(this.timer);
+			}
+			this.nativeCallEndHandler();
+			this.timer = setInterval(() => {
+				this.nativeCallEndHandler();
+			}, 10000);
+		},
+		mutipleRemove() {
+			const checkedMessages = this.$store.getters.storeHistoryMessageList.filter((message) => message.checked);
+			if (checkedMessages.length === 0) {
+				uni.$u.toast('请先选择要操作的消息');
+				return;
+			}
+			let count = 0;
+			checkedMessages.map((message) => {
+				IMSDK.asyncApi(IMSDK.IMMethods.DeleteMessage, IMSDK.uuid(), {
+					conversationID: this.$store.getters.storeCurrentConversation.conversationID,
+					clientMsgID: message.clientMsgID
+				}).then(() => {
+					count++;
+					if (count === checkedMessages.length) {
+						this.deleteMessages(checkedMessages);
+						this.mutipleCheckUpdateHandler(false);
+						uni.$u.toast('删除成功');
+					}
+				});
+			});
+		},
+		forward(type) {
+			const checkedMessages = this.$store.getters.storeHistoryMessageList.filter((message) => message.checked);
+			if (checkedMessages.length === 0) {
+				uni.$u.toast('请先选择要操作的消息');
+				return;
+			}
+			const currentConversation = this.$store.getters.storeCurrentConversation;
+			const title = currentConversation.groupID ? `群聊${currentConversation.showName}的聊天记录` : `和${currentConversation.showName}的聊天记录`;
+
+			const mergeInfo = {
+				messageList: [...checkedMessages],
+				summaryList: checkedMessages.map((message) => `${message.senderNickname}:${parseMessageByType(message)}`),
+				title
+			};
+			uni.navigateTo({
+				url: `/pages_im/pages/common/contactChoose/index?type=${type}&mergeInfo=${encodeURIComponent(JSON.stringify(mergeInfo))}`
+			});
+		},
+		closeMenu() {
+			this.getEl('.message_menu_container').then((res) => {
+				if (res) {
+					this.menuOutsideFlag += 1;
+				}
+			});
+		},
+		async pageClick(e) {
+			this.getEl('.message_menu_container').then((res) => {
+				if (res) {
+					this.menuOutsideFlag += 1;
+				}
+			});
+			this.footerOutsideFlag += 1;
+		},
+		getEl(el) {
+			return new Promise((resolve) => {
+				const query = uni.createSelectorQuery().in(this);
+				query.select(el)
+					.boundingClientRect((data) => {
+						resolve(data);
+					}).exec();
+			});
+		},
+		initSuccess() {
+			this.initLoading = false;
+		},
+		async getCheckedUsers(data) {
+			this.$refs.chatingFooterRef.customEditorCtx.undo();
+			for (const item of data) {
+				this.$refs.chatingFooterRef.insertAt(item.userID, item.nickname);
+				await uni.$u.sleep(200);
+			}
+		},
+		sendRtcInvite(mediaType, userIDList) {
+			const isVideo = mediaType === 'video';
+			const inviteeUserIDList = userIDList ?? [this.$store.getters.storeCurrentConversation.userID];
+			callingModule.startLiveChat(isVideo, inviteeUserIDList, this.$store.getters.storeCurrentConversation.groupID, this.$store.getters.storeCurrentUserID);
+		},
+		chooseCallMediaType(mediaType) {
+			return new Promise((resolve, reject) => {
+				resolve(mediaType);
+				if (mediaType) {
+					resolve(mediaType);
+					return;
+				}
+				// uni.showActionSheet({
+				// 	itemList: ['语音通话', '视频通话'],
+				// 	success: ({ tapIndex }) => {
+				// 		resolve(tapIndex ? 'video' : 'audio');
+				// 	},
+				// 	fail: () => reject()
+				// });
+			});
+		},
+		// page event
+		typingHandler() {
+			if (this.$refs.chatingHeaderRef) {
+				this.$refs.chatingHeaderRef.updateTyping();
+			}
+		},
+		onlineCheckHandler() {
+			if (this.$refs.chatingHeaderRef) {
+				this.$refs.chatingHeaderRef.checkOnline();
+			}
+		},
+		atSomeOneHandler({ userID, nickname }) {
+			if (typeof plus !== 'undefined') {
+				if (plus.os.name == 'iOS') {
+					var UIImpactFeedbackGenerator = plus.ios.importClass('UIImpactFeedbackGenerator');
+					var impact = new UIImpactFeedbackGenerator();
+					impact.prepare();
+					impact.init(1);
+					impact.impactOccurred();
+				} else {
+					uni.vibrateLong();
+				}
+			} else {
+				uni.vibrateLong();
+			}
+			if (this.$refs.chatingFooterRef) {
+				this.$refs.chatingFooterRef.insertAt(userID, nickname);
+			}
+		},
+		mutipleCheckUpdateHandler({ flag, message }) {
+			this.mutipleCheckVisible = flag;
+			if (message) {
+				this.$store.dispatch('message/updateOneMessage', {
+					message: {
+						...message,
+						checked: true
+					}
+				});
+			}
+			if (!flag) {
+				const tmpMessageList = [...this.$store.getters.storeHistoryMessageList];
+				tmpMessageList.map((message) => (message.checked = false));
+				this.$store.commit('message/SET_HISTORY_MESSAGE_LIST', tmpMessageList);
+			}
+		},
+		rtcInvitePrepare(mediaType) {
+			this.chooseCallMediaType(mediaType).then((callMediaType) => {
+				if (this.$store.getters.storeCurrentConversation.userID) {
+					this.sendRtcInvite(callMediaType);
+				} else {
+					uni.$u.route('/pages_im/pages/conversation/groupMemberList/index', {
+						type: GroupMemberListTypes.CallInvite,
+						groupID: this.$store.getters.storeCurrentConversation.groupID,
+						callMediaType: callMediaType
+					});
+				}
+			});
+		},
+		setPageListener() {
+			IMSDK.subscribe('onRecvC2CReadReceipt', ({ data: receiptList }) => {
+				console.log('onRecvC2CReadReceipt', receiptList);
+			});
+			uni.$on(PageEvents.TypingUpdate, this.typingHandler);
+			uni.$on(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
+			uni.$on(PageEvents.ScrollToBottom, this.scrollToBottom);
+			uni.$on(PageEvents.AtSomeOne, this.atSomeOneHandler);
+			uni.$on(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
+			uni.$on(PageEvents.RtcCall, this.rtcInvitePrepare);
+			uni.$on(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
+		},
+		disposePageListener() {
+			uni.$off(PageEvents.TypingUpdate, this.typingHandler);
+			uni.$off(PageEvents.OnlineStateCheck, this.onlineCheckHandler);
+			uni.$off(PageEvents.ScrollToBottom, this.scrollToBottom);
+			uni.$off(PageEvents.AtSomeOne, this.atSomeOneHandler);
+			uni.$off(PageEvents.MutipleCheckUpdate, this.mutipleCheckUpdateHandler);
+			uni.$off(PageEvents.RtcCall, this.rtcInvitePrepare);
+			uni.$off(PageEvents.NativeCallEnd, this.nativeCallEndHandler);
+		},
+		mutipleCheckUpdate() {
+			this.mutipleCheckUpdateHandler({ flag: false });
+		},
+		isUserInFriendList(sorUserId,storeList) {
+		    if (!storeList || !Array.isArray(storeList)) {
+		        return false;
+		    }
+		    return storeList.some(item => item.userID === sorUserId);
+		}
+
+	},
+	// 原生导航栏按钮点击事件(titleNView.buttons)
+	onNavigationBarButtonTap(e) {
+		if (e?.id === 'more' || e?.index === 0) {
+			const isSingle = this.$store.getters.storeCurrentConversation?.conversationType === SessionType.Single;
+			const url = isSingle ? '/pages_im/pages/conversation/singleSettings/index' : '/pages_im/pages/conversation/groupSettings/index';
+			uni.navigateTo({ url });
+		}
+	},
+    onBackPress() {
+		if (this.mutipleCheckVisible) {
+			this.mutipleCheckUpdateHandler({ flag: false });
+			return true;
+		} else {
+			uni.switchTab({
+				url: '/pages_im/pages/conversation/conversationList/index'
+			});
+			return true;
+		}
+    },
+	beforeDestroy() {
+		uni.switchTab({
+			url: '/pages_im/pages/conversation/conversationList/index'
+		});
+	}
+	
+};
+</script>
+
+<style lang="scss" scoped>
+.chating_container {
+	@include colBox(false);
+	height: 100vh;
+		width: 100%;
+		overflow-y: hidden;
+		 touch-action: none; // 禁止触摸时的默认滚动/回弹行为
+		box-sizing: border-box;
+		background-repeat: no-repeat;
+		background-size: 100%;
+		position: relative;
+		z-index: 2;
+		background: #f5f7fa;
+	.watermark {
+		font-size: 16px; /* 水印文字大小 */
+		color: rgba(0, 0, 0, 0.2); /* 水印文字颜色,使用透明度控制可见度 */
+		position: absolute; /* 水印相对定位 */
+		transform: rotate(-45deg);
+		pointer-events: none; /* 防止水印文字干扰交互 */
+
+		/* 为不同的水印元素设置不同的偏移,以避免重叠 */
+		// transform-origin: top right;
+		// margin-top: 20px;
+		// margin-right: 20px;
+	}
+
+	// ::before {
+	//   content: "Your Watermark Text"; /* 替换为你想要的水印文字 */
+	//   font-size: 16px; /* 水印文字大小 */
+	//   color: rgba(0, 0, 0, 0.2); /* 水印文字颜色,使用透明度控制可见度 */
+	//   position: absolute; /* 水印相对定位 */
+	//   top: 20px; /* 距离顶部的距离 */
+	//   right: 20px; /* 距离右侧的距离 */
+	//   transform: rotate(-45deg); /* 将水印旋转为倾斜状态 */
+	//   pointer-events: none; /* 防止水印文字干扰交互 */
+	//   z-index: -1; /* 将水印置于底层 */
+	// }
+
+	.mutiple_action_container {
+		display: flex;
+		border-top: 1px solid #eaebed;
+		background: #f0f2f6;
+		justify-content: space-evenly;
+		padding: 12px 24px;
+
+		.action_item {
+			@include centerBox();
+			flex-direction: column;
+
+			image {
+				width: 48px;
+				height: 48px;
+			}
+		}
+	}
+}
+</style>

BIN
pages_im/pages/conversation/chating1.zip


+ 472 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/ChatingActionBar.vue

@@ -0,0 +1,472 @@
+<template>
+	<view class="chat_action_bar">
+		<u-row class="action_row">
+			<u-col v-for="item in actionList" :key="item.idx" @click="actionClick(item)" span="3">
+				<view class="action_item">
+					<image :src="item.icon" alt="" srcset="" />
+					<text class="action_item_title">{{ item.title }}</text>
+				</view>
+			</u-col>
+		</u-row>
+	</view>
+</template>
+
+<script>
+import { mapGetters, mapActions } from 'vuex';
+import { ChatingFooterActionTypes, ContactChooseTypes, PageEvents } from '../../../../../constant';
+import { AmapWebKey } from '../../../../../common/config';
+import IMSDK, { IMMethods } from 'openim-uniapp-polyfill';
+import { getPurePath } from '../../../../../util/common';
+import { callingModule } from '../../../../../util/imCommon';
+import { premissionCheck } from "@/js_sdk/wa-permission/permission.js"
+// import { gotoAppPermissionSetting,requestAndroidPermission } from "../../../../../util/permission";
+
+export default {
+	components: {},
+	data() {
+		return {
+			userID: '',
+			isUser: false,
+			isDoctor:false,
+			readFilePermission:false,
+			actionList: [
+				{
+					idx: 0,
+					type: ChatingFooterActionTypes.takePhoto,
+					title: '拍照',
+					icon: require('../../../../../static/images/new/take_pictures_icon.png')
+				},
+				{
+					idx: 1,
+					type: ChatingFooterActionTypes.Album,
+					title: '相册',
+					icon: require('../../../../../static/images/new/album_icon.png')
+				},
+				{
+					idx: 2,
+					type: ChatingFooterActionTypes.Camera,
+					title: '视频',
+					icon: require('../../../../../static/images/new/video_icon.png')
+				},
+				{
+					idx: 3,
+					type: ChatingFooterActionTypes.pickCamera,
+					title: '录像',
+					icon: require('../../../../../static/images/new/picture_recording_icon.png')
+				}
+				,{
+				  idx: 4,
+				  type: ChatingFooterActionTypes.Call,
+				  title: "语音通话",
+				  icon: require("../../../../../static/images/new/voice_call_icon.png"),
+				},
+				{
+					idx: 5,
+					type: ChatingFooterActionTypes.VideoCall,
+					title: "视频通话",
+					icon: require("../../../../../static/images/new/video_call_icon.png"),
+				 }
+				//{
+				//         idx: 6,
+				//         type: ChatingFooterActionTypes.File,
+				//         title: "问诊订单",
+				//         icon: require("../../../../../static/images/chating_action_file.png"),
+				// },
+				// {
+				//   idx: 4,
+				//   type: ChatingFooterActionTypes.Card,
+				//   title: "名片",
+				//   icon: require("../../../../../static/images/chating_action_card.png"),
+				// },
+				// {
+				//   idx: 12,
+				//   type: ChatingFooterActionTypes.Location,
+				//   title: "位置",
+				//   icon: require("../../../../../static/images/chating_action_location.png"),
+				// }
+			]
+		};
+	},
+	computed: {
+		...mapGetters(['timStore']),
+		imType() {
+			return this.timStore?.imType ?? '';
+		},
+		orderId() {
+			return this.timStore?.orderId ?? '';
+		},
+		orderType() {
+			return this.timStore?.orderType ?? '';
+		}
+	},
+	mounted() {
+		let userID = this.$store.getters.storeCurrentConversation.userID;
+		this.userID = userID.substring(1);
+		this.isUser = userID.indexOf('U') != -1;
+		this.isDoctor=userID.indexOf('D') != -1;
+		if (this.$companyUserIsLogin() && this.isUser) {
+			// this.actionList.push({
+			// 	idx: 4,
+			// 	type: ChatingFooterActionTypes.Call,
+			// 	title: '语音通话',
+			// 	icon: require('../../../../../static/images/audio-calling.svg')
+			// });
+			// this.actionList.push({
+			// 	idx: 5,
+			// 	type: ChatingFooterActionTypes.VideoCall,
+			// 	title: '视频通话',
+			// 	icon: require('../../../../../static/images/chating_action_call.png')
+			// });
+			
+			// this.actionList.push({
+			// 	idx: 6,
+			// 	type: ChatingFooterActionTypes.Order,
+			// 	title: '问诊订单',
+			// 	icon: require('../../../../../static/images/new/consultation_order_icon.png')
+			// });
+			// this.actionList.push({
+			// 	idx: 8,
+			// 	type: ChatingFooterActionTypes.StoreOrder,
+			// 	title: '药品订单',
+			// 	icon: require('../../../../../static/images/new/drug_orders_icon.png')
+			// });
+			// this.actionList.push({
+			// 	idx: 9,
+			// 	type: ChatingFooterActionTypes.Package,
+			// 	title: '疗法',
+			// 	icon: require('../../../../../static/images/new/therapy_icon.png')
+			// });
+			
+			// this.actionList.push({
+			// 	idx: 10,
+			// 	type: ChatingFooterActionTypes.CouponPackage,
+			// 	title: '私域疗法券',
+			// 	icon: require('../../../../../static/images/new/therapy_voucher_icon.png')
+			// });
+			
+			// this.actionList.push({
+			// 	idx: 11,
+			// 	type: ChatingFooterActionTypes.InquirySelect,
+			// 	title: '会诊',
+			// 	icon: require('../../../../../static/images/new/consultation_doctors_icon.png')
+			// });
+			
+			// this.actionList.push({
+			// 	idx: 12,
+			// 	type: ChatingFooterActionTypes.Course,
+			// 	title: '课程',
+			// 	icon: require('../../../../../static/images/new/course_icon.png')
+			// });
+			
+			return;
+		}
+
+		//if((this.imType==1&&this.orderType==2)||this.imType==2){  //语音通话
+			// this.actionList.push({
+			// 	idx: 4,
+			// 	type: ChatingFooterActionTypes.Call,
+			// 	title: '语音通话',
+			// 	icon: require('../../../../../static/images/audio-calling.svg')
+			// });
+		//}
+		//if((this.imType==1&&this.orderType==2)||this.imType==2){  //视频通话
+			// this.actionList.push({
+			// 	idx: 5,
+			// 	type: ChatingFooterActionTypes.VideoCall,
+			// 	title: '视频通话',
+			// 	icon: require('../../../../../static/images/chating_action_call.png')
+			// });
+		//}
+		
+		
+	    if(this.isDoctor){
+			if (this.imType == 1) {
+				//问诊订单
+				this.actionList.push({
+					idx: 6,
+					type: ChatingFooterActionTypes.Order,
+					title: '问诊订单',
+					icon: require('../../../../../static/images/new/consultation_order_icon.png')
+				});
+			}
+			if (this.imType == 2) {
+				//视频通话
+				this.actionList.push({
+					idx: 7,
+					type: ChatingFooterActionTypes.Follow,
+					title: '随访单',
+					icon: require('../../../../../static/images/new/consultation_order_icon.png')
+				});
+				this.actionList.push({
+					idx: 8,
+					type: ChatingFooterActionTypes.StoreOrder,
+					title: '药品订单',
+					icon: require('../../../../../static/images/new/drug_orders_icon.png')
+				});
+			}
+		}
+		
+		
+	},
+
+	methods: {
+		async actionClick(action) {
+			switch (action.type) {
+				case ChatingFooterActionTypes.takePhoto:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Camera, index: 0 });
+					break;
+				case ChatingFooterActionTypes.Album:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Album, index: 0 });
+					break;
+				case ChatingFooterActionTypes.Camera:
+				    if (uni.$u.os() != 'ios') {
+				    	this.requestPressmition();
+				    	if(!this.readFilePermission){
+				    		return;
+				    	}
+				    }
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Album, index: 1 });
+					break;
+				case ChatingFooterActionTypes.pickCamera:
+					if (uni.$u.os() != 'ios') {
+						this.requestPressmition();
+						if(!this.readFilePermission){
+							return;
+						}
+					}
+					this.$emit('prepareMediaMessage', { type: ChatingFooterActionTypes.Camera, index: 1 });
+					break;
+				case ChatingFooterActionTypes.Call:
+					if (!this.$store.getters.storeCurrentConversation.groupID) {
+						uni.$emit(PageEvents.RtcCall, 'audio');
+						return;
+					}
+					IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
+						if (data.invitation) {
+							uni.showModal({
+								title: '提示',
+								content: '群通话进行中,是否直接加入?',
+								confirmText: '确认',
+								cancelText: '取消',
+								success: (res) => {
+									if (res.confirm) {
+										callingModule.joinRoomLiveChat(data);
+									}
+								}
+							});
+						} else {
+							uni.$emit(PageEvents.RtcCall, 'audio');
+						}
+					});
+					break;
+				case ChatingFooterActionTypes.VideoCall:
+					if (!this.$store.getters.storeCurrentConversation.groupID) {
+						uni.$emit(PageEvents.RtcCall, 'video');
+						return;
+					}
+					IMSDK.asyncApi('signalingGetRoomByGroupID', IMSDK.uuid(), this.$store.getters.storeCurrentConversation.groupID).then(({ data }) => {
+						if (data.invitation) {
+							uni.showModal({
+								title: '提示',
+								content: '群通话进行中,是否直接加入?',
+								confirmText: '确认',
+								cancelText: '取消',
+								success: (res) => {
+									if (res.confirm) {
+										callingModule.joinRoomLiveChat(data);
+									}
+								}
+							});
+						} else {
+							uni.$emit(PageEvents.RtcCall, 'video');
+						}
+					});
+					break;
+
+				case ChatingFooterActionTypes.Order:
+					{
+						if (this.$companyUserIsLogin()) {
+							uni.navigateTo({
+								url: '/pages/company/inquiryOrderIMList?userId=' + this.userID
+							});
+						} else {
+							console.log("qxj orderId:", this.timStore.orderId)
+							uni.navigateTo({
+								url: '/pages/store/inquiryOrderDetails?orderId=' + this.timStore.orderId
+							});
+						}
+					}
+					break;
+				case ChatingFooterActionTypes.Follow:
+					uni.navigateTo({
+						url: '/pages/user/followDetails?followId=' + this.timStore.followId
+					});
+					break;
+				case ChatingFooterActionTypes.StoreOrder:
+					{
+						if (this.$companyUserIsLogin()) {
+							uni.navigateTo({
+								url: '/pages/company/storeOrderList?userId=' + this.userID
+							});
+						} else {
+							uni.navigateTo({
+								url: '/pages/store/storeOrderDetail?orderId=' + this.timStore.orderId
+							});
+						}
+					}
+					break;
+
+				case ChatingFooterActionTypes.Package:  //疗法
+					{
+						if (this.$companyUserIsLogin()) {
+							uni.navigateTo({
+								url: '/pages/company/packageList?isIM=1'
+							});
+						}
+					}
+				break;
+					
+				case ChatingFooterActionTypes.CouponPackage:
+					{
+						if (this.$companyUserIsLogin()) {
+							uni.navigateTo({
+								url: '/pages/company/couponList?couponType=5&isIM=1'
+							});
+						}
+					}
+				break;
+					
+				case ChatingFooterActionTypes.InquirySelect:
+					{
+						if (this.$companyUserIsLogin()) {
+							let companyId=uni.getStorageSync('companyId');
+							let companyUserId=uni.getStorageSync('companyUserId');	
+							uni.navigateTo({
+								url: '/pages/store/inquirySelectType?companyId='+companyId+'&companyUserId='+companyUserId+'&isIM=1'
+							});
+						}
+					}
+				break;
+				case ChatingFooterActionTypes.Course:
+					if (this.$companyUserIsLogin()) {
+						uni.navigateTo({
+							url: '/pages_im/pages/conversation/course/courseList?userId=' + this.userID
+						});
+					}
+				break;
+
+				case ChatingFooterActionTypes.Card:
+					uni.navigateTo({
+						url: `/pages_im/pages/common/contactChoose/index?type=${ContactChooseTypes.Card}`
+					});
+					break;
+
+				case ChatingFooterActionTypes.File:
+					if (uni.$u.os() != 'ios') {
+						this.requestPressmition();
+						if(!this.readFilePermission){
+							return;
+						}
+					}
+					IMSDK.pickFile().then(async (path) => {
+						console.log(path);
+						const idx = path.lastIndexOf('/');
+						const fileName = path.slice(idx + 1);
+						const message = await IMSDK.asyncApi(IMMethods.CreateFileMessageFromFullPath, IMSDK.uuid(), {
+							filePath: getPurePath(path),
+							fileName
+						});
+						this.$emit('sendMessage', message);
+					});
+					break;
+				case ChatingFooterActionTypes.Location:
+					uni.chooseLocation({
+						success: async (res) => {
+							if (res) {
+								const options = {
+									name: res.name,
+									latng: `${res.latitude},${res.longitude}`,
+									addr: res.address,
+									city: res.address,
+									module: 'locationPicker',
+									latitude: res.latitude,
+									longitude: res.longitude,
+									url: `https://restapi.amap.com/v3/staticmap?size=600*300&markers=-1,https://cache.amap.com/lbs/static/cuntom_marker1.png,0:${res.longitude},${res.latitude}&key=${AmapWebKey}`
+								};
+								const message = await IMSDK.asyncApi(IMMethods.CreateLocationMessage, IMSDK.uuid(), {
+									description: JSON.stringify(options),
+									longitude: res.longitude,
+									latitude: res.latitude
+								});
+								this.$emit('sendMessage', message);
+							} else {
+								uni.$u.toast('获取位置失败');
+							}
+						},
+						fail: ({ errMsg }) => {
+							if (!errMsg.includes('cancel')) {
+								uni.$u.toast('获取位置失败');
+							}
+						}
+					});
+					break;
+				default:
+					break;
+			}
+		},
+	
+	    async requestPressmition(){
+	    	let result = await premissionCheck("EXTERNAL_STORAGE");
+	    	console.log("premission result:"+result);
+	    	if(result == 1) {
+	    		this.readFilePermission=true;
+	    	}
+	    }
+	
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.chat_action_bar {
+	position: relative;
+	background: #f0f2f6;
+	padding: 24rpx 36rpx;
+
+	.action_row {
+		flex-wrap: wrap;
+		margin-bottom: 24rpx;
+	}
+
+	.action_item {
+		@include centerBox();
+		flex-direction: column;
+		margin-top: 28rpx;
+		image {
+			width: 58rpx;
+			height: 58rpx;
+			background-color: #fff;
+			border-radius: 28rpx;
+			padding:28rpx;
+		}
+
+		&_title {
+			font-size: 24rpx;
+			color: #999;
+			margin-top: 16rpx;
+		}
+	}
+}
+</style>

+ 51 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/ChatingEmojiBar.vue

@@ -0,0 +1,51 @@
+<template>
+  <view class="chat_emoji_bar">
+    <u-row class="emoji_row">
+      <u-col
+        @click="clickEmoji(emoji)"
+        v-for="emoji in emojiList"
+        :key="emoji"
+        :id="emoji"
+        span="2"
+        style="display: flex;justify-content: center;align-items: center;">
+        <text style="font-size: 20px;margin-bottom: 6px;">{{ emoji }}</text>
+      </u-col>
+    </u-row>
+  </view>
+</template>
+
+<script>
+import emojis from "../../../../../common/emojis.js";
+export default {
+  components: {},
+  data() {
+    return {};
+  },
+  computed: {
+    emojiList() {
+      return emojis;
+    },
+  },
+  mounted() {},
+  methods: {
+    clickEmoji(emoji) {
+      this.$emit("insertEmoji", emoji);
+    },
+    backspace() {
+      this.$emit("backspace");
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.chat_emoji_bar {
+  padding: 24rpx 36rpx;
+  position: relative;
+
+  .emoji_row {
+    flex-wrap: wrap;
+    margin-bottom: 12rpx;
+  }
+}
+</style>

+ 441 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/CustomEditor.vue

@@ -0,0 +1,441 @@
+<template>
+	<view
+		:insertImageFlag="insertImageFlag"
+		:insertAtFlag="insertAtFlag"
+		:insertEmojiFlag="insertEmojiFlag"
+		:blurFlag="blurFlag"
+		:change:insertEmojiFlag="input.insertEmojiFlagUpdate"
+		:change:insertAtFlag="input.insertAtFlagUpdate"
+		:change:insertImageFlag="input.insertImageFlagUpdate"
+		:change:blurFlag="input.blurFlagUpdate"
+		class="editor_wrap"
+        :class="{'blur-trigger': blurFlag % 2 === 1}"
+        @touchstart="input.handleTouchStart"
+        @click="input.handleForceUpdate"
+        >
+		<editor :placeholder="placeholder" ref="editor2" id="editor2" @ready="editorReady" @focus="editorFocus" @blur="editorBlur" @input="editorInput" />
+		<view class="canvas_container">
+			<canvas v-if="canvasData.show" canvas-id="atCanvas" :style="{ width: canvasData.width }" id="atCanvas"></canvas>
+		</view>
+	</view>
+</template>
+
+<script>
+import { html2Text } from '../../../../../util/common';
+export default {
+	props: {
+		placeholder: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			editorCtx: null,
+			canvasData: {
+				width: 0,
+				show: false
+			},
+			imageData: {},
+			insertImageFlag: null,
+			insertAtFlag: null,
+			lastStr: '',
+			emojiData: '',
+			insertEmojiFlag: null,
+			blurFlag: 0,
+            inputTimeout: null // 用于防抖
+		};
+	},
+    mounted() {
+        
+    },
+	methods: {
+		editorReady() {
+			uni.createSelectorQuery()
+				.select('#editor2')
+				.context((res) => {
+					this.$emit('ready', res);
+					this.editorCtx = res.context;
+				}).exec();
+		},
+		insertImage(imageData) {
+			this.imageData = imageData;
+			this.insertImageFlag = true;
+		},
+		insertEmoji(emoji) {
+			this.$nextTick(() => {
+				this.insertEmojiFlag = true;
+				this.emojiData = emoji;
+			});
+		},
+		internalInsertEmoji() {
+			this.editorCtx.insertText({
+				text: this.emojiData,
+				complete: () => {
+					this.insertEmojiFlag = false;
+					this.emojiData = null;
+				}
+			});
+		},
+		internalInsertImage() {
+			this.editorCtx.insertImage({
+				...this.imageData,
+				complete: () => {
+					this.insertImageFlag = false;
+					this.insertAtFlag = null;
+					// plus.key.showSoftKeybord()
+					// this.setDraftTextItem();
+				}
+			});
+		},
+		internalInsertAtEl({ text, width, userID, nickname }) {
+			this.canvasData.width = `${width}px`;
+			this.canvasData.show = true;
+			setTimeout(() => {
+				const ctx = uni.createCanvasContext('atCanvas');
+				const fontSize = 14;
+				ctx.setFontSize(fontSize);
+				ctx.setFillStyle('#3e44ff');
+				ctx.fillText(text, 0, 16);
+				ctx.draw();
+				this.canvasToTempFilePath(userID, nickname, width);
+			}, 20);
+		},
+		createCanvasData(userID, nickname) {
+			this.$nextTick(() => {
+				this.insertAtFlag = {
+					userID,
+					nickname
+				};
+			});
+		},
+		canvasToTempFilePath(sendID, senderNickname, width) {
+			uni.canvasToTempFilePath({
+				canvasId: 'atCanvas',
+				success: (res) => {
+					this.insertImage({
+						src: res.tempFilePath,
+						width,
+						height: '20px',
+						data: {
+							sendID,
+							senderNickname
+						},
+						extClass: 'at_el'
+					});
+					uni.createCanvasContext('atCanvas').clearRect();
+				}
+			});
+		},
+		editorFocus() {
+			this.$emit('focus');
+		},
+		editorBlur() {
+			console.log("qxj editorBlur");
+			this.$emit('blur');
+		},
+		editorInput(e) {
+			let str = e.detail.html;
+			const oldArr = (this.lastStr ?? '').split('');
+			let contentStr = str;
+			oldArr.forEach((str) => {
+				contentStr = contentStr.replace(str, '');
+			});
+			contentStr = html2Text(contentStr);
+			if (contentStr === '@') {
+				this.$emit('tryAt');
+			}
+
+            // 实时检测空内容并重置
+            // 如果内容只有换行符(用户删除了所有文本但留下了空行),强制重置为标准空状态
+            const text = e.detail.text.replace(/\n/g, '');
+            const hasImage = str.indexOf('<img') !== -1;
+            if (!hasImage && text.trim().length === 0 && str !== '<p><br></p>') {
+                // 只有当内容不是标准的空状态时才重置,避免循环触发
+                // 注意:这里不能直接 setContents,因为会导致光标跳动
+                // 只能在用户明显清空时做处理,或者在 blur 时处理
+                // 但为了防止高度异常,我们可以尝试在这里做轻量级检查
+            }
+
+            // 防抖发送 input 事件,避免频繁触发父组件逻辑导致桥接阻塞
+            if (this.inputTimeout) {
+                clearTimeout(this.inputTimeout);
+            }
+            this.inputTimeout = setTimeout(() => {
+                this.$emit('input', e);
+            }, 100);
+            
+			this.lastStr = e.detail.html;
+		},
+		focus() {
+			this.$refs.editor2.focus();
+		},
+		blur() {
+			console.log("qxj blur");
+			// 1. 立即触发 renderjs 更新(负责视觉层重置)
+            // 直接同步修改数据,不再使用 setTimeout,争取第一时间触发
+            this.blurFlag = this.blurFlag + 1;
+            console.log("1111", this.blurFlag);
+
+            // 2. 先执行内容清理,避免空行堆积导致的高度异常
+            // 不等待回调,直接尝试清理(如果编辑器支持同步清理更好,但这里只能通过 context)
+            if (this.editorCtx) {
+                 this.editorCtx.getContents({
+                     success: (res) => {
+                         const html = res.html;
+                         const text = res.text.replace(/\n/g, '');
+                         const hasImage = html.indexOf('<img') !== -1;
+                         // 增强判断:只要没有图片,且文本去空后长度为0,就认为是空
+                         if (!hasImage && text.trim().length === 0) {
+                             // 关键:在这里直接重置
+                             this.editorCtx.setContents({
+                                 html: '<p><br></p>'
+                             });
+                             // 同时清空 renderjs 的内容
+                             // 但由于无法直接通信,只能依赖 renderjs 自身的检测
+                         }
+                     }
+                 });
+            }
+
+			// 3. 恢复原生失焦
+			if (this.editorCtx && this.editorCtx.blur) {
+				this.editorCtx.blur();
+			}
+		}
+		
+	}
+};
+</script>
+
+<script module="input" lang="renderjs">
+export default {
+    data() {
+        return {
+            editorInstance: null,
+            timer: null
+        }
+    },
+    mounted() {
+		// 在 mounted 中提前获取 editor 引用,避免每次查询
+		this.editorInstance = this.$el.querySelector('.ql-editor');
+		// 监听键盘收起或输入框失焦事件
+		// 使用 focusout 并开启捕获阶段,确保能捕获到 blur 事件
+		// 直接监听根元素,而不是 editorInstance,防止 editorInstance 为空或被替换
+		this.$el.addEventListener('focusout', this.onEditorBlur, true);
+		// 同时监听 input 事件,实时防止空行堆积
+		if (this.editorInstance) {
+			this.editorInstance.addEventListener('input', this.onEditorInput);
+		}
+	},
+	beforeDestroy() {
+		this.$el.removeEventListener('focusout', this.onEditorBlur, true);
+		if (this.editorInstance) {
+			this.editorInstance.removeEventListener('input', this.onEditorInput);
+		}
+	},
+	methods: {
+		onEditorInput() {
+			const editor = this.editorInstance || this.$el.querySelector('.ql-editor');
+			if (editor) {
+				// 实时检查:如果只有换行符,强制清空
+				if (editor.textContent.trim() === '' && !editor.innerHTML.includes('<img')) {
+					// 只有当高度明显异常时才干预,避免太激进
+					if (editor.offsetHeight > 50) { // 假设单行高度约30-40px
+						console.log('renderjs realtime clean triggered');
+						editor.innerHTML = '<p><br></p>';
+					}
+				}
+			}
+		},
+		onEditorBlur() {
+			console.log('renderjs direct focusout detected');
+            // 在 renderjs 层直接清理内容,不等待 JS 通知的延迟
+			const editor = this.editorInstance || this.$el.querySelector('.ql-editor');
+            if (editor) {
+                // 如果内容只包含换行符,强制清空
+                if (editor.textContent.trim() === '' && !editor.innerHTML.includes('<img')) {
+                    console.log('renderjs forcing content clean');
+                    editor.innerHTML = '<p><br></p>';
+                }
+				
+                // 使用 requestAnimationFrame 确保在下一帧渲染前执行重置
+                // 这样比 setTimeout 更精准,也能避免页面闪烁
+                window.requestAnimationFrame(() => {
+                    // 强制关闭键盘
+                    editor.setAttribute('inputmode', 'none');
+                    editor.blur();
+                    
+                    // 强制重置高度样式
+                    editor.style.height = 'auto'; // 先重置为 auto
+                    editor.style.minHeight = '30px'; // 确保最小高度
+                    
+                    // 稍微延迟后恢复 inputmode,防止下次聚焦失败
+                    if (this.timer) clearTimeout(this.timer);
+                    this.timer = setTimeout(() => {
+                        editor.removeAttribute('inputmode');
+                        this.timer = null;
+                    }, 50);
+                });
+            }
+		},
+        handleTouchStart(event, ownerInstance) {
+            // 仅仅为了让 renderjs 保持活跃,或者在这里直接处理某些逻辑
+        },
+        handleForceUpdate(event, ownerInstance) {
+            // 通过触发点击事件等方式来强制唤醒 renderjs 响应
+        },
+		blurFlagUpdate(newValue, oldValue, ownerVm, vm) {
+			console.log("renderjs blurFlagUpdate", newValue);
+			if (!newValue) return;
+            // 优先使用缓存的实例,如果没有则重新获取
+			const editor = this.editorInstance || this.$el.querySelector('.ql-editor');
+			if (editor) {
+				editor.setAttribute('inputmode', 'none'); // 强制关闭软键盘
+				editor.blur();
+				console.log('blur',editor.innerHTML);
+				editor.innerHTML = '<p><br></p>';
+				console.log("4444");
+				// 检查并清理内容中的多余换行符,这可能是导致高度异常的原因
+				// 如果内容为空或者只包含空白字符,强制清空
+				if (!editor.innerHTML || editor.innerHTML === '<p><br></p>' || editor.textContent.trim() === '') {
+					// 保持至少一个空段落以维持格式,但确保它是干净的
+					editor.innerHTML = '<p><br></p>';
+				}
+				// 强制重置高度样式
+				// 极端手段:先隐藏再显示,强制浏览器重排
+				editor.style.display = 'none';
+				// 强制回流
+				editor.offsetHeight; 
+				editor.style.display = 'block';
+				
+				editor.style.height = '30px'; 
+                // 清除之前的 timer,避免多次覆盖
+                if (this.timer) clearTimeout(this.timer);
+				this.timer = setTimeout(() => {
+					editor.style.height = 'auto'; 
+					editor.removeAttribute('inputmode'); // 恢复输入模式
+                    this.timer = null;
+				}, 100);
+			}
+		},
+        insertEmojiFlagUpdate(newValue, oldValue, ownerVm, vm) {
+			if (newValue === null) {
+				return;
+			}
+			if (newValue) {
+				this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
+				ownerVm.callMethod('internalInsertEmoji')
+			} else {
+				this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
+			}
+		},
+		insertImageFlagUpdate(newValue, oldValue, ownerVm, vm) {
+			if (newValue === null) {
+				return;
+			}
+			if (newValue) {
+				this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
+				ownerVm.callMethod('internalInsertImage')
+			} else {
+				this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
+			}
+		},
+		insertAtFlagUpdate(newValue, oldValue, ownerVm, vm){
+			if (newValue === null) {
+				return;
+			}
+			if (newValue) {
+				const data = this.truncateText(`@${newValue.nickname}`,120)
+				ownerVm.callMethod('internalInsertAtEl', {...data,...newValue})
+			}
+		},
+		truncateText(text, maxWidth) {
+			const container = document.createElement("div");
+			container.style.width = "auto";
+			container.style.overflow = "hidden";
+			container.style.textOverflow = "ellipsis";
+			container.style.whiteSpace = "nowrap";
+			container.style.position = "absolute";
+			container.style.visibility = "hidden";
+
+			const textNode = document.createTextNode(text);
+			container.appendChild(textNode);
+
+			document.body.appendChild(container);
+
+			const isOverflowing = container.scrollWidth > maxWidth;
+
+			if (!isOverflowing) {
+				const width = container.clientWidth + 4
+				document.body.removeChild(container);
+				return {
+					text,
+					width
+				};
+			}
+
+			container.style.width = maxWidth + "px";
+			container.style.visibility = "visible";
+
+			let truncatedText = text;
+			while (container.scrollWidth > maxWidth) {
+			truncatedText = truncatedText.slice(0, -1);
+			container.textContent = truncatedText + "...";
+			}
+
+			document.body.removeChild(container);
+			return {
+				text: `${truncatedText}...`,
+				width: maxWidth + 4
+			};
+		}
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+.editor_wrap {
+	position: relative;
+}
+
+#editor2 {
+	background-color: #fff;
+	min-height: 30px;
+	max-height: 120px;
+	height: auto;
+	word-break: break-all;
+}
+
+/deep/.ql-editor {
+	img {
+		vertical-align: sub !important;
+	}
+
+	p {
+		padding: 4px;
+	}
+}
+
+.canvas_container {
+	position: fixed;
+	bottom: -99px;
+	z-index: -100;
+
+	&_name {
+		max-width: 480rpx;
+		display: inline-block;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	#atCanvas {
+		height: 20px;
+	}
+
+	.convas_container_name {
+		font-size: 16px !important;
+	}
+}
+</style>

+ 151 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/CustomTextarea.vue

@@ -0,0 +1,151 @@
+<template>
+	<view class="textarea_wrap">
+		<textarea
+			class="custom_textarea"
+			:value="inputValue"
+			:placeholder="placeholder"
+			:maxlength="-1"
+			:auto-height="true"
+			:show-confirm-bar="false"
+			:adjust-position="false"
+			:cursor-spacing="20"
+			@input="handleInput"
+			@focus="handleFocus"
+			@blur="handleBlur"
+			@confirm="handleConfirm"
+		/>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		placeholder: {
+			type: String,
+			default: ''
+		},
+		value: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			inputValue: '',
+			cursor: -1
+		};
+	},
+	watch: {
+		value: {
+			immediate: true,
+			handler(val) {
+				this.inputValue = val;
+			}
+		}
+	},
+	methods: {
+		handleInput(e) {
+			const value = e.detail.value;
+			this.inputValue = value;
+			this.cursor = e.detail.cursor;
+			
+			// 检测 @ 输入
+			const lastChar = value.slice(this.cursor - 1, this.cursor);
+			if (lastChar === '@') {
+				this.$emit('tryAt');
+			}
+			
+			this.$emit('input', e);
+		},
+		handleFocus(e) {
+			this.$emit('focus', e);
+		},
+		handleBlur(e) {
+			this.$emit('blur', e);
+		},
+		handleConfirm(e) {
+			this.$emit('confirm', e);
+		},
+		// 供外部调用的方法
+		insertEmoji(emoji) {
+			// 在光标处插入 emoji
+			const value = this.inputValue;
+			// 如果没有光标位置,默认追加到末尾
+			const cursor = this.cursor >= 0 ? this.cursor : value.length;
+			
+			const newValue = value.slice(0, cursor) + emoji + value.slice(cursor);
+			this.inputValue = newValue;
+			
+			// 更新光标位置
+			this.cursor = cursor + emoji.length;
+			
+			// 触发 input 事件
+			this.$emit('input', { detail: { value: newValue, cursor: this.cursor } });
+		},
+		insertAt(nickname) {
+			// 在光标处插入 @Name
+			// 简单处理:直接插入 @nickname + 空格
+			// 如果需要处理用户已经输入了 @ 的情况,需要更复杂的判断(比如判断光标前一个字符是否为 @)
+			const atText = `@${nickname} `;
+			this.insertEmoji(atText);
+		},
+		createCanvasData(userID, nickname) {
+			// 兼容 CustomEditor 的接口
+			this.insertAt(nickname);
+		},
+		clear() {
+			this.inputValue = '';
+			this.cursor = -1;
+			this.$emit('input', { detail: { value: '', cursor: -1 } });
+		},
+		backspace() {
+			const value = this.inputValue || '';
+			const cursor = this.cursor >= 0 ? this.cursor : value.length;
+			if (cursor === 0) return;
+			
+			// 使用 Array.from 处理 surrogate pairs (emoji)
+			const left = value.slice(0, cursor);
+			const right = value.slice(cursor);
+			const leftArr = Array.from(left);
+			if (leftArr.length === 0) return;
+			
+			leftArr.pop();
+			const newLeft = leftArr.join('');
+			const newValue = newLeft + right;
+			
+			this.inputValue = newValue;
+			// cursor 更新为新左侧字符串长度
+			this.cursor = newLeft.length;
+			
+			this.$emit('input', { detail: { value: newValue, cursor: this.cursor } });
+		},
+		focus() {
+			// textarea 无法通过 JS 强制聚焦,只能依赖用户操作或 auto-focus
+			// 但我们可以尝试通过 v-if 刷新组件来触发 auto-focus,或者使用 uni.hideKeyboard() 后再 show
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.textarea_wrap {
+	width: 100%;
+	min-height: 30px;
+	display: flex;
+	align-items: center;
+    background-color: #fff;
+    border-radius: 4px;
+    padding: 4px 8px;
+    box-sizing: border-box; /* 确保 padding 不会撑大宽度 */
+}
+
+.custom_textarea {
+	width: 100%;
+	min-height: 20px; /* 调整最小高度,配合 padding */
+	max-height: 100px; /* 限制最大高度 */
+	line-height: 20px;
+	font-size: 16px;
+	padding: 0; /* padding 移到 wrap 上 */
+    background-color: transparent; /* 背景透明,显示 wrap 的背景 */
+}
+</style>

+ 385 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/RecordOverlay.vue

@@ -0,0 +1,385 @@
+<template>
+  <u-overlay :show="visible" opacity="0">
+    <view class="record_container">
+
+      <view class="layer_container">
+        <view class="prompt-layer prompt-layer-1" v-if="!isCancelActive">
+          <text style="color: white; margin-bottom: 36rpx">{{ duration }}</text>
+          <view class="prompt-loader">
+            <view class="em" v-for="(item, index) in 20" :key="index"></view>
+          </view>
+          <text style="color: white; margin-top: 36rpx">手指上滑 取消发送</text>
+        </view>
+        <view class="prompt-layer prompt-layer-1 cancel" v-else>
+          <image src="../../../../../static/images/recorder_cancel.png"></image>
+          <text style="color: white; margin-top: 36rpx">松开手指 取消发送</text>
+        </view>
+      </view>
+    </view>
+  </u-overlay>
+</template>
+
+<script>
+const recorderManager = uni.getRecorderManager();
+const windowWidth = uni.getWindowInfo().windowWidth;
+let timer;
+
+const RecordActions = {
+  Cancel: "Cancel",
+  Translate: "Translate",
+};
+
+export default {
+  name: "",
+  components: {},
+  props: {
+    visible: Boolean,
+  },
+  data() {
+    return {
+      cancelFlag: false,
+      activeAction: null,
+      duration: 0,
+    };
+  },
+  computed: {
+    isCancelActive() {
+      return this.activeAction === RecordActions.Cancel;
+    },
+    isTranslateActive() {
+      return this.activeAction === RecordActions.Translate;
+    },
+  },
+  watch: {
+    visible(newVal) {
+      if (newVal) {
+        this.recordStart();
+      }
+    },
+  },
+  methods: {
+    recordStart() {
+      recorderManager.onStop(({ tempFilePath }) => {
+        if (this.isCancelActive || this.isTranslateActive) {
+          uni.$u.toast("已取消");
+        } else {
+          if (this.duration < 1) {
+            uni.$u.toast("录制时间太短啦");
+          } else {
+            this.$emit("recordFinish", tempFilePath, this.duration);
+          }
+        }
+        this.clearTimer();
+        this.activeAction = null;
+        this.cancelFlag = false;
+      });
+      recorderManager.onStart((res) => {
+        this.startTimer();
+      });
+      recorderManager.onError((err) => {
+        console.log(err);
+      });
+      recorderManager.start();
+    },
+    touchMoveSpeech(e, recordHeight) {
+      const touches = e.touches[0];
+      this.cancelFlag = touches.pageY < recordHeight;
+      if (this.cancelFlag) {
+        const isCancel = touches.pageX < windowWidth / 2;
+        this.activeAction = RecordActions.Cancel;
+      } else {
+        this.activeAction = null;
+      }
+    },
+    checkStopType() {
+      console.log(this.activeAction);
+      recorderManager.stop();
+    },
+    startTimer() {
+      timer = setInterval(() => this.duration++, 1000);
+    },
+    clearTimer() {
+      if (timer) {
+        clearInterval(timer);
+        this.duration = 0;
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+.prompt-layer {
+  width: 250rpx;
+  height: 250rpx;
+  border-radius: 16rpx;
+  background: #0c1c33;
+  // opacity: 0.7;
+  padding: 26rpx;
+  box-sizing: border-box;
+  position: relative;
+  // position: absolute;
+  // left: 50%;
+  // transform: translateX(-50%);
+  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
+}
+
+// .prompt-layer::after {
+// 	content: '';
+// 	display: block;
+// 	border: 24rpx solid rgba(0, 0, 0, 0);
+// 	border-top-color: #fff;
+// 	position: absolute;
+// 	bottom: -40rpx;
+// 	left: 50%;
+// 	transform: translateX(-50%);
+// }
+
+.prompt-layer-1 {
+  font-size: 24rpx;
+  width: 380rpx;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  top: -600rpx;
+}
+
+.cancel {
+  background: #ff381f;
+  // opacity: 0.7;
+
+  image {
+    width: 70rpx;
+    height: 100rpx;
+  }
+}
+
+.prompt-layer-1 .p {
+  color: #000;
+}
+
+.prompt-layer-1 .span {
+  color: rgba(0, 0, 0, 0.6);
+}
+
+.prompt-loader .em {
+}
+
+/* 语音音阶------------- */
+.prompt-loader {
+  width: 260rpx;
+  height: 40rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12rpx;
+}
+
+.prompt-loader .em {
+  display: block;
+  background: white;
+  width: 1px;
+  height: 10%;
+  margin-right: 2.5px;
+  float: left;
+}
+
+.prompt-loader .em:last-child {
+  margin-right: 0px;
+}
+
+.prompt-loader .em:nth-child(1) {
+  animation: load 2.5s 1.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(2) {
+  animation: load 2.5s 1.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(3) {
+  animation: load 2.5s 1s infinite linear;
+}
+
+.prompt-loader .em:nth-child(4) {
+  animation: load 2.5s 0.8s infinite linear;
+}
+
+.prompt-loader .em:nth-child(5) {
+  animation: load 2.5s 0.6s infinite linear;
+}
+
+.prompt-loader .em:nth-child(6) {
+  animation: load 2.5s 0.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(7) {
+  animation: load 2.5s 0.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(8) {
+  animation: load 2.5s 0s infinite linear;
+}
+
+.prompt-loader .em:nth-child(9) {
+  animation: load 2.5s 0.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(10) {
+  animation: load 2.5s 0.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(11) {
+  animation: load 2.5s 0.6s infinite linear;
+}
+
+.prompt-loader .em:nth-child(12) {
+  animation: load 2.5s 0.8s infinite linear;
+}
+
+.prompt-loader .em:nth-child(13) {
+  animation: load 2.5s 1s infinite linear;
+}
+
+.prompt-loader .em:nth-child(14) {
+  animation: load 2.5s 1.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(15) {
+  animation: load 2.5s 1.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(16) {
+  animation: load 2.5s 1.6s infinite linear;
+}
+
+.prompt-loader .em:nth-child(17) {
+  animation: load 2.5s 1.8s infinite linear;
+}
+
+.prompt-loader .em:nth-child(18) {
+  animation: load 2.5s 2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(19) {
+  animation: load 2.5s 2.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(20) {
+  animation: load 2.5s 2.4s infinite linear;
+}
+
+@keyframes load {
+  0% {
+    height: 10%;
+  }
+
+  50% {
+    height: 100%;
+  }
+
+  100% {
+    height: 10%;
+  }
+}
+
+.record_container {
+  display: flex;
+  flex-direction: column-reverse;
+  height: 100%;
+  width: 100%;
+
+  .record_area {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 260rpx;
+    width: 150%;
+    background-color: #dfdfdf;
+    border-radius: 50% / 100% 100% 0 0;
+    position: relative;
+    left: -25%;
+
+    image {
+      width: 36px;
+      height: 36px;
+    }
+
+    &:after {
+      content: attr(data-tip);
+      font-size: 26rpx;
+      color: #bebebe;
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      top: -64rpx;
+    }
+  }
+
+  .record_action_bar {
+    display: flex;
+    width: 80%;
+    min-height: 200rpx;
+    justify-content: space-between;
+    margin-left: 10%;
+    margin-bottom: 48rpx;
+
+    .record_action {
+      height: 144rpx;
+      width: 144rpx;
+      border-radius: 50%;
+      background-color: #484848;
+      color: #8e8e8e;
+      transition: all 0.25s;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 56rpx;
+      font-weight: 500;
+      transform: rotate(-24deg);
+      position: relative;
+
+      &:after {
+        content: attr(data-tip);
+        position: absolute;
+        font-size: 26rpx;
+        color: #bebebe;
+        transform: rotate(24deg);
+        top: -50%;
+        left: 50%;
+        width: 120rpx;
+        text-align: center;
+      }
+
+      &:last-child {
+        transform: rotate(24deg);
+
+        &:after {
+          transform: rotate(-24deg);
+          left: auto;
+          right: 40%;
+        }
+      }
+
+      &_cancling {
+        transform: rotate(-24deg) scale(1.2);
+        background-color: #eeedf0;
+        color: #696768;
+      }
+
+      &_translating {
+        transform: rotate(24deg) scale(1.2) !important;
+        background-color: #eeedf0;
+        color: #696768;
+      }
+    }
+  }
+
+  .layer_container {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+  }
+}
+</style>

+ 123 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/chooseFile.js

@@ -0,0 +1,123 @@
+function chooseFile(callback, acceptType) {
+  //acceptType为你要查的文件类型"image/*","audio/*","video/*;image/*"
+  // intent.setType("image/*");
+  //intent.setType("audio/*");
+  //选择音频
+  //intent.setType("video/*;image/*");
+  //选择视频 (mp4 3gp 是android支持的视频格式)
+  var CODE_REQUEST = 1000;
+  var main = plus.android.runtimeMainActivity();
+  if (plus.os.name == "Android") {
+    var Intent = plus.android.importClass("android.content.Intent");
+    var intent = new Intent(Intent.ACTION_GET_CONTENT);
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+    if (acceptType) {
+      intent.setType(acceptType);
+    } else {
+      intent.setType("*/*");
+    }
+    main.onActivityResult = function (requestCode, resultCode, data) {
+      if (requestCode == CODE_REQUEST) {
+        var uri = data.getData();
+        plus.android.importClass(uri);
+        var Build = plus.android.importClass("android.os.Build");
+        var isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+        var DocumentsContract = plus.android.importClass(
+          "android.provider.DocumentsContract",
+        );
+        // DocumentProvider
+        if (isKitKat && DocumentsContract.isDocumentUri(main, uri)) {
+          // console.log("版本大于 4.4 ");
+          // ExternalStorageProvider
+          if ("com.android.externalstorage.documents" == uri.getAuthority()) {
+            var docId = DocumentsContract.getDocumentId(uri);
+            var split = docId.split(":");
+            var type = split[0];
+
+            if ("primary" == type) {
+              var Environment = plus.android.importClass(
+                "android.os.Environment",
+              );
+              callback(
+                Environment.getExternalStorageDirectory() + "/" + split[1],
+              );
+            } else {
+              var System = plus.android.importClass("java.lang.System");
+              var sdPath = System.getenv("SECONDARY_STORAGE");
+              if (sdPath) {
+                callback(sdPath + "/" + split[1]);
+              }
+            }
+          }
+          // DownloadsProvider
+          else if (
+            "com.android.providers.downloads.documents" == uri.getAuthority()
+          ) {
+            var id = DocumentsContract.getDocumentId(uri);
+            var ContentUris = plus.android.importClass(
+              "android.content.ContentUris",
+            );
+            var contentUri = ContentUris.withAppendedId(
+              //    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+              Uri.parse("content://downloads/public_downloads"),
+              id,
+            );
+            callback(getDataColumn(main, contentUri, null, null));
+          }
+          // MediaProvider
+          else if (
+            "com.android.providers.media.documents" == uri.getAuthority()
+          ) {
+            var docId = DocumentsContract.getDocumentId(uri);
+            var split = docId.split(":");
+            var type = split[0];
+
+            var MediaStore = plus.android.importClass(
+              "android.provider.MediaStore",
+            );
+            if ("image" == type) {
+              contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+            } else if ("video" == type) {
+              contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+            } else if ("audio" == type) {
+              contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+            }
+
+            var selection = "_id=?";
+            var selectionArgs = new Array();
+            selectionArgs[0] = split[1];
+
+            callback(getDataColumn(main, contentUri, selection, selectionArgs));
+          }
+        }
+        // MediaStore (and general)
+        else if ("content" == uri.getScheme()) {
+          callback(getDataColumn(main, uri, null, null));
+        }
+        // File
+        else if ("file" == uri.getScheme()) {
+          callback(uri.getPath());
+        }
+      }
+    };
+    main.startActivityForResult(intent, CODE_REQUEST);
+  }
+}
+
+function getDataColumn(main, uri, selection, selectionArgs) {
+  plus.android.importClass(main.getContentResolver());
+  let cursor = main
+    .getContentResolver()
+    .query(uri, ["_data"], selection, selectionArgs, null);
+  plus.android.importClass(cursor);
+  if (cursor != null && cursor.moveToFirst()) {
+    var column_index = cursor.getColumnIndexOrThrow("_data");
+    var result = cursor.getString(column_index);
+    cursor.close();
+    return result;
+  }
+  return null;
+}
+
+export default chooseFile;

+ 968 - 0
pages_im/pages/conversation/chating1/components/ChatingFooter/index.vue

@@ -0,0 +1,968 @@
+<template>
+	<view class="chating_footer" :style="{ paddingBottom: keyboardHeight + 'px' }">
+		<view class="forbidden_footer" v-if="getPlaceholder.length > 0">
+		<image style="margin-right: 8rpx;width:50rpx" src="../../../../../static/images/forbidden_footer.png" mode="aspectFit" />
+			<text>{{ getPlaceholder }}</text>
+		</view>
+		
+		<view v-show="getPlaceholder.length <= 0" :style="{ 'pointer-events': getPlaceholder ? 'none' : 'auto' }">
+			<view class="chat_footer">
+				<image v-if="showRecord"
+					@click="updateRecordBtn"
+					style="width: 26px; height: 26px;"
+					src="../../../../../static/images/chating_footer_audio_recording.png" />
+
+                 <image v-else
+                 	@click="updateRecordBtn"
+                 	style="width: 26px; height: 26px;"
+                 	src="../../../../../static/images/chating_footer_audio.png" />
+
+				<view class="input_content">
+                    <!-- 临时替换为 CustomTextarea,原 CustomEditor 已注释 -->
+					<CustomTextarea
+						:placeholder="getPlaceholder"
+                        :value="inputHtml"
+						v-show="!showRecord"
+						class="custom_editor"
+						ref="customEditor"
+						@focus="editorFocus"  
+						@blur="editorBlur"
+						@input="editorInput"
+						@tryAt="showAtPanel"
+                        @confirm="sendTextMessage"
+                    />
+					<!-- <CustomEditor
+						:placeholder="getPlaceholder"
+						v-show="!showRecord"
+						class="custom_editor"
+						ref="customEditor"
+						@ready="editorReady"
+						@focus="editorFocus"  
+						@blur="editorBlur"
+						@input="editorInput"
+						@tryAt="showAtPanel"/> -->
+
+					<view v-if="storeQuoteMessage" class="quote_message">
+						<view class="content">
+							<u-parse :content="getQuotedContent" />
+						</view>
+						<image @click="cancelQuote" style="width: 16px; height: 16px" src="../../../../../static/images/chating_footer_quote_close.png" alt="" />
+					</view>
+					<button class="record_btn" @longpress="longpressRecord" @touchmove="touchMoveSpeech" @touchend="endRecord" v-show="showRecord" customStyle="height:30px;">
+						{{ recording ? '松手发送' : '按住说话' }}
+					</button>
+				</view>
+				<view class="footer_action_area">
+					<image @click.prevent="updateEmojiBar"  class="emoji_action" src="../../../../../static/images/chating_footer_emoji.png" alt="" srcset="" /> 
+					
+
+					<image  @click.prevent="updateActionBar" v-show="!hasContent" src="../../../../../static/images/chating_footer_add.png" alt="" srcset="" />
+					
+					<button class="btnSend" v-show="hasContent && !emojiBarVisible" @touchend.prevent="sendTextMessage">发送</button>
+					<button class="btnSend" v-show="hasContent && emojiBarVisible" @touchend.prevent="sendTextMessage">发送</button>
+					
+					<!-- <image v-show="hasContent && !emojiBarVisible" @touchend.prevent="sendTextMessage" src="../../../../../static/images/send_btn.png" alt="" srcset="" /> -->
+					<!-- <image v-show="hasContent && emojiBarVisible" @click.prevent="sendTextMessage" src="../../../../../static/images/send_btn.png" alt="" srcset="" /> -->
+				</view>
+			</view>
+
+			<chating-action-bar @sendMessage="sendMessage" @prepareMediaMessage="prepareMediaMessage" v-show="actionBarVisible" />
+
+			<chating-emoji-bar @insertEmoji="emojiClick" @backspace="backspace" v-show="emojiBarVisible" /> 
+			<u-action-sheet
+				:safeAreaInsetBottom="true"
+				round="12"
+				:actions="actionSheetMenu"
+				@select="selectClick"
+				:closeOnClickOverlay="true"
+				:closeOnClickAction="true"
+				:show="showActionSheet"
+				@close="showActionSheet = false"></u-action-sheet>
+			<record-overlay @recordFinish="recordFinish" ref="recordOverlayRef" :visible="recording" />
+		</view>
+	</view>
+</template>
+
+<script>
+import { mapGetters, mapActions } from 'vuex';
+import { formatInputHtml, getPurePath, html2Text } from '../../../../../util/common';
+import { parseMessageByType, parseAt } from '../../../../../util/imCommon';
+import { ChatingFooterActionTypes, UpdateMessageTypes, GroupMemberListTypes, PageEvents } from '../../../../../constant';
+import IMSDK, { GroupMemberRole, GroupStatus, IMMethods, MessageStatus, MessageType, SessionType } from 'openim-uniapp-polyfill';
+import UParse from '../../../../../components/gaoyia-parse/parse.vue';
+import CustomEditor from './CustomEditor.vue';
+import CustomTextarea from './CustomTextarea.vue';
+import ChatingActionBar from './ChatingActionBar.vue';
+import ChatingEmojiBar from './ChatingEmojiBar.vue';
+import RecordOverlay from './RecordOverlay.vue';
+import { gotoAppPermissionSetting, requestAndroidPermission, judgeIosPermission } from '../../../../../util/permission';
+import { he } from 'date-fns/locale';
+
+const needClearTypes = [
+	// MessageType.AdvanceTextMessage,
+	MessageType.TextMessage,
+	MessageType.AtTextMessage,
+	MessageType.QuoteMessage
+];
+
+const albumChoose = [
+	{
+		name: '图片',
+		type: ChatingFooterActionTypes.Album,
+		idx: 0
+	},
+	{
+		name: '视频',
+		type: ChatingFooterActionTypes.Album,
+		idx: 1
+	}
+];
+const cameraChoose = [
+	{
+		name: '拍照',
+		type: ChatingFooterActionTypes.Camera,
+		idx: 0
+	},
+	{
+		name: '录制',
+		type: ChatingFooterActionTypes.Camera,
+		idx: 1
+	}
+];
+
+export default {
+	components: {
+		CustomEditor,
+        CustomTextarea,
+		ChatingActionBar,
+		ChatingEmojiBar,
+		UParse,
+		RecordOverlay
+	},
+	props: {
+		footerOutsideFlag: Number
+	},
+	data() {
+		return {
+			customEditorCtx: null,
+			inputHtml: '',
+			showRecord: false,
+			actionBarVisible: false,
+			emojiBarVisible: false,
+			isInputFocus: false,
+			actionSheetMenu: [],
+			showActionSheet: false,
+			recording: false,
+			recordTop: uni.getWindowInfo().windowHeight - 130,
+			conversationID: null,
+			isJoinGroup: true,
+			isSending: false, // 发送状态标志
+			isClickAction:false,
+			isClickEmoji:false,
+            keyboardHeight: 0 // 记录键盘高度
+		};
+	},
+	computed: {
+		...mapGetters(['storeQuoteMessage', 'storeCurrentConversation', 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeBlackList', 'storeRevokeMap','timStore']),
+		getQuotedContent() {
+			if (!this.storeQuoteMessage) {
+				return '';
+			}
+			return `回复${parseMessageByType(this.storeQuoteMessage)}`;
+		},
+		hasContent() {
+			return html2Text(this.inputHtml) !== '';
+		},
+		isNomal() {
+			return this.storeCurrentMemberInGroup.roleLevel === GroupMemberRole.Nomal;
+		},
+		getPlaceholder() {
+			const isSingle = this.storeCurrentConversation.conversationType === SessionType.Single;
+			if (!isSingle && this.storeCurrentGroup.status === GroupStatus.Muted) {
+				return !this.isNomal ? '' : '群主或管理员已开启全体禁言';
+			}
+			if (!isSingle && !this.isJoinGroup) {
+				return '您已不在该群组';
+			}
+			if (!isSingle && this.storeCurrentMemberInGroup.muteEndTime > Date.now()) {
+				return `您已被管理员或群主禁言!`;
+			}
+			if (isSingle && this.storeBlackList.find((black) => black.userID === this.storeCurrentConversation.userID)) {
+				return '对方已被拉入黑名单';
+			}
+			return '';
+		},
+		imType() {
+			return this.timStore?.imType ?? '' ;
+		},
+		orderId() {
+			return this.timStore?.orderId ?? '' ;
+		},
+		orderType() {
+			return this.timStore?.orderType ?? '' ;
+		},
+	},
+	watch: {
+		footerOutsideFlag(newVal) {
+			this.onClickActionBarOutside();
+			this.onClickEmojiBarOutside();
+		},
+		actionBarVisible(val) {
+			this.checkComplaintButtonVisible();
+		},
+		emojiBarVisible(val) {
+			this.checkComplaintButtonVisible();
+		}
+	},
+	mounted() {
+		this.setIMListener();
+		this.setKeyboardListener();
+		if (this.storeCurrentConversation.groupID) {
+			IMSDK.asyncApi(IMMethods.IsJoinGroup, IMSDK.uuid(), this.storeCurrentConversation.groupID).then((res) => {
+				this.isJoinGroup = res.data;
+			});
+		}
+		this.conversationID = this.storeCurrentConversation.conversationID;
+		this.parseDraftText(this.storeCurrentConversation.draftText);
+		IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
+			conversationID: this.conversationID,
+			draftText: ''
+		});
+		uni.$on(PageEvents.ReEditMessage, this.setReEditMessage);
+	    uni.$on("sendIM", this.sendPackageImMsg);
+		uni.$on("sendIMCts", this.sendCtsImMsg);
+	},
+	beforeDestroy() {
+		uni.$emit('showComplaintButton');
+		this.disposeIMListener();
+		this.disposeKeyboardListener();
+		if (this.inputHtml && this.inputHtml.trim()) {
+			IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
+				conversationID: this.conversationID,
+				draftText: html2Text(this.inputHtml)
+			});
+		}
+		uni.$off("sendIM", this.sendPackageImMsg);
+		uni.$off("sendIMCts", this.sendCtsImMsg);
+	},
+	methods: {
+		...mapActions('message', ['pushNewMessage', 'updateOneMessage', 'updateQuoteMessageRevoke']),
+		checkComplaintButtonVisible() {
+			this.$nextTick(() => {
+				if (this.actionBarVisible || this.emojiBarVisible) {
+					uni.$emit('hideComplaintButton');
+				} else {
+					uni.$emit('showComplaintButton');
+				}
+			});
+		},
+		setReEditMessage(id) {
+			if (this.$refs.customEditor) {
+				this.$refs.customEditor.clear();
+			}
+			setTimeout(() => {
+				this.$refs.customEditor.insertEmoji(this.storeRevokeMap[id].text);
+				this.$store.commit('message/SET_QUOTE_MESSAGE', this.storeRevokeMap[id].quoteMessage);
+			}, 50);
+		},
+		parseDraftText(draftText) {
+			let currentText = '';
+			for (let i = 0; i < draftText.length; i++) {
+				const currentChar = draftText[i];
+				if (currentChar === '<') {
+					if (currentText) {
+						const text = currentText;
+						setTimeout(() => {
+							this.$refs.customEditor.insertEmoji(text);
+						}, 100);
+						currentText = '';
+					}
+					const imgEndIndex = draftText.indexOf('>', i);
+					if (imgEndIndex !== -1) {
+						const imgTag = draftText.substring(i, imgEndIndex + 1);
+
+						if (imgTag.includes('class="at_el"')) {
+							const customDataReg = /data-custom=".+"/;
+							const atInfoArr = imgTag.match(customDataReg)[0].slice(13, -1).split('&amp;');
+							this.$refs.customEditor.createCanvasData(atInfoArr[0].slice(7), atInfoArr[1].slice(15));
+						}
+						i = imgEndIndex;
+					}
+				} else {
+					currentText += currentChar;
+				}
+			}
+			if (currentText) {
+				setTimeout(() => {
+					this.$refs.customEditor.insertEmoji(currentText);
+				}, 700);
+			}
+		},
+		longpressRecord() {
+			this.recording = true;
+			this.$nextTick(() => this.getRecordAreaData());
+		},
+		touchMoveSpeech(e) {
+			uni.$u.throttle(this.$refs.recordOverlayRef.touchMoveSpeech(e, this.recordTop), 250);
+		},
+		endRecord() {
+			this.recording = false;
+			this.$refs.recordOverlayRef.checkStopType();
+		},
+		async recordFinish(path, duration) {
+			const message = await IMSDK.asyncApi(IMMethods.CreateSoundMessageFromFullPath, IMSDK.uuid(), {
+				soundPath: getPurePath(path),
+				duration
+			});
+			this.sendMessage(message);
+		},
+		getRecordAreaData() {
+			this.getEl('.record_area').then((data) => {
+				if (data) {
+					this.recordTop = data.top;
+				}
+			});
+		},
+		getEl(el) {
+			return new Promise((resolve) => {
+				const query = uni.createSelectorQuery().in(this);
+				query.select(el).boundingClientRect((data) => {
+					resolve(data);
+				}).exec();
+			});
+		},
+		async createTextMessage() {
+			let message = '';
+            // 切换为 textarea 后,inputHtml 实际上是纯文本
+            const text = String(this.inputHtml || '');
+			console.log('createTextMessage text:',text);
+            // 简单的 @ 解析逻辑:查找 @Name 格式
+            // 这里暂时简化处理,如果需要精确的 @ 功能,需要维护一个 @ 用户列表
+            // 暂时只发送普通文本,后续可以增强 @ 解析
+			if (this.storeQuoteMessage) {
+				console.log("1111");
+				message = await IMSDK.asyncApi(IMMethods.CreateQuoteMessage, IMSDK.uuid(), {
+					text,
+					message: this.storeQuoteMessage
+				});
+			} else {
+				console.log("2222",text);
+				message = await IMSDK.asyncApi(IMMethods.CreateTextMessage, IMSDK.uuid(), text);
+			}
+			if (!message) {
+				throw new Error('CreateTextMessage returned null/undefined');
+			}
+			return message;
+		},
+		async sendTextMessage() {
+			console.log('sendTextMessage inputHtml:', this.inputHtml);
+			if (!this.inputHtml || !this.inputHtml.trim()) {
+				console.log('sendTextMessage: input is empty');
+				return; 
+			}
+			
+			// 非关键操作,忽略错误
+			try {
+				IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
+					conversationID: this.storeCurrentConversation.conversationID,
+					focus: false
+				});
+			} catch (e) {
+				console.warn('changeInputStates failed:', e);
+			}
+			
+			 try {
+			      this.isSending = true; // 开始发送,设置标志位
+			      const message = await this.createTextMessage();
+				  console.log('createTextMessage result:', message);
+			      await this.sendMessage(message);
+                  
+                  // 发送成功后清空
+                  this.inputHtml = '';
+                  if (this.$refs.customEditor) {
+                      this.$refs.customEditor.clear();
+                  }
+                  
+			    } catch (error) {
+					  console.error('发送消息失败:', error);
+					  // 详细打印 error 对象
+					  if (typeof error === 'object') {
+						  console.log('Error details:', JSON.stringify(error));
+					  }
+					  uni.showToast({
+						 title: '发送失败: ' + (error.errMsg || '未知错误'),
+						 icon: 'none'
+					  });
+			    } finally {
+			        this.isSending = false; // 发送完成,重置标志位
+			  }
+		},
+		async sendCtsPackageMessage(item){
+			let payloadJson={
+				payload:{
+					 data:"package", 
+					 extension:{
+						packageId:item.packageId,
+						title:item.packageName,
+						companyId:item.companyId,
+						companyUserId:item.companyUserId,
+						image:item.imgUrl
+					 }
+		         }
+			};
+			let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
+				data: JSON.stringify(payloadJson),
+				extension: '',
+				description: 'package'
+			});
+			this.sendMessage(message);
+		},
+		sendPackageImMsg(item){
+			console.log("qxj package:"+item.packageId);
+			this.sendCtsPackageMessage(item);
+		},
+		
+		async sendCtsMessage(item){
+			 let payloadJson={};
+			 if(item.type=="couponPackage"){
+			     payloadJson={
+			     	payload:{
+			     		 data:item.type, 
+			     		 extension:{
+			     			couponId:item.couponId,
+			     			title:item.title,
+							price:item.price,
+							minPrice:item.minPrice,
+							limitTime:item.limitTime,
+			     			companyId:item.companyId,
+			     			companyUserId:item.companyUserId,
+			     			//image:item.imgUrl
+			     		 }
+			          }
+			     };
+			 
+			 }
+			 if(item.type=="inquirySelect"){
+			     payloadJson={
+			     	payload:{
+			     		 data:item.type, 
+			     		 extension:{
+			     			packageId:item.packageId,
+			     			title:item.lable,
+							type:item.value,
+			     			companyId:item.companyId,
+			     			companyUserId:item.companyUserId,
+			     			//image:item.imgUrl
+			     		 }
+			          }
+			     };
+			 }
+			let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
+				 data: JSON.stringify(payloadJson),
+				 extension: '',
+				 description: item.type
+			});
+			this.sendMessage(message);
+		},
+		sendCtsImMsg(item){
+			this.sendCtsMessage(item);
+		},
+		
+		sendMessage(message) {
+			var customerData={
+			  	type:this.timStore?.type,
+			  	imType:this.timStore?.imType,
+			  	orderId:this.timStore?.orderId,
+			  	followId:this.timStore?.followId,
+			  	orderType:this.timStore?.orderType
+			}
+			let userId=this.storeCurrentConversation.userID;
+			if(userId!=undefined  && (userId!="" || userId.length>0)){
+				if(userId.indexOf('D')!==-1){
+					message.ex=JSON.stringify(customerData);
+				}
+			}
+			this.pushNewMessage(message);
+			if (needClearTypes.includes(message.contentType)) {
+				if(this.$refs.customEditor){
+					this.$refs.customEditor.clear();
+				}
+			}
+			let offlinePushInfo={
+				title:message.senderNickname,
+				desc: parseMessageByType(message),
+				ex: "",
+				iOSPushSound: "",
+				iOSBadgeCount: true
+			};
+			
+			this.$emit('scrollToBottom');
+			IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
+				recvID: this.storeCurrentConversation.userID,
+				groupID: this.storeCurrentConversation.groupID,
+				message,
+				offlinePushInfo
+			}).then(({ data }) => {
+					this.updateOneMessage({
+						message: data,
+						isSuccess: true
+					});
+				}).catch(({ data, errCode, errMsg }) => {
+					this.updateOneMessage({
+						message: data,
+						type: UpdateMessageTypes.KeyWords,
+						keyWords: [
+							{
+								key: 'status',
+								value: MessageStatus.Failed
+							},
+							{
+								key: 'errCode',
+								value: errCode
+							}
+						]
+					});
+				});
+			if (this.storeQuoteMessage) {
+				this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
+			}
+		},
+		// action
+		cancelQuote() {
+			this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
+		},
+		onClickActionBarOutside() {
+			if (this.actionBarVisible) {
+				this.actionBarVisible = false;
+			}
+		},
+		onClickEmojiBarOutside() {
+			if (this.emojiBarVisible) {
+				this.emojiBarVisible = false;
+			}
+		},
+		updateEmojiBar() {
+			if (this.recording) {
+				return;
+			}
+			if (this.showRecord) {
+				this.showRecord = false;
+			}
+			
+			this.onClickActionBarOutside();
+			if(!this.emojiBarVisible){
+				this.emojiBarVisible=true;
+				this.$emit('scrollToBottom');
+			}
+			this.isClickEmoji=true;
+			setTimeout(()=>{
+				this.isClickEmoji=false;
+			},300);
+			//this.emojiBarVisible = !this.emojiBarVisible;
+		},
+		updateActionBar() {
+			if (this.recording) {
+				return;
+			}
+			if (this.showRecord) {
+				this.showRecord = false;
+			}
+			this.onClickEmojiBarOutside();
+			this.actionBarVisible = !this.actionBarVisible;
+			this.isClickAction=true;
+			setTimeout(()=>{
+				this.isClickAction=false;
+			},300);
+		},
+		
+		editorInput(e) {
+			// 适配 Textarea 的 input 事件
+            const value = e.detail.value || '';
+            this.inputHtml = value;
+		},
+		editorReady(res) {
+            // Textarea 不需要 context
+			this.$emit('ready', res);
+		},
+		editorFocus() {
+			this.isInputFocus = true;
+			this.$emit('focus');
+			// 优化:键盘弹起前先给个预估高度,防止被遮挡
+			if (this.keyboardHeight === 0) {
+			    const lastHeight = uni.getStorageSync('lastKeyboardHeight') || 300;
+			    this.keyboardHeight = lastHeight;
+			}
+		},
+		editorBlur() {
+			this.isInputFocus = false;
+			this.$emit('blur');
+		},
+		showAtPanel() {
+			if (!this.$store.getters.storeCurrentConversation.groupID) return;
+			uni.$u.route('/pages_im/pages/conversation/groupMemberList/index', {
+				type: GroupMemberListTypes.ChooseAt,
+				groupID: this.$store.getters.storeCurrentConversation.groupID
+			});
+		},
+		backspace() {
+			if (this.$refs.customEditor) {
+				this.$refs.customEditor.backspace();
+			}
+		},
+		updateRecordBtn() {
+			if (this.recording) {
+				return;
+			}
+			if (!this.showRecord) {
+				this.checkRecordPermission();
+			}
+			this.showRecord = !this.showRecord;
+			this.onClickActionBarOutside();
+			this.onClickEmojiBarOutside();
+			this.$emit('scrollToBottom');
+		},
+		async checkRecordPermission() {
+			if (uni.$u.os() === 'ios') {
+				const iosFlag = judgeIosPermission('record');
+				if (iosFlag === 0) {
+					uni.$u.toast('您已禁止访问麦克风,请前往开启');
+					setTimeout(() => gotoAppPermissionSetting(), 250);
+				}
+				if (iosFlag === -1) {
+					let recorderManager = uni.getRecorderManager();
+					recorderManager.onStop(() => (recorderManager = null));
+					recorderManager.start();
+					setTimeout(() => recorderManager.stop());
+				}
+			} else {
+				const androidFlag = await requestAndroidPermission('android.permission.RECORD_AUDIO');
+				if (androidFlag === -1) {
+					uni.$u.toast('您已禁止访问麦克风,请前往开启');
+					setTimeout(() => gotoAppPermissionSetting(), 250);
+				}
+			}
+		},
+		prepareMediaMessage(data) {
+			// if (data.type === ChatingFooterActionTypes.Album) {
+			// 	this.actionSheetMenu = [...albumChoose];
+			// } else {
+			// 	this.actionSheetMenu = [...cameraChoose];
+			// }
+			// this.showActionSheet = false;
+			console.log("qxj prepareMediaMessage data:"+JSON.stringify(data));
+			this.selectClick(data);
+		},
+		updateTyping() {
+			if (this.storeCurrentConversation.conversationType === SessionType.Single) {
+				IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
+					conversationID: this.storeCurrentConversation.conversationID,
+					focus: true
+				});
+			}
+		},
+ 
+		// from comp
+		emojiClick(emoji) {
+			this.$refs.customEditor.insertEmoji(emoji);
+		},
+		batchCreateImageMesage(paths) {
+			paths.forEach(async (path) => {
+				const message = await IMSDK.asyncApi(IMMethods.CreateImageMessageFromFullPath, IMSDK.uuid(), getPurePath(path));
+				this.sendMessage(message);
+			});
+		},
+		selectClick(data) {
+			if (data.index === 0) {
+				if (data.type === ChatingFooterActionTypes.Album) {
+					this.chooseOrShotImage(['album']).then((paths) => this.batchCreateImageMesage(paths));
+				} else {
+					this.chooseOrShotImage(['camera']).then((paths) => this.batchCreateImageMesage(paths));
+				}
+			} 
+			else {
+				const whenGetFile = (data) => {
+					const purePath = getPurePath(data.path);
+					IMSDK.getVideoCover(purePath).then(async ({ path }) => {
+						console.log(getPurePath(path));
+						const message = await IMSDK.asyncApi(IMMethods.CreateVideoMessageFromFullPath, IMSDK.uuid(), {
+							videoPath: purePath,
+							videoType: data.videoType,
+							duration: Number(data.duration.toFixed()),
+							snapshotPath: getPurePath(path)
+						});
+						this.sendMessage(message);
+					});
+				};
+				if (data.type === ChatingFooterActionTypes.Album) {
+					this.chooseOrShotVideo(['album']).then(whenGetFile);
+				} else {
+					this.chooseOrShotVideo(['camera']).then(whenGetFile);
+				}
+			}
+		},
+		chooseOrShotImage(sourceType) {
+			return new Promise((resolve, reject) => {
+				uni.chooseImage({
+					count: 9,
+					sizeType: ['compressed'],
+					sourceType,
+					success: function ({ tempFilePaths }) {
+						resolve(tempFilePaths);
+					},
+					fail: function (err) {
+						console.log(err);
+						reject(err);
+					}
+				});
+			});
+		},
+		chooseOrShotVideo(sourceType) {
+			return new Promise((resolve, reject) => {
+				uni.chooseVideo({
+					compressed: true,
+					sourceType,
+					extension: ['mp4'],
+					success: function ({ tempFilePath, duration }) {
+						const idx = tempFilePath.lastIndexOf('.');
+						const videoType = tempFilePath.slice(idx + 1);
+						if (tempFilePath.includes('_doc/')) {
+							tempFilePath = `file://${plus.io.convertLocalFileSystemURL(tempFilePath)}`;
+						}
+						console.log(tempFilePath);
+						resolve({
+							path: tempFilePath,
+							videoType,
+							duration
+						});
+					},
+					fail: function (err) {
+						console.log(err);
+						reject(err);
+					}
+				});
+			});
+		},
+		insertAt(userID, nickname) {
+			const { atUsersInfo } = formatInputHtml(this.inputHtml);
+			if (atUsersInfo.find((item) => item.atUserID === userID)) {
+				return;
+			}
+			this.$refs.customEditor.createCanvasData(userID, nickname);
+		},
+
+		// im listener
+		sendProgressHandler({ data: { progress, message } }) {
+			this.updateOneMessage({
+				message,
+				type: UpdateMessageTypes.KeyWords,
+				keyWords: {
+					key: 'uploadProgress',
+					value: progress
+				}
+			});
+		},
+		joinedGroupChangeHandler({ data }) {
+			if (data.groupID === this.storeCurrentConversation.groupID) {
+				IMSDK.asyncApi(IMMethods.IsJoinGroup, IMSDK.uuid(), data.groupID).then((res) => {
+					this.isJoinGroup = res.data;
+				});
+			}
+		},
+		setIMListener() {
+			IMSDK.subscribe("SendMessageProgress", this.sendProgressHandler);
+			uni.$on(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
+			uni.$on(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
+		},
+		disposeIMListener() {
+			IMSDK.unsubscribe("SendMessageProgress", this.sendProgressHandler);
+			uni.$off(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
+			uni.$off(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
+		},
+        // keyboard
+		setKeyboardListener() {
+			uni.onKeyboardHeightChange((res) => {
+				this.keyboardChangeHander(res);
+			});
+		},
+		disposeKeyboardListener() {
+			uni.offKeyboardHeightChange(this.keyboardChangeHander);
+		},
+		keyboardChangeHander(res) {
+            // 当键盘高度变化时,如果需要手动调整 footer 位置(adjust-position=false时)
+            // 目前我们的布局通常是 flex,可能不需要额外设置 bottom,除非是 fixed 定位
+            // 但为了确保万无一失,我们可以通知父组件或调整自身样式
+            
+            // 更新键盘高度,用于动态调整 padding-bottom
+			//console.log('keyboardChangeHander', res.height);
+            this.keyboardHeight = res.height;
+            if (res.height > 0) {
+                uni.setStorage({ key: 'lastKeyboardHeight', data: res.height });
+            }
+
+			// 只有当高度真正变化时才触发
+			// 这里我们主要处理一些状态同步,比如表情面板的互斥
+			if (res.height > 0) {
+				// 键盘弹起,关闭表情和功能面板
+				if (this.actionBarVisible) {
+					this.actionBarVisible = false;
+				}
+				if (this.emojiBarVisible) {
+					this.emojiBarVisible = false;
+				}
+                this.$emit('keyboardHeightChange', res.height);
+			} else {
+                this.$emit('keyboardHeightChange', 0);
+            }
+            
+            // 滚动到底部
+            if (res.height > 0) {
+                setTimeout(() => {
+                    this.$emit('scrollToBottom');
+                }, 200);
+            }
+		}
+	}
+};
+</script>
+
+<script module="snap" lang="renderjs">
+export default {
+	methods: {
+		getSnapFlagUpdate(newValue, oldValue, ownerVm, vm) {
+			if (newValue === null) {
+				return;
+			}
+			const {
+				path
+			} = newValue
+			this.getVideoSnshot(path).then(base64 => {
+				ownerVm.callMethod('receiveSnapBase64', {
+					...newValue,
+					base64
+				})
+			})
+		},
+		getVideoSnshot(item) {
+			return new Promise((reslove, reject) => {
+				var video = document.createElement("VIDEO");
+				video.style.display = 'none';
+				video.setAttribute('crossOrigin', 'anonymous');
+				video.setAttribute("autoplay", "autoplay");
+				video.setAttribute("muted", "muted");
+				video.innerHTML = "<source src=" + item + ' type="audio/mp4">';
+				var canvas = document.createElement("canvas");
+				var ctx = canvas.getContext("2d");
+				video.addEventListener("canplay", function() {
+					var anw = document.createAttribute("width");
+					anw.nodeValue = video.videoWidth;
+					var anh = document.createAttribute("height");
+					anh.nodeValue = video.videoHeight;
+					canvas.setAttributeNode(anw);
+					canvas.setAttributeNode(anh);
+					ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
+					var base64 = canvas.toDataURL("image/png");
+					video.pause();
+					reslove(base64);
+				});
+			});
+		},
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+	.chating_footer{
+	}
+.custom_editor {
+	img {
+		vertical-align: sub;
+	}
+}
+
+.forbidden_footer {
+	width: 100%;
+	height: 112rpx;
+	color: #8e9ab0;
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	background: #f0f2f6;
+	flex-shrink: 0;
+	flex-grow: 0;
+}
+
+.chat_footer {
+	display: flex;
+	align-items: center;
+	// background-color: #e9f4ff;
+	background: #f0f2f6;
+	// height: 50px;
+	max-height: 120px;
+	padding: 24rpx 20rpx;
+
+	.input_content {
+		flex: 1;
+		min-height: 30px;
+		max-height: 120px;
+		margin: 0 24rpx;
+		border-radius: 8rpx;
+		position: relative;
+		font-size: 36rpx;
+		.record_btn {
+			// background-color: #3c9cff;
+			background: #fff;
+			color: black;
+			height: 30px;
+			line-height: 30px;
+			
+		}
+	}
+
+	.quote_message {
+		@include vCenterBox();
+		justify-content: space-between;
+		margin-top: 12rpx;
+		padding: 8rpx;
+		// padding-top: 20rpx;
+		border-radius: 6rpx;
+		background-color: #fff;
+		color: #666;
+
+		.content {
+			::v-deep uni-view {
+				@include ellipsisWithLine(2);
+			}
+		}
+	}
+
+	.footer_action_area {
+		display: flex;
+		align-items: center;
+		.emoji_action {
+			margin-right: 24rpx;
+		}
+		.btnSend{
+			font-size: 36rpx;
+			color: #fff;
+			border-radius:6px;
+			background-color:#1ab84d ;
+			padding:20rpx 26rpx;
+		}
+
+		image {
+			width: 26px;
+			height: 26px;
+		}
+	}
+
+	.send_btn {
+		height: 30px;
+		line-height: 30px;
+		background-color: #4a9cfc;
+		padding: 0 8px;
+		border-radius: 4px;
+		color: #fff;
+	}
+}
+</style>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików