feat: allow assigning sites directly from role page

This commit is contained in:
Chris
2026-04-03 01:56:22 +08:00
parent 2004203758
commit d59407d04c
5 changed files with 68 additions and 1 deletions

View File

@@ -646,6 +646,27 @@ def list_role_sites(role_key: str, db: Session = Depends(get_db)) -> RoleSitesRe
return RoleSitesResponse(role_key=role_key, sites=result)
@router.put("/roles/{role_key}/sites", response_model=RoleSitesResponse)
def assign_role_sites(role_key: str, payload: UserSiteAssignRequest, db: Session = Depends(get_db)) -> RoleSitesResponse:
roles_repo = RolesRepository(db)
sites_repo = SitesRepository(db)
site_roles_repo = SiteRolesRepository(db)
role = roles_repo.get_by_key(role_key)
if not role:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="role_not_found")
site_ids: list[str] = []
for site_key in list(dict.fromkeys(payload.site_keys)):
site = sites_repo.get_by_key(site_key)
if not site:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"site_not_found:{site_key}")
site_ids.append(site.id)
site_roles_repo.set_role_sites(role_id=role.id, site_ids=site_ids)
return list_role_sites(role_key=role_key, db=db)
@router.get("/members", response_model=ListResponse)
def list_members(
db: Session = Depends(get_db),

View File

@@ -35,3 +35,9 @@ class SiteRolesRepository:
for role_id in role_ids:
self.db.add(SiteRole(site_id=site_id, role_id=role_id))
self.db.commit()
def set_role_sites(self, *, role_id: str, site_ids: list[str]) -> None:
self.db.execute(delete(SiteRole).where(SiteRole.role_id == role_id))
for site_id in site_ids:
self.db.add(SiteRole(site_id=site_id, role_id=role_id))
self.db.commit()

View File

@@ -23,6 +23,7 @@
4. 角色管理DB 關聯為主)
- 欄位:`role_key`, `system_key`, `name`, `description`, `status`
- 關聯操作:指派到 Site新增/刪除 `site_roles`
- 需支援在「角色頁」直接多選站台並儲存(不必切到站台頁)。
5. 會員管理CRUD
- 欄位:`user_sub`, `username`, `email`, `display_name`, `is_active`, `status`

View File

@@ -5,3 +5,4 @@ export const createRole = (data) => adminHttp.post('/admin/roles', data)
export const updateRole = (roleKey, data) => adminHttp.patch(`/admin/roles/${roleKey}`, data)
export const deleteRole = (roleKey) => adminHttp.delete(`/admin/roles/${roleKey}`)
export const getRoleSites = (roleKey) => adminHttp.get(`/admin/roles/${roleKey}/sites`)
export const setRoleSites = (roleKey, siteKeys) => adminHttp.put(`/admin/roles/${roleKey}/sites`, { site_keys: siteKeys })

View File

@@ -79,6 +79,18 @@
</el-dialog>
<el-dialog v-model="showSitesDialog" :title="`角色綁定站台:${selectedRoleLabel}`" width="980px">
<el-form label-width="120px" class="mb-4">
<el-form-item label="指派站台">
<el-select v-model="selectedSiteKeys" multiple filterable style="width: 100%" placeholder="請選擇站台">
<el-option
v-for="site in siteOptions"
:key="site.site_key"
:label="`${site.company_display_name} / ${site.display_name} (${site.site_key})`"
:value="site.site_key"
/>
</el-select>
</el-form-item>
</el-form>
<el-table :data="roleSites" border stripe v-loading="sitesLoading">
<template #empty><el-empty description="此角色尚未綁定站台" /></template>
<el-table-column prop="company_display_name" label="公司" min-width="160" />
@@ -86,6 +98,7 @@
<el-table-column prop="site_key" label="Site Key" min-width="180" />
</el-table>
<template #footer>
<el-button type="primary" :loading="savingSites" @click="handleSaveRoleSites">儲存站台綁定</el-button>
<el-button @click="showSitesDialog = false">關閉</el-button>
</template>
</el-dialog>
@@ -96,8 +109,9 @@
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { getRoles, createRole, updateRole, deleteRole, getRoleSites } from '@/api/roles'
import { getRoles, createRole, updateRole, deleteRole, getRoleSites, setRoleSites } from '@/api/roles'
import { getSystems } from '@/api/systems'
import { getSites } from '@/api/sites'
const roles = ref([])
const systems = ref([])
@@ -131,8 +145,12 @@ const rules = {
const showSitesDialog = ref(false)
const selectedRoleLabel = ref('')
const selectedRoleKey = ref('')
const roleSites = ref([])
const sitesLoading = ref(false)
const savingSites = ref(false)
const siteOptions = ref([])
const selectedSiteKeys = ref([])
async function load() {
loading.value = true
@@ -144,6 +162,8 @@ async function load() {
])
roles.value = rolesRes.data?.items || []
systems.value = systemsRes.data?.items || []
const sitesRes = await getSites({ limit: 1000, offset: 0 })
siteOptions.value = sitesRes.data?.items || []
} catch (err) {
error.value = true
errorMsg.value = err.response?.data?.detail || '載入角色失敗'
@@ -240,19 +260,37 @@ async function handleDelete(row) {
}
async function openSites(row) {
selectedRoleKey.value = row.role_key
selectedRoleLabel.value = `${row.system_name} / ${row.name}`
showSitesDialog.value = true
sitesLoading.value = true
try {
const res = await getRoleSites(row.role_key)
roleSites.value = res.data?.sites || []
selectedSiteKeys.value = roleSites.value.map(item => item.site_key)
} catch (_err) {
ElMessage.error('載入角色站台失敗')
roleSites.value = []
selectedSiteKeys.value = []
} finally {
sitesLoading.value = false
}
}
async function handleSaveRoleSites() {
if (!selectedRoleKey.value) return
savingSites.value = true
try {
await setRoleSites(selectedRoleKey.value, selectedSiteKeys.value)
const res = await getRoleSites(selectedRoleKey.value)
roleSites.value = res.data?.sites || []
ElMessage.success('角色站台綁定已更新')
} catch (err) {
ElMessage.error(err.response?.data?.detail || '儲存角色站台失敗')
} finally {
savingSites.value = false
}
}
onMounted(load)
</script>