feat: allow assigning sites directly from role page
This commit is contained in:
@@ -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)
|
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)
|
@router.get("/members", response_model=ListResponse)
|
||||||
def list_members(
|
def list_members(
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
@@ -35,3 +35,9 @@ class SiteRolesRepository:
|
|||||||
for role_id in role_ids:
|
for role_id in role_ids:
|
||||||
self.db.add(SiteRole(site_id=site_id, role_id=role_id))
|
self.db.add(SiteRole(site_id=site_id, role_id=role_id))
|
||||||
self.db.commit()
|
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()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
4. 角色管理(DB 關聯為主)
|
4. 角色管理(DB 關聯為主)
|
||||||
- 欄位:`role_key`, `system_key`, `name`, `description`, `status`
|
- 欄位:`role_key`, `system_key`, `name`, `description`, `status`
|
||||||
- 關聯操作:指派到 Site(新增/刪除 `site_roles`)
|
- 關聯操作:指派到 Site(新增/刪除 `site_roles`)
|
||||||
|
- 需支援在「角色頁」直接多選站台並儲存(不必切到站台頁)。
|
||||||
|
|
||||||
5. 會員管理(CRUD)
|
5. 會員管理(CRUD)
|
||||||
- 欄位:`user_sub`, `username`, `email`, `display_name`, `is_active`, `status`
|
- 欄位:`user_sub`, `username`, `email`, `display_name`, `is_active`, `status`
|
||||||
|
|||||||
@@ -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 updateRole = (roleKey, data) => adminHttp.patch(`/admin/roles/${roleKey}`, data)
|
||||||
export const deleteRole = (roleKey) => adminHttp.delete(`/admin/roles/${roleKey}`)
|
export const deleteRole = (roleKey) => adminHttp.delete(`/admin/roles/${roleKey}`)
|
||||||
export const getRoleSites = (roleKey) => adminHttp.get(`/admin/roles/${roleKey}/sites`)
|
export const getRoleSites = (roleKey) => adminHttp.get(`/admin/roles/${roleKey}/sites`)
|
||||||
|
export const setRoleSites = (roleKey, siteKeys) => adminHttp.put(`/admin/roles/${roleKey}/sites`, { site_keys: siteKeys })
|
||||||
|
|||||||
@@ -79,6 +79,18 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="showSitesDialog" :title="`角色綁定站台:${selectedRoleLabel}`" width="980px">
|
<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">
|
<el-table :data="roleSites" border stripe v-loading="sitesLoading">
|
||||||
<template #empty><el-empty description="此角色尚未綁定站台" /></template>
|
<template #empty><el-empty description="此角色尚未綁定站台" /></template>
|
||||||
<el-table-column prop="company_display_name" label="公司" min-width="160" />
|
<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-column prop="site_key" label="Site Key" min-width="180" />
|
||||||
</el-table>
|
</el-table>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<el-button type="primary" :loading="savingSites" @click="handleSaveRoleSites">儲存站台綁定</el-button>
|
||||||
<el-button @click="showSitesDialog = false">關閉</el-button>
|
<el-button @click="showSitesDialog = false">關閉</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -96,8 +109,9 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
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 { getSystems } from '@/api/systems'
|
||||||
|
import { getSites } from '@/api/sites'
|
||||||
|
|
||||||
const roles = ref([])
|
const roles = ref([])
|
||||||
const systems = ref([])
|
const systems = ref([])
|
||||||
@@ -131,8 +145,12 @@ const rules = {
|
|||||||
|
|
||||||
const showSitesDialog = ref(false)
|
const showSitesDialog = ref(false)
|
||||||
const selectedRoleLabel = ref('')
|
const selectedRoleLabel = ref('')
|
||||||
|
const selectedRoleKey = ref('')
|
||||||
const roleSites = ref([])
|
const roleSites = ref([])
|
||||||
const sitesLoading = ref(false)
|
const sitesLoading = ref(false)
|
||||||
|
const savingSites = ref(false)
|
||||||
|
const siteOptions = ref([])
|
||||||
|
const selectedSiteKeys = ref([])
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -144,6 +162,8 @@ async function load() {
|
|||||||
])
|
])
|
||||||
roles.value = rolesRes.data?.items || []
|
roles.value = rolesRes.data?.items || []
|
||||||
systems.value = systemsRes.data?.items || []
|
systems.value = systemsRes.data?.items || []
|
||||||
|
const sitesRes = await getSites({ limit: 1000, offset: 0 })
|
||||||
|
siteOptions.value = sitesRes.data?.items || []
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = true
|
error.value = true
|
||||||
errorMsg.value = err.response?.data?.detail || '載入角色失敗'
|
errorMsg.value = err.response?.data?.detail || '載入角色失敗'
|
||||||
@@ -240,19 +260,37 @@ async function handleDelete(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openSites(row) {
|
async function openSites(row) {
|
||||||
|
selectedRoleKey.value = row.role_key
|
||||||
selectedRoleLabel.value = `${row.system_name} / ${row.name}`
|
selectedRoleLabel.value = `${row.system_name} / ${row.name}`
|
||||||
showSitesDialog.value = true
|
showSitesDialog.value = true
|
||||||
sitesLoading.value = true
|
sitesLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getRoleSites(row.role_key)
|
const res = await getRoleSites(row.role_key)
|
||||||
roleSites.value = res.data?.sites || []
|
roleSites.value = res.data?.sites || []
|
||||||
|
selectedSiteKeys.value = roleSites.value.map(item => item.site_key)
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
ElMessage.error('載入角色站台失敗')
|
ElMessage.error('載入角色站台失敗')
|
||||||
roleSites.value = []
|
roleSites.value = []
|
||||||
|
selectedSiteKeys.value = []
|
||||||
} finally {
|
} finally {
|
||||||
sitesLoading.value = false
|
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)
|
onMounted(load)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user