diff --git a/App.vue b/App.vue index 4614aae..7ca8d8e 100644 --- a/App.vue +++ b/App.vue @@ -2,6 +2,7 @@ import { getVersion } from './request/api/api' + import { webSocketManager } from '@/utils/ws/websocket.js'; export default { // //初始化状态-如果登录过,保持登录状态 onLaunch: async function() { @@ -81,6 +82,8 @@ content: '当前微信版本过低,可能无法使用该功能,请升级到最新版本后重试。' }) } + + this.initWebSocket(); }, onShow: function(e) { @@ -110,7 +113,12 @@ }, onUnload:function() { // uni.removeStorageSync('token') - } + }, + methods: { + initWebSocket() { + webSocketManager.connect(); + } + } } diff --git a/manifest.json b/manifest.json index b4c905f..88b3b9b 100644 --- a/manifest.json +++ b/manifest.json @@ -77,7 +77,17 @@ "optimization" : { "subPackages" : true }, - "permission" : {} + "permission": { + "scope.userLocation": { + "desc": "你的位置信息将用于获取附近服务" + }, + "scope.record": { + "desc": "需要录音功能发送语音消息" + }, + "scope.camera": { + "desc": "需要摄像头拍照和视频通话" + } + } }, "mp-alipay" : { "usingComponents" : true @@ -91,5 +101,6 @@ "uniStatistics" : { "enable" : false }, - "vueVersion" : "2" + "vueVersion" : "2", + "sassImplementationName": "node-sass" } diff --git a/package-lock.json b/package-lock.json index 79da191..f1d27b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,19 +5,305 @@ "packages": { "": { "dependencies": { + "@vue/composition-api": "^1.4.1", "js-base64": "^3.7.7", - "uview-ui": "^1.8.8" + "pinia": "^2.0.14", + "uview-ui": "^1.8.8", + "vue-demi": "^0.14.5", + "vuex": "^3.6.2" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", + "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.23.5", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + }, + "optionalDependencies": { + "prettier": "^1.18.2 || ^2.0.0" + } + }, + "node_modules/@vue/composition-api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@vue/composition-api/-/composition-api-1.4.1.tgz", + "integrity": "sha512-ZTat9ru/rwecveRnFzlO2mduOBpGfnBdXn+WtBcFLV9UsL/D+6nX47RWuLiVdNxNDX0qphGZRC+JDjwt+YTnRA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.1" + }, + "peerDependencies": { + "vue": ">= 2.5 < 3" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT", + "peer": true + }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.7.tgz", "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/uview-ui": { "version": "1.8.8", "resolved": "https://registry.npmmirror.com/uview-ui/-/uview-ui-1.8.8.tgz", "integrity": "sha512-Osal3yzXiHor0In9OPTZuXTaqTbDglMZ9RGK/MPYDoQQs+y0hrBCUD0Xp5T70C8i2lLu2X6Z11zJhmsQWMR7Jg==" + }, + "node_modules/vue": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", + "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", + "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-sfc": "2.7.16", + "csstype": "^3.1.0" + } + }, + "node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vuex": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", + "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==", + "license": "MIT", + "peerDependencies": { + "vue": "^2.0.0" + } } } } diff --git a/package.json b/package.json index f0f1b34..db2320c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "dependencies": { + "@vue/composition-api": "^1.4.1", "js-base64": "^3.7.7", - "uview-ui": "^1.8.8" + "pinia": "^2.0.14", + "uview-ui": "^1.8.8", + "vue-demi": "^0.14.5", + "vuex": "^3.6.2" } } diff --git a/pages.json b/pages.json index be6ebf1..c2db908 100644 --- a/pages.json +++ b/pages.json @@ -5,7 +5,8 @@ // npm安装方式 "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" }, - "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + "pages": [ + //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "path": "pages/home/index", "style": { @@ -110,9 +111,11 @@ // "needLogin": true } ], - "subPackages": [{ + "subPackages": [ + { "root": "subPackages", - "pages": [{ + "pages": [ + { "path": "doctor/doctor-detail", "style": { "navigationBarTitleText": "医生详情", @@ -162,9 +165,11 @@ } } ] - }, { + }, + { "root": "subPackages", - "pages": [{ + "pages": [ + { "path": "my/myinfo-add", "style": { "navigationBarTitleText": "添加就诊人", @@ -191,7 +196,8 @@ "navigationBarTitleText": "订单详情", "enablePullDownRefresh": false } - }, { + }, + { "path": "my/myreport", "style": { "navigationBarTitleText": "检查报告", @@ -211,7 +217,8 @@ "navigationBarTitleText": "线上复诊", "enablePullDownRefresh": false } - }, { + }, + { "path": "my/mystore", "style": { "navigationStyle": "default", @@ -219,7 +226,8 @@ "navigationBarBackgroundColor": "#1777FF", "navigationBarTextStyle": "white" } - }, { + }, + { "path": "my/mycollection", "style": { "navigationBarTitleText": "个人资料", @@ -246,7 +254,8 @@ "navigationBarTitleText": "处方记录", "enablePullDownRefresh": true } - }, { + }, + { "path": "my/myappiont", "style": { "navigationBarTitleText": "挂号记录", @@ -283,34 +292,42 @@ } } ] - }, { + }, + { "root": "subPackages", - "pages": [{ - "path": "register/register", - "style": { - "navigationBarTitleText": "确认挂号信息" + "pages": [ + { + "path": "register/register", + "style": { + "navigationBarTitleText": "确认挂号信息" + } + }, + { + "path": "register/register-success", + "style": { + "navigationBarTitleText": "确认挂号信息" + } + }, + { + "path": "register/register-info", + "style": { + "navigationBarTitleText": "挂号详情", + "navigationStyle": "custom" + } } - }, { - "path": "register/register-success", - "style": { - "navigationBarTitleText": "确认挂号信息" - } - }, { - "path": "register/register-info", - "style": { - "navigationBarTitleText": "挂号详情", - "navigationStyle": "custom" - } - }] - }, { + ] + }, + { "root": "subPackages", - "pages": [{ + "pages": [ + { "path": "shop/orders", "style": { "navigationBarTitleText": "商城订单", "enablePullDownRefresh": true } - }, { + }, + { "path": "shop/search-home", "style": { "navigationBarTitleText": "萧康诊所门店" @@ -374,7 +391,8 @@ }, { "root": "subPackages", - "pages": [{ + "pages": [ + { "path": "setup/filed", "style": { "navigationBarTitleText": "设置" @@ -415,6 +433,13 @@ "style": { "navigationBarTitleText": "客服中心" } + }, + { + "path": "chat/chat", + "style": { + "navigationBarTitleText": "在线问诊", + "enablePullDownRefresh": false + } } ] } @@ -425,7 +450,8 @@ "spacing": "4px", "backgroundColor": "#fff", "selectedColor": "#1575FC", - "list": [{ + "list": [ + { "pagePath": "pages/home/home", "text": "首页", "iconPath": "static/home/02.png", @@ -449,7 +475,8 @@ "iconPath": "static/home/07.png", "selectedIconPath": "static/home/08.png" } - ] + ], + "color": "" }, "globalStyle": { "navigationBarTextStyle": "black", diff --git a/pages/index/index.vue b/pages/index/index.vue index 4cb1463..0d19007 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,310 +1,405 @@ - - - - 全部消息 - - - 清除未读 - - + + + + 全部消息 + + + 清除未读 + + - - - - - - - - - - - - - 系统消息 - {{system.time}} - - - 您有{{system.num}}条未读信息 - - - 暂无新消息 - - - + + + + + + + + + + + + + 系统消息 + {{system.time}} + + + 您有{{system.num}}条未读信息 + + + 暂无新消息 + + + - - - - - - - - - - - - 产品订单 - {{order.time}} - - - 您有{{order.num}}条未读信息 - - - 暂无新消息 - - - + + + + + + + + + + + + 产品订单 + {{order.time}} + + + 您有{{order.num}}条未读信息 + + + 暂无新消息 + + + - - - - - - - - - - - - 医生消息 - {{doctor.time}} - - - 您有{{doctor.num}}条新的消息 - - - 暂无新消息 - - - - + + + + + + + + + + + + 医生消息 + {{doctor.time}} + + + 您有{{doctor.num}}条新的消息 + + + 暂无新消息 + + + + + + + + + + + + + + + + + 在线问诊 + {{doctor.time}} + + + 您有{{doctor.num}}条新的消息 + + + 暂无新消息 + + + + + + + + + 在线问诊 + + + + + + + + + + + + + + + + + + {{ item.nick_name }} + {{item.last_message_time}} + + + {{ item.last_message }} + 您有{{item.un_read_num}}条新的消息 + + + 暂无新消息 + + + + \ No newline at end of file diff --git a/request/api/im.js b/request/api/im.js index 30e9975..e8b24c5 100644 --- a/request/api/im.js +++ b/request/api/im.js @@ -3,4 +3,15 @@ import http from './request' // export async function sendMsg(params) { // let data = await http('/v1/im/message-send', params) // return data -// } \ No newline at end of file +// } +// 登录 手机号授权 +export async function sendToUserApi(params) { + return await http('/imApi/send-to-user', { + method: 'POST', + data: params, + // 传输方式是json + headers: { + 'Content-Type': 'application/json' + } + }, 1) +} \ No newline at end of file diff --git a/request/api/request.js b/request/api/request.js index 6f73f2d..496d64c 100644 --- a/request/api/request.js +++ b/request/api/request.js @@ -18,14 +18,18 @@ const sensitiveData = [ 'idcard' ] -function request(url, params) { - let baseURL = "https://app.xiaokang88.com/member" - // let baseURL = "http://www.xk888.com/member" +function request(url, params, method = 0) { + // let baseURL = "https://app.xiaokang88.com/member" + let baseURL = "http://127.0.0.1:18000/member" + if (url.split('/').indexOf('imApi') !== -1) { + baseURL = "http://127.0.0.1:12080/api" + } if (url.split('/').indexOf('newApi') !== -1) { baseURL = "https://shop.xiaokang88.com/member" } url = url.replace('/oldApi', ''); url = url.replace('/newApi', ''); + url = url.replace('/imApi', ''); // 加密 if (params != null) { @@ -44,7 +48,7 @@ function request(url, params) { // 请求头 header: { "authorization": 'Bearer ' + uni.getStorageSync("token"), - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": method === 0? "application/x-www-form-urlencoded": 'application/json' }, // 成功钩子 diff --git a/store/chat/chat.js b/store/chat/chat.js new file mode 100644 index 0000000..7ebd08e --- /dev/null +++ b/store/chat/chat.js @@ -0,0 +1,81 @@ +// chat.js +// 聊天消息管理器 +const ChatManager = { + // 当前房间ID + currentRoomId: null, + + // 存储未读消息数 { roomId: count } + unreadCounts: {}, + + // 进入聊天室 + enterRoom(roomId) { + this.currentRoomId = roomId; + uni.setStorageSync('currentRoomId', roomId); + console.log(`进入房间: ${roomId}`); + }, + + // 离开聊天室 + leaveRoom() { + this.currentRoomId = null; + uni.removeStorageSync('currentRoomId'); + console.log('离开当前房间'); + }, + + // 获取当前房间ID + getCurrentRoomId() { + return this.currentRoomId || uni.getStorageSync('currentRoomId'); + }, + + // 增加未读消息数 + increaseUnreadCount(roomId) { + // 如果当前不在该房间,才增加未读计数 + if (this.getCurrentRoomId() !== roomId) { + this.unreadCounts[roomId] = (this.unreadCounts[roomId] || 0) + 1; + this.notifyUnreadChange(); + console.log(`房间 ${roomId} 未读消息 +1, 当前: ${this.unreadCounts[roomId]}`); + } + }, + + // 重置未读消息数 + resetUnreadCount(roomId) { + if (this.unreadCounts[roomId]) { + this.unreadCounts[roomId] = 0; + this.notifyUnreadChange(); + console.log(`重置房间 ${roomId} 未读消息数`); + } + }, + + // 获取未读消息数 + getUnreadCount(roomId) { + return this.unreadCounts[roomId] || 0; + }, + + // 获取所有未读消息数 + getAllUnreadCount() { + return Object.values(this.unreadCounts).reduce((sum, count) => sum + count, 0); + }, + + // 通知未读消息变化 + notifyUnreadChange() { + uni.$emit('unread-message-update', { + total: this.getAllUnreadCount(), + byRoom: {...this.unreadCounts} + }); + }, + + // 处理收到的消息 + handleIncomingMessage(message) { + const currentRoomId = this.getCurrentRoomId(); + + // 如果当前在消息所属的房间,直接显示 + if (currentRoomId === message.room_id) { + // 通知聊天页面显示消息 + uni.$emit('new-message', message); + } else { + // 不在当前房间,增加未读消息数 + this.increaseUnreadCount(message.room_id); + } + } +}; + +export { ChatManager }; \ No newline at end of file diff --git a/store/modules/user.js b/store/modules/user.js index 70e48cb..f24529f 100644 --- a/store/modules/user.js +++ b/store/modules/user.js @@ -37,7 +37,7 @@ // state, // dispatch // }, res) { -// console.log('user-123'); +// console.log('user-252'); // // 存到状态中 // state.user = res // state.user_token = uni.getStorageSync('token'); diff --git a/subPackages/chat/chat.vue b/subPackages/chat/chat.vue new file mode 100644 index 0000000..dd8bef2 --- /dev/null +++ b/subPackages/chat/chat.vue @@ -0,0 +1,1293 @@ + + + + + + + {{ doctorUserInfo.nick_name }} + 在线 · 心血管内科 + + + + + + + + + + + + 职称: + 主任医师 + + + 医院: + 北京协和医院 + + + 擅长: + 冠心病、高血压、心力衰竭 + + + + + + + + + + + + + + + + + + {{ msg.duration }}'' + + + + + {{ msg.content }} + + {{ msg.time }} + + + + + + + + + + + + + + + {{ msg.duration }}'' + + + {{ msg.content }} + + {{ msg.time }} + + + + + + + + + + + + + 拍照 + + + + 图片 + + + + 视频 + + + + 文件 + + + + 处方 + + + + + + + + + + + + + + + + + + + + + + + {{ recording ? '松开结束' : '按住说话' }} + + + {{ recordingTime }}s + + + + + + + + + + + + + + + + + + 录音中... {{ recordingTime }}秒 + 上划取消发送 + + + + + + + + \ No newline at end of file diff --git a/subPackages/doctor/doctor-detail.vue b/subPackages/doctor/doctor-detail.vue index 2a1a6aa..4f8d4f2 100644 --- a/subPackages/doctor/doctor-detail.vue +++ b/subPackages/doctor/doctor-detail.vue @@ -102,7 +102,7 @@ registerList, storeChange, userList - } from '../../request/api/api' + } from '@/request/api/api' export default { data() { return { diff --git a/subPackages/doctor/doctor-userinfo.vue b/subPackages/doctor/doctor-userinfo.vue index a207445..8ffa81e 100644 --- a/subPackages/doctor/doctor-userinfo.vue +++ b/subPackages/doctor/doctor-userinfo.vue @@ -519,7 +519,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -573,7 +573,7 @@ display: flex; flex-wrap: wrap; - /deep/ .u-tag { + ::v-deep .u-tag { margin: 10rpx 20rpx 10rpx 0; } @@ -615,11 +615,11 @@ margin-top: 2rpx; } - /deep/ .u-input { + ::v-deep .u-input { margin-left: 130rpx; } - /deep/ .u-input__right-icon ::before { + ::v-deep .u-input__right-icon ::before { content: "" } @@ -671,7 +671,7 @@ .u_inp { margin: 40rpx 0; - /deep/ .u-input { + ::v-deep .u-input { background: #F1F5F9; } } diff --git a/subPackages/my/myinfo-add.vue b/subPackages/my/myinfo-add.vue index e276d09..6c5f050 100644 --- a/subPackages/my/myinfo-add.vue +++ b/subPackages/my/myinfo-add.vue @@ -634,7 +634,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 400rpx; } } @@ -657,7 +657,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -740,11 +740,11 @@ margin-top: 2rpx; } - /deep/ .u-input { + ::v-deep .u-input { margin-left: 130rpx; } - /deep/ .u-input__right-icon ::before { + ::v-deep .u-input__right-icon ::before { content: "" } @@ -830,7 +830,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -884,7 +884,7 @@ display: flex; flex-wrap: wrap; - /deep/ .u-tag { + ::v-deep .u-tag { margin: 10rpx 20rpx 10rpx 0; } @@ -926,11 +926,11 @@ margin-top: 2rpx; } - /deep/ .u-input { + ::v-deep .u-input { margin-left: 130rpx; } - /deep/ .u-input__right-icon ::before { + ::v-deep .u-input__right-icon ::before { content: "" } @@ -982,7 +982,7 @@ .u_inp { margin: 40rpx 0; - /deep/ .u-input { + ::v-deep .u-input { background: #F1F5F9; } } diff --git a/subPackages/my/myinfo-edit.vue b/subPackages/my/myinfo-edit.vue index bc72e74..7a5b08b 100644 --- a/subPackages/my/myinfo-edit.vue +++ b/subPackages/my/myinfo-edit.vue @@ -723,7 +723,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -746,7 +746,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -828,11 +828,11 @@ margin-top: 2rpx; } - /deep/ .u-input { + ::v-deep .u-input { margin-left: 130rpx; } - /deep/ .u-input__right-icon ::before { + ::v-deep .u-input__right-icon ::before { content: "" } @@ -918,7 +918,7 @@ color: #1E293B; } - /deep/ .u-input { + ::v-deep .u-input { width: 380rpx; } } @@ -972,7 +972,7 @@ display: flex; flex-wrap: wrap; - /deep/ .u-tag { + ::v-deep .u-tag { margin: 10rpx 20rpx 10rpx 0; } @@ -1014,11 +1014,11 @@ margin-top: 2rpx; } - /deep/ .u-input { + ::v-deep .u-input { margin-left: 130rpx; } - /deep/ .u-input__right-icon ::before { + ::v-deep .u-input__right-icon ::before { content: "" } @@ -1070,7 +1070,7 @@ .u_inp { margin: 40rpx 0; - /deep/ .u-input { + ::v-deep .u-input { background: #F1F5F9; } } diff --git a/utils/ws/websocket.js b/utils/ws/websocket.js new file mode 100644 index 0000000..4a6985b --- /dev/null +++ b/utils/ws/websocket.js @@ -0,0 +1,181 @@ +export class WebSocketManager { + constructor(url) { + this.url = url; + this.socket = null; + this.isConnected = false; + this.reconnectAttempts = 0; + this.maxReconnectAttempts = 5; + this.reconnectDelay = 1000; + this.messageHandlers = []; + this.pingInterval = null; + } + + connect() { + if (this.isConnected) return; + + console.log('正在连接WebSocket...'); + this.socket = uni.connectSocket({ + url: this.url, + success: () => { + console.log('WebSocket连接成功'); + }, + fail: (err) => { + console.error('WebSocket连接失败:', err); + this.handleReconnect(); + } + }); + + this.socket.onOpen(() => { + console.log('WebSocket连接已打开'); + this.isConnected = true; + this.reconnectAttempts = 0; + + // 开始心跳检测 + this.startHeartbeat(); + + // 绑定当前用户 + const userId = uni.getStorageSync('userId'); + if (userId) { + this.bindUser(userId); + } + }); + + this.socket.onMessage((res) => { + try { + const data = JSON.parse(res.data); + console.log('收到消息:', data); + this.handleMessage(data); + } catch (e) { + console.error('解析消息失败:', e); + } + }); + + this.socket.onClose((res) => { + console.log('WebSocket连接关闭:', res); + this.isConnected = false; + this.stopHeartbeat(); + this.handleReconnect(); + }); + + this.socket.onError((err) => { + console.error('WebSocket错误:', err); + }); + } + + bindUser(userId) { + this.send({ + type: 'bind', + userId: userId + }); + } + + send(message) { + if (!this.isConnected) { + console.error('WebSocket未连接'); + return false; + } + + try { + const messageStr = JSON.stringify(message); + this.socket.send({ + data: messageStr, + success: () => { + console.log('消息发送成功:', message); + }, + fail: (err) => { + console.error('消息发送失败:', err); + } + }); + return true; + } catch (err) { + console.error('发送消息出错:', err); + return false; + } + } + + addMessageHandler(handler) { + this.messageHandlers.push(handler); + } + + removeMessageHandler(handler) { + const index = this.messageHandlers.indexOf(handler); + if (index !== -1) { + this.messageHandlers.splice(index, 1); + } + } + + handleMessage(data) { + // 如果对象中有clientId则给websocket发送 + if (data.clientId) { + const userId = '252'; // 实际项目中从存储获取 + const bindMessage = { + request_type: 'bind', + user_type: 'user', + sender_user_id: `user-${userId}`, + }; + this.send(bindMessage); + } + + // 处理聊天消息 + if (data.message_type !== undefined) { + // 将消息转发给聊天管理器 + this.messageHandlers.forEach(handler => { + try { + handler(data); + } catch (e) { + console.error('消息处理出错:', e); + } + }); + } else { + // 其他消息处理 + this.messageHandlers.forEach(handler => { + try { + handler(data); + } catch (e) { + console.error('消息处理出错:', e); + } + }); + } + } + + startHeartbeat() { + this.pingInterval = setInterval(() => { + if (this.isConnected) { + this.send({type: 'ping'}); + } + }, 300000); + } + + stopHeartbeat() { + if (this.pingInterval) { + clearInterval(this.pingInterval); + this.pingInterval = null; + } + } + + handleReconnect() { + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + console.log('已达到最大重连次数'); + return; + } + + setTimeout(() => { + this.reconnectAttempts++; + this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000); + console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); + this.connect(); + }, this.reconnectDelay); + } + + disconnect() { + if (this.socket) { + this.socket.close(); + this.socket = null; + } + this.isConnected = false; + this.stopHeartbeat(); + } +} + +// 全局WebSocket实例 +export const webSocketManager = new WebSocketManager('ws://127.0.0.1:12080/ws'); \ No newline at end of file
{{ item.last_message }}
您有{{item.un_read_num}}条新的消息