- 创建信息记录文档,包含项目管理要求、产物范围、Git仓库、制品仓库信息 - 添加服务器部署信息,包括应用服务器、MySQL/Redis服务器、Jenkins服务配置 - 记录邮件服务、DNS/HTTPS证书配置及安全备注 - 创建API联调文档,包含线上入口、ID约定、初始化管理员账号信息 - 添加统一响应格式、常见错误码、鉴权规则说明 - 提供核心接口清单,涵盖tenant-service、im-service、push-service等服务 - 补充curl示例,包含运营平台登录、IM登录、会话管理等操作示例 - 实现会话控制器,支持置顶、免打扰、标记已读、草稿等功能 - 添加全局异常处理器,统一处理业务异常和参数校验错误 - 创建IM管理控制器,提供用户管理、好友请求、黑名单等管理功能
258 行
10 KiB
Vue
258 行
10 KiB
Vue
<template>
|
|
<div v-if="app">
|
|
<el-page-header @back="$router.back()" content="IM 服务配置" style="margin-bottom:24px" />
|
|
|
|
<el-card style="margin-bottom:16px">
|
|
<el-descriptions :column="2" border>
|
|
<el-descriptions-item label="应用名称">{{ app.name }}</el-descriptions-item>
|
|
<el-descriptions-item label="包名">{{ app.packageName }}</el-descriptions-item>
|
|
<el-descriptions-item label="AppKey">
|
|
<el-text class="mono">{{ app.appKey }}</el-text>
|
|
<el-button link @click="copy(app.appKey)"><el-icon><CopyDocument /></el-icon></el-button>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="服务状态">
|
|
<el-tag :type="imEnabled ? 'success' : 'info'">{{ imEnabled ? '已开通' : '未开通' }}</el-tag>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
</el-card>
|
|
|
|
<el-card style="margin-bottom:16px">
|
|
<template #header>即时通讯服务</template>
|
|
<div class="service-header">
|
|
<span class="service-name">即时通讯 (IM)</span>
|
|
<el-switch :model-value="imEnabled" @change="(val: boolean) => onToggleImService(val)" />
|
|
</div>
|
|
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
|
<!-- 服务配置页使用的是租户应用主键 app.id;IM 管理页才带 appKey。 -->
|
|
<el-button size="small" @click="$router.push({ path: `/apps/${route.params.appId}/im`, query: { appKey: app.appKey } })">
|
|
即时通讯管理 →
|
|
</el-button>
|
|
<el-button size="small" @click="$router.push(`/apps/${route.params.appId}`)">
|
|
返回详情
|
|
</el-button>
|
|
</div>
|
|
</el-card>
|
|
|
|
<el-card style="margin-bottom:16px">
|
|
<template #header>登录与消息</template>
|
|
<div class="service-config-list">
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">允许陌生人发消息</div>
|
|
<div class="service-config-desc">关闭后,仅好友之间可以发送单聊消息。</div>
|
|
</div>
|
|
<el-switch
|
|
:model-value="imConfig.allowStrangerMessage"
|
|
@change="(val: boolean) => onToggleImConfig({ allowStrangerMessage: val })"
|
|
/>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">黑名单检查</div>
|
|
<div class="service-config-desc">控制对方拉黑后,发送方是否仍显示发送成功。</div>
|
|
</div>
|
|
<el-switch
|
|
:model-value="imConfig.blacklistSendSuccess"
|
|
@change="(val: boolean) => onToggleImConfig({ blacklistSendSuccess: val })"
|
|
/>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">消息可撤回时长</div>
|
|
<div class="service-config-desc">单位分钟,默认 2 分钟。</div>
|
|
</div>
|
|
<el-input-number
|
|
:model-value="imConfig.messageRecallMinutes"
|
|
:min="0"
|
|
:max="1440"
|
|
controls-position="right"
|
|
@change="(val: number | undefined) => onToggleImConfig({ messageRecallMinutes: val ?? 2 })"
|
|
/>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">历史消息存储时长</div>
|
|
<div class="service-config-desc">单位天,默认 7 天。</div>
|
|
</div>
|
|
<el-input-number
|
|
:model-value="imConfig.historyRetentionDays"
|
|
:min="1"
|
|
:max="3650"
|
|
controls-position="right"
|
|
@change="(val: number | undefined) => onToggleImConfig({ historyRetentionDays: val ?? 7 })"
|
|
/>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">会话列表拉取个数</div>
|
|
<div class="service-config-desc">云端会话拉取上限,默认 100,最大 500。</div>
|
|
</div>
|
|
<el-input-number
|
|
:model-value="imConfig.conversationPullLimit"
|
|
:min="1"
|
|
:max="500"
|
|
controls-position="right"
|
|
@change="(val: number | undefined) => onToggleImConfig({ conversationPullLimit: val ?? 100 })"
|
|
/>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">删除会话后多端同步</div>
|
|
<div class="service-config-desc">一端删除会话后是否同步到其他端。</div>
|
|
</div>
|
|
<el-switch
|
|
:model-value="imConfig.multiClientConversationDeleteSync"
|
|
@change="(val: boolean) => onToggleImConfig({ multiClientConversationDeleteSync: val })"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
|
|
<el-card>
|
|
<template #header>好友与关系链</template>
|
|
<div class="service-config-list">
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">默认加好友验证方式</div>
|
|
<div class="service-config-desc">对应腾讯控制台的好友关系链验证方式设置。</div>
|
|
</div>
|
|
<el-select
|
|
:model-value="imConfig.friendRequestMode"
|
|
style="width: 180px"
|
|
@change="(val: ImServiceConfig['friendRequestMode']) => onToggleImConfig({ friendRequestMode: val, allowFriendRequest: val !== 'DISALLOW' })"
|
|
>
|
|
<el-option label="需要验证" value="REQUIRE_CONFIRM" />
|
|
<el-option label="自动通过" value="DIRECT_ACCEPT" />
|
|
<el-option label="不允许加好友" value="DISALLOW" />
|
|
</el-select>
|
|
</div>
|
|
<div class="service-config-row">
|
|
<div>
|
|
<div class="service-config-title">允许群加入申请</div>
|
|
<div class="service-config-desc">关闭后,用户不能向公开群发送入群申请。</div>
|
|
</div>
|
|
<el-switch
|
|
:model-value="imConfig.allowGroupJoinRequest"
|
|
@change="(val: boolean) => onToggleImConfig({ allowGroupJoinRequest: val })"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { appApi, type App, type FeatureService, type ImServiceConfig } from '@/api/app'
|
|
|
|
const route = useRoute()
|
|
const app = ref<App | null>(null)
|
|
const services = ref<FeatureService[]>([])
|
|
const imService = computed(() => services.value.find(s => s.serviceType === 'IM') ?? null)
|
|
const imEnabled = computed(() => imService.value?.enabled ?? false)
|
|
const imConfig = computed<ImServiceConfig>(() => {
|
|
const service = imService.value
|
|
if (!service?.config) {
|
|
return defaultImConfig()
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(service.config) as Partial<ImServiceConfig>
|
|
return {
|
|
allowStrangerMessage: parsed.allowStrangerMessage ?? false,
|
|
allowFriendRequest: parsed.allowFriendRequest ?? true,
|
|
friendRequestMode: normalizeFriendRequestMode(parsed.friendRequestMode, parsed.allowFriendRequest),
|
|
allowGroupJoinRequest: parsed.allowGroupJoinRequest ?? true,
|
|
blacklistSendSuccess: parsed.blacklistSendSuccess ?? true,
|
|
messageRecallMinutes: parsed.messageRecallMinutes ?? 2,
|
|
historyRetentionDays: parsed.historyRetentionDays ?? 7,
|
|
conversationPullLimit: parsed.conversationPullLimit ?? 100,
|
|
multiClientConversationDeleteSync: parsed.multiClientConversationDeleteSync ?? false,
|
|
}
|
|
} catch {
|
|
return defaultImConfig()
|
|
}
|
|
})
|
|
|
|
function defaultImConfig(): ImServiceConfig {
|
|
return {
|
|
allowStrangerMessage: false,
|
|
allowFriendRequest: true,
|
|
friendRequestMode: 'REQUIRE_CONFIRM',
|
|
allowGroupJoinRequest: true,
|
|
blacklistSendSuccess: true,
|
|
messageRecallMinutes: 2,
|
|
historyRetentionDays: 7,
|
|
conversationPullLimit: 100,
|
|
multiClientConversationDeleteSync: false,
|
|
}
|
|
}
|
|
|
|
function normalizeFriendRequestMode(
|
|
mode: ImServiceConfig['friendRequestMode'] | string | undefined,
|
|
allowFriendRequest: boolean | undefined,
|
|
): ImServiceConfig['friendRequestMode'] {
|
|
const normalized = (mode ?? '').toString().trim().toUpperCase()
|
|
if (normalized === 'DIRECT_ACCEPT' || normalized === 'DISALLOW' || normalized === 'REQUIRE_CONFIRM') {
|
|
return normalized
|
|
}
|
|
return allowFriendRequest === false ? 'DISALLOW' : 'REQUIRE_CONFIRM'
|
|
}
|
|
|
|
async function loadData() {
|
|
const id = route.params.appId as string
|
|
const [appRes, svcRes] = await Promise.all([
|
|
appApi.get(id),
|
|
appApi.getServices(id),
|
|
])
|
|
app.value = appRes.data.data
|
|
services.value = svcRes.data.data
|
|
}
|
|
|
|
function imServicePlatform() {
|
|
return imService.value?.platform ?? 'ANDROID'
|
|
}
|
|
|
|
async function onToggleImService(enable: boolean) {
|
|
if (enable) {
|
|
return ElMessage.info('请通过服务开通流程启用 IM')
|
|
}
|
|
await ElMessageBox.confirm('确认关闭 即时通讯 服务?', '关闭服务', {
|
|
type: 'warning',
|
|
confirmButtonText: '确认关闭',
|
|
cancelButtonText: '取消',
|
|
})
|
|
await appApi.toggleService(route.params.appId as string, imServicePlatform(), 'IM', false)
|
|
ElMessage.success('已关闭')
|
|
loadData()
|
|
}
|
|
|
|
async function onToggleImConfig(patch: Partial<ImServiceConfig>) {
|
|
const nextConfig: ImServiceConfig = {
|
|
...imConfig.value,
|
|
...patch,
|
|
}
|
|
await appApi.updateServiceConfig(route.params.appId as string, imServicePlatform(), 'IM', nextConfig)
|
|
ElMessage.success('IM 配置已更新')
|
|
loadData()
|
|
}
|
|
|
|
function copy(text: string) {
|
|
navigator.clipboard.writeText(text)
|
|
ElMessage.success('已复制')
|
|
}
|
|
|
|
onMounted(loadData)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.mono { font-family: monospace; font-size: 12px; }
|
|
.service-header { display: flex; justify-content: space-between; align-items: center; font-weight: 500; }
|
|
.service-name { font-size: 15px; }
|
|
.service-config-list { display: flex; flex-direction: column; gap: 12px; }
|
|
.service-config-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; font-size: 13px; color: #666; }
|
|
.service-config-title { font-size: 13px; font-weight: 500; color: #303133; line-height: 1.4; }
|
|
.service-config-desc { margin-top: 4px; font-size: 12px; color: #909399; line-height: 1.4; }
|
|
</style>
|