XuqmGroup-Web/tenant-platform/src/views/im/ImConfigView.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.idIM 管理页才带 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>