feat(im): 添加即时通讯SDK核心功能
- 实现IM API接口定义,包括消息、群组、好友、黑名单等功能 - 定义IM消息相关数据模型,包含聊天类型、消息类型、用户资料等 - 实现ImSDK单例类,提供登录、消息发送、群组管理、好友管理等核心功能 - 添加WebSocket连接管理,支持自动重连机制 - 实现历史消息查询、群组操作、用户资料管理等API调用 - 添加会话状态管理,支持置顶、静音、草稿等功能 - 集成文件上传结果,支持多媒体消息发送 - 实现连接状态监听和事件回调机制
这个提交包含在:
父节点
867227fc51
当前提交
54abc82e46
1
tenant-platform/components.d.ts
vendored
1
tenant-platform/components.d.ts
vendored
@ -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>
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户