# 旧版前端实现分析

## User详情页权限控制机制

### 1. 核心概念

#### 1.1 Permlevel (权限级别)
- 字段级权限控制机制，取值范围 0-9
- Level 0: 文档级权限，所有用户必须有 Level 0 权限才能访问文档
- Level 1+: 字段级权限，用于隐藏或保护特定字段

#### 1.2 User PageType 权限定义
在 `user.json` 中，关键字段的权限级别设置：
```json
{
  "fieldname": "sb1",           // Roles section
  "permlevel": 1,
  "read_only": 1
},
{
  "fieldname": "role_profile_name",
  "permlevel": 1
},
{
  "fieldname": "roles",         // Has Role table
  "permlevel": 1,
  "hidden": 1,
  "read_only": 1
},
{
  "fieldname": "roles_html",    // 角色编辑器挂载点
  "permlevel": 0                // 无权限限制，由js控制显示
}
```

### 2. 旧版前端实现链条

#### 2.1 权限检查入口 (`user.js`)

```javascript
// Line 79: 检查用户是否有编辑用户的权限
frm.can_edit_roles = has_access_to_edit_user();

// Line 417-428: 权限检查函数
function has_access_to_edit_user() {
  return has_common(jingrow.user_roles, get_roles_for_editing_user());
}

function get_roles_for_editing_user() {
  return (
    jingrow
      .get_meta("User")
      .permissions.filter((perm) => perm.permlevel >= 1 && perm.write)
      .map((perm) => perm.role) || ["System Manager"]
  );
}
```

**关键逻辑**：
- 只有拥有 `permlevel >= 1` 且有 `write` 权限的角色才能编辑角色字段
- 通常是 System Manager / Administrator

#### 2.2 角色编辑器初始化 (`user.js` Line 86-108)

```javascript
if (
  frm.can_edit_roles &&
  !frm.is_new() &&
  ["System User", "Website User"].includes(frm.pg.user_type)
) {
  if (!frm.roles_editor) {
    const role_area = $('<div class="role-editor">').appendTo(
      frm.fields_dict.roles_html.wrapper
    );

    frm.roles_editor = new jingrow.RoleEditor(
      role_area,
      frm,
      frm.pg.role_profile_name ? 1 : 0  // 是否禁用
    );

    if (frm.pg.user_type == "System User") {
      var module_area = $("<div>").appendTo(frm.fields_dict.modules_html.wrapper);
      frm.module_editor = new jingrow.ModuleEditor(frm, module_area);
    }
  } else {
    frm.roles_editor.show();
  }
}
```

**关键逻辑**：
- 只有当 `frm.can_edit_roles = true` 时才创建角色编辑器
- 角色编辑器挂载到 `roles_html` 字段（permlevel 0，所有人可见）
- 但编辑器内部会根据权限控制是否可编辑

#### 2.3 角色编辑器实现 (`roles_editor.js`)

```javascript
class jingrow.RoleEditor {
  constructor(wrapper, frm, disable) {
    this.frm = frm;
    this.wrapper = wrapper;
    this.disable = disable;  // 禁用标志
    
    // 创建 MultiCheck 控件
    this.multicheck = jingrow.ui.form.make_control({
      parent: wrapper,
      df: {
        fieldname: "roles",
        fieldtype: "MultiCheck",
        select_all: true,
        columns: "15rem",
        get_data: () => {
          return jingrow.xcall("jingrow.core.pagetype.user.user.get_all_roles")
            .then((roles) => {
              return roles.map((role) => {
                return {
                  label: __(role),
                  value: role,
                  checked: user_roles.includes(role),
                };
              });
            });
        },
        on_change: () => {
          this.set_roles_in_table();
          this.frm.dirty();
        },
      },
      render_input: true,
    });
  }

  // Line 45-49: 根据 disable 标志启用/禁用复选框
  set_enable_disable() {
    $(this.wrapper)
      .find('input[type="checkbox"]')
      .attr("disabled", this.disable ? true : false);
  }

  show() {
    this.reset();
    this.set_enable_disable();  // 每次显示时检查是否禁用
  }
}
```

#### 2.4 自身编辑特殊逻辑 (`user.py`)

```python
# Line 260-262: 网站权限检查
def has_website_permission(self, ptype, user, verbose=False):
    """Returns true if current user is the session user"""
    return self.name == jingrow.session.user
```

**关键逻辑**：
- User PageType 重写了 `has_website_permission` 方法
- 当前用户可以编辑自己的 User 记录（基本信息）
- 但 permlevel 1+ 字段仍然受权限控制保护

#### 2.5 后端权限验证 (`page.py`)

```python
# Line 710-718: 获取用户可访问的权限级别
def get_permlevel_access(self, permission_type="write"):
    allowed_permlevels = []
    roles = jingrow.get_roles()

    for perm in self.get_permissions():
        if perm.role in roles and perm.get(permission_type) and perm.permlevel not in allowed_permlevels:
            allowed_permlevels.append(perm.permlevel)

    return allowed_permlevels

# Line 720-724: 检查是否有权限访问特定字段
def has_permlevel_access_to(self, fieldname, df=None, permission_type="read"):
    if not df:
        df = self.meta.get_field(fieldname)

    return df.permlevel in self.get_permlevel_access(permission_type)
```

#### 2.6 字段可见性控制 (`perm.js`)

```javascript
// Line 200-249: 获取字段状态（Read/Write/None）
jingrow.perm.get_field_display_status = function (df, perm, pg, explain) {
  if (!df.permlevel) df.permlevel = 0;
  let p = perm[df.permlevel];
  let status = "None";

  // permission
  if (p) {
    if (p.write && !df.disabled && !df.is_virtual) {
      status = "Write";
    } else if (p.read) {
      status = "Read";
    }
  }
  if (explain) console.log("By Permission:" + status);

  // hidden
  if (cint(df.hidden)) status = "None";
  if (explain) console.log("By Hidden:" + status);

  // hidden due to dependency
  if (cint(df.hidden_due_to_dependency)) status = "None";
  
  return status;
}
```

### 3. 完整执行链条总结

#### 3.1 普通用户编辑自己的账号

1. **页面加载**
   - `user.js` 检查权限：`has_access_to_edit_user()` 返回 `false`（普通用户无 permlevel 1 权限）
   - `frm.can_edit_roles = false`

2. **字段可见性**
   - Basic Info 字段（permlevel 0）：可见且可编辑
   - Roles section（sb1, permlevel 1）：不可见（因为当前用户角色没有 permlevel 1 的 read 权限）
   - `roles_html`（permlevel 0）：可见，但因为 `frm.can_edit_roles = false`，不会挂载角色编辑器

3. **编辑权限**
   - `has_website_permission()` 返回 `true`（检查 `jingrow.session.user == self.name`）
   - 可以编辑 permlevel 0 的基本信息字段
   - 无法编辑 permlevel 1+ 的字段（后端会重置这些字段）

4. **保存验证**
   - `page.py` 的 `reset_values_if_no_permlevel_access()` 会重置用户无权访问的高权限字段
   - 即使前端绕过检查修改了 permlevel 1 字段，后端也会忽略

#### 3.2 系统管理员编辑用户

1. **页面加载**
   - `has_access_to_edit_user()` 返回 `true`（System Manager 有 permlevel 1 write 权限）
   - `frm.can_edit_roles = true`

2. **字段可见性**
   - 所有字段可见（包括 permlevel 1+ 字段）
   - `roles_html` 可见，且会挂载角色编辑器

3. **角色编辑器**
   - `RoleEditor` 初始化，`disable = false`
   - 可以勾选/取消勾选角色
   - `set_roles_in_table()` 同步到 `roles` 子表

4. **保存验证**
   - 有 permlevel 1 write 权限，可以修改所有字段
   - 后端不会重置高权限字段

### 4. 关键安全机制

1. **前端多层检查**
   - 权限检查：`has_access_to_edit_user()` 
   - 条件渲染：只有 `frm.can_edit_roles = true` 才创建编辑器
   - 禁用控制：`set_enable_disable()` 根据权限禁用复选框

2. **后端强制验证**
   - `get_permlevel_access()` 计算用户可访问的权限级别
   - `reset_values_if_no_permlevel_access()` 重置无权字段
   - `has_website_permission()` 允许用户编辑自己的记录

3. **Permlevel 机制**
   - Level 0: 基础文档权限，所有用户必须有
   - Level 1+: 字段级权限，额外保护敏感字段
   - 必须先有 Level 0 权限，才能有 Level 1+ 权限意义

### 5. 新版前端实现要点

#### 5.1 需要实现的功能

1. **权限判断**
   - 获取当前用户角色
   - 判断是否有 permlevel 1+ write 权限
   - 判断是否编辑自己的账号

2. **字段可见性控制**
   - 检查字段的 `permlevel` 属性
   - 根据用户权限决定是否显示字段
   - permlevel 0: 所有人可见
   - permlevel 1+: 只有有权限的用户可见

3. **编辑权限控制**
   - 基本信息（permlevel 0）：自己或管理员可编辑
   - 角色权限（permlevel 1）：只有管理员可编辑

#### 5.2 实现方案

1. **扩展权限 Store**
   ```typescript
   // 添加方法检查用户是否有特定 permlevel 的权限
   function hasPermlevelAccess(pagetype: string, permlevel: number, permissionType: 'read' | 'write'): boolean
   ```

2. **扩展 shouldShowField**
   ```typescript
   function shouldShowField(field: any) {
     // 现有检查：hidden, depends_on
     
     // 新增：permlevel 检查
     if (field.permlevel && field.permlevel > 0) {
       return permissionStore.hasPermlevelAccess(entity.value, field.permlevel, 'read')
     }
     
     return true
   }
   ```

3. **自身编辑特殊处理**
   ```typescript
   // 判断是否编辑自己的账号
   const isEditingSelf = computed(() => {
     return authStore.user?.name === id.value
   })
   
   // canEdit 逻辑
   const canEdit = computed(() => {
     if (isEditingSelf.value) {
       // 编辑自己：可以编辑 permlevel 0 字段
       return true
     }
     // 编辑他人：需要 write 权限
     return permissionStore.canWrite(entity.value)
   })
   ```

4. **HTML 控件权限控制**
   ```typescript
   // roles_html / modules_html 的显示逻辑
   const actualCanEdit = computed(() => {
     if (isRolesHtml.value || isModulesHtml.value) {
       // 需要 permlevel 1 write 权限
       return permissionStore.hasPermlevelAccess('User', 1, 'write')
     }
     return props.canEdit
   })
   ```

### 6. 最佳实践

1. **分层权限控制**
   - 前端：提供友好的用户体验，隐藏用户无权访问的字段
   - 后端：强制验证，确保数据安全

2. **权限检查顺序**
   ```
   1. Administrator → 全部权限
   2. has_website_permission → 自身编辑权限
   3. permlevel access → 字段级权限
   4. user permissions → 记录级过滤
   ```

3. **代码组织**
   - 权限逻辑集中在 Store
   - 组件只负责调用和渲染
   - 避免权限逻辑分散在多个组件

4. **性能优化**
   - 权限数据在登录时加载并缓存
   - 避免每个字段都发起权限检查请求
   - 使用 computed 缓存权限判断结果
