feat: MiniProgram IM Demo - login + chat pages
这个提交包含在:
当前提交
5ec928da81
15
app.js
普通文件
15
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')
|
||||
},
|
||||
})
|
||||
14
app.json
普通文件
14
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"
|
||||
}
|
||||
9
package.json
普通文件
9
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"
|
||||
}
|
||||
}
|
||||
104
pages/chat/chat.js
普通文件
104
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: '' })
|
||||
},
|
||||
})
|
||||
4
pages/chat/chat.json
普通文件
4
pages/chat/chat.json
普通文件
@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationBarTitleText": "聊天"
|
||||
}
|
||||
53
pages/chat/chat.wxml
普通文件
53
pages/chat/chat.wxml
普通文件
@ -0,0 +1,53 @@
|
||||
<view class="chat-container">
|
||||
<view class="header">
|
||||
<text class="status {{connected ? 'online' : 'offline'}}">{{connected ? '已连接' : '未连接'}}</text>
|
||||
<text class="user">当前用户: {{userId}}</text>
|
||||
</view>
|
||||
|
||||
<view class="sidebar">
|
||||
<view class="tab-bar">
|
||||
<view class="tab {{activeTab==='conv'?'active':''}}" bindtap="switchTab" data-tab="conv">会话</view>
|
||||
<view class="tab {{activeTab==='friend'?'active':''}}" bindtap="switchTab" data-tab="friend">好友</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="list">
|
||||
<block wx:if="{{activeTab==='conv'}}">
|
||||
<view wx:for="{{conversations}}" wx:key="targetId" class="item" bindtap="selectConv" data-id="{{item.targetId}}" data-type="{{item.chatType}}">
|
||||
<view class="item-title">{{item.targetId}}</view>
|
||||
<view class="item-meta">{{item.lastMsgContent||'暂无消息'}}</view>
|
||||
</view>
|
||||
<view wx:if="{{conversations.length===0}}" class="empty">暂无会话</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{activeTab==='friend'}}">
|
||||
<view wx:for="{{friends}}" wx:key="*this" class="item" bindtap="startChat" data-id="{{item}}">
|
||||
<view class="item-title">{{item}}</view>
|
||||
</view>
|
||||
<view wx:if="{{friends.length===0}}" class="empty">暂无好友</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="chat-area" wx:if="{{currentTarget}}">
|
||||
<view class="chat-header">{{currentTarget}} ({{currentChatType==='SINGLE'?'单聊':'群聊'}})</view>
|
||||
<scroll-view scroll-y class="message-list" scroll-into-view="msg-{{messages.length-1}}">
|
||||
<view wx:for="{{messages}}" wx:key="id" id="msg-{{index}}" class="msg-row {{item.fromId===userId?'self':'other'}}">
|
||||
<view class="msg-bubble">
|
||||
<view class="msg-sender">{{item.fromId||item.fromUserId}}</view>
|
||||
<view class="msg-content">{{item.revoked?'消息已撤回':item.content}}</view>
|
||||
<view class="msg-meta">
|
||||
<text class="tag">{{item.status}}</text>
|
||||
<text class="time">{{item.createdAt}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="input-area">
|
||||
<input class="msg-input" placeholder="输入消息" value="{{inputText}}" bindinput="onInput" confirm-type="send" bindconfirm="sendText" />
|
||||
<button class="send-btn" type="primary" size="mini" bindtap="sendText">发送</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="empty-chat">选择一个会话开始聊天</view>
|
||||
</view>
|
||||
152
pages/chat/chat.wxss
普通文件
152
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;
|
||||
}
|
||||
45
pages/login/login.js
普通文件
45
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 })
|
||||
}
|
||||
},
|
||||
})
|
||||
4
pages/login/login.json
普通文件
4
pages/login/login.json
普通文件
@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
7
pages/login/login.wxml
普通文件
7
pages/login/login.wxml
普通文件
@ -0,0 +1,7 @@
|
||||
<view class="login-container">
|
||||
<view class="title">Xuqm IM Demo</view>
|
||||
<input class="input" placeholder="请输入用户ID" value="{{userId}}" bindinput="onUserIdInput" />
|
||||
<input class="input" placeholder="请输入密码" password value="{{password}}" bindinput="onPasswordInput" />
|
||||
<button class="btn" type="primary" bindtap="onLogin" loading="{{loading}}">登录</button>
|
||||
<view wx:if="{{error}}" class="error">{{error}}</view>
|
||||
</view>
|
||||
31
pages/login/login.wxss
普通文件
31
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;
|
||||
}
|
||||
77
project.config.json
普通文件
77
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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
7
sitemap.json
普通文件
7
sitemap.json
普通文件
@ -0,0 +1,7 @@
|
||||
{
|
||||
"desc": "关于本小程序的索引",
|
||||
"rules": [{
|
||||
"action": "allow",
|
||||
"page": "*"
|
||||
}]
|
||||
}
|
||||
5
utils/sdk.js
普通文件
5
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 }
|
||||
正在加载...
在新工单中引用
屏蔽一个用户