feat(前端): 增加账号申请、批量权限设置与分页每页数量
This commit is contained in:
parent
85d28a9bfc
commit
19ac981144
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -38,8 +38,6 @@ declare module 'vue' {
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
|
||||
@ -12,6 +12,14 @@ interface LoginData {
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
interface ApplyAccountPayload {
|
||||
nickname: string
|
||||
email: string
|
||||
phone: string
|
||||
password: string
|
||||
password_confirmation: string
|
||||
}
|
||||
|
||||
interface MeData {
|
||||
user: {
|
||||
id: number
|
||||
@ -27,6 +35,9 @@ export const authApi = {
|
||||
login(data: LoginPayload) {
|
||||
return request.post<unknown, ApiResponse<LoginData>>('/auth/login', data)
|
||||
},
|
||||
applyAccount(data: ApplyAccountPayload) {
|
||||
return request.post('/auth/apply-account', data)
|
||||
},
|
||||
me() {
|
||||
return request.get<unknown, ApiResponse<MeData>>('/auth/me')
|
||||
},
|
||||
|
||||
@ -19,6 +19,9 @@ export const usersApi = {
|
||||
syncPermissions(id: number, permissionIds: number[]) {
|
||||
return request.put(`/users/${id}/permissions`, { permission_ids: permissionIds })
|
||||
},
|
||||
syncBatchAssignments(payload: { user_ids: number[]; role_ids: number[]; permission_ids: number[] }) {
|
||||
return request.put('/users/batch-assignments', payload)
|
||||
},
|
||||
importUsers(file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
@ -115,7 +115,7 @@
|
||||
</el-form>
|
||||
<div v-else class='quick-use-server-groups'>
|
||||
<div v-for='group in quickUseServerGroups' :key='group.server.id' class='group-row'>
|
||||
<div class='group-title'>{{ group.server.display_name || group.server.name }}</div>
|
||||
<div class='group-title'>{{ group.server.display_name || group.server.name || `服务器#${group.server.id}` }}</div>
|
||||
<div class='group-actions btn-gap-8'>
|
||||
<el-button
|
||||
v-for='resource in group.resources'
|
||||
@ -591,7 +591,7 @@ async function fetchQuickUseResources(): Promise<void> {
|
||||
.filter((item: any) => Boolean(item.parent_id))
|
||||
.map((item: any) => {
|
||||
const server = serverMap.get(item.parent_id)
|
||||
const serverName = server?.display_name || server?.name || '服务器'
|
||||
const serverName = server?.display_name || server?.name || `服务器#${item.parent_id}`
|
||||
const resourceName = item.display_name || item.name || `资源#${item.id}`
|
||||
return {
|
||||
id: Number(item.id),
|
||||
@ -599,8 +599,8 @@ async function fetchQuickUseResources(): Promise<void> {
|
||||
protocols: Array.isArray(item.protocols) ? item.protocols : ['SSH'],
|
||||
parent_id: Number(item.parent_id || 0),
|
||||
server_id: Number(item.parent_id || 0),
|
||||
server_name: server?.name || '',
|
||||
server_display_name: server?.display_name || '',
|
||||
server_name: server?.name || `server_${item.parent_id}`,
|
||||
server_display_name: server?.display_name || `服务器#${item.parent_id}`,
|
||||
display_name: item.display_name || item.name || '',
|
||||
name: item.name || '',
|
||||
allow_copy_temp_password: Boolean(item.allow_copy_temp_password),
|
||||
|
||||
@ -30,6 +30,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class='mt-4'
|
||||
layout='total, sizes, prev, pager, next'
|
||||
:total='total'
|
||||
:current-page='page'
|
||||
:page-size='perPage'
|
||||
:page-sizes='[10, 20, 50, 100]'
|
||||
@current-change='handlePageChange'
|
||||
@size-change='handleSizeChange'
|
||||
/>
|
||||
|
||||
<el-dialog v-model='dialogVisible' :title='editingId ? "编辑账号" : "新增账号"' width='560px'>
|
||||
<el-form :model='form' label-width='100px'>
|
||||
@ -60,6 +70,9 @@ const saving = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(20)
|
||||
const refreshingMap = ref<Record<number, boolean>>({})
|
||||
const form = reactive<any>({ name: '', username: '', password: '', is_active: true })
|
||||
const refreshPollAttempts = 90
|
||||
@ -68,13 +81,25 @@ const refreshPollIntervalMs = 1000
|
||||
async function fetchList(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const response: any = await accountsApi.list({ page: 1, per_page: 200 })
|
||||
const response: any = await accountsApi.list({ page: page.value, per_page: perPage.value })
|
||||
rows.value = response.data.data || []
|
||||
total.value = Number(response.data.total || 0)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number): void {
|
||||
page.value = nextPage
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(nextSize: number): void {
|
||||
perPage.value = nextSize
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function openCreate(): void {
|
||||
editingId.value = null
|
||||
Object.assign(form, { name: '', username: '', password: '', is_active: true })
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
</el-form-item>
|
||||
<div class='assist-links'>
|
||||
<a href='javascript:void(0)'>无法访问您的帐户?</a>
|
||||
<a href='javascript:void(0)' @click='applyDialogVisible = true'>账号申请</a>
|
||||
</div>
|
||||
<div class='btn-row'>
|
||||
<el-button :loading='submitting' type='primary' class='login-btn' @click='handleLogin'>登录</el-button>
|
||||
@ -44,6 +45,30 @@
|
||||
<el-button type='primary' :loading='forcingPassword' @click='submitForcePasswordChange'>确认修改并进入系统</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model='applyDialogVisible' title='账号申请' width='520px'>
|
||||
<el-form ref='applyFormRef' :model='applyForm' :rules='applyRules' label-width='90px'>
|
||||
<el-form-item label='昵称' prop='nickname'>
|
||||
<el-input v-model='applyForm.nickname' placeholder='请输入昵称' />
|
||||
</el-form-item>
|
||||
<el-form-item label='邮箱' prop='email'>
|
||||
<el-input v-model='applyForm.email' placeholder='请输入邮箱' />
|
||||
</el-form-item>
|
||||
<el-form-item label='手机号' prop='phone'>
|
||||
<el-input v-model='applyForm.phone' placeholder='请输入手机号' />
|
||||
</el-form-item>
|
||||
<el-form-item label='密码' prop='password'>
|
||||
<el-input v-model='applyForm.password' type='password' show-password placeholder='请输入密码' />
|
||||
</el-form-item>
|
||||
<el-form-item label='确认密码' prop='password_confirmation'>
|
||||
<el-input v-model='applyForm.password_confirmation' type='password' show-password placeholder='请再次输入密码' />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click='applyDialogVisible = false'>取消</el-button>
|
||||
<el-button type='primary' :loading='applying' @click='submitApplyAccount'>提交申请</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -63,9 +88,19 @@ const formRef = ref<FormInstance>()
|
||||
const submitting = ref(false)
|
||||
const forcingPassword = ref(false)
|
||||
const forcePasswordDialogVisible = ref(false)
|
||||
const applyDialogVisible = ref(false)
|
||||
const applying = ref(false)
|
||||
const applyFormRef = ref<FormInstance>()
|
||||
const forcePasswordForm = reactive({ current_password: '', password: '', password_confirmation: '' })
|
||||
|
||||
const form = reactive({ account: '', password: '' })
|
||||
const applyForm = reactive({
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
})
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const phoneRegex = /^1\d{10}$/
|
||||
const rules: FormRules = {
|
||||
@ -87,6 +122,13 @@ const rules: FormRules = {
|
||||
}],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
}
|
||||
const applyRules: FormRules = {
|
||||
nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
|
||||
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
password_confirmation: [{ required: true, message: '请再次输入密码', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
async function handleLogin(): Promise<void> {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
@ -159,6 +201,36 @@ async function handleForceUserLogout(): Promise<void> {
|
||||
ElMessage.success('已退出当前账号')
|
||||
}
|
||||
|
||||
async function submitApplyAccount(): Promise<void> {
|
||||
const valid = await applyFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
if (applyForm.password !== applyForm.password_confirmation) {
|
||||
ElMessage.warning('两次输入的密码不一致')
|
||||
return
|
||||
}
|
||||
|
||||
applying.value = true
|
||||
try {
|
||||
await authApi.applyAccount(applyForm)
|
||||
ElMessage.success('申请提交成功,请联系或等待管理员添加权限')
|
||||
applyDialogVisible.value = false
|
||||
Object.assign(applyForm, {
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
})
|
||||
} catch (error: any) {
|
||||
const first = error?.errors ? (Object.values(error.errors)[0] as string[])?.[0] : null
|
||||
ElMessage.error(first || error?.message || '账号申请失败')
|
||||
} finally {
|
||||
applying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
|
||||
@ -57,7 +57,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination class='mt-4' layout='total, prev, pager, next' :total='total' :current-page='page' :page-size='perPage' @current-change='handlePageChange' />
|
||||
<el-pagination
|
||||
class='mt-4'
|
||||
layout='total, sizes, prev, pager, next'
|
||||
:total='total'
|
||||
:current-page='page'
|
||||
:page-size='perPage'
|
||||
:page-sizes='[10, 20, 50, 100]'
|
||||
@current-change='handlePageChange'
|
||||
@size-change='handleSizeChange'
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -112,6 +121,12 @@ function handlePageChange(nextPage: number): void {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(nextSize: number): void {
|
||||
perPage.value = nextSize
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSortChange(payload: { prop: string, order: 'ascending' | 'descending' | null }): void {
|
||||
if (!payload.prop || !payload.order) {
|
||||
sortBy.value = 'requested_at'
|
||||
|
||||
@ -21,6 +21,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class='mt-4'
|
||||
layout='total, sizes, prev, pager, next'
|
||||
:total='total'
|
||||
:current-page='page'
|
||||
:page-size='perPage'
|
||||
:page-sizes='[10, 20, 50, 100]'
|
||||
@current-change='handlePageChange'
|
||||
@size-change='handleSizeChange'
|
||||
/>
|
||||
|
||||
<el-dialog v-model='dialogVisible' :title='editingId ? "编辑权限" : "新增权限"' width='560px'>
|
||||
<el-form :model='form' label-width='95px'>
|
||||
@ -61,6 +71,9 @@ const saving = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(20)
|
||||
const form = reactive({ name: '', category: 'general', description: '', guard_name: 'api' })
|
||||
const categoryOptions = computed<string[]>(() => {
|
||||
const categories = rows.value
|
||||
@ -73,13 +86,25 @@ const categoryOptions = computed<string[]>(() => {
|
||||
async function fetchList(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const response: any = await permissionsApi.list({ per_page: 200 })
|
||||
const response: any = await permissionsApi.list({ page: page.value, per_page: perPage.value })
|
||||
rows.value = response.data.data || []
|
||||
total.value = Number(response.data.total || 0)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number): void {
|
||||
page.value = nextPage
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(nextSize: number): void {
|
||||
perPage.value = nextSize
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function openCreate(): void {
|
||||
editingId.value = null
|
||||
Object.assign(form, { name: '', category: 'general', description: '', guard_name: 'api' })
|
||||
|
||||
@ -26,6 +26,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class='mt-4'
|
||||
layout='total, sizes, prev, pager, next'
|
||||
:total='total'
|
||||
:current-page='page'
|
||||
:page-size='perPage'
|
||||
:page-sizes='[10, 20, 50, 100]'
|
||||
@current-change='handlePageChange'
|
||||
@size-change='handleSizeChange'
|
||||
/>
|
||||
|
||||
<el-dialog v-model='dialogVisible' :title='editingId ? "编辑角色" : "新增角色"' width='92vw' top='4vh'>
|
||||
<el-form :model='form' label-width='95px'>
|
||||
@ -60,6 +70,9 @@ const saving = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(20)
|
||||
const permissionOptions = ref<any[]>([])
|
||||
const selectedPermissionNodes = ref<Array<number | string>>([])
|
||||
|
||||
@ -70,13 +83,25 @@ const permissionCascader = computed(() => buildPermissionCascader(permissionOpti
|
||||
async function fetchList(): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const response: any = await rolesApi.list({ page: 1, per_page: 200 })
|
||||
const response: any = await rolesApi.list({ page: page.value, per_page: perPage.value })
|
||||
rows.value = response.data.data || []
|
||||
total.value = Number(response.data.total || 0)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number): void {
|
||||
page.value = nextPage
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(nextSize: number): void {
|
||||
perPage.value = nextSize
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
async function fetchPermissions(): Promise<void> {
|
||||
const response: any = await permissionsApi.list({ page: 1, per_page: 500 })
|
||||
permissionOptions.value = response.data.data || []
|
||||
@ -153,4 +178,3 @@ function sortByPermissions(a: any, b: any): number {
|
||||
:deep(.permission-panel .el-cascader-node) { width: 100%; }
|
||||
:deep(.permission-panel .el-cascader-panel) { height: 100%; }
|
||||
</style>
|
||||
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
<template #header>
|
||||
<div class='flex justify-between items-center'>
|
||||
<span class='font-700'>服务器资源管理</span>
|
||||
<div class='top-actions btn-gap-8' v-if='hasPermission("platform.servers.manage")'>
|
||||
<div class='top-actions btn-gap-8'>
|
||||
<el-button @click='downloadSsoComponent'>下载单点登录组件</el-button>
|
||||
<template v-if='hasPermission("platform.servers.manage")'>
|
||||
<el-button type='primary' @click='openCreateServer'>新增服务器</el-button>
|
||||
<el-button type='success' @click='openCreateResource()'>新增资源</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -361,6 +364,18 @@ function openCreateResource(parentId?: number): void {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function downloadSsoComponent(): void {
|
||||
const downloadUrl = `${window.location.origin}/USMSSOsetup.exe`
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = downloadUrl
|
||||
anchor.download = 'USMSSOsetup.exe'
|
||||
anchor.target = '_blank'
|
||||
anchor.rel = 'noopener'
|
||||
document.body.appendChild(anchor)
|
||||
anchor.click()
|
||||
document.body.removeChild(anchor)
|
||||
}
|
||||
|
||||
function openEdit(row: any): void {
|
||||
editingId.value = row.id
|
||||
Object.assign(form, {
|
||||
|
||||
@ -4,13 +4,15 @@
|
||||
<div class='flex justify-between items-center'>
|
||||
<span class='font-700'>用户管理</span>
|
||||
<div class='btn-gap-8' v-if='hasPermission("platform.users.manage")'>
|
||||
<el-button type='warning' :disabled='selectedUserIds.length === 0' @click='openBatchAssignDialog'>批量设置用户组和权限</el-button>
|
||||
<el-button type='success' :loading='importing' @click='openImportDialog'>批量导入</el-button>
|
||||
<el-button type='primary' @click='openCreate'>新增用户</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data='rows' v-loading='loading' @sort-change='handleSortChange'>
|
||||
<el-table :data='rows' v-loading='loading' @sort-change='handleSortChange' @selection-change='handleSelectionChange'>
|
||||
<el-table-column v-if='hasPermission("platform.users.manage")' type='selection' width='48' />
|
||||
<el-table-column prop='id' label='ID' width='70' sortable='custom' />
|
||||
<el-table-column prop='nickname' label='昵称' min-width='130' sortable='custom' />
|
||||
<el-table-column prop='email' label='邮箱' min-width='180' sortable='custom' />
|
||||
@ -32,7 +34,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination class='mt-4' layout='total, prev, pager, next' :total='total' :current-page='page' :page-size='perPage' @current-change='handlePageChange' />
|
||||
<el-pagination
|
||||
class='mt-4'
|
||||
layout='total, sizes, prev, pager, next'
|
||||
:total='total'
|
||||
:current-page='page'
|
||||
:page-size='perPage'
|
||||
:page-sizes='[10, 20, 50, 100]'
|
||||
@current-change='handlePageChange'
|
||||
@size-change='handleSizeChange'
|
||||
/>
|
||||
|
||||
<el-dialog v-model='dialogVisible' :title='editingId ? "编辑用户" : "新增用户"' width='560px'>
|
||||
<el-form :model='form' label-width='95px'>
|
||||
@ -63,6 +74,28 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model='batchAssignDialogVisible' title='批量设置用户组和用户权限' width='760px'>
|
||||
<el-alert type='info' :closable='false' show-icon>
|
||||
<template #default>已选择 {{ selectedUserIds.length }} 个用户,提交后将覆盖这些用户的角色与直授权限。</template>
|
||||
</el-alert>
|
||||
<el-form :model='batchAssignForm' label-width='90px' class='mt-4'>
|
||||
<el-form-item label='用户组'>
|
||||
<el-select v-model='batchAssignForm.role_ids' multiple collapse-tags clearable class='w-full' placeholder='请选择用户组'>
|
||||
<el-option v-for='item in roleOptions' :key='item.id' :label='item.name' :value='item.id' />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='用户权限'>
|
||||
<div class='permission-panel-wrap'>
|
||||
<el-cascader-panel v-model='batchAssignPermissionNodes' :options='permissionCascader' :props='cascaderProps' class='permission-panel' />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click='batchAssignDialogVisible = false'>取消</el-button>
|
||||
<el-button type='primary' :loading='batchAssigning' @click='submitBatchAssign'>提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model='importDialogVisible' title='批量导入用户' width='620px'>
|
||||
<el-alert type='info' show-icon :closable='false'>
|
||||
<template #default>
|
||||
@ -123,13 +156,17 @@ const savingPermissions = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const permissionDialogVisible = ref(false)
|
||||
const importDialogVisible = ref(false)
|
||||
const batchAssignDialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const permissionTargetUserId = ref<number | null>(null)
|
||||
const importing = ref(false)
|
||||
const batchAssigning = ref(false)
|
||||
const selectedUserIds = ref<number[]>([])
|
||||
const rows = ref<any[]>([])
|
||||
const roleOptions = ref<any[]>([])
|
||||
const permissionOptions = ref<any[]>([])
|
||||
const selectedPermissionNodes = ref<Array<number | string>>([])
|
||||
const batchAssignPermissionNodes = ref<Array<number | string>>([])
|
||||
const importFile = ref<File | null>(null)
|
||||
const importResult = ref<{ created_count: number; skipped_count: number; error_count: number; errors: Array<{ line: number; message: string }> } | null>(null)
|
||||
const total = ref(0)
|
||||
@ -139,6 +176,7 @@ const sortBy = ref('created_at')
|
||||
const sortOrder = ref<'asc' | 'desc'>('desc')
|
||||
|
||||
const form = reactive<any>({ nickname: '', email: '', phone: '', password: '', force_password_change: false, role_ids: [] })
|
||||
const batchAssignForm = reactive<{ role_ids: number[] }>({ role_ids: [] })
|
||||
const cascaderProps = { multiple: true, emitPath: false, checkStrictly: false }
|
||||
const permissionCascader = computed(() => buildPermissionCascader(permissionOptions.value))
|
||||
|
||||
@ -153,6 +191,7 @@ async function fetchList(): Promise<void> {
|
||||
})
|
||||
const list = response.data.data || []
|
||||
rows.value = await Promise.all(list.map(async (item: any) => (await usersApi.detail(item.id) as any).data))
|
||||
selectedUserIds.value = []
|
||||
total.value = response.data.total || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
@ -160,12 +199,19 @@ async function fetchList(): Promise<void> {
|
||||
}
|
||||
|
||||
async function fetchOptions(): Promise<void> {
|
||||
try {
|
||||
const [rolesResponse, permissionsResponse]: any = await Promise.all([
|
||||
rolesApi.list({ page: 1, per_page: 200 }),
|
||||
permissionsApi.list({ page: 1, per_page: 500 }),
|
||||
rolesApi.list({ page: 1, per_page: 100 }),
|
||||
permissionsApi.list({ page: 1, per_page: 200 }),
|
||||
])
|
||||
roleOptions.value = rolesResponse.data.data || []
|
||||
permissionOptions.value = permissionsResponse.data.data || []
|
||||
} catch (error: any) {
|
||||
roleOptions.value = []
|
||||
permissionOptions.value = []
|
||||
const first = error?.errors ? (Object.values(error.errors)[0] as string[])?.[0] : null
|
||||
ElMessage.error(first || error?.message || '加载用户组选项失败')
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number): void {
|
||||
@ -173,6 +219,12 @@ function handlePageChange(nextPage: number): void {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(nextSize: number): void {
|
||||
perPage.value = nextSize
|
||||
page.value = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSortChange(payload: { prop: string, order: 'ascending' | 'descending' | null }): void {
|
||||
if (!payload.prop || !payload.order) {
|
||||
sortBy.value = 'created_at'
|
||||
@ -269,6 +321,16 @@ function openImportDialog(): void {
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
function openBatchAssignDialog(): void {
|
||||
if (selectedUserIds.value.length === 0) {
|
||||
ElMessage.warning('请先勾选用户')
|
||||
return
|
||||
}
|
||||
batchAssignForm.role_ids = []
|
||||
batchAssignPermissionNodes.value = []
|
||||
batchAssignDialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleImportFileChange(file: any): void {
|
||||
importFile.value = file?.raw || null
|
||||
}
|
||||
@ -319,6 +381,35 @@ async function downloadImportTemplate(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectionChange(selection: any[]): void {
|
||||
selectedUserIds.value = selection
|
||||
.map((item: any) => Number(item.id || 0))
|
||||
.filter((id: number) => id > 0)
|
||||
}
|
||||
|
||||
async function submitBatchAssign(): Promise<void> {
|
||||
if (selectedUserIds.value.length === 0) {
|
||||
ElMessage.warning('请先勾选用户')
|
||||
return
|
||||
}
|
||||
batchAssigning.value = true
|
||||
try {
|
||||
await usersApi.syncBatchAssignments({
|
||||
user_ids: selectedUserIds.value,
|
||||
role_ids: batchAssignForm.role_ids,
|
||||
permission_ids: extractPermissionIds(batchAssignPermissionNodes.value),
|
||||
})
|
||||
ElMessage.success('批量设置成功')
|
||||
batchAssignDialogVisible.value = false
|
||||
await fetchList()
|
||||
} catch (error: any) {
|
||||
const first = error?.errors ? (Object.values(error.errors)[0] as string[])?.[0] : null
|
||||
ElMessage.error(first || error?.message || '批量设置失败')
|
||||
} finally {
|
||||
batchAssigning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([fetchOptions(), fetchList()])
|
||||
})
|
||||
|
||||
@ -33,7 +33,8 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://sso.scirc.top/api', // 后端地址
|
||||
// target: 'https://sso.scirc.top/api', // 后端地址
|
||||
target: 'http://localhost:8080', // 后端地址
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, '') // 去掉 /api 前缀
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user