feat(M5): 前端完善 + 部署 — Vue3 全量页面、仪表盘统计与我的审计接口、systemd/sudoers/迁移脚本、部署文档

This commit is contained in:
2026-08-30 10:50:46 +08:00
parent c0e2ff975a
commit b305d7b371
67 changed files with 3209 additions and 171 deletions
+58 -4
View File
@@ -77,10 +77,11 @@ func (s *UserService) GetByID(ctx context.Context, id uint) (*model.User, error)
// UserFilter 用户列表筛选条件。
type UserFilter struct {
Status string // active / disabled / expired,空为全部
Supervisor string // 挂靠老师模糊匹配
Page int
PageSize int
Status string // active / disabled / expired,空为全部
Supervisor string // 挂靠老师模糊匹配
ExpiresBefore *time.Time // 仅返回 expire_at 早于该时刻的用户(仪表盘"近期待过期"用)
Page int
PageSize int
}
// List 分页查询用户(admin)。
@@ -92,6 +93,9 @@ func (s *UserService) List(ctx context.Context, f UserFilter) ([]model.User, int
if f.Supervisor != "" {
q = q.Where("supervisor LIKE ?", "%"+f.Supervisor+"%")
}
if f.ExpiresBefore != nil {
q = q.Where("expire_at IS NOT NULL AND expire_at < ?", f.ExpiresBefore)
}
var total int64
if err := q.Count(&total).Error; err != nil {
return nil, 0, err
@@ -113,6 +117,56 @@ func (s *UserService) List(ctx context.Context, f UserFilter) ([]model.User, int
return users, total, nil
}
// UserStats 仪表盘统计(PLAN §8):各状态用户数、近 30 天待过期用户、待审批申请数。
type UserStats struct {
Total int64 `json:"total"`
Active int64 `json:"active"`
Disabled int64 `json:"disabled"`
Expired int64 `json:"expired"`
ExpiringSoon []model.User `json:"expiring_soon"` // 30 天内到期且仍 active
PendingApprovals int64 `json:"pending_approvals"`
}
// Stats 汇总仪表盘统计。
func (s *UserService) Stats(ctx context.Context) (*UserStats, error) {
st := &UserStats{}
q := s.db.WithContext(ctx).Model(&model.User{})
if err := q.Count(&st.Total).Error; err != nil {
return nil, err
}
byStatus := map[string]int64{}
var rows []struct {
Status string
C int64
}
if err := s.db.WithContext(ctx).Model(&model.User{}).Select("status, count(*) as c").Group("status").Scan(&rows).Error; err != nil {
return nil, err
}
for _, r := range rows {
byStatus[r.Status] = r.C
}
st.Active = byStatus[model.UserStatusActive]
st.Disabled = byStatus[model.UserStatusDisabled]
st.Expired = byStatus[model.UserStatusExpired]
// 近 30 天待过期(active 且 expire_at 在 30 天内)
window := time.Now().Add(30 * 24 * time.Hour)
var soon []model.User
if err := s.db.WithContext(ctx).Model(&model.User{}).
Where("status = ? AND expire_at IS NOT NULL AND expire_at < ?", model.UserStatusActive, window).
Order("expire_at ASC").Limit(10).Find(&soon).Error; err != nil {
return nil, err
}
st.ExpiringSoon = soon
var pending int64
if err := s.db.WithContext(ctx).Model(&model.Approval{}).Where("status = ?", model.StatusPending).Count(&pending).Error; err != nil {
return nil, err
}
st.PendingApprovals = pending
return st, nil
}
// Create 创建外部用户:DB 记录 + 系统账号(useradd + passwd -l)。
// username 不含前缀;ttl 为有效期时长,0 表示用配置默认(90 天)。
// 系统建号失败时回滚 DB 记录,保证两侧一致。