Files
member-frontend/src/pages/admin/PermissionGroupsPage.vue

372 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>
<div>
<h2 class="text-xl font-bold text-gray-800 mb-6">群組與權限管理</h2>
<el-card class="mb-6 shadow-sm">
<template #header>
<div class="flex items-center justify-between">
<span class="font-medium">群組列表</span>
<el-button type="primary" @click="showCreateGroup = true" :icon="Plus">新增群組</el-button>
</div>
</template>
<el-skeleton v-if="loadingGroups" :rows="4" animated />
<el-table v-else :data="groups" stripe border class="w-full">
<template #empty><el-empty description="目前無群組" /></template>
<el-table-column prop="group_key" label="Group Key" width="180" />
<el-table-column prop="name" label="群組名稱" min-width="220" />
<el-table-column prop="status" label="狀態" width="120" />
<el-table-column label="操作" width="300">
<template #default="{ row }">
<el-button size="small" @click="openEditGroup(row)">編輯</el-button>
<el-button size="small" @click="openBindingDialog(row)">設定關聯</el-button>
<el-button size="small" type="danger" @click="handleDeleteGroup(row)">刪除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="showBindingDialog" :title="`群組關聯設定:${bindingGroupKey}`" width="980px">
<el-form :model="bindingForm" label-width="130px">
<el-form-item label="站台(公司/站台)">
<el-select v-model="bindingForm.site_keys" multiple filterable clearable style="width: 100%" placeholder="選擇站台">
<el-option v-for="s in siteOptions" :key="s.value" :label="s.label" :value="s.value" />
</el-select>
</el-form-item>
<el-form-item label="系統(多選)">
<el-select v-model="bindingForm.system_keys" multiple filterable clearable style="width: 100%" placeholder="選擇系統">
<el-option v-for="s in systems" :key="s.system_key" :label="`${s.name} (${s.system_key})`" :value="s.system_key" />
</el-select>
</el-form-item>
<el-form-item label="模組(多選)">
<el-select v-model="bindingForm.module_keys" multiple filterable clearable style="width: 100%" placeholder="選擇模組(可空,空值代表系統層)">
<el-option
v-for="m in filteredModuleOptions"
:key="m.module_key"
:label="`${m.name} (${m.module_key})`"
:value="m.module_key"
/>
</el-select>
</el-form-item>
<el-form-item label="會員(多選)">
<el-select v-model="bindingForm.member_subs" multiple filterable clearable style="width: 100%" placeholder="選擇會員">
<el-option
v-for="m in members"
:key="m.user_sub"
:label="`${m.display_name || m.email || '(no-name)'} (${m.user_sub})`"
:value="m.user_sub"
/>
</el-select>
</el-form-item>
<el-form-item label="操作(多選)">
<el-select v-model="bindingForm.actions" multiple style="width: 100%" placeholder="選擇操作">
<el-option label="view" value="view" />
<el-option label="edit" value="edit" />
</el-select>
</el-form-item>
</el-form>
<el-alert
title="規則scope 固定為 siteaction 只允許 view/edit可同時選"
type="info"
:closable="false"
class="mb-4"
/>
<el-table :data="bindingPreview" border stripe v-loading="bindingLoading" max-height="260">
<template #empty><el-empty description="目前沒有授權規則" /></template>
<el-table-column prop="scope_display" label="公司/站台" min-width="220" />
<el-table-column prop="system" label="系統" width="120" />
<el-table-column prop="module" label="模組" min-width="180" />
<el-table-column prop="action" label="操作" width="100" />
</el-table>
<template #footer>
<el-button @click="showBindingDialog = false">取消</el-button>
<el-button type="primary" :loading="savingBinding" @click="saveBindings">儲存關聯</el-button>
</template>
</el-dialog>
<el-dialog v-model="showCreateGroup" title="新增群組" @close="resetCreateForm">
<el-form :model="createForm" label-width="120px">
<el-form-item label="群組名稱">
<el-input v-model="createForm.name" placeholder="群組名稱" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreateGroup = false">取消</el-button>
<el-button type="primary" :loading="creatingGroup" @click="handleCreateGroup">確認</el-button>
</template>
</el-dialog>
<el-dialog v-model="showEditGroup" title="編輯群組" @close="resetEditGroupForm">
<el-form :model="editGroupForm" label-width="120px">
<el-form-item label="Group Key">
<el-input :model-value="editGroupForm.group_key" disabled />
</el-form-item>
<el-form-item label="群組名稱">
<el-input v-model="editGroupForm.name" />
</el-form-item>
<el-form-item label="狀態">
<el-select v-model="editGroupForm.status" style="width: 100%">
<el-option label="active" value="active" />
<el-option label="inactive" value="inactive" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showEditGroup = false">取消</el-button>
<el-button type="primary" :loading="savingGroup" @click="handleEditGroup">儲存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import {
getPermissionGroups,
createPermissionGroup,
updatePermissionGroup,
deletePermissionGroup,
getPermissionGroupPermissions,
getPermissionGroupBindings,
updatePermissionGroupBindings
} from '@/api/permission-groups'
import { getSystems } from '@/api/systems'
import { getModules } from '@/api/modules'
import { getSites } from '@/api/sites'
import { getCompanies } from '@/api/companies'
import { getMembers } from '@/api/members'
const groups = ref([])
const loadingGroups = ref(false)
const systems = ref([])
const modules = ref([])
const sites = ref([])
const companies = ref([])
const members = ref([])
const showCreateGroup = ref(false)
const creatingGroup = ref(false)
const createForm = reactive({ name: '' })
const showEditGroup = ref(false)
const savingGroup = ref(false)
const editGroupForm = reactive({ group_key: '', name: '', status: 'active' })
const showBindingDialog = ref(false)
const bindingGroupKey = ref('')
const bindingLoading = ref(false)
const savingBinding = ref(false)
const bindingPreview = ref([])
const bindingForm = reactive({
site_keys: [],
system_keys: [],
module_keys: [],
member_subs: [],
actions: ['view']
})
const companyLookup = computed(() => {
const map = {}
for (const c of companies.value) map[c.company_key] = c.name
return map
})
const siteOptions = computed(() => {
return sites.value.map(s => {
const companyName = companyLookup.value[s.company_key] || s.company_key
return {
value: s.site_key,
label: `${companyName}/${s.name} (${s.site_key})`
}
})
})
const filteredModuleOptions = computed(() => {
if (bindingForm.system_keys.length === 0) return modules.value
return modules.value.filter(m => bindingForm.system_keys.includes(m.system_key) && !m.module_key.endsWith('.__system__'))
})
function resetCreateForm() {
createForm.name = ''
}
function resetEditGroupForm() {
editGroupForm.group_key = ''
editGroupForm.name = ''
editGroupForm.status = 'active'
}
function resetBindingForm() {
bindingForm.site_keys = []
bindingForm.system_keys = []
bindingForm.module_keys = []
bindingForm.member_subs = []
bindingForm.actions = ['view']
bindingPreview.value = []
}
async function loadGroups() {
loadingGroups.value = true
try {
const res = await getPermissionGroups()
groups.value = res.data?.items || []
} catch (err) {
ElMessage.error('載入群組失敗')
} finally {
loadingGroups.value = false
}
}
async function loadCatalogs() {
const [systemsRes, modulesRes, sitesRes, companiesRes, membersRes] = await Promise.all([
getSystems(),
getModules(),
getSites(),
getCompanies(),
getMembers()
])
systems.value = systemsRes.data?.items || []
modules.value = modulesRes.data?.items || []
sites.value = sitesRes.data?.items || []
companies.value = companiesRes.data?.items || []
members.value = membersRes.data?.items || []
}
async function handleCreateGroup() {
if (!createForm.name) {
ElMessage.warning('請填寫完整資訊')
return
}
creatingGroup.value = true
try {
const res = await createPermissionGroup(createForm)
ElMessage.success(`新增成功:${res.data?.group_key || ''}`)
showCreateGroup.value = false
resetCreateForm()
await loadGroups()
} catch (err) {
ElMessage.error('新增失敗')
} finally {
creatingGroup.value = false
}
}
function openEditGroup(row) {
editGroupForm.group_key = row.group_key
editGroupForm.name = row.name
editGroupForm.status = row.status || 'active'
showEditGroup.value = true
}
async function handleEditGroup() {
savingGroup.value = true
try {
await updatePermissionGroup(editGroupForm.group_key, {
name: editGroupForm.name,
status: editGroupForm.status
})
ElMessage.success('群組更新成功')
showEditGroup.value = false
await loadGroups()
} catch (err) {
ElMessage.error('群組更新失敗')
} finally {
savingGroup.value = false
}
}
async function handleDeleteGroup(row) {
try {
await ElMessageBox.confirm(`確認刪除群組 ${row.name}${row.group_key}`, '刪除確認', { type: 'warning' })
await deletePermissionGroup(row.group_key)
ElMessage.success('刪除成功')
await loadGroups()
} catch (err) {
if (err === 'cancel') return
ElMessage.error(err.response?.data?.detail || '刪除失敗')
}
}
async function openBindingDialog(row) {
bindingGroupKey.value = row.group_key
showBindingDialog.value = true
bindingLoading.value = true
resetBindingForm()
try {
const [bindingsRes, previewRes] = await Promise.all([
getPermissionGroupBindings(row.group_key),
getPermissionGroupPermissions(row.group_key)
])
const data = bindingsRes.data || {}
bindingForm.site_keys = data.site_keys || []
bindingForm.system_keys = data.system_keys || []
bindingForm.module_keys = data.module_keys || []
bindingForm.member_subs = data.member_subs || []
bindingForm.actions = (data.actions && data.actions.length > 0) ? data.actions : ['view']
bindingPreview.value = toPreview(previewRes.data?.items || [])
} catch (err) {
ElMessage.error('載入群組關聯失敗')
} finally {
bindingLoading.value = false
}
}
function toPreview(items) {
const siteLabelMap = {}
for (const s of siteOptions.value) siteLabelMap[s.value] = s.label
return items.map(i => ({
...i,
module: i.module || '(系統層)',
scope_display: siteLabelMap[i.scope_id] || i.scope_id
}))
}
async function saveBindings() {
if (!bindingGroupKey.value) return
if (bindingForm.site_keys.length === 0) {
ElMessage.warning('至少需要選擇 1 個站台')
return
}
if (bindingForm.system_keys.length === 0 && bindingForm.module_keys.length === 0) {
ElMessage.warning('至少需要選擇 1 個系統或模組')
return
}
if (bindingForm.actions.length === 0) {
ElMessage.warning('至少需要選擇 1 個操作')
return
}
savingBinding.value = true
try {
await updatePermissionGroupBindings(bindingGroupKey.value, {
site_keys: bindingForm.site_keys,
system_keys: bindingForm.system_keys,
module_keys: bindingForm.module_keys,
member_subs: bindingForm.member_subs,
actions: bindingForm.actions
})
const previewRes = await getPermissionGroupPermissions(bindingGroupKey.value)
bindingPreview.value = toPreview(previewRes.data?.items || [])
ElMessage.success('群組關聯已更新')
} catch (err) {
ElMessage.error(err.response?.data?.detail || '更新失敗')
} finally {
savingBinding.value = false
}
}
onMounted(async () => {
await Promise.all([loadGroups(), loadCatalogs()])
})
</script>