fix: align client integration contracts

This commit is contained in:
2026-07-31 13:43:16 +08:00
parent 5b09010be9
commit 151d76f77c
14 changed files with 120 additions and 60 deletions
+17 -6
View File
@@ -13,6 +13,8 @@ const keyword = ref('')
const editorOpen = ref(false)
const editingId = ref('')
const loading = ref(false)
const saving = ref(false)
const actionRoleId = ref('')
const form = reactive({ name: '', code: '', description: '' })
const filteredRoles = computed(() => {
@@ -54,13 +56,16 @@ async function saveRole() {
ElMessage.warning('请完整填写角色名称、编码和说明')
return
}
saving.value = true
try {
if (editingId.value) store.updateRole(editingId.value, { name: form.name, description: form.description })
if (editingId.value) await store.updateRole(editingId.value, { name: form.name, description: form.description })
else await store.createRole({ ...form, code: form.code.trim().toUpperCase() })
editorOpen.value = false
ElMessage.success(editingId.value ? '角色已更新' : '角色已新增')
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '保存失败')
} finally {
saving.value = false
}
}
@@ -68,22 +73,28 @@ async function toggleStatus(role: TenantRole) {
const action = role.status === 'ACTIVE' ? '停用' : '启用'
try {
await ElMessageBox.confirm(`${action}后将影响拥有该角色的成员权限。`, `${action}角色 ${role.name}`, { type: 'warning', confirmButtonText: action, cancelButtonText: '取消' })
store.toggleRoleStatus(role.id)
actionRoleId.value = role.id
await store.toggleRoleStatus(role.id)
ElMessage.success(`角色已${action}`)
} catch (error) {
if (error === 'cancel' || error === 'close') return
ElMessage.error(error instanceof Error ? error.message : '操作失败')
} finally {
actionRoleId.value = ''
}
}
async function removeRole(role: TenantRole) {
try {
await ElMessageBox.confirm('删除后不能恢复,仅未被成员使用的自定义角色可以删除。', `删除角色 ${role.name}`, { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' })
store.deleteRole(role.id)
actionRoleId.value = role.id
await store.deleteRole(role.id)
ElMessage.success('角色已删除')
} catch (error) {
if (error === 'cancel' || error === 'close') return
ElMessage.error(error instanceof Error ? error.message : '删除失败')
} finally {
actionRoleId.value = ''
}
}
@@ -95,7 +106,7 @@ function assignPermissions(role: TenantRole) {
<template>
<section v-loading="loading" class="roles-page">
<div class="role-guidance client-card"><ShieldCheck :size="22" /><div><strong>角色用于约束企业内部成员可见菜单和可执行操作</strong><span>企业管理员为系统内置角色,不能停用或删除;平台后台权限不会出现在客户工作台。</span></div></div>
<div v-if="store.rolesLoadedFromApi" class="role-contract-notice client-card">角色列表、新增和已授权权限回显已接入后端;编辑、启停、删除路径尚未进入当前 Gateway OpenAPI,相关写操作暂不开放。</div>
<div v-if="store.rolesLoadedFromApi" class="role-contract-notice client-card">角色列表、新增、编辑、启停、删除及权限回显均使用真实 IAM 接口;系统内置角色和仍有关联成员的角色继续受服务端保护。</div>
<div class="role-toolbar client-card">
<el-input v-model="keyword" clearable placeholder="搜索角色名称、编码或说明"><template #prefix><Search :size="16" /></template></el-input>
@@ -107,7 +118,7 @@ function assignPermissions(role: TenantRole) {
<header><div class="role-icon"><LockKeyhole v-if="role.builtin" :size="20" /><ShieldCheck v-else :size="20" /></div><div class="role-heading"><div><h3>{{ role.name }}</h3><el-tag v-if="role.builtin" effect="plain">内置</el-tag><el-tag :type="role.status === 'ACTIVE' ? 'success' : 'info'" effect="light">{{ role.status === 'ACTIVE' ? '启用' : '停用' }}</el-tag></div><code>{{ role.code }}</code></div></header>
<p>{{ role.description }}</p>
<dl><div><dt><Users :size="15" />成员数</dt><dd>{{ store.roleMemberCount(role.id) }}</dd></div><div><dt><ShieldCheck :size="15" />权限数</dt><dd>{{ role.permissionCodes.length }}</dd></div></dl>
<footer><el-button v-if="auth.hasPermission('company:permission:list')" link type="primary" @click="assignPermissions(role)">{{ auth.hasPermission('company:permission:assign') && !role.builtin ? '分配权限' : '查看权限' }}</el-button><el-button v-if="!store.rolesLoadedFromApi && auth.hasPermission('company:role:write')" link type="primary" @click="openEdit(role)"><Pencil :size="15" />编辑</el-button><el-button v-if="!store.rolesLoadedFromApi && auth.hasPermission('company:role:write') && !role.builtin" link :type="role.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(role)">{{ role.status === 'ACTIVE' ? '停用' : '启用' }}</el-button><el-button v-if="!store.rolesLoadedFromApi && auth.hasPermission('company:role:write') && !role.builtin" link type="danger" @click="removeRole(role)"><Trash2 :size="15" />删除</el-button></footer>
<footer><el-button v-if="auth.hasPermission('company:permission:list')" link type="primary" :disabled="actionRoleId === role.id" @click="assignPermissions(role)">{{ auth.hasPermission('company:permission:assign') && !role.builtin ? '分配权限' : '查看权限' }}</el-button><el-button v-if="auth.hasPermission('company:role:write') && !role.builtin" link type="primary" :disabled="actionRoleId === role.id" @click="openEdit(role)"><Pencil :size="15" />编辑</el-button><el-button v-if="auth.hasPermission('company:role:write') && !role.builtin" link :type="role.status === 'ACTIVE' ? 'warning' : 'success'" :loading="actionRoleId === role.id" @click="toggleStatus(role)">{{ role.status === 'ACTIVE' ? '停用' : '启用' }}</el-button><el-button v-if="auth.hasPermission('company:role:write') && !role.builtin" link type="danger" :disabled="actionRoleId === role.id" @click="removeRole(role)"><Trash2 :size="15" />删除</el-button></footer>
</article>
</div>
<div v-else class="client-card client-empty">没有匹配的企业角色。</div>
@@ -119,7 +130,7 @@ function assignPermissions(role: TenantRole) {
<el-form-item label="角色说明" required><el-input v-model="form.description" type="textarea" :rows="4" maxlength="200" show-word-limit /></el-form-item>
<div class="role-form-tip">保存后请前往“菜单权限”为角色分配可见菜单和操作权限。</div>
</el-form>
<template #footer><div class="drawer-actions"><el-button @click="editorOpen = false">取消</el-button><el-button type="primary" @click="saveRole">保存</el-button></div></template>
<template #footer><div class="drawer-actions"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="saveRole">保存</el-button></div></template>
</el-drawer>
</section>
</template>