feat: 实现组织成员角色管理功能

v3
veypi 1 week ago
parent 691f1df75b
commit 37acea3420

@ -294,8 +294,12 @@ func getUserOrgs(userID string) ([]userOrgInfo, error) {
continue continue
} }
// 解析角色ID // 从 UserRole 表查询用户的角色
roles := parseRoles(m.RoleIDs) var roles []string
cfg.DB().Model(&models.UserRole{}).
Joins("JOIN roles ON user_roles.role_id = roles.id").
Where("user_roles.user_id = ? AND user_roles.org_id = ?", userID, m.OrgID).
Pluck("roles.code", &roles)
result = append(result, userOrgInfo{ result = append(result, userOrgInfo{
OrgID: m.OrgID, OrgID: m.OrgID,
@ -308,11 +312,3 @@ func getUserOrgs(userID string) ([]userOrgInfo, error) {
return result, nil return result, nil
} }
func parseRoles(roleIDs string) []string {
if roleIDs == "" {
return []string{}
}
// 简单解析,实际可能需要更复杂的逻辑
return []string{}
}

@ -47,16 +47,42 @@ func create(x *vigo.X, req *CreateRequest) (*models.Org, error) {
return nil, vigo.ErrInternalServer.WithError(err) return nil, vigo.ErrInternalServer.WithError(err)
} }
// 创建组织的默认角色
adminRole := &models.Role{
OrgID: &org.ID,
Code: "admin",
Name: "管理员",
Status: 1,
IsSystem: true,
}
memberRole := &models.Role{
OrgID: &org.ID,
Code: "member",
Name: "成员",
Status: 1,
IsSystem: true,
}
if err := cfg.DB().Create(adminRole).Error; err != nil {
cfg.DB().Delete(org)
return nil, vigo.ErrInternalServer.WithError(err)
}
if err := cfg.DB().Create(memberRole).Error; err != nil {
cfg.DB().Delete(org)
cfg.DB().Delete(adminRole)
return nil, vigo.ErrInternalServer.WithError(err)
}
// 授予创建者 admin 角色 // 授予创建者 admin 角色
if err := auth.VBaseAuth.GrantRole(x.Context(), ownerID, org.ID, "admin"); err != nil { if err := auth.VBaseAuth.GrantRole(x.Context(), ownerID, org.ID, "admin"); err != nil {
// 最好回滚,这里简化处理 // 回滚
cfg.DB().Delete(&models.Role{}).Where("org_id = ?", org.ID)
cfg.DB().Delete(org)
return nil, vigo.ErrInternalServer.WithError(err) return nil, vigo.ErrInternalServer.WithError(err)
} }
member := &models.OrgMember{ member := &models.OrgMember{
OrgID: org.ID, OrgID: org.ID,
UserID: ownerID, UserID: ownerID,
RoleIDs: "admin",
Status: models.MemberStatusActive, Status: models.MemberStatusActive,
JoinedAt: org.CreatedAt.Format("2006-01-02 15:04:05"), JoinedAt: org.CreatedAt.Format("2006-01-02 15:04:05"),
} }

@ -67,7 +67,14 @@ func AuthMiddleware() func(*vigo.X) error {
} }
x.Set("org_id", orgID) x.Set("org_id", orgID)
x.Set("org_roles", member.RoleIDs)
// 从 UserRole 表查询用户的角色
var roleCodes []string
cfg.DB().Model(&models.UserRole{}).
Joins("JOIN roles ON user_roles.role_id = roles.id").
Where("user_roles.user_id = ? AND user_roles.org_id = ?", claims.UserID, orgID).
Pluck("roles.code", &roleCodes)
x.Set("org_roles", roleCodes)
return nil return nil
} }

@ -41,7 +41,6 @@ type OrgMember struct {
vigo.Model vigo.Model
OrgID string `json:"org_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"` OrgID string `json:"org_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"`
UserID string `json:"user_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"` UserID string `json:"user_id" gorm:"type:varchar(36);uniqueIndex:idx_org_user;not null"`
RoleIDs string `json:"role_ids" gorm:"size:200"` // 逗号分隔
Position string `json:"position" gorm:"size:50"` Position string `json:"position" gorm:"size:50"`
Department string `json:"department" gorm:"size:50"` Department string `json:"department" gorm:"size:50"`
JoinedAt string `json:"joined_at"` JoinedAt string `json:"joined_at"`

@ -17,6 +17,7 @@
"common.loading": "Loading...", "common.loading": "Loading...",
"common.not_found": "Not Found", "common.not_found": "Not Found",
"common.save": "Save", "common.save": "Save",
"common.status": "Status",
"nav.dashboard": "Dashboard", "nav.dashboard": "Dashboard",
"nav.home": "Home", "nav.home": "Home",
"nav.oauth": "OAuth Apps", "nav.oauth": "OAuth Apps",
@ -24,6 +25,7 @@
"nav.roles": "Roles", "nav.roles": "Roles",
"nav.profile": "Profile", "nav.profile": "Profile",
"nav.users": "Users", "nav.users": "Users",
"org.code": "Code",
"org.create": "Create Organization", "org.create": "Create Organization",
"org.create_first": "Create Organization", "org.create_first": "Create Organization",
"org.created": "Created successfully", "org.created": "Created successfully",
@ -36,6 +38,8 @@
"org.edit": "Edit Organization", "org.edit": "Edit Organization",
"org.feature_coming": "Feature coming soon", "org.feature_coming": "Feature coming soon",
"org.info": "Information", "org.info": "Information",
"org.joined_at": "Joined At",
"org.max_members": "Max Members",
"org.member_removed": "Member removed", "org.member_removed": "Member removed",
"org.members": "Members", "org.members": "Members",
"org.name": "Organization Name", "org.name": "Organization Name",
@ -81,6 +85,7 @@
"common.loading": "加载中...", "common.loading": "加载中...",
"common.not_found": "页面未找到", "common.not_found": "页面未找到",
"common.save": "保存", "common.save": "保存",
"common.status": "状态",
"nav.dashboard": "仪表盘", "nav.dashboard": "仪表盘",
"nav.home": "首页", "nav.home": "首页",
"nav.oauth": "OAuth应用", "nav.oauth": "OAuth应用",
@ -88,6 +93,7 @@
"nav.roles": "角色管理", "nav.roles": "角色管理",
"nav.profile": "个人中心", "nav.profile": "个人中心",
"nav.users": "用户管理", "nav.users": "用户管理",
"org.code": "组织代码",
"org.create": "创建组织", "org.create": "创建组织",
"org.create_first": "创建第一个组织", "org.create_first": "创建第一个组织",
"org.created": "创建成功", "org.created": "创建成功",
@ -100,6 +106,8 @@
"org.edit": "编辑组织", "org.edit": "编辑组织",
"org.feature_coming": "功能即将推出", "org.feature_coming": "功能即将推出",
"org.info": "基本信息", "org.info": "基本信息",
"org.joined_at": "加入时间",
"org.max_members": "成员上限",
"org.member_removed": "成员已移除", "org.member_removed": "成员已移除",
"org.members": "成员列表", "org.members": "成员列表",
"org.name": "组织名称", "org.name": "组织名称",

@ -182,6 +182,24 @@
color: var(--text-color-secondary); color: var(--text-color-secondary);
} }
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-full);
font-size: var(--font-size-xs);
font-weight: 500;
}
.status-active {
background-color: color-mix(in srgb, var(--color-success), transparent 85%);
color: var(--color-success);
}
.status-inactive {
background-color: color-mix(in srgb, var(--text-color-disabled), transparent 85%);
color: var(--text-color-secondary);
}
.loading-state { .loading-state {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -267,6 +285,18 @@
<span class="info-label">ID</span> <span class="info-label">ID</span>
<span class="info-value">{{ org.id }}</span> <span class="info-value">{{ org.id }}</span>
</div> </div>
<div class="info-item">
<span class="info-label">{{ $t('org.code') }}</span>
<span class="info-value">{{ org.code }}</span>
</div>
<div class="info-item">
<span class="info-label">{{ $t('common.status') }}</span>
<span class="info-value">
<span class="status-badge" :class="org.status === 1 ? 'status-active' : 'status-inactive'">
{{ org.status === 1 ? 'Active' : 'Inactive' }}
</span>
</span>
</div>
<div class="info-item"> <div class="info-item">
<span class="info-label">{{ $t('org.created_at') }}</span> <span class="info-label">{{ $t('org.created_at') }}</span>
<span class="info-value">{{ formatDate(org.created_at) }}</span> <span class="info-value">{{ formatDate(org.created_at) }}</span>
@ -299,6 +329,8 @@
<th>{{ $t('user.username') }}</th> <th>{{ $t('user.username') }}</th>
<th>{{ $t('user.email') }}</th> <th>{{ $t('user.email') }}</th>
<th>{{ $t('user.role') }}</th> <th>{{ $t('user.role') }}</th>
<th>{{ $t('common.status') }}</th>
<th>{{ $t('org.joined_at') }}</th>
<th>{{ $t('common.actions') }}</th> <th>{{ $t('common.actions') }}</th>
</tr> </tr>
</thead> </thead>
@ -307,10 +339,16 @@
<td>{{ member.username }}</td> <td>{{ member.username }}</td>
<td>{{ member.email || '-' }}</td> <td>{{ member.email || '-' }}</td>
<td> <td>
<span class="role-badge" :class="member.role === 'admin' ? 'role-admin' : 'role-member'"> <span class="role-badge" :class="member.role_ids === 'admin' ? 'role-admin' : 'role-member'">
{{ member.role || 'member' }} {{ member.role_ids || 'member' }}
</span>
</td>
<td>
<span class="status-badge" :class="member.status === 1 ? 'status-active' : 'status-inactive'">
{{ member.status === 1 ? 'Active' : 'Inactive' }}
</span> </span>
</td> </td>
<td>{{ formatDate(member.joined_at) }}</td>
<td> <td>
<v-btn size="sm" color="danger" variant="outline" :click="() => removeMember(member)" <v-btn size="sm" color="danger" variant="outline" :click="() => removeMember(member)"
v-if="member.id !== currentUserId"> v-if="member.id !== currentUserId">
@ -347,6 +385,7 @@
orgId = $router.params.id; orgId = $router.params.id;
org = null; org = null;
members = []; members = [];
totalMembers = 0;
loading = false; loading = false;
currentUserId = $env.$vbase.user?.id; currentUserId = $env.$vbase.user?.id;
@ -366,6 +405,7 @@
]); ]);
org = orgRes; org = orgRes;
members = membersRes.items || []; members = membersRes.items || [];
totalMembers = membersRes.total || 0;
} catch (e) { } catch (e) {
$message.error(e.message); $message.error(e.message);
if (e.status === 404) { if (e.status === 404) {

Loading…
Cancel
Save