package controllers import ( "strconv" "strings" "server/models" "github.com/beego/beego/v2/client/orm" "server/pkg/identity" ) // 移动端通讯录(企业内部通讯录)只读接口。 // // 复用 AppOaController 的鉴权与响应封装(oaClaims / oaErr / oaOk): // 鉴权放宽到 app / platform 令牌,数据一律按 JWT 中的 TenantId 隔离,前端无法指定租户。 // 写入能力(同步员工 / 新增 / 编辑 / 删除)仍留在 Web 管理端。 // appContactDTO 通讯录条目 DTO(移动端展示字段)。 func appContactDTO(row models.BackendErpContact, orgName string) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "org_id": contactDerefUint64(row.OrgID), "org_name": orgName, "contact_type": row.ContactType, "contact_name": row.ContactName, "gender": row.Gender, "phone": contactDerefString(row.Phone), "mobiles": contactDerefString(row.Mobiles), "work_phone": contactDerefString(row.WorkPhone), "email": contactDerefString(row.Email), "wechat": contactDerefString(row.Wechat), "qq": contactDerefString(row.QQ), "avatar": contactDerefString(row.Avatar), "company_name": contactDerefString(row.CompanyName), "dept_name": contactDerefString(row.DeptName), "position_title": contactDerefString(row.PositionTitle), "address": contactDerefString(row.Address), "remark": contactDerefString(row.Remark), "is_starred": row.IsStarred, "status": row.Status, } } // appContactOrgNames 批量查询组织 ID → 名称映射,避免逐行查询。 func appContactOrgNames(ids []uint64) map[uint64]string { names := make(map[uint64]string) if len(ids) == 0 { return names } var orgs []models.BackendOrganization if _, err := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("id__in", ids).All(&orgs); err == nil { for _, o := range orgs { names[o.ID] = o.OrgName } } return names } // ContactList GET /app/oa/contact/list 通讯录分页列表。 // 支持 keyword(姓名/手机号/邮箱/职位/部门)、org_id(按组织筛选)、contact_type(1 内部 2 外部)。 func (c *AppOaController) ContactList() { claims, err := c.oaClaims() if err != nil { code := identity.Status(err) c.oaErr(code, code, err.Error()) return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 20) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } keyword := strings.TrimSpace(c.GetString("keyword")) orgID, _ := c.GetUint64("org_id") contactType, _ := c.GetInt("contact_type") qs := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("tid", claims.TenantId) if orgID > 0 { qs = qs.Filter("org_id", orgID) } if contactType > 0 { qs = qs.Filter("contact_type", contactType) } if keyword != "" { cond := orm.NewCondition() cond = cond.Or("contact_name__contains", keyword) cond = cond.Or("phone__contains", keyword) cond = cond.Or("mobiles__contains", keyword) cond = cond.Or("email__contains", keyword) cond = cond.Or("position_title__contains", keyword) cond = cond.Or("dept_name__contains", keyword) // 用 GetCond().AndCond 追加,避免 SetCond 覆盖掉租户等前置过滤条件 qs = qs.SetCond(qs.GetCond().AndCond(cond)) } total, _ := qs.Count() var rows []models.BackendErpContact if _, err := qs.OrderBy("-is_starred", "contact_name", "-id"). Limit(pageSize).Offset((page - 1) * pageSize).All(&rows); err != nil && err != orm.ErrNoRows { c.oaErr(500, 500, "查询通讯录失败") return } ids := make([]uint64, 0, len(rows)) for _, r := range rows { if r.OrgID != nil && *r.OrgID > 0 { ids = append(ids, *r.OrgID) } } names := appContactOrgNames(ids) list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { orgName := "" if r.OrgID != nil { orgName = names[*r.OrgID] } list = append(list, appContactDTO(r, orgName)) } c.oaOk(map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize}) } // ContactOrgTree GET /app/oa/contact/org-tree 组织架构树(含各部门内部联系人数量)。 func (c *AppOaController) ContactOrgTree() { claims, err := c.oaClaims() if err != nil { code := identity.Status(err) c.oaErr(code, code, err.Error()) return } var orgs []models.BackendOrganization if _, err := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("tid", claims.TenantId). OrderBy("sort", "id").All(&orgs); err != nil && err != orm.ErrNoRows { c.oaErr(500, 500, "查询组织架构失败") return } counts := make(map[uint64]int64) for _, org := range orgs { cnt, _ := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("org_id", org.ID). Filter("tid", claims.TenantId). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("contact_type", 1).Count() counts[org.ID] = cnt } total, _ := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("tid", claims.TenantId). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("contact_type", 1).Count() tree := (&BackendErpContactController{}).buildContactOrgTree(orgs, counts, uint64(total)) c.oaOk(tree) } // ContactDetail GET /app/oa/contact/:id 联系人详情。 func (c *AppOaController) ContactDetail() { claims, err := c.oaClaims() if err != nil { code := identity.Status(err) c.oaErr(code, code, err.Error()) return } id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if id == 0 { c.oaErr(400, 400, "无效的联系人ID") return } var row models.BackendErpContact if models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", id). Filter("tid", claims.TenantId). Filter("delete_time__isnull", true). Exclude("status", 0).One(&row) != nil { c.oaErr(404, 404, "联系人不存在") return } orgName := "" if row.OrgID != nil && *row.OrgID > 0 { var org models.BackendOrganization if models.Orm.QueryTable(new(models.BackendOrganization)). Filter("id", *row.OrgID).One(&org) == nil { orgName = org.OrgName } } c.oaOk(appContactDTO(row, orgName)) }