commit 5ec928da8146f8560badd71e4b5fcc87a281cafb Author: 徐勤民 Date: Thu Apr 30 19:35:55 2026 +0800 feat: MiniProgram IM Demo - login + chat pages diff --git a/app.js b/app.js new file mode 100644 index 0000000..cb57635 --- /dev/null +++ b/app.js @@ -0,0 +1,15 @@ +const localIP = '10.222.233.79' + +App({ + globalData: { + sdk: null, + userId: '', + token: '', + baseUrl: `http://${localIP}:8082`, + wsUrl: `ws://${localIP}:8082/ws/im`, + }, + + onLaunch() { + console.log('MiniProgram Demo launched') + }, +}) diff --git a/app.json b/app.json new file mode 100644 index 0000000..26702f9 --- /dev/null +++ b/app.json @@ -0,0 +1,14 @@ +{ + "pages": [ + "pages/login/login", + "pages/chat/chat" + ], + "window": { + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#fff", + "navigationBarTitleText": "Xuqm IM Demo", + "navigationBarTextStyle": "black" + }, + "style": "v2", + "sitemapLocation": "sitemap.json" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..520005a --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "xuqm-im-miniprogram-demo", + "version": "0.1.0", + "description": "XuqmGroup WeChat MiniProgram IM Demo", + "private": true, + "dependencies": { + "xuqm-group-wechat-mini-program-sdk": "file:../XuqmGroup-WeChatMiniProgramSDK" + } +} diff --git a/pages/chat/chat.js b/pages/chat/chat.js new file mode 100644 index 0000000..e03766f --- /dev/null +++ b/pages/chat/chat.js @@ -0,0 +1,104 @@ +const app = getApp() + +Page({ + data: { + userId: '', + connected: false, + activeTab: 'conv', + conversations: [], + friends: [], + messages: [], + currentTarget: '', + currentChatType: 'SINGLE', + inputText: '', + }, + + onLoad() { + const sdk = app.globalData.sdk + const userId = app.globalData.userId + this.setData({ userId }) + + sdk.on('connected', () => this.setData({ connected: true })) + sdk.on('disconnected', () => this.setData({ connected: false })) + sdk.on('message', (msg) => this.handleMessage(msg)) + sdk.on('revoke', ({ msgId }) => this.handleRevoke(msgId)) + + this.loadConversations() + this.loadFriends() + }, + + onUnload() { + app.globalData.sdk?.disconnect() + }, + + handleMessage(msg) { + const messages = [...this.data.messages, msg] + this.setData({ messages }) + }, + + handleRevoke(msgId) { + const messages = this.data.messages.map(m => + m.id === msgId ? { ...m, revoked: true, content: '' } : m + ) + this.setData({ messages }) + }, + + async loadConversations() { + try { + const conversations = await app.globalData.sdk.listConversations() + this.setData({ conversations }) + } catch (err) { + console.error('loadConversations failed', err) + } + }, + + async loadFriends() { + try { + const friends = await app.globalData.sdk.listFriends() + this.setData({ friends }) + } catch (err) { + console.error('loadFriends failed', err) + } + }, + + switchTab(e) { + this.setData({ activeTab: e.currentTarget.dataset.tab }) + }, + + selectConv(e) { + const { id, type } = e.currentTarget.dataset + this.setData({ currentTarget: id, currentChatType: type, messages: [] }) + this.loadHistory(id, type) + }, + + startChat(e) { + const id = e.currentTarget.dataset.id + this.setData({ currentTarget: id, currentChatType: 'SINGLE', messages: [] }) + this.loadHistory(id, 'SINGLE') + }, + + async loadHistory(targetId, chatType) { + try { + const result = await app.globalData.sdk.fetchHistory(targetId, { page: 0, size: 50 }) + this.setData({ messages: result.content || [] }) + } catch (err) { + console.error('loadHistory failed', err) + } + }, + + onInput(e) { + this.setData({ inputText: e.detail.value }) + }, + + sendText() { + const { inputText, currentTarget, currentChatType } = this.data + if (!inputText.trim() || !currentTarget) return + app.globalData.sdk.send({ + toId: currentTarget, + chatType: currentChatType, + msgType: 'TEXT', + content: inputText.trim(), + }) + this.setData({ inputText: '' }) + }, +}) diff --git a/pages/chat/chat.json b/pages/chat/chat.json new file mode 100644 index 0000000..cf95ea0 --- /dev/null +++ b/pages/chat/chat.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "聊天" +} diff --git a/pages/chat/chat.wxml b/pages/chat/chat.wxml new file mode 100644 index 0000000..6905061 --- /dev/null +++ b/pages/chat/chat.wxml @@ -0,0 +1,53 @@ + + + {{connected ? '已连接' : '未连接'}} + 当前用户: {{userId}} + + + + + 会话 + 好友 + + + + + + {{item.targetId}} + {{item.lastMsgContent||'暂无消息'}} + + 暂无会话 + + + + + {{item}} + + 暂无好友 + + + + + + {{currentTarget}} ({{currentChatType==='SINGLE'?'单聊':'群聊'}}) + + + + {{item.fromId||item.fromUserId}} + {{item.revoked?'消息已撤回':item.content}} + + {{item.status}} + {{item.createdAt}} + + + + + + + + + + + + 选择一个会话开始聊天 + diff --git a/pages/chat/chat.wxss b/pages/chat/chat.wxss new file mode 100644 index 0000000..b4c58ad --- /dev/null +++ b/pages/chat/chat.wxss @@ -0,0 +1,152 @@ +.chat-container { + display: flex; + flex-direction: column; + height: 100vh; + background: #f5f7fa; +} +.header { + display: flex; + justify-content: space-between; + padding: 16rpx 24rpx; + background: #fff; + border-bottom: 1rpx solid #eee; +} +.status { + font-size: 28rpx; +} +.online { color: #07c160; } +.offline { color: #e64340; } +.user { font-size: 28rpx; color: #666; } + +.sidebar { + display: flex; + flex-direction: row; + flex: 1; + overflow: hidden; +} +.tab-bar { + width: 160rpx; + background: #fff; + border-right: 1rpx solid #eee; +} +.tab { + padding: 24rpx 0; + text-align: center; + font-size: 28rpx; + color: #666; + border-bottom: 1rpx solid #f0f0f0; +} +.tab.active { + color: #1989fa; + background: #eef5ff; +} +.list { + flex: 1; + padding: 16rpx; +} +.item { + padding: 20rpx; + background: #fff; + border-radius: 8rpx; + margin-bottom: 16rpx; +} +.item-title { + font-size: 30rpx; + font-weight: 500; + color: #333; +} +.item-meta { + font-size: 24rpx; + color: #999; + margin-top: 8rpx; +} +.empty { + text-align: center; + color: #999; + padding: 40rpx; +} + +.chat-area { + flex: 1; + display: flex; + flex-direction: column; +} +.chat-header { + padding: 20rpx; + background: #fff; + text-align: center; + font-size: 30rpx; + border-bottom: 1rpx solid #eee; +} +.message-list { + flex: 1; + padding: 20rpx; +} +.msg-row { + display: flex; + margin-bottom: 20rpx; +} +.msg-row.self { + justify-content: flex-end; +} +.msg-row.other { + justify-content: flex-start; +} +.msg-bubble { + max-width: 70%; + padding: 16rpx 20rpx; + border-radius: 12rpx; + background: #fff; +} +.msg-row.self .msg-bubble { + background: #95ec69; +} +.msg-sender { + font-size: 22rpx; + color: #999; + margin-bottom: 4rpx; +} +.msg-content { + font-size: 30rpx; + color: #333; + word-break: break-all; +} +.msg-meta { + display: flex; + justify-content: space-between; + margin-top: 8rpx; +} +.tag { + font-size: 20rpx; + color: #1989fa; +} +.time { + font-size: 20rpx; + color: #999; +} + +.input-area { + display: flex; + padding: 16rpx; + background: #fff; + border-top: 1rpx solid #eee; +} +.msg-input { + flex: 1; + height: 72rpx; + border: 1rpx solid #ddd; + border-radius: 8rpx; + padding: 0 16rpx; + font-size: 30rpx; +} +.send-btn { + margin-left: 16rpx; +} +.empty-chat { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #999; + font-size: 32rpx; +} diff --git a/pages/login/login.js b/pages/login/login.js new file mode 100644 index 0000000..3be2bba --- /dev/null +++ b/pages/login/login.js @@ -0,0 +1,45 @@ +const app = getApp() + +// 引入 SDK(实际使用时通过 npm 包或复制 dist 文件) +const { XuqmMiniProgramSDK } = require('../../utils/sdk') + +Page({ + data: { + userId: 'user_a', + password: '123456', + loading: false, + error: '', + }, + + onLoad() { + const sdk = new XuqmMiniProgramSDK() + const { baseUrl, wsUrl } = app.globalData + sdk.init({ appKey: 'ak_demo_chat', appSecret: 'secret', debug: true, baseUrl, wsUrl }) + app.globalData.sdk = sdk + }, + + onUserIdInput(e) { + this.setData({ userId: e.detail.value }) + }, + + onPasswordInput(e) { + this.setData({ password: e.detail.value }) + }, + + async onLogin() { + const { userId, password } = this.data + if (!userId || !password) { + this.setData({ error: '请输入用户ID和密码' }) + return + } + this.setData({ loading: true, error: '' }) + try { + const sdk = app.globalData.sdk + await sdk.loginWithDemo(userId, password) + app.globalData.userId = userId + wx.navigateTo({ url: '/pages/chat/chat' }) + } catch (err) { + this.setData({ error: err.message || '登录失败', loading: false }) + } + }, +}) diff --git a/pages/login/login.json b/pages/login/login.json new file mode 100644 index 0000000..92041e3 --- /dev/null +++ b/pages/login/login.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "登录" +} diff --git a/pages/login/login.wxml b/pages/login/login.wxml new file mode 100644 index 0000000..c7eb5c8 --- /dev/null +++ b/pages/login/login.wxml @@ -0,0 +1,7 @@ + + Xuqm IM Demo + + + + {{error}} + diff --git a/pages/login/login.wxss b/pages/login/login.wxss new file mode 100644 index 0000000..a175769 --- /dev/null +++ b/pages/login/login.wxss @@ -0,0 +1,31 @@ +.login-container { + padding: 80rpx 40rpx; + display: flex; + flex-direction: column; + align-items: center; +} +.title { + font-size: 48rpx; + font-weight: bold; + margin-bottom: 60rpx; + color: #333; +} +.input { + width: 100%; + height: 88rpx; + border: 1rpx solid #ddd; + border-radius: 8rpx; + padding: 0 24rpx; + margin-bottom: 24rpx; + font-size: 32rpx; + box-sizing: border-box; +} +.btn { + width: 100%; + margin-top: 24rpx; +} +.error { + color: #e64340; + margin-top: 24rpx; + font-size: 28rpx; +} diff --git a/project.config.json b/project.config.json new file mode 100644 index 0000000..f41d094 --- /dev/null +++ b/project.config.json @@ -0,0 +1,77 @@ +{ + "description": "XuqmGroup WeChat MiniProgram IM Demo", + "packOptions": { + "ignore": [] + }, + "setting": { + "urlCheck": false, + "es6": true, + "enhance": true, + "postcss": true, + "preloadBackgroundData": false, + "minified": true, + "newFeature": false, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "uglifyFileName": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": false, + "lazyloadPlaceholderEnable": false, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "enableEngineNative": false, + "useIsolateContext": true, + "userConfirmedBundleSwitch": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "disableUseStrict": false, + "minifyWXML": true, + "showES6CompileOption": false, + "useCompilerPlugins": false + }, + "compileType": "miniprogram", + "libVersion": "2.32.0", + "appid": "touristappid", + "projectname": "XuqmIMDemo", + "debugOptions": { + "hidedInDevtools": [] + }, + "scripts": {}, + "staticServerOptions": { + "baseURL": "", + "servePath": "" + }, + "isGameTourist": false, + "condition": { + "search": { + "list": [] + }, + "conversation": { + "list": [] + }, + "game": { + "list": [] + }, + "plugin": { + "list": [] + }, + "gamePlugin": { + "list": [] + }, + "miniprogram": { + "list": [] + } + } +} diff --git a/sitemap.json b/sitemap.json new file mode 100644 index 0000000..8a63106 --- /dev/null +++ b/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本小程序的索引", + "rules": [{ + "action": "allow", + "page": "*" + }] +} diff --git a/utils/sdk.js b/utils/sdk.js new file mode 100644 index 0000000..85c92c8 --- /dev/null +++ b/utils/sdk.js @@ -0,0 +1,5 @@ +// 微信小程序 Demo 的 SDK 入口 +// 实际使用时通过 npm install 引入,这里直接引用 SDK 构建产物 +const { XuqmMiniProgramSDK } = require('../miniprogram_npm/xuqm-group-wechat-mini-program-sdk/index.js') + +module.exports = { XuqmMiniProgramSDK }