Files
sino-mci/admin-web/src/views/ChannelSubject.vue

369 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<PageCard>
<template #toolbar-left>
<el-input
v-model="searchKeyword"
placeholder="搜索主体名称 / 编码 / CorpID"
clearable
class="search-input"
@keyup.enter="handleSearch"
/>
<el-button @click="handleSearch">查询</el-button>
</template>
<template #toolbar-right>
<el-button type="primary" @click="openCreateDialog">新增主体</el-button>
</template>
<el-table
:data="pagedSubjects"
class="sino-table"
@row-click="handleRowClick"
>
<el-table-column prop="subjectName" label="主体名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="channelType" label="渠道类型" width="100">
<template #default="{ row }">
<el-tag :type="row.channelType === 'wecom' ? 'primary' : 'info'" size="small">
{{ channelLabel(row.channelType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="subjectCode" label="主体编码" min-width="140" show-overflow-tooltip />
<el-table-column prop="corpId" label="CorpID" min-width="220" show-overflow-tooltip />
<el-table-column label="状态" width="120">
<template #default="{ row }">
<div class="status-cell" @click.stop>
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="(val: any) => handleStatusChange(row, val)"
/>
<span class="status-text">{{ row.status === 1 ? '启用' : '禁用' }}</span>
</div>
</template>
</el-table-column>
</el-table>
<template #pagination>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="filteredSubjects.length"
layout="total, prev, pager, next"
background
/>
</template>
<!-- 新增弹窗 -->
<el-dialog v-model="dialogVisible" title="新增渠道主体" width="600px" destroy-on-close>
<el-form :model="dialogForm" label-width="120px">
<el-form-item label="渠道">
<el-select v-model="dialogForm.channelType" placeholder="选择渠道" style="width: 100%">
<el-option label="企微" value="wecom" />
<el-option label="钉钉" value="dingtalk" />
<el-option label="飞书" value="feishu" />
</el-select>
</el-form-item>
<el-form-item label="主体编码">
<el-input v-model="dialogForm.subjectCode" placeholder="主体编码" />
</el-form-item>
<el-form-item label="主体名称">
<el-input v-model="dialogForm.subjectName" placeholder="主体名称" />
</el-form-item>
<el-form-item label="CorpID">
<el-input v-model="dialogForm.corpId" placeholder="CorpID" />
</el-form-item>
<el-form-item label="AgentID">
<el-input v-model="dialogForm.agentId" placeholder="AgentID" />
</el-form-item>
<el-form-item label="AgentSecret">
<el-input v-model="dialogForm.agentSecret" placeholder="AgentSecret" />
</el-form-item>
<el-form-item label="会话存档">
<el-switch v-model="dialogForm.sessionArchiveEnabled" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="会话存档私钥">
<el-input v-model="dialogForm.sessionArchivePrivateKey" type="textarea" :rows="3" placeholder="会话存档私钥" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</template>
</el-dialog>
<!-- 详情/编辑右侧抽屉 -->
<el-drawer
v-model="drawerVisible"
:title="`渠道主体详情${currentSubject.subjectName ? ' - ' + currentSubject.subjectName : ''}`"
size="480px"
destroy-on-close
>
<el-form :model="dialogForm" label-width="120px">
<el-form-item label="渠道">
<el-select v-model="dialogForm.channelType" placeholder="选择渠道" style="width: 100%">
<el-option label="企微" value="wecom" />
<el-option label="钉钉" value="dingtalk" />
<el-option label="飞书" value="feishu" />
</el-select>
</el-form-item>
<el-form-item label="主体编码">
<el-input v-model="dialogForm.subjectCode" placeholder="主体编码" />
</el-form-item>
<el-form-item label="主体名称">
<el-input v-model="dialogForm.subjectName" placeholder="主体名称" />
</el-form-item>
<el-form-item label="CorpID">
<el-input v-model="dialogForm.corpId" placeholder="CorpID" />
</el-form-item>
<el-form-item label="AgentID">
<el-input v-model="dialogForm.agentId" placeholder="AgentID" />
</el-form-item>
<el-form-item label="AgentSecret">
<el-input v-model="dialogForm.agentSecret" placeholder="AgentSecret" />
</el-form-item>
<el-form-item label="会话存档">
<el-switch v-model="dialogForm.sessionArchiveEnabled" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="会话存档私钥">
<el-input v-model="dialogForm.sessionArchivePrivateKey" type="textarea" :rows="3" placeholder="会话存档私钥" />
</el-form-item>
</el-form>
<template #footer>
<div class="drawer-footer">
<div class="drawer-actions-left">
<el-button v-if="currentSubject.channelType === 'wecom'" type="warning" plain @click="handleSyncEnterpriseContacts(currentSubject)">同步企业通讯录</el-button>
<el-button type="danger" plain @click="handleDrawerDelete">删除</el-button>
</div>
<div class="drawer-actions">
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</div>
</div>
</template>
</el-drawer>
</PageCard>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
createChannelSubject,
listChannelSubjects,
updateChannelSubject,
deleteChannelSubject,
updateChannelSubjectStatus,
} from '@/api/channelSubject'
import { syncSubjectEnterpriseContacts } from '@/api/syncTask'
import { getSyncTask } from '@/api/syncTask'
import PageCard from '@/components/PageCard.vue'
const subjects = ref<any[]>([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const editingId = ref<number | null>(null)
const dialogForm = reactive({
channelType: 'wecom',
subjectCode: '',
subjectName: '',
corpId: '',
agentId: '',
agentSecret: '',
sessionArchiveEnabled: 0,
sessionArchivePrivateKey: '',
})
const drawerVisible = ref(false)
const currentSubject = ref<any>({})
const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const filteredSubjects = computed(() => {
const kw = searchKeyword.value.trim().toLowerCase()
if (!kw) return subjects.value
return subjects.value.filter(
(s) =>
(s.subjectName || '').toLowerCase().includes(kw) ||
(s.subjectCode || '').toLowerCase().includes(kw) ||
(s.corpId || '').toLowerCase().includes(kw)
)
})
const pagedSubjects = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredSubjects.value.slice(start, start + pageSize.value)
})
function handleSearch() {
currentPage.value = 1
}
function resetDialogForm() {
dialogForm.channelType = 'wecom'
dialogForm.subjectCode = ''
dialogForm.subjectName = ''
dialogForm.corpId = ''
dialogForm.agentId = ''
dialogForm.agentSecret = ''
dialogForm.sessionArchiveEnabled = 0
dialogForm.sessionArchivePrivateKey = ''
}
function fillDialogForm(row: any) {
dialogForm.channelType = row.channelType ?? 'wecom'
dialogForm.subjectCode = row.subjectCode ?? ''
dialogForm.subjectName = row.subjectName ?? ''
dialogForm.corpId = row.corpId ?? ''
dialogForm.agentId = row.agentId ?? ''
dialogForm.agentSecret = row.agentSecret ?? ''
dialogForm.sessionArchiveEnabled = row.sessionArchiveEnabled ?? 0
dialogForm.sessionArchivePrivateKey = row.sessionArchivePrivateKey ?? ''
}
function openCreateDialog() {
isEdit.value = false
editingId.value = null
resetDialogForm()
dialogVisible.value = true
}
function handleRowClick(row: any) {
isEdit.value = true
editingId.value = row.id
currentSubject.value = { ...row }
fillDialogForm(row)
drawerVisible.value = true
}
async function loadSubjects() {
try {
const res: any = await listChannelSubjects()
subjects.value = res.data || []
} catch (e: any) {
ElMessage.error(e.message)
}
}
async function handleSave() {
try {
if (isEdit.value && editingId.value != null) {
await updateChannelSubject(editingId.value, dialogForm)
ElMessage.success('更新成功')
} else {
await createChannelSubject(dialogForm)
ElMessage.success('创建成功')
}
dialogVisible.value = false
drawerVisible.value = false
await loadSubjects()
} catch (e: any) {
ElMessage.error(e.message)
}
}
async function handleDelete(row: any) {
try {
await ElMessageBox.confirm('确认删除该渠道主体?', '提示', { type: 'warning' })
await deleteChannelSubject(row.id)
ElMessage.success('删除成功')
await loadSubjects()
} catch (e: any) {
if (e !== 'cancel') {
ElMessage.error(e.message)
}
}
}
async function handleDrawerDelete() {
if (!currentSubject.value?.id) return
await handleDelete(currentSubject.value)
drawerVisible.value = false
}
async function handleStatusChange(row: any, val: any) {
try {
await updateChannelSubjectStatus(row.id, { status: val })
ElMessage.success('状态更新成功')
await loadSubjects()
} catch (e: any) {
ElMessage.error(e.message)
await loadSubjects()
}
}
async function pollTask(taskId: number, label: string) {
const maxAttempts = 60
for (let i = 0; i < maxAttempts; i++) {
await new Promise((resolve) => setTimeout(resolve, 2000))
try {
const res: any = await getSyncTask(taskId)
const task = res.data
if (!task) continue
if (task.taskStatus === 2) {
ElMessage.success(`${label}同步完成,共 ${task.syncedCount ?? 0}`)
return
}
if (task.taskStatus === 3) {
ElMessage.error(`${label}同步失败:${task.errorMessage || '未知错误'}`)
return
}
} catch (e: any) {
// ignore polling error
}
}
ElMessage.warning(`${label}同步时间较长,请稍后到列表查看`)
}
async function handleSyncEnterpriseContacts(row: any) {
try {
const res: any = await syncSubjectEnterpriseContacts(row.id)
const taskId = res.data?.id
if (taskId) {
ElMessage.info('企业通讯录同步任务已提交')
await pollTask(taskId, '企业通讯录')
}
} catch (e: any) {
ElMessage.error(e.message)
}
}
function channelLabel(type: string) {
const map: Record<string, string> = {
wecom: '企微',
dingtalk: '钉钉',
feishu: '飞书',
}
return map[type] || type
}
function mask(value: string) {
if (!value) return '-'
if (value.length <= 4) return '****'
return value.slice(0, 2) + '****' + value.slice(-2)
}
onMounted(loadSubjects)
</script>
<style scoped>
.search-input {
width: 280px;
}
.drawer-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.drawer-actions {
display: flex;
align-items: center;
gap: 12px;
}
</style>