diff --git a/components.d.ts b/components.d.ts index 8ad4cf6..d9d92cf 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] diff --git a/src/api/auth.ts b/src/api/auth.ts index b143a49..3352b3a 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -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>('/auth/login', data) }, + applyAccount(data: ApplyAccountPayload) { + return request.post('/auth/apply-account', data) + }, me() { return request.get>('/auth/me') }, diff --git a/src/api/users.ts b/src/api/users.ts index e8677e0..193bf1a 100644 --- a/src/api/users.ts +++ b/src/api/users.ts @@ -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) diff --git a/src/components/dashboard/SystemCard.vue b/src/components/dashboard/SystemCard.vue index 24ce1d5..6a07b17 100644 --- a/src/components/dashboard/SystemCard.vue +++ b/src/components/dashboard/SystemCard.vue @@ -115,7 +115,7 @@
-
{{ group.server.display_name || group.server.name }}
+
{{ group.server.display_name || group.server.name || `服务器#${group.server.id}` }}
{ .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 { 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), diff --git a/src/pages/AccountsPage.vue b/src/pages/AccountsPage.vue index d5c3ca1..a0e52f6 100644 --- a/src/pages/AccountsPage.vue +++ b/src/pages/AccountsPage.vue @@ -30,6 +30,16 @@ + @@ -60,6 +70,9 @@ const saving = ref(false) const dialogVisible = ref(false) const editingId = ref(null) const rows = ref([]) +const total = ref(0) +const page = ref(1) +const perPage = ref(20) const refreshingMap = ref>({}) const form = reactive({ name: '', username: '', password: '', is_active: true }) const refreshPollAttempts = 90 @@ -68,13 +81,25 @@ const refreshPollIntervalMs = 1000 async function fetchList(): Promise { 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 }) diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue index 9bb98e5..82f13c7 100644 --- a/src/pages/LoginPage.vue +++ b/src/pages/LoginPage.vue @@ -18,6 +18,7 @@
@@ -44,6 +45,30 @@ 确认修改并进入系统 + + + + + + + + + + + + + + + + + + + + +
@@ -63,9 +88,19 @@ const formRef = ref() const submitting = ref(false) const forcingPassword = ref(false) const forcePasswordDialogVisible = ref(false) +const applyDialogVisible = ref(false) +const applying = ref(false) +const applyFormRef = ref() 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 { const valid = await formRef.value?.validate().catch(() => false) @@ -159,6 +201,36 @@ async function handleForceUserLogout(): Promise { ElMessage.success('已退出当前账号') } +async function submitApplyAccount(): Promise { + 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) { diff --git a/src/pages/LogsPage.vue b/src/pages/LogsPage.vue index 07b5a0e..c4cbe51 100644 --- a/src/pages/LogsPage.vue +++ b/src/pages/LogsPage.vue @@ -57,7 +57,16 @@ - + @@ -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' diff --git a/src/pages/PermissionsPage.vue b/src/pages/PermissionsPage.vue index 8c813a2..7174603 100644 --- a/src/pages/PermissionsPage.vue +++ b/src/pages/PermissionsPage.vue @@ -21,6 +21,16 @@ + @@ -61,6 +71,9 @@ const saving = ref(false) const dialogVisible = ref(false) const editingId = ref(null) const rows = ref([]) +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(() => { const categories = rows.value @@ -73,13 +86,25 @@ const categoryOptions = computed(() => { async function fetchList(): Promise { 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' }) diff --git a/src/pages/RolesPage.vue b/src/pages/RolesPage.vue index 919cf09..08777ce 100644 --- a/src/pages/RolesPage.vue +++ b/src/pages/RolesPage.vue @@ -26,6 +26,16 @@ + @@ -60,6 +70,9 @@ const saving = ref(false) const dialogVisible = ref(false) const editingId = ref(null) const rows = ref([]) +const total = ref(0) +const page = ref(1) +const perPage = ref(20) const permissionOptions = ref([]) const selectedPermissionNodes = ref>([]) @@ -70,13 +83,25 @@ const permissionCascader = computed(() => buildPermissionCascader(permissionOpti async function fetchList(): Promise { 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 { 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%; } - diff --git a/src/pages/ServersPage.vue b/src/pages/ServersPage.vue index 5e22f54..c2ba9e0 100644 --- a/src/pages/ServersPage.vue +++ b/src/pages/ServersPage.vue @@ -3,9 +3,12 @@ @@ -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, { diff --git a/src/pages/UsersPage.vue b/src/pages/UsersPage.vue index 3d97c8f..cbbfae6 100644 --- a/src/pages/UsersPage.vue +++ b/src/pages/UsersPage.vue @@ -4,13 +4,15 @@
用户管理
+ 批量设置用户组和权限 批量导入 新增用户
- + + @@ -32,7 +34,16 @@ - + @@ -63,6 +74,28 @@ + + + + + + + + + + + +
+ +
+
+
+ +
+