feat(im): 添加即时通讯SDK核心功能

- 实现IM API接口定义,包括消息、群组、好友、黑名单等功能
- 定义IM消息相关数据模型,包含聊天类型、消息类型、用户资料等
- 实现ImSDK单例类,提供登录、消息发送、群组管理、好友管理等核心功能
- 添加WebSocket连接管理,支持自动重连机制
- 实现历史消息查询、群组操作、用户资料管理等API调用
- 添加会话状态管理,支持置顶、静音、草稿等功能
- 集成文件上传结果,支持多媒体消息发送
- 实现连接状态监听和事件回调机制
这个提交包含在:
XuqmGroup 2026-04-28 21:05:07 +08:00
父节点 867227fc51
当前提交 54abc82e46
共有 3 个文件被更改,包括 242 次插入1 次删除

查看文件

@ -17,6 +17,7 @@ declare module 'vue' {
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']

查看文件

@ -61,6 +61,31 @@ export interface RnBundle {
createdAt: string
}
export interface UnifiedAppUploadItem {
fileKey: string
platform: 'ANDROID' | 'IOS'
versionName: string
versionCode: number
changeLog?: string
forceUpdate: boolean
appStoreUrl?: string
marketUrl?: string
}
export interface UnifiedRnUploadItem {
fileKey: string
moduleId: string
platform: 'ANDROID' | 'IOS'
version: string
minCommonVersion?: string
note?: string
}
export interface UnifiedReleaseManifest {
appVersions: UnifiedAppUploadItem[]
rnBundles: UnifiedRnUploadItem[]
}
export const updateAdminApi = {
listAppVersions(appId: string, platform: 'ANDROID' | 'IOS') {
return updateClient.get<{ data: AppVersion[] }>('/api/v1/updates/app/list', {
@ -109,4 +134,10 @@ export const updateAdminApi = {
headers: { 'Content-Type': 'multipart/form-data' },
})
},
uploadUnifiedRelease(formData: FormData) {
return updateClient.post('/api/v1/updates/unified/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
},
}

查看文件

@ -3,6 +3,9 @@
<el-page-header @back="$router.back()" :content="`版本管理 — ${appId}`" style="margin-bottom:20px" />
<el-card>
<div class="toolbar" style="margin-bottom: 16px">
<el-button type="primary" @click="showUnifiedUpload = true">一键上传</el-button>
</div>
<el-tabs v-model="activeTab">
<!-- App Versions -->
<el-tab-pane label="App 整包版本" name="app">
@ -186,6 +189,73 @@
<el-button type="primary" @click="submitRnUpload" :loading="uploadingRn">上传</el-button>
</template>
</el-dialog>
<!-- Unified Release Dialog -->
<el-dialog v-model="showUnifiedUpload" title="一键上传" width="920px">
<el-form label-width="110px">
<el-divider content-position="left">Android / iOS 整包</el-divider>
<div class="unified-grid">
<div v-for="item in unifiedAppForms" :key="item.platform" class="unified-block">
<div class="unified-block-title">{{ item.platform === 'ANDROID' ? 'Android' : 'iOS' }}</div>
<el-form-item label="启用">
<el-switch v-model="item.enabled" />
</el-form-item>
<template v-if="item.enabled">
<el-form-item label="版本名称"><el-input v-model="item.versionName" /></el-form-item>
<el-form-item label="版本码"><el-input-number v-model="item.versionCode" :min="1" /></el-form-item>
<el-form-item label="强制更新"><el-switch v-model="item.forceUpdate" /></el-form-item>
<el-form-item label="更新说明"><el-input v-model="item.changeLog" type="textarea" :rows="2" /></el-form-item>
<el-form-item label="包文件">
<el-upload
:auto-upload="false"
:limit="1"
:on-change="f => item.file = f.raw ?? null"
:accept="item.platform === 'ANDROID' ? '.apk' : '.ipa'">
<el-button>选择文件</el-button>
</el-upload>
</el-form-item>
</template>
</div>
</div>
<el-divider content-position="left">
Bundle 插件上传
<el-button link type="primary" @click="addUnifiedBundle" style="margin-left:8px">新增插件</el-button>
</el-divider>
<div v-for="(item, index) in unifiedBundleForms" :key="item.key" class="unified-bundle-row">
<div class="unified-bundle-head">
<div>插件 {{ index + 1 }}</div>
<el-button link type="danger" @click="removeUnifiedBundle(index)" :disabled="unifiedBundleForms.length === 1">删除</el-button>
</div>
<div class="unified-grid">
<el-form-item label="模块ID"><el-input v-model="item.moduleId" /></el-form-item>
<el-form-item label="平台">
<el-select v-model="item.platform">
<el-option value="ANDROID" label="Android" />
<el-option value="IOS" label="iOS" />
</el-select>
</el-form-item>
<el-form-item label="版本"><el-input v-model="item.version" /></el-form-item>
<el-form-item label="最低 Common 版本"><el-input v-model="item.minCommonVersion" /></el-form-item>
<el-form-item label="说明"><el-input v-model="item.note" type="textarea" :rows="2" /></el-form-item>
<el-form-item label="Bundle 文件">
<el-upload
:auto-upload="false"
:limit="1"
:on-change="f => item.file = f.raw ?? null"
accept=".bundle,.js">
<el-button>选择文件</el-button>
</el-upload>
</el-form-item>
</div>
</div>
</el-form>
<template #footer>
<el-button @click="showUnifiedUpload = false">取消</el-button>
<el-button type="primary" @click="submitUnifiedUpload" :loading="uploadingUnified">上传</el-button>
</template>
</el-dialog>
</div>
</template>
@ -200,7 +270,7 @@ const appId = route.params.appId as string
const activeTab = ref('app')
const appPlatform = ref<'ANDROID' | 'IOS'>('ANDROID')
const rnPlatform = ref('')
const rnPlatform = ref<'ANDROID' | 'IOS' | ''>('')
const rnModuleFilter = ref('')
const appVersions = ref<AppVersion[]>([])
@ -235,6 +305,42 @@ const rnUploadForm = ref({
file: null as File | null,
})
const showUnifiedUpload = ref(false)
const uploadingUnified = ref(false)
const unifiedAppForms = ref([
{
key: 'ANDROID',
enabled: true,
platform: 'ANDROID' as const,
versionName: '',
versionCode: 1,
forceUpdate: false,
changeLog: '',
file: null as File | null,
},
{
key: 'IOS',
enabled: true,
platform: 'IOS' as const,
versionName: '',
versionCode: 1,
forceUpdate: false,
changeLog: '',
file: null as File | null,
},
])
const unifiedBundleForms = ref([
{
key: 'bundle-0',
moduleId: '',
platform: 'ANDROID' as 'ANDROID' | 'IOS',
version: '',
minCommonVersion: '',
note: '',
file: null as File | null,
},
])
function formatTime(t: string) {
return t ? new Date(t).toLocaleString('zh-CN') : '-'
}
@ -356,6 +462,86 @@ async function submitRnUpload() {
} finally { uploadingRn.value = false }
}
function addUnifiedBundle() {
unifiedBundleForms.value.push({
key: `bundle-${Date.now()}-${unifiedBundleForms.value.length}`,
moduleId: '',
platform: 'ANDROID',
version: '',
minCommonVersion: '',
note: '',
file: null,
})
}
function removeUnifiedBundle(index: number) {
unifiedBundleForms.value.splice(index, 1)
if (unifiedBundleForms.value.length === 0) {
addUnifiedBundle()
}
}
async function submitUnifiedUpload() {
const appItems = unifiedAppForms.value
.filter(item => item.enabled && item.file)
.map(item => ({
fileKey: item.key,
platform: item.platform,
versionName: item.versionName,
versionCode: item.versionCode,
changeLog: item.changeLog || undefined,
forceUpdate: item.forceUpdate,
}))
const bundleItems = unifiedBundleForms.value
.filter(item => item.file)
.map(item => ({
fileKey: item.key,
moduleId: item.moduleId,
platform: item.platform,
version: item.version,
minCommonVersion: item.minCommonVersion || undefined,
note: item.note || undefined,
}))
if (!appItems.length && !bundleItems.length) {
ElMessage.warning('请至少选择一个 App 包或 Bundle 文件')
return
}
for (const item of unifiedAppForms.value) {
if (item.enabled && item.file && (!item.versionName || !item.versionCode)) {
ElMessage.warning('请填写整包版本信息')
return
}
}
for (const item of unifiedBundleForms.value) {
if (item.file && (!item.moduleId || !item.version)) {
ElMessage.warning('请填写 Bundle 版本信息')
return
}
}
uploadingUnified.value = true
try {
const fd = new FormData()
fd.append('appId', appId)
fd.append('manifest', JSON.stringify({ appVersions: appItems, rnBundles: bundleItems }))
for (const item of unifiedAppForms.value) {
if (item.enabled && item.file) fd.append(item.key, item.file)
}
for (const item of unifiedBundleForms.value) {
if (item.file) fd.append(item.key, item.file)
}
await updateAdminApi.uploadUnifiedRelease(fd)
ElMessage.success('一键上传成功')
showUnifiedUpload.value = false
loadAppVersions()
loadRnBundles()
} finally {
uploadingUnified.value = false
}
}
onMounted(() => {
loadAppVersions()
loadRnBundles()
@ -364,4 +550,27 @@ onMounted(() => {
<style scoped>
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.unified-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.unified-block,
.unified-bundle-row {
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
padding: 12px;
background: var(--el-bg-color-page);
}
.unified-block-title,
.unified-bundle-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
font-weight: 600;
}
.unified-bundle-row + .unified-bundle-row {
margin-top: 12px;
}
</style>